Tóm tắt
CPI đi qua 11 bước runtime bao gồm kiểm tra quyền hạn, chuyển đổi tài khoản và đồng bộ dữ liệu. Độ sâu gọi tối đa: 5 (9 với SIMD-0268). Quy tắc quyền hạn ngăn callee leo thang vượt quá những gì caller đã cấp.
Quy tắc quyền hạn
CPI mở rộng quyền hạn tài khoản của caller cho callee với việc thực thi nghiêm
ngặt. Runtime kiểm tra các quy tắc này trong
prepare_next_instruction:
| Tình huống | Cho phép? | Điểm thực thi | Lỗi |
|---|---|---|---|
| Caller truyền tài khoản dưới dạng writable, callee đánh dấu writable | Có | -- | -- |
| Caller truyền tài khoản dưới dạng read-only, callee đánh dấu writable | Không | prepare_next_instruction | PrivilegeEscalation |
| Caller truyền tài khoản dưới dạng writable, callee đánh dấu read-only | Có | -- | -- |
| Caller truyền tài khoản dưới dạng signer, callee đánh dấu signer | Có | -- | -- |
| Caller truyền tài khoản dưới dạng non-signer, callee đánh dấu signer, tài khoản là PDA được tạo từ seeds của caller | Có | prepare_next_instruction | -- |
| Caller truyền tài khoản dưới dạng non-signer, callee đánh dấu signer, tài khoản KHÔNG phải là PDA từ caller | Không | prepare_next_instruction | PrivilegeEscalation |
| Caller truyền tài khoản dưới dạng signer, callee đánh dấu non-signer | Có | -- | -- |
| Chương trình A gọi chính nó trực tiếp (A -> A) | Có | push() | -- |
| Chương trình A gọi B, B gọi lại A (reentrancy gián tiếp) | Không | push() | ReentrancyNotAllowed |
| CPI đến native loader, bpf_loader, bpf_loader_deprecated hoặc precompile | Không | check_authorized_program | ProgramNotSupported |
| Không tìm thấy tài khoản trong transaction | Không | prepare_next_instruction | MissingAccount |
Các quy tắc đặc quyền có thể được tóm tắt như sau:
- Đặc quyền ghi không thể leo thang. Nếu caller đánh dấu một tài khoản là chỉ đọc, callee không thể đánh dấu nó là ghi được.
- Đặc quyền signer yêu cầu ủy quyền. Một tài khoản có thể là signer trong
callee chỉ khi (a) nó đã là signer trong caller, HOẶC (b) nó là PDA được tạo
từ seeds của chương trình gọi thông qua
invoke_signed. - Giảm đặc quyền luôn được phép. Callee có thể sử dụng ít đặc quyền hơn so với những gì caller cấp.
Luồng thực thi CPI
Một CPI đi qua nhiều lớp runtime. Phần này ghi lại toàn bộ pipeline từ lời gọi SDK của chương trình qua ranh giới syscall vào runtime và quay lại. Mỗi bước tham chiếu đến file nguồn triển khai nó.
Chiều cao tối đa của lời gọi instruction chương trình được gọi là
max_instruction_stack_depth
và được đặt thành
MAX_INSTRUCTION_STACK_DEPTH
hằng số là 5. Với MAX_INSTRUCTION_STACK_DEPTH_SIMD_0268 được kích hoạt, giá trị này tăng lên 9.
Chiều cao stack 1 là instruction giao dịch ban đầu. Mỗi CPI tăng chiều cao lên 1. Tối đa 5 có nghĩa là một chương trình có thể thực hiện CPI đến độ sâu 4 cấp (8 cấp với SIMD-0268).
Bước 1: Chương trình gọi invoke hoặc invoke_signed
Chương trình gọi
invoke
hoặc
invoke_signed.
invoke là một wrapper mỏng gọi invoke_signed với một mảng signer
seeds rỗng. Hàm SDK serialize Instruction, slice AccountInfo, và
signer seeds vào bộ nhớ VM, sau đó kích hoạt syscall.
Bước 2: Điểm vào syscall
SBF VM điều phối đến
sol_invoke_signed_rust
syscall handler, gọi vào điểm vào chung:
cpi_common.
Bước 3: Tiêu thụ chi phí gọi hàm
Hành động đầu tiên bên trong cpi_common là
tính phí chi phí gọi hàm cố định
từ bộ đếm tính toán dùng chung:
invoke_units
= 1.000 CU (hoặc 946 CU với SIMD-0339).
Bước 4: Dịch lệnh từ bộ nhớ VM
Trình xử lý syscall dịch lệnh từ không gian địa chỉ VM của chương trình sang các
kiểu Rust phía host thông qua
translate_instruction_rust,
đọc một struct StableInstruction, xác thực độ dài dữ liệu so với
MAX_INSTRUCTION_DATA_LEN
(10.240 byte), sau đó tính phí
chi phí tuần tự hóa dữ liệu.
Bước 5: Dịch seed người ký và tạo PDA
Trình xử lý gọi
translate_signers_rust.
Đối với mỗi tập seed người ký, runtime sẽ:
- Kiểm tra số lượng tập seed người ký so với
MAX_SIGNERS(16). - Kiểm tra độ dài của mỗi tập seed so với
MAX_SEEDS(16 seed mỗi tập). - Gọi
Pubkey::create_program_addressvới các seed và program ID của người gọi. Nếu các seed không tạo ra PDA hợp lệ, CPI sẽ thất bại vớiBadSeeds. - Thu thập các pubkey PDA kết quả vào một vec
signers.
Các PDA được tạo này được coi là người ký hợp lệ cho lệnh callee.
Bước 6: Kiểm tra chương trình được ủy quyền
Trước khi tiếp tục, runtime gọi
check_authorized_program
để xác minh chương trình đích được phép cho CPI. Các chương trình sau bị chặn:
- Native loader
bpf_loadervàbpf_loader_deprecatedbpf_loader_upgradeable(ngoại trừ các lệnh quản lý cụ thể:upgrade,set_authority,set_authority_checked(feature-gated),extend_program_checked(feature-gated),close)- Các chương trình precompile (ed25519, secp256k1, v.v.)
Vi phạm sẽ trả về
ProgramNotSupported.
Bước 7: Xác minh đặc quyền (prepare_next_instruction)
Runtime gọi
prepare_next_instruction
để xây dựng danh sách InstructionAccount của callee và thực thi các quy
tắc đặc quyền. Xem Quy tắc đặc quyền bên dưới để biết bảng
quyết định đầy đủ.
Bước 8: Dịch thông tin tài khoản
Handler gọi translate_accounts để:
- Xác thực số lượng thông tin tài khoản
so với
MAX_CPI_ACCOUNT_INFOS(128, hoặc 255 với SIMD-0339). - Tính phí dịch thông tin tài khoản
(chỉ SIMD-0339):
(num_account_infos * 80) / 250CU. - Với mỗi tài khoản không thể thực thi và không trùng lặp, xây dựng một
CallerAccountbằng cách dịch con trỏ từ bộ nhớ VM sang bộ nhớ host. Điều này bao gồm tính phí tuần tự hóa dữ liệu cho mỗi tài khoản:account_data_len / cpi_bytes_per_unitCU.
Bước 9: Đồng bộ tài khoản trước CPI (từ caller đến callee)
Trước khi thực thi callee, runtime đồng bộ các thay đổi tài khoản của caller để
callee có thể thấy chúng. Hàm
update_callee_account
được gọi cho mỗi tài khoản đã dịch, sao chép lamport, dữ liệu và owner. Xem
Đồng bộ dữ liệu tài khoản
để biết chi tiết ánh xạ trường.
Bước 10: Đẩy ngữ cảnh lệnh, thực thi callee và pop
Runtime gọi
process_instruction,
để:
- Gọi
push()để thêm một frame mới vào ngăn xếp lệnh.push()thực thi quy tắc reentrancy: một chương trình chỉ có thể gọi chính nó nếu nó là caller trực tiếp (tức là chương trình A có thể gọi A, nhưng A không thể gọi B mà B lại gọi A). Vi phạm sẽ trả vềReentrancyNotAllowed. - Gọi
process_executable_chainđể phân giải điểm vào của chương trình callee và gọi nó. Callee chạy với cùng bộ đếm compute được chia sẻ. Mọi tiêu thụ CU của callee đều giảm ngân sách còn lại của caller. - Gọi
pop()để xóa frame của callee và xác minh rằng số dư lamport không thay đổi (UnbalancedInstructionnếu không).
Bước 11: Đồng bộ tài khoản sau CPI (từ callee đến caller)
Sau khi process_instruction trả về (bao gồm cả pop), runtime đồng bộ các thay
đổi trở lại caller thông qua
update_caller_account
cho mỗi tài khoản có thể ghi. Ngoài ra,
update_caller_account_region
cập nhật ánh xạ vùng bộ nhớ VM cho các tài khoản có vùng dữ liệu thay đổi. Xem
Đồng bộ dữ liệu tài khoản
để biết chi tiết ánh xạ trường.
Syscall CPI trả về 0 (thành công) cho chương trình gọi.
Is this page helpful?