Thực Thi Chương Trình

Tóm Tắt

Các chương trình được biên dịch sang sBPF thông qua LLVM và chạy trong một VM cách ly với ngân sách 1,4M CU mỗi giao dịch. Runtime lưu vào bộ nhớ đệm tối đa 512 chương trình đã biên dịch, cung cấp syscall cho việc ghi log, CPI, mã hóa và bộ nhớ, đồng thời trì hoãn các lần triển khai mới 1 slot.

Biên Dịch

Solana sử dụng LLVM để biên dịch các chương trình thành ELF binary chứa Solana Bytecode Format (sBPF). ELF binary được lưu trữ onchain trong một tài khoản thực thi được.

sBPF là biến thể tùy chỉnh của Solana dựa trên bytecode eBPF, được điều chỉnh cho Solana runtime. Đây không phải eBPF tiêu chuẩn và có các sửa đổi đặc thù của Solana.

Viết chương trình

Các chương trình Solana chủ yếu được viết bằng Rust theo một trong ba cách tiếp cận:

Xem Cross-Program Invocations để biết các khái niệm và ví dụ về CPI áp dụng cho cả ba cách tiếp cận này.

Mô hình thực thi chương trình

Khi một giao dịch được xử lý, runtime thực thi từng lệnh tuần tự thông qua process_message(). Với mỗi lệnh, runtime:

  1. Chuẩn bị ngữ cảnh lệnh. Gọi prepare_next_top_level_instruction() để ánh xạ các chỉ số tài khoản của lệnh, thiết lập cờ ký và có thể ghi, và cấu hình TransactionContext.

  2. Kiểm tra precompile. Nếu chương trình là một precompile, runtime sẽ gọi process_precompile(), vẫn đẩy và bật một stack frame (thông qua push() và pop()) nhưng bỏ qua sBPF VM và tra cứu bộ nhớ đệm chương trình, thực thi trực tiếp native code.

  3. Đẩy một stack frame. (Các bước 3-6 xảy ra bên trong InvokeContext::process_instruction() và process_executable_chain(), được gọi từ process_message().) Gọi push() trên InvokeContext, tăng chiều cao stack lệnh và thực thi quy tắc reentrancy: một chương trình chỉ có thể tự re-enter nếu caller trực tiếp (chương trình ở đầu stack lệnh hiện tại) là cùng một chương trình. Đệ quy sâu (A -> A -> A) được cho phép, tùy thuộc vào giới hạn độ sâu stack. Các mẫu reentrancy khác (ví dụ: A gọi B gọi A) trả về InstructionError::ReentrancyNotAllowed.

  4. Phân giải chương trình. Runtime gọi process_executable_chain() để xác định loader. Nếu chủ sở hữu của program account là native loader, chương trình là builtin và hàm entrypoint của nó được tra cứu trực tiếp từ ProgramCacheForTxBatch. Nếu chủ sở hữu là một trong các BPF loader (bpf_loader_deprecated, bpf_loader, bpf_loader_upgradeable, hoặc loader_v4), entrypoint builtin của chính loader đó sẽ được gọi thay thế.

  5. Thực thi chương trình BPF. Đối với các chương trình BPF, loader entrypoint tra cứu executable đã biên dịch từ bộ nhớ đệm chương trình. Hàm execute() sau đó:

    • Serialize dữ liệu tài khoản vào một parameter buffer phẳng
    • Tạo sBPF VM với stack, heap và các vùng bộ nhớ
    • Chạy code đã biên dịch, tiêu thụ compute unit trong quá trình thực thi. Trả về ComputationalBudgetExceeded nếu ngân sách bị vượt quá.
    • Deserialize dữ liệu tài khoản từ buffer trở lại thành trạng thái tài khoản
  6. Bật stack frame. Gọi pop() để xác minh rằng lệnh không vi phạm các quy tắc kế toán của runtime (số dư lamport cân bằng, các tài khoản chỉ đọc không bị sửa đổi, kích thước dữ liệu tài khoản nằm trong giới hạn).

  7. Tích lũy compute unit. Các compute unit được tiêu thụ bởi lệnh được cộng vào tổng giao dịch thông qua saturating_add.

Bộ nhớ đệm chương trình

Runtime duy trì một ProgramCache toàn cục lưu trữ các chương trình đã được xác minh và biên dịch. Nó nhận biết fork-graph và xử lý các quy tắc hiển thị triển khai, eviction và recompilation tại ranh giới epoch.

Các loại cache entry

Mỗi chương trình được lưu trong bộ nhớ đệm đều có một ProgramCacheEntryType xác định hành vi runtime của nó:

LoạiMô tả
LoadedChương trình đã được xác minh và biên dịch, sẵn sàng để thực thi.
BuiltinChương trình native được biên dịch vào binary của validator (System, Stake, Vote, v.v.). Không được lưu trữ onchain.
UnloadedChương trình đã được xác minh trước đó mà executable đã biên dịch bị evict khỏi bộ nhớ để giải phóng không gian. Vẫn theo dõi thống kê sử dụng. Có thể được tải lại mà không cần xác minh lại.
FailedVerificationTombstone cho các chương trình không vượt qua sBPF verifier trong feature set hiện tại. Có thể trở thành Loaded nếu việc kích hoạt tính năng thay đổi các quy tắc xác minh.
ClosedTombstone cho các chương trình đã bị đóng rõ ràng hoặc chưa bao giờ được triển khai. Cũng được sử dụng cho các tài khoản (như buffer account) thuộc về một loader nhưng không chứa executable code.
DelayVisibilityTombstone tổng hợp được trả về bởi ProgramCacheForTxBatch::find() khi một entry Loaded tồn tại nhưng chưa có hiệu lực (tức là effective_slot của nó nằm trong tương lai). Không bao giờ được lưu trực tiếp trong bộ nhớ đệm.

Độ trễ hiển thị

Các chương trình mới được triển khai hoặc nâng cấp không có hiệu lực ngay lập tức. Hằng số DELAY_VISIBILITY_SLOT_OFFSET là 1, nghĩa là một chương trình được triển khai trong slot N sẽ có hiệu lực trong slot N+1. Trong slot triển khai, bất kỳ nỗ lực nào gọi phiên bản mới sẽ trả về DelayVisibility, khiến runtime báo cáo "Program is not deployed."

Chính sách eviction

Bộ nhớ đệm chứa tối đa MAX_LOADED_ENTRY_COUNT (512) entry chương trình đã biên dịch. Khi đạt giới hạn, các chương trình ít được sử dụng nhất sẽ bị evict sang trạng thái Unloaded. Mức sử dụng được theo dõi bởi tx_usage_counter (tăng mỗi lần một giao dịch tham chiếu đến chương trình) và latest_access_slot.

Recompilation tại ranh giới epoch

Nếu việc kích hoạt tính năng thay đổi ProgramRuntimeEnvironments tại ranh giới epoch, tất cả các chương trình được lưu trong bộ nhớ đệm sẽ được biên dịch lại theo môi trường mới.

Dữ liệu trả về

Các chương trình có thể thiết lập dữ liệu trả về thông qua syscall sol_set_return_data. Dữ liệu được lưu trữ trong một struct TransactionReturnData ở cấp độ giao dịch, chứa các byte dữ liệu và program_id của chương trình mà lệnh của nó đã gọi syscall. Kích thước tối đa là 1.024 byte (MAX_RETURN_DATA).

Is this page helpful?

Mục lục

Chỉnh sửa trang