Solana Anchor PDA托管SOL功能开发及unknown signer报错排查
问题排查与修复方案
报错直接原因
unknown signer报错是测试代码调用RPC接口时,未传入账户结构要求的必填签名者账户导致的,同时Rust程序逻辑和测试代码都存在多处不合理的地方,具体如下:
一、Rust程序存在的问题
- AddGif账户结构体的
#[instruction]宏参数和add_gif函数入参顺序、数量完全不匹配,会导致Anchor推导PDA时参数校验失败 - 硬编码PDA的bump值为5,不符合PDA生成规则,PDA的bump必须由链上计算得出,不能硬编码
add_result、place_bet两个函数错误复用了AddGif账户结构,AddGif中的pool_wallet标记为init模式,只有首次创建池的时候需要初始化,后续调用这两个函数时会重复尝试初始化已存在的账户,直接报错
Rust代码修复示例
- 修正AddGif账户结构
#[derive(Accounts)] // 入参顺序和add_gif函数保持完全一致 #[instruction(image_link: String, pool_name: String, pool_desc: String, win_opt:String, close: u32, verify:String, fee:u8)] pub struct AddGif<'info> { #[account(mut)] pub base_account: Account<'info, BaseAccount>, // 去掉硬编码的bump=5,用bump关键字让Anchor自动计算 #[account(init, seeds=[pool_name.as_bytes(),b"pool_wallet"], space=9000, bump, payer=user)] pub pool_wallet: Account<'info, PoolWallet>, #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System> }
- 新增独立的账户结构给add_result和place_bet使用
#[derive(Accounts)] pub struct AddResult<'info> { #[account(mut)] pub base_account: Account<'info, BaseAccount>, // 要求调用者签名,后续可以加校验判断是否为池所有者 pub user: Signer<'info>, } #[derive(Accounts)] pub struct PlaceBet<'info> { #[account(mut)] pub base_account: Account<'info, BaseAccount>, /// CHECK: 后续可增加校验逻辑确保是对应池的托管地址 #[account(mut)] pub pool_wallet: UncheckedAccount<'info>, #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System>, }
- 修改对应函数的上下文参数
pub fn add_result(ctx: Context<AddResult>,result:String, pool_id:u32 ) -> ProgramResult { // 原有逻辑不变 } pub fn place_bet(ctx: Context<PlaceBet>, pred: String, pool_id:u32, stake_bal:u32, user:String) -> ProgramResult { // 原有逻辑不变 }
二、JS测试代码存在的问题
- 错误生成普通密钥对作为PDA托管地址:PDA是程序派生地址,没有对应私钥,不能用
Keypair.generate()生成,需要用findProgramAddress推导 - 后续RPC调用缺少必填账户:第二次调用addGif、调用addResult、placeBet时,只传了baseAccount,没有传入要求的user、pool_wallet、system_program等必填账户,直接触发签名者缺失错误
JS测试代码修复示例
- 调用addGif前先推导对应池的PDA地址
// 第一个池的PDA推导,seed和Rust端保持一致:poolName的字节 + b"pool_wallet" const [poolWalletPDA1] = await anchor.web3.PublicKey.findProgramAddress( [Buffer.from("Test Pool"), Buffer.from("pool_wallet")], program.programId ); // 调用addGif时传入推导得到的PDA,不需要添加到签名者列表 await program.rpc.addGif("insert image link here", "Test Pool","This is a test Pool", "option 1; option 2", 9897, "Verify here", 5,{ accounts: { baseAccount: baseAccount.publicKey, poolWallet: poolWalletPDA1, user: provider.wallet.publicKey, systemProgram: SystemProgram.programId }, signers: [baseAccount], }); // 第二个池同理推导对应PDA再调用 const [poolWalletPDA2] = await anchor.web3.PublicKey.findProgramAddress( [Buffer.from("Test Pool 2"), Buffer.from("pool_wallet")], program.programId ); await program.rpc.addGif("insert image link here 3", "Test Pool 2","This is a test Pool 2", "2option 1; 2option 2", 789897, "2Verify here", 2,{ accounts: { baseAccount: baseAccount.publicKey, poolWallet: poolWalletPDA2, user: provider.wallet.publicKey, systemProgram: SystemProgram.programId }, signers: [baseAccount], });
- 补全addResult和placeBet的必填账户
await program.rpc.addResult("winner 1",0,{ accounts: { baseAccount: baseAccount.publicKey, user: provider.wallet.publicKey } }); // placeBet需要传入对应池的PDA地址 await program.rpc.placeBet("winner 1",1,125,"user", { accounts: { baseAccount: baseAccount.publicKey, poolWallet: poolWalletPDA2, // 对应pool_id=1的池的PDA user: provider.wallet.publicKey, systemProgram: SystemProgram.programId } })
内容的提问来源于stack exchange,提问作者Joby Ingram-Dodd
相关产品推荐
相关产品推荐

