理解入口函数
Solana 程序的调用机制
每笔 Solana 交易可以包含多个指令(Instruction)。一个指令就是对某个链上程序的一次调用,包含三个核心部分:
rustpub struct Instruction { pub program_id: Pubkey, // 1. 目标程序地址 (你是谁?) pub accounts: Vec<AccountMeta>, // 2. 涉及的账户列表 (你要操作哪些文件?) pub data: Vec<u8>, // 3. 调用数据 (你要做什么?) }
当指令被执行时,Solana 运行时 (Runtime) 会加载目标程序,并将这些信息作为参数传递给程序的入口函数。
入口函数签名
所有 Solana 程序(原生 Rust)都必须实现这个标准的入口函数:
rustpub 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。
处理顺序:客户端传入账户的顺序是固定的(例如:[用户, 系统程序])。程序内部通常使用迭代器按顺序提取:
rustlet 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) 的关键机制。