本文详细介绍了Solana程序中的函数分发机制,解释了其在Native Rust程序中的重要性,并对比了与以太坊和Anchor框架的区别。文章深入阐述了Anchor如何通过8字节鉴别器实现函数分发,并提供了三种在原生Rust程序中实现分发的方法。最后,通过一个完整的Native Rust程序示例和TypeScript客户端,演示了如何使用简单字节方法进行函数分发。
Solana 中的函数分发是根据指令数据中编码的特定标识符,将传入指令路由到适当的处理函数的过程。
在我们之前的原生 Rust Solana 教程中,我们将所有程序逻辑都放在 process_instruction 函数中。这对于只有一个指令的简单程序是可行的。然而,当一个程序支持多个指令时,入口点会充满解析逻辑、条件检查和处理代码。一个更清晰的方法是将逻辑移动到单独的函数中,并将每个指令路由到正确的处理程序。
与 Ethereum 不同,EVM 通过内置选择器将调用路由到正确的函数,Solana 程序必须自己路由指令。Anchor 通过生成 8 字节的 discriminator 并将其添加到指令数据的前面来解决这个问题。程序读取这个值并分发到正确的处理程序。我们将在下一节详细解释这一点。
在本教程中,我们将演示如何在原生 Rust Solana 程序中处理函数分发。
当你在 Anchor 中编写如下函数时:
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// Initialize logic
Ok(())
}
pub fn update_counter(ctx: Context<UpdateCounter>, new_value: u64) -> Result<()> {
// Update logic
Ok(())
}
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
// Close logic
Ok(())
}
}
Anchor 生成必要的代码,根据指令数据将指令路由到适当的函数。在底层,它执行三个主要步骤:
sha256("global:" + function_name))。这个唯一的标识符有助于将每个指令路由到正确的处理函数,这类似于 Ethereum,其中函数选择器是函数签名的 Keccak-256 哈希的前 4 字节。let mutdiscriminator = [0u8; 8];
let preimage = format!("global:{}", ix_name); // ix_name = 函数名
let hash = sha2::Sha256::digest(preimage.as_bytes());
discriminator.copy_from_slice(&hash[..8]);
// Anchor 生成的概念性表示
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// 分割出 8 字节的 discriminator
let (discriminator, remaining_data) = instruction_data.split_at(8);
// 匹配已知的指令 discriminator
match discriminator {
// 每个指令的 discriminator 都在编译时计算
INITIALIZE_DISCRIMINATOR => initialize(program_id, accounts, remaining_data),
UPDATE_COUNTER_DISCRIMINATOR => update_counter(program_id, accounts, remaining_data),
_ => return Err(ProgramError::InvalidInstructionData),
}
}
有了这三个步骤,我们编写 Rust 函数,Anchor 为我们处理指令路由和所有指令数据解析。但由于我们使用的是原生 Rust,我们必须自己完成这些步骤。
我们将使用 Rust 构建一个原生 Solana 程序,它有三个函数:process_instruction 函数和两个指令处理程序。在 process_instruction 中,我们将检查指令数据的第一个字节,并使用 match 语句分发到适当的处理程序。第一个函数将简单地记录一条消息,以演示路由机制正确识别并调用了预期的处理程序。第二个函数将遍历 account 并解析指令数据字节。
一旦我们了解了路由的工作原理,我们之前在原生 Rust Solana 教程中介绍的所有其他概念,即 account 创建、Borsh serialization、cross-program invocation,都可以在你的处理函数内部以相同的方式应用。
我们可以通过多种方式实现函数分发。虽然没有单一的强制约定(程序可以自由选择自己的方法),但简单字节方法和 Borsh 序列化枚举在原生 Rust 程序中最常见,而 Anchor 使用其哈希方法。以下是主要方法:
const INITIALIZE: u8 = 0 和 const UPDATE: u8 = 1。当客户端调用我们的程序时,它将这些值中的一个放在指令数据的开头(字节位置 0)。然后我们的程序读取第一个字节,并将其与定义的常量进行比较,以确定客户端打算调用哪个函数,并相应地路由执行。我们将使用简单字节方法作为我们的示例。
我们的程序将有三个主要功能:
process_instruction: 指令处理器,接收所有指令并根据指令数据将其路由到适当的处理程序。say_hello: 一个简单的处理函数,用于记录问候消息。inspect_accounts: 此函数将从我们将构建的客户端接收字符串消息,并将其与程序 ID 和该客户端提供的 account 信息一起记录。首先,让我们设置项目结构来演示函数分发:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!