引言
2024年,Solana 异军突起,其总锁仓价值(TVL)从年初的10亿美元飙升至近50亿美元,成为第四大公链。
与以太坊相比,Solana 为用户提供了更快的速度和更低的成本,体验更佳。其历史证明(Proof of History)共识机制和异步交易处理模型为开发者提供了高交易吞吐量和低延迟,使其成为各类去中心化应用的优选平台。
BlockSec 特别策划了 “Solana 极简入门” 系列,涵盖 Solana 的基础概念、Solana 交易分析实操指南,以及 Solana 智能合约编写教程。
作为本系列的开篇之作,本文将深入探讨 Solana 网络内的关键概念,包括其运行机制、账户模型和交易,为编写正确高效的 Solana 智能合约奠定基础。
eBPF:Solana 交易执行的基石
要编写和执行智能合约,区块链通常需要一种编程语言和一个图灵完备的计算环境。
以太坊上的智能合约通常使用一种名为 Solidity 的高级语言编写。编译器将其翻译成字节码,然后由以太坊虚拟机(EVM)执行。Solana 没有开发全新的虚拟机和语言,而是选择充分利用现有的先进技术。eBPF(extended Berkeley Packet Filter)虚拟机,最初是为扩展 Linux 内核功能而设计的,已被 Solana 选作其底层执行环境。
那么,eBPF 相较于 EVM 有何优势?
与仅限于解释执行的 EVM 不同,eBPF 支持即时(Just-In-Time, 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 Program 是 BPF Loader 程序。它是所有其他程序账户的所有者,负责部署、更新和执行自定义程序。当一个“钱包”账户需要更新它部署的程序时,实际上是通过委托给 BPF Loader 程序来完成的,因为只有程序的所有者才有直接修改数据的权限。
除了 Native Programs,Solana 还提供了一组称为 Sysvars 的账户。这些账户为 Solana 上的程序提供有关 Solana 网络当前状态的信息和全局变量,例如当前时钟和最近的区块哈希。
账户租金
在 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 的快速发展,使其成为最受欢迎的区块链之一。



