Dati on-chain e SQL · Lezione 3 di 9
Il modello UTXO per gli analisti
10 min di lettura
Bitcoin e le blockchain che ne derivano (Litecoin, Bitcoin Cash, Dogecoin e altre) non memorizzano saldi. Memorizzano output distinti, creati da una transazione e distrutti da un'altra. Ogni saldo, flusso, tracciamento e cluster che un analista produce per queste blockchain si basa su tale struttura, e la maggior parte degli errori dei principianti (doppi conteggi, saldi fantasma, "pagamenti" che in realtà erano resti) deriva dal dimenticarla.
Le monete sono output
Un output di transazione è un importo abbinato a uno script di blocco (lo scriptPubKey). È identificato dal suo outpoint: il txid della transazione che lo ha creato e la sua posizione nell'elenco degli output di tale transazione, espresso come txid:vout. Un output ha soltanto due stati: non speso o speso, e viene speso esattamente una volta e per intero. Non esistono spese parziali né un "conto" da cui prelevare.
Un input non contiene un importo. Fa riferimento a un outpoint precedente e fornisce i dati (firma, chiave pubblica, script) che soddisfano lo script di blocco di quell'output. Per conoscere il valore di un input, occorre consultare l'output che esso spende (si veda la lezione Transazioni Bitcoin grezze e tipi di script).
Ne derivano tre conseguenze per l'analisi:
| Ciò che vedono gli utenti | Ciò che registra la blockchain |
|---|---|
| "Il mio indirizzo contiene 1,2 BTC" | Diversi output non spesi i cui script vengono decodificati in quell'indirizzo, per un totale di 1,2 BTC |
| "Ho inviato 0,5 BTC" | Una transazione che ha consumato output interi e ne ha creati di nuovi, solitamente includendo un resto |
| "Il mio saldo è diminuito" | Alcuni output sono passati dall'insieme degli output non spesi a quello degli output spesi |
Il saldo di un indirizzo è quindi un valore derivato, mai un dato memorizzato. Le nuove monete vengono introdotte tramite la transazione coinbase, che non ha input reali e corrisponde il sussidio del blocco più le commissioni. Gli output coinbase possono essere spesi soltanto in un blocco successivo di almeno 100 blocchi a quello che li ha creati, secondo una regola detta maturità coinbase.
Il grafo di spesa
Poiché ogni input fa riferimento esattamente a un output precedente, la blockchain forma un grafo di spesa. Ecco un piccolo esempio decodificato:
{
"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" }
]
}
La transazione b7e0…5d19 spende l'output 0 di 4f3a…c21e (0,80 BTC) e l'output 1 di 9b1d…07aa (0,30 BTC). Gli input ammontano complessivamente a 1,10 BTC; gli output a 1,0995 BTC; la commissione è la differenza, pari a 0,0005 BTC. I due output sono ora non spesi e diventeranno archi soltanto quando verranno spesi.
Nella rappresentazione a grafo, le transazioni sono nodi e gli output sono archi diretti dalla transazione che li ha creati a quella che li ha spesi. Gli output non spesi sono archi ancora privi di destinazione. Il grafo è aciclico e procede sempre in avanti nel tempo, quindi qualsiasi satoshi può essere seguito a ritroso, passaggio dopo passaggio, fino alle transazioni coinbase che lo hanno creato.
Una proprietà conta più di ogni altra: all'interno di una transazione, il protocollo non collega input specifici a output specifici. Quando entrano 0,80 e 0,30 BTC ed escono 1,00 e 0,0995, nulla sulla blockchain indica quale input abbia "pagato" quale output. Il valore viene aggregato e ridistribuito. Ogni metodo di tracciamento che associa input a output (first-in-first-out, haircut e così via) è una convenzione dell'analista, non un fatto ricavato dai dati; la lezione Metodi di tracciamento e relative conseguenze li mette a confronto.
Gli output di resto
Poiché gli output vengono spesi per intero, un pagamento invia solitamente una parte del valore al destinatario e restituisce il resto al mittente come resto. Nell'esempio precedente, uno dei due output è probabilmente il resto, ma il protocollo non indica quale. La sua identificazione si basa su euristiche (tipo di script, importi tondi, utilizzo di un indirizzo nuovo), trattate nella lezione Euristiche di rilevamento del resto.
Identificare erroneamente il resto ha conseguenze rilevanti. Trattare il resto come un pagamento significa inventare una controparte e indirizzare il tracciamento lungo il ramo sbagliato. Conteggiare tutti gli output come valore trasferito gonfia il "volume" on-chain, perché il resto e i trasferimenti verso sé stessi non spostano valore tra soggetti diversi. Le misure rigorose del volume cercano di escludere resti e consolidamenti, e l'affidabilità di ciascuna di esse dipende da quella del relativo modello di identificazione del resto.
Riconoscere alcune forme comuni di transazione è utile:
| Forma | Struttura tipica | Significato abituale |
|---|---|---|
| Pagamento semplice | 1–3 input, 2 output | Pagamento più resto |
| Consolidamento | Molti input, 1 output | Un portafoglio che unisce piccoli output, spesso quando le commissioni sono basse |
| Pagamento in blocco | Pochi input, molti output | Un servizio che paga molti clienti contemporaneamente |
| Passaggio di una peel chain | 1 input, 2 output: uno piccolo, uno grande | Parte di una peel chain; l'output grande prosegue (si veda la lezione Grafi delle transazioni e metriche dei grafi) |
Età delle monete e dormienza
Ogni output registra il blocco in cui è stato creato, quindi ogni output non speso ha un'età. Quando un output viene speso, gli analisti calcolano i giorni-moneta distrutti (CDD): l'importo moltiplicato per il numero di giorni durante i quali è rimasto non speso. Un output di 2 BTC creato 400 giorni prima distrugge 800 giorni-moneta quando viene speso. Il CDD totale di una giornata diviso per i BTC movimentati quel giorno fornisce una dormienza media: per quanto tempo, in media, le monete movimentate erano rimaste ferme.
L'età è importante nelle indagini perché le monete dormienti da lungo tempo che si muovono improvvisamente rappresentano un classico segnale di allerta. Gran parte dei bitcoin sottratti a Bitfinex nel 2016 è rimasta nel portafoglio dell'hacker per anni, finché le autorità non hanno sequestrato circa 94.000 BTC nel 2022. Monitorare gli output collegati a un vecchio incidente per rilevarne la prima spesa è una delle regole più semplici e preziose che un analista possa impostare.
Una suddivisione dell'insieme UTXO per età, a un'altezza fissa, si presenta così in SQL. Lo schema è quello generico utilizzato in tutto questo modulo (lezioni 2.7 e 2.9): una tabella outputs con una riga per output e una tabella inputs in cui ogni riga indica l'outpoint che spende (spent_transaction_hash, spent_output_index); i valori sono espressi in satoshi.
-- Output non spesi all'altezza 900.000, raggruppati per età (circa 144 blocchi al giorno)
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: meno di 1 mese'
WHEN 900000 - o.block_number < 144 * 365 THEN '2: 1-12 mesi'
ELSE '3: oltre 1 anno' 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 -- nessuna spesa a tale altezza o prima
GROUP BY 1
ORDER BY 1;
La query mantiene soltanto gli output creati all'altezza scelta o prima, per i quali non esiste una spesa corrispondente a tale altezza o prima: questa è la definizione dell'insieme UTXO in quel punto. Le fasce di età utilizzano il conteggio dei blocchi come approssimazione del tempo; per ottenere età precise, eseguire una join con la tabella dei blocchi e utilizzare i timestamp dei blocchi (tenendo presenti le avvertenze sui timestamp nella lezione Blocchi, intestazioni e API dei nodi). Si noti il divisore 100.000.000,0: in alcuni motori, la divisione intera troncherebbe il risultato senza segnalarlo.
L'insieme UTXO
L'insieme di tutti gli output non spesi a un determinato blocco è l'insieme UTXO. I nodi lo conservano in un database dedicato (il chainstate), perché è ciò di cui hanno bisogno per convalidare le nuove transazioni. Ha raggiunto dimensioni considerevoli, in parte a causa degli esperimenti con iscrizioni e token che hanno creato molti piccoli output, quindi le query su di esso sono onerose.
In questo contesto sono utili due metodi RPC di Bitcoin Core:
gettxoutsetinforestituisce statistiche riepilogative sull'insieme, tra cuitxouts(il numero di output non spesi) etotal_amount(le monete che contengono). La richiesta di un'altezza storica richiede il coinstatsindex facoltativo.scantxoutsetcerca nell'insieme UTXO attuale gli output corrispondenti a descrittori di output comeaddr(...)e restituisce ogni corrispondenza con txid, vout, importo e altezza. Vede soltanto gli output non spesi, mai lo storico.
Bitcoin Core non conserva alcun indice degli indirizzi, quindi per ottenere lo storico completo delle transazioni di un indirizzo è necessario un indicizzatore o un data warehouse (si veda la lezione Blocchi, intestazioni e API dei nodi).
L'insieme consente anche una verifica di plausibilità. Il suo total_amount dovrebbe essere vicino, ma inferiore, al sussidio totale emesso fino a quel momento, perché una parte del valore è dimostrabilmente non spendibile e non entra mai nell'insieme: la ricompensa del blocco genesi, gli output OP_RETURN e le commissioni o il sussidio che i miner non hanno riscosso. Una pipeline il cui totale UTXO calcolato supera l'emissione contiene un errore, in genere spese mancanti o output duplicati.
Il riutilizzo degli indirizzi
Gli indirizzi non sono conti. I portafogli che seguono le buone pratiche derivano da un seed gerarchico deterministico un nuovo indirizzo per ogni ricezione, e l'utente non ne vede la maggior parte. Una persona può controllare migliaia di indirizzi; un singolo indirizzo del portafoglio online di un exchange può interagire con milioni di controparti.
Il riutilizzo degli indirizzi (ricevere più volte allo stesso indirizzo) è comunque comune: indirizzi per donazioni, alcuni indirizzi di deposito degli exchange, vecchi portafogli e utenti poco attenti. Per un analista è un vantaggio e una trappola. Il riutilizzo collega i pagamenti tra loro senza alcuna euristica e, una volta che da un indirizzo è stata effettuata una spesa, la sua chiave pubblica è visibile sulla blockchain. Tuttavia, le metriche a livello di indirizzo, come "indirizzi attivi" o "indirizzi che contengono più di 1 BTC", misurano script, non persone, e non dovrebbero mai essere presentate come conteggi degli utenti.
Una sottigliezza: la stessa chiave pubblica può produrre diversi indirizzi, uno per ciascun tipo di script (per esempio P2PKH legacy e P2WPKH SegWit nativo). Sembrano non correlati, ma una volta che la chiave viene rivelata in una spesa, un analista può collegarli tramite la chiave pubblica stessa.
Focus sull'esame: un output è identificato dal suo outpoint (txid:vout) e viene speso una sola volta e per intero; gli input non contengono importi, quindi i valori degli input e i saldi sono derivati dagli output stessi; il protocollo non collega input specifici a output specifici, quindi il tracciamento da input a output è una convenzione; gli output di resto e il motivo per cui una loro classificazione errata distorce tracciamenti e volumi; la maturità coinbase di 100 blocchi, i giorni-moneta distrutti (importo × giorni senza essere speso) e la dormienza come segnali di allerta; il calcolo dell'insieme UTXO a una determinata altezza (creato a tale altezza o prima, non speso a tale altezza o prima);gettxoutsetinfoescantxoutsete i relativi limiti; il riutilizzo degli indirizzi e il motivo per cui i conteggi degli indirizzi non corrispondono ai conteggi degli utenti.