Thực thi CPI và quyền hạn

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ốngCho phép?Điểm thực thiLỗi
Caller truyền tài khoản dưới dạng writable, callee đánh dấu writable----
Caller truyền tài khoản dưới dạng read-only, callee đánh dấu writableKhôngprepare_next_instructionPrivilegeEscalation
Caller truyền tài khoản dưới dạng writable, callee đánh dấu read-only----
Caller truyền tài khoản dưới dạng signer, callee đánh dấu signer----
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 callerprepare_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ừ callerKhôngprepare_next_instructionPrivilegeEscalation
Caller truyền tài khoản dưới dạng signer, callee đánh dấu non-signer----
Chương trình A gọi chính nó trực tiếp (A -> A)push()--
Chương trình A gọi B, B gọi lại A (reentrancy gián tiếp)Khôngpush()ReentrancyNotAllowed
CPI đến native loader, bpf_loader, bpf_loader_deprecated hoặc precompileKhôngcheck_authorized_programProgramNotSupported
Không tìm thấy tài khoản trong transactionKhôngprepare_next_instructionMissingAccount

Các quy tắc đặc quyền có thể được tóm tắt như sau:

  1. Đặ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.
  2. Đặ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.
  3. 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_commontí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ẽ:

  1. Kiểm tra số lượng tập seed người ký so với MAX_SIGNERS (16).
  2. Kiểm tra độ dài của mỗi tập seed so với MAX_SEEDS (16 seed mỗi tập).
  3. Gọi Pubkey::create_program_address vớ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ới BadSeeds.
  4. 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_loaderbpf_loader_deprecated
  • bpf_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 để:

  1. 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).
  2. Tính phí dịch thông tin tài khoản (chỉ SIMD-0339): (num_account_infos * 80) / 250 CU.
  3. 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 CallerAccount bằ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_unit CU.

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, để:

  1. 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.
  2. 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.
  3. Gọi pop() để xóa frame của callee và xác minh rằng số dư lamport không thay đổi (UnbalancedInstruction nế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?

Quản lý bởi

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