解析 | Solana中的关键数据结构
Solana中的关键数据结构
为了方便合约的书写,Solana官方提供了C和Rust的SDK,对于Rust来说,只要在工程中添加
solana-program = "1.6.7"
即可以添加SDK的依赖,这里的版本号可以自行选择。而SDK的相关代码可以查阅相关Crate solana_sdk这里介绍一些SDK中提供的主要数据结构。
Pubkey
#[repr(transparent)]
#[derive(
Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash, AbiExample,
)]
pub struct Pubkey([u8; 32]);]
Pubkey实际上就是32个字符表示的base58的Account地址,在Instruction中,我们看到的ProgramId 就是这样的类型,因为Program本身其实是一个文件,也就是Account,只是是可执行的文件。
AccountInfo
/// Account information
#[derive(Clone)]
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a Pubkey,
/// This account's data contains a loaded program (and is now read-only)
pub executable: bool,
/// The epoch at which this account will next owe rent
pub rent_epoch: Epoch,
}
AccountInfo就是一个Account在链上的表达形式,可以认为是一个文件的属性,想象一下state函数列出的文件属性。
其中,key表示文件名,也就是base58的地址。而文件大小可以认为是lamports,这里区别于我们操作系统里面的文件,操作系统里面的文件的大小是可以为0的,且文件存在,而Solana链上的Account如果其大小也就是lamports为0的话,就认为这个文件被删除了。
这里的“executable”表示文件是否可执行,如果是可执行的,那么就是一个智能合约账号。而data里面则是文件的内容,类似电脑上的ls列出的文件属性,和cat列出来的文件内容。
每个文件都要由一个程序来创建,这个程序称之为这个文件的拥有者,也就是这里的owner。
ProgramResult
/// Reasons the program may fail
#[derive(Clone, Debug, Deserialize, Eq, Error, PartialEq, Serialize)]
pub enum ProgramError {
/// Allows on-chain programs to implement program-specific error types and see them returned
/// by the Solana runtime. A program-specific error may be any type that is represented as
/// or serialized to a u32 integer.
#[error("Custom program error: {0:#x}")]
Custom(u32)
...
}
use std::{
result::Result as ResultGeneric,
};
pub type ProgramResult = ResultGeneric<(), ProgramError>;
ProgramResult实际上类型为ProgramError的Result对象,而ProgramError是Solana自定义的一个Error的枚举,也就是Solana抛出来的错误枚举。
在合约中,当正常逻辑执行结束后,我们通过Ok()来返回这里Reuslt正确的结果,如果出错了,则通过这里的Result中的ProgramError错误返回。
AccountMeta
/// Account metadata used to define Instructions
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct AccountMeta {
/// An account's public key
pub pubkey: Pubkey,
/// True if an Instruction requires a Transaction signature matching `pubkey`.
pub is_signer: bool,
/// True if the `pubkey` can be loaded as a read-write account.
pub is_writable: bool,
}
AccountMeta主要用于Instruction结构的定义,用于协助传递这个指令需要的其他账号信息,其中包括了账号的地址,这个账号是否为签名账号,以及这个账号对应的内容(AccountInfo)是否可以修改。
Instruction
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Instruction {
/// Pubkey of the instruction processor that executes this instruction
pub program_id: Pubkey,
/// Metadata for what accounts should be passed to the instruction processor
pub accounts: Vec,
/// Opaque data passed to the instruction processor
pub data: Vec<u8>,
}
Instruction在上面已经有介绍了,代表一个处理指令,包含了要处理他的程序的地址program_id,以及这个程序处理时需要用到的AccountMeta表示的账号信息,还有这个指令对应的具体数据payload部分的data。
这里真实的用户协议数据是序列化后,存放在data里面的,所以整体流程是DApp客户端将自定义的指令数据序列化到data里面,然后将账号信息和data发到链上,Solana节点为其找到要执行的程序,并将账号信息和数据data传递给合约程序,合约程序里面将这个data数据反序列化,得到客户端传过来的具体参数。
总结
Solana的合约编程,其实主要就是对Account的增删改查,或者说就是我们普通程序中的对文件的增删改查。这其中需要使用Solana提供的SDK,按照其框架进行编程,主要数据结构在SDK中都有提供,在编写逻辑的时候,可以直接使用。