Datos en cadena y SQL · Lección 5 de 9
El modelo de cuentas y la reconstrucción de saldos
16 min de lectura
Ethereum y las numerosas cadenas que copian su diseño mantienen un saldo actualizado para cada dirección en lugar de un conjunto de monedas independientes. Esto cambia qué se puede rastrear, qué significa una "transacción" y quién es el actor real: con las cuentas inteligentes, la dirección que firma, la dirección que paga el gas y la dirección del campo from suelen corresponder a tres partes diferentes, y confundirlas atribuye la actividad a la parte equivocada. También determina cómo abordar una pregunta habitual en los casos: "¿cuánto tenía esta dirección en esa fecha y cómo llegó a tenerlo?", que un examinador riguroso responde de dos maneras: calculando el saldo a partir del historial y consultándolo en el estado de la cadena, para después explicar cualquier diferencia.
Estado, no monedas
Una cadena con modelo de cuentas almacena un estado global: una correspondencia entre cada dirección de 20 bytes y un registro de cuenta. En Ethereum, ese registro tiene cuatro campos:
| Campo | Significado | Uso para el analista |
|---|---|---|
nonce | Transacciones enviadas (EOA) o contratos creados (contrato) | Recuento de actividad, ordenación, comprobaciones de direcciones nuevas |
balance | Moneda nativa, en wei | Fondos en un bloque |
codeHash | Hash del código de la cuenta (vacío para una EOA simple) | Distinguir entre EOA, contratos y EOA delegadas |
storageRoot | Raíz del almacenamiento de clave-valor de la cuenta | Donde residen los saldos de tokens y los datos del contrato |
Cada bloque deja constancia criptográfica del estado resultante mediante el stateRoot de su encabezado. Los saldos se consultan con eth_getBalance(address, block) y el almacenamiento de los contratos mediante eth_call (por ejemplo, el balanceOf de un token) o eth_getStorageAt.
| Pregunta | Cadenas UTXO | Cadenas de cuentas |
|---|---|---|
| Unidad de valor | Salidas independientes, que se gastan completas | Un saldo por dirección |
| Cambio | Una salida nueva, a menudo a una dirección nueva | Ninguno: el remanente permanece en el saldo |
| Vinculación de un pago saliente con un pago entrante | Exacta por salida; ambigua dentro de una transacción con múltiples entradas | Nunca exacta: todos los depósitos se mezclan |
| Reutilización de direcciones | Desaconsejada | Normal; una dirección durante años |
| Protección contra repetición | Cada salida solo se puede gastar una vez | Cada transacción debe usar el siguiente nonce |
Una vez que llegan 10 ETH a una dirección que ya tiene 50 ETH, nada en los datos indica cuáles de esos ETH salieron después. Cualquier atribución de un pago saliente a un depósito concreto es una convención de rastreo elegida por el analista, no un hecho registrado (la lección 3.3 aborda los métodos).
EOA, contratos y EOA delegadas
Una cuenta de propiedad externa (EOA) está controlada por una clave privada y es el único tipo de cuenta que puede originar una transacción. Una cuenta de contrato tiene código y almacenamiento y solo actúa cuando se la llama. eth_getCode(address, block) devuelve 0x para una EOA y el bytecode desplegado para un contrato. Tres dificultades hacen que esta comprobación sea menos sencilla de lo que parece:
- Momento. El código solo existe a partir del bloque en el que se creó el contrato. Las direcciones pueden calcularse antes del despliegue (CREATE2 permite hacerlo de forma deliberada), por lo que una dirección sin código en el bloque N puede ser un contrato en N+1. Compruebe el código en el bloque pertinente.
- Destrucción. Antes de la actualización Dencun (marzo de 2024),
SELFDESTRUCTeliminaba el código de un contrato. Desde EIP-6780, solo lo hace cuando se llama en la misma transacción que creó el contrato; en caso contrario, solo envía el saldo a otra dirección. - Delegación. Desde la actualización Pectra (mayo de 2025), una EOA puede contener el designador de delegación de EIP-7702 de 23 bytes,
0xef0100seguido de una dirección delegada. Una regla simplista de "tiene código, por tanto es un contrato" la clasifica incorrectamente: la cuenta sigue controlada por su clave privada.
Por lo tanto, un clasificador sólido devuelve tres clases (EOA, contrato, EOA delegada) para un bloque determinado.
El nonce es un regalo para los analistas. Para una EOA, equivale al número de transacciones enviadas, por lo que un nonce de 0 significa que la dirección nunca ha enviado nada, y los intervalos sin actividad o las ráfagas de actividad revelan patrones operativos (lección 4.3). Hay dos salvedades: cada autorización EIP-7702 que firma una EOA también incrementa su nonce, y el nonce de un contrato cuenta las creaciones de contratos, empezando en 1 según EIP-161. Las direcciones de contrato se derivan de quien los despliega: CREATE usa la dirección y el nonce de quien realiza el despliegue; CREATE2 usa la dirección de quien realiza el despliegue, un valor salt y el hash del código de creación, por lo que los vínculos con quien realiza el despliegue constituyen evidencia sólida.
Dónde reside el historial
Una consulta de estado responde "¿qué tenía esta dirección en el bloque N?". El historial responde "¿cómo llegó a tenerlo?", y está distribuido entre varios conjuntos de datos:
- transacciones de nivel superior: valor enviado, más el gas pagado por el remitente;
- trazas de llamadas internas: valor movido por contratos;
- registros: transferencias de tokens y otros eventos;
- cambios de saldo que no son transacciones: comisiones de prioridad abonadas al destinatario de comisiones del bloque, retiros de la capa de consenso abonados directamente a las direcciones desde abril de 2023 (EIP-4895: sin gas, sin ejecución de código, sin hash de transacción), asignaciones del bloque génesis y, antes de septiembre de 2022, recompensas de minería.
Los nodos completos solo conservan el estado reciente, por lo que consultar el estado de bloques antiguos requiere un nodo de archivo o un proveedor que ofrezca uno (lección 2.1).
Transacciones internas
En el protocolo, una transacción es únicamente el mensaje firmado de nivel superior. Cuando un contrato reenvía ETH durante la ejecución, se trata de una llamada de mensaje: no tiene hash, recibo ni registro, a menos que el código emita uno. Los exploradores las denominan "transacciones internas", una etiqueta práctica pero engañosa. Supongamos que la transacción de un usuario envía 0 ETH a un contrato de billetera 0x7c3E…12, que durante la ejecución llama a 0xD4e5…3a con el valor 0xde0b6b3a7640000, que equivale a 10^18 wei o 1 ETH. La lista de transacciones de 0xD4e5…3a no muestra nada, pero su saldo aumentó en 1 ETH. Solo una traza (lección 2.4) explica el cambio. Las subllamadas revertidas no mueven nada y deben excluirse.
Una transferencia ERC-20 es algo distinto: un cambio en el almacenamiento del contrato del token anunciado por un evento Transfer, no una transacción interna (lección 3.5).
Cuentas inteligentes: quién firma, quién paga, quién actúa
Una cuenta inteligente es una billetera implementada como contrato, con reglas como múltiples firmantes, límites de gasto, recuperación o gas patrocinado.
Las billeteras de contrato (las billeteras multifirma son el caso clásico) mantienen los fondos en una dirección de contrato. Los propietarios firman fuera de la cadena y uno de ellos, o un retransmisor, envía la transacción, por lo que la EOA de from puede ser simplemente el último firmante.
ERC-4337 incorpora la abstracción de cuentas sin cambios en el protocolo. Los usuarios firman una UserOperation (sus campos incluyen sender, nonce, callData, límites de gas, paymaster y signature) y la envían a un mempool independiente. Un agrupador reúne muchas de ellas en una sola transacción que llama a handleOps en un contrato EntryPoint de instancia única, que valida y ejecuta cada operación y paga al agrupador con el depósito de la cuenta o el de un patrocinador de gas. Para cada operación, emite:
UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed)
El from de la transacción es el agrupador, una transacción puede contener operaciones de usuarios sin relación entre sí, el recibo abarca todo el lote y el actor real es el sender del evento. Con el esquema genérico de la lección 2.7:
-- Una fila por cada UserOperation de ERC-4337, con agrupador, cuenta inteligente y patrocinador de gas
SELECT l.block_number,
l.transaction_hash,
t.from_address AS bundler,
'0x' || SUBSTR(l.topic2, 27, 40) AS smart_account, -- sender indexado
'0x' || SUBSTR(l.topic3, 27, 40) AS paymaster -- dirección cero = ninguno
FROM logs AS l
JOIN transactions AS t ON t.hash = l.transaction_hash
WHERE l.topic0 = '0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f'
AND l.contract_address IN (SELECT address FROM entrypoint_contracts);
topic0 es el hash Keccak-256 de la firma del evento, y SUBSTR(topic, 27, 40) extrae una dirección indexada de su tema de 32 bytes rellenado con ceros (lección 2.9); algunos motores denominan a la función SUBSTRING, y los conjuntos de datos que almacenan temas en formato binario requieren extraer segmentos de bytes en su lugar. Restringir la consulta a direcciones EntryPoint conocidas (una por versión, guardadas en una pequeña tabla del caso) evita coincidencias con eventos similares de otros contratos.
EIP-7702 permite que una EOA existente adopte el comportamiento de una cuenta inteligente. Una transacción de tipo 4 contiene autorizaciones [chain_id, address, nonce, y_parity, r, s]; cada una establece el código del firmante como el designador de delegación, que persiste hasta que se sustituye o elimina, y una autorización con chain_id 0 es válida en todas las cadenas. La dirección conserva su historial y su clave, pero ahora puede agrupar llamadas o hacer que otros paguen su gas. Los destinos de delegación también son evidencia: un panel público de datos en cadena publicado por la firma de negociación Wintermute en junio de 2025 detectó que más del 80% de las primeras delegaciones apuntaban a copias de un contrato de "barrido" que vacía billeteras cuyas claves se han filtrado.
| Patrón | Quién autoriza | from de la transacción (paga el gas) | Dónde encontrar al actor real |
|---|---|---|---|
| Transacción de una EOA simple | Clave de la EOA | La misma EOA | from |
| Billetera de contrato | Firmas de los propietarios en los datos de llamada | Un propietario o un retransmisor | to (la billetera) y sus eventos |
| Metatransacción (ERC-2771) | Firma del usuario | Un retransmisor a través de un reenviador de confianza | Datos de llamada decodificados |
| ERC-4337 | Reglas de validación de la cuenta inteligente | Agrupador, reembolsado por la cuenta o el patrocinador de gas | UserOperationEvent.sender |
| EIP-7702 patrocinada | Autorización de la EOA y carga útil firmada | Un patrocinador | La EOA delegada a la que se llama |
Nunca equipare a quien paga el gas con el actor hasta identificar el patrón. La financiación del gas sigue siendo evidencia útil, pero, cuando existe patrocinio, puede apuntar a un servicio en lugar de a un propietario.
Dos vías para obtener un saldo
| Vía | Cómo | Ventajas | Desventajas |
|---|---|---|---|
| Calcular a partir del historial | Sumar todos los abonos y cargos hasta un bloque | Explica cada movimiento; funciona en un almacén de datos | Es fácil pasar por alto una categoría de movimiento |
| Consultar el estado | Pedir a un nodo el saldo en un bloque | Es la referencia definitiva para esa cadena y ese bloque | No ofrece explicación; los bloques antiguos requieren un estado de archivo |
La cifra calculada se incluye en el informe porque cuenta la historia; la cifra del estado sirve de comprobación. Cuando coinciden, el historial está completo. Cuando difieren, falta algo, y la magnitud de la diferencia suele indicar qué.
Saldos UTXO
En una cadena UTXO, el saldo de un conjunto de scripts a la altura H es la suma de las salidas destinadas a ellos creadas en H o antes, y no gastadas en H ni antes (lección 2.3). Para una dirección:
| Referencia de salida | Creada en | Importe (BTC) | Gastada en |
|---|---|---|---|
4f3a…c21e:1 | 880.100 | 0,4000 | 880.900 |
a91c…3b07:0 | 880.450 | 1,2500 | no gastada |
77d2…e4a0:3 | 881.020 | 0,0800 | 885.300 |
c0b5…91f2:0 | 884.700 | 0,3000 | no gastada |
En 882.000, las salidas no gastadas son a91c…3b07:0 y 77d2…e4a0:3: 1,33 BTC. En 886.000, son a91c…3b07:0 y c0b5…91f2:0: 1,55 BTC. Lo recibido (2,03) menos lo enviado (0,48) da los mismos 1,55, una comprobación interna útil. Una billetera no es una dirección: si se conocen los descriptores de salida o una clave pública extendida (por ejemplo, proporcionados por el propietario en una disputa), derive todas las direcciones, incluidas las de cambio, antes de sumar. Las transacciones del mempool no son saldos; las salidas coinbase con menos de 100 bloques de antigüedad existen, pero todavía no se pueden gastar. scantxoutset de Bitcoin Core solo ve el conjunto UTXO actual, por lo que las comprobaciones históricas requieren un indexador o un segundo conjunto de datos creado de forma independiente.
Saldos nativos en cadenas de cuentas
saldo(N) = valor recibido en transacciones de nivel superior exitosas
+ valor recibido en llamadas internas exitosas (trazas)
+ retiros de la capa de consenso y recompensas del destinatario de comisiones
- valor enviado en transacciones de nivel superior y llamadas internas exitosas
- comisiones de gas pagadas en cada transacción enviada, exitosa o fallida
Las asignaciones del bloque génesis y las recompensas de minería anteriores a la Fusión añaden términos para las direcciones más antiguas. Un ejemplo resuelto para 0x6F2a…c19E en el bloque N:
| Concepto | Fuente | Importe (ETH) |
|---|---|---|
| Depósito desde un exchange | Transacción | +5,000000 |
| Pago de un contrato | Traza (llamada interna) | +1,000000 |
| Retiros de validador | Lista de retiros del bloque | +0,032000 |
| Pago enviado | Transacción | −2,500000 |
| Gas de ese pago: 21.000 × 20 gwei | Recibo | −0,000420 |
| Transacción fallida: valor no enviado | status del recibo = 0 | 0 |
| Gas de la transacción fallida: 70.000 × 30 gwei | Recibo | −0,002100 |
| Saldo calculado | 3,529480 |
Si eth_getBalance(0x6F2a…c19E, N) devuelve 3,529480 ETH, el historial está completo. Los errores dejan huellas: ignorar las trazas subestima el saldo exactamente en el abono interno (1,0); contabilizar el valor de la transacción fallida lo subestima en ese valor; olvidar su gas lo sobrestima en 0,0021; omitir los retiros deja una diferencia de importes pequeños y regulares. El gas es el gas utilizado × el precio efectivo del gas indicado en el recibo, nunca el límite de gas, más las comisiones de blobs para las transacciones de tipo 3.
Saldos de tokens: eventos frente a balanceOf
Los saldos de tokens residen en el almacenamiento del contrato del token. Pueden calcularse sumando los eventos Transfer o consultarse con balanceOf(holder) mediante eth_call en el bloque N:
-- Saldos de tokens en el bloque 21.000.000, calculados a partir de eventos Transfer
WITH moves AS (
SELECT to_address AS holder, amount_raw AS delta
FROM token_transfers
WHERE token_address = '0x5d3a…7c10' AND block_number <= 21000000
UNION ALL
SELECT from_address AS holder, -amount_raw AS delta
FROM token_transfers
WHERE token_address = '0x5d3a…7c10' AND block_number <= 21000000
)
SELECT holder, SUM(delta) / POWER(10, 6) AS balance -- el token tiene 6 decimales
FROM moves
GROUP BY holder
HAVING SUM(delta) <> 0;
Cada transferencia se convierte en un abono y un cargo. Las acuñaciones son transferencias desde la dirección cero, por lo que esta termina con un saldo negativo igual a todo lo acuñado menos todo lo quemado mediante envíos a ella: una comprobación integrada del suministro. Mantenga los enteros sin procesar en un tipo decimal de gran capacidad hasta la división final.
Los eventos y balanceOf difieren por motivos conocidos: los tokens con reajuste de saldos modifican todos los saldos mediante un multiplicador global sin un evento por titular; los tokens que devengan intereses calculan balanceOf a partir de un índice creciente; los tokens con comisión por transferencia abonan al receptor menos de lo que envía el remitente, y algunos emiten el importe bruto; y algunos contratos antiguos o controlados por el emisor acuñan sin Transfer o emiten un evento personalizado al congelar y quemar. En estos casos, el estado es la referencia definitiva y los eventos solo cuentan la historia de las transferencias explícitas.
Instantáneas y conciliación
Un saldo "al 31 de marzo" es ambiguo hasta que se vincula a un bloque: tome el último bloque cuya marca de tiempo sea igual o anterior al momento de corte, en UTC, y registre su altura y su hash. Según EIP-1898, eth_getBalance y eth_call aceptan un hash de bloque, lo que garantiza que la respuesta procede de ese bloque exacto incluso si una reorganización sustituye posteriormente el bloque de esa altura. Para las instantáneas multicadena, cada cadena tiene su propio bloque para el mismo instante (los bloques de aproximadamente 10 minutos de Bitcoin ofrecen intervalos menos precisos que los intervalos de 12 segundos de Ethereum, y un bloque de rollup puede reflejar depósitos que su L1 acaba de incluir); indique el bloque elegido en cada una.
Una reconstrucción no está terminada hasta que se comprueba: compare los saldos calculados con el estado (todas las direcciones incluidas en el alcance, o una muestra en varios bloques); compruebe que los saldos de tokens calculados, excluida la dirección cero, equivalgan a totalSupply() en el mismo bloque; trate cualquier saldo acumulado negativo como un abono faltante; compruebe los límites (cero antes de la primera actividad y lo que dejó un barrido conocido); y establezca una tolerancia de antemano, documentando la causa de cada diferencia residual.
| Síntoma | Causa probable |
|---|---|
| Saldo calculado demasiado bajo por importes que coinciden con pagos de contratos | Faltan llamadas internas de las trazas |
| Saldo calculado demasiado alto por importes pequeños | No se ha deducido el gas de las transacciones fallidas |
| Abonos pequeños y regulares sin explicación | Retiros de la capa de consenso o recompensas del destinatario de comisiones |
Los saldos de los titulares de tokens se desvían de balanceOf diariamente | Token con reajuste de saldos o que devenga intereses |
La suma de los saldos difiere de totalSupply() | Acuñaciones o quemas sin eventos Transfer |
Enfoque del examen: espere preguntas basadas en escenarios sobre quién actuó y por qué un saldo no se concilia: los cuatro campos de la cuenta y el significado del nonce; por qué los saldos mezclados hacen que el rastreo a nivel de moneda sea una convención; la clasificación de EOA, contratos y EOA delegadas de EIP-7702 en un bloque (0xef0100, EIP-6780); las transacciones internas como llamadas de mensaje visibles solo en las trazas; los retiros y las recompensas de comisiones como abonos que no son transacciones; los roles de ERC-4337 yUserOperationEvent.sender; los saldos calculados frente a los del estado; los componentes del saldo nativo, especialmente el gas de las transacciones fallidas; los saldos de tokens a partir de eventos y la comprobación del suministro mediante la dirección cero; los tokens que invalidan las sumas de eventos; la fijación de instantáneas por altura y hash (EIP-1898); qué sugiere cada tipo de diferencia.