引言
2024 年,Solana 强势崛起,其总锁仓价值 (TVL) 从年初的 10 亿美元飙升至近 50 亿美元,成为第四大公链。
与以太坊相比,Solana 为用户提供了更快的速度和更低的成本,带来了卓越的体验。其基于历史证明的共识机制和异步交易处理模型,为开发者提供了高交易吞吐量和低延迟,使其成为各类去中心化应用的青睐平台。
BlockSec 特别策划了 “Solana 简化” 系列,内容涵盖 Solana 的基本概念、Solana 交易分析实操指南以及 Solana 智能合约编写教程。
作为本系列的开篇之作,本文将深入探讨 Solana 网络内的关键概念,包括其运行机制、账户模型和交易,为编写正确且高效的 Solana 智能合约奠定基础。
eBPF:Solana 交易执行的基石
要编写和执行智能合约,区块链通常需要一种编程语言和一种图灵完备的计算环境。
以太坊上的智能合约通常用一种称为 Solidity 的高级语言编写。编译器将其翻译成字节码,然后由以太坊虚拟机 (EVM) 执行。Solana 没有开发全新的虚拟机和语言,而是选择充分利用现有的先进技术。eBPF (extended Berkeley Packet Filter) 虚拟机,最初是为了扩展 Linux 内核功能而设计的,已被 Solana 选作其底层执行环境。
那么,eBPF 相较于 EVM 有何优势?
与仅限于解释执行的 EVM 不同,eBPF 支持即时 (JIT) 编译,能够将字节码转换为处理器可直接执行的机器指令。这种能力极大地提高了程序的效率。
此外,eBPF 拥有高效的指令集和成熟的基础设施。开发者可以使用 Rust 语言编写智能合约。利用 LLVM 编译器框架的 eBPF 后端,Rust 程序可以直接编译成 eBPF 字节码。
Solana 的账户模型
Solana 账户结构
Solana 上数据的存储形式为账户。如下所示,Solana 内的所有数据都可以看作一个巨大的键值数据库。该数据库的键是账户地址。对于“钱包”账户(即直接由 Solana 用户通过公私钥对控制的账户),这些地址是使用 Ed25519 签名系统生成的公钥。数据库的值则包含每个账户的具体信息,包括余额和其他相关信息。
Solana 使用以下名为 AccountInfo 的结构来描述一个账户。
每个账户的 AccountInfo 包含四个字段。以下是各字段的解释:
-
Data字段:此字段存储与账户相关的数据。如果账户是一个程序(即智能合约),则存储 eBPF 字节码。否则,数据的格式通常由账户创建者定义。 -
Executable字段:此字段用于指示该账户是否为程序。需要注意的是,与以太坊不同,Solana 中的程序是可以更新的。 -
Lamports字段:此字段记录账户中 Solana 代币的余额。Lamports 实际上是 SOL 代币的最小单位(1 SOL = 10 亿 Lamports)。 -
Owner字段:此字段指示账户的所有者。在 Solana 中,每个账户都有一个“所有者”。例如,所有“钱包”账户的所有者是 System Program,这是 Solana 网络上的一个特殊账户,负责账户创建等功能。账户所有者是唯一能够修改账户数据并从余额中扣除 Lamports 的实体(尽管任何人都可以通过转账向账户增加 Lamports)。
预定义的 Solana 账户
Solana 拥有一组预定义的、部署在固定地址的可执行程序,称为 Native Programs。随着 Solana 网络的升级,这些预定义的程序也可以进行更新。它们作为 API 和库函数,在 Solana 网络内提供特定的功能。
在 Native Programs 中,开发者经常与之交互的是 System Program。System Program 为开发者提供了一组指令,每条指令都执行一项独立任务。例如,开发者可以使用 CreateAccount 指令创建新账户,或使用 Transfer 指令将 Lamports 转账给其他账户。
另一个常见的 Native Programs 是 BPF Loader 程序。它是所有其他程序账户的所有者,负责部署、更新和执行自定义程序。当一个“钱包”账户需要更新其已部署的程序时,实际上是通过委托给 BPF Loader 程序来完成的,因为只有程序的所有者才有直接修改数据的权限。
除了 Native Programs,Solana 还提供了一组称为 Sysvars 的账户。这些账户为 Solana 上的程序提供与 Solana 网络当前状态相关的信息和全局变量,例如当前时钟和最新的区块哈希。
账户租金 (Account Rent)
在 Solana 区块链上,每个账户都必须保持一定数量的 Lamports 作为最低余额,称为租金。与现实生活中的租金不同,Solana 上的租金是可以收回的。为了确保账户中的数据在链上可用,账户必须持有适量的 Lamports。租金数额与账户占用的数据大小有关。
任何试图将账户余额减少到低于租金数额的交易都会失败,除非将其余额减少到零。将余额降至零表示账户的租金已被收回,并在交易结束时,Solana 将在垃圾回收过程中清除该账户的相应数据。
🧐 在 Solana 浏览器中查看 Solana 账户
为了更好地理解上述概念,我们使用了 Solana 的"Hello World"项目部署了一个程序账户,可以使用 Solana 的区块链浏览器 Solscan 进行查看。

如上图所示,我们首先可以看到该账户被标记为“Program”。从发送者的余额中扣除了一部分 Lamports 作为该账户的租金,因此 SOL Balance 字段不为空。此外,由于我们创建的账户是程序,其 Executable 字段设置为 Yes。
你可能会困惑 Executable Data 字段存储的是一个地址,而不是 eBPF 程序。如前所述,Solana 允许程序更新,这实际上是通过“代理”模式实现的。由于程序账户本身不能直接修改,Solana 会创建一个单独的数据账户来存储 eBPF 程序,而程序账户中的 Data 字段只存储该数据账户的地址。
每当需要更新程序时,只需修改数据账户的 Data 字段。使用 Solscan 查看数据账户,我们可以发现它被标记为“Program Executable Data Account”,其 Data 字段存储着实际的程序。

“More Info”部分中的 Owner 字段是 BPF Loader,这与我们之前提到的信息一致。
有人可能会注意到“Overview”的最后一个字段是 Upgrade Authority,它不在 AccountInfo 中。这是什么意思?
如前所述,“钱包”账户将程序更新委托给 BPF Loader。在更新之前,BPF Loader 会验证委托者是否是最初部署程序的账户。由于程序账户的 Owner 已经被设置为 BPF Loader,它没有空间来存储这些信息。因此 Solana 将其放入数据账户的 Data 字段。这就是为什么“Overview”中存在 Upgrade Authority 字段,它实际上是部署程序的钱包地址。
下图显示了程序账户和数据账户之间的关系。请注意,数据账户的 Data 字段包括钱包地址和 eBPF 代码。

Solana 中的交易和指令
在 Solana 中,用户通过发出交易来执行程序。Solana 的一个独特之处在于其能够并行执行这些交易,这是其闪电般快速交易速度的关键原因。现在,让我们仔细看看 Solana 中交易的设计。
Solana 交易由签名和消息组成。一笔交易可以包含多个签名。交易的消息由四部分组成,如下所示。

Header 和 Compact Array of Account Addresses 指定了交易中涉及的所有账户及其在交易期间的特征,包括账户是否为签名者以及在执行期间是否可写。利用这些信息,Solana 可以验证签名者账户提供的签名,并并行处理交易,只要这些交易不包含对同一状态进行写入的账户。
Recent Blockhash 作为交易的时间戳。如果一笔交易的区块哈希比最新区块哈希旧 150 个区块,则该交易被视为过期,将不会被执行。
Compact Array of Instructions 是交易中最重要的部分,包含一个或多个指令。指令本质上会触发一个程序账户提供的例程的执行。每条指令包含三个字段,如下所示。

第一个字段 Program ID Index 指定了指令的接收者,即需要处理该指令的链上程序。该地址不直接存储 32 字节的地址,而是放置在交易消息的 Compact Array of Account Addresses 中,该字段仅存储一个 u8 索引,指向数组中的地址。
与第一个字段类似,第二个字段存储账户地址索引,称为 Compact Array of Account Address Indexes。该数组指定了此指令涉及的所有账户。
最后一个字段是一个字节数组,包含程序处理指令所需的附加数据,例如函数参数。
需要注意的是,Solana 按照顺序处理交易中的所有指令,并保证交易的原子性执行。这意味着交易要么全部成功完成,所有指令都得到处理,要么完全失败。不会出现部分指令被处理而其他指令未被处理的情况。
🧐 在 Solana 浏览器中查看 Solana 交易
我们使用另一个 Solana 浏览器来查看之前创建程序账户的交易。在“Overview”部分,您可以看到 Solana 交易签名、最近的区块哈希和其他信息:

在“Account Input”部分,列出了当前交易中涉及的所有账户及其在交易中的特征。我们可以看到,除了发送者和程序账户地址外,还包含了两个 Native Programs 和 Sysvar 账户。

由于这是一笔简单的程序创建交易,它只包含两条指令。第一条指令的接收者是 System Program,负责创建程序账户。第二条指令的接收者是 BPF Loader,它创建了一个数据账户来存储部署的 eBPF 代码,并将该数据账户的地址写入程序账户的 Data 字段。

结论
Solana 上的智能合约使用 Rust 开发,并在 eBPF 虚拟机上运行。Solana 遵循账户模型,链上账户需要保持足够的租金以避免数据被移除。交易由一个或多个指令组成,这些指令定义了所有必需的账户,从而实现并行处理,提高吞吐量,同时降低响应延迟。这些特性共同促进了 Solana 的快速发展,使其成为最受欢迎的区块链之一。



