Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Infraestructura y operaciones14 min de lectura

Controlar un nodo de Base con la Engine API: newPayload y forkchoiceUpdated

Un análisis técnico profundo del contrato de la Engine API de OP-Stack que conecta op-node con op-geth en Base, con ejemplos ejecutables y un manual de solución de problemas.

TL;DR

La Engine API es la interfaz JSON-RPC autenticada que conecta un cliente de consenso de OP-Stack (op-node) con un motor de ejecución (op-geth u op-reth) en Base. No es el RPC público eth; se ejecuta en un puerto local y secreto protegido por autenticación JWT. Las tres familias de métodos principales son engine_newPayloadV{n} (validar e importar un payload), engine_forkchoiceUpdatedV{n} (establecer head/safe/finalized y, opcionalmente, iniciar la construcción de bloques) y engine_getPayloadV{n} (recuperar un payload construido). Comprender los valores de payloadStatus (VALID, INVALID, SYNCING, ACCEPTED) y latestValidHash es esencial para diagnosticar problemas de sincronización y derivación. Este artículo proporciona ejemplos ejecutables, una tabla de resultados para autoevaluación y un manual de solución de problemas.

La arquitectura de dos clientes de OP-Stack en Base

Base es un rollup de OP-Stack. Su software de nodo se divide en dos procesos cooperativos: una capa de consenso/derivación llamada op-node, y un motor de ejecución como op-geth, op-reth u op-erigon. El op-node deriva la cadena canónica a partir de los datos de L1 (los lotes del secuenciador y las raíces de estado publicadas en Ethereum), mientras que el motor de ejecución mantiene el estado de la EVM, ejecuta transacciones y sirve los métodos JSON-RPC públicos eth.

Estos dos procesos se comunican a través de la Engine API, una interfaz JSON-RPC separada que se ejecuta en un puerto local y secreto (habitualmente el 8551) y está protegida por autenticación JWT. Esto es deliberadamente distinto del puerto RPC público eth (habitualmente el 8545), al que se conectan las carteras, los indexadores y las dApps. La Engine API es un plano de control de operador/clúster, no un plano de datos público.

El artículo Estado de sincronización del nodo OP Stack de Base y la Engine API explica cómo leer el estado de sincronización desde el lado de op-node. Este artículo profundiza más: documenta el propio contrato de los métodos engine_*, la semántica de payloadStatus y la secuencia que sigue op-node para controlar el motor de ejecución.

  • op-node: capa de consenso/derivación, deriva la cadena desde L1, controla el motor de ejecución.
  • Motor de ejecución (op-geth/op-reth/op-erigon): estado de la EVM, ejecución de transacciones, RPC público eth.
  • Engine API: JSON-RPC autenticado en un puerto local (p. ej., 8551), protegido por JWT.
  • RPC público eth: sin autenticación, sirve a dApps y carteras; nunca debe exponer los métodos engine_*.

Por qué los métodos engine_* nunca deben exponerse públicamente

La Engine API puede cambiar el head canónico, importar payloads arbitrarios e iniciar la construcción de bloques. Si se expone en una interfaz pública, un atacante podría forzar al nodo a aceptar payloads inválidos, reorganizar la cadena o detener la producción de bloques. El requisito de JWT es una frontera de seguridad estricta, no una comodidad.

En Base, el op-node y el motor de ejecución suelen ejecutarse en el mismo host o en una red privada. El puerto del motor debe estar vinculado a localhost o a una interfaz privada y protegido por un cortafuegos. El secreto JWT es una cadena hexadecimal de 32 bytes compartida entre op-node y el motor de ejecución, que se pasa mediante una ruta de archivo (p. ej., --authrpc.jwtsecret).

Para los integradores que solo necesitan observar la cadena, el RPC de administración público de op-node (espacios de nombres opt_ y admin_) es la superficie de solo lectura que se debe preferir. Expone el estado de sincronización, la configuración del rollup y la información de derivación sin el riesgo de mutar el motor de ejecución.

  • La Engine API puede mutar el head canónico e importar payloads; trátala como un plano de control.
  • Vincula el puerto del motor a localhost o a una interfaz privada; nunca lo expongas a Internet.
  • Usa autenticación JWT (--authrpc.jwtsecret) compartida entre op-node y el motor de ejecución.
  • Para observación, prefiere el RPC de administración de op-node (opt_/admin_) en lugar de los métodos engine_*.

Las tres familias de métodos principales de la Engine API y su versionado

La Engine API se define en la especificación execution-apis de Ethereum. Tres familias de métodos son importantes para controlar un motor de ejecución de OP-Stack: engine_newPayloadV{n}, engine_forkchoiceUpdatedV{n} y engine_getPayloadV{n}. El sufijo de versión (V1, V2, V3) está fijado al fork; quien llama debe usar la versión que coincida con el fork actual de la cadena y el conjunto admitido por el cliente de ejecución.

engine_newPayloadV{n}(payload, expectedBlobVersionedHashes, parentBeaconBlockRoot) valida e importa un payload de ejecución. Devuelve un objeto payloadStatus con status, latestValidHash y validationError. En las cadenas de OP-Stack, el parámetro parentBeaconBlockRoot suele ser null porque no hay cadena de balizas; el conjunto exacto de parámetros varía según la versión y el cliente.

engine_forkchoiceUpdatedV{n}(forkchoiceState, payloadAttributes) establece los hashes de bloque head, safe y finalized. Cuando payloadAttributes está presente, también inicia la construcción de bloques y devuelve un payloadId. engine_getPayloadV{n}(payloadId) recupera el payload construido, que quien llama inserta después mediante la siguiente llamada newPayload.

El sufijo de versión debe coincidir con el fork actual de la cadena. Este es un comportamiento documentado que varía según el fork y la versión del cliente; consulta las notas de la versión del cliente de ejecución y la especificación execution-apis para el mapeo exacto.

  • engine_newPayloadV{n}: valida e importa un payload de ejecución; devuelve payloadStatus.
  • engine_forkchoiceUpdatedV{n}: establece head/safe/finalized; con payloadAttributes, inicia la construcción y devuelve payloadId.
  • engine_getPayloadV{n}: recupera el payload construido por payloadId para insertarlo mediante newPayload.
  • El sufijo de versión (V1/V2/V3) está fijado al fork; debe coincidir con el fork de la cadena y el soporte del cliente.

Interpretar un payloadStatus: VALID, INVALID, SYNCING, ACCEPTED

Cada llamada a engine_newPayloadV{n} devuelve un objeto payloadStatus. Según lo especificado en la especificación de la Engine API de execution-apis de Ethereum, el campo status es uno de VALID, INVALID, SYNCING o ACCEPTED, y el objeto también incluye latestValidHash y validationError. VALID significa que el payload se validó e importó. INVALID significa que el payload no superó la validación; latestValidHash nombra el último ancestro válido para que quien llama pueda revertir a él, y validationError contiene el motivo. SYNCING significa que al motor de ejecución le falta el padre y aún no puede validar. ACCEPTED significa que el payload se aceptó pero no se validó por completo (normalmente porque el padre es desconocido y el motor es optimista).

Para un resultado INVALID, latestValidHash es el campo crítico. El op-node lo usa para restablecer su forkchoice al último ancestro válido, descartando la rama inválida. Si latestValidHash es null o cero, el motor no pudo determinar un ancestro válido, lo que suele indicar un problema más profundo de estado o de configuración.

validationError es una cadena legible por humanos que a menudo contiene el motivo exacto: hash de bloque incorrecto, raíz de estado inválida, discrepancia en el límite de gas o error de marca de tiempo. Registrar este campo es esencial para solucionar fallos de derivación.

  • VALID: payload validado e importado.
  • INVALID: el payload no superó la validación; latestValidHash nombra el último ancestro válido; validationError contiene el motivo.
  • SYNCING: al motor de ejecución le falta el padre; aún no puede validar.
  • ACCEPTED: payload aceptado pero no validado por completo (optimista).
  • latestValidHash null/cero en INVALID indica un problema más profundo.

La secuencia de op-node: forkchoiceUpdated, getPayload, newPayload

Cuando op-node construye un nuevo bloque (como secuenciador o durante la derivación), sigue una secuencia específica. Primero, llama a engine_forkchoiceUpdatedV{n} con el estado de forkchoice actual y payloadAttributes que describe el bloque a construir. El motor de ejecución comienza a construir y devuelve un payloadId. Segundo, op-node llama a engine_getPayloadV{n}(payloadId) para recuperar el payload construido. Tercero, op-node llama a engine_newPayloadV{n} con ese payload para validarlo e importarlo. Finalmente, op-node vuelve a llamar a engine_forkchoiceUpdatedV{n}, esta vez con el nuevo hash de head y sin payloadAttributes, para hacer canónico el bloque.

Llamar a engine_getPayloadV{n} antes de que una construcción haya producido un payload devuelve un error de payload desconocido. El payloadId solo es válido después de que una llamada a forkchoiceUpdated con payloadAttributes haya iniciado la construcción. Esta secuencia está documentada en la especificación del nodo de rollup de OP Stack.

En Base, los heads safe y finalized se derivan de forma diferente que en L1. El head safe corresponde a la cadena derivada de L1 que op-node ha procesado, mientras que el head finalized corresponde a la finalidad de L1. Esta interacción con la derivación de L1 se trata en Finalidad, bloques safe y finalized de Base OP Stack y Derivación de L1 y marca de tiempo de Base OP Stack.

  • Paso 1: forkchoiceUpdated con payloadAttributes → payloadId.
  • Paso 2: getPayload(payloadId) → payload construido.
  • Paso 3: newPayload(payload) → payloadStatus.
  • Paso 4: forkchoiceUpdated con el nuevo head, sin payloadAttributes → canónico.
  • getPayload antes de construir devuelve un error de payload desconocido.

Ejemplo ejecutable: engine_forkchoiceUpdatedV3 a través del puerto autenticado

El siguiente ejemplo de Node.js emite engine_forkchoiceUpdatedV3 sin payloadAttributes e imprime el estado resultante. Se conecta al puerto autenticado del motor (por defecto 8551) usando un secreto JWT. Este ejemplo es ilustrativo del esquema; ejecútalo contra un nodo que tú operes con un token de autenticación JWT coincidente.

El secreto JWT es una cadena hexadecimal de 32 bytes. El ejemplo lo lee de una ruta de archivo, genera un token firmado y envía la solicitud JSON-RPC. Sustituye los hashes del estado de forkchoice por valores de los bloques head, safe y finalized actuales de tu propio nodo.

const fs = require('fs');
const jwt = require('jsonwebtoken');
const fetch = require('node-fetch');

const JWT_SECRET = fs.readFileSync('/path/to/jwt.hex', 'utf8').trim();
const ENGINE_URL = 'http://127.0.0.1:8551';

function makeToken() {
  const payload = { iat: Math.floor(Date.now() / 1000) };
  return jwt.sign(payload, Buffer.from(JWT_SECRET, 'hex'), { algorithm: 'HS256' });
}

async function forkchoiceUpdatedV3() {
  const token = makeToken();
  const body = {
    jsonrpc: '2.0',
    id: 1,
    method: 'engine_forkchoiceUpdatedV3',
    params: [
      {
        headBlockHash: '0x...',      // replace with your current head
        safeBlockHash: '0x...',      // replace with your safe head
        finalizedBlockHash: '0x...'  // replace with your finalized head
      },
      null // no payloadAttributes: do not start building
    ]
  };
  const res = await fetch(ENGINE_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify(body)
  });
  const json = await res.json();
  console.log(JSON.stringify(json, null, 2));
  // Expect: { result: { payloadStatus: { status: 'VALID', latestValidHash: '0x...', validationError: null }, payloadId: null } }
}

forkchoiceUpdatedV3().catch(console.error);

Ejemplo ejecutable: forma JSON de engine_newPayloadV3 e interpretación

El siguiente ejemplo muestra la forma JSON de una llamada a engine_newPayloadV3 y cómo interpretar un payloadStatus VALID frente a INVALID. El objeto payload contiene los campos del payload de ejecución: parentHash, feeRecipient, stateRoot, receiptsRoot, logsBloom, prevRandao, blockNumber, gasLimit, gasUsed, timestamp, extraData, baseFeePerGas, blockHash y transactions. El segundo parámetro es expectedBlobVersionedHashes (un array, a menudo vacío en OP-Stack), y el tercero es parentBeaconBlockRoot (null en OP-Stack).

Después de enviar la solicitud, inspecciona el payloadStatus. Si status es VALID, el payload se importó. Si status es INVALID, lee latestValidHash para encontrar el último ancestro válido y validationError para conocer el motivo. Este ejemplo es ilustrativo; ejecútalo contra un nodo que tú operes.

const fs = require('fs');
const jwt = require('jsonwebtoken');
const fetch = require('node-fetch');

const JWT_SECRET = fs.readFileSync('/path/to/jwt.hex', 'utf8').trim();
const ENGINE_URL = 'http://127.0.0.1:8551';

function makeToken() {
  const payload = { iat: Math.floor(Date.now() / 1000) };
  return jwt.sign(payload, Buffer.from(JWT_SECRET, 'hex'), { algorithm: 'HS256' });
}

async function newPayloadV3() {
  const token = makeToken();
  const body = {
    jsonrpc: '2.0',
    id: 1,
    method: 'engine_newPayloadV3',
    params: [
      {
        parentHash: '0x...',
        feeRecipient: '0x...',
        stateRoot: '0x...',
        receiptsRoot: '0x...',
        logsBloom: '0x...',
        prevRandao: '0x...',
        blockNumber: '0x...',
        gasLimit: '0x...',
        gasUsed: '0x...',
        timestamp: '0x...',
        extraData: '0x...',
        baseFeePerGas: '0x...',
        blockHash: '0x...',
        transactions: []
      },
      [],   // expectedBlobVersionedHashes
      null  // parentBeaconBlockRoot (null on OP-Stack)
    ]
  };
  const res = await fetch(ENGINE_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    body: JSON.stringify(body)
  });
  const json = await res.json();
  console.log(JSON.stringify(json, null, 2));
  // If VALID: payload imported.
  // If INVALID: inspect latestValidHash and validationError.
}

newPayloadV3().catch(console.error);

Tabla de resultados: medir el comportamiento de la Engine API en tu propio nodo

Dado que el comportamiento de la Engine API varía según el fork, la versión del cliente y la configuración del nodo, el enfoque más fiable es medir contra tu propio nodo. Usa la tabla siguiente para registrar observaciones. Rellénala por nodo, por versión de método y por ejecución. No te fíes de cifras de terceros; el comportamiento de tu nodo es la verdad absoluta para tu despliegue.

Ejecuta cada método engine_* contra tu puerto autenticado y registra el estado devuelto, cualquier latestValidHash en INVALID, y el head vs safe vs finalized observados. Repite tras una actualización de fork para confirmar que la versión del método sigue coincidiendo.

  • Versión del método engine (p. ej., V3): registra el sufijo exacto usado.
  • Estado devuelto (VALID/INVALID/SYNCING/ACCEPTED): registra payloadStatus.status.
  • latestValidHash en cualquier INVALID: registra el hash o null.
  • head vs safe vs finalized observados: registra los tres hashes de tu nodo.
  • validationError: registra la cadena si está presente.
  • Versión del cliente y fork: registra la versión de op-geth/op-reth y el fork de la cadena.

Solución de problemas: fallos comunes de la Engine API y sus causas

La mayoría de los fallos de la Engine API se engloban en unas pocas categorías: autenticación, discrepancia de versión, errores de secuencia y fallos de validación de payload. Los fallos de autenticación devuelven HTTP 401 o un error JSON-RPC sobre falta de autorización; comprueba que el archivo del secreto JWT coincide entre op-node y el motor de ejecución y que el token no ha caducado.

Los errores de discrepancia de versión ocurren cuando el sufijo del método no coincide con el fork actual de la cadena. Por ejemplo, llamar a engine_newPayloadV2 en una cadena que requiere V3 devuelve un error de método no encontrado o de parámetros inválidos. Consulta las notas de la versión del cliente de ejecución y la especificación execution-apis para la versión correcta.

Los errores de secuencia incluyen llamar a engine_getPayloadV{n} antes de que una construcción haya producido un payload, lo que devuelve un error de payload desconocido. Asegúrate de que se haya llamado primero a forkchoiceUpdated con payloadAttributes. Los fallos de validación de payload devuelven INVALID con un validationError; lee latestValidHash para revertir al último ancestro válido.

Para problemas relacionados con la sincronización, el artículo Estado de sincronización del nodo OP Stack de Base y la Engine API cubre cómo leer los heads unsafe/safe/finalized. Para monitorización, consulta Monitorización de endpoints RPC.

  • 401/no autorizado: discrepancia en el secreto JWT o token caducado.
  • Método no encontrado / parámetros inválidos: el sufijo de versión no coincide con el fork.
  • Payload desconocido: se llamó a getPayload antes de que la construcción produjera un payload.
  • INVALID con validationError: lee latestValidHash y validationError para conocer la causa.
  • SYNCING: al motor de ejecución le falta el padre; espera a la sincronización o revisa la derivación de L1.

Limitaciones y compromisos de la Engine API

La Engine API está autenticada y fijada a una versión. No es una interfaz de indexación pública; pertenece a un nodo que tú ejecutas. El requisito de JWT implica que no puedes simplemente apuntar un cliente RPC público al puerto del motor. El sufijo de versión debe coincidir con el fork actual de la cadena, y este mapeo es un comportamiento documentado que varía según el fork y la versión del cliente.

Llamar a los métodos engine_* es una acción de operador/clúster, no una acción de indexación pública. Para observación, el RPC de administración público de op-node (espacios de nombres opt_/admin_) es la superficie de solo lectura que un integrador debería preferir. Expone el estado de sincronización y la información de derivación sin el riesgo de mutar el motor de ejecución.

En Base, los heads safe y finalized se derivan de forma diferente que en L1, lo que afecta a cómo forkchoiceUpdated los interpreta. Esta interacción con la derivación de L1 es una consideración clave para los operadores. Para detalles específicos de la red, consulta Base.

  • Autenticada: JWT en el puerto del motor; no para exposición pública.
  • Fijada a versión: el sufijo del método debe coincidir con el fork de la cadena; varía según fork y cliente.
  • Acción de operador: no es una interfaz de indexación pública; ejecútala en tu propio nodo.
  • Observación: prefiere el RPC de administración de op-node (opt_/admin_) para acceso de solo lectura.
  • La derivación de safe/finalized en Base difiere de L1; afecta a la semántica de forkchoice.

Próximos pasos: integrar el conocimiento de la Engine API en tus operaciones de Base

Si operas un nodo de Base, asegúrate de que tu op-node y tu motor de ejecución estén configurados con secretos JWT coincidentes y de que el puerto del motor esté vinculado a localhost o a una interfaz privada. Usa la tabla de resultados para establecer una línea base del comportamiento de la Engine API de tu nodo y vuelve a medir tras las actualizaciones de fork.

Para los integradores que necesitan observar Base sin ejecutar un nodo, el endpoint RPC de Base (RPC Assistant) proporciona una superficie RPC pública gestionada. Para la planificación de infraestructura, consulta Precios de RPC y Servicio de API. El centro de aprendizaje de OnFinality recopila guías relacionadas sobre operaciones de OP-Stack, finalidad y derivación.

Recuerda: la Engine API es un plano de control. Úsala para controlar tu propio nodo, no para servir tráfico público. Para observación pública, usa el RPC eth o el RPC de administración de op-node.

  • Configura secretos JWT coincidentes y vincula el puerto del motor de forma privada.
  • Establece una línea base del comportamiento de la Engine API con la tabla de resultados; vuelve a medir tras los forks.
  • Usa RPC gestionado para observación pública; consulta el endpoint RPC de Base (RPC Assistant).
  • Explora más guías en el centro de aprendizaje de OnFinality.

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