Ethereum: de historia a labs
Artículo 47 de 48
98%
completado al llegar aquí
Sistemas distribuidosNota de investigación13 min de lectura
Arquitectura production-ready para dApps
Arquitectura para depósitos y retiros sobre un custodio: confirmación por finalized, reorgs, idempotencia por nonce, conciliación y respuesta a incidentes.
Ruta Ethereum: 0/48
Prerequisitos recomendados
Si alguno de estos conceptos todavía está medio nebuloso, empieza por aquí. Te ahorra tropiezos y un par de cejas fruncidas.
Observabilidad Ethereum para dApps
Qué medir en una dApp Ethereum: atraso del RPC, del indexador y de la finalidad, ciclo de vida de transacciones y conciliación del índice contra la cadena.
Blockchain11 min de lectura→Indexadores: eventos, reorgs e idempotencia
Cómo indexar logs de Ethereum que sobreviven reintentos, backfills y reorgs: cursor con hash, etiquetas safe y finalized, y efectos idempotentes.
Blockchain14 min de lectura→Access control: owner, roles, multisig y timelocks
Autorización en contratos Ethereum: Ownable y Ownable2Step, roles con AccessControl, multisig con umbral y TimelockController para separar operación diaria de cambios de política.
Blockchain13 min de lectura→
Un cliente pide retirar 2 ETH. Tu servicio llama a la API del proveedor de custodia para que firme y difunda la transacción. La llamada se corta por timeout. ¿Reintentas? Si el primer pedido sí llegó, acabas de pagar dos veces. Si no reintentas y no llegó, el cliente espera para siempre.
Ese es el tipo de problema que separa una demo de un sistema en producción. Este capítulo arma la arquitectura de un servicio de depósitos y retiros sobre Ethereum cuando las llaves las guarda un custodio: un proveedor que conserva las claves privadas, firma transacciones según políticas y las envía a la red. El custodio resuelve la firma. La política de confirmación, la contabilidad, la idempotencia y la conciliación siguen siendo tuyas.
Flujo visual
Las piezas y quién responde por cada una
Brújula de lectura
Actor, evidencia y estado
Actor
Tu servicio y el custodioEl custodio firma; tu servicio decide cuándo pedir una firma y cuándo dar algo por hecho.
Evidencia
Recibo en finalizedUn recibo con status 1 en un bloque finalizado es la prueba que cierra un depósito o un retiro.
Estado
Libro contableSaldos pendientes y disponibles, cada movimiento atado a un bloque o a una intención única.
Preguntas guía
Usa estas preguntas para revisar un diseño real de tu equipo, no solo este capítulo:
- ¿En qué etiqueta de bloque pasa un depósito de pendiente a disponible, y quién aprobó esa decisión?
- Si el bloque de un depósito sale de la cadena canónica, ¿qué fila de tu base de datos cambia?
- ¿Qué identificador permite reintentar un retiro sin riesgo de pagarlo dos veces?
- ¿Cómo detectas depósitos de ETH nativo, que hoy no emiten logs en mainnet?
- ¿Con qué frecuencia comparas tu libro con el custodio y con la cadena, y qué haces si no cuadran?
Conexiones dentro de la serie
- Antes: Observabilidad Ethereum para dApps, cuyas métricas usa este diseño para frenar retiros.
- Después: De la serie a los labs: cómo practicar sin quemar ETH real, para ensayar reorgs y reintentos en local.
Prerequisitos internos recomendados:
- Indexadores: eventos, reorgs e idempotencia, para el cursor con hash y las etiquetas.
- Access control: owner, roles, multisig y timelocks, para los permisos de los contratos propios.
- Una transacción Ethereum por dentro, para nonce, gas y recibos.
Estado de un depósito: de latest a finalized
Un depósito no es un evento, es una máquina de estados. Proponemos cuatro estados. Detectado: viste la transferencia en un bloque latest. Pendiente: lo registras en el libro como saldo no disponible y se lo muestras al cliente. Disponible: el bloque alcanzó finalized y el saldo se puede retirar. Descartado: el bloque salió de la cadena canónica antes de finalizar.
La etiqueta finalized es la que la especificación describe como criptoeconómicamente segura: no se revierte salvo intervención manual coordinada por la comunidad. latest puede salir de la cadena incluso en condiciones normales. [1] Revertir un bloque finalizado exigiría que un atacante perdiera al menos un tercio del ETH en stake. [2]
De ahí sale la regla central para este tipo de servicio: un saldo solo es retirable si el depósito que lo origina está en un bloque finalizado. Mostrarlo antes como pendiente es buena experiencia de usuario. Permitir retirarlo antes convierte cada reorg en una pérdida.
La transición a Descartado no es teórica. Si el indexador retrocede por una reorg, todos los depósitos atados a los blockHash revertidos vuelven atrás. Si la misma transacción reaparece en la rama nueva, entra como un depósito nuevo, con su propio blockHash y logIndex, y recorre la máquina desde el principio.
Máquinas de estado de un depósito y de un retiro
Cómo leer esta figura
Carril superior, depósito: detectado en latest, pendiente con saldo no disponible, disponible cuando el bloque es finalized. Desde detectado y pendiente sale una flecha roja a descartado, rotulada reorg antes de finalized. Carril inferior, retiro: solicitado con clave de idempotencia y saldo reservado, enviado al custodio, difundido con remitente y nonce, incluido con recibo, y completado si el status es 1 y el bloque es finalized. Desde incluido sale una flecha a fallido si el status es 0, con el gas pagado; desde difundido sale una flecha de reemplazo con el mismo nonce y comisión mayor que vuelve a difundido; una reorg devuelve incluido a difundido.
Traza de ejecución de un depósito: qué mirar en la cadena
Para tokens ERC-20, el depósito se detecta por el evento Transfer(address indexed _from, address indexed _to, uint256 _value), que el estándar exige emitir en cada transferencia, incluso de valor cero. [3] Cualquier contrato puede emitir un evento con esa firma. Por eso el filtro debe incluir siempre la dirección del contrato del token que aceptas, no solo el topic del evento.
Un contrato de token puede tener lógica propia, por ejemplo una comisión al transferir, que haga que el evento no refleje lo que realmente recibió la dirección. Para los tokens que aceptes, conviene verificar al menos en la conciliación que el saldo real de la dirección de depósito, leído con balanceOf en finalized, coincide con la suma de lo acreditado.
El ETH nativo es otro caso. En el EVM, los logs solo se crean con los opcodes LOG0 a LOG4; una transferencia de ETH no ejecuta ninguno. [5] Una transferencia directa desde una cuenta externa se ve en la lista de transacciones del bloque. Una enviada por un contrato, como una smart wallet, no queda registrada como log en ninguna parte; para verla hace falta rastrear la ejecución o comparar saldos.
Eso puede cambiar. EIP-7708 propone que toda transferencia de ETH con valor distinto de cero hacia otra cuenta emita un log idéntico a un LOG3, con el mismo topic de Transfer y la dirección emisora 0xfffffffffffffffffffffffffffffffffffffffe. [6] Está programada para la actualización Glamsterdam, que ya se activó en Sepolia y Hoodi; al momento de escribir, la fecha de mainnet aún no figura. [7] Cuando llegue, un filtro por topic sin dirección empezará a recibir esos logs: otra razón para filtrar siempre por contrato.
Invariante de un retiro: una intención, un nonce
La pregunta inicial, reintentar o no, se resuelve con una clave de idempotencia: un identificador único por intención de retiro que tu servicio genera y guarda antes de llamar al custodio. Si la llamada falla o se corta, consultas el estado de esa intención en lugar de crear otra. Si tu custodio acepta un identificador externo por solicitud, pásale esa misma clave; si no, guarda el identificador que te devuelva junto a la tuya.
En la cadena, la unidad de idempotencia es el nonce: un contador que indica el número de transacción de la cuenta emisora. [4] Como cada nonce de una cuenta se usa una sola vez, dos transacciones con el mismo remitente y nonce no pueden quedar ambas en la cadena. Si un retiro queda pendiente por comisión baja, la respuesta correcta es reemplazarlo: misma cuenta, mismo nonce, comisión mayor. Enviar uno nuevo con otro nonce puede terminar en dos pagos.
Por eso el retiro se sigue por (remitente, nonce) y no solo por hash: un reemplazo cambia el hash. El retiro termina en uno de dos estados finales. Completado: recibo con status 1 en un bloque finalizado. Fallido: recibo con status 0; la transacción se incluyó, pagó gas y no movió los fondos, así que la reserva vuelve al saldo del cliente y el caso pasa a revisión. Una reorg antes de finalized devuelve el retiro a "difundido". [1]
El esquema siguiente es ilustrativo (PostgreSQL). Su objetivo es que la base de datos, no la memoria del proceso, garantice que nada se aplica dos veces.
-- Ilustrativo: restricciones que vuelven idempotentes depósitos y retiros.
CREATE TABLE deposits (
chain_id bigint NOT NULL,
block_hash bytea NOT NULL,
log_index integer NOT NULL,
token bytea NOT NULL, -- contrato del token aceptado
to_address bytea NOT NULL,
amount numeric(78) NOT NULL,
status text NOT NULL CHECK (status IN ('pending', 'available', 'discarded')),
PRIMARY KEY (chain_id, block_hash, log_index)
);
CREATE TABLE withdrawals (
idempotency_key uuid PRIMARY KEY, -- se escribe antes de llamar al custodio
from_address bytea,
nonce numeric(20),
tx_hash bytea, -- cambia si hay reemplazo
status text NOT NULL CHECK (status IN
('requested', 'submitted', 'broadcast', 'included', 'completed', 'failed')),
UNIQUE (from_address, nonce)
);Riesgo operativo: permisos, pausa y un incidente que cruza capas
Si tu arquitectura incluye contratos propios, como un contrato de depósitos o un vault, sus permisos de administración son parte de la superficie de producción. Una clave de administrador en una sola laptop contradice todo lo anterior. El capítulo de access control cubre multisig y timelocks; aquí importa un mecanismo más: la pausa de emergencia.
OpenZeppelin ofrece el módulo Pausable, que permite implementar una parada de emergencia activable por una cuenta autorizada. Solo protege las funciones donde aplicas los modificadores whenNotPaused o whenPaused; heredar el módulo no pausa nada por sí solo. [8] Decide de antemano qué funciones se pausan, quién puede pausar y quién puede reanudar.
Ahora, un incidente típico. El contrato emitió el evento correcto, el recibo tiene status 1, pero la interfaz muestra el saldo anterior. El problema puede estar en la caché, en el indexador, en la política de confirmación, en el proveedor de RPC o en una consulta con otra clave. Reintentar la transacción sería la peor respuesta: duplicaría una intención que la cadena ya ejecutó. El procedimiento empieza al revés: tomar el hash, leer el recibo en dos fuentes, ubicar el bloque respecto de finalized, y recién entonces mirar el índice.
Antes de habilitar retiros en producción
0 de 9 revisados
Quién decide qué es verdad
Quédate con una frase: el custodio firma, pero tu sistema decide qué es verdad. Un depósito es disponible cuando su bloque es finalizado; un retiro es único por su clave de idempotencia y por su nonce; y el libro contable se demuestra correcto conciliándolo con la cadena. La ruta práctica sigue desde aquí con llaves, firmas y custodia.
Bibliografía académica
- [1]
Ethereum Execution APIs specification, schema BlockTag.
Definiciones de latest, safe y finalized; recibos con status.
- [2]
ethereum.org, "Proof-of-stake (PoS)."
Finalidad y costo de revertir un bloque finalizado.
- [3]
F. Vogelsteller and V. Buterin, "EIP-20: Token Standard," Ethereum Improvement Proposals, 2015.
Evento Transfer obligatorio en cada transferencia.
- [4]
ethereum.org, "Transactions."
Nonce como contador secuencial de transacciones de la cuenta.
- [5]
ethereum.org, "Opcodes for the EVM."
LOG0 a LOG4 como únicos opcodes que crean logs.
- [6]
V. Buterin et al., "EIP-7708: ETH transfers emit a log," Ethereum Improvement Proposals, 2024.
Log de Transfer emitido por SYSTEM_ADDRESS para transferencias de ETH.
- [7]
T. Beiko et al., "EIP-7773: Hardfork Meta - Glamsterdam," Ethereum Improvement Proposals, 2024.
EIPs programadas y fechas de activación de Glamsterdam.
- [8]
OpenZeppelin, "Utilities: Pausable," OpenZeppelin Contracts 5.x documentation.
Parada de emergencia y modificadores whenNotPaused y whenPaused.
Rutas vecinas
Si este tema te abrió hambre, sigue por aquí.
Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.
Series relacionadas
HotStuff: de cero a estado del arte
Ruta progresiva para entender consenso BFT moderno: quórums, sincronía parcial, locks, taxonomía one/two/three-chain, QC/highQC, multipipeline, view-change, responsiveness, AggQC, Fast-HotStuff, sync y frontera DAG-BFT.
Substrate: del runtime al consenso propio
Serie de 20 capítulos, de neófito a experto, sobre Substrate (Polkadot SDK): runtime en Wasm, FRAME, extrinsics, estado, pesos, actualizaciones sin fork, Aura, BABE, GRANDPA, sincronización, parachains, XCM, Sassafras, BEEFY y cómo conectar un consenso BFT propio a un nodo.
Ruta práctica: activos digitales en la operación
Nueve módulos, de menor a mayor dependencia, para decidir cómo usar custodia, wallets, llaves, firmas multifirma y MPC, AML/KYT y stablecoins con Fireblocks, BitGo, Kaleido, Circle y Tether.
Labs para convertir lectura en práctica
Prueba de trabajo y reajuste de dificultad
Laboratorio determinista para medir el costo de la prueba de trabajo y ver cómo el reajuste por altura de bloque estabiliza el tiempo entre bloques.
Consensus benchmark
Banco reproducible para comparar consenso BFT y documentar los trade-offs de HotStuff-2.
Ethereum local: Anvil, wallet y primera transacción
Laboratorio base para levantar una red local, crear cuentas, firmar transacciones y observar receipts sin gastar ETH real.
RPC y Cast: leer Ethereum con lupa
Ejercicios con JSON-RPC y Cast para consultar bloques, nonces, gas, receipts, logs y errores reproducibles.
Sigue leyendo
Ideas conectadas con este artículo
Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.
Indexadores: eventos, reorgs e idempotencia
Cómo consumir logs, manejar confirmations, backfills, replays y reorgs sin inventar exactly-once.
Clientes y nodos: diversidad antes que monocultivo
Execution clients, consensus clients, node operators, client diversity, APIs y por qué Ethereum no debe depender de una sola implementación.
Observabilidad Ethereum para dApps
Métricas de RPC, gas, fallos de transacciones, lag de eventos, indexadores y alertas útiles.
Ethereum: mapa de lectura antes de tocar Solidity
Ruta completa para aprender Ethereum desde historia y arquitectura hasta EVM, proof-of-stake, L2, seguridad, DeFi y labs reproducibles.