简介
2024年,Solana 强势崛起,其总锁仓量(TVL)从年初的十亿美元飙升至近五十亿美元,成为第四大公链。
与以太坊相比,Solana 以更快的速度和更低的成本为用户提供了更优越的体验。基于历史证明的共识机制和异步交易处理模型为开发者提供了高交易吞吐量和低延迟,使其成为各类去中心化应用的热门平台。
BlockSec 特别策划了 《Solana 简明指南》 系列,涵盖 Solana 基础概念、Solana 交易分析实践指南以及 Solana 智能合约编写教程。
作为本系列的第一篇文章,本文将深入探讨 Solana 网络中的关键概念,包括其运行机制、账户模型和交易,为在 Solana 上编写正确且高效的智能合约奠定基础。
eBPF:Solana 交易执行的基石
为了编写和执行智能合约,区块链通常需要一种编程语言和一个图灵完备的计算环境。
以太坊上的智能合约通常使用一种名为 Solidity 的高级语言编写,编译器将其转换为字节码,然后在以太坊虚拟机(EVM)中执行。Solana 并未另起炉灶开发全新的虚拟环境和语言,而是选择充分利用现有的先进技术。eBPF(扩展伯克利数据包过滤器)虚拟机最初是为扩展 Linux 内核功能而设计的,被 Solana 选为其底层执行环境。
那么,eBPF 相比 EVM 有哪些优势?
与仅限于解释执行的 EVM 不同,eBPF 支持即时(JIT)编译,能够将字节码直接翻译为处理器可执行的机器指令,这一特性显著提升了程序执行效率。
此外,eBPF 拥有高效的指令集和成熟的基础设施。开发者可以使用 Rust 语言编写智能合约,借助 LLVM 编译器框架的 eBPF 后端,Rust 程序可以直接编译为 eBPF 字节码。
Solana 的账户模型
Solana 账户结构
数据在 Solana 上以账户的形式存储。如下图所示,Solana 中的所有数据可以概念化为一个庞大的键值数据库。该数据库中的键是账户地址。对于"钱包"账户(即用户通过公私钥对直接控制的账户),这些地址是使用 Ed25519 签名系统生成的公钥。数据库中的值由每个账户的具体详情组成,包括余额和其他相关信息。
Solana 使用以下名为 AccountInfo 的结构来描述账户。
每个账户的 AccountInfo 包含四个字段,以下是对各字段的说明:
-
Data字段:该字段存储与账户相关的数据。如果账户是程序(即智能合约),则存储 eBPF 字节码;否则,数据格式通常由账户创建者自行定义。 -
Executable字段:该字段用于指示账户是否为程序。需要注意的是,与以太坊不同,Solana 中的程序是可以更新的。 -
Lamports字段:该字段记录账户中 Solana 代币的余额。Lamports 实际上是 SOL 代币的最小单位(1 SOL = 10 亿 Lamports)。 -
Owner字段:该字段表示账户的所有者。在 Solana 中,每个账户都有一个"所有者"。例如,所有"钱包"账户的所有者是系统程序,这是 Solana 网络上负责账户创建等功能的特殊账户。账户所有者是唯一能够修改账户数据和从余额中扣除 Lamports 的实体(但任何人都可以通过转账增加账户的 Lamports)。
Solana 预定义账户
Solana 有一组预定义的可执行程序,称为 Native Programs,它们部署在固定地址。随着 Solana 网络升级,这些预定义程序也可以更新。它们充当 API 和库函数,在 Solana 网络中提供特定功能。
在 Native Programs 中,开发者经常交互的是 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 的快速发展,使其成为备受青睐的区块链之一。



