Creazione di grafi delle transazioni · Lezione 1 di 6
Grafi delle transazioni: nodi, archi e direzione
9 min di lettura
Quasi ogni domanda di tracciamento, da «dove sono finiti i fondi rubati?» a «questo deposito è esposto a un servizio soggetto a sanzioni?», è una domanda su un grafo. Prima di poter tracciare, valutare o visualizzare qualsiasi cosa, devi decidere quali sono i nodi e gli archi del grafo; queste scelte determinano in modo discreto quali risposte sono possibili. Questa lezione mostra come trasformare transazioni grezze in un grafo che puoi difendere.
Tre grafi dagli stessi dati
Gli stessi dati di blocco possono essere modellati in tre modi, e gli analisti passano continuamente dall’uno all’altro.
| Grafo | Nodi | Archi | Ideale per |
|---|---|---|---|
| Grafo delle transazioni | Transazioni (e, nelle chain UTXO, gli output che creano) | Un output creato da una transazione e speso da un’altra | Tracciamento UTXO preciso e senza perdita di informazioni |
| Grafo degli indirizzi | Indirizzi | Valore inviato da un indirizzo a un altro | Chain basate su account, panoramiche rapide |
| Grafo delle entità | Cluster o servizi etichettati | Flussi aggregati tra entità | Reportistica, analisi dell’esposizione e delle controparti |
Il grafo delle transazioni è quello più vicino ai dati grezzi. In Bitcoin, ogni output o resta non speso o viene consumato da esattamente un input successivo, perciò le relazioni di spesa formano un grafo diretto aciclico preciso: il denaro non può fluire all’indietro nel tempo. In questo grafo non c’è nulla di inferito.
Il grafo degli indirizzi accorpa le transazioni in modo che ogni indirizzo diventi un nodo. È naturale per Ethereum e altre chain basate su account, dove un trasferimento passa davvero da un account a un altro. Nelle chain UTXO introduce ambiguità, come mostra l’esempio svolto qui sotto.
Il grafo delle entità sostituisce gli indirizzi con cluster («wallet hot di Exchange X», «cluster di furto 1»). È il grafo mostrato nella maggior parte dei report, ma ogni nodo dipende dal clustering e dall’attribuzione, che comportano tassi di errore propri (vedi la lezione Cos’è il clustering e perché è importante). Mantieni disponibili i livelli sottostanti, così ogni arco di un grafo delle entità può essere ricondotto alle transazioni che lo compongono.
Molti strumenti usano anche un modello ibrido: un grafo bipartito con due tipi di nodi, indirizzi e transazioni, in cui gli archi vanno da un indirizzo a una transazione (un input) e da una transazione a un indirizzo (un output). Mantiene la precisione del grafo delle transazioni e mostra comunque chi detiene cosa: è così che molti strumenti di visualizzazione del tracciamento rappresentano i flussi.
Archi: direzione, peso e tempo
Un arco utile contiene più informazioni di «A ha pagato B». Come minimo, memorizza:
- Direzione: origine e destinazione. Il tracciamento in avanti segue gli archi uscenti; quello all’indietro segue gli archi entranti.
- Peso: l’importo, nell’unità nativa dell’asset dopo la conversione dei decimali (BTC, ETH o token divisi per 10^decimals), più, facoltativamente, il suo valore in una valuta di riferimento al momento del trasferimento, indicando la fonte del prezzo.
- Tempo: altezza del blocco e timestamp del blocco in UTC. L’altezza ordina gli eventi in modo affidabile; i timestamp sono approssimativi (vedi la lezione Blocchi e header come dati).
- Identità: hash della transazione, più l’indice dell’output (UTXO) oppure l’indice del log o la posizione della trace (modello basato su account), così che l’arco sia univoco e verificabile rispetto a un nodo.
- Asset: coin nativa o indirizzo del contratto del token. Non sommare mai archi appartenenti ad asset diversi.
Due indirizzi possono essere collegati da molti archi in momenti diversi, quindi la struttura è un multigrafo. Aggregare archi paralleli in un unico arco «totale inviato» va bene per un grafico, ma distrugge l’ordinamento da cui dipende il tracciamento: i fondi arrivati dopo un pagamento non possono averlo finanziato.
Da una transazione UTXO agli archi
Ecco una transazione Bitcoin decodificata, arricchita con l’indirizzo e il valore di ogni output speso (gli input grezzi fanno riferimento solo a un outpoint, quindi un indicizzatore o una seconda ricerca fornisce questi campi):
{
"txid": "4f3a…c21e",
"block_height": 871204,
"vin": [
{"prev_txid": "9b1e…04aa", "vout": 1, "address": "bc1q…7k3f", "value": 0.60000000},
{"prev_txid": "c07d…e912", "vout": 0, "address": "bc1q…m2pz", "value": 0.50000000}
],
"vout": [
{"n": 0, "address": "bc1q…x9td", "value": 0.80000000},
{"n": 1, "address": "bc1q…h4wa", "value": 0.29000000}
]
}
Gli input totalizzano 1,10 BTC e gli output 1,09 BTC, quindi la commissione è di 0,01 BTC.
Nel grafo delle transazioni, questo è un nodo con due archi entranti (gli outpoint 9b1e…04aa:1 e c07d…e912:0) e due archi uscenti (gli output 0 e 1). Non serve fare alcuna ipotesi.
Nel grafo degli indirizzi, devi creare archi da indirizzo a indirizzo, ma Bitcoin non indica quale input abbia pagato quale output. I due input vengono accorpati. Rappresentare tutte le combinazioni crea quattro archi (2 input × 2 output), e devi scegliere come assegnare loro un peso. La scelta più comune è l’allocazione proporzionale: il primo input ha fornito 0,60 ÷ 1,10 = 54,55% del valore, quindi gli viene attribuito il 54,55% di ciascun output.
| Arco | Calcolo | Peso (BTC) |
|---|---|---|
| bc1q…7k3f → bc1q…x9td | 0,80 × 0,5455 | 0,4364 |
| bc1q…7k3f → bc1q…h4wa | 0,29 × 0,5455 | 0,1582 |
| bc1q…m2pz → bc1q…x9td | 0,80 × 0,4545 | 0,3636 |
| bc1q…m2pz → bc1q…h4wa | 0,29 × 0,4545 | 0,1318 |
I pesi totalizzano 1,09 BTC e la commissione di 0,01 BTC esce dal grafo (alcuni analisti aggiungono un nodo sink «commissione» per conservare il valore). Una transazione con 50 input e 50 output genererebbe 2.500 archi di questo tipo: per questo le grandi operazioni di consolidamento e i pagamenti in batch possono sovraccaricare un grafo degli indirizzi. Stabilire se bc1q…h4wa sia un pagamento o il resto restituito al mittente è un’inferenza distinta (vedi la lezione Euristiche per il rilevamento del resto).
Dai dati basati su account agli archi
Nelle chain in stile Ethereum, gli archi provengono da tre fonti e un grafo costruito usando una sola di esse non rileverà tutti i movimenti di denaro:
- Transazioni di primo livello con un
valuediverso da zero: dal firmatario all’indirizzoto. - Trasferimenti interni di coin nativa effettuati da contratti durante l’esecuzione, che compaiono solo nelle trace (frame
callcon valore). - Trasferimenti di token, decodificati dai log: un evento ERC-20
Transfer(address,address,uint256)ha topic00xddf252ad…b3ef, il mittente e il destinatario nei topic 1 e 2 e l’importo grezzo nel campo data.
Si applicano tre precauzioni. Escludi le transazioni con esito revert (ricevuta status = 0) e i frame delle trace con esito revert: non hanno spostato nulla, anche se il mittente ha comunque pagato il gas. Tratta i contratti come nodi, ma etichettali, perché un router DEX o un contratto token è un meccanismo, non una controparte. Ricorda inoltre che un singolo swap produce di solito due archi in asset diversi: utente → pool in un token e pool → utente in un altro. Il tracciamento del valore attraverso questa coppia è trattato nella lezione Tracciamento cross-chain.
Creare una lista di archi in SQL
La maggior parte delle attività sui grafi inizia da una lista di archi piatta in un data warehouse. La query qui sotto ne crea una per un intervallo di blocchi Ethereum. I nomi delle tabelle e delle colonne seguono uno schema ETL pubblico tipico; verifica quelli del tuo sistema.
-- Lista di archi: ETH nativo (dalle trace) più trasferimenti ERC-20
SELECT block_number, block_timestamp, transaction_hash AS txid,
from_address AS src, to_address AS dst,
'ETH' AS asset, value / 1e18 AS amount -- da wei a ETH
FROM traces
WHERE value > 0 AND status = 1 -- solo frame riusciti
AND block_number BETWEEN 21000000 AND 21050000
UNION ALL
SELECT tt.block_number, tt.block_timestamp, tt.transaction_hash,
tt.from_address, tt.to_address,
tt.token_address, tt.value / POWER(10, tk.decimals)
FROM token_transfers tt
JOIN tokens tk ON tk.address = tt.token_address -- decimali per token
WHERE tt.block_number BETWEEN 21000000 AND 21050000;
La prima metà preleva i movimenti di coin nativa dalla tabella delle trace, che in questo schema include sia le chiamate di primo livello sia quelle interne, mantenendo solo i frame riusciti con valore. La seconda metà aggiunge i trasferimenti di token e converte gli interi grezzi usando i decimali di ciascun token. UNION ALL mantiene ogni riga, perché qui i duplicati sono trasferimenti distinti e reali. Entrambe le metà applicano lo stesso filtro sui blocchi, limitando la scansione e rendendo il risultato riproducibile. I valori interi di grandi dimensioni possono superare la capacità di alcuni tipi numerici dei motori; per questo motivo molti data warehouse memorizzano value come stringa o come decimale a 256 bit.
Scelte pratiche prima del tracciamento
- Delimita il grafo. Fissa un punto di partenza, un intervallo temporale e un limite di hop prima di eseguire la query. Un’espansione senza limiti raggiunge ogni exchange nel giro di pochi hop e smette di costituire una prova.
- Contrassegna i supernodi. I wallet hot degli exchange, i bridge e i contratti popolari hanno milioni di archi. Segnalali come punti di arresto invece di espanderli.
- Scegli l’archiviazione in base alla domanda. Le liste di archi in SQL gestiscono bene aggregazione e filtri; i database e le librerie a grafo (come NetworkX o igraph) sono più adatti alla ricerca dei percorsi e alla centralità. Molti team esportano da SQL un sottografo delimitato e lo importano in uno strumento per grafi.
- Mantieni la provenienza su ogni arco: txid, indice, altezza del blocco e query che l’ha prodotto, affinché un altro analista possa ricostruire lo stesso grafo.
Focus sull'esame: la differenza tra grafi delle transazioni, degli indirizzi e delle entità e gli scopi per cui ciascuno è adatto; perché il grafo delle spese UTXO è preciso mentre il grafo degli indirizzi richiede un’ipotesi di allocazione; calcolo dei pesi proporzionali degli archi per una transazione con più input e più output; attributi necessari per ogni arco (direzione, importo, asset, altezza del blocco, ora UTC, txid e indice); le tre fonti di archi basati su account (transazioni, trace, log dei token) e perché quelli con esito revert vengono esclusi; perché gli archi paralleli devono mantenere i propri timestamp; delimitazione di un grafo e trattamento dei supernodi come punti di arresto.