Gavin Wood, 6 de septiembre de 2021. “XCM: The Cross Consensus Message Format”
A medida que se acerca el lanzamiento final de Polkadot 1.0, completo con Parachains, el formato de Cross Consensus Messaging, XCM para abreviar, se acerca a su primer lanzamiento listo para la producción. Esta es una introducción al formato, sus objetivos, cómo su funcionamiento puede ser usado para lograr tareas típicas entre cadenas.
Un dato divertido para empezar … XCM es el formato de mensajería de “consenso cruzado”, en lugar de simplemente “cadena cruzada”. Esta diferencia es una señal de los objetivos del formato que está diseñado para comunicar el tipo de ideas enviadas no solo entre cadenas, sino también entre contratos inteligentes (smart contracts) y paletas (pallets), y sobre puentes (bridges) y enclaves fragmentados (sharded) como Polkadot’s Spree.
🤟 Un formato, no un Protocolo
Para comprender mejor XCM, es importante comprender sus límites y dónde encaja en la pila de tecnología de Polkadot. XCM es un formato de mensajería. No es un protocolo de mensajería. No se puede utilizar para “enviar” ningún mensaje entre sistemas; su utilidad es sólo expresar lo que debe hacer el receptor.
Sin incluir los puentes y la paleta de contratos, Polkadot viene con tres sistemas distintos para comunicar mensajes XCM entre sus cadenas constituyentes: UMP, DMP y XCMP. UMP (Upward Message Passing) permite a las parachains enviar mensajes a su Relay Chain. DMP (Downward Message Passing) permite a la Relay Chain pasar mensajes a una de sus parachains. XCMP, es quizás el más conocido de ellos, y esto permite que las parachains se envíen mensajes entre ellas. XCM se puede utilizar para expresar el significado de los mensajes en cada uno de estos tres canales de comunicación.
Además de enviar mensajes entre cadenas, XCM también es útil en otros contextos, para realizar transacciones con una cadena cuyo formato de transacción no necesariamente conoces bien de antemano. Con cadenas cuya lógica de negocios cambia poco (por ejemplo, Bitcoin), el formato de transacción, o el formato utilizado por las billeteras para enviar instrucciones a la cadena, tiende a permanecer exactamente igual, o al menos compatible, indefinidamente. Con cadenas basadas en metaprotocolos altamente evolucionables como Polkadot y sus parachains constituyentes, la lógica empresarial se puede actualizar a través de la red con una sola transacción. Esto puede cambiar cualquier cosa, incluido el formato de la transacción, lo que presenta un problema potencial para los tenedores de billeteras, especialmente para las billeteras que deben mantenerse fuera de línea (como Parity Signer). Dado que XCM está bien versionado, es abstracto y general, se puede usar como un medio para proporcionar un formato de transacción duradero para que las billeteras lo utilicen para crear muchas transacciones comunes.
🥅 Objetivos
XCM pretende ser un lenguaje que comunique ideas entre sistemas de consenso. Debe ser lo suficientemente general como para que sea útil en todo un ecosistema en crecimiento. Debe ser extensible. Dado que la extensibilidad inevitablemente implicará un cambio, también debe ser compatible con el futuro y con versiones posteriores. Finalmente, debe ser lo suficientemente eficiente como para funcionar en cadena y posiblemente en un entorno medido.
Como todos los idiomas, algunas personas tenderán a utilizar algunos elementos más que otros. XCM no está diseñado de tal manera que se espere que todos los sistemas que admiten XCM puedan interpretar cualquier posible mensaje XCM. Algunos mensajes no tendrán interpretaciones razonables en algunos sistemas. Otros pueden ser razonables, pero aún así no ser admitidos por el intérprete intencionalmente debido a limitaciones de recursos o porque el mismo contenido puede expresarse de una manera más clara y canónica. Los sistemas inevitablemente solo admitirán un subconjunto de posibles mensajes. Los sistemas con grandes limitaciones de recursos (como los contratos inteligentes) pueden admitir solo un “dialecto” muy limitado.
Esta generalidad se extiende incluso a conceptos como el pago de tarifas por ejecutar el mensaje XCM. Dado que sabemos que XCM puede usarse en diversos sistemas, incluida una plataforma de contrato inteligente con medidor de gas y parachains de la comunidad hasta interacciones confiables entre las parachains del sistema y su Relay chain, no queremos integrar en elementos como el pago de tarifas demasiado profundo y irreversible en el protocolo.
😬 ¿Por qué no utilizar simplemente el formato de mensaje nativo?
Aprovechar el formato nativo de mensaje / transacción de una cadena o contrato inteligente puede ser útil en ciertas circunstancias, pero tiene algunos inconvenientes importantes que lo hacen menos útil para los objetivos de XCM. En primer lugar, existe una falta de compatibilidad entre las cadenas, por lo que un sistema que pretenda enviar mensajes a más de un destino necesitaría comprender cómo crear un mensaje para cada uno. En esa nota, incluso un solo destino puede alterar su formato nativo de transacción / mensaje con el tiempo. Los contratos inteligentes pueden obtener actualizaciones, las cadenas de bloques pueden introducir nuevas funciones o alterar las existentes y, al hacerlo, cambiar su formato de transacción.
En segundo lugar, los casos de uso comunes en cadenas no encajan fácilmente en una sola transacción; es posible que se requieran trucos especiales para retirar fondos, intercambiarlos y luego depositar el resultado, todo dentro de una sola transacción. Las notificaciones posteriores de transferencias, necesarias para un marco coherente de activos de reserva, no existen en cadenas que desconozcan otras.
En tercer lugar, operaciones como el pago de tarifas no encajan fácilmente en un modelo que asume que el pago de tarifas ya se ha negociado como mensajes de contrato inteligente. Los sobres de transacciones, en comparación, proporcionan algún sistema para el pago del procesamiento, pero también están generalmente diseñados para contener una firma que no es algo que tenga sentido cuando se comunican entre sistemas de consenso.
🎬 Algunos casos de uso iniciales
Si bien el objetivo de XCM es ser general, flexible y preparado para el futuro, existen, por supuesto, necesidades prácticas que debe abordar, entre ellas la transferencia de tokens entre cadenas. El pago opcional de tarifas (quizás usando esos tokens) es otro, como es una interfaz general para llevar a cabo un servicio de intercambio, común en todo el mundo De-fi. Por último, debería ser posible utilizar el lenguaje XCM para realizar alguna acción específica de la plataforma; por ejemplo, dentro de una cadena de Substrate, puede ser deseable enviar una llamada remota a uno de sus pallets (paletas) para acceder a una función de nicho.
Además de eso, hay muchos modelos para transferir tokens que nos gustaría admitir: Podríamos simplemente querer controlar una cuenta en una cadena remota, permitiendo que la cadena local tenga una dirección en la cadena remota para recibir fondos y eventualmente transferir esos fondos que controla a otras cuentas en esa cadena remota.
Podríamos tener dos sistemas de consenso, los cuales son hogares nativos para un token en particular. Imagina un token como USDT o USDC, que tienen instancias, todas perfectamente fungibles, en varias cadenas diferentes. Debería ser posible quemar un token de este tipo en una cadena y acuñar su token correspondiente en otra cadena soportada. En el lenguaje de XCM, lo llamamos teleporting (teletransportación) debido a la idea de que el movimiento aparente de un activo de hecho ocurre destruyéndolo en un lado y creando un clon en el otro lado.
Por último, puede haber dos cadenas que deseen nominar una tercera cadena, una en la que un activo podría considerarse nativo, para utilizarlo como reserva para ese activo. La forma derivada del activo en cada una de esas cadenas estaría totalmente respaldada, lo que permitiría intercambiar el activo derivado por el activo subyacente en la cadena de reserva que lo respalda. Este podría ser el caso en el que las dos cadenas no necesariamente confían entre sí, pero (al menos en lo que respecta al activo en cuestión) están dispuestas a confiar en la cadena nativa del activo. Un ejemplo aquí sería donde tenemos varias parachains de la comunidad a las que les gustaría enviarse DOT entre sí. Cada uno tiene una forma local de DOT que está completamente respaldada por DOT controlada por la parachain en la cadena Statemint (un centro nativo para DOT). Cuando la forma local de DOT se envía entre las cadenas, en segundo plano, el DOT “real” se mueve entre cuentas parachain en Statemint.
Incluso este nivel de funcionalidad aparentemente modesto tiene un número relativamente grande de configuraciones cuyo uso podría ser deseable y requiere un diseño interesante para evitar el sobreajuste.
La Anatomía de XCM
En el núcleo del formato XCM se encuentra el XCVM. Al contrario de lo que podría parecer para algunos, este no es un (válido) número romano (aunque si lo fuera, probablemente significaría 905). De hecho, esto significa Cross-Consensus Virtual Machine (Máquina Virtual de Consenso Cruzado). Es una computadora de nivel ultra alto no Turing completa cuyas instrucciones están diseñadas para estar aproximadamente al mismo nivel que las transacciones.
Un “mensaje” en XCM es en realidad solo un programa que se ejecuta en el XCVM. Es una o más instrucciones XCM. El programa se ejecuta hasta que corre (run) hasta el final o llega a un error, momento en el que termina (lo dejo intencionalmente sin explicar por ahora) y se detiene.
El XCVM incluye varios registros, así como acceso al estado general del sistema de consenso que lo aloja. Las instrucciones pueden cambiar un registro, pueden cambiar el estado del sistema de consenso o ambos.
Un ejemplo de dicha instrucción sería TransferAsset
que se utiliza para transferir un activo a alguna otra dirección en el sistema remoto. Es necesario que se le diga qué activo (s) transferir y a quién / dónde se transferirá el activo. En Rust, se declara así:
Como puedes adivinar, assets
es el parámetro que expresa qué activos se van a transferir y los estados del beneficiary
a quién / dónde se van a poner. Por supuesto, nos falta otra información, a saber, de quién / dónde se tomarán los activos. Esto se infiere automáticamente del Origin Register. Cuando comienza el programa, este registro generalmente se establece de acuerdo con el sistema de transporte (el puente, XCMP o lo que sea) para reflejar de dónde vino realmente el mensaje, y es el mismo tipo de información que el beneficiary
. El Origin Register (registro de origen) funciona como un registro protegido: el programa no puede configurarlo arbitrariamente, aunque hay dos instrucciones que pueden utilizarse para modificarlo de determinadas formas.
Los tipos utilizados son ideas bastante fundamentales en XCM: activos, representados por MultiAsset
y ubicaciones dentro del consenso, representadas por MultiLocation
. El Origin Register es una MultiLocation
opcional (opcional, porque se puede borrar por completo si se desea).
📍 Ubicaciones en XCM
El tipoMultiLocation
identifica cualquier ubicación única que existe dentro del mundo del consenso. Es una idea bastante abstracta y puede representar todo tipo de cosas que existen dentro del consenso, desde una blockchain escalable de múltiples fragmentos (scalable multi-shard blockchain) como Polkadot hasta una pequeña cuenta de activos ERC-20 en una parachain. En términos de informática, en realidad es solo una estructura de datos global única, independientemente de su tamaño o complejidad.
MultiLocation
siempre expresa una ubicación relativa a la ubicación actual. Puedes pensar en ello un poco como una ruta del sistema de archivos, pero donde no hay forma de expresar directamente el “root” (raíz) del árbol del sistema de archivos. Esto es por una simple razón: en el mundo de Polkadot, las blockchains se pueden fusionar y separar de otras blockchains. Una blockchain puede comenzar su vida por sí sola y, eventualmente, elevarse para convertirse en una parachain dentro de un consenso más amplio. Si hiciera eso, entonces el significado de “root” cambiaría de la noche a la mañana y esto podría significar un caos para los mensajes XCM y cualquier otra cosa que use MultiLocation
. Para simplificar las cosas, excluimos esta posibilidad por completo.
Las ubicaciones en XCM son jerárquicas; algunos lugares en el consenso están totalmente encapsulados dentro de otros lugares en el consenso. Una parachain de Polkadot existe completamente dentro del consenso general de Polkadot y lo llamamos una ubicación interior. Dicho de manera más estricta, podemos decir que siempre que haya un sistema de consenso cualquier cambio en el que implique un cambio en otro sistema de consenso, entonces el primer sistema es interior al segundo. Por ejemplo, un contrato inteligente de Canvas es interior a la paleta de contratos que lo aloja. Un UTXO en Bitcoin es interior a la blockchain de Bitcoin.
Esto significa que XCM no distingue entre las dos preguntas “¿quién?” ¿y donde?”. Desde el punto de vista de algo bastante abstracto como XCM, la diferencia no es realmente importante: los dos se difuminan y se vuelven esencialmente lo mismo.
MultiLocation
s se utilizan para identificar lugares para enviar mensajes XCM, lugares que pueden recibir activos e incluso pueden ayudar a describir el tipo de activo en sí, como veremos. Cosas muy útiles.
Cuando se escriben en un texto como este artículo, se expresan como un número de ..
(o “parent”, el sistema de consenso encapsulado) componentes seguidos de un número de junctions (uniones), todos separados por /
. (Esto no es lo que generalmente sucede cuando los expresamos en un lenguaje como Rust, pero tiene sentido por escrito, ya que se parece mucho a las rutas de directorio familiares que se usan ampliamente). Los junctions identifican una ubicación interior dentro de su sistema de consenso encapsulado. Si no hay parents/junctions en absoluto, simplemente decimos que la ubicación es Here.
Algunos ejemplos:
../Parachain(1000)
: Evaluado dentro de una parachain, esto identificaría nuestra parachain hermana de índice 1000. (En Rust escribiríamos ParentThen(Parachain(1000)).into()
.)
../AccountId32(0x1234...cdef)
: Evaluado dentro de una parachain, esto identificaría la cuenta de 32 bytes 0x1234…cdef
en la Relay-chain.
Parachain(42)/AccountKey20(0x1234...abcd)
: Evaluado en una Relay-chain, esto identificaría la cuenta de 20 bytes 0x1234…abcd
en la parachain número 42 (presumiblemente algo como Moonbeam que aloja cuentas compatibles con Ethereum).
Hay muchos tipos diferentes de junction para identificar lugares que puede encontrar en la cadena en todo tipo de formas, como claves, índices, blobs binarios y descripciones de pluralidad.
💰 Activos en XCM
Cuando se trabaja en XCM, a menudo es necesario hacer referencia a un activo de algún tipo. Esto se debe a que prácticamente todas las blockchains públicas que existen dependen de algún activo digital nativo para proporcionar la columna vertebral de su economía interna y mecanismo de seguridad. Para las Blockchains de Proof of Work (prueba de trabajo) como Bitcoin, el activo nativo (BTC) se utiliza para recompensar a los mineros que hacen crecer la blockchain y evitar el doble gasto. Para las blockchains Proof-of-Stake (prueba de participación) como Polkadot, el activo nativo (DOT) se utiliza como una forma de colateral, donde los encargados de la red (conocidos como stakers) deben arriesgarlo para generar bloques válidos y ser recompensados en especie.
Algunas blockchains gestionan múltiples activos, p. Ej. el marco ERC-20 de Ethereum permite administrar muchos activos diferentes en cadena. Algunos administran activos que no son fungibles como ETH de Ethereum, sino que son no fungibles: únicos en su tipo; Crypto-kitties fue un ejemplo temprano de tales tokens no fungibles o NFT.
XCM está diseñado para poder manejar todos estos activos sin sudar. Para este propósito, existe el tipo de datos MultiAsset
junto con sus tipos asociados MultiAssets
, WildMultiAsset y MultiAssetFilter
. Veamos MultiAsset
en Rust:
Entonces, hay dos campos que definen nuestro activo: id
y fun
, esto es bastante indicativo de cómo XCM se acerca a los activos. En primer lugar, se debe proporcionar una identidad de activo general. Para los activos fungibles, esto simplemente identifica el activo. En el caso de las NFT, esto identifica la “clase” general de activos; pueden existir diferentes instancias de activos dentro de esta clase.
La identidad del activo se expresa de dos formas; ya sea Concrete o Abstract. Abstract no está realmente en uso, pero permite que los ID de activos se especifiquen por nombre. Esto es conveniente, pero depende de que el receptor interprete el nombre en la forma que espera el remitente, lo que puede no ser siempre tan fácil. Concrete es de uso general y utiliza una location para identificar un activo sin ambigüedades. Para los activos nativos (como DOT), el activo tiende a identificarse como la cadena que acuña el activo (la Relay Chain Polkadot en este caso, que sería la ubicación..
de una de sus parachains). Los activos que se administran principalmente dentro de la paleta de una cadena pueden identificarse por una ubicación que incluye su índice dentro de esa paleta. Por ejemplo, la parachain Karura puede referirse a un activo en la parachain Statemine con la ubicación ../Parachain(1000)/PalletInstance(50)/GeneralIndex(42)
.
En segundo lugar, deben ser fungibles o no fungibles. Si son fungibles, entonces debería haber una cantidad asociada non-zero (distinta de cero). Si no son fungibles, entonces, en lugar de una cantidad, debería haber alguna indicación de qué instancia son. (Esto se expresa comúnmente con un índice, pero XCM también permite el uso de otros tipos de datos, como matrices y blobs binarios).
Esto cubre MultiAsset
, pero hay otros tres tipos asociados que a veces usamos. MultiAsset
es uno de ellos y en realidad solo significa un conjunto de elementos MultiAsset
. Luego tenemosWildMultiAsset
; este es un comodín que se puede usar para hacer coincidir uno o más elementos de MultiAsset
. En realidad, solo hay dos tipos de comodines que admite:All
(que coincide con todos los activos) y AllOf
, que coincide con todos los activos de una identidad particular (AssetId
) y fungibilidad. En particular, para estos últimos, no es necesario especificar el monto (en el caso de los fungibles) o instancia (s) (para los no fungibles) y todos están emparejados.
Finalmente, está MultiAssetFilter
. Esto se usa con más frecuencia y en realidad es solo una combinación de MultiAsset
, y WildMultiAsset
; que permite especificar un comodín o una lista de definite (definidos) activos (es decir, no comodines).
En la API de Rust XCM, proporcionamos muchas conversiones para que trabajar con estos tipos de datos sea lo más sencillo posible. Por ejemplo, para especificar el MultiAsset
fungible que equivale a 100 unidades indivisibles del activo DOT (Planck, para aquellos que lo saben) cuando estamos en la Relay Chain de Polkadot, entonces usaríamos (Here, 100).into()
.
👉 El Holding Register
Echemos un vistazo a otra instrucción de XCM: WithdrawAsset
(retirar activo). A primera vista, esto es un poco como la primera mitad de TransferAsset
: retira algunos activos de la cuenta del origen. Pero, ¿qué hace con ellos? — Si no se depositan en ningún lugar, seguramente será una operación bastante inútil. Si miramos su declaración de Rust, encontraremos algunas pistas más sobre su uso:
Entonces, esta vez solo hay un parámetro (de tipo MultiAssets
y que dicta qué activos deben retirarse de la propiedad del Origin Register (Registro de Origen). Pero no hay una ubicación especificada en la que colocar los activos.
Estos activos no gastados que se mantienen temporalmente se conocen como Holding Register (Registro de Tenencia). (“Holding” porque están en una posición temporal que no puede persistir indefinidamente y “Register”, ya que es un poco como un registro de CPU, un lugar para datos de trabajo). Hay una serie de instrucciones que operan en el Holding Register. Uno muy simple es la instrucción DepositAsset
. Echemos un vistazo:
¡Ajá! El lector astuto verá que esto se parece bastante a la mitad faltante de la instrucción TransferAsset
. Tenemos el parámetro de assets
que especifica cuál de los activos debe eliminarse del Holding Register para depositarse en la cadena. max_assets
permite que el autor de XCM informe al receptor cuántos activos únicos se pretenden depositar. (Esto es útil cuando se calculan las tarifas antes de conocer el contenido del Holding Register, ya que depositar un activo puede ser una operación costosa). Finalmente está el beneficiary
, que es el mismo parámetro que conocimos anteriormente en la operación TransferAsset
Hay muchas instrucciones que expresan acciones a realizar en el Holding Register, y DepositAsset
es una de las más simples. Algunos otros son bastante más sofisticados 😬.
🤑 Pago de tarifas en XCM
El pago de tarifas en XCM es un caso de uso bastante importante. La mayoría de las parachains en la comunidad de Polkadot requerirán que sus interlocutores paguen su camino por cualquier operación que deseen realizar, para que no queden expuestos al “spam de transacciones” y un ataque de denegación de servicio. Existen excepciones a esto cuando las cadenas tienen buenas razones para creer que su interlocutor se comportará bien; este es el caso cuando la Relay Chain Polkadot se corresponde con la cadena de bien común Statemint en Polkadot. Sin embargo, para el caso general, las tarifas son una buena forma de garantizar que los mensajes XCM y sus protocolos de transporte no se utilicen en exceso. Veamos cómo se pueden pagar las tarifas cuando los mensajes XCM llegan a Polkadot.
Como ya se mencionó, XCM no incluye la idea de tarifas y pago de tarifas como ciudadano de primera clase (first-class citizen): a diferencia de, digamos, el modelo de transacción de Ethereum, el pago de tarifas no es algo integrado en el protocolo que los casos de uso que no tengan necesidad, deban eludir de manera notoria. Al igual que Rust con sus abstracciones de costo cero, el pago de tarifas viene sin grandes gastos generales de diseño en XCM.
Para los sistemas que requieren algún pago de tarifa, XCM brinda la capacidad de comprar recursos de ejecución con activos. Hacerlo, en términos generales, consta de tres partes:
- En primer lugar, es necesario proporcionar algunos activos.
- En segundo lugar, se debe negociar el intercambio de activos por tiempo de cálculo (o peso, en el lenguaje de Substrate).
- Finalmente, las operaciones de XCM se realizarán según las instrucciones.
La primera parte está gestionada por una de varias instrucciones XCM que proporcionan activos. Ya conocemos uno de estos (WithdrawAsset
), pero hay varios otros que veremos más adelante. Los activos resultantes en el Holding Register se utilizarán, por supuesto, para pagar las tarifas asociadas con la ejecución del XCM. Cualquier activo que no se utilice para pagar tarifas lo depositaremos en alguna cuenta de destino. Para nuestro ejemplo, asumiremos que el XCM está sucediendo en la Relay Chain Polkadot y que es por 1 DOT (que son 10,000,000,000 de unidades indivisibles).
Hasta ahora, nuestra instrucción XCM se ve así:
Esto nos lleva a la segunda parte, intercambiar (algunos de) estos activos por tiempo de cómputo para pagar nuestro XCM. Para ello contamos con la instrucción XCM BuyExecution
. Echemos un vistazo:
El primer ítem fees
es el monto que debe tomarse del Holding Register y utilizarse para el pago de las tarifas. Técnicamente, es solo el máximo, ya que cualquier saldo no utilizado se devuelve de inmediato.
La cantidad que termina siendo gastada la determina el sistema de interpretación: las fees
solo la limitan y si el sistema de interpretación necesita pagar más por la ejecución deseada, entonces la instrucción BuyExecution
resultará en un error. El segundo elemento especifica una cantidad de tiempo de ejecución que se comprará. Por lo general, esto no debería ser menor que el peso total del programa XCM.
En nuestro ejemplo, asumiremos que todas las instrucciones XCM tienen un peso de un millón, por lo que son dos millones para nuestros dos elementos hasta ahora (WithdrawAsset
y BuyExecution
) y uno más para lo que viene a continuación. Simplemente usaremos todo el DOT que tenemos para pagar esas tarifas (lo cual es una buena idea si confiamos en que la cadena de destino no tendrá tarifas locas, asumiremos que sí). Echemos un vistazo a nuestro XCM hasta ahora:
La tercera parte de nuestro XCM consiste en depositar los fondos restantes en el Holding Register. Para esto, solo usaremos la instrucción DepositAsset.
En realidad, no sabemos cuánto queda en el Holding Register, pero eso no importa, ya que podemos especificar un comodín para el/os activo/s que se debe/n depositar. Los colocaremos en la cuenta soberana de Statemint (que se identifica como Parachain(1000)
.
Por lo tanto, nuestra instrucción final de XCM se ve así:
⛓ Mover Activos entre Cadenas en XCM
Enviar un activo a otra cadena es probablemente el caso de uso más común para la mensajería entre cadenas. Permitir que una cadena administre el activo nativo de otra cadena permite todo tipo de casos de uso de derivados (sin juego de palabras), el más simple es un intercambio descentralizado pero generalmente agrupado como finanzas descentralizadas o De-fi.
En términos generales, hay dos formas en que los activos se mueven entre las cadenas y esto depende de si las cadenas confían o no en la seguridad y la lógica de las demás.
✨ Teleporting (Teletransportación)
Para las cadenas que confían entre sí (fragmentos tan homogéneos bajo el mismo consenso general y paraguas de seguridad), podemos usar un marco que Polkadot llama teleporting, que básicamente solo significa destruir un activo en el lado de envío y acuñarlo en el lado de recepción. Esto es simple y eficiente: solo requiere la coordinación de las dos cadenas y solo implica una acción en cada lado. Desafortunadamente, si la cadena de recepción no puede confiar al 100% en la cadena de envío para que realmente destruya el activo que está acuñando (y de hecho no para acuñar activos fuera de las reglas acordadas para el activo), entonces la cadena de envío realmente no tiene base para acuñar el activo en la parte posterior de un mensaje.
Veamos cómo se vería el XCM que teletransportó (la mayor parte de) 1 DOT de la Relay Chain Polkadot a su cuenta soberana en Statemint. Asumiremos que las tarifas ya están pagadas en el lado de Polkadot.
Como puedes ver, esto se parece bastante al patrón directo de withdraw-buy-deposit (retiro-compra-depósito) que vimos por última vez. La diferencia es la instrucción InitiateTeleport
que se inserta alrededor de las dos últimas instrucciones (BuyExecution
y DepositAsset
) Detrás de escena, la cadena del remitente (Polkadot Relay) está creando un mensaje completamente nuevo cuando ejecuta la instrucción InitiateTeleport
; toma el campo xcm
y lo coloca dentro de un nuevo XCM, ReceiveTeleportedAsset
, y envía este XCM a la cadena del receptor (Statemint). Statemint confía en que la Relay chain Polkadot haya destruido el 1 DOT de su lado antes de enviar el mensaje. (¡Lo hace!)
El beneficiary
se indica como Parent.into()
, y un lector astuto podría preguntarse a qué podría referirse esto en el contexto de la Relay chain Polkadot. La respuesta sería “nada”, pero no hay ningún error aquí. Todo en el parámetro xcm
está escrito desde la perspectiva del lado receptor, por lo que a pesar de que es parte del XCM general que se alimenta en la Relay Chain de Polkadot, solo se ejecuta en Statemint y, por lo tanto, se escribe en el contexto de Statemint.
Cuando Statemint finalmente recibe el mensaje, se ve así:
Puedes notar que esto se parece bastante al anteriorWithdrawAsset
XCM. La única diferencia importante es que en lugar de financiar las tarifas y el depósito a través de un retiro de una cuenta local, se está creando “mágicamente” confiando en que el DOT fue destruido fielmente en el lado de envío (Polkadot Relay Chain) y honrando el mensaje ReceiveTeleportedAsset
.
En particular, el identificador de activos del 1 DOT que enviamos en Polkadot Relay-chain (Here
, refiriéndose a la Relay-chain en sí, el hogar nativo del DOT) se ha mutado automáticamente en su representación en Statemint: Parent.into()
, que es la ubicación de la Relay chain desde el contexto de Statemint.
El beneficiary
también se especifica como Relay chain Polkadot y, por lo tanto, su cuenta soberana (en Statemint) se acredita con el nuevo 1 DOT acuñado (minteado) menos las tarifas. El XCM podría haber nombrado fácilmente una cuenta u otro lugar para el beneficiary
. Tal como está, un TransferAsset
posterior enviado desde la Relay chain podría usarse para mover este 1 DOT.
🏦 Reservas
La forma alternativa de transferir activos entre cadenas es un poco más complicada. Se utiliza un tercero conocido como reserve. El nombre proviene de la banca de reserva, donde los activos se mantienen “en reserva” para dar credibilidad a la idea de que alguna promesa emitida es valiosa. Por ejemplo, si podemos creer razonablemente que exactamente 1 DOT “real” (por ejemplo, Statemint o Relay-chain) es canjeable por cada DOT “derivado” emitido en una parachain independiente, entonces podemos tratar el DOT de la parachain como económicamente equivalente al DOT real. (La mayoría de los bancos hacen algo llamado banca de reserva fraccionaria, lo que significa que mantienen menos del valor nominal en reserva. Esto funciona bien hasta que demasiadas personas desean canjear, y entonces todo puede salir bastante mal bastante rápido). Entonces, la reserva es el lugar que almacena los activos “reales” y, a efectos de transferencia, en cuya lógica y seguridad confían tanto el emisor como el receptor. Todos los activos correspondientes del lado del emisor y del receptor serían entonces derivados, pero estarían respaldados por el activo de reserva “real” al 100%. Suponiendo que la parachain se comportó bien (es decir, que estaba libre de errores y su gobierno decidió no salir corriendo con la reserva), esto haría que el DOT derivado tenga más o menos el mismo valor que el DOT de reserva subyacente. Los activos de reserva se mantienen en la cuenta soberana del remitente / receptor (es decir, la cuenta controlable por la cadena del remitente o receptor) en la cadena de reserva, por lo que hay una buena razón para que, a menos que algo salga mal con la parachain, estarán bien protegidos.
Volviendo al mecanismo de transferencia, el remitente indicaría a la reserva que transfiera los activos que posee el remitente (y utiliza como reserva para su propia versión del mismo activo) a la cuenta soberana del receptor, y la reserva, ¡no el remitente! — informa al receptor sobre su nuevo crédito. Esto significa que el remitente y el receptor no necesitan confiar en la lógica o la seguridad del otro, sino solo en la de la cadena utilizada como reserva. Sin embargo, implica que los tres lados deben coordinarse, lo que aumenta el costo, el tiempo y la complejidad generales.
Veamos el XCM requerido. Esta vez enviaremos 1 DOT desde la parachain 2000 a la parachain 2001, que usa DOT respaldado por reserva en la parachain 1000. Nuevamente, asumiremos que las tarifas ya están pagadas por el remitente.
Esto es un poco más complejo, como se prometió. Repasemos esto. La parte exterior trata de extraer el 1 DOT en el lado del remitente (parachain 2000) y retirar el 1 DOT correspondiente que se encuentra en Statemint (parachain 1000); utiliza InitiateReserveWithdraw
para este propósito y es bastante autoexplicativo.
Ahora tenemos 1 DOT en el Holding Register de Statemint. Antes de que podamos hacer cualquier otra cosa, necesitamos ganar algo de tiempo de ejecución en Statemint. Esto, de nuevo, parece bastante familiar:
Estamos usando nuestro 1 DOT para pagar las tarifas y asumimos un millón por operación de XCM. Con esa operación pagada, depositamos el 1 DOT (menos las tarifas, y somos perezosos, así que usamos All.into()
) en la cuenta soberana para la parachain 2001, pero lo hacemos como un activo de reserva, lo que significa que también requieren que Statemint envíe una notificación XCM a esa cadena receptora informándole de la transferencia junto con algunas instrucciones a ejecutar sobre los activos derivados resultantes. Las instrucciones deDepositReserveAsset
no siempre tienen mucho sentido; para que tenga sentido, el dest
debe ser una ubicación que pueda contener razonablemente fondos en la cadena de reserva, pero también una a la que la cadena de reserva pueda enviar un XCM. Las parachains hermanas encajan perfectamente.
La parte final define parte del mensaje que llega a la parachain 2001. Al igual que al iniciar una operación de teletransporte, DepositReserveAsset
redacta y envía un nuevo mensaje en este caso ReserveAssetDeposited
. Es este mensaje, aunque contiene el programa XCM que definimos, el que llega a la parachain receptora. Se verá algo como esto:
(Esto supone que no se cobraron tarifas en Statemint y que el 1 DOT completo lo superó. Eso no es especialmente realista, por lo que la línea de assets
probablemente tendrá un número más bajo).
La mayor parte del mensaje debería resultar bastante familiar; la única diferencia significativa con el mensaje ReceiveTeleportedAsset
que vimos en la última sección es la instrucción de nivel superior ReserveAssetDeposited
, que cumple un propósito similar, solo que en lugar de significar “la cadena de envío quemó activos para que puedas acuñar activos equivalentes”, significa “la cadena de envío recibió activos y los mantiene en reserva para que puedas acuñar derivados totalmente respaldados ”. De cualquier manera, la cadena de destino los acuña en el Holding Register y los depositamos en la cuenta soberana del remitente en la cadena de recepción. 🎉
🏁 Conclusión
Eso es todo por este artículo; espero que haya sido útil para explicar qué es XCM y los conceptos básicos de cómo está diseñado para funcionar. En los próximo(s) artículo (s?), analizaremos en profundidad la arquitectura de XCVM, su modelo de ejecución y su manejo de errores, el sistema de versiones de XCM y cómo se pueden administrar las actualizaciones al formato en un ecosistema interdependiente bien conectado, así como su sistema de consulta-respuesta y cómo funciona XCM en Substrate. También discutiremos algunas de las direcciones futuras de XCM, las características planificadas y el proceso para desarrollarlo.
Gracias a David Palm.