LogoAnchor 中文文档

零拷贝

学习如何在 Solana 程序中使用 Anchor 的零拷贝反序列化功能来处理大型账户数据。

使用方法

零拷贝是一种反序列化功能,允许程序直接从内存中读取账户数据而无需复制。这在处理大型账户时特别有用。

要使用零拷贝,请将 bytemuck crate 添加到你的依赖项中。添加 min_const_generics 功能以允许在你的零拷贝类型中使用任意大小的数组。

Cargo.toml
[dependencies] bytemuck = { version = "1.20.0", features = ["min_const_generics"] } anchor-lang = "0.30.1"

定义零拷贝账户

要定义使用零拷贝的账户类型,请使用 #[account(zero_copy)] 注解结构体。

 #[account(zero_copy)] pub struct Data {  // 10240 字节 - 8 字节账户标识符  pub data: [u8; 10232], }

#[account(zero_copy)] 属性自动实现了零拷贝反序列化所需的几个特性:

 #[derive(Copy, Clone)] #[derive(bytemuck::Zeroable)] #[derive(bytemuck::Pod)] #[repr(C)] struct Data {  // --snip-- }

使用 AccountLoader 处理零拷贝账户

要反序列化一个零拷贝账户,请使用 AccountLoader<'info, T>,其中 T 是使用 #[account(zero_copy)] 属性定义的零拷贝账户类型。

例如:

#[derive(Accounts)] pub struct InstructionAccounts<'info> {    pub zero_copy_account: AccountLoader<'info, Data>, }

初始化零拷贝账户

init 约束可以与 AccountLoader 类型一起使用来创建零拷贝账户。

#[derive(Accounts)] pub struct Initialize<'info> {  #[account(    init,  // 10240 字节是使用 init 约束分配的最大空间  space = 8 + 10232,  payer = payer,  )]  pub data_account: AccountLoader<'info, Data>,  #[account(mut)]  pub payer: Signer<'info>,  pub system_program: Program<'info, System>, }

由于 CPI 限制,init 约束最多只能分配 10240 字节。在底层,init 约束会调用 SystemProgram 创建账户。

首次初始化零拷贝账户时,使用 load_init 获取账户数据的可变引用。load_init 方法还会设置账户标识符。

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {    let account = &mut ctx.accounts.data_account.load_init()?;  account.data = [1; 10232];  Ok(()) }

对于需要超过 10240 字节的账户,请使用 zero 约束而不是 initzero 约束通过检查账户标识符是否已设置来验证账户是否未初始化。

#[derive(Accounts)] pub struct Initialize<'info> {    #[account(zero)]  pub data_account: AccountLoader<'info, Data>, }

使用 zero 约束时,你需要先在单独的指令中通过直接调用 System Program 来创建账户。这允许你创建最大为 Solana 最大账户大小 10MB (10_485_760 字节) 的账户,绕过了 CPI 的限制。

和之前一样,使用 load_init 获取账户数据的可变引用并设置账户标识符。由于 8 字节保留给账户标识符,最大数据大小为 10_485_752 字节 (10MB - 8 字节)。

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {    let account = &mut ctx.accounts.data_account.load_init()?;  account.data = [1; 10_485_752];  Ok(()) }

更新零拷贝账户

当你需要可变访问来更新现有的零拷贝账户时,使用 load_mut:

#[derive(Accounts)] pub struct Update<'info> {   #[account(mut)]  pub data_account: AccountLoader<'info, Data>, }
pub fn update(ctx: Context<Update>) -> Result<()> {    let account = &mut ctx.accounts.data_account.load_mut()?;  account.data = [2; 10232];  Ok(()) }

读取零拷贝账户

使用 load 仅读取账户数据。

#[derive(Accounts)] pub struct ReadOnly<'info> {  pub data_account: AccountLoader<'info, Data>, }
pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {    let account = &ctx.accounts.data_account.load()?;  msg!("前 10 字节: {:?}", &account.data[..10]);  Ok(()) }

示例

以下示例展示了在 Anchor 中初始化零拷贝账户的两种方法:

  1. 使用 init 约束在单个指令中初始化账户
  2. 使用 zero 约束初始化数据大于 10240 字节的账户

零拷贝

lib.rs
use anchor_lang::prelude::*;   declare_id!("8B7XpDXjPWodpDUWDSzv4q9k73jB5WdNQXZxNBj1hqw1");   #[program] pub mod zero_copy {  use super::*;  pub fn initialize(ctx: Context<Initialize>) -> Result<()> {  let account = &mut ctx.accounts.data_account.load_init()?;  account.data = [1; 10232];  Ok(())  }    pub fn update(ctx: Context<Update>) -> Result<()> {  let account = &mut ctx.accounts.data_account.load_mut()?;  account.data = [2; 10232];  Ok(())  } }   #[derive(Accounts)] pub struct Initialize<'info> {  #[account(  init,  // 10240 字节是使用 init 约束分配的最大空间  space = 8 + 10232,  payer = payer,  )]  pub data_account: AccountLoader<'info, Data>,  #[account(mut)]  pub payer: Signer<'info>,  pub system_program: Program<'info, System>, }   #[derive(Accounts)] pub struct Update<'info> {  #[account(mut)]  pub data_account: AccountLoader<'info, Data>, }   #[account(zero_copy)] pub struct Data {  // 10240 字节 - 8 字节账户标识符  pub data: [u8; 10232], }

初始化大型账户

当初始化一个需要超过 10,240 字节空间的账户时,你必须将初始化过程分为两步:

  1. 在单独的指令中调用 System Program 创建账户
  2. 在你的程序指令中初始化账户数据

注意,Solana 的最大账户大小为 10MB (10_485_760 字节),8 字节保留给账户标识符。

lib.rs
use anchor_lang::prelude::*;   declare_id!("CZgWhy3FYPFgKE5v9atSGaiQzbSB7cM38ofwX1XxeCFH");   #[program] pub mod zero_copy_two {  use super::*;  pub fn initialize(ctx: Context<Initialize>) -> Result<()> {  let account = &mut ctx.accounts.data_account.load_init()?;  account.data = [1; 10_485_752];  Ok(())  } }   #[derive(Accounts)] pub struct Initialize<'info> {  #[account(zero)]  pub data_account: AccountLoader<'info, Data>, }   #[account(zero_copy)] pub struct Data {  // 10240 字节 - 8 字节账户标识符  pub data: [u8; 10_485_752], }

On this page

在GitHub上编辑