Transaktions-Pipeline

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_signatures im Header überein
  • Alle Instruktions-program_id_index und account_indices liegen 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::Initialized parsen
  • Die gespeicherte Durable Nonce stimmt mit dem recent_blockhash der 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_fee abdecken, wobei min_balance 0 für System-Konten oder rent.minimum_balance(NonceState::size()) für Nonce-Konten beträgt; andernfalls InsufficientFundsForFee
  • 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:

  1. Ruft das Konto aus dem Cache oder der Konten-Datenbank ab
  2. Aktualisiert den Mietbefreiungsstatus bei Bedarf
  3. Akkumuliert die Datengröße des Kontos zum loaded_accounts_data_size_limit (Standard 64 MiB). Jedes Konto verursacht einen Basis-Overhead von TRANSACTION_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:

  1. Für jede Anweisung ruft die Runtime prepare_next_top_level_instruction auf, die den InstructionContext erstellt. Dieser Kontext enthält Referenzen zu den Konten der Anweisung (aufgelöst aus den kompilierten Indizes), den Anweisungsdaten und dem Programm-Konten-Index.
  2. Die Runtime prüft, ob das Programm ein Precompile ist (Ed25519, Secp256k1, Secp256r1). Precompiles werden direkt verifiziert, ohne die BPF-VM aufzurufen.
  3. Für alle anderen Programme ruft die Runtime process_instruction auf, die das Programm aus dem Cache lädt und es in der virtuellen BPF-Maschine ausführt.
  4. 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).
  5. 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:

FehlerPhaseUrsache
AccountInUseSchedulingKonto ist bereits durch eine andere Transaktion im selben Batch gesperrt
AccountLoadedTwiceSchedulingEin pubkey erscheint zweimal im account_keys der Transaktion
AccountNotFoundFee-Payer-ValidierungFee-Payer-Konto existiert nicht
ProgramAccountNotFoundKonto-LadenEin aufgerufenes Programm existiert nicht
InsufficientFundsForFeeFee-Payer-ValidierungFee-Payer kann Gebühr + Rent-Exempt-Minimum nicht decken
InvalidAccountForFeeFee-Payer-ValidierungFee-Payer ist kein System- oder Nonce-Konto
AlreadyProcessedStatus-CacheTransaktion wurde bereits verarbeitet
BlockhashNotFoundAltersprüfungBlockhash nicht in der Warteschlange und keine gültige Nonce
InstructionErrorAusführungEin Fehler ist bei der Verarbeitung einer Instruktion aufgetreten (enthält Instruktionsindex und spezifischen InstructionError)
CallChainTooDeepKonto-LadenLoader-Aufrufkette ist zu tief
MissingSignatureForFeeSanitizeTransaktion erfordert eine Gebühr, hat aber keine Signatur
InvalidAccountIndexSanitizeTransaktion enthält eine ungültige Konto-Referenz
SignatureFailureSigverifyEd25519-Signatur kann nicht verifiziert werden (Paket wird verworfen)
InvalidProgramForExecutionKonto-LadenProgramm gehört keinem gültigen Loader
SanitizeFailureSanitizeTransaktion konnte Konto-Offsets nicht korrekt bereinigen
ClusterMaintenanceSchedulingTransaktionen sind derzeit aufgrund von Cluster-Wartung deaktiviert
AccountBorrowOutstandingAusführungTransaktionsverarbeitung hat ein Konto mit ausstehender geliehener Referenz hinterlassen
WouldExceedMaxBlockCostLimitSchedulingTransaktion würde maximales Block-Kostenlimit überschreiten
UnsupportedVersionSanitizeTransaktionsversion wird nicht unterstützt
InvalidWritableAccountKonto-LadenTransaktion lädt ein beschreibbares Konto, das nicht beschrieben werden kann
WouldExceedMaxAccountCostLimitSchedulingTransaktion würde maximales Konto-Kostenlimit innerhalb des Blocks überschreiten
WouldExceedAccountDataBlockLimitSchedulingTransaktion würde Konto-Datenlimit innerhalb des Blocks überschreiten
TooManyAccountLocksSchedulingTransaktion hat zu viele Konten gesperrt
AddressLookupTableNotFoundKonto-LadenAddress-Lookup-Table-Konto existiert nicht
InvalidAddressLookupTableOwnerKonto-LadenAddress-Lookup-Table gehört dem falschen Programm
InvalidAddressLookupTableDataKonto-LadenAddress-Lookup-Table enthält ungültige Daten
InvalidAddressLookupTableIndexKonto-LadenAddress-Table-Lookup verwendet einen ungültigen Index
InvalidRentPayingAccountPost-Execution-PrüfungKonto wechselte von Rent-Exempt zu Rent-Paying
WouldExceedMaxVoteCostLimitSchedulingTransaktion würde maximales Vote-Kostenlimit überschreiten
WouldExceedAccountDataTotalLimitSchedulingTransaktion würde gesamtes Konto-Datenlimit überschreiten
DuplicateInstructionCompute-Budget-ParsingDoppelte Compute-Budget-Instruktionsvariante in derselben Transaktion
InsufficientFundsForRentPost-Execution-PrüfungKonto hat nicht genug Lamports, um Rent für seine Datengröße zu decken
MaxLoadedAccountsDataSizeExceededKonto-LadenGesamte geladene Daten überschreiten 64-MiB-Limit
InvalidLoadedAccountsDataSizeLimitCompute-Budget-ParsingSetLoadedAccountsDataSizeLimit auf 0 gesetzt
ResanitizationNeededSanitizeTransaktion unterschied sich vor/nach Feature-Aktivierung und benötigt erneute Bereinigung
ProgramExecutionTemporarilyRestrictedKonto-LadenProgrammausführung ist vorübergehend auf dem referenzierten Konto eingeschränkt
UnbalancedTransactionPost-Execution-PrüfungGesamter Lamport-Saldo vor der Transaktion entspricht nicht dem Saldo danach
ProgramCacheHitMaxLimitKonto-LadenProgramm-Cache hat maximales Limit erreicht
CommitCancelledCommitCommit intern abgebrochen

Is this page helpful?

Verwaltet von

© 2026 Solana Foundation.
Alle Rechte vorbehalten.
Verbinden Sie sich