Dados on-chain e SQL · Aula 6 de 9
Qualidade dos dados, normalização e proveniência
10 min de leitura
A maioria dos números incorretos na análise de blockchain não decorre de heurísticas ruins, mas de erros de dados corriqueiros: um valor incorreto por um fator de 10^12, um limite entre dias no fuso horário errado, uma transferência contada duas vezes, uma transação da Polygon misturada aos totais da Ethereum. Esses erros são fáceis de cometer e difíceis de identificar em um gráfico pronto. Esta aula apresenta as regras de normalização que os evitam e o registro de proveniência que permite a qualquer pessoa verificar seu trabalho.
Unidades: mantenha os inteiros até o fim
As blockchains armazenam valores como números inteiros da menor unidade. As casas decimais são uma convenção de exibição.
| Ativo | Unidade base | Conversão |
|---|---|---|
| Bitcoin | satoshi | 1 BTC = 100.000.000 sat (10^8) |
| Ether | wei | 1 ETH = 10^18 wei; 1 gwei = 10^9 wei |
| Tokens ERC-20 | unidades brutas | Divida por 10^decimals, lido do contrato do token |
As casas decimais dos tokens variam: 18 é comum, mas USD Coin usa 6 e Wrapped Bitcoin usa 8. O valor de decimals é opcional no padrão ERC-20 e é puramente informativo: os saldos do contrato são sempre inteiros brutos. Leia esse valor de cada contrato em cada blockchain, em vez de pressupô-lo, pois uma versão de um token transferida por ponte pode diferir da original. O custo de uma suposição errada é alto: um valor bruto de 2.500.000.000 corresponde a 2.500 tokens com 6 casas decimais, mas a 0,0000000025 com 18 casas decimais, um fator de 10^12.
Disso decorrem duas armadilhas. Primeiro, ponto flutuante. O Bitcoin Core retorna valores em BTC como números JSON, como 0.00485900, e um analisador descuidado os transforma em valores binários de ponto flutuante que não conseguem representar exatamente a maioria das frações decimais; assim, as somas apresentam pequenos desvios e as conciliações falham. Interprete os valores como strings decimais e converta-os imediatamente em satoshis inteiros. Segundo, estouro de capacidade. Os valores na Ethereum são inteiros sem sinal de 256 bits, de até cerca de 1,16 × 10^77. Um BIGINT com sinal de 64 bits tem como limite 9.223.372.036.854.775.807, que em wei corresponde a apenas cerca de 9,22 ETH, e nem mesmo um DECIMAL de 38 dígitos consegue armazenar todos os valores possíveis. Armazene os valores brutos como texto ou em um tipo numérico de alta capacidade e faça a conversão na última etapa:
-- Os valores brutos permanecem exatos; a conversão ocorre apenas para exibição
SELECT tt.transaction_hash,
tt.log_index,
tt.amount_raw, -- inteiro exato mantido como texto
CAST(tt.amount_raw AS DECIMAL(38, 0))
/ POWER(10, tk.decimals) AS amount_display -- aproximado
FROM token_transfers tt
JOIN tokens tk
ON tk.chain_id = tt.chain_id
AND tk.token_address = tt.token_address;
A junção por chain_id e pelo endereço do contrato seleciona o decimals correto para cada token em cada blockchain. A conversão de tipo falha para valores com mais de 38 dígitos, o que sinaliza tokens de spam com ofertas absurdas em vez de corromper silenciosamente os totais; alguns mecanismos oferecem, como alternativa, decimais de 76 dígitos ou tipos nativos de 256 bits. POWER retorna um número de ponto flutuante na maioria dos mecanismos, portanto amount_display é uma aproximação, aceitável apenas para exibição; somas e conciliações usam amount_raw. Não divida por um DECIMAL(38, 0) como alternativa: alguns mecanismos atribuem ao resultado a escala dos operandos, zero neste caso, e o truncam silenciosamente para um número inteiro.
Tempo: UTC e horário do bloco
Os timestamps dos blocos usam tempo Unix: segundos desde 1 de janeiro de 1970, UTC. Mantenha todos os timestamps em UTC, agrupe dias e meses em UTC e informe isso no relatório. A conversão para um fuso horário local desloca transações entre dias, e as mudanças de horário de verão criam horas que ocorrem duas vezes ou que não ocorrem. Uma transação minerada às 23:30 UTC de 31 de março pertence a março em um relatório UTC, mas a 1 de abril para um analista cujas ferramentas exibem silenciosamente UTC+2; se duas equipes compararem totais mensais produzidos dessa forma, eles serão diferentes sem que nenhuma delas perceba o motivo. Armazene timestamps como valores UTC (ou segundos Unix brutos) e converta-os apenas na camada de apresentação, identificando o fuso usado.
Lembre-se do que o timestamp significa. Ele representa o horário do bloco, não o momento da assinatura ou da transmissão, e no Bitcoin pode ser anterior ao do bloco precedente. Ordene os eventos pela altura (e pela posição dentro do bloco), não pelo timestamp. Ao correlacionar atividades entre blockchains, leve em conta os diferentes intervalos entre blocos e as regras de timestamp.
Identificadores e seus formatos
O mesmo identificador pode ser escrito de várias formas, e as junções falham silenciosamente quando os formatos diferem.
- Endereços EVM aparecem no formato de checksum EIP-55, com letras maiúsculas e minúsculas (
0xA1b2…9F), em carteiras e exploradores, mas em minúsculas nos logs e na maioria dos conjuntos de dados. Converta todos os endereços e hashes hexadecimais para minúsculas para armazenamento e junções; mantenha o formato com checksum apenas para exibição. - Valores com preenchimento: um endereço em um tópico de log ocupa 32 bytes; o endereço corresponde aos últimos 20.
- Txids do Bitcoin são exibidos na ordem inversa de sua ordem interna de bytes; armazene o formato de exibição.
- Endereços Bitcoin: endereços bech32 e bech32m (
bc1…) não diferenciam maiúsculas de minúsculas e, por convenção, são escritos em minúsculas, mas endereços Base58 (1…,3…) diferenciam maiúsculas de minúsculas. Converter um endereço Base58 para minúsculas o destrói. - Quantidades hexadecimais do JSON-RPC da Ethereum (
"0x1406f52") devem ser interpretadas na base 16; lê-las como strings decimais ou remover o prefixo produz resultados sem sentido.
Identificadores de blockchain
O mesmo endereço pode existir em dezenas de blockchains EVM, controlado pela mesma chave, mas com saldos sem relação entre si, e os hashes de transação têm formato idêntico em todas elas. Cada linha precisa de um identificador de blockchain. As blockchains EVM usam o ID da blockchain introduzido pela EIP-155 para proteção contra repetição e retornado por eth_chainId:
| Blockchain | ID da blockchain |
|---|---|
| Rede principal da Ethereum | 1 |
| OP Mainnet | 10 |
| BNB Smart Chain | 56 |
| Gnosis | 100 |
| Polygon PoS | 137 |
| Base | 8453 |
| Arbitrum One | 42161 |
| Avalanche C-Chain | 43114 |
| Sepolia (rede de testes) | 11155111 |
Um registro de IDs de blockchain mantido pela comunidade é a referência usual para as demais. Use eth_chainId em vez de net_version, cujo ID de rede pode diferir do ID da blockchain. Exclua explicitamente as redes de testes e trate tokens transferidos por pontes como ativos distintos, com seus próprios endereços de contrato em cada blockchain; a aula Pontes, camadas 2, sidechains e outros modelos de blockchain aborda o mapeamento de fluxos entre elas. O Bitcoin não tem ID de blockchain, mas suas redes de testes usam prefixos de endereço diferentes (tb1… em vez de bc1…).
Deduplicação e chaves naturais
Duplicatas são introduzidas por cargas retroativas sobrepostas, solicitações repetidas, reorganizações (a mesma transação registrada em dois blocos) e exportações mescladas. Definir uma chave natural para cada tipo de registro permite detectá-las:
| Registro | Chave natural |
|---|---|
| Bloco | block_hash |
| Transação Bitcoin | block_hash, txid |
| Saída Bitcoin | txid, vout |
| Transação EVM | chain_id, hash |
| Log EVM | chain_id, block_hash, log_index |
| Rastreamento EVM | chain_id, transaction_hash, trace_address |
Por que uma transação Bitcoin precisa do hash do bloco? Porque não há garantia de que os txids sejam únicos: duas transações coinbase antigas foram repetidas exatamente, nos blocos 91.842 e 91.880, que a BIP 30 preservou como exceções e que a BIP 34 posteriormente tornou impossíveis ao exigir a altura do bloco em toda coinbase. A consulta abaixo mantém uma cópia de cada log e apenas logs de blocos canônicos:
-- Uma linha por log, apenas de blocos canônicos
WITH ranked AS (
SELECT l.*,
ROW_NUMBER() OVER (
PARTITION BY l.chain_id, l.block_hash, l.log_index
ORDER BY l.ingested_at DESC) AS rn
FROM raw_logs l
)
SELECT *
FROM ranked
WHERE rn = 1
AND block_hash IN (SELECT block_hash FROM canonical_blocks);
ROW_NUMBER numera as cópias de cada chave e mantém a ingerida mais recentemente; o filtro final descarta logs de blocos substituídos por uma reorganização. Duplicatas lógicas também importam: contar um log Transfer e o rastreamento da mesma chamada de token, ou ETH nativo mais WETH, conta o valor duas vezes.
Registro da proveniência
Proveniência é o registro de onde um conjunto de dados veio e de como foi produzido. Anexe um registro a cada extração que sustente uma constatação:
{
"dataset": "token_transfers_to_target",
"chain_id": 1,
"block_range": [21000000, 21000999],
"anchor_block_hash": "0x5e1c…a7d0",
"finality": "finalized",
"source": "own archive node",
"client_version": "Geth/v1.16.x",
"method": "eth_getLogs",
"query_file": "queries/transfers_to_target.sql",
"retrieved_at_utc": "2026-10-28T14:05:00Z",
"row_count": 37,
"output_sha256": "b94d…e9f1"
}
O intervalo de blocos e o hash de ancoragem fixam os dados; o status de finalização mostra que eles não podem mudar; a fonte, a versão do cliente e o método identificam como foram obtidos; o arquivo de consulta registra a transformação; o horário de obtenção em UTC os situa no tempo; e a contagem de linhas e o hash SHA-256 do arquivo de saída permitem a qualquer pessoa confirmar que tem os mesmos dados. Um revisor com um nó pode executar novamente a consulta e comparar. A aula Ferramentas, reprodutibilidade e validação estende isso a consultas versionadas em um data warehouse.
Erros de dados comuns
| Erro | Sintoma | Prevenção |
|---|---|---|
| Valores em ponto flutuante | Totais incorretos nos últimos dígitos | Unidades base inteiras, interpretação decimal |
| Casas decimais incorretas | Valores incorretos por 10^12 (6 tratado como 18) | Ler decimals por contrato e blockchain |
| Fuso horário local | Totais diários deslocados entre dias | UTC em todas as etapas |
| ID da blockchain ausente | Atividade de sidechain nos totais da rede principal | chain_id em cada linha e junção |
| Transações malsucedidas contabilizadas | Valor que nunca foi movimentado | Filtrar por status = 1 e por erros de rastreamento |
| Linhas de blocos substituídos por reorganização mantidas | Transações ausentes da blockchain final | Fazer junção com blocos canônicos pelo hash |
| Identificadores corrompidos por alteração de maiúsculas e minúsculas | Junções deixam de encontrar linhas; endereços Base58 inválidos | Converter apenas hexadecimais para minúsculas |
Um teste final de baixo custo detecta muitos desses problemas de uma só vez: selecione de três a cinco linhas que sejam mais importantes para uma constatação e confira cada uma com um nó ou uma segunda fonte, comparando o hash do bloco, o valor em unidades base e o timestamp. Se uma única linha divergir, pare e descubra o motivo antes de confiar no agregado.
Foco no exame: satoshi, wei, gwei e casas decimais dos tokens, e por que os valores permanecem inteiros; riscos de ponto flutuante e de estouro de capacidade de 64 bits (BIGINT comporta apenas cerca de 9,22 ETH em wei); timestamps UTC e ordenação por altura; checksums EIP-55 versus armazenamento em minúsculas e a diferenciação entre maiúsculas e minúsculas do Base58; IDs de blockchain (EIP-155, eth_chainId) e por que cada linha precisa de um; chaves naturais para deduplicação e os txids duplicados da BIP 30; os campos de um registro de proveniência; os erros de dados comuns e como evitá-los.