Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Integración y desarrollo13 min de lectura

Bundles MEV de Ethereum: mecánica del RPC eth_sendBundle

En qué se diferencian los bundles de Ethereum de las transacciones, cómo enviarlos a un builder mediante JSON-RPC y cómo diagnosticar las tres causas distintas por las que un bundle no se incluye.

TL;DR

Un bundle de Ethereum es un array ordenado de transacciones en bruto totalmente firmadas más un bloque objetivo opcional, que se envía a un constructor de bloques en lugar de al mempool público. La inclusión es atómica sobre el array: o todas las transacciones se incluyen consecutivamente en el bloque objetivo o ninguna lo hace. eth_sendBundle no forma parte de la especificación execution-apis de Ethereum; es un método específico de los builders, por lo que apuntarlo a un nodo completo estándar o a un proveedor RPC de propósito general devuelve un error de método no encontrado en lugar de un fallo de envío. Las tres causas distintas por las que un bundle no se incluye son: mal formado (firma, nonce o gas inválidos), válido pero no rentable (la simulación tiene éxito pero gana una puja mejor) y endpoint incorrecto (la llamada nunca llegó a un builder). Este artículo cubre la forma de la petición, el flujo de trabajo de simulación antes del envío, un ejemplo ejecutable en Node.js y un método de diagnóstico que puedes ejecutar contra tu propio endpoint.

Mecánica de los bundles: arrays de transacciones ordenados y atómicos

Un bundle es un array ordenado de transacciones en bruto totalmente firmadas más un bloque objetivo opcional, que se envía a un constructor de bloques en lugar de al mempool público. La propiedad que lo define es la atomicidad sobre el array: o todas las transacciones del bundle se incluyen consecutivamente en el bloque objetivo o ninguna lo hace. Esto es lo que hace útiles los bundles para patrones de backrun y arbitraje, donde una secuencia rentable solo funciona si todas las partes se ejecutan en orden.

Esa misma propiedad hace que un único miembro mal formado envenene todo el envío. Si una transacción tiene una firma incorrecta, un nonce obsoleto o gas insuficiente, el builder no puede incluir el array de forma atómica y se descarta el bundle completo. Esta es una diferencia estructural respecto a una transacción simple, que se evalúa de forma independiente y puede reemplazarse o descartarse sin afectar a nada más.

Como los miembros del bundle están prefirmados, reemplazar un miembro implica volver a firmar todo el bundle. Un bundle cuya primera transacción ya fue incluida por otra vía deja a los miembros restantes en fallo atómico o, si se envían por separado, compitiendo por el nonce. Por lo tanto, la gestión de nonces es una preocupación de primer orden; consulta Gestión de nonces EVM con eth_getTransactionCount para el lado de lectura de ese problema.

  • Bundle = array ordenado de transacciones en bruto firmadas + bloque objetivo opcional.
  • Atomicidad: todos los miembros se incluyen consecutivamente en el bloque objetivo, o ninguno lo hace.
  • Un miembro mal formado invalida todo el bundle.
  • Los miembros prefirmados implican que reemplazar uno requiere volver a firmar todo el bundle.

Por qué un endpoint de bundles no es un endpoint RPC normal

eth_sendBundle no forma parte en absoluto de la especificación execution-apis de Ethereum. Es un método específico de los builders, documentado por Flashbots en docs.flashbots.net junto con eth_callBundle y eth_cancelBundle. Apuntar una llamada de bundle a un nodo completo estándar o a un proveedor RPC de propósito general devuelve un error de método no encontrado en lugar de un fallo de envío.

La disponibilidad y la forma exacta de la respuesta varían según el proveedor. Algunos builders solo exponen eth_sendBundle; otros añaden métodos de simulación o cancelación. Trata el conjunto de métodos como documentado / varía según el proveedor, y verifícalo contra el endpoint que realmente piensas usar antes de construir un pipeline de envío en torno a él.

Para el tráfico ordinario de lectura y escritura, un endpoint estándar de Ethereum sigue siendo la herramienta adecuada. La página de la red Ethereum de OnFinality y la guía de endpoints RPC describen la superficie JSON-RPC convencional, que es donde pertenecen eth_sendRawTransaction, eth_getTransactionCount y las lecturas de bloques.

  • eth_sendBundle es específico de los builders, no forma parte de execution-apis.
  • Un endpoint incorrecto devuelve método no encontrado, no un fallo de envío.
  • La disponibilidad de métodos y la forma de la respuesta son documentado / varía según el proveedor.

Campos de la petición y qué restringe cada uno

La forma de la petición de bundle documentada por Flashbots contiene tres campos. El array de transacciones firmadas es la lista ordenada de transacciones en bruto. El número o hash del bloque objetivo restringe la elegibilidad: un bundle solo es elegible para el bloque al que va dirigido, por lo que un objetivo obsoleto nunca se incluye de forma silenciosa. El campo opcional de hashes de transacciones que pueden revertir permite al searcher declarar qué miembros pueden revertir sin invalidar el bundle.

Los campos de transacción dentro de cada transacción en bruto siguen la especificación execution-apis de Ethereum, incluidos los campos de comisión de EIP-1559 que determinan la comisión de prioridad efectiva que debe pujar un bundle. EIP-1559 define maxFeePerGas, maxPriorityFeePerGas y la quema de la comisión base, que en conjunto determinan lo que un builder recibe realmente de un bundle.

Un error común es tratar el bloque objetivo como algo orientativo. No lo es. Si construyes un bundle para el bloque N y lo envías después de que se produzca N, el bundle no es elegible independientemente de lo rentable que habría sido. Volver a fijar el objetivo implica reconstruir y volver a firmar.

  • array de transacciones firmadas: transacciones en bruto totalmente firmadas y ordenadas.
  • bloque objetivo: número o hash; la elegibilidad se limita a ese bloque.
  • hashes de transacciones que pueden revertir: miembros que pueden revertir sin invalidar el bundle.
  • Los campos de comisión siguen EIP-1559 y determinan la puja de comisión de prioridad efectiva.

Simulación antes del envío con eth_callBundle

eth_callBundle previsualiza un bundle contra el estado actual para un bloque dado, devolviendo el uso de gas real y la transferencia al coinbase. Esta es la única forma de saber que un bundle revertiría antes de gastar un bloque en él. Una simulación que devuelve un error JSON-RPC apunta a un bundle mal formado; una simulación que tiene éxito pero muestra una transferencia baja al coinbase apunta a un bundle válido pero no rentable.

Ejecuta la simulación contra el mismo bloque que pretendes como objetivo. Simular contra un estado de bloque diferente puede producir un resultado que no refleje lo que verá el builder. Si la simulación tiene éxito, registra los valores de gas y coinbase para poder compararlos con lo que ocurra realmente tras el envío.

La simulación no garantiza la inclusión. Te dice que el bundle está bien formado y es ejecutable contra el estado que simulaste; no te dice si un searcher competidor pujará más por la misma oportunidad.

  • eth_callBundle devuelve el gas real y la transferencia al coinbase.
  • Simula contra el mismo bloque que pretendes como objetivo.
  • El éxito de la simulación es necesario pero no suficiente para la inclusión.

Ejemplo ejecutable en Node.js: construir, simular, enviar, verificar

El script siguiente construye una petición de bundle, llama a eth_callBundle para simularlo, lo envía con eth_sendBundle y luego verifica la inclusión obteniendo el bloque objetivo y comprobando que los hashes de transacción del bundle aparecen en el orden esperado. Usa la API global fetch disponible en Node.js moderno y asume que ya has firmado las transacciones en bruto.

Reemplaza la URL del endpoint por el endpoint del builder que pretendes usar. El script no asume ningún proveedor concreto; mostrará un error de método no encontrado si el endpoint no es un endpoint de builder, lo cual es en sí mismo un diagnóstico útil.

const ENDPOINT = process.env.BUNDLE_RPC_URL; // builder endpoint
const TARGET_BLOCK = process.env.TARGET_BLOCK; // e.g. "0x112a880"
const RAW_TXS = JSON.parse(process.env.RAW_TXS); // array of 0x-prefixed signed raw txs

async function rpc(method, params) {
  const res = await fetch(ENDPOINT, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(method + " error: " + JSON.stringify(json.error));
  return json.result;
}

async function main() {
  const bundle = { txs: RAW_TXS, blockNumber: TARGET_BLOCK };

  // 1. Simulate against the target block
  const sim = await rpc("eth_callBundle", [bundle, TARGET_BLOCK]);
  console.log("simulation:", JSON.stringify(sim, null, 2));

  // 2. Submit
  const submitted = await rpc("eth_sendBundle", [bundle]);
  console.log("submitted bundle hash:", submitted.bundleHash);

  // 3. Verify inclusion in the target block
  const block = await rpc("eth_getBlockByNumber", [TARGET_BLOCK, false]);
  if (!block) {
    console.log("target block not yet produced");
    return;
  }
  const included = block.transactions;
  const expected = RAW_TXS.map((raw) => {
    // derive hash from raw tx using your signing library, e.g. ethers.Transaction.from(raw).hash
    return raw;
  });
  console.log("block tx count:", included.length);
  console.log("bundle members present:", expected.every((h) => included.includes(h)));
}

main().catch((e) => { console.error(e.message); process.exit(1); });

Tabla de resultados: medir contra tu propio endpoint

Como los endpoints de bundles y sus conjuntos de métodos varían según el proveedor, la única forma fiable de caracterizar uno es medirlo tú mismo. Rellena la tabla siguiente para cada endpoint que pretendas usar. No asumas que los valores de otro proveedor son transferibles.

Ejecuta el script anterior contra cada endpoint y registra el resultado. El patrón de resultados te dice si el endpoint es siquiera un endpoint de builder, si admite simulación y si tus bundles están llegando a la fase de inclusión.

  • URL del endpoint: el endpoint del builder que probaste.
  • eth_callBundle compatible: sí / no / método no encontrado.
  • eth_sendBundle compatible: sí / no / método no encontrado.
  • Resultado de la simulación: éxito con valores de gas y coinbase, o error.
  • Respuesta del envío: hash del bundle devuelto, o error.
  • Inclusión observada en el bloque objetivo: sí / no / bloque aún no producido.

Las tres clases de fallo, mantenidas separadas

Los bundles mal formados son inválidos: un error de firma, nonce o gas hace que el bundle sea inutilizable, y esto se manifiesta como un error JSON-RPC de eth_callBundle. La solución es volver a firmar o reconstruir el miembro problemático y volver a simular. Esta clase es determinista y reproducible.

Los bundles válidos pero no rentables se simulan con éxito, pero el builder encontró una puja mejor. Esto se manifiesta como un envío que devuelve un hash de bundle y luego simplemente no aparece en el bloque objetivo. No hay error que capturar; el bundle era correcto y perdió por economía. La solución es mejorar la puja o la oportunidad, no depurar la petición.

Los fallos por endpoint incorrecto ocurren cuando la llamada nunca llegó a un builder, manifestándose como un método no encontrado o una forma de respuesta inesperada. Esta es la clase que produce el clásico resultado vacío de eth_sendBundle: un bundle correctamente formado enviado a un endpoint que no implementa el método. Verifica el endpoint antes de interpretar cualquier respuesta.

  • Mal formado: error JSON-RPC de eth_callBundle; se soluciona volviendo a firmar.
  • Válido pero no rentable: se devuelve el hash del bundle, sin inclusión; se soluciona mejorando la puja.
  • Endpoint incorrecto: método no encontrado o forma inesperada; se soluciona usando un endpoint de builder.

Interacción entre nonces y reemplazo

Como los miembros del bundle están prefirmados, reemplazar un miembro implica volver a firmar todo el bundle. Si la primera transacción de un bundle ya fue incluida por otra vía, los miembros restantes fallan de forma atómica o, si se envían por separado, compiten por el nonce. Esta es la interacción que más problemas causa en producción.

El lado de lectura de la gestión de nonces se cubre en Gestión de nonces EVM con eth_getTransactionCount, y la semántica de reemplazo para transacciones ordinarias se cubre en Reemplazo de eth_sendRawTransaction y errores de comisión insuficiente. Los bundles no heredan esas reglas de reemplazo; se reconstruyen, no se reemplazan.

Si necesitas inspeccionar qué está ya pendiente antes de reconstruir, la página El pool de transacciones de Ethereum y el espacio de nombres txpool describe la superficie de inspección. Tras la inclusión, eth_getBlockReceipts: recibos en bloque en una sola llamada es una forma cómoda de confirmar el estado de cada miembro en una sola petición.

  • Reemplazar un miembro del bundle requiere volver a firmar todo el bundle.
  • Un bundle parcialmente incluido deja a los miembros restantes en fallo atómico o compitiendo por el nonce.
  • Los bundles se reconstruyen, no se reemplazan, a diferencia de las transacciones ordinarias.

Solución de problemas: diagnosticar un resultado vacío de eth_sendBundle

Un resultado vacío de eth_sendBundle es el síntoma más común reportado en foros públicos, y se corresponde con una de las tres clases de fallo. Recórrelas en orden: primero confirma que el endpoint implementa el método, luego confirma que el bundle se simula y después confirma que el bloque objetivo sigue siendo actual.

Si eth_callBundle devuelve un error JSON-RPC, el bundle está mal formado. Si tiene éxito pero el envío devuelve un hash de bundle y no sigue ninguna inclusión, el bundle era válido pero perdió por economía. Si el propio envío devuelve un método no encontrado o una forma inesperada, el endpoint no es un endpoint de builder.

Un bloque objetivo obsoleto es un fallo silencioso: el bundle está bien formado y puede simularse, pero no es elegible para el bloque al que iba dirigido. Comprueba siempre el número del bloque objetivo contra la cabeza actual antes de interpretar una no inclusión como una pérdida económica.

  • Confirma que el endpoint implementa eth_sendBundle antes de interpretar cualquier respuesta.
  • Ejecuta eth_callBundle; un error JSON-RPC significa mal formado.
  • Un hash de bundle sin inclusión significa válido pero no rentable.
  • Un método no encontrado significa endpoint incorrecto.
  • Un bloque objetivo obsoleto es un fallo silencioso, no económico.

Limitaciones y compensaciones

La inclusión de un bundle es una decisión comercial del builder, no una garantía del protocolo. Nada en este artículo es una afirmación sobre la tasa de inclusión de ningún proveedor. Un bundle que se simula con éxito puede no incluirse nunca, y no existe ningún mecanismo on-chain que obligue a un builder a aceptarlo.

Apunta a un bloque no finalizado significa que tu propia política de reorg y confirmación sigue aplicándose. Un bundle incluido en un bloque que luego se reorganiza no es un resultado liquidado. Trata la inclusión como provisional hasta que se cumpla tu umbral de confirmación.

Los endpoints de builders y sus conjuntos de métodos cambian sin previo aviso. Los métodos documentados hoy pueden renombrarse, eliminarse o complementarse. Construye tu integración de modo que un método no encontrado o una forma de respuesta inesperada se manejen explícitamente en lugar de darse por supuestos.

  • La inclusión es una decisión comercial del builder, no una garantía del protocolo.
  • Aquí no se afirma nada sobre la tasa de inclusión de ningún proveedor.
  • Los bloques objetivo no finalizados implican que tu política de reorg sigue aplicándose.
  • Los endpoints de builders y sus conjuntos de métodos cambian sin previo aviso.

Próximos pasos: construir un pipeline de bundles

Empieza midiendo el endpoint que elijas con la tabla de resultados anterior. Confirma que eth_callBundle y eth_sendBundle son compatibles y registra las formas de respuesta que observes. Esto te da una línea base antes de construir nada encima.

Luego separa tus registros por clase de fallo. Registra los bundles mal formados con el error de simulación, registra los bundles válidos pero no rentables con el valor de coinbase que simulaste y registra los fallos por endpoint incorrecto por separado. Esto hace que las tres clases sean distinguibles en producción.

Para la infraestructura que los rodea, el hub de aprendizaje de OnFinality recopila las páginas adyacentes de transacciones, nonces y recibos, y las páginas del servicio de API y de precios de RPC describen la superficie de endpoint convencional que seguirás necesitando junto a cualquier endpoint de builder.

  • Mide tu endpoint antes de construir sobre él.
  • Registra por clase de fallo: mal formado, no rentable, endpoint incorrecto.
  • Mantén un endpoint estándar de Ethereum junto a cualquier endpoint de builder.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar