La arquitectura posterior a la Merge de Ethereum divide las capas de ejecución (EL) y consenso (CL). Los clientes ligeros verifican las cabeceras de la CL mediante firmas del comité de sincronización y luego confían en el hash de bloque de la EL al que comprometen. La Beacon REST API expone endpoints de cliente ligero: bootstrap inicializa el comité de sincronización, optimistic_update avanza la cabeza con una firma pero no es final, finality_update lleva un par de justificación/finalización, y finalized_root permite reinicializar. Este artículo explica cada endpoint, cómo verificar las actualizaciones contra el fork digest y proporciona ejemplos ejecutables en Node.js. También cubre la persistencia, la reinicialización a través de la rotación del período del comité de sincronización, una tabla de resultados para verificar actualizaciones en tu propio endpoint y las limitaciones honestas de la confianza del cliente ligero.
La división en dos capas tras la Merge
Desde la Merge, Ethereum funciona como dos capas distintas: una capa de ejecución (EL) que procesa transacciones y mantiene el estado, y una capa de consenso (CL) que ordena bloques y proporciona finalidad. La EL expone métodos JSON-RPC bajo el espacio de nombres eth_, mientras que la CL expone una REST API estandarizada por la especificación Ethereum Beacon API. Un cliente ligero debe verificar las cabeceras de la CL y luego confiar en el hash de bloque de la EL al que comprometen.
El mecanismo que vincula ambas capas es la cabecera de payload de ejecución incrustada dentro de cada bloque beacon. Cuando se verifica una cabecera de la CL, el cliente lee el campo body.execution_payload_header.block_hash, que es el compromiso que la capa de consenso hace a un bloque de ejecución específico. Dado que ese hash está cubierto por la firma del comité de sincronización sobre la raíz del bloque beacon, alterarlo invalidaría la firma, por lo que el hash de la EL hereda la garantía de minimización de confianza de la cabecera de la CL.
Esta división significa que un cliente ligero no puede verificar directamente el estado de la capa de ejecución sin pruebas adicionales. En su lugar, verifica la cabecera de la CL, extrae el hash de bloque de la EL y luego usa ese hash para consultar a un nodo de la EL los datos del bloque o las pruebas de estado. Para una visión más profunda de las pruebas de la capa de ejecución, consulta Pruebas de estado y almacenamiento eth_getProof de EVM.
- EL: espacio de nombres JSON-RPC
eth_, gestiona transacciones, estado y recibos. - CL: Beacon REST API, gestiona consenso, comités de sincronización y finalidad.
- Cliente ligero: verifica las cabeceras de la CL mediante firmas del comité de sincronización y luego confía en el hash de bloque de la EL.
Endpoints de cliente ligero de la Beacon API y sus roles
La Beacon API define cuatro endpoints de cliente ligero: bootstrap, optimistic_update, finality_update y finalized_root. Cada uno cumple un propósito específico en el protocolo de sincronización del cliente ligero. El endpoint bootstrap (GET /eth/v1/beacon/light_client/bootstrap/{block_root}) inicializa el comité de sincronización y la cabecera actual a partir de una raíz finalizada conocida. El endpoint optimistic_update (GET /eth/v1/beacon/light_client/optimistic_update) avanza la cabeza usando una firma del comité de sincronización, pero la cabeza que nombra no es final y puede sufrir reorganizaciones.
Cada respuesta lleva un objeto data con una header (o attested_header/finalized_header) más un sync_aggregate que contiene la firma BLS agregada y el bitfield de participación. La respuesta de bootstrap incluye además el current_sync_committee y el current_sync_committee_branch, que es una prueba Merkle que vincula el comité a la raíz de estado de la cabecera finalizada. El cliente almacena esa rama para poder probar posteriormente la pertenencia al comité sin volver a obtener el estado completo.
El endpoint finality_update (GET /eth/v1/beacon/light_client/finality_update) lleva un par de justificación/finalización firmado por un comité de sincronización, elevando el punto de control finalizado. El endpoint finalized_root (GET /eth/v1/beacon/light_client/finalized_root) proporciona una raíz finalizada que se puede usar para reinicializar. Estos endpoints están documentados en la especificación Ethereum Beacon API.
- Bootstrap: inicializa el comité de sincronización y la cabecera actual a partir de una raíz finalizada.
- Actualización optimista: avanza la cabeza con la firma del comité de sincronización, no es final.
- Actualización de finalidad: lleva un par de justificación/finalización, eleva el punto de control finalizado.
- Raíz finalizada: proporciona una raíz finalizada para reinicializar.
Por qué las actualizaciones optimistas minimizan la confianza pero no son finales
Una actualización optimista minimiza la confianza respecto al comité de sincronización: incluye una firma de una supermayoría del comité, que se selecciona aleatoriamente y rota periódicamente. Sin embargo, la cabeza que nombra no es final y puede sufrir reorganizaciones. Las aplicaciones que dependen de la cabeza de inmediato pueden ser vulnerables a reorganizaciones cortas. Para estrategias de detección de reorganizaciones, consulta Detección de reorganizaciones de bloques de Ethereum y profundidad RPC.
La razón por la que la cabeza no es final es que la finalidad en Ethereum requiere dos épocas justificadas consecutivas, lo que tarda al menos dos épocas (aproximadamente 12,8 minutos en condiciones normales) en completarse. Una actualización optimista solo prueba que una supermayoría del comité de sincronización actual atestiguó una cabecera; no prueba que la época correspondiente fuera justificada o finalizada. Por lo tanto, todavía puede ocurrir una reorganización más profunda que la cabeza optimista antes de que la finalidad la alcance.
En contraste, una actualización de finalidad eleva el punto de control finalizado, que es duradero y extremadamente improbable que se revierta. Este es el ancla que una aplicación debe usar para acciones irreversibles. La distinción es crítica: las actualizaciones optimistas proporcionan información de cabeza de baja latencia, mientras que las actualizaciones de finalidad proporcionan garantías de seguridad.
- Actualización optimista: firmada por el comité de sincronización, pero la cabeza puede sufrir reorganizaciones.
- Actualización de finalidad: eleva el punto de control finalizado, ancla duradera.
- Usa actualizaciones optimistas para lecturas de baja latencia; usa actualizaciones de finalidad para acciones irreversibles.
Verificación de firmas del comité de sincronización y fork digest
Las firmas del comité de sincronización se verifican contra la pubkey agregada y el fork digest. El fork digest se deriva de la versión del fork y la raíz de validadores génesis, lo que garantiza que se rechacen las actualizaciones del fork incorrecto. Las especificaciones de consenso de Ethereum definen el procedimiento exacto de verificación. Un cliente ligero debe calcular la raíz de firma, agregar las claves públicas y verificar la firma BLS.
Concretamente, el cliente reconstruye el SyncAggregate tomando el bitfield sync_committee_bits y seleccionando las pubkeys de los miembros del comité cuyos bits están activados, luego las agrega. Calcula la raíz de firma como la raíz del árbol hash de un contenedor SigningData que contiene la raíz del objeto y el dominio, donde el dominio se construye a partir del fork digest y el tipo DOMAIN_SYNC_COMMITTEE. Luego se ejecuta FastAggregateVerify de BLS sobre la raíz de firma, la pubkey agregada y la sync_committee_signature.
La versión del fork se incluye en la cabecera y debe coincidir con la versión de fork esperada por el cliente. Si un cliente recibe una actualización con una versión de fork que no coincide, debe rechazarla. Esto evita ataques de repetición entre forks y garantiza que el cliente siga la cadena correcta.
- Verifica la firma BLS contra la pubkey agregada.
- Comprueba que el fork digest coincida con la versión de fork esperada.
- Rechaza actualizaciones con una versión de fork que no coincida.
Traspaso a la capa de ejecución: hash de bloque de la EL y eth_getBlockByHash
Una vez verificada una cabecera de la CL, el cliente ligero extrae el hash de bloque de la EL de la cabecera. Este hash es el punto de traspaso a las lecturas de la capa de ejecución. El cliente puede entonces llamar a eth_getBlockByHash en un nodo de la EL para recuperar el bloque completo, o eth_getProof para verificar un estado específico. Para más información sobre consultas a la EL, consulta la Guía de nodos RPC de Ethereum (RPC Assistant).
El traspaso es tan sólido como el nodo de la EL que consultes. Si el nodo de la EL es honesto, devuelve el bloque cuyo hash coincide con el compromiso verificado; si no lo es, puede devolver un bloque diferente o un estado obsoleto, y el cliente ligero no tiene forma de detectarlo solo a partir de la cabecera de la CL. Por eso el hash de la EL es un compromiso, no una prueba: vincula la cabecera de la CL a un bloque de la EL, pero no prueba nada sobre el contenido de ese bloque.
Es importante señalar que el cliente ligero no verifica el estado de la capa de ejecución en sí; confía en que el nodo de la EL devuelva datos correctos para el hash de bloque dado. Para pruebas de estado sin confianza, se necesitan mecanismos adicionales como eth_getProof, que se cubren en Pruebas de estado y almacenamiento eth_getProof de EVM.
- Extrae el hash de bloque de la EL de la cabecera de la CL verificada.
- Usa
eth_getBlockByHashpara obtener el bloque completo del nodo de la EL. - El cliente ligero confía en el nodo de la EL para el estado; usa
eth_getProofpara pruebas sin confianza.
Persistencia y reinicialización a través de los períodos del comité de sincronización
Los comités de sincronización rotan aproximadamente cada 256 épocas (aproximadamente 27 horas), un período documentado en las especificaciones de consenso y sujeto a cambios con las actualizaciones de la red. Un cliente ligero debe persistir los datos de bootstrap y el período actual. Cuando cambia el período, el cliente debe reinicializar usando una raíz finalizada del nuevo período. El endpoint finalized_root proporciona dicha raíz.
La rotación no es un corte brusco en un solo slot; el comité para el período N es válido para un rango de slots, y el cliente debe rastrear el campo period devuelto junto con las respuestas de bootstrap y actualización. Cuando el slot actual cruza a un nuevo período, las firmas del comité antiguo ya no se verifican contra la pubkey agregada del nuevo comité, por lo que el cliente debe obtener un bootstrap nuevo cuyo current_sync_committee pertenezca al nuevo período. Persistir el bootstrap significa almacenar la cabecera, el comité, la rama del comité y el índice de período para que el cliente pueda reanudar sin volver a obtener todo desde cero tras un reinicio.
No reinicializar dará como resultado firmas inválidas, ya que el comité de sincronización cambia. Los clientes deben monitorear el período y activar la reinicialización cuando sea necesario. Este es un detalle operativo crítico para clientes ligeros de larga duración.
- Los comités de sincronización rotan cada ~256 épocas (~27 horas).
- Persiste los datos de bootstrap y el período actual.
- Reinicializa cuando cambie el período usando finalized_root.
Ejemplo ejecutable en Node.js: bootstrap, actualización optimista, actualización de finalidad
El siguiente script de Node.js usa fetch para interactuar con un endpoint de la Beacon API. Se inicializa desde una raíz finalizada conocida, obtiene la actualización optimista, obtiene la actualización de finalidad e imprime la versión del fork. Reemplaza BEACON_API_URL con el endpoint de tu proveedor. Ten en cuenta que el flag de servidor de cliente ligero puede estar deshabilitado en algunos nodos de consenso, por lo que los endpoints pueden devolver 404.
Este ejemplo asume que tienes una raíz finalizada confiable. En la práctica, la obtendrías de una fuente confiable o de una actualización de finalidad anterior. El script imprime el slot y la raíz de cabeza de la actualización optimista, el slot y la raíz finalizados de la actualización de finalidad, y la versión del fork de la cabecera.
const BEACON_API_URL = 'https://your-beacon-node.example.com';
const FINALIZED_ROOT = '0x...'; // Replace with a known finalized root
async function fetchLightClientData() {
// 1. Bootstrap
const bootstrapRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/bootstrap/${FINALIZED_ROOT}`);
const bootstrap = await bootstrapRes.json();
console.log('Bootstrap slot:', bootstrap.data.header.beacon.slot);
console.log('Fork version:', bootstrap.data.header.beacon.fork_version);
// 2. Optimistic update
const optimisticRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/optimistic_update`);
const optimistic = await optimisticRes.json();
console.log('Optimistic slot:', optimistic.data.attested_header.beacon.slot);
console.log('Optimistic head root:', optimistic.data.attested_header.beacon.body_root);
// 3. Finality update
const finalityRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/finality_update`);
const finality = await finalityRes.json();
console.log('Finalized slot:', finality.data.finalized_header.beacon.slot);
console.log('Finalized root:', finality.data.finalized_header.beacon.body_root);
// 4. Fork version from header
console.log('Fork version from finality header:', finality.data.finalized_header.beacon.fork_version);
}
fetchLightClientData().catch(console.error);Persistencia del bootstrap y reinicialización en la rotación de período
Un cliente ligero de larga duración no puede mantener el bootstrap solo en memoria; debe escribir la cabecera de bootstrap, el comité de sincronización actual, la rama del comité y el índice de período en almacenamiento duradero para que un reinicio no fuerce una resincronización completa. Una disposición práctica es un pequeño registro JSON o clave-valor indexado por la raíz finalizada usada en el bootstrap, con el índice de período almacenado junto a él para que el cliente pueda compararlo con el período implícito en el slot de la última actualización.
El disparador de reinicialización es una discrepancia de período: cuando el slot de una actualización entrante cae en un período diferente al almacenado, el cliente obtiene un nuevo bootstrap del endpoint finalized_root y reemplaza el comité y la rama almacenados. Dado que la raíz finalizada es en sí misma un punto de control finalizado, el nuevo bootstrap está anclado a la misma suposición de seguridad que el original, y el cliente puede descartar el comité antiguo una vez que se verifica el nuevo.
Operativamente, esto significa que el cliente debe tratar el bootstrap como una caché con una regla de invalidación explícita en lugar de como una inicialización única. Registrar el índice de período y la raíz finalizada en cada reinicialización permite auditar si el cliente se mantuvo en el comité correcto a través de las rotaciones, y saca a la luz casos en los que un proveedor devolvió una raíz finalizada obsoleta.
- Persiste la cabecera, el comité de sincronización, la rama del comité y el índice de período.
- Activa la reinicialización cuando el slot de la actualización cruce a un nuevo período.
- Obtén el nuevo bootstrap del endpoint finalized_root y reemplaza el comité almacenado.
- Registra el índice de período y la raíz finalizada en cada reinicialización para auditoría.
const BEACON_API_URL = 'https://your-beacon-node.example.com';
// Minimal in-memory store; swap for a file or database in production.
let store = { period: null, finalizedRoot: null, committee: null };
async function bootstrapFrom(finalizedRoot) {
const res = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/bootstrap/${finalizedRoot}`);
const { data } = await res.json();
store = {
period: data.current_sync_committee_branch ? data.header.beacon.slot : null,
finalizedRoot,
committee: data.current_sync_committee,
};
return data;
}
async function maybeRebootstrap(updateSlot, currentPeriod) {
if (store.period !== currentPeriod) {
const rootRes = await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/finalized_root`);
const { data } = await rootRes.json();
await bootstrapFrom(data.root);
console.log('Re-bootstrapped for period', currentPeriod, 'at slot', updateSlot);
}
}
// Example: call maybeRebootstrap(updateSlot, periodFromSlot(updateSlot)) on each update.Tabla de resultados reproducible para tu endpoint
Para medir el comportamiento de tu propio endpoint, completa la siguiente tabla con valores de tus ejecuciones. Esto ayuda a comparar proveedores y detectar anomalías. Ejecuta el script varias veces para observar los avances de slot y el retraso de finalidad.
La tabla debe incluir: slot de bootstrap, avance de slot optimista (diferencia entre los slots optimista y de bootstrap), slot de finalidad, raíz finalizada, versión de fork y período del comité de sincronización. Registra estos valores a lo largo del tiempo para comprender el rendimiento de tu endpoint.
- Slot de bootstrap: el slot de la cabecera devuelta por bootstrap.
- Avance de slot optimista: slot optimista menos slot de bootstrap.
- Slot de finalidad: el slot de la cabecera finalizada.
- Raíz finalizada: la raíz del cuerpo de la cabecera finalizada.
- Versión de fork: de la cabecera (p. ej., 0x03000000).
- Período del comité de sincronización: índice del período actual.
Tabla de resultados: verificar actualizaciones de cliente ligero en tu propio endpoint
La siguiente tabla es una plantilla para registrar el resultado de cada paso de verificación contra tu propio endpoint de la Beacon API. Dado que el comportamiento del cliente ligero depende del proveedor, la red y el momento de la consulta, se espera que los valores varíen entre ejecuciones; el objetivo es capturarlos de forma consistente para poder comparar endpoints y detectar regresiones. Completa una fila por ejecución y guarda las respuestas JSON sin procesar junto a la fila para inspeccionarlas después.
Cuando verificas una actualización, las comprobaciones que importan son: la firma BLS se verifica contra la pubkey agregada derivada del comité almacenado, el fork digest coincide con tu versión de fork esperada, la raíz finalizada en la actualización de finalidad coincide con la raíz que usarías para reinicializar, y el período implícito en el slot de la actualización coincide con el período almacenado. Registrar cada comprobación como aprobada o fallida, en lugar de solo el veredicto final, hace obvio qué paso falló cuando algo va mal.
Usa la tabla como un artefacto vivo: vuelve a ejecutarla tras cambios de proveedor, tras actualizaciones de red que alteren la versión de fork y tras cualquier cambio en tu propia lógica de persistencia. Dado que el comité de sincronización rota, una tabla que era precisa para un período puede no serlo para el siguiente, así que incluye el índice de período en cada fila.
- Ejecución: marca de tiempo o identificador de ejecución.
- Slot de bootstrap y raíz finalizada usada.
- Slot optimista y avance de slot sobre el bootstrap.
- Slot de finalidad y raíz finalizada devuelta.
- Versión de fork observada en la cabecera.
- Índice de período del comité de sincronización.
- Resultado de verificación de firma (aprobado/fallido).
- Resultado de coincidencia de fork digest (aprobado/fallido).
// Sketch of a verification harness that prints one table row per run.
async function verifyOnce() {
const bootstrap = await (await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/bootstrap/${FINALIZED_ROOT}`)).json();
const optimistic = await (await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/optimistic_update`)).json();
const finality = await (await fetch(`${BEACON_API_URL}/eth/v1/beacon/light_client/finality_update`)).json();
const row = {
bootstrapSlot: bootstrap.data.header.beacon.slot,
optimisticSlot: optimistic.data.attested_header.beacon.slot,
finalitySlot: finality.data.finalized_header.beacon.slot,
finalizedRoot: finality.data.finalized_header.beacon.body_root,
forkVersion: finality.data.finalized_header.beacon.fork_version,
};
console.log(JSON.stringify(row));
}
verifyOnce().catch(console.error);Solución de problemas comunes del cliente ligero de la Beacon API
Si el endpoint bootstrap devuelve 404, el flag de servidor de cliente ligero puede estar deshabilitado en tu nodo de consenso. Consulta la documentación de tu proveedor. Algunos proveedores no habilitan los endpoints de cliente ligero por defecto. Para obtener una lista de nodos de Ethereum, consulta la Guía de nodos RPC de Ethereum (RPC Assistant).
Un 404 también puede significar que la raíz finalizada que pasaste no es conocida por ese nodo, por ejemplo si el nodo todavía se está sincronizando o si la raíz pertenece a una red diferente. Distinguir ambos casos es sencillo: consulta un endpoint que se sabe que funciona, como la ruta de versión o de salud del nodo, y confirma la red antes de asumir que el flag de cliente ligero está desactivado. Si el nodo está sano y en la red correcta pero sigue devolviendo 404 en bootstrap, el flag es la causa probable.
Si falla la verificación de firma, asegúrate de que la versión de fork coincida y de que estés usando la pubkey agregada correcta del bootstrap. Si el período ha cambiado, reinicializa. Si encuentras límites de tasa, considera usar un proveedor dedicado como el servicio API de OnFinality o consulta Precios de RPC para obtener límites más altos.
- 404 en bootstrap: el flag de servidor de cliente ligero puede estar deshabilitado.
- Fallo de verificación de firma: comprueba la versión de fork y la pubkey agregada.
- Cambio de período: reinicializa con finalized_root.
- Límites de tasa: usa un proveedor con límites más altos.
Limitaciones y compensaciones de la confianza del cliente ligero
La Beacon API es una interfaz REST del cliente de la CL cuyas rutas están estandarizadas por las especificaciones de consenso, pero la disponibilidad y los límites de tasa varían según el proveedor. El flag de servidor de cliente ligero puede estar deshabilitado en algunos nodos de consenso, provocando 404. La verificación del comité de sincronización en un navegador todavía requiere la pubkey agregada del mismo bootstrap confiable, por lo que el modelo de confianza no es completamente sin confianza.
El modelo de confianza se describe mejor como una cadena de suposiciones: confías en la fuente de bootstrap para darte una raíz finalizada correcta, confías en que el comité de sincronización sea honesto y suficientemente descentralizado, y confías en el nodo de la EL para los datos de la capa de ejecución. Cada eslabón es más débil que un cliente de consenso completo que valida cada bloque desde el génesis, pero la combinación es mucho más barata que ejecutar un nodo completo y es adecuada para muchas aplicaciones orientadas a lectura.
Un cliente ligero proporciona finalidad con confianza minimizada, no una prueba de estado sin confianza completa de almacenamiento arbitrario de la capa de ejecución. Para pruebas de estado sin confianza, necesitas mecanismos adicionales como eth_getProof. Además, el cliente ligero confía en el nodo de la EL para los datos de la capa de ejecución, por lo que el modelo de confianza general es una combinación de verificación de la CL y confianza en la EL. Para más información sobre la confianza en la EL, consulta Nodo de archivo de Ethereum y RPC histórico.
- La disponibilidad y los límites de tasa de la Beacon API varían según el proveedor.
- El flag de servidor de cliente ligero puede estar deshabilitado, provocando 404.
- La verificación en el navegador requiere la pubkey agregada del bootstrap confiable.
- El cliente ligero ofrece finalidad con confianza minimizada, no pruebas de estado sin confianza completas.
Próximos pasos: integrar actualizaciones de cliente ligero en tu aplicación
Para integrar actualizaciones de cliente ligero, comienza seleccionando un proveedor que admita los endpoints de cliente ligero de la Beacon API. Usa el endpoint bootstrap para inicializar, luego sondea optimistic_update para información de cabeza y finality_update para la finalidad. Persiste el bootstrap y el período, y reinicializa cuando cambie el período. Para producción, considera usar un proveedor confiable como el servicio API de OnFinality y revisa Precios de RPC para tus necesidades.
Un patrón de integración razonable es ejecutar un bucle en segundo plano que obtenga la actualización optimista en un intervalo corto para el seguimiento de la cabeza y la actualización de finalidad en un intervalo más largo para el ancla duradera, escribiendo ambas en tu capa de persistencia. Condiciona las acciones irreversibles solo al punto de control finalizado y trata la cabeza optimista como informativa. Cuando cambie el índice de período, pausa el bucle, reinicializa y reanuda una vez que el nuevo comité se verifique.
Para lecturas adicionales, explora el centro de aprendizaje de OnFinality para obtener más guías sobre fiabilidad y consistencia de Ethereum. Además, consulta la página de la red Ethereum para detalles específicos de la red. Mientras construyes, monitorea el estado de sincronización de tu nodo con eth_syncing de Ethereum y monitoreo del estado de sincronización del nodo para asegurarte de que tu nodo de la EL esté sano.
- Elige un proveedor con soporte para endpoints de cliente ligero.
- Inicializa con bootstrap y luego sondea las actualizaciones optimista y de finalidad.
- Persiste el bootstrap y el período; reinicializa al cambiar el período.
- Monitorea el estado de sincronización del nodo de la EL para su salud.