本文详细介绍了如何在Solana区块链上使用原生Rust编写程序,而非依赖Anchor框架。文章解释了Solana程序的入口点(Entrypoint)和指令处理器(Instruction Processor)的工作原理,通过具体代码示例演示了如何设置项目、编写指令处理逻辑,以及使用entrypoint!宏连接程序入口,并提供了TypeScript客户端进行测试。
我们进入模块 7 :原生 Solana 程序
在本系列中,我们一直使用 Anchor 框架来构建 Solana 程序。本教程将教你如何使用原生 Rust 而不依赖 Anchor 来编写它们。
你可能希望这样做有几个原因,例如:
到目前为止,我们一直使用 Anchor 来创建程序,并像这样编写函数:
#[program]
pub mod my_program {
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
// function logic
Ok(())
}
pub fn update(ctx: Context<Update>, value: u64) -> Result<()> {
// function logic
Ok(())
}
}
#[program] 属性宏在幕后自动生成一个程序入口点。这个入口点接收所有传入指令,并根据客户端传递的指令数据将它们分派到你的各个函数(initialize、update 等)。在原生 Rust 中,我们将使用 Solana SDK 中的 entrypoint! 宏来创建入口点并手动处理分派。
将入口点视为 Solana 程序的“前门”。在 Ethereum 中,每个公共函数都像有多个前门:EVM 可以直接调用 ERC20 中的 transfer()、approve() 等公共函数,或任何其他公共函数。Solana 的工作方式不同。Solana 程序只有一个前门(入口点),处理来自客户端的所有传入调用。
入口点函数不是由我们直接编写的。它在编译时由 Solana SDK 提供的 entrypoint! 宏生成(我们稍后编写原生程序时会看到)。当客户端调用 Solana 程序时,运行时会调用入口点,入口点会反序列化传入指令并将其传递给我们定义的指令处理器函数(我们接下来会讨论)。指令处理器可以将指令路由到正确的程序函数,执行账户验证,或直接处理业务逻辑。
因此,entrypoint! 宏处理运行时调用你的程序、反序列化指令参数并将其转发给指令处理器所需的所有低级代码。这让你可以使用普通的 Rust 函数和类型编写程序逻辑,而宏则管理与 Solana 的接口。
在原生 Rust Solana 程序中,我们需要定义一个指令处理器:一个处理传入指令的函数。当客户端向你的程序发送指令时,Solana 运行时会调用你程序的入口点,然后入口点会反序列化顶级指令参数并将其传递给你的指令处理器函数。这就是你的程序接收和处理每个指令的方式。
这个指令处理器有一个标准的函数签名:
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult
process_instruction 函数的三个参数是:
program_id:你的程序地址accounts:你的程序需要读取或写入的所有账户instruction_data:包含你程序序列化指令数据的原始字节ProgramResult 返回类型是 Result<(), ProgramError> 的类型别名,其中 ProgramError 是一个枚举,定义了 Solana 程序可能返回的错误。
在 Anchor 中,原始的 process_instruction 参数和返回类型是隐藏的。相反,你的处理器会收到一个 Context<T>,其中包含完全反序列化的账户和指令数据,并应用了自动验证,因此你可以直接使用类型化的结构体而不是原始字节切片。
你可以随意命名这个指令处理器函数,但 Solana 生态系统约定俗成地命名为 process_instruction。这就是你传递给 entrypoint! 宏的函数(正如我们前面讨论的)。
现在,让我们编写一个 Solana 程序,其中包含一个通过 entrypoint! 宏连接的指令处理器,并执行它。我们将使用 TypeScript 客户端测试我们的程序,以了解它在实践中是如何工作的。
我们将构建一个简单的 Solana 程序,其中包含一个执行基本算术并记录结果的指令处理器。这将演示入口点和指令处理器在实践中是如何协同工作的。
如果你一直在遵循之前的教程,你本地应该已经安装了 Rust 和 Solana。如果没有,请参阅 Solana Hello World(安装和故障排除)。
现在让我们为我们的程序创建一个新目录并初始化它,运行以下命令:
mkdir solana-entrypoint-tutorial # 为我们的程序创建一个新目录
cd solana-entrypoint-tutorial # 进入目录
cargo init --lib # 初始化一个新的 Rust 库
接下来,更新项目的 Cargo.toml,使其看起来像这样:
[package]
name = "solana-entrypoint-tutorial"
version = "0.1.0"
edition = "2021" # added
## NEWLY ADDED
[lib]
crate-type = ["cdylib", "lib"]
[dependencies]
solana-program = "3.0.0"
我们添加了两个配置:
crate-type = ["cdylib", "lib"]:告诉 Rust 将我们的库编译为 Solana 可以加载的动态库solana-program = "3.0.0":核心 Solana 程序库,提供了链上程序所需的所有类型和函数现在让我们创建我们的程序。
我们将从一个执行基本算术并记录结果的指令处理器开始。
在 Anchor 中,你可能会在 #[program] 模块中定义一个执行基本数学运算的函数,像这样:
#[program]
pub mod some_program {
pub fn do_math(ctx: Context<DoMath>) -> Result<()> {
let result = 5 + 3;
msg!("Result: {}", result);
Ok(())
}
}
但对于原生 Rust Solana 程序,我们定义一个指令处理器,并使用 entrypoint! 宏将其连接到程序的入口点。虽然你可以定义其他公共函数,但它们必须从指令处理器中调用,因为所有执行都始于此。
entrypoint! 宏完成了繁重的工作:它生成 Solana 运行时调用的实际入口点代码,反序列...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!