Riepilogo
Le transazioni attraversano 8 fasi: ricezione, verifica delle firme, sanitizzazione, controlli di budget/età, validazione del pagatore delle commissioni, caricamento degli account, esecuzione delle istruzioni e commit.
Pipeline di elaborazione delle transazioni
Quando una transazione arriva a un validator, passa attraverso una serie di fasi di validazione ed esecuzione. Quanto segue descrive la pipeline completa dalla ricezione al commit, con riferimenti ai file sorgente nel client validator agave.
1. Ricezione e deserializzazione
Il validator riceve i byte della transazione tramite UDP/QUIC. I byte grezzi
devono rientrare in un singolo pacchetto (PACKET_DATA_SIZE = 1.232 byte).
I byte vengono deserializzati in una VersionedTransaction, che contiene
l'array delle firme e un VersionedMessage (legacy o v0).
2. Verifica delle firme (sigverify)
Le firme vengono verificate nella
fase sigverify
prima che la transazione entri nella fase banking. Per ogni firma all'indice
i, il verificatore controlla Ed25519(signatures[i], account_keys[i],
message_bytes). Se una firma non è valida, il pacchetto viene scartato.
La verifica è parallelizzata: il validator suddivide i batch di pacchetti in
blocchi di
VERIFY_PACKET_CHUNK_SIZE
(128) e li elabora in parallelo.
3. Sanitizzazione
La transazione deserializzata viene sanitizzata per produrre una
SanitizedTransaction (o
RuntimeTransaction).
La sanitizzazione valida gli invarianti strutturali:
- Il numero di firme corrisponde a
num_required_signaturesnell'header - Tutti gli
program_id_indexeaccount_indicesdelle istruzioni sono entro i limiti - Il pagatore delle commissioni (indice account 0) è un firmatario scrivibile
Il wrapper RuntimeTransaction memorizza nella cache i metadati
precalcolati da
TransactionMeta:
l'hash del messaggio, il flag di transazione di voto, i conteggi delle firme
precompilate (Ed25519/secp256k1/secp256r1), i dettagli delle istruzioni del
budget di calcolo e la lunghezza totale dei dati delle istruzioni.
4. Verifica budget di calcolo, età e cache dello stato
Il metodo
check_transactions
esegue diversi controlli per transazione:
Budget di calcolo: le istruzioni del budget di calcolo della transazione
vengono prima analizzate e validate. I dettagli delle commissioni vengono
calcolati dai limiti del budget e dalla commissione di prioritizzazione. Se il
budget di calcolo non è valido o è in conflitto, la transazione fallisce con
errori di analisi del budget di calcolo come DuplicateInstruction,
InstructionError(..., InvalidInstructionData), o
InvalidLoadedAccountsDataSizeLimit.
Età del blockhash: il recent_blockhash della transazione viene cercato
nella
BlockhashQueue.
Se l'hash viene trovato e la sua età rientra in MAX_PROCESSING_AGE (150
slot), la transazione procede. Se non viene trovato, il validator verifica la
presenza di un nonce durevole valido.
Cache dello stato: l'hash del messaggio della transazione viene verificato
rispetto a una cache dello stato. Se trovato, la transazione viene rifiutata con
AlreadyProcessed.
5. Valida nonce e pagatore delle commissioni
Il metodo
validate_transaction_nonce_and_fee_payer
nella SVM gestisce due validazioni:
Validazione del nonce (se applicabile): per le transazioni con nonce, il validator carica l'account nonce e verifica:
- L'account è di proprietà del System Program
- Viene analizzato come
State::Initialized - Il nonce durevole memorizzato corrisponde al
recent_blockhashdella transazione - Il nonce può essere avanzato (il suo nonce durevole corrente differisce dal prossimo nonce durevole, cioè il nonce non è già stato utilizzato nel blocco corrente)
- L'autorità del nonce ha firmato la transazione
Se valido, il nonce viene avanzato al valore del prossimo nonce durevole. Vedi
validate_transaction_nonce.
Validazione del pagatore delle commissioni: l'account del pagatore delle
commissioni (sempre indice 0) viene caricato e verificato da
validate_fee_payer:
- L'account deve esistere (lamports > 0), altrimenti
AccountNotFound - L'account deve essere un account di sistema o un account nonce, altrimenti
InvalidAccountForFee - I lamports devono coprire
min_balance + total_fee, dovemin_balanceè 0 per gli account di sistema orent.minimum_balance(NonceState::size())per gli account nonce; altrimentiInsufficientFundsForFee - Dopo la detrazione della commissione, l'account deve rimanere esente da affitto (non può passare da esente da affitto a soggetto ad affitto)
La commissione viene detratta dal pagatore della commissione in questa fase.
Viene salvata un'istantanea del pagatore della commissione con la commissione
sottratta (e del nonce avanzato, se applicabile) come RollbackAccounts,
che sono gli account che vengono committati anche se l'esecuzione fallisce.
6. Caricamento degli account
load_transaction
carica tutti gli account referenziati dalla transazione. Il
AccountLoader
avvolge l'archivio account esterno e mantiene una cache locale al batch in modo
che gli account modificati da transazioni precedenti nello stesso batch siano
visibili a quelle successive.
Per ogni account non pagatore di commissioni, il loader:
- Recupera l'account dalla cache o dall'accounts-db
- Aggiorna lo stato di esenzione dall'affitto se necessario
- Accumula la dimensione dei dati dell'account verso il
loaded_accounts_data_size_limit(predefinito 64 MiB). Ogni account comporta un overhead di base diTRANSACTION_ACCOUNT_BASE_SIZE(64 byte) più la lunghezza dei suoi dati
Per ogni programma invocato dalle istruzioni della transazione, il loader
verifica che il program account esista e sia di proprietà di un loader valido
(NativeLoader o uno dei PROGRAM_OWNERS). I programmi non validi
falliscono con ProgramAccountNotFound o InvalidProgramForExecution.
I programmi LoaderV3 (aggiornabili) caricano implicitamente il loro programdata account associato, che conta anch'esso verso il limite di dimensione dei dati caricati.
Se il caricamento dell'account fallisce ma il pagatore della commissione è stato
validato con successo, la transazione diventa un risultato
FeesOnly:
la commissione viene comunque riscossa ma nessuna istruzione viene eseguita.
7. Esecuzione delle istruzioni
execute_loaded_transaction
crea un TransactionContext con tutti gli account caricati e invoca
process_message.
Le istruzioni vengono eseguite sequenzialmente nell'ordine in cui appaiono nel
messaggio. Ogni invocazione di istruzione crea un InvokeContext e chiama
il programma di destinazione.
Dettagli dell'elaborazione delle istruzioni
La funzione
process_message
del runtime itera attraverso ogni istruzione e chiama il programma di
destinazione:
- Per ogni istruzione, il runtime chiama
prepare_next_top_level_instruction, che costruisce ilInstructionContext. Questo contesto contiene riferimenti agli account dell'istruzione (risolti dagli indici compilati), ai dati dell'istruzione e all'indice del program account. - Il runtime verifica se il programma è un precompile (Ed25519, Secp256k1, Secp256r1). I precompile vengono verificati direttamente senza invocare la VM BPF.
- Per tutti gli altri programmi, il runtime invoca
process_instruction, che carica il programma dalla cache e lo esegue nella macchina virtuale BPF. - Dopo il completamento dell'istruzione, il runtime
verifica
che il saldo totale di lamport in tutti gli account dell'istruzione non sia
cambiato (controllo
UnbalancedInstruction). - Se un'istruzione fallisce, l'intera transazione viene annullata. Nessuna modifica di stato intermedia viene salvata.
Ogni istruzione incrementa la traccia delle istruzioni. La traccia include sia
le istruzioni di livello superiore che qualsiasi CPI da esse
invocata. La lunghezza totale della traccia (istruzioni di livello superiore più
tutte le CPI annidate) non può superare 64 (MAX_INSTRUCTION_TRACE_LENGTH).
Il superamento di questo limite restituisce
InstructionError::MaxInstructionTraceLengthExceeded.
Dopo l'esecuzione, il runtime verifica che:
- La somma dei lamport in tutti gli account non sia cambiata
- Nessun account sia passato da rent-exempt a rent-paying
8. Commit o rollback
Se l'esecuzione ha successo, gli stati degli account modificati dal
TransactionContext vengono scritti nella cache locale del batch del
AccountLoader. Se l'esecuzione fallisce, vengono scritti solo i
RollbackAccounts (fee payer con commissione dedotta e nonce avanzato). La
commissione viene comunque riscossa, ma tutte le altre modifiche agli account
vengono scartate.
Riepilogo della pipeline
Receive packet (UDP/QUIC)--> Deserialize into VersionedTransaction--> Sigverify (parallel Ed25519 verification)--> Sanitize (structural validation, metadata extraction)--> Parse compute budget, calculate fees--> Check blockhash age (or verify nonce account)--> Check status cache (dedup)--> Validate nonce authority and advanceability (if nonce transaction)--> Validate fee payer (load, check balance, deduct fee)--> Load all accounts (with data size limits)--> Load programs (verify loaders)--> Execute instructions sequentially--> Verify post-conditions (lamport balance, rent state)--> Commit account changes (or rollback on failure)
Riferimento errori delle transazioni
La seguente tabella elenca tutte le varianti di
TransactionError
e in quale fase della pipeline si verificano:
| Errore | Fase | Causa |
|---|---|---|
AccountInUse | Scheduling | L'account è già bloccato da un'altra transazione nello stesso batch |
AccountLoadedTwice | Scheduling | Una pubkey appare due volte nell'account_keys della transazione |
AccountNotFound | Validazione fee payer | L'account del fee payer non esiste |
ProgramAccountNotFound | Caricamento account | Un programma invocato non esiste |
InsufficientFundsForFee | Validazione fee payer | Il fee payer non può coprire la commissione + il minimo rent-exempt |
InvalidAccountForFee | Validazione fee payer | Il fee payer non è un account di sistema o nonce |
AlreadyProcessed | Cache di stato | La transazione è già stata elaborata |
BlockhashNotFound | Controllo età | Blockhash non in coda e non è un nonce valido |
InstructionError | Esecuzione | Si è verificato un errore durante l'elaborazione di un'istruzione (include l'indice dell'istruzione e lo specifico InstructionError) |
CallChainTooDeep | Caricamento account | La catena di chiamate del loader è troppo profonda |
MissingSignatureForFee | Sanitize | La transazione richiede una commissione ma non ha alcuna firma presente |
InvalidAccountIndex | Sanitize | La transazione contiene un riferimento account non valido |
SignatureFailure | Sigverify | La firma Ed25519 non verifica (il pacchetto viene scartato) |
InvalidProgramForExecution | Caricamento account | Il programma non è di proprietà di un loader valido |
SanitizeFailure | Sanitize | La transazione non è riuscita a sanitizzare correttamente gli offset degli account |
ClusterMaintenance | Scheduling | Le transazioni sono attualmente disabilitate a causa della manutenzione del cluster |
AccountBorrowOutstanding | Esecuzione | L'elaborazione della transazione ha lasciato un account con un riferimento preso in prestito in sospeso |
WouldExceedMaxBlockCostLimit | Scheduling | La transazione supererebbe il limite massimo di costo del blocco |
UnsupportedVersion | Sanitize | La versione della transazione non è supportata |
InvalidWritableAccount | Caricamento account | La transazione carica un account scrivibile che non può essere scritto |
WouldExceedMaxAccountCostLimit | Scheduling | La transazione supererebbe il limite massimo di costo dell'account all'interno del blocco |
WouldExceedAccountDataBlockLimit | Scheduling | La transazione supererebbe il limite di dati dell'account all'interno del blocco |
TooManyAccountLocks | Scheduling | La transazione ha bloccato troppi account |
AddressLookupTableNotFound | Caricamento account | L'account della tabella di ricerca degli indirizzi non esiste |
InvalidAddressLookupTableOwner | Caricamento account | La tabella di ricerca degli indirizzi è di proprietà del programma sbagliato |
InvalidAddressLookupTableData | Caricamento account | La tabella di ricerca degli indirizzi contiene dati non validi |
InvalidAddressLookupTableIndex | Caricamento account | La ricerca nella tabella degli indirizzi utilizza un indice non valido |
InvalidRentPayingAccount | Controllo post-esecuzione | L'account è passato da rent-exempt a rent-paying |
WouldExceedMaxVoteCostLimit | Scheduling | La transazione supererebbe il limite massimo di costo dei voti |
WouldExceedAccountDataTotalLimit | Scheduling | La transazione supererebbe il limite totale di dati dell'account |
DuplicateInstruction | Parsing compute budget | Variante di istruzione compute budget duplicata nella stessa transazione |
InsufficientFundsForRent | Controllo post-esecuzione | L'account non ha abbastanza lamport per coprire il rent per la dimensione dei suoi dati |
MaxLoadedAccountsDataSizeExceeded | Caricamento account | I dati totali caricati superano il limite di 64 MiB |
InvalidLoadedAccountsDataSizeLimit | Parsing compute budget | SetLoadedAccountsDataSizeLimit impostato a 0 |
ResanitizationNeeded | Sanitize | La transazione differiva prima/dopo l'attivazione della funzionalità e necessita di risanitizzazione |
ProgramExecutionTemporarilyRestricted | Caricamento account | L'esecuzione del programma è temporaneamente limitata sull'account di riferimento |
UnbalancedTransaction | Controllo post-esecuzione | Il saldo totale in lamport prima della transazione non è uguale al saldo dopo |
ProgramCacheHitMaxLimit | Caricamento account | La cache del programma ha raggiunto il limite massimo |
CommitCancelled | Commit | Commit annullato internamente |
Is this page helpful?