Cross Program Invocation
Cross Program Invocation (CPI) xảy ra khi một chương trình Solana trực tiếp gọi các lệnh của một chương trình khác. Điều này cho phép khả năng kết hợp giữa các chương trình. Nếu bạn coi một lệnh Solana như một điểm cuối API mà một chương trình cung cấp cho mạng lưới, thì CPI giống như một điểm cuối nội bộ gọi một điểm cuối khác.
Khi thực hiện CPI, một chương trình có thể ký thay mặt cho một PDA được tạo ra từ ID chương trình của nó. Các đặc quyền ký này được mở rộng từ chương trình gọi đến chương trình được gọi.
Ví dụ về Cross-program invocation
Khi thực hiện CPI, các đặc quyền tài khoản được mở rộng từ chương trình này sang chương trình khác. Giả sử Chương trình A nhận được một lệnh với một tài khoản người ký và một tài khoản có thể ghi. Sau đó Chương trình A thực hiện CPI đến Chương trình B. Bây giờ, Chương trình B có thể sử dụng các tài khoản giống như Chương trình A, với các quyền ban đầu của chúng. (Nghĩa là Chương trình B có thể ký với tài khoản người ký và có thể ghi vào tài khoản có thể ghi.) Nếu Chương trình B thực hiện CPI riêng của nó, nó có thể chuyển tiếp các quyền này, lên đến độ sâu là 4.
Chiều cao tối đa của việc gọi lệnh chương trình được gọi là
max_instruction_stack_depth
và được đặt thành hằng số MAX_INSTRUCTION_STACK_DEPTH
là 5.
Chiều cao ngăn xếp bắt đầu từ 1 cho giao dịch ban đầu và tăng thêm 1 mỗi khi một chương trình gọi một lệnh khác, giới hạn độ sâu gọi cho CPI là 4.
CPI với người ký PDA
Khi một CPI yêu cầu một PDA signer, hàm
invoke_signed
được sử dụng. Nó nhận signer seeds được sử dụng để tạo
ra PDAs signer. Runtime của Solana nội bộ gọi
create_program_address
sử dụng signers_seeds và program_id của chương trình gọi. Khi một PDA được
xác minh, nó sẽ được
thêm vào như một signer hợp lệ.
pub fn invoke_signed(instruction: &Instruction,account_infos: &[AccountInfo],signers_seeds: &[&[&[u8]]],) -> ProgramResult {// --snip--invoke_signed_unchecked(instruction, account_infos, signers_seeds)}
Các ví dụ dưới đây thực hiện CPI với PDA signers sử dụng Anchor và Native Rust. Mỗi ví dụ bao gồm một lệnh duy nhất để chuyển SOL từ một PDA đến một tài khoản người nhận, sử dụng CPI được ký bởi PDA.
Anchor
Các ví dụ sau đây cho thấy ba cách tiếp cận để triển khai CPI trong một chương trình Anchor. Các ví dụ này tương đương về chức năng, nhưng mỗi ví dụ minh họa một mức độ trừu tượng hóa khác nhau.
- Ví dụ 1: Sử dụng 
CpiContextcủa Anchor và hàm trợ giúp. - Ví dụ 2: Sử dụng hàm 
system_instruction::transfertừ cratesolana_program. (Ví dụ 1 là một trừu tượng hóa của cách triển khai này.) - Ví dụ 3: Xây dựng lệnh CPI thủ công. Cách tiếp cận này hữu ích khi không có crate nào có sẵn để giúp xây dựng lệnh mà bạn muốn gọi.
 
use anchor_lang::prelude::*;use anchor_lang::system_program::{transfer, Transfer};declare_id!("BrcdB9sV7z9DvF9rDHG263HUxXgJM3iCQdF36TcxbFEn");#[program]pub mod cpi {use super::*;pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {let from_pubkey = ctx.accounts.pda_account.to_account_info();let to_pubkey = ctx.accounts.recipient.to_account_info();let program_id = ctx.accounts.system_program.to_account_info();let seed = to_pubkey.key();let bump_seed = ctx.bumps.pda_account;let signer_seeds: &[&[&[u8]]] = &[&[b"pda", seed.as_ref(), &[bump_seed]]];let cpi_context = CpiContext::new(program_id,Transfer {from: from_pubkey,to: to_pubkey,},).with_signer(signer_seeds);transfer(cpi_context, amount)?;Ok(())}}#[derive(Accounts)]pub struct SolTransfer<'info> {#[account(mut,seeds = [b"pda", recipient.key().as_ref()],bump,)]pda_account: SystemAccount<'info>,#[account(mut)]recipient: SystemAccount<'info>,system_program: Program<'info, System>,}
Rust
Ví dụ dưới đây thực hiện CPI với PDA signers từ một chương trình được viết bằng Native Rust. Nó bao gồm một lệnh duy nhất chuyển SOL từ tài khoản PDA đến tài khoản khác. CPI được ký bởi tài khoản PDA. (Tệp kiểm thử sử dụng LiteSVM để kiểm tra chương trình.)
use borsh::BorshDeserialize;use solana_program::{account_info::AccountInfo,entrypoint,entrypoint::ProgramResult,program::invoke_signed,program_error::ProgramError,pubkey::Pubkey,system_instruction,};// Declare program entrypointentrypoint!(process_instruction);// Define program instructions#[derive(BorshDeserialize)]enum ProgramInstruction {SolTransfer { amount: u64 },}impl ProgramInstruction {fn unpack(input: &[u8]) -> Result<Self, ProgramError> {Self::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)}}pub fn process_instruction(program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {// Deserialize instruction datalet instruction = ProgramInstruction::unpack(instruction_data)?;// Process instructionmatch instruction {ProgramInstruction::SolTransfer { amount } => {// Parse accountslet [pda_account_info, recipient_info, system_program_info] = accounts else {return Err(ProgramError::NotEnoughAccountKeys);};// Derive PDA and verify it matches the account provided by clientlet recipient_pubkey = recipient_info.key;let seeds = &[b"pda", recipient_pubkey.as_ref()];let (expected_pda, bump_seed) = Pubkey::find_program_address(seeds, program_id);if expected_pda != *pda_account_info.key {return Err(ProgramError::InvalidArgument);}// Create the transfer instructionlet transfer_ix = system_instruction::transfer(pda_account_info.key,recipient_info.key,amount,);// Create signer seeds for PDAlet signer_seeds: &[&[&[u8]]] = &[&[b"pda", recipient_pubkey.as_ref(), &[bump_seed]]];// Invoke the transfer instruction with PDA as signerinvoke_signed(&transfer_ix,&[pda_account_info.clone(),recipient_info.clone(),system_program_info.clone(),],signer_seeds,)?;Ok(())}}}
CPIs không có PDA signers
Khi một CPI không yêu cầu PDA signers, hàm
invoke
được sử dụng. Hàm invoke gọi hàm invoke_signed với mảng signers_seeds
rỗng. Mảng signers rỗng chỉ ra rằng không có PDA nào được yêu cầu để ký.
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {invoke_signed(instruction, account_infos, &[])}
Các ví dụ dưới đây thực hiện CPI sử dụng Anchor và Native Rust. Mỗi ví dụ bao gồm một lệnh duy nhất chuyển SOL từ tài khoản này sang tài khoản khác.
Anchor
Các ví dụ sau đây trình bày ba cách tiếp cận để triển khai CPI trong một chương trình Anchor. Các ví dụ có chức năng tương đương nhau, nhưng mỗi ví dụ thể hiện một mức độ trừu tượng khác nhau.
- Ví dụ 1: Sử dụng 
CpiContextcủa Anchor và hàm trợ giúp. - Ví dụ 2: Sử dụng hàm 
system_instruction::transfertừ cratesolana_program. - Ví dụ 3: Xây dựng lệnh CPI thủ công. Cách tiếp cận này hữu ích khi không có crate nào tồn tại để giúp xây dựng lệnh bạn muốn gọi.
 
use anchor_lang::prelude::*;use anchor_lang::system_program::{transfer, Transfer};declare_id!("9AvUNHjxscdkiKQ8tUn12QCMXtcnbR9BVGq3ULNzFMRi");#[program]pub mod cpi {use super::*;pub fn sol_transfer(ctx: Context<SolTransfer>, amount: u64) -> Result<()> {let from_pubkey = ctx.accounts.sender.to_account_info();let to_pubkey = ctx.accounts.recipient.to_account_info();let program_id = ctx.accounts.system_program.to_account_info();let cpi_context = CpiContext::new(program_id,Transfer {from: from_pubkey,to: to_pubkey,},);transfer(cpi_context, amount)?;Ok(())}}#[derive(Accounts)]pub struct SolTransfer<'info> {#[account(mut)]sender: Signer<'info>,#[account(mut)]recipient: SystemAccount<'info>,system_program: Program<'info, System>,}
Rust
Ví dụ sau đây cho thấy cách thực hiện CPI từ một chương trình được viết bằng Native Rust. Nó bao gồm một lệnh duy nhất chuyển SOL từ tài khoản này sang tài khoản khác. (File kiểm thử sử dụng LiteSVM để kiểm tra chương trình.)
use borsh::BorshDeserialize;use solana_program::{account_info::AccountInfo,entrypoint,entrypoint::ProgramResult,program::invoke,program_error::ProgramError,pubkey::Pubkey,system_instruction,};// Declare program entrypointentrypoint!(process_instruction);// Define program instructions#[derive(BorshDeserialize)]enum ProgramInstruction {SolTransfer { amount: u64 },}impl ProgramInstruction {fn unpack(input: &[u8]) -> Result<Self, ProgramError> {Self::try_from_slice(input).map_err(|_| ProgramError::InvalidInstructionData)}}pub fn process_instruction(_program_id: &Pubkey,accounts: &[AccountInfo],instruction_data: &[u8],) -> ProgramResult {// Deserialize instruction datalet instruction = ProgramInstruction::unpack(instruction_data)?;// Process instructionmatch instruction {ProgramInstruction::SolTransfer { amount } => {// Parse accountslet [sender_info, recipient_info, system_program_info] = accounts else {return Err(ProgramError::NotEnoughAccountKeys);};// Verify the sender is a signerif !sender_info.is_signer {return Err(ProgramError::MissingRequiredSignature);}// Create and invoke the transfer instructionlet transfer_ix = system_instruction::transfer(sender_info.key,recipient_info.key,amount,);invoke(&transfer_ix,&[sender_info.clone(),recipient_info.clone(),system_program_info.clone(),],)?;Ok(())}}}
Is this page helpful?