El modelo de ejecución asíncrona de Monad desacopla el ordenamiento y el compromiso de las transacciones de su ejecución. A diferencia de las cadenas EVM clásicas, una transacción puede incluirse en un bloque y aparecer en los recibos antes de que se complete su ejecución (y la de sus dependencias). Esta guía explica el mecanismo, cómo interpretar el estado y el ordenamiento de los recibos, y proporciona un script de sondeo reproducible para observar el ciclo de vida en un endpoint de Monad.
Respuesta directa: Qué cambia con la ejecución asíncrona de Monad
En una cadena EVM clásica, las transacciones de un bloque se ejecutan secuencialmente y el bloque solo se produce después de que la ejecución se completa. Monad invierte esto: ordena y compromete los bloques primero, luego ejecuta las transacciones de forma asíncrona y en paralelo, utilizando especulación optimista y seguimiento de dependencias. Como resultado, el momento en que una transacción se incluye en un bloque y el momento en que realmente se ejecuta no son los mismos. Cuando consultas eth_getTransactionReceipt, es posible que veas un recibo con un status que refleja el resultado eventual, pero la ejecución aún puede estar pendiente para esa transacción o sus dependencias. Esta guía explica el mecanismo, qué significan los campos del recibo bajo ejecución diferida y cómo construir código de integración robusto que no asuma una ejecución secuencial inmediata.
La fuente autorizada para este comportamiento es la documentación de Monad sobre el ciclo de vida de transacciones y ejecución asíncrona. Al momento de escribir esto, Monad se encuentra en fases de testnet/devnet; el comportamiento en mainnet puede diferir. Siempre verifica contra la documentación en vivo y el endpoint específico al que te diriges. Esta guía proporciona un método reproducible para observar el ciclo de vida en tu propio endpoint.
Modelo de ejecución de Monad: Ordenar primero, ejecutar después
Monad utiliza una arquitectura segmentada donde la producción de bloques se separa de la ejecución. La capa de consenso acuerda el orden de las transacciones y los bloques (el orden canónico) antes de que ocurra la ejecución. Esto a menudo se describe como 'acordar primero, ejecutar después' (ver publicación del foro de Monad). La ejecución se realiza de forma asíncrona, con múltiples bloques ejecutándose en paralelo donde las dependencias lo permitan.
Para que esto sea seguro, Monad utiliza ejecución optimista con especulación. Cuando se propone un bloque, la capa de ejecución ejecuta especulativamente las transacciones basándose en el estado actual, incluso si algunas dependencias (transacciones anteriores en el mismo bloque o de bloques anteriores) aún no han terminado de ejecutarse. El seguimiento de dependencias asegura que una transacción que lee el estado escrito por otra transacción espere a que esa escritura esté disponible. Si una ejecución especulativa resulta incorrecta (por ejemplo, porque el resultado real de una dependencia difiere del estado especulado), la ejecución se revierte y se vuelve a ejecutar. Esta reconciliación es interna y no debería afectar el resultado final comprometido, que sigue el orden canónico.
Para el integrador, la conclusión clave es que el orden canónico de las transacciones se fija en el momento del compromiso, pero la ejecución de una transacción puede ir por detrás. Esto significa que cuando ves una transacción en un bloque (a través de eth_getBlockByNumber o similar), su ejecución puede no haberse completado aún. El recibo, cuando está disponible, refleja el resultado de esa ejecución, pero su disponibilidad no garantiza que todas las transacciones anteriores se hayan ejecutado.
Envío e inclusión de transacciones vs. ejecución
Enviar una transacción en Monad utiliza el método estándar eth_sendRawTransaction (o el equivalente de tu biblioteca). La transacción se transmite a la red y eventualmente se incluye en un bloque. La inclusión significa que la transacción es parte del orden canónico y tiene un número de bloque e índice de transacción. Sin embargo, la inclusión no significa que la ejecución haya ocurrido.
Para verificar la inclusión, puedes usar eth_getTransactionByHash. Esto devuelve los detalles de la transacción, incluidos blockNumber y blockHash, una vez que la transacción se incluye. Pero el status de la transacción (éxito/fracaso) solo se conoce después de la ejecución. Para eso, necesitas eth_getTransactionReceipt.
El recibo contiene un campo status (0x1 para éxito, 0x0 para fracaso) y logs (para eventos). En Monad, el recibo puede estar disponible antes de que la transacción realmente se haya ejecutado, porque el recibo se genera como parte de los metadatos del bloque. Sin embargo, el status y los logs reflejan el resultado eventual después de la ejecución y la reconciliación. En la práctica, es posible que veas un recibo con un blockNumber pero la ejecución de la transacción aún está pendiente. Esto es una diferencia con las cadenas clásicas donde la disponibilidad del recibo implica la finalización de la ejecución.
Semántica del estado del recibo bajo ejecución diferida
Cuando llamas a eth_getTransactionReceipt en Monad, el objeto devuelto incluye los campos estándar: transactionHash, transactionIndex, blockHash, blockNumber, from, to, cumulativeGasUsed, gasUsed, contractAddress, logs, logsBloom, status y effectiveGasPrice. El campo status es particularmente importante: indica si la transacción tendrá éxito o fallará una vez ejecutada. Pero debido a que la ejecución es asíncrona, un recibo con status: 0x1 no significa que los cambios de estado se hayan aplicado aún, solo que el motor de ejecución ha determinado el resultado.
Para transacciones que dependen de otras transacciones (por ejemplo, una llamada a contrato que lee el estado escrito por una transacción anterior), el recibo podría no estar disponible hasta que esas dependencias se ejecuten. En la práctica, es posible que veas un recibo que aparece más tarde que la inclusión del bloque, o que veas un recibo con un status que luego cambia si una ejecución especulativa se revierte (aunque esto debería ser raro y no es parte de la garantía de la API pública).
La documentación de Monad establece que la ejecución es determinista y que el estado final coincide con el orden canónico. Por lo tanto, puedes confiar en el status y los logs del recibo como el resultado final, incluso si la ejecución aún está en progreso. Sin embargo, no debes asumir que los efectos de la transacción son visibles en las consultas de estado (por ejemplo, eth_getBalance o eth_call) inmediatamente después de que el recibo esté disponible. Puede haber un desfase entre la disponibilidad del recibo y la finalización del estado.
Garantías de ordenamiento y gestión de nonces
Monad preserva el orden canónico de las transacciones según lo determina la capa de consenso. Esto significa que para una cuenta determinada, las transacciones se ordenan por nonce, y el estado final refleja ese orden. Sin embargo, debido a que la ejecución es asíncrona, no puedes asumir que una transacción con un nonce más bajo se ha ejecutado antes de que se incluya una transacción con un nonce más alto. El ordenamiento solo está garantizado a nivel de estado, no en la línea de tiempo de ejecución.
Para la gestión de nonces, esto tiene implicaciones. Si envías múltiples transacciones desde la misma cuenta, aún debes incrementar el nonce correctamente, como en cualquier cadena EVM. Pero no debes confiar en que la ejecución de una transacción anterior esté completa antes de enviar la siguiente. En su lugar, debes rastrear el nonce y usar eth_getTransactionCount con el parámetro de bloque apropiado (por ejemplo, 'pending' o 'latest') para determinar el siguiente nonce disponible. Para una inmersión más profunda, consulta nuestra guía sobre gestión de nonces EVM bajo concurrencia.
Al construir un bucle de espera de recibo, debes sondear eth_getTransactionReceipt hasta que devuelva un resultado no nulo. Sin embargo, debido a que la ejecución puede ir por detrás, es posible que también quieras esperar hasta que los efectos de la transacción sean visibles en el estado, si tu aplicación depende de eso. Por ejemplo, si envías una transferencia y luego quieres verificar el saldo del destinatario, debes sondear eth_getBalance hasta que refleje el valor esperado, en lugar de asumir que se actualiza inmediatamente después de que el recibo esté disponible.
Ejemplo práctico: Observando el ciclo de vida con un script de Node.js
El siguiente script envía una transacción de transferencia simple a un endpoint de Monad (debes proporcionar la URL del endpoint y una clave privada con fondos). Luego sondea eth_getTransactionReceipt cada segundo durante hasta 30 segundos, imprimiendo el número de bloque, el estado y el número de logs en cada sondeo. Esto te permite observar cuándo el recibo está disponible en relación con la inclusión. Ejecútalo contra un endpoint de testnet/devnet de Monad; no asumas disponibilidad en mainnet.
Limitaciones: Este script es con fines educativos. El tiempo exacto y el comportamiento pueden variar según el endpoint y la fase de la red. Siempre verifica contra la documentación en vivo de Monad y el comportamiento de tu endpoint.
const { Web3 } = require('web3');
// Configuración: reemplaza con tu endpoint y clave privada
const RPC_URL = 'https://your-monad-endpoint.example.com';
const PRIVATE_KEY = '0x...';
const TO_ADDRESS = '0x...';
const web3 = new Web3(RPC_URL);
async function main() {
const account = web3.eth.accounts.privateKeyToAccount(PRIVATE_KEY);
web3.eth.accounts.wallet.add(account);
// Obtener nonce
const nonce = await web3.eth.getTransactionCount(account.address, 'pending');
// Construir transacción
const tx = {
from: account.address,
to: TO_ADDRESS,
value: web3.utils.toWei('0.001', 'ether'),
gas: 21000,
gasPrice: await web3.eth.getGasPrice(),
nonce: nonce
};
// Firmar y enviar
const signedTx = await web3.eth.accounts.signTransaction(tx, PRIVATE_KEY);
const txHash = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log('Hash de transacción:', txHash);
// Sondeo de recibo
const timeout = 30000; // 30 segundos
const start = Date.now();
let receipt = null;
while (Date.now() - start < timeout) {
receipt = await web3.eth.getTransactionReceipt(txHash);
if (receipt) {
console.log(`Sondeo a ${Date.now() - start}ms: blockNumber=${receipt.blockNumber}, status=${receipt.status}, logs=${receipt.logs.length}`);
// Opcionalmente, romper cuando status esté definido
if (receipt.status !== undefined) break;
} else {
console.log(`Sondeo a ${Date.now() - start}ms: recibo aún no disponible`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if (!receipt) {
console.log('Tiempo de espera agotado: recibo no disponible en 30s');
}
}
main().catch(console.error);Tabla de resultados: Completa tus observaciones
Ejecuta el script contra tu endpoint de Monad y registra los tiempos. Esta tabla te ayudará a comprender la relación entre inclusión y ejecución en tu red específica.
- Tiempo hasta que el recibo esté disponible: El tiempo desde el envío de la transacción hasta la primera respuesta de recibo no nula.
- Tiempo hasta que el estado esté definido: El tiempo cuando el campo
statusdel recibo ya no esundefined(si aplica). - Número de bloque en el primer recibo: El número de bloque cuando el recibo aparece por primera vez.
- Número de sondeos hasta el estado: Cuántos sondeos tomó obtener un estado definitivo.
| Métrica | Valor (segundos) |
|--------|-----------------|
| Tiempo hasta el primer recibo | |
| Tiempo hasta que el estado esté definido | |
| Número de bloque en el primer recibo | |
| Sondeos hasta el estado | |Solución de problemas: Errores comunes y correcciones
Al integrarte con la ejecución asíncrona de Monad, es posible que encuentres problemas que surgen de asumir una ejecución inmediata. Aquí hay errores comunes y cómo abordarlos.
- Recibo no disponible después de la inclusión: Si ves la transacción en un bloque pero
eth_getTransactionReceiptdevuelve null, significa que la ejecución no se ha completado. Espera y vuelve a sondear. No asumas que la transacción falló. - Campo de estado nulo: Algunas implementaciones de RPC pueden devolver un recibo con
status: nullsi la ejecución aún está pendiente. Trátalo como 'desconocido', no como fallo. - Estado no actualizado después del recibo: Si consultas un saldo o llamas a un contrato inmediatamente después de recibir un recibo, el estado puede no reflejar los efectos de la transacción aún. Sondea el estado hasta que coincida con las expectativas.
- Nonce demasiado bajo o demasiado alto: Debido a que la ejecución es asíncrona, podrías enviar una transacción con un nonce demasiado alto si confías en el último nonce ejecutado. Usa
eth_getTransactionCountcon 'pending' para obtener el siguiente nonce esperado. - Logs de eventos faltantes: Si indexas logs de una transacción que aún no se ha ejecutado, podrías perderlos. Asegúrate de sondear los logs después de que el recibo esté disponible y la ejecución esté completa.
- Transacciones revertidas: Si una transacción revierte, el recibo tendrá
status: 0x0. Esto es definitivo. Sin embargo, la reversión puede ocurrir después de un retraso, así que prepárate para un fallo tardío.
Limitaciones y compensaciones de la ejecución asíncrona
La ejecución asíncrona de Monad ofrece un mayor rendimiento mediante la segmentación, pero introduce complejidad para los desarrolladores. La principal compensación es que no puedes confiar en la semántica de ejecución síncrona. Esto afecta la depuración, la indexación de eventos y cualquier lógica que asuma cambios de estado inmediatos.
Además, el comportamiento exacto de la disponibilidad del recibo y la semántica del estado puede evolucionar a medida que Monad avanza hacia mainnet. La documentación es la fuente principal, pero debes probar contra tu endpoint objetivo. Por ejemplo, algunos endpoints podrían devolver un recibo solo después de que la ejecución esté completa, mientras que otros podrían devolverlo antes. Esto aún no está estandarizado.
Para datos históricos, ten en cuenta que los nodos de archivo de Monad pueden tener un comportamiento diferente. Consulta nuestra guía sobre Consulta del estado histórico de Monad a través de RPC para más información.
Próximos pasos y lecturas adicionales
Para construir aplicaciones confiables en Monad, necesitas comprender su ciclo de vida de transacciones único. Comienza leyendo la documentación oficial de Ciclo de vida de transacciones de Monad y Ejecución asíncrona. Luego, experimenta con el script anterior en una testnet.
Para obtener más orientación específica de Monad, explora nuestros otros recursos: