Zusammenfassung
Transaktionen durchlaufen 8 Stufen: Empfang, Signaturverifizierung, Bereinigung, Budget-/Altersprüfungen, Gebührenzahler-Validierung, Konto-Laden, Instruktionsausführung und Commit.
Transaktionsverarbeitungs-Pipeline
Wenn eine Transaktion bei einem Validator eingeht, durchläuft sie eine Reihe von Validierungs- und Ausführungsstufen. Im Folgenden wird die vollständige Pipeline vom Empfang bis zum Commit beschrieben, mit Quelldatei-Referenzen in den agave Validator-Client.
1. Empfang und Deserialisierung
Der Validator empfängt Transaktions-Bytes über UDP/QUIC. Die rohen Bytes müssen
in ein einzelnes Paket passen (PACKET_DATA_SIZE = 1.232 Bytes). Die Bytes
werden in eine VersionedTransaction deserialisiert, die das Signatur-Array
und eine VersionedMessage (entweder Legacy oder v0) enthält.
2. Signaturverifizierung (sigverify)
Signaturen werden in der
sigverify-Stufe
verifiziert, bevor die Transaktion in die Banking-Stufe eintritt. Für jede
Signatur am Index i prüft der Verifizierer Ed25519(signatures[i],
account_keys[i], message_bytes). Wenn eine Signatur ungültig ist, wird das
Paket verworfen.
Die Verifizierung ist parallelisiert: Der Validator teilt Paket-Batches in
Chunks von
VERIFY_PACKET_CHUNK_SIZE
(128) auf und verarbeitet sie parallel.
3. Bereinigung
Die deserialisierte Transaktion wird bereinigt, um eine
SanitizedTransaction (oder
RuntimeTransaction)
zu erzeugen. Die Bereinigung validiert strukturelle Invarianten:
- Anzahl der Signaturen stimmt mit
num_required_signaturesim Header überein - Alle Instruktions-
program_id_indexundaccount_indicesliegen innerhalb der Grenzen - Der Gebührenzahler (Konto-Index 0) ist ein beschreibbarer Signer
Der RuntimeTransaction-Wrapper speichert vorberechnete Metadaten aus
TransactionMeta:
den Nachrichten-Hash, das Vote-Transaktions-Flag, Precompile-Signaturzählungen
(Ed25519/secp256k1/secp256r1), Compute-Budget-Instruktionsdetails und die
gesamte Instruktionsdatenlänge.
4. Compute-Budget, Alter und Status-Cache prüfen
Die
check_transactions
Methode führt mehrere Prüfungen pro Transaktion durch:
Compute-Budget: Die Compute-Budget-Anweisungen der Transaktion werden zuerst
geparst und validiert. Gebührendetails werden aus den Budget-Limits und der
Priorisierungsgebühr berechnet. Wenn das Compute-Budget ungültig oder
widersprüchlich ist, schlägt die Transaktion mit Compute-Budget-Parsing-Fehlern
wie DuplicateInstruction, InstructionError(..., InvalidInstructionData)
oder InvalidLoadedAccountsDataSizeLimit fehl.
Blockhash-Alter: Der recent_blockhash der Transaktion wird in der
BlockhashQueue
nachgeschlagen. Wenn der Hash gefunden wird und sein Alter innerhalb von
MAX_PROCESSING_AGE (150 Slots) liegt, wird die Transaktion fortgesetzt.
Falls nicht gefunden, prüft der Validator auf eine gültige
Durable Nonce.
Status-Cache: Der Message-Hash der Transaktion wird gegen einen Status-Cache
geprüft. Falls gefunden, wird die Transaktion mit AlreadyProcessed
abgelehnt.
5. Nonce und Fee-Payer validieren
Die
validate_transaction_nonce_and_fee_payer
Methode in der SVM führt zwei Validierungen durch:
Nonce-Validierung (falls zutreffend): Bei Nonce-Transaktionen lädt der Validator das Nonce-Konto und überprüft:
- Das Konto gehört dem System Program
- Es lässt sich als
State::Initializedparsen - Die gespeicherte Durable Nonce stimmt mit dem
recent_blockhashder Transaktion überein - Die Nonce kann fortgeschritten werden (ihre aktuelle Durable Nonce unterscheidet sich von der nächsten Durable Nonce, d. h. die Nonce wurde im aktuellen Block noch nicht verwendet)
- Die Nonce-Authority hat die Transaktion signiert
Falls gültig, wird die Nonce auf den nächsten Durable-Nonce-Wert
fortgeschritten. Siehe
validate_transaction_nonce.
Fee-Payer-Validierung: Das Fee-Payer-Konto (immer Index 0) wird geladen und
geprüft durch
validate_fee_payer:
- Konto muss existieren (Lamports > 0), andernfalls
AccountNotFound - Konto muss ein System-Konto oder Nonce-Konto sein, andernfalls
InvalidAccountForFee - Lamports müssen
min_balance + total_feeabdecken, wobeimin_balance0 für System-Konten oderrent.minimum_balance(NonceState::size())für Nonce-Konten beträgt; andernfallsInsufficientFundsForFee - Nach Gebührenabzug muss das Konto rent-exempt bleiben (kann nicht von rent-exempt zu rent-paying wechseln)
Die Gebühr wird in dieser Phase vom Gebührenzahler abgezogen. Ein Snapshot des
Gebührenzahlers nach Abzug der Gebühr (und der erweiterten Nonce, falls
zutreffend) wird als RollbackAccounts gespeichert, das sind die Konten,
die auch dann committet werden, wenn die Ausführung fehlschlägt.
6. Konten laden
load_transaction
lädt alle Konten, auf die die Transaktion verweist. Der
AccountLoader
umschließt den externen Konto-Speicher und verwaltet einen Batch-lokalen Cache,
sodass Konten, die von früheren Transaktionen im selben Batch geändert wurden,
für spätere sichtbar sind.
Für jedes Nicht-Gebührenzahler-Konto führt der Loader folgende Schritte aus:
- Ruft das Konto aus dem Cache oder der Konten-Datenbank ab
- Aktualisiert den Mietbefreiungsstatus bei Bedarf
- Akkumuliert die Datengröße des Kontos zum
loaded_accounts_data_size_limit(Standard 64 MiB). Jedes Konto verursacht einen Basis-Overhead vonTRANSACTION_ACCOUNT_BASE_SIZE(64 Bytes) plus seiner Datenlänge
Für jedes Programm, das durch die Anweisungen der Transaktion aufgerufen wird,
überprüft der Loader, dass das Programm-Konto existiert und einem gültigen
Loader gehört (NativeLoader oder einem der PROGRAM_OWNERS). Ungültige
Programme schlagen fehl mit ProgramAccountNotFound oder
InvalidProgramForExecution.
LoaderV3 (upgradeable) Programme laden implizit ihr zugehöriges Programmdaten-Konto, welches ebenfalls zum Limit der geladenen Datengröße zählt.
Wenn das Laden des Kontos fehlschlägt, aber der Gebührenzahler erfolgreich
validiert wurde, wird die Transaktion zu einem
FeesOnly
Ergebnis: Die Gebühr wird weiterhin eingezogen, aber es werden keine Anweisungen
ausgeführt.
7. Anweisungen ausführen
execute_loaded_transaction
erstellt einen TransactionContext mit allen geladenen Konten und ruft
process_message
auf. Anweisungen werden sequenziell in der Reihenfolge ausgeführt, in der sie in
der Nachricht erscheinen. Jeder Anweisungsaufruf erstellt einen
InvokeContext und ruft das Zielprogramm auf.
Details zur Anweisungsverarbeitung
Die
process_message
Funktion der Runtime iteriert durch jede Anweisung und ruft das Zielprogramm
auf:
- Für jede Anweisung ruft die Runtime
prepare_next_top_level_instructionauf, die denInstructionContexterstellt. Dieser Kontext enthält Referenzen zu den Konten der Anweisung (aufgelöst aus den kompilierten Indizes), den Anweisungsdaten und dem Programm-Konten-Index. - Die Runtime prüft, ob das Programm ein Precompile ist (Ed25519, Secp256k1, Secp256r1). Precompiles werden direkt verifiziert, ohne die BPF-VM aufzurufen.
- Für alle anderen Programme ruft die Runtime
process_instructionauf, die das Programm aus dem Cache lädt und es in der virtuellen BPF-Maschine ausführt. - Nach Abschluss der Anweisung
verifiziert
die Runtime, dass sich der gesamte Lamport-Saldo über alle Anweisungs-Konten
hinweg nicht geändert hat (
UnbalancedInstruction-Prüfung). - Wenn eine Anweisung fehlschlägt, wird die gesamte Transaktion zurückgerollt. Es werden keine Zwischenzustandsänderungen übernommen.
Jede Anweisung erhöht den Anweisungs-Trace. Der Trace umfasst sowohl Anweisungen
der obersten Ebene als auch alle von ihnen aufgerufenen CPIs.
Die gesamte Trace-Länge (Anweisungen der obersten Ebene plus alle
verschachtelten CPIs) darf 64 nicht überschreiten
(MAX_INSTRUCTION_TRACE_LENGTH). Das Überschreiten dieser Grenze gibt
InstructionError::MaxInstructionTraceLengthExceeded zurück.
Nach der Ausführung verifiziert die Runtime, dass:
- Die Summe der Lamports über alle Konten hinweg sich nicht geändert hat
- Kein Konto von mietbefreit zu mietpflichtig gewechselt ist
8. Commit oder Rollback
Wenn die Ausführung erfolgreich ist, werden die geänderten Kontenzustände aus
dem TransactionContext in den batch-lokalen Cache des AccountLoader
zurückgeschrieben. Wenn die Ausführung fehlschlägt, werden nur die
RollbackAccounts (Gebührenzahler mit abgezogener Gebühr und
fortgeschrittener Nonce) zurückgeschrieben. Die Gebühr wird weiterhin
eingezogen, aber alle anderen Kontenänderungen werden verworfen.
Pipeline-Zusammenfassung
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)
Transaktionsfehler-Referenz
Die folgende Tabelle listet alle
TransactionError-Varianten
und in welcher Pipeline-Phase sie auftreten:
| Fehler | Phase | Ursache |
|---|---|---|
AccountInUse | Scheduling | Konto ist bereits durch eine andere Transaktion im selben Batch gesperrt |
AccountLoadedTwice | Scheduling | Ein pubkey erscheint zweimal im account_keys der Transaktion |
AccountNotFound | Fee-Payer-Validierung | Fee-Payer-Konto existiert nicht |
ProgramAccountNotFound | Konto-Laden | Ein aufgerufenes Programm existiert nicht |
InsufficientFundsForFee | Fee-Payer-Validierung | Fee-Payer kann Gebühr + Rent-Exempt-Minimum nicht decken |
InvalidAccountForFee | Fee-Payer-Validierung | Fee-Payer ist kein System- oder Nonce-Konto |
AlreadyProcessed | Status-Cache | Transaktion wurde bereits verarbeitet |
BlockhashNotFound | Altersprüfung | Blockhash nicht in der Warteschlange und keine gültige Nonce |
InstructionError | Ausführung | Ein Fehler ist bei der Verarbeitung einer Instruktion aufgetreten (enthält Instruktionsindex und spezifischen InstructionError) |
CallChainTooDeep | Konto-Laden | Loader-Aufrufkette ist zu tief |
MissingSignatureForFee | Sanitize | Transaktion erfordert eine Gebühr, hat aber keine Signatur |
InvalidAccountIndex | Sanitize | Transaktion enthält eine ungültige Konto-Referenz |
SignatureFailure | Sigverify | Ed25519-Signatur kann nicht verifiziert werden (Paket wird verworfen) |
InvalidProgramForExecution | Konto-Laden | Programm gehört keinem gültigen Loader |
SanitizeFailure | Sanitize | Transaktion konnte Konto-Offsets nicht korrekt bereinigen |
ClusterMaintenance | Scheduling | Transaktionen sind derzeit aufgrund von Cluster-Wartung deaktiviert |
AccountBorrowOutstanding | Ausführung | Transaktionsverarbeitung hat ein Konto mit ausstehender geliehener Referenz hinterlassen |
WouldExceedMaxBlockCostLimit | Scheduling | Transaktion würde maximales Block-Kostenlimit überschreiten |
UnsupportedVersion | Sanitize | Transaktionsversion wird nicht unterstützt |
InvalidWritableAccount | Konto-Laden | Transaktion lädt ein beschreibbares Konto, das nicht beschrieben werden kann |
WouldExceedMaxAccountCostLimit | Scheduling | Transaktion würde maximales Konto-Kostenlimit innerhalb des Blocks überschreiten |
WouldExceedAccountDataBlockLimit | Scheduling | Transaktion würde Konto-Datenlimit innerhalb des Blocks überschreiten |
TooManyAccountLocks | Scheduling | Transaktion hat zu viele Konten gesperrt |
AddressLookupTableNotFound | Konto-Laden | Address-Lookup-Table-Konto existiert nicht |
InvalidAddressLookupTableOwner | Konto-Laden | Address-Lookup-Table gehört dem falschen Programm |
InvalidAddressLookupTableData | Konto-Laden | Address-Lookup-Table enthält ungültige Daten |
InvalidAddressLookupTableIndex | Konto-Laden | Address-Table-Lookup verwendet einen ungültigen Index |
InvalidRentPayingAccount | Post-Execution-Prüfung | Konto wechselte von Rent-Exempt zu Rent-Paying |
WouldExceedMaxVoteCostLimit | Scheduling | Transaktion würde maximales Vote-Kostenlimit überschreiten |
WouldExceedAccountDataTotalLimit | Scheduling | Transaktion würde gesamtes Konto-Datenlimit überschreiten |
DuplicateInstruction | Compute-Budget-Parsing | Doppelte Compute-Budget-Instruktionsvariante in derselben Transaktion |
InsufficientFundsForRent | Post-Execution-Prüfung | Konto hat nicht genug Lamports, um Rent für seine Datengröße zu decken |
MaxLoadedAccountsDataSizeExceeded | Konto-Laden | Gesamte geladene Daten überschreiten 64-MiB-Limit |
InvalidLoadedAccountsDataSizeLimit | Compute-Budget-Parsing | SetLoadedAccountsDataSizeLimit auf 0 gesetzt |
ResanitizationNeeded | Sanitize | Transaktion unterschied sich vor/nach Feature-Aktivierung und benötigt erneute Bereinigung |
ProgramExecutionTemporarilyRestricted | Konto-Laden | Programmausführung ist vorübergehend auf dem referenzierten Konto eingeschränkt |
UnbalancedTransaction | Post-Execution-Prüfung | Gesamter Lamport-Saldo vor der Transaktion entspricht nicht dem Saldo danach |
ProgramCacheHitMaxLimit | Konto-Laden | Programm-Cache hat maximales Limit erreicht |
CommitCancelled | Commit | Commit intern abgebrochen |
Is this page helpful?