Solana账户所有者重分配报错:跨程序调用权限问题排查
Solana账户Owner重分配错误修复方案
问题背景
作为Solana新手,尝试重分配账户Owner的步骤如下:
- 部署自定义程序;
- 创建Owner为已部署程序ID的账户;
- 调用程序修改账户Owner为系统程序。
但第三步执行时出现错误:
SendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
认为跨程序调用无需额外签名,签名方应为自定义程序,需要修改代码修复该问题。
原Rust程序代码
use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, msg, program::{invoke, invoke_signed}, program_error::ProgramError, program_pack::{IsInitialized, Pack}, pubkey::Pubkey, sysvar::{rent::Rent, Sysvar}, entrypoint }; use std::str::FromStr; use solana_program::system_instruction::SystemInstruction; use spl_token::state::Account as TokenAccount; use crate::{error::EscrowError, instruction::EscrowInstruction, state::Escrow}; use crate::processor::Processor; entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { let iter = &mut accounts.iter(); let account = next_account_info(iter)?; if account.owner != program_id { return Err(ProgramError::IncorrectProgramId); } let str = "11111111111111111111111111111111"; let system_program: Pubkey = Pubkey::from_str(str).unwrap(); let instr = solana_program::system_instruction::assign(account.key, &system_program); invoke_signed( &instr, &[account.clone()], &[&[&program_id.as_ref()]] )?; Ok(()) }
原客户端JS代码
import { Connection, CreateAccountParams, Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction, TransactionInstruction, } from "@solana/web3.js"; //@ts-expect-error missing types import * as BufferLayout from "buffer-layout"; import * as fs from "fs"; import {getPrivateKey} from "./utils"; const key = Keypair.fromSecretKey(getPrivateKey("my_key")); const reassignFunc = async () => { const connection = new Connection("http://127.0.0.1:8899", "confirmed"); const to = Keypair.generate(); console.log(to.publicKey.toString()); const createInstr = SystemProgram.createAccount({ fromPubkey: key.publicKey, newAccountPubkey: to.publicKey, lamports: 4_000_000, space: 0, programId: SystemProgram.programId, }); let transaction = new Transaction().add(createInstr); let res = await sendAndConfirmTransaction(connection, transaction, [key, to]); console.log(res); const assignInstr = SystemProgram.assign({ accountPubkey: to.publicKey, programId: new PublicKey("FhNwgZtYLE87ugXmUEthdYEGs8KbYoLUhnYpy74gEr8s"), }); transaction = new Transaction().add(assignInstr); res = await sendAndConfirmTransaction(connection, transaction, [to]); console.log(res); const assignInstr2 = new TransactionInstruction({ programId: new PublicKey("FhNwgZtYLE87ugXmUEthdYEGs8KbYoLUhnYpy74gEr8s"), keys: [{pubkey: to.publicKey, isSigner: false, isWritable: true}] }); transaction = new Transaction().add(assignInstr2); res = await sendAndConfirmTransaction(connection, transaction, [key]); console.log(res); }; reassignFunc();
错误原因分析
核心问题在于:
- 跨程序调用系统
assign指令时,未传入系统程序账户作为参数,也未声明目标账户的可写权限; - 错误使用
invoke_signed,当前场景下程序已拥有目标账户的Owner权限,无需额外签名。
修复后代码
修改后的Rust程序代码
use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, program::invoke, program_error::ProgramError, pubkey::Pubkey, entrypoint }; use std::str::FromStr; entrypoint!(process_instruction); fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8], ) -> ProgramResult { let iter = &mut accounts.iter(); // 目标账户:当前Owner为本程序,需修改其Owner为系统程序 let target_account = next_account_info(iter)?; // 系统程序账户:跨程序调用必须传入 let system_program = next_account_info(iter)?; // 验证目标账户当前归属正确 if target_account.owner != program_id { return Err(ProgramError::IncorrectProgramId); } // 验证系统程序账户合法性 let system_program_id = Pubkey::from_str("11111111111111111111111111111111").unwrap(); if system_program.key != &system_program_id || !system_program.is_executable { return Err(ProgramError::IncorrectProgramId); } // 构建assign指令 let assign_instr = solana_program::system_instruction::assign( target_account.key, &system_program_id, ); // 跨程序调用:传入目标账户(可写)和系统程序账户 invoke( &assign_instr, &[target_account.clone(), system_program.clone()], )?; Ok(()) }
Rust代码修改点
- 新增系统程序账户参数:跨程序调用系统指令必须传入系统程序账户,并验证其合法性;
- 替换
invoke_signed为invoke:目标账户Owner已为本程序,程序拥有修改权限,无需额外签名; - 移除无关依赖:删除了spl_token、Escrow等无关代码,聚焦核心逻辑。
修改后的客户端JS代码
import { Connection, Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction, TransactionInstruction, } from "@solana/web3.js"; import {getPrivateKey} from "./utils"; const key = Keypair.fromSecretKey(getPrivateKey("my_key")); // 替换为你部署的自定义程序ID const MY_PROGRAM_ID = new PublicKey("FhNwgZtYLE87ugXmUEthdYEGs8KbYoLUhnYpy74gEr8s"); const reassignFunc = async () => { const connection = new Connection("http://127.0.0.1:8899", "confirmed"); const targetAccount = Keypair.generate(); console.log("目标账户地址:", targetAccount.publicKey.toString()); // 步骤1:创建初始Owner为系统程序的账户 const createInstr = SystemProgram.createAccount({ fromPubkey: key.publicKey, newAccountPubkey: targetAccount.publicKey, lamports: await connection.getMinimumBalanceForRentExemption(0), space: 0, programId: SystemProgram.programId, }); let transaction = new Transaction().add(createInstr); let res = await sendAndConfirmTransaction(connection, transaction, [key, targetAccount]); console.log("创建账户交易哈希:", res); // 步骤2:将账户Owner改为自定义程序 const assignToProgramInstr = SystemProgram.assign({ accountPubkey: targetAccount.publicKey, programId: MY_PROGRAM_ID, }); transaction = new Transaction().add(assignToProgramInstr); res = await sendAndConfirmTransaction(connection, transaction, [targetAccount]); console.log("分配Owner到程序交易哈希:", res); // 步骤3:调用自定义程序,将账户Owner改回系统程序 const reassignInstr = new TransactionInstruction({ programId: MY_PROGRAM_ID, keys: [ {pubkey: targetAccount.publicKey, isSigner: false, isWritable: true}, {pubkey: SystemProgram.programId, isSigner: false, isWritable: false} // 必须传入系统程序账户 ] }); transaction = new Transaction().add(reassignInstr); res = await sendAndConfirmTransaction(connection, transaction, [key]); console.log("重分配Owner交易哈希:", res); }; reassignFunc();
JS代码修改点
- 添加系统程序账户参数:调用自定义程序时,必须传入系统程序账户供内部跨程序调用使用;
- 动态计算最低租金:用
getMinimumBalanceForRentExemption替代硬编码的lamports,符合Solana规范; - 变量命名优化:将
to改为targetAccount,提升代码可读性。
内容的提问来源于stack exchange,提问作者Nikita Duginets
相关产品推荐
相关产品推荐

