7. 理解入口函数

理解入口函数

Solana 程序的调用机制

每笔 Solana 交易可以包含多个指令(Instruction)。一个指令就是对某个链上程序的一次调用,包含三个核心部分:

rust
pub struct Instruction { pub program_id: Pubkey, // 1. 目标程序地址 (你是谁?) pub accounts: Vec<AccountMeta>, // 2. 涉及的账户列表 (你要操作哪些文件?) pub data: Vec<u8>, // 3. 调用数据 (你要做什么?) }

当指令被执行时,Solana 运行时 (Runtime) 会加载目标程序,并将这些信息作为参数传递给程序的入口函数


入口函数签名

所有 Solana 程序(原生 Rust)都必须实现这个标准的入口函数:

rust
pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult

让我们逐一拆解这三个参数。

参数一:program_id

program_id 是当前正在执行的程序自己的地址(公钥)。

  • 用途:主要用于权限检查。例如,当程序需要通过 PDA 签名时,它需要验证自己是否真的是这个 PDA 的所有者。

参数二:accounts (核心)

accounts 是一个 AccountInfo 的切片(数组)。 这是 Solana 编程模型最独特的地方:程序不能随意访问链上的任意账户。它只能访问调用方(客户端)在交易中显式传入的账户。

每个 AccountInfo 包含了账户在链上的实时状态:

  • key: 账户的公钥地址。
  • is_signer: 交易发起人是否对这个账户进行了签名?(用于鉴权,例如:只有 owner 签名了才能扣钱)。
  • is_writable: 客户端是否声明要修改这个账户?(如果没声明 writable,程序试图修改数据会报错)。
  • lamports: 账户余额(可修改)。
  • data: 账户存储的二进制数据(可修改)。
  • owner: 账户的所有者程序 ID。

处理顺序:客户端传入账户的顺序是固定的(例如:[用户, 系统程序])。程序内部通常使用迭代器按顺序提取:

rust
let accounts_iter = &mut accounts.iter(); let account_user = next_account_info(accounts_iter)?; // 第1个 let account_system = next_account_info(accounts_iter)?; // 第2个

参数三:data

data 是调用方传入的字节数组。它完全是自定义的。 在我们的“链上存储器”项目中,这个 data 就是用户想要保存的字符串内容。 在更复杂的程序中(如 Token 程序),data 的第一个字节通常是指令索引 (Instruction Index),用来区分是“转账”还是“铸造”。


与以太坊的对比

特性Ethereum (Solidity)Solana (Rust)
数据访问合约可以读取全局任意状态 (State Trie)。必须提前声明。程序只能看到传入的 accounts
状态存储数据存在合约自己的 storage 中。数据存在独立的 Account 中,程序本身无状态。
并行执行不支持。因为不知道合约会动谁的数据,必须串行。支持。因为输入账户已声明,运行时知道哪些交易不冲突。

这种“显式声明账户”的设计虽然增加了开发时的繁琐度,但它是 Solana 实现海量并行处理 (Sealevel) 的关键机制。

RUSTPlayground
EDITOR ACTIVE
Initializing RUST Environment...

入口函数透视镜

Client: Build Instruction
Accounts (Vec<AccountMeta>)
0
Payer
UserWallet
1
Storage
PdaAccount
2
System
SystemProg
Instruction Data (Vec<u8>)
Bytes: [72, 101, 108, 108, 111]
lib.rs: process_instruction
program_id: &Pubkey
MyProgram111...
accounts: &[AccountInfo]
AccountInfo [0]
key: UserWallet
is_signer:true
is_writable:true
lamports: 5000000000
owner: 1111...1111
data: 0x
AccountInfo [1]
key: PdaAccount
is_signer:false
is_writable:true
lamports: 2000000
owner: MyProgram
data: 0x00... (Empty)
AccountInfo [2]
key: SystemProg
is_signer:false
is_writable:false
lamports: 1
owner: NativeLoader
data: 0x (Native)
data: &[u8]
[72, 101, 108, 108, 111]
// "Hello"