如何检测Solana程序是否通过CPI方式被调用?
Solana程序检测当前调用是否为CPI发起的实现方法
核心逻辑:Solana运行时会维护交易的调用栈,用户直接发起的顶层调用栈高度为1,跨程序调用(CPI)的栈高度大于1,官方SDK已经封装了现成的判断接口,不需要自己解析sysvar或者栈数据。
原生Solana SDK实现
直接调用SDK内置的solana_program::program::is_cpi()函数即可,返回值为true时代表当前是CPI调用,返回false代表是用户直接发起的顶层调用。
如果需要进一步校验发起CPI的程序身份,可以在确认是CPI调用后,调用solana_program::program::get_caller()获取调用方的程序ID,和白名单做比对即可。
示例代码:
use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program, pubkey::Pubkey, msg, }; entrypoint!(process_instruction); pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { let is_cpi = program::is_cpi(); if is_cpi { msg!("当前调用为CPI发起"); // 可选:校验调用方程序身份 let caller_program_id = program::get_caller()?; // 此处添加白名单校验逻辑,比如判断caller_program_id是否属于允许调用的程序 } else { msg!("当前调用为用户直接发起的顶层调用"); } Ok(()) }
Anchor框架实现
Anchor已经对原生判断逻辑做了封装,直接在指令上下文Context上调用is_cpi()方法即可得到结果,不需要额外引入原生SDK的调用。
如果需要获取CPI调用方的程序ID,同样可以调用原生SDK的get_caller()方法。
示例代码:
use anchor_lang::prelude::*; declare_id!("你的程序公钥"); #[program] pub mod demo_program { use super::*; pub fn test_instruction(ctx: Context<TestCtx>) -> Result<()> { if ctx.is_cpi() { msg!("当前为CPI调用"); // 可选:获取调用方程序ID做权限校验 // let caller = solana_program::program::get_caller()?; } else { msg!("当前为用户直接调用"); } Ok(()) } } #[derive(Accounts)] pub struct TestCtx {}
注意事项
- 不要通过「是否有签名者」判断调用类型:CPI调用可以通过PDA签名、传入签名者账户等方式获得签名权限,顶层用户调用也可能构造无有效签名的非法指令,这个判断逻辑不可靠
- 不要自行硬编码栈高数值做判断:后续Solana运行时可能调整栈初始高度,直接使用官方提供的
is_cpi()接口可以保证跨版本兼容 - 涉及权限控制的场景,不要只判断是否为CPI就放通逻辑,必须校验调用方程序ID是否在白名单内,防止恶意程序构造CPI调用越权操作
内容的提问来源于stack exchange,提问作者noooooooob
相关产品推荐
相关产品推荐

