这篇教程详细讲解了如何在Solana原生程序中使用密钥对创建账户来存储数据。内容涵盖了账户分配、初始化、Borsh数据序列化以及与System Program的交互,并通过Rust程序和TypeScript客户端示例,演示了账户创建、数据写入与验证的完整过程。
在本分为两部分的教程中,我们将学习如何使用两种方法在原生 Solana 程序中创建用于存储数据的账户:密钥对(本部分)和程序派生地址 (PDA)(第二部分)。我们的目标是深入理解账户分配、初始化和数据序列化——这是 Anchor 的 #[account(init)] 宏 所抽象出的底层逻辑。
对于这两种方法,我们都将构建一个程序,它创建一个账户并将数据写入其中,然后使用 TypeScript 客户端进行测试以验证数据是否正确写入。
运行以下命令创建目录并使用 Cargo 初始化 Rust 项目:
Copymkdir solana-storage-write
cd solana-storage-write
cargo init --lib
将 Cargo.toml 更新为以下配置,它设置了 crate 类型并添加了 Solana 程序依赖项:
Copy[package]
name = "solana-storage-write"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
solana-program = "1.18"
borsh = "0.10"
请注意,我们添加了 borsh = "0.10" 用于序列化,这在创建账户时是必需的。我们已经在之前的教程中详细介绍了 Borsh 序列化。
现在我们来创建我们的程序。
在深入代码之前,让我们了解一下存储数据的 Solana 账户是怎样的:

data 字段是我们存储序列化结构体的地方(我们稍后会讲到)。当我们创建一个账户时,我们会指定该字段应容纳多少字节,系统程序会分配该空间。
以下步骤展示了如何创建用于数据存储的基于密钥对的账户:
u64 计数字段的 CounterData 结构体),并用 Borsh 对其进行序列化现在让我们在代码中看看。将 src/lib.rs 文件中的代码替换为以下内容。我们添加了代码注释以显示我们在何处实现了上述每个步骤:
Copyuse borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint,
entrypoint::ProgramResult,
msg,
program::invoke,
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
system_instruction, system_program,
};
entrypoint!(process_instruction);
// 这代表我们将存储在账户中的数据
// 我们添加了 Borsh 派生宏用于序列化和反序列化
#[derive(BorshSerialize, BorshDeserialize, Debug)]
pub struct CounterData {
pub count: u64,
}
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
msg!("Storage Write Program: Creating storage account and writing data");
let accounts_iter = &mut accounts.iter();
// 步骤 1:获取我们需要的账户
// 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)?;
// 验证系统程序
if system_program.key != &system_program::ID {
msg!("Invalid system program");
return Err(ProgramError::IncorrectProgramId);
}
// 验证签名者账户确实是签名者
if !signer.is_signer {
msg!("Signer must be a signer");
return Err(ProgramError::MissingRequiredSignature);
}
// 步骤 2:创建我们的计数器数据
let counter_data = CounterData { count: 42 };
// 步骤 2:用 Borsh 序列化数据 (u64 变成 8 字节的小端格式)
let serialized_data = counter_data.try_to_vec()?; // [42, 0, 0, 0, 0, 0, 0, 0]
// 步骤 3:确定数据所需空间
let space = serialized_data.len();
msg!("Creating storage account with {} bytes", space);
msg!("Serialized data: {:?}", serialized_data);
// 步骤 4:计算免租金所需的 lamports
let lamports = Rent::default().minimum_balance(space);
// 步骤 5:使用系统程序创建账户
let create_account_ix = system_instruction::create_account(
signer.key,
storage_account.key,
lamports,
space as u64,
program_id,
);
// 步骤 5(续):执行 create_account 指令
invoke(
&create_account_ix,
&[\
signer.clone(),\
storage_account.clone(),\
system_program.clone(),\
],
)?;
msg!("Storage account created successfully");
// 步骤 6:将序列化数据写入账户
let mut account_data = storage_account.try_borrow_mut_data()?;
account_data.copy_from_slice(&serialized_data);
msg!("Data written to storage account");
Ok(())
}
现在我们将看看账户创建背后的步骤。
我们程序中的账户创建分两步进行。首先,我们使用 system_instruction::create_account() 构建指令——这是一个辅助函数,它用正确的账户和数据为账户创建构建 Instruction 结构体(在 CPI 教程中,我们手动构建了这个结构体)。

其次,我们使用 invoke() 执行此指令,它对系统程序执行跨程序调用 (CPI)。然后系统程序实际在链上创建具有我们指定参数的账户。
create_account 指令系统程序的 create_account 指令通过转移 lamports、分配数据空间和分配所有者来创建新的链上账户。它需要五个参数:
由于系统程序要求新账户地址在 create_account 指令中是签名者,因此密钥对账户按预期工作:交易包含来自密钥对私钥的签名。PDA 没有私钥,因此创建它的程序必须通过 invoke_signed() 提供种子,运行时使用这些种子重新派生和验证 PDA,授予其签名权限(我们将在第 2 部分中看到)。
Solana 如何知道密钥对地址可用于账户创建?
客户端生成一个新的密钥对并在交易中传递其公钥。我们的程序通过 `let stor...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!