Solana 60 天课程

2025年02月27日更新 92 人订阅
原价: ¥ 66 限时优惠
专栏简介

原生 Solana:创建存储账户 二

  • RareSkills
  • 发布于 2026-02-28 09:22
  • 阅读 797

这篇文章详细介绍了如何在 Solana 链上使用程序派生地址(PDA)创建存储账户。它对比了 PDA 与传统密钥对账户的区别,并深入讲解了 invoke_signed() 机制、种子 (seed) 与碰撞字节 (bump_seed) 的作用,提供了完整的 Rust 链上程序和 TypeScript 客户端代码示例。

原生Solana:创建用于存储的账户

在本教程的第一部分中,我们使用密钥对在原生 Rust 中创建了存储账户,其中账户需要一个私钥来为其初始化进行签名。现在我们将探索一种不同的方法,使用程序派生地址 (PDA),它们没有私钥,但仍然可以通过特殊的签名机制用作存储账户。

使用 PDA 创建存储账户

在深入代码之前,让我们了解一下 PDA 账户创建与基于密钥对的账户有何不同:

密钥对账户:

  • 拥有可以签署交易的私钥
  • 密钥对必须为其自身的初始化进行签名
  • 在创建账户时要求 isSigner: true

PDA 账户:

  • 从种子和程序 ID 确定性派生
  • 没有私钥,因此它们不能直接签署交易或指令
  • 我们的程序使用 invoke_signed() 代表 PDA 作为签名者
  • 需要用于派生地址的种子作为所有权证明

这种根本的区别意味着在创建 PDA 账户时,我们将使用 invoke_signed() 而不是 invoke(),因为系统程序需要签名才能初始化任何账户。

构建 PDA 存储程序

用此版本替换 src/lib.rs 中的代码(来自第一部分)。在下面的代码中,我们:

  1. 导入用于 PDA 创建的额外依赖 (invoke_signed, Rent, Sysvar)
  2. 获取所需的账户(存储账户、签名者、系统程序、租金)
  3. 验证我们收到了正确的系统程序,并且签名者账户有效
  4. 创建值为 100 的 CounterData 并使用 Borsh 进行序列化
  5. 使用 invoke_signed 通过种子和 bump 派生 PDA 地址来创建 PDA 存储账户
  6. 将序列化数据直接写入账户
Copyuse borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    msg,
    program::invoke_signed,
    program_error::ProgramError,
    pubkey::Pubkey,
    system_instruction, system_program,
    sysvar::{rent::Rent, Sysvar},
};

entrypoint!(process_instruction);

// 这表示我们将存储在账户中的数据
// 我们添加了 Borsh derive 宏用于序列化和反序列化
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CounterData {
    pub count: u64,
}

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    msg!("存储写入程序:创建 PDA 存储账户并写入数据");

    let accounts_iter = &mut accounts.iter();

    // 获取我们需要的账户
        // next_account_info() 从迭代器中提取下一个 AccountInfo
    let storage_account = next_account_info(accounts_iter)?;
    let signer = next_account_info(accounts_iter)?;
    let system_program = next_account_info(accounts_iter)?;
    let rent = next_account_info(accounts_iter)?;

    // 验证系统程序
    if system_program.key != &system_program::ID {
        msg!("无效的系统程序");
        return Err(ProgramError::IncorrectProgramId);
    }

    // 验证签名者是签名者
    if !signer.is_signer {
        msg!("签名者必须是签名者");
        return Err(ProgramError::MissingRequiredSignature);
    }

    // 创建我们的计数器数据
    let counter_data = CounterData { count: 100 };
    let serialized_data = counter_data.try_to_vec()?;
    let space = serialized_data.len();

    msg!("创建 {} 字节的 PDA 存储账户", space);
    msg!("序列化数据: {:?}", serialized_data);

    // 获取租金信息
    let rent_sysvar = Rent::from_account_info(rent)?;
    let lamports = rent_sysvar.minimum_balance(space);

    // 定义我们 PDA 的种子
    let seed = b"storage";
    let (expected_pda, bump_seed) = Pubkey::find_program_address(&[seed], program_id);

    // 验证提供的账户是否是预期的 PDA
    if storage_account.key != &expected_pda {
        msg!("提供了无效的 PDA");
        return Err(ProgramError::InvalidAccountData);
    }

    // 使用系统程序通过 PDA 签名创建账户
    let create_account_ix = system_instruction::create_account(
        signer.key,
        storage_account.key,
        lamports,
        space as u64,
        program_id,
    );

    // create_account 指令所需的账户
    let accounts = &[\
        signer.clone(),\
        storage_account.clone(),\
        system_program.clone(),\
    ];

    // PDA 签名的种子(seed + bump)
    let signer_seeds = &[&seed[..], &[bump_seed]];
    invoke_signed(&create_account_ix, accounts, &[&signer_seeds[..]])?;

    // 将数据写入账户
    let mut account_data = storage_account.try_borrow_mut_data()?;
    account_data.copy_from_slice(&serialized_data);

    msg!("数据已写入 PDA 存储账户");

    Ok(())
}

既然我们已经看到了完整的实现,现在让我们来研究使 PDA 账户创建成为可能的核心机制。

使用 invoke_signed() 创建 PDA

invoke_signed() 允许我们的程序通过提供用于派生该地址的种子来充当 PDA 的签名者。Solana 运行时会验证这些种子是否确实派生了 PDA,如果验证成功,...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论