Solana/程序开发入门/入口函数解释

Solana 的每一笔交易中都包含一个或多个指令. 一个指令是对链上某个程序账户的调用, 包含三部分, 源码:

pub struct Instruction {
    /// Pubkey of the program that executes this instruction.
    pub program_id: Pubkey,
    /// Metadata describing accounts that should be passed to the program.
    pub accounts: Vec<AccountMeta>,
    /// Opaque data passed to the program for its own interpretation.
    pub data: Vec<u8>,
}

所以, 当一个指令被执行时, 它就会被送给您写的程序的入口函数 process_instruction().

程序入口函数签名

solana_program::entrypoint!(process_instruction);

pub fn process_instruction(
    program_id: &solana_program::pubkey::Pubkey,
    accounts: &[solana_program::account_info::AccountInfo],
    data: &[u8],
) -> solana_program::entrypoint::ProgramResult {
    solana_program::msg!("Hello Solana!");
    Ok(())
}

我们一一解释.

参数 program_id: &solana_program::pubkey::Pubkey 是当前这个合约(程序账户)本身的地址. 在链上, 每个部署的程序都有一个账户地址(公钥). 当交易指令调用这个程序时, solana 会把它写进 program_id 字段中. 您可以用它来做校验, 比如检查某个账户是否是由这个程序创建的 pda. 比如:

let expected_pda = solana_program::pubkey::Pubkey::create_program_address(&[seed], program_id)?;

参数 accounts: &[solana_program::account_info::AccountInfo] 是指令中涉及的账户列表, 对应 Instruction.accounts 里的 Vec<AccountMeta> 项. 这些账户由调用方指定, 并且程序不能随便添加账户, 只能用这些已传入的账户.

每个 AccountInfo 包含:

  1. 账户地址 (key)
  2. 是否是签名者 (is_signer)
  3. 是否是可写账户 (is_writable)
  4. Lamports 余额 (lamports)
  5. 数据 (data)
  6. 所属程序 (owner)
  7. ...

程序通常需要自己根据账户位置索引来解读这些账户. 比如:

let account_user = &accounts[0];
let account_user_pda = &accounts[1];

注意账户的顺序非常重要, 您必须和调用方传入的顺序一一对应.

参数 data: &[u8] 是调用方自定义的指令数据. 通常我们会自己设计一个结构, 然后用 borsh, serde 等序列化方案或手动解析来从字节数组中提取内容. 您可以把它理解为"程序要执行什么操作 + 参数", 类似函数调用时传参.

这个模式和其他链, 比如以太坊的函数调用, 很不一样, solana 追求高性能, 所以要求调用方把所有要用的账户和数据一次性传进来, 程序自己不会查链, 也不扫账户, 而是基于这些参数做纯函数式的运算.