Skip to main content

Account

官方基础文档: Accounts and Storing State | Solana quicknode 文档

把 solana-program 中的 AccountInfo 抠出来方便大家理解文档里的metadata:

/// Account information
#[derive(Clone)]
#[repr(C)]
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc<RefCell<&'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc<RefCell<&'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a Pubkey,
/// The epoch at which this account will next owe rent
pub rent_epoch: Epoch,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// This account's data contains a loaded program (and is now read-only)
pub executable: bool,
}

三个重要的属性:owner is_signer is_writable

同时注意 lamportsdata 的类型 alloc::rc::Rc<RefCell<T>>Rc<T>是一个引用计数类型,它使得多个所有者可以共享同一份数据。Rc代表"Reference Counted"。这说明跨程序调用时多个program会同时持有一个 account data,因此需要注意 跨程序调用后的 data reload。

CreateAccount

owner 涉及到 account 的注册和初始化过程。 solana 中的 account 是需要注册后才能使用的,这需要调用 一个 native program (类似evm中的precompiled contract或者 L2中的 system contract) , SystemProgram : 11111111111111111111111111111111CreateAccount instruction 。

CreateAccount 会首先检查这个 account 是否使用过,检查非常简单,看余额是否为0。

然后进入 allocate 分配空间:检查要注册的公钥是否签名,这就要求我们需要有正在注册的account的私钥,而不是注册任意地址。然后检查 account.data 是否为空并且 owner 是否是 SystemProgram 自身,因为 SystemProgram 是所有地址默认的 owner。

然后 assign 会调用 account.set_owner(new owner) 来转移所有权,这个函数中对 new owner的pubkey做了很多检查:

        // Only the owner can assign a new owner
if !self.is_owned_by_current_program() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the account is not executable
if self.is_executable() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the data is zero-initialized or empty
if !is_zeroed(self.get_data()) {
return Err(InstructionError::ModifiedProgramId);
}
// don't touch the account if the owner does not change
if self.get_owner().to_bytes() == pubkey {
return Ok(());
}
self.touch()?; 👈 这个地方更新了一个 bit map,用于记录在ix里访问了哪些acounts index,我暂时没看有啥用
self.account.copy_into_owner_from_slice(pubkey);
Ok(())

所以其实 create account 做的事情不复杂,我们直接随便从主网 dump 一个 account 看下:

╰─ solana account -u m HLF7gaiXdPFbnChZhGLpwKQzyGRcqfzYeo3AxM22fbVN

Public Key: HLF7gaiXdPFbnChZhGLpwKQzyGRcqfzYeo3AxM22fbVN
Balance: 0.00203928 SOL
Owner: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Executable: false
Rent Epoch: 0
Length: 165 (0xa5) bytes
0000: c9 9f 79 87 d6 8d aa 5c d5 9e 22 aa b5 4c f0 e9 ..y....\.."..L..
0010: cf a4 e2 8e 3c d3 3a a9 b4 aa bd 90 fc eb 34 9d ....<.:.......4.
0020: 7c 33 36 dd ef 97 b5 2f 73 72 89 c3 c0 80 80 4d |36..../sr.....M
0030: 6b 2b bb d1 e3 31 53 aa 0b c7 e0 ca 6e b0 9b b5 k+...1S.....n...
0040: c9 ed 79 4b 0b 06 00 00 00 00 00 00 00 00 00 00 ..yK............
0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0060: 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 ................
0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00a0: 00 00 00 00 00 .....

这就是solana 中的 storage了。

只有 account 的 owner 才对 account data有写权限,也就是获得 is_writable 这个flag,或者传入一个 mut 引用。

还有一个非常重要的特性:使用 create account 注册的account是拥有私钥的,这意味着这个account可以使用外部签名。参阅此测试: https://git.o9s.xyz/ronnyxing/learn-solana-suniswap/-/blob/master/tests/i_account_create.rs?ref_type=heads#L23

account data 的序列化与反序列化

一般使用 Borsh 或 serde 格式,但是其实可以完全自己定义。可以参考cookbook Solana Cookbook | Serializing Data

instruction 中的 account 输入

不管是普通 account,还是 program,甚至是 native program(例如 solana system program),只要是这个 ix 用到了,就必须在tx中输入这个 account。因此必须校验每个 account pubkey 是否正确。

sysvars

但是 sysvars 相关的 account 可以不输入就直接使用([TODO]原理未知,可能类似全局只读变量,预加载?)例如获取时间戳:Clock::get()

其他 sysvars 👉 Solana Sysvar Cluster Data | Solana Validator

调整程序空间

realloc 约束用于在指令开始时调整程序账户的空间。它要求账户是可变的(即 mut),并适用于 AccountAccountLoader 类型。它被定义为 #[account(realloc =, realloc::payer =, realloc::zero =)]。当增加账户数据长度时,从 realloc::payer 转移 lamports 到程序账户,以保持租金豁免。如果数据长度减少,则从程序账户将 lamports 移回 realloc::payer。realloc::zero 约束决定新分配的内存是否应该进行零初始化。零初始化确保新内存是干净的,没有任何残留或不需要的数据。

不建议手动使用AccountInfo::realloc,而是使用realloc约束。这是因为缺乏运行时检查,以确保重新分配不超过MAX_PERMITTED_DATA_INCREASE限制,否则可能导致覆盖其他账户中的数据。该约束还会检查并阻止在单个指令中重复重新分配。

refer:

TODO

  1. 租金豁免是强制的吗?

    1. 现在是的
  2. rent epoch 到期后会发生什么,主网目前是什么状况?

  3. 如何 "删除" account?

  4. 把一个 account owner设置为 "EOA" address是否有意义?

  5. tx的原始输入中需要包含所有的 account 吗?似乎内部 cpi 可以用到未输入的 account。