You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();

错误原因分析

核心问题在于:

  1. 跨程序调用系统assign指令时,未传入系统程序账户作为参数,也未声明目标账户的可写权限;
  2. 错误使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 20:23:10