Περίληψη
Οι συναλλαγές περνούν από 8 στάδια: λήψη, επαλήθευση υπογραφής, εξυγίανση, έλεγχοι προϋπολογισμού/ηλικίας, επικύρωση πληρωτή προμήθειας, φόρτωση λογαριασμού, εκτέλεση εντολής και καταχώριση.
Διαδικασία επεξεργασίας συναλλαγών
Όταν μια συναλλαγή φτάνει σε έναν validator, περνά από μια σειρά σταδίων επικύρωσης και εκτέλεσης. Το παρακάτω περιγράφει την πλήρη διαδικασία από τη λήψη έως την καταχώριση, με αναφορές σε αρχεία πηγαίου κώδικα στον agave validator client.
1. Λήψη και αποσειριοποίηση
Ο validator λαμβάνει bytes συναλλαγής μέσω UDP/QUIC. Τα ακατέργαστα bytes πρέπει
να χωρούν σε ένα μόνο πακέτο (PACKET_DATA_SIZE = 1.232 bytes). Τα bytes
αποσειριοποιούνται σε μια VersionedTransaction, η οποία περιέχει τον
πίνακα υπογραφών και ένα VersionedMessage (είτε legacy είτε v0).
2. Επαλήθευση υπογραφής (sigverify)
Οι υπογραφές επαληθεύονται στο
στάδιο sigverify
πριν η συναλλαγή εισέλθει στο στάδιο banking. Για κάθε υπογραφή στο ευρετήριο
i, ο επαληθευτής ελέγχει Ed25519(signatures[i], account_keys[i],
message_bytes). Αν κάποια υπογραφή είναι άκυρη, το πακέτο απορρίπτεται.
Η επαλήθευση είναι παραλληλοποιημένη: ο validator χωρίζει τις παρτίδες πακέτων
σε τμήματα των
VERIFY_PACKET_CHUNK_SIZE
(128) και τα επεξεργάζεται παράλληλα.
3. Εξυγίανση
Η αποσειριοποιημένη συναλλαγή εξυγιαίνεται για να παραχθεί μια
SanitizedTransaction (ή
RuntimeTransaction).
Η εξυγίανση επικυρώνει δομικά αναλλοίωτα:
- Ο αριθμός των υπογραφών ταιριάζει με το
num_required_signaturesστην κεφαλίδα - Όλα τα
program_id_indexκαιaccount_indicesτων εντολών βρίσκονται εντός ορίων - Ο πληρωτής προμήθειας (ευρετήριο λογαριασμού 0) είναι εγγράψιμος υπογράφων
Το περιτύλιγμα RuntimeTransaction αποθηκεύει προϋπολογισμένα μεταδεδομένα
από το
TransactionMeta:
το hash του μηνύματος, τη σημαία συναλλαγής ψήφου, τους αριθμούς υπογραφών
προμεταγλώττισης (Ed25519/secp256k1/secp256r1), τις λεπτομέρειες εντολής
προϋπολογισμού υπολογισμού και το συνολικό μήκος δεδομένων εντολής.
4. Έλεγχος προϋπολογισμού υπολογισμού, ηλικίας και κρυφής μνήμης κατάστασης
Η μέθοδος
check_transactions
εκτελεί διάφορους ελέγχους ανά συναλλαγή:
Προϋπολογισμός υπολογισμού: Οι οδηγίες προϋπολογισμού υπολογισμού της
συναλλαγής αναλύονται και επικυρώνονται πρώτα. Οι λεπτομέρειες χρέωσης
υπολογίζονται από τα όρια προϋπολογισμού και τη χρέωση προτεραιότητας. Εάν ο
προϋπολογισμός υπολογισμού είναι μη έγκυρος ή αντικρουόμενος, η συναλλαγή
αποτυγχάνει με σφάλματα ανάλυσης προϋπολογισμού υπολογισμού όπως
DuplicateInstruction, InstructionError(..., InvalidInstructionData) ή
InvalidLoadedAccountsDataSizeLimit.
Ηλικία blockhash: Το recent_blockhash της συναλλαγής αναζητείται στο
BlockhashQueue.
Εάν το hash βρεθεί και η ηλικία του είναι εντός MAX_PROCESSING_AGE (150
slots), η συναλλαγή προχωρά. Εάν δεν βρεθεί, ο validator ελέγχει για έγκυρο
durable nonce.
Κρυφή μνήμη κατάστασης: Το hash μηνύματος της συναλλαγής ελέγχεται έναντι
μιας κρυφής μνήμης κατάστασης. Εάν βρεθεί, η συναλλαγή απορρίπτεται με
AlreadyProcessed.
5. Επικύρωση nonce και πληρωτή χρέωσης
Η μέθοδος
validate_transaction_nonce_and_fee_payer
στο SVM χειρίζεται δύο επικυρώσεις:
Επικύρωση nonce (εάν ισχύει): Για συναλλαγές nonce, ο validator φορτώνει τον λογαριασμό nonce και επαληθεύει:
- Ο λογαριασμός ανήκει στο System Program
- Αναλύεται ως
State::Initialized - Το αποθηκευμένο durable nonce ταιριάζει με το
recent_blockhashτης συναλλαγής - Το nonce μπορεί να προχωρήσει (το τρέχον durable nonce διαφέρει από το επόμενο durable nonce, δηλαδή το nonce δεν έχει ήδη χρησιμοποιηθεί στο τρέχον block)
- Η εξουσιοδότηση nonce έχει υπογράψει τη συναλλαγή
Εάν είναι έγκυρο, το nonce προχωρά στην επόμενη τιμή durable nonce. Δείτε
validate_transaction_nonce.
Επικύρωση πληρωτή χρέωσης: Ο λογαριασμός πληρωτή χρέωσης (πάντα δείκτης 0)
φορτώνεται και ελέγχεται από τη
validate_fee_payer:
- Ο λογαριασμός πρέπει να υπάρχει (lamports > 0), διαφορετικά
AccountNotFound - Ο λογαριασμός πρέπει να είναι system account ή nonce account, διαφορετικά
InvalidAccountForFee - Τα lamports πρέπει να καλύπτουν το
min_balance + total_fee, όπου τοmin_balanceείναι 0 για system accounts ήrent.minimum_balance(NonceState::size())για nonce accounts· διαφορετικάInsufficientFundsForFee - Μετά την αφαίρεση της χρέωσης, ο λογαριασμός πρέπει να παραμείνει απαλλαγμένος από ενοίκιο (δεν μπορεί να μεταβεί από απαλλαγμένος από ενοίκιο σε πληρωτής ενοικίου)
Η χρέωση αφαιρείται από τον πληρωτή χρέωσης σε αυτό το στάδιο. Ένα στιγμιότυπο
του πληρωτή χρέωσης μετά την αφαίρεση της χρέωσης (και του προηγμένου nonce, εάν
υπάρχει) αποθηκεύεται ως RollbackAccounts, που είναι οι λογαριασμοί που
καταχωρούνται ακόμα και αν η εκτέλεση αποτύχει.
6. Φόρτωση λογαριασμών
Η
load_transaction
φορτώνει όλους τους λογαριασμούς στους οποίους αναφέρεται η συναλλαγή. Ο
AccountLoader
τυλίγει το εξωτερικό αποθετήριο λογαριασμών και διατηρεί μια τοπική cache για
την ομάδα, ώστε οι λογαριασμοί που τροποποιήθηκαν από προηγούμενες συναλλαγές
στην ίδια ομάδα να είναι ορατοί στις επόμενες.
Για κάθε λογαριασμό που δεν είναι πληρωτής χρέωσης, ο φορτωτής:
- Ανακτά τον λογαριασμό από την cache ή το accounts-db
- Ενημερώνει την κατάσταση απαλλαγής από ενοίκιο εάν χρειάζεται
- Συσσωρεύει το μέγεθος δεδομένων του λογαριασμού προς το
loaded_accounts_data_size_limit(προεπιλογή 64 MiB). Κάθε λογαριασμός επιβαρύνεται με ένα βασικό overheadTRANSACTION_ACCOUNT_BASE_SIZE(64 bytes) συν το μήκος των δεδομένων του
Για κάθε πρόγραμμα που καλείται από τις οδηγίες της συναλλαγής, ο φορτωτής
επαληθεύει ότι ο λογαριασμός του προγράμματος υπάρχει και ανήκει σε έγκυρο
φορτωτή (NativeLoader ή έναν από τους PROGRAM_OWNERS). Μη έγκυρα
προγράμματα αποτυγχάνουν με ProgramAccountNotFound ή
InvalidProgramForExecution.
Τα προγράμματα LoaderV3 (αναβαθμίσιμα) φορτώνουν σιωπηρά τον συσχετισμένο λογαριασμό programdata, ο οποίος επίσης μετράει στο όριο μεγέθους φορτωμένων δεδομένων.
Εάν η φόρτωση λογαριασμού αποτύχει αλλά ο πληρωτής χρέωσης επικυρώθηκε επιτυχώς,
η συναλλαγή γίνεται αποτέλεσμα
FeesOnly:
η χρέωση εξακολουθεί να εισπράττεται αλλά δεν εκτελούνται οδηγίες.
7. Εκτέλεση οδηγιών
Η
execute_loaded_transaction
δημιουργεί ένα TransactionContext με όλους τους φορτωμένους λογαριασμούς
και καλεί την
process_message.
Οι οδηγίες εκτελούνται διαδοχικά με τη σειρά που εμφανίζονται στο μήνυμα. Κάθε
κλήση οδηγίας δημιουργεί ένα InvokeContext και καλεί το πρόγραμμα-στόχο.
Λεπτομέρειες επεξεργασίας οδηγιών
Η συνάρτηση
process_message
του runtime επαναλαμβάνει κάθε οδηγία και καλεί το πρόγραμμα-στόχο:
- Για κάθε εντολή, το runtime καλεί το
prepare_next_top_level_instruction, το οποίο δημιουργεί τοInstructionContext. Αυτό το context περιέχει αναφορές στους λογαριασμούς της εντολής (επιλυμένους από τους μεταγλωττισμένους δείκτες), τα δεδομένα της εντολής και τον δείκτη του program account. - Το runtime ελέγχει αν το πρόγραμμα είναι precompile (Ed25519, Secp256k1, Secp256r1). Τα precompiles επαληθεύονται απευθείας χωρίς να καλείται το BPF VM.
- Για όλα τα άλλα προγράμματα, το runtime καλεί το
process_instruction, το οποίο φορτώνει το πρόγραμμα από την cache και το εκτελεί στο BPF virtual machine. - Μετά την ολοκλήρωση της εντολής, το runtime
επαληθεύει
ότι το συνολικό υπόλοιπο lamport σε όλους τους λογαριασμούς εντολών δεν έχει
αλλάξει (έλεγχος
UnbalancedInstruction). - Αν οποιαδήποτε εντολή αποτύχει, ολόκληρη η συναλλαγή ακυρώνεται. Καμία ενδιάμεση αλλαγή κατάστασης δεν καταχωρείται.
Κάθε εντολή αυξάνει το instruction trace. Το trace περιλαμβάνει τόσο εντολές
ανώτατου επιπέδου όσο και οποιαδήποτε CPIs καλούν. Το συνολικό
μήκος του trace (εντολές ανώτατου επιπέδου συν όλα τα ένθετα CPIs) δεν μπορεί να
υπερβαίνει το 64 (MAX_INSTRUCTION_TRACE_LENGTH). Η υπέρβαση αυτού του
ορίου επιστρέφει InstructionError::MaxInstructionTraceLengthExceeded.
Μετά την εκτέλεση, το runtime επαληθεύει ότι:
- Το άθροισμα των lamports σε όλους τους λογαριασμούς δεν έχει αλλάξει
- Κανένας λογαριασμός δεν μετέβη από rent-exempt σε rent-paying
8. Commit ή rollback
Αν η εκτέλεση πετύχει, οι τροποποιημένες καταστάσεις λογαριασμών από το
TransactionContext γράφονται πίσω στην batch-local cache του
AccountLoader. Αν η εκτέλεση αποτύχει, μόνο τα RollbackAccounts (fee
payer με αφαιρεμένη χρέωση και προωθημένο nonce) γράφονται πίσω. Η χρέωση
εξακολουθεί να εισπράττεται, αλλά όλες οι άλλες αλλαγές λογαριασμών
απορρίπτονται.
Σύνοψη 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)
Αναφορά σφαλμάτων συναλλαγών
Ο παρακάτω πίνακας παραθέτει όλες τις παραλλαγές
TransactionError
και σε ποιο στάδιο του pipeline εμφανίζονται:
| Σφάλμα | Στάδιο | Αιτία |
|---|---|---|
AccountInUse | Προγραμματισμός | Ο λογαριασμός είναι ήδη κλειδωμένος από άλλη συναλλαγή στην ίδια ομάδα |
AccountLoadedTwice | Προγραμματισμός | Ένα pubkey εμφανίζεται δύο φορές στο account_keys της συναλλαγής |
AccountNotFound | Επικύρωση πληρωτή προμήθειας | Ο λογαριασμός πληρωτή προμήθειας δεν υπάρχει |
ProgramAccountNotFound | Φόρτωση λογαριασμού | Ένα πρόγραμμα που καλέστηκε δεν υπάρχει |
InsufficientFundsForFee | Επικύρωση πληρωτή προμήθειας | Ο πληρωτής προμήθειας δεν μπορεί να καλύψει την προμήθεια + το ελάχιστο rent-exempt |
InvalidAccountForFee | Επικύρωση πληρωτή προμήθειας | Ο πληρωτής προμήθειας δεν είναι λογαριασμός συστήματος ή nonce |
AlreadyProcessed | Προσωρινή μνήμη κατάστασης | Η συναλλαγή είχε ήδη επεξεργαστεί |
BlockhashNotFound | Έλεγχος ηλικίας | Το blockhash δεν βρίσκεται στην ουρά και δεν είναι έγκυρο nonce |
InstructionError | Εκτέλεση | Προέκυψε σφάλμα κατά την επεξεργασία μιας εντολής (περιλαμβάνει δείκτη εντολής και συγκεκριμένο InstructionError) |
CallChainTooDeep | Φόρτωση λογαριασμού | Η αλυσίδα κλήσεων του φορτωτή είναι πολύ βαθιά |
MissingSignatureForFee | Εξυγίανση | Η συναλλαγή απαιτεί προμήθεια αλλά δεν έχει υπογραφή |
InvalidAccountIndex | Εξυγίανση | Η συναλλαγή περιέχει μη έγκυρη αναφορά λογαριασμού |
SignatureFailure | Επαλήθευση υπογραφής | Η υπογραφή Ed25519 δεν επαληθεύεται (το πακέτο απορρίπτεται) |
InvalidProgramForExecution | Φόρτωση λογαριασμού | Το πρόγραμμα δεν ανήκει σε έγκυρο φορτωτή |
SanitizeFailure | Εξυγίανση | Η συναλλαγή απέτυχε να εξυγιάνει σωστά τις μετατοπίσεις λογαριασμών |
ClusterMaintenance | Προγραμματισμός | Οι συναλλαγές είναι προσωρινά απενεργοποιημένες λόγω συντήρησης του cluster |
AccountBorrowOutstanding | Εκτέλεση | Η επεξεργασία της συναλλαγής άφησε έναν λογαριασμό με εκκρεμή δανεισμένη αναφορά |
WouldExceedMaxBlockCostLimit | Προγραμματισμός | Η συναλλαγή θα υπερέβαινε το μέγιστο όριο κόστους μπλοκ |
UnsupportedVersion | Εξυγίανση | Η έκδοση της συναλλαγής δεν υποστηρίζεται |
InvalidWritableAccount | Φόρτωση λογαριασμού | Η συναλλαγή φορτώνει εγγράψιμο λογαριασμό που δεν μπορεί να γραφτεί |
WouldExceedMaxAccountCostLimit | Προγραμματισμός | Η συναλλαγή θα υπερέβαινε το μέγιστο όριο κόστους λογαριασμού εντός του μπλοκ |
WouldExceedAccountDataBlockLimit | Προγραμματισμός | Η συναλλαγή θα υπερέβαινε το όριο δεδομένων λογαριασμού εντός του μπλοκ |
TooManyAccountLocks | Προγραμματισμός | Η συναλλαγή κλείδωσε πάρα πολλούς λογαριασμούς |
AddressLookupTableNotFound | Φόρτωση λογαριασμού | Ο λογαριασμός πίνακα αναζήτησης διευθύνσεων δεν υπάρχει |
InvalidAddressLookupTableOwner | Φόρτωση λογαριασμού | Ο πίνακας αναζήτησης διευθύνσεων ανήκει στο λάθος πρόγραμμα |
InvalidAddressLookupTableData | Φόρτωση λογαριασμού | Ο πίνακας αναζήτησης διευθύνσεων περιέχει μη έγκυρα δεδομένα |
InvalidAddressLookupTableIndex | Φόρτωση λογαριασμού | Η αναζήτηση πίνακα διευθύνσεων χρησιμοποιεί μη έγκυρο δείκτη |
InvalidRentPayingAccount | Έλεγχος μετά την εκτέλεση | Ο λογαριασμός μετέβη από rent-exempt σε rent-paying |
WouldExceedMaxVoteCostLimit | Προγραμματισμός | Η συναλλαγή θα υπερέβαινε το μέγιστο όριο κόστους ψήφου |
WouldExceedAccountDataTotalLimit | Προγραμματισμός | Η συναλλαγή θα υπερέβαινε το συνολικό όριο δεδομένων λογαριασμού |
DuplicateInstruction | Ανάλυση προϋπολογισμού υπολογισμού | Διπλότυπη παραλλαγή εντολής προϋπολογισμού υπολογισμού στην ίδια συναλλαγή |
InsufficientFundsForRent | Έλεγχος μετά την εκτέλεση | Ο λογαριασμός δεν έχει αρκετά lamports για να καλύψει το rent για το μέγεθος δεδομένων του |
MaxLoadedAccountsDataSizeExceeded | Φόρτωση λογαριασμού | Τα συνολικά φορτωμένα δεδομένα υπερβαίνουν το όριο των 64 MiB |
InvalidLoadedAccountsDataSizeLimit | Ανάλυση προϋπολογισμού υπολογισμού | Το SetLoadedAccountsDataSizeLimit ορίστηκε σε 0 |
ResanitizationNeeded | Εξυγίανση | Η συναλλαγή διέφερε πριν/μετά την ενεργοποίηση χαρακτηριστικού και χρειάζεται επανεξυγίανση |
ProgramExecutionTemporarilyRestricted | Φόρτωση λογαριασμού | Η εκτέλεση προγράμματος είναι προσωρινά περιορισμένη στον αναφερόμενο λογαριασμό |
UnbalancedTransaction | Έλεγχος μετά την εκτέλεση | Το συνολικό υπόλοιπο lamport πριν τη συναλλαγή δεν ισούται με το υπόλοιπο μετά |
ProgramCacheHitMaxLimit | Φόρτωση λογαριασμού | Η προσωρινή μνήμη προγράμματος έφτασε το μέγιστο όριο |
CommitCancelled | Commit | Το commit ακυρώθηκε εσωτερικά |
Is this page helpful?