Pipeline delle transazioni

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_signatures nell'header
  • Tutti gli program_id_index e account_indices delle 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_blockhash della 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, dove min_balance è 0 per gli account di sistema o rent.minimum_balance(NonceState::size()) per gli account nonce; altrimenti InsufficientFundsForFee
  • 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:

  1. Recupera l'account dalla cache o dall'accounts-db
  2. Aggiorna lo stato di esenzione dall'affitto se necessario
  3. Accumula la dimensione dei dati dell'account verso il loaded_accounts_data_size_limit (predefinito 64 MiB). Ogni account comporta un overhead di base di TRANSACTION_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:

  1. Per ogni istruzione, il runtime chiama prepare_next_top_level_instruction, che costruisce il InstructionContext. Questo contesto contiene riferimenti agli account dell'istruzione (risolti dagli indici compilati), ai dati dell'istruzione e all'indice del program account.
  2. Il runtime verifica se il programma è un precompile (Ed25519, Secp256k1, Secp256r1). I precompile vengono verificati direttamente senza invocare la VM BPF.
  3. Per tutti gli altri programmi, il runtime invoca process_instruction, che carica il programma dalla cache e lo esegue nella macchina virtuale BPF.
  4. 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).
  5. 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:

ErroreFaseCausa
AccountInUseSchedulingL'account è già bloccato da un'altra transazione nello stesso batch
AccountLoadedTwiceSchedulingUna pubkey appare due volte nell'account_keys della transazione
AccountNotFoundValidazione fee payerL'account del fee payer non esiste
ProgramAccountNotFoundCaricamento accountUn programma invocato non esiste
InsufficientFundsForFeeValidazione fee payerIl fee payer non può coprire la commissione + il minimo rent-exempt
InvalidAccountForFeeValidazione fee payerIl fee payer non è un account di sistema o nonce
AlreadyProcessedCache di statoLa transazione è già stata elaborata
BlockhashNotFoundControllo etàBlockhash non in coda e non è un nonce valido
InstructionErrorEsecuzioneSi è verificato un errore durante l'elaborazione di un'istruzione (include l'indice dell'istruzione e lo specifico InstructionError)
CallChainTooDeepCaricamento accountLa catena di chiamate del loader è troppo profonda
MissingSignatureForFeeSanitizeLa transazione richiede una commissione ma non ha alcuna firma presente
InvalidAccountIndexSanitizeLa transazione contiene un riferimento account non valido
SignatureFailureSigverifyLa firma Ed25519 non verifica (il pacchetto viene scartato)
InvalidProgramForExecutionCaricamento accountIl programma non è di proprietà di un loader valido
SanitizeFailureSanitizeLa transazione non è riuscita a sanitizzare correttamente gli offset degli account
ClusterMaintenanceSchedulingLe transazioni sono attualmente disabilitate a causa della manutenzione del cluster
AccountBorrowOutstandingEsecuzioneL'elaborazione della transazione ha lasciato un account con un riferimento preso in prestito in sospeso
WouldExceedMaxBlockCostLimitSchedulingLa transazione supererebbe il limite massimo di costo del blocco
UnsupportedVersionSanitizeLa versione della transazione non è supportata
InvalidWritableAccountCaricamento accountLa transazione carica un account scrivibile che non può essere scritto
WouldExceedMaxAccountCostLimitSchedulingLa transazione supererebbe il limite massimo di costo dell'account all'interno del blocco
WouldExceedAccountDataBlockLimitSchedulingLa transazione supererebbe il limite di dati dell'account all'interno del blocco
TooManyAccountLocksSchedulingLa transazione ha bloccato troppi account
AddressLookupTableNotFoundCaricamento accountL'account della tabella di ricerca degli indirizzi non esiste
InvalidAddressLookupTableOwnerCaricamento accountLa tabella di ricerca degli indirizzi è di proprietà del programma sbagliato
InvalidAddressLookupTableDataCaricamento accountLa tabella di ricerca degli indirizzi contiene dati non validi
InvalidAddressLookupTableIndexCaricamento accountLa ricerca nella tabella degli indirizzi utilizza un indice non valido
InvalidRentPayingAccountControllo post-esecuzioneL'account è passato da rent-exempt a rent-paying
WouldExceedMaxVoteCostLimitSchedulingLa transazione supererebbe il limite massimo di costo dei voti
WouldExceedAccountDataTotalLimitSchedulingLa transazione supererebbe il limite totale di dati dell'account
DuplicateInstructionParsing compute budgetVariante di istruzione compute budget duplicata nella stessa transazione
InsufficientFundsForRentControllo post-esecuzioneL'account non ha abbastanza lamport per coprire il rent per la dimensione dei suoi dati
MaxLoadedAccountsDataSizeExceededCaricamento accountI dati totali caricati superano il limite di 64 MiB
InvalidLoadedAccountsDataSizeLimitParsing compute budgetSetLoadedAccountsDataSizeLimit impostato a 0
ResanitizationNeededSanitizeLa transazione differiva prima/dopo l'attivazione della funzionalità e necessita di risanitizzazione
ProgramExecutionTemporarilyRestrictedCaricamento accountL'esecuzione del programma è temporaneamente limitata sull'account di riferimento
UnbalancedTransactionControllo post-esecuzioneIl saldo totale in lamport prima della transazione non è uguale al saldo dopo
ProgramCacheHitMaxLimitCaricamento accountLa cache del programma ha raggiunto il limite massimo
CommitCancelledCommitCommit annullato internamente

Is this page helpful?

Gestito da

© 2026 Solana Foundation.
Tutti i diritti riservati.
Resta connesso