Glossario / Riferimento per sviluppatori
CompactSize
Noto anche come Compact size unsigned integer, VarInt.
- Che cos'è un compactSize?
- CompactSize è l'intero a lunghezza variabile con cui bitcoin antepone conteggi e lunghezze nei dati serializzati, e costa un byte per i valori inferiori a 253 e fino a nove per gli altri.
I formati binari di Bitcoin non riportano nomi di campo, quindi a un parser va detto quanti input seguono e quanto è lungo ogni script. CompactSize risponde con un solo byte: da 0 a 252 è quel byte da solo, e i valori più grandi occupano un marcatore più altri due, quattro o otto byte. Ogni pagamento che invii contiene almeno quattro di questi campi.
Come funziona
CompactSize legge il proprio primo byte per decidere quanto resta di sé.
Un primo byte inferiore a 0xfd è il valore, quindi i conteggi da 0 a 252 occupano un byte e nient'altro. Un primo byte pari a 0xfd significa che il valore sta nei due byte successivi, 0xfe nei quattro successivi e 0xff negli otto successivi. Tutti questi numeri successivi sono little endian. La codifica più lunga possibile è di nove byte e può esprimere un valore superiore a 18 miliardi di miliardi, che nessuna struttura reale si avvicina a richiedere.
Le codifiche devono essere minime. Scrivere il numero 5 come 0xfd 0x05 0x00 è aritmeticamente identico a scrivere 0x05, e Bitcoin Core lo rifiuta, sollevando un errore di codifica non canonica durante la deserializzazione. Senza quella regola la stessa transazione potrebbe essere scritta in diversi modi, ognuno con un identificatore diverso, il che è un problema di malleabilità e non di ordine.
C'è un tetto pratico molto al di sotto di quello aritmetico. Bitcoin Core rifiuta di allocare memoria per qualsiasi oggetto deserializzato che dichiari di essere più grande di 0x02000000 byte, circa 33,5 megabyte, e il protocollo peer-to-peer limita separatamente un singolo messaggio a 4.000.000 byte. Un peer ostile non può quindi far riservare al tuo nodo gigabyte di memoria annunciando una transazione con un miliardo di miliardi di input.
Vale la pena segnalare una trappola di nomenclatura. La documentazione del formato di rete lo chiama var_int, le library dei wallet di solito lo chiamano VarInt, e il codice sorgente di Bitcoin Core usa il nome VarInt per una codifica completamente diversa e non correlata, che compare solo nei suoi file di database e di undo. Quando leggi il codice, controlla quale dei due hai davanti.
Dove lo vedi
CompactSize è la struttura più frequente in bitcoin che non abbia alcun nome visibile all'utente.
In una transazione grezza compare subito dopo la versione di quattro byte, o dopo il marcatore e il flag segwit quando sono presenti, e contiene il conteggio degli input. Ricompare prima di ogni script per darne la lunghezza, e di nuovo prima del conteggio degli output. In un blocco serializzato porta il conteggio delle transazioni. Nel protocollo peer-to-peer apre il payload dei messaggi inv, getdata, addr e headers.
Il messaggio headers mostra la codifica nel suo aspetto più strano. Ogni intestazione di blocco di 80 byte è seguita da un conteggio di transazioni CompactSize pari a zero, quindi una voce occupa 81 byte invece di 80, e un lotto completo di 2.000 intestazioni arriva a 162.000 byte sulla rete. Il conteggio è sempre zero perché un messaggio headers non trasporta mai transazioni. Il campo esiste perché lo stesso codice di parsing possa leggere un'intestazione indipendentemente dal fatto che sia seguita o meno da un corpo di blocco.
Se decodifichi a mano una transazione grezza e i campi smettono di avere senso a metà, quasi sempre la causa è un CompactSize letto male. Consuma un byte di meno e tutto ciò che segue risulta spostato.