Quy trình xử lý giao dịch

Tóm tắt

Các giao dịch trải qua 8 giai đoạn: nhận, xác minh chữ ký, kiểm tra tính hợp lệ, kiểm tra ngân sách/thời gian, xác thực người trả phí, tải tài khoản, thực thi lệnh và commit.

Quy trình xử lý giao dịch

Khi một giao dịch đến validator, nó sẽ trải qua một loạt các giai đoạn xác thực và thực thi. Phần sau mô tả quy trình đầy đủ từ việc nhận đến commit, với các tham chiếu tệp nguồn vào agave validator client.

1. Nhận và giải mã

Validator nhận các byte giao dịch qua UDP/QUIC. Các byte thô phải vừa trong một gói tin duy nhất (PACKET_DATA_SIZE = 1.232 byte). Các byte được giải mã thành một VersionedTransaction, chứa mảng chữ ký và một VersionedMessage (legacy hoặc v0).

2. Xác minh chữ ký (sigverify)

Các chữ ký được xác minh trong giai đoạn sigverify trước khi giao dịch vào giai đoạn banking. Với mỗi chữ ký tại chỉ số i, trình xác minh kiểm tra Ed25519(signatures[i], account_keys[i], message_bytes). Nếu bất kỳ chữ ký nào không hợp lệ, gói tin sẽ bị loại bỏ.

Việc xác minh được song song hóa: validator chia các batch gói tin thành các chunk VERIFY_PACKET_CHUNK_SIZE (128) và xử lý chúng song song.

3. Kiểm tra tính hợp lệ

Giao dịch đã giải mã được kiểm tra để tạo ra một SanitizedTransaction (hoặc RuntimeTransaction). Việc kiểm tra xác thực các bất biến cấu trúc:

  • Số lượng chữ ký khớp với num_required_signatures trong header
  • Tất cả program_id_indexaccount_indices của lệnh đều nằm trong giới hạn
  • Người trả phí (chỉ số tài khoản 0) là một signer có thể ghi

Wrapper RuntimeTransaction lưu trữ metadata được tính toán trước từ TransactionMeta: message hash, cờ giao dịch vote, số lượng chữ ký precompile (Ed25519/secp256k1/secp256r1), chi tiết lệnh compute budget và tổng độ dài dữ liệu lệnh.

4. Kiểm tra ngân sách tính toán, tuổi và bộ nhớ đệm trạng thái

Phương thức check_transactions thực hiện một số kiểm tra cho mỗi giao dịch:

Ngân sách tính toán: Các chỉ thị ngân sách tính toán của giao dịch được phân tích và xác thực trước. Chi tiết phí được tính toán từ giới hạn ngân sách và phí ưu tiên. Nếu ngân sách tính toán không hợp lệ hoặc xung đột, giao dịch sẽ thất bại với các lỗi phân tích ngân sách tính toán như DuplicateInstruction, InstructionError(..., InvalidInstructionData), hoặc InvalidLoadedAccountsDataSizeLimit.

Tuổi blockhash: recent_blockhash của giao dịch được tra cứu trong BlockhashQueue. Nếu tìm thấy hash và tuổi của nó nằm trong MAX_PROCESSING_AGE (150 slot), giao dịch sẽ tiếp tục. Nếu không tìm thấy, validator sẽ kiểm tra durable nonce hợp lệ.

Bộ nhớ đệm trạng thái: Hash thông điệp của giao dịch được kiểm tra với bộ nhớ đệm trạng thái. Nếu tìm thấy, giao dịch sẽ bị từ chối với AlreadyProcessed.

5. Xác thực nonce và người trả phí

Phương thức validate_transaction_nonce_and_fee_payer trong SVM xử lý hai xác thực:

Xác thực nonce (nếu có): Đối với các giao dịch nonce, validator tải tài khoản nonce và xác minh:

  • Tài khoản thuộc sở hữu của System Program
  • Nó được phân tích là State::Initialized
  • Durable nonce được lưu trữ khớp với recent_blockhash của giao dịch
  • Nonce có thể được nâng cấp (durable nonce hiện tại khác với durable nonce tiếp theo, tức là nonce chưa được sử dụng trong block hiện tại)
  • Quyền nonce đã ký giao dịch

Nếu hợp lệ, nonce được nâng cấp lên giá trị durable nonce tiếp theo. Xem validate_transaction_nonce.

Xác thực người trả phí: Tài khoản người trả phí (luôn là chỉ số 0) được tải và kiểm tra bởi validate_fee_payer:

  • Tài khoản phải tồn tại (lamports > 0), nếu không sẽ báo AccountNotFound
  • Tài khoản phải là tài khoản hệ thống hoặc tài khoản nonce, nếu không sẽ báo InvalidAccountForFee
  • Lamports phải đủ để chi trả min_balance + total_fee, trong đó min_balance là 0 đối với tài khoản hệ thống hoặc rent.minimum_balance(NonceState::size()) đối với tài khoản nonce; nếu không sẽ báo InsufficientFundsForFee
  • Sau khi khấu trừ phí, tài khoản phải vẫn được miễn phí thuê (không thể chuyển từ miễn phí thuê sang phải trả phí thuê)

Phí được khấu trừ từ người trả phí ở giai đoạn này. Một bản chụp nhanh của người trả phí đã trừ phí (và nonce nâng cao, nếu có) được lưu dưới dạng RollbackAccounts, đây là các tài khoản được cam kết ngay cả khi việc thực thi thất bại.

6. Tải tài khoản

load_transaction tải tất cả các tài khoản được tham chiếu bởi giao dịch. AccountLoader bao bọc kho lưu trữ tài khoản bên ngoài và duy trì bộ nhớ đệm cục bộ theo lô để các tài khoản được sửa đổi bởi các giao dịch trước đó trong cùng một lô có thể hiển thị cho các giao dịch sau đó.

Đối với mỗi tài khoản không phải người trả phí, trình tải:

  1. Lấy tài khoản từ bộ nhớ đệm hoặc accounts-db
  2. Cập nhật trạng thái miễn phí thuê nếu cần
  3. Tích lũy kích thước dữ liệu của tài khoản vào loaded_accounts_data_size_limit (mặc định 64 MiB). Mỗi tài khoản phát sinh chi phí cơ bản là TRANSACTION_ACCOUNT_BASE_SIZE (64 byte) cộng với độ dài dữ liệu của nó

Đối với mỗi chương trình được gọi bởi các lệnh của giao dịch, trình tải xác minh rằng tài khoản chương trình tồn tại và thuộc sở hữu của một trình tải hợp lệ (NativeLoader hoặc một trong các PROGRAM_OWNERS). Các chương trình không hợp lệ sẽ thất bại với ProgramAccountNotFound hoặc InvalidProgramForExecution.

Các chương trình LoaderV3 (có thể nâng cấp) ngầm định tải tài khoản programdata liên quan của chúng, cũng được tính vào giới hạn kích thước dữ liệu đã tải.

Nếu việc tải tài khoản thất bại nhưng người trả phí đã được xác thực thành công, giao dịch sẽ trở thành kết quả FeesOnly: phí vẫn được thu nhưng không có lệnh nào được thực thi.

7. Thực thi lệnh

execute_loaded_transaction tạo một TransactionContext với tất cả các tài khoản đã tải và gọi process_message. Các lệnh được thực thi tuần tự theo thứ tự chúng xuất hiện trong thông điệp. Mỗi lần gọi lệnh tạo một InvokeContext và gọi chương trình mục tiêu.

Chi tiết xử lý lệnh

Hàm process_message của runtime lặp qua từng lệnh và gọi chương trình mục tiêu:

  1. Đối với mỗi instruction, runtime gọi prepare_next_top_level_instruction, hàm này xây dựng InstructionContext. Context này chứa các tham chiếu đến các account của instruction (được phân giải từ các chỉ mục đã biên dịch), instruction data và chỉ mục program account.
  2. Runtime kiểm tra xem program có phải là precompile (Ed25519, Secp256k1, Secp256r1) hay không. Các precompile được xác minh trực tiếp mà không cần gọi BPF VM.
  3. Đối với tất cả các program khác, runtime gọi process_instruction, hàm này tải program từ cache và thực thi nó trong máy ảo BPF.
  4. Sau khi instruction hoàn thành, runtime xác minh rằng tổng số dư lamport trên tất cả các instruction account không thay đổi (kiểm tra UnbalancedInstruction).
  5. Nếu bất kỳ instruction nào thất bại, toàn bộ transaction sẽ được rollback. Không có thay đổi trạng thái trung gian nào được commit.

Mỗi instruction tăng instruction trace. Trace bao gồm cả các instruction cấp cao nhất và bất kỳ CPI nào mà chúng gọi. Tổng độ dài trace (các instruction cấp cao nhất cộng với tất cả các CPI lồng nhau) không được vượt quá 64 (MAX_INSTRUCTION_TRACE_LENGTH). Vượt quá giới hạn này sẽ trả về InstructionError::MaxInstructionTraceLengthExceeded.

Sau khi thực thi, runtime xác minh rằng:

  • Tổng số lamport trên tất cả các account không thay đổi
  • Không có account nào chuyển từ trạng thái miễn phí rent sang trạng thái phải trả rent

8. Commit hoặc rollback

Nếu thực thi thành công, các trạng thái account đã sửa đổi từ TransactionContext được ghi lại vào cache cục bộ theo batch của AccountLoader. Nếu thực thi thất bại, chỉ có RollbackAccounts (fee payer với phí đã khấu trừ và nonce đã tăng) được ghi lại. Phí vẫn được thu, nhưng tất cả các thay đổi account khác đều bị loại bỏ.

Tóm tắt 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)

Tham chiếu lỗi transaction

Bảng sau liệt kê tất cả các biến thể TransactionError và giai đoạn pipeline mà chúng xảy ra:

LỗiGiai đoạnNguyên nhân
AccountInUseLập lịchTài khoản đã bị khóa bởi giao dịch khác trong cùng một lô
AccountLoadedTwiceLập lịchMột pubkey xuất hiện hai lần trong account_keys của giao dịch
AccountNotFoundXác thực người trả phíTài khoản người trả phí không tồn tại
ProgramAccountNotFoundTải tài khoảnChương trình được gọi không tồn tại
InsufficientFundsForFeeXác thực người trả phíNgười trả phí không đủ để chi trả phí + mức tối thiểu miễn rent
InvalidAccountForFeeXác thực người trả phíNgười trả phí không phải là tài khoản hệ thống hoặc tài khoản nonce
AlreadyProcessedBộ nhớ đệm trạng tháiGiao dịch đã được xử lý trước đó
BlockhashNotFoundKiểm tra tuổiBlockhash không có trong hàng đợi và không phải là nonce hợp lệ
InstructionErrorThực thiĐã xảy ra lỗi khi xử lý một lệnh (bao gồm chỉ số lệnh và InstructionError cụ thể)
CallChainTooDeepTải tài khoảnChuỗi gọi loader quá sâu
MissingSignatureForFeeSanitizeGiao dịch yêu cầu phí nhưng không có chữ ký
InvalidAccountIndexSanitizeGiao dịch chứa tham chiếu tài khoản không hợp lệ
SignatureFailureSigverifyChữ ký Ed25519 không xác minh được (gói tin bị loại bỏ)
InvalidProgramForExecutionTải tài khoảnChương trình không thuộc sở hữu của loader hợp lệ
SanitizeFailureSanitizeGiao dịch không sanitize được offset tài khoản một cách chính xác
ClusterMaintenanceLập lịchGiao dịch hiện đang bị vô hiệu hóa do bảo trì cластер
AccountBorrowOutstandingThực thiXử lý giao dịch để lại tài khoản với tham chiếu mượn chưa giải quyết
WouldExceedMaxBlockCostLimitLập lịchGiao dịch sẽ vượt quá giới hạn chi phí khối tối đa
UnsupportedVersionSanitizePhiên bản giao dịch không được hỗ trợ
InvalidWritableAccountTải tài khoảnGiao dịch tải tài khoản có thể ghi nhưng không thể ghi được
WouldExceedMaxAccountCostLimitLập lịchGiao dịch sẽ vượt quá giới hạn chi phí tài khoản tối đa trong khối
WouldExceedAccountDataBlockLimitLập lịchGiao dịch sẽ vượt quá giới hạn dữ liệu tài khoản trong khối
TooManyAccountLocksLập lịchGiao dịch khóa quá nhiều tài khoản
AddressLookupTableNotFoundTải tài khoảnTài khoản bảng tra cứu địa chỉ không tồn tại
InvalidAddressLookupTableOwnerTải tài khoảnBảng tra cứu địa chỉ thuộc sở hữu của chương trình sai
InvalidAddressLookupTableDataTải tài khoảnBảng tra cứu địa chỉ chứa dữ liệu không hợp lệ
InvalidAddressLookupTableIndexTải tài khoảnTra cứu bảng địa chỉ sử dụng chỉ số không hợp lệ
InvalidRentPayingAccountKiểm tra sau thực thiTài khoản chuyển từ miễn rent sang phải trả rent
WouldExceedMaxVoteCostLimitLập lịchGiao dịch sẽ vượt quá giới hạn chi phí bỏ phiếu tối đa
WouldExceedAccountDataTotalLimitLập lịchGiao dịch sẽ vượt quá tổng giới hạn dữ liệu tài khoản
DuplicateInstructionPhân tích ngân sách tính toánBiến thể lệnh ngân sách tính toán trùng lặp trong cùng một giao dịch
InsufficientFundsForRentKiểm tra sau thực thiTài khoản không có đủ lamport để chi trả rent cho kích thước dữ liệu của nó
MaxLoadedAccountsDataSizeExceededTải tài khoảnTổng dữ liệu đã tải vượt quá giới hạn 64 MiB
InvalidLoadedAccountsDataSizeLimitPhân tích ngân sách tính toánSetLoadedAccountsDataSizeLimit được đặt thành 0
ResanitizationNeededSanitizeGiao dịch khác nhau trước/sau khi kích hoạt tính năng và cần sanitize lại
ProgramExecutionTemporarilyRestrictedTải tài khoảnThực thi chương trình tạm thời bị hạn chế trên tài khoản được tham chiếu
UnbalancedTransactionKiểm tra sau thực thiTổng số dư lamport trước giao dịch không bằng số dư sau
ProgramCacheHitMaxLimitTải tài khoảnBộ nhớ đệm chương trình đạt giới hạn tối đa
CommitCancelledCommitCommit bị hủy nội bộ

Is this page helpful?

Quản lý bởi

© 2026 Solana Foundation.
Đã đăng ký bản quyền.
Kết nối