简介
2024 年,Solana 强势崛起,其总锁仓价值(TVL)从年初的 10 亿美元激增至近 50 亿美元,一跃成为全球第四大公链。
相比以太坊(Ethereum),Solana 凭借更快的速度和更低的成本为用户提供了卓越的体验。其基于历史证明(Proof of History)的共识机制和异步事务处理模型,为开发者提供了高吞吐量和低延迟的性能,使其成为各种去中心化应用的首选平台。
BlockSec 特别策划了 “Solana 极简入门”(Solana Simplified) 系列,内容涵盖 Solana 的基本概念、分析 Solana 交易的操作指南,以及编写 Solana 智能合约的进阶教程。
作为该系列的第一篇文章,本文将深入探讨 Solana 网络中的核心概念,包括其运行机制、账户模型和交易流程,为编写正确且高效的 Solana 智能合约打下坚实基础。
eBPF:Solana 交易执行的基石
为了编写和执行智能合约,区块链通常需要一种编程语言和一个图灵完备的计算环境。
以太坊上的智能合约通常使用名为 Solidity 的高级语言编写。编译器将其转换为字节码,然后在以太坊虚拟机(EVM)中运行。Solana 没有选择开发全新的虚拟环境,而是充分利用了现有的先进技术。Solana 选择使用最初为扩展 Linux 内核功能而设计的 eBPF(扩展伯克利数据包过滤器)虚拟机,作为其底层的执行环境。
那么,相比 EVM,eBPF 有什么优势呢?
与仅限于解释执行(interpreted execution)的 EVM 不同,eBPF 支持即时(JIT)编译,能够将字节码转换为处理器可以直接执行的机器指令。这种能力显著提高了程序的执行效率。
此外,eBPF 具备高效的指令集和成熟的基础设施。开发者可以使用 Rust 语言开发智能合约。通过利用 LLVM 编译器框架的 eBPF 后端,Rust 程序可以直接编译为 eBPF 字节码。
Solana 的账户模型
Solana 账户结构
在 Solana 上,数据是以账户(Account)的形式存储的。如下图所示,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 网络提供特定的功能。
在原生程序中,开发者最常交互的是 System Program(系统程序)。系统程序为开发者提供了一组指令,每条指令负责执行一项独立的任务。例如,开发者可以使用 CreateAccount 指令来创建新账户,或使用 Transfer 指令向其他账户转账。
另一个常见的原生程序是 BPF Loader 程序。它是所有其他程序账户的所有者,负责自定义程序的部署、更新和执行。当一个“钱包”账户需要更新其所部署的程序时,实际上是通过委托给 BPF Loader 程序来完成的,因为只有程序的所有者才拥有修改数据的直接权限。
除了原生程序,Solana 还提供了一组称为 Sysvars(系统变量)的账户。这些账户为 Solana 上的程序提供与当前网络状态相关的信息和全局变量,例如当前时间戳和最新的区块哈希。
账户租金(Rent)
在 Solana 区块链上,每个账户都必须维持一定数量的 Lamports 作为最低余额,这被称为“租金”。与现实生活中的租金不同,Solana 上的租金是可以收回的。为了确保账户中的数据在链上可用,账户必须持有足量的 Lamports。租金金额与账户占用的数据大小有关。
任何试图将账户余额降至租金金额以下的交易都会失败(除非余额降为零)。余额降为零表示账户租金已被收回,在交易结束时,Solana 将在垃圾回收过程中清理相应账户的数据。
🧐 在 Solana 浏览器中查看 Solana 账户
为了更好地理解上述概念,我们使用了 Solana 的 “Hello World”项目 部署了一个程序账户,该账户可以通过 Solana 区块链浏览器 Solscan 查看。

如上图所示,我们可以首先看到该账户被标记为“Program”(程序)。账户租金从发送者的余额中扣除,因此 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 会验证委托者是否为最初部署该程序的账户。由于程序账户的所有者已经设置为 BPF Loader,它没有空间存储这一信息,因此 Solana 将其放入了数据账户的 Data 字段中。这就是为什么在“Overview”中会出现 Upgrade Authority 字段,它实际上就是部署该程序的钱包地址。
下图展示了程序账户与数据账户之间的关系。请注意,数据账户的 Data 字段既包含钱包地址,也包含 eBPF 代码。

Solana 中的交易与指令
在 Solana 中,用户通过发起交易来执行程序。Solana 的一个独特之处在于其并行执行交易的能力,这也是其交易速度极快的主要原因。现在让我们详细看看 Solana 的交易是如何设计的。
Solana 交易由签名(Signatures)和消息(Message)组成。一个交易可以包含多个签名。交易的消息由四个部分组成,如下图所示。

Header(头部)和 Compact Array of Account Addresses(账户地址紧凑数组)指定了交易中涉及的所有账户及其特征,包括账户是否为签名者,以及在执行过程中是否可写入。通过这些信息,Solana 可以验证签名者提供的签名,并并行处理交易——前提是这些交易不包含任何写入同一状态的账户。
Recent Blockhash(最近的区块哈希)充当交易的时间戳。如果交易的区块哈希比最新区块哈希旧 150 个区块,则该交易被视为过期,不会被执行。
Compact Array of Instructions(指令紧凑数组)是交易中最重要的部分,包含一条或多条指令。每条指令实际上都会触发程序账户提供的程序逻辑执行。每条指令由三个字段组成,如下图所示。

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

在“Account Input”(账户输入)部分,列出了当前交易涉及的所有账户及其交易特性。可以看到,除了发送者和程序账户地址外,还包括了两个“原生程序”和“系统变量”账户。

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

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



