Solana 60 天课程

2025年02月27日更新 89 人订阅
原价: ¥ 53 限时优惠
专栏简介 开始 Solana - 安装与故障排除 Solana 和 Rust 中的算术与基本类型 Solana Anchor 程序 IDL Solana中的Require、Revert和自定义错误 Solana程序是可升级的,并且没有构造函数 Solidity开发者的Rust基础 Rust不寻常的语法 Rust 函数式过程宏 Rust 结构体与属性式和自定义派生宏 Rust 和 Solana 中的可见性与“继承” Solana时钟及其他“区块”变量 Solana 系统变量详解 Solana 日志、“事件”与交易历史 Tx.origin、msg.sender 和 onlyOwner 在 Solana 中:识别调用者 Solana 计算单元与交易费用介绍 在 Solana 和 Anchor 中初始化账户 Solana 计数器教程:在账户中读写数据 使用 Solana web3 js 和 Anchor 读取账户数据 在Solana中创建“映射”和“嵌套映射” Solana中的存储成本、最大存储容量和账户调整 在 Solana 中读取账户余额的 Anchor 方法:address(account).balance 功能修饰符(view、pure、payable)和回退函数在 Solana 中不存在的原因 在 Solana 上实现 SOL 转账及构建支付分配器 使用不同签名者修改账户 PDA(程序派生地址)与 Solana 中的密钥对账户 理解 Solana 中的账户所有权:从PDA中转移SOL Anchor 中的 Init if needed 与重初始化攻击 Solana 中的多重调用:批量交易与交易大小限制 Solana 中的所有者与权限 在Solana中删除和关闭账户与程序 在 Anchor 中:不同类型的账户 在链上读取另一个锚点程序账户数据 在 Anchor 中的跨程序调用(CPI) SPL Token 的运作方式 使用 Anchor 和 Web3.js 转移 SPL Token Solana 教程 - 如何实现 Token 出售 基础银行教程 Metaplex Token 元数据工作原理 使用Metaplex实施代币元数据 使用 LiteSVM 进行时间旅行测试 Solana Token-2022 标准规范 生息代币第一部分 计息代币第二部分 Solana 指令自省 Solana 中的 Ed25519 签名验证 Solana - Switchboard 预言机使用 原生Solana:程序入口与执行 原生 Solana :读取账户数据 原生 Solana :Borsh 序列化 原生 Solana:使用 invoke 和 invoke signed 进行跨程序调用 原生 Solana :创建存储账户 (一) 原生 Solana:创建存储账户 二 原生 Solana: 函数分发 原生 Solana:关键安全检查 Rust 程序到 SBF 编译 sBPF 虚拟机和指令集介绍 跟踪 sBPF 指令执行和计算成本 Solana 程序执行与输入序列化 指令处理器和运行时设置 sBPF 内存布局和寄存器约定 使用 sBPF 汇编读取 Solana 指令输入 Solana 系统调用:sBPF 汇编中的日志记录

原生 Solana:创建存储账户 二

这篇文章详细介绍了如何在 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 条评论

请先 登录 后评论