Solana 60 天课程

2025年02月27日更新 89 人订阅
原价: ¥ 53 限时优惠
专栏简介 开始 Solana - 安装与故障排除 Solana 和 Rust 中的算术与基本类型 Solana Anchor 程序 IDL Solana中的Require、Revert和自定义错误 Solana程序是可升级的,并且没有构造函数 Solidity开发者的Rust基础 Rust不寻常的语法 Rust 函数式过程宏 Rust 结构体与属性式和自定义派生宏 Rust 和 Solana 中的可见性与“继承” Solana时钟及其他“区块”变量 Solana 系统变量详解 Solana 日志、“事件”与交易历史 Tx.origin、msg.sender 和 onlyOwner 在 Solana 中:识别调用者 Solana 计算单元与交易费用介绍 在 Solana 和 Anchor 中初始化账户 Solana 计数器教程:在账户中读写数据 使用 Solana web3 js 和 Anchor 读取账户数据 在Solana中创建“映射”和“嵌套映射” Solana中的存储成本、最大存储容量和账户调整 在 Solana 中读取账户余额的 Anchor 方法:address(account).balance 功能修饰符(view、pure、payable)和回退函数在 Solana 中不存在的原因 在 Solana 上实现 SOL 转账及构建支付分配器 使用不同签名者修改账户 PDA(程序派生地址)与 Solana 中的密钥对账户 理解 Solana 中的账户所有权:从PDA中转移SOL Anchor 中的 Init if needed 与重初始化攻击 Solana 中的多重调用:批量交易与交易大小限制 Solana 中的所有者与权限 在Solana中删除和关闭账户与程序 在 Anchor 中:不同类型的账户 在链上读取另一个锚点程序账户数据 在 Anchor 中的跨程序调用(CPI) SPL Token 的运作方式 使用 Anchor 和 Web3.js 转移 SPL Token Solana 教程 - 如何实现 Token 出售 基础银行教程 Metaplex Token 元数据工作原理 使用Metaplex实施代币元数据 使用 LiteSVM 进行时间旅行测试 Solana Token-2022 标准规范 生息代币第一部分 计息代币第二部分 Solana 指令自省 Solana 中的 Ed25519 签名验证 Solana - Switchboard 预言机使用 原生Solana:程序入口与执行 原生 Solana :读取账户数据 原生 Solana :Borsh 序列化 原生 Solana:使用 invoke 和 invoke signed 进行跨程序调用 原生 Solana :创建存储账户 (一) 原生 Solana:创建存储账户 二 原生 Solana: 函数分发 原生 Solana:关键安全检查 Rust 程序到 SBF 编译 sBPF 虚拟机和指令集介绍 跟踪 sBPF 指令执行和计算成本 Solana 程序执行与输入序列化 指令处理器和运行时设置 sBPF 内存布局和寄存器约定 使用 sBPF 汇编读取 Solana 指令输入 Solana 系统调用:sBPF 汇编中的日志记录

原生Solana:程序入口与执行

本文详细介绍了如何在Solana区块链上使用原生Rust编写程序,而非依赖Anchor框架。文章解释了Solana程序的入口点(Entrypoint)和指令处理器(Instruction Processor)的工作原理,通过具体代码示例演示了如何设置项目、编写指令处理逻辑,以及使用entrypoint!宏连接程序入口,并提供了TypeScript客户端进行测试。

原生 Solana:程序入口点与执行

我们进入模块 7 :原生 Solana 程序

在本系列中,我们一直使用 Anchor 框架来构建 Solana 程序。本教程将教你如何使用原生 Rust 而不依赖 Anchor 来编写它们。

你可能希望这样做有几个原因,例如:

  • 低级控制:你可以控制数据如何序列化和反序列化、账户验证以及程序逻辑的结构。
  • 性能:在原生 Rust 中,对于不需要的简单操作,你可以跳过 Anchor 的序列化、反序列化和账户验证步骤,这将导致更少的 compute unit 使用量。
  • 更小的二进制文件大小:没有 Anchor 的开销(额外的 Rust 宏和依赖项)意味着部署的程序更小。
  • 学习:理解底层机制能让你成为一名更好的 Solana 开发者。

到目前为止,我们一直使用 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] 属性宏在幕后自动生成一个程序入口点。这个入口点接收所有传入指令,并根据客户端传递的指令数据将它们分派到你的各个函数(initializeupdate 等)。在原生 Rust 中,我们将使用 Solana SDK 中的 entrypoint! 宏来创建入口点并手动处理分派。

什么是 Solana 中的入口点?

将入口点视为 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 程序

项目设置

我们将构建一个简单的 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 运行时调用的实际入口点代码,反序列...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论