Dados on-chain e SQL · Aula 3 de 9
O modelo UTXO para analistas
9 min de leitura
O Bitcoin e as blockchains derivadas dele (Litecoin, Bitcoin Cash, Dogecoin e outras) não armazenam saldos. Elas armazenam saídas discretas que são criadas por uma transação e destruídas por outra. Todo saldo, fluxo, rastreamento e agrupamento que um analista produz para essas blockchains se baseia nessa estrutura, e a maioria dos erros de iniciantes (dupla contagem, saldos fantasmas, "pagamentos" que na verdade eram troco) decorre de esquecê-la.
Moedas são saídas
Uma saída de transação é um valor acompanhado de um script de bloqueio (o scriptPubKey). Ela é identificada por seu outpoint: o txid da transação que a criou e sua posição na lista de saídas dessa transação, escrito como txid:vout. Uma saída tem apenas dois estados: não gasta ou gasta, e é gasta exatamente uma vez e por inteiro. Não há gasto parcial nem uma "conta" da qual retirar fundos.
Uma entrada não carrega um valor. Ela aponta para um outpoint anterior e fornece os dados (assinatura, chave pública, script) que satisfazem o script de bloqueio daquela saída. Para saber quanto vale uma entrada, você consulta a saída que ela gasta (veja a aula Transações brutas de Bitcoin e tipos de script).
Disso decorrem três consequências para a análise:
| O que os usuários veem | O que a blockchain registra |
|---|---|
| "Meu endereço tem 1,2 BTC" | Várias saídas não gastas cujos scripts são decodificados para esse endereço, somando 1,2 BTC |
| "Enviei 0,5 BTC" | Uma transação que consumiu saídas inteiras e criou novas, geralmente incluindo troco |
| "Meu saldo diminuiu" | Saídas passaram do conjunto de não gastas para o conjunto de gastas |
Um saldo de endereço é, portanto, um valor derivado, nunca armazenado. Novas moedas entram por meio da transação coinbase, que não tem entradas reais e paga o subsídio do bloco mais as taxas. As saídas coinbase só podem ser gastas em um bloco situado pelo menos 100 blocos após aquele que as criou, uma regra chamada maturidade da coinbase.
O grafo de gastos
Como cada entrada aponta para exatamente uma saída anterior, a blockchain forma um grafo de gastos. Veja um pequeno exemplo decodificado:
{
"txid": "b7e0…5d19",
"vin": [
{ "txid": "4f3a…c21e", "vout": 0 },
{ "txid": "9b1d…07aa", "vout": 1 }
],
"vout": [
{ "n": 0, "value": 1.00000000, "address": "3J9x…Qe1m" },
{ "n": 1, "value": 0.09950000, "address": "bc1q…r8tn" }
]
}
A transação b7e0…5d19 gasta a saída 0 de 4f3a…c21e (0,80 BTC) e a saída 1 de 9b1d…07aa (0,30 BTC). As entradas somam 1,10 BTC; as saídas somam 1,0995 BTC; a taxa é a diferença, 0,0005 BTC. As duas saídas agora estão não gastas e só se tornarão arestas quando algo as gastar.
Na leitura como grafo, as transações são nós e as saídas são arestas direcionadas da transação que as criou para a transação que as gastou. Saídas não gastas são arestas ainda sem destino. O grafo é acíclico e sempre aponta para a frente no tempo, de modo que qualquer satoshi pode ser seguido para trás, salto a salto, até as transações coinbase que o criaram.
Uma propriedade importa mais do que qualquer outra: dentro de uma transação, o protocolo não vincula entradas específicas a saídas específicas. Quando entram 0,80 e 0,30 BTC e saem 1,00 e 0,0995, nada na blockchain diz qual entrada "pagou" qual saída. O valor é reunido e redistribuído. Todo método de rastreamento que atribui entradas a saídas (primeiro a entrar, primeiro a sair, rateio proporcional e assim por diante) é uma convenção do analista, não um fato extraído dos dados; a aula Métodos de rastreamento e suas consequências compara esses métodos.
Saídas de troco
Como as saídas são gastas por inteiro, um pagamento geralmente envia parte do valor ao destinatário e devolve o restante ao remetente como troco. No exemplo acima, uma das duas saídas provavelmente é troco, mas o protocolo não indica qual. Identificá-la é uma questão de heurísticas (tipo de script, valores redondos, uso de endereço novo), abordadas na aula Heurísticas de detecção de troco.
Classificar o troco incorretamente tem grandes efeitos. Trate o troco como pagamento e você inventa uma contraparte e direciona o rastreamento para o ramo errado. Conte todas as saídas como valor transferido e o "volume" na blockchain fica inflado, porque troco e transferências para si mesmo não movimentam valor entre partes. Medidas rigorosas de volume tentam excluir troco e consolidações, e a qualidade de cada uma depende da qualidade de seu modelo de troco.
Reconhecer alguns formatos comuns de transação ajuda:
| Formato | Estrutura típica | Significado habitual |
|---|---|---|
| Pagamento simples | 1–3 entradas, 2 saídas | Pagamento mais troco |
| Consolidação | Muitas entradas, 1 saída | Uma carteira reunindo pequenas saídas, frequentemente quando as taxas estão baixas |
| Pagamento em lote | Poucas entradas, muitas saídas | Um serviço pagando muitos clientes de uma só vez |
| Etapa de descascamento | 1 entrada, 2 saídas: uma pequena, uma grande | Parte de uma cadeia de descascamento; a saída grande dá continuidade (veja a aula Grafos de transações e métricas de grafos) |
Idade das moedas e dormência
Cada saída registra o bloco em que foi criada, de modo que toda saída não gasta tem uma idade. Quando uma saída é gasta, os analistas calculam os dias-moeda destruídos (CDD): o valor multiplicado pelo número de dias em que ela permaneceu não gasta. Uma saída de 2 BTC criada 400 dias antes destrói 800 dias-moeda quando é gasta. O CDD total de um dia dividido pelos BTC movimentados naquele dia fornece uma dormência média: por quanto tempo, em média, as moedas movimentadas haviam permanecido paradas.
A idade importa nas investigações porque moedas inativas por muito tempo que se movimentam de repente são um alerta clássico. Grande parte dos bitcoins roubados da Bitfinex em 2016 permaneceu na carteira do hacker por anos, até que as autoridades apreenderam cerca de 94.000 BTC em 2022. Monitorar saídas vinculadas a um incidente antigo para detectar seu primeiro gasto é uma das regras mais simples e valiosas que um analista pode estabelecer.
Uma decomposição do conjunto UTXO por idade, em uma altura fixa, fica assim em SQL. O esquema é o genérico usado em todo este módulo (aulas 2.7 e 2.9): uma tabela outputs com uma linha por saída e uma tabela inputs em que cada linha identifica o outpoint que ela gasta (spent_transaction_hash, spent_output_index); os valores estão em satoshis.
-- Saídas não gastas na altura 900000, agrupadas por idade (cerca de 144 blocos por dia)
WITH spent AS (
SELECT spent_transaction_hash, spent_output_index
FROM inputs
WHERE block_number <= 900000
)
SELECT
CASE WHEN 900000 - o.block_number < 144 * 30 THEN '1: menos de 1 mês'
WHEN 900000 - o.block_number < 144 * 365 THEN '2: 1-12 meses'
ELSE '3: mais de 1 ano' END AS age_band,
COUNT(*) AS utxos,
SUM(o.value) / 100000000.0 AS btc
FROM outputs o
LEFT JOIN spent s
ON s.spent_transaction_hash = o.transaction_hash
AND s.spent_output_index = o.output_index
WHERE o.block_number <= 900000
AND s.spent_transaction_hash IS NULL -- sem gasto nessa altura ou antes dela
GROUP BY 1
ORDER BY 1;
A consulta mantém apenas saídas criadas na altura escolhida ou antes dela que não tenham um gasto correspondente nessa altura ou antes dela, o que é a definição do conjunto UTXO naquele ponto. As faixas etárias usam contagens de blocos como aproximação do tempo; para idades precisas, faça uma junção com a tabela de blocos e use os timestamps dos blocos (levando em conta as ressalvas sobre timestamps na aula Blocos, cabeçalhos e APIs de nós). Observe o divisor 100000000.0: a divisão inteira truncaria silenciosamente o resultado em alguns mecanismos.
O conjunto UTXO
O conjunto de todas as saídas não gastas em um determinado bloco é o conjunto UTXO. Os nós o mantêm em um banco de dados dedicado (o chainstate) porque é disso que precisam para validar novas transações. Ele cresceu bastante, impulsionado em parte por experimentos com inscrições e tokens que criaram muitas saídas pequenas, por isso as consultas sobre ele são pesadas.
Dois métodos RPC do Bitcoin Core são úteis aqui:
gettxoutsetinforetorna estatísticas resumidas do conjunto, incluindotxouts(o número de saídas não gastas) etotal_amount(as moedas que elas contêm). Consultar uma altura histórica exige o coinstatsindex opcional.scantxoutsetpesquisa no conjunto UTXO atual saídas que correspondam a descritores de saída comoaddr(...)e retorna cada correspondência com seu txid, vout, valor e altura. Ele vê apenas saídas não gastas, nunca o histórico.
O Bitcoin Core não mantém um índice de endereços, portanto um histórico completo de transações de um endereço exige um indexador ou um data warehouse (veja a aula Blocos, cabeçalhos e APIs de nós).
O conjunto também permite uma verificação de consistência. Seu total_amount deve estar próximo do total de subsídios já emitidos, mas abaixo dele, porque parte do valor é comprovadamente impossível de gastar e nunca entra no conjunto: a recompensa do bloco gênese, saídas OP_RETURN e taxas ou subsídios que os mineradores deixaram de reivindicar. Um pipeline cujo total UTXO calculado excede a emissão tem um erro, normalmente gastos ausentes ou saídas duplicadas.
Reutilização de endereços
Endereços não são contas. Carteiras que seguem boas práticas derivam um endereço novo para cada recebimento a partir de uma semente determinística hierárquica, e o usuário nunca vê a maioria deles. Uma pessoa pode controlar milhares de endereços; um endereço de carteira quente de uma exchange pode interagir com milhões de contrapartes.
A reutilização de endereços (receber várias vezes no mesmo endereço) é comum mesmo assim: endereços de doação, alguns endereços de depósito de exchanges, carteiras antigas e usuários descuidados. Para um analista, ela é uma dádiva e uma armadilha. A reutilização vincula pagamentos entre si sem nenhuma heurística e, depois que um endereço é usado para gastar, sua chave pública fica visível na blockchain. Mas métricas no nível de endereço, como "endereços ativos" ou "endereços com mais de 1 BTC", medem scripts, não pessoas, e nunca devem ser apresentadas como contagens de usuários.
Uma sutileza: a mesma chave pública pode produzir vários endereços diferentes, um por tipo de script (por exemplo, P2PKH legado e P2WPKH SegWit nativo). Eles parecem não ter relação entre si, mas, assim que a chave é revelada em um gasto, um analista pode vinculá-los pela própria chave pública.
Foco no exame: uma saída é identificada por seu outpoint (txid:vout) e é gasta uma vez e por inteiro; entradas não carregam valor, portanto os valores das entradas e os saldos são derivados das próprias saídas; o protocolo não vincula entradas específicas a saídas específicas, portanto o rastreamento de entradas para saídas é uma convenção; saídas de troco e por que classificá-las incorretamente distorce rastreamentos e volume; maturidade da coinbase de 100 blocos, dias-moeda destruídos (valor × dias sem gastar) e dormência como alertas; cálculo do conjunto UTXO em uma altura (criado nessa altura ou antes dela, não gasto nessa altura ou antes dela);gettxoutsetinfoescantxoutsete seus limites; reutilização de endereços e por que contagens de endereços não são contagens de usuários.
Questões de prática
Verifique o que você aprendeu. Estas questões não contam para o exame.
Entre para salvar seu progresso e continuar de onde parou. É grátis. Entrar