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:
Anchor
Một framework sử dụng Rust macro để giảm boilerplate. Được khuyến nghị cho hầu hết các nhà phát triển.
Pinocchio
Một thư viện Rust nhẹ, zero-copy được tối ưu hóa cho mức tiêu thụ compute và kích thước binary. Bao gồm các crate dành riêng cho chương trình cho các CPI phổ biến.
Native Rust
Rust thuần túy không dùng framework. Cung cấp toàn quyền kiểm soát nhưng yêu cầu triển khai thủ công nhiều hơ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:
-
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ìnhTransactionContext. -
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 quapush()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. -
Đẩ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ọipush()trênInvokeContext, 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. -
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ặcloader_v4), entrypoint builtin của chính loader đó sẽ được gọi thay thế. -
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ề
ComputationalBudgetExceedednế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
-
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). -
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ại | Mô tả |
|---|---|
Loaded | Chương trình đã được xác minh và biên dịch, sẵn sàng để thực thi. |
Builtin | Chươ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. |
Unloaded | Chươ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. |
FailedVerification | Tombstone 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. |
Closed | Tombstone 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. |
DelayVisibility | Tombstone 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?