2024 年,Solana 异军突起,TVL 从年初的十亿美元飙升至如今的近五十亿美元,一跃成为第 4 大公链。
与 Ethereum 相比,Solana 以更快的速度和更低廉的费用为用户带来了更为优越的体验。其基于 POH 的共识机制和异步的交易执行模式为开发者提供了高吞吐量和低延迟的区块链性能,成为各种去中心化应用的首选平台。
BlockSec 特别策划了《玩转 Solana》系列文章,涵盖 Solana 基本概念、查看和分析 Solana 交易实用指南以及编写 Solana 智能合约教程等干货内容。
作为该系列的第 1 期,本文将深入介绍 Solana 网络中的关键概念,包括其运行机制,账户模型和交易,为大家编写出正确且高效的 Solana 合约打下基础。
eBPF: Solana 交易的执行基石
为了编写和执行智能合约,区块链往往需要一套编程语言和图灵完备的计算环境。
熟悉 Ethereum 的朋友们应该知道,以太坊上的智能合约通常使用高级语言 Solidity 来编写,而 Solidity 编译产生的字节码则运行在一个叫做以太坊虚拟机的环境中。
Solana 并没有选择开发全新的虚拟环境和语言,而是充分利用了现有的优秀技术。原本用于拓展 Linux 内核功能的 eBPF(extended Berkeley Packet Filter)虚拟机被 Solana 选中并作为底层的执行环境。
那么,eBPF 相对于 EVM 有哪些优势?
相较于只支持解释执行的 EVM,eBPF 能够以即时编译(JIT)模式直接将字节码转换成处理器可以直接执行的机器指令,从而更高效地运行程序。
eBPF 拥有一套高效的指令集和成熟的基础设施。开发者只需要使用 Rust 语言即可编写智能合约。LLVM 编译框架提供了一个eBPF的后端,利用它可以直接将这些 Rust 语言编写的程序编译成可运行在eBPF虚拟机上的字节码。
Solana 的账户模型
1. Solana 账户结构
Solana 上的数据以账户的形式存储。如下图所示,我们可将 Solana 中的所有数据视作一个庞大的键值对数据库。数据库的键是账户的地址,对于“钱包”账户(即由
Solana 用户通过公私钥对直接控制的账户)而言,这个地址是使用 Ed25519 签名系统生成的公钥;而数据库的值是该账户的具体信息,包含余额和其它相关信息。
Solana 使用名为 AccountInfo 的结构来描述一个账户,其组成如下图所示。
Solana 中的每个账户均包含四个字段。这里我们对其进行逐一解释。
Data 字段存储了该账户有关的数据。如果该账户为一个程序(即智能合约),则它存储的其实就是 eBPF 字节码。否则,Data 中信息格式一般由账户创建者自行定义。
Executable 字段用于标识该账户是否为程序。需要注意的是,与以太坊不同,Solana 中的程序是可以更新的。
Lamports 字段记录了该账户 Solana 代币的余额。Lamports 实际上是 SOL Token 的最小单位(1 SOL = 10 亿 Lamports)。
Owner 字段指示了当前账户的拥有者。在 Solana 中,任何一个账户都有一个“Owner”。例如,所有“钱包”账户的拥有者都是 System Program,这是 Solana 网络上的一个特殊账户,负责账户创建等功能。账户拥有者是唯一能够修改账户数据和扣除 Lamports 余额的人(但任何人都可以增加 Lamports,即向账户执行转账功能)。
2. 预定义的 Solana 账户
Solana 拥有一套称作Native Programs的预定义运行程序,它们被部署在固定的地址上。随着 Solana 网络的升级,这些预定义的程序也可能会被更新。我们可以将这些程序理解成 Solana 网络下提供特定功能的 API 和库函数。
在 Native Programs 中,开发者经常需要与之交互的一个程序是System Program。System Program 为开发者提供了一些指令(Instructions),我们可以把每条指令理解成是一个独立的方法。例如,开发者可使用 CreateAccount 指令来创建新的账户,或者使用 Transfer 指令将 Lamports 转账给其它账户。
另外一个常见的 Native Programs 是 BPF Loader 程序。它是所有其它程序账户的拥有者,其负责部署、更新和执行特定的程序。当一个“钱包”账户需要更新它部署过的程序时,实际上就是通过委托 BPF Loader 程序来完成的,毕竟只有程序的拥有者才有直接权限修改数据。
除了 Native Programs,Solana 还提供了一组被称作Sysvar的账户。它们为 Solana 上的程序提供了与当前 Solana 网络状态相关的信息和全局变量,例如当前的时钟,最近的区块哈希等。
3. 账户租金
在 Solana 链上,每个账户需要保持一定数量的 Lamports 作为最低额度,这被称为租金。与现实生活中的租金概念不同,Solana 上的租金是可以收回的。为了确保账户在链上的数据是可用的,账户需要持有相应数量的 Lamports。租金的数额与账户在链上存储空间的大小相关。
任何试图将账户余额扣减至低于租金数额的交易都会失败,除非这笔交易直接将账户的余额扣减至零。这种操作表明该账户的租金已被收回,在交易执行结束时,Solana 会通过垃圾回收清空相应账户的存储空间。
- 🧐 在浏览器中查看「Solana 账户」
为了带领大家更好地理解相关概念,我们使用 Solana 提供的“Hello World”项目创建了一个程序账户,可以通过 Solana 的区块链浏览器 Solscan 来查看以下账户👇的相关信息。
CJWhxB4qEWBv9eGYUkTN881bNDMDkLbzH1FmdwqLLhoe
如上图所示,我们首先可以看到,该账户已被 Solana 浏览器标明为“Program”。在创建该账户时从发送者的余额里扣除了一部分 Lamports 作为该账户的租金,故而我们可以看到其 SOL Balance 字段不为空。
其次,由于我们创建的是一个程序,其 Executable 字段为 Yes。这里可能有一个难以理解的地方,那就是读者也许会发现 Data 字段存储的是一个地址而非 eBPF 程序。我们在前面提到过,Solana 允许更新程序,而它实际上是通过一种“代理”模式来实现的。由于 Solana 并不允许直接修改程序账户,所以它创建了一个数据账户用来存储 eBPF 程序,而在程序账户的 Data 字段只存储数据账户的地址。每当需要更新程序时,就只需修改数据账户中的 Data 字段。我们用 Solscan 查看 Executable Data 字段的账户可以发现它被标记为“Program Executable Data Account”,其 Data 字段存储了实际的程序:
回到上一张图片,我们可以发现在 More info 中 Owner 字段为 BPF Loader,这与我们在上一节中的描述是一致的。
在 Overview 中还有一个名为“Upgrade Authority”的字段,它的含义是什么呢?
正如我们前面提到的,“钱包”账户是通过委托 BPF Loader 来更新程序的,而在更新之前,BPF Loader 需要验证委托者是否拥有更新的权限。由于程序账户的 Owner 字段已经是 BPF Loader,其自身已经没有空间来存储该信息了,因此 Solana 选择把这个信息存储在数据账户的 Data 字段中,这个信息实际上就是部署程序的钱包地址,也就是这里的“Upgrade Authority”。下图展示了程序账户与数据账户之间的关系,可以看到数据账户的 Data 字段由钱包地址和 eBPF 代码两部分信息组成。
Solana 的交易和指令
在 Solana 中,用户同样通过签发交易(Transactions)来执行程序。其特别之处在于,Solana 能够并行执行这些交易,这也是其能够提供闪电般交易速度的重要原因。接下来我们来看看 Solana 的交易是如何设计的。
一笔 Solana 交易由签名和消息主体组成。一笔交易可包含多个签名。交易的消息主体由四个部分组成,如下图所示。
消息的头部信息(Header)和账户地址数组(Compact array of account addresses)两个字段指定了交易涉及的所有账户以及账户在交易中的特征:包括该账户是否提供了签名以及执行过程中是否会被写入。利用这些信息,Solana 能够验证相应账户提供的签名,并且能够并行地执行那些不触碰相同账户集合的交易。
最近的区块哈希(Recent Blockhash)是交易的时间戳。Solana 网络会确保交易来自于最近的 150 个区块,否则交易会被认为过期从而不被执行。
指令数组(Compact array of Instructions)是交易中最重要的部分,包含了一条或多条指令。一条指令实际上是对某个程序提供的一段例程的调用。指令由三个字段组成,如下图所示:
第一个字段 Program ID Index 指定了指令的接收者,即需要处理该指令的链上程序。它不直接存放一个 32 字节的地址,而是将该地址放在消息主体中的账户地址数组中。该字段使用一个字节的下标指明其在数组中的位置,实现了一种空间复用。
和第一个字段类似,第二个字段是由账户地址下标组成的数组(Compact array of account address indexes),它指明了处理该指令涉及到的所有账户。
最后一个字段是一段字节数组,它是程序处理该指令需要的额外信息,可以把它理解成函数的参数。
需要注意的是,Solana 会按照顺序处理交易中的所有指令,并确保交易的执行是原子的。这意味着一笔交易中的指令要么全部失败,要么全部成功执行,不会出现部分指令成功执行而部分失败的情况。
- 🧐 在浏览器中查看「Solana 交易」
我们使用另外一个 Solana 浏览器来查看前面创建程序账户的交易👇。在 Overview 中能够看到 Solana 交易的签名、最近区块哈希等信息:
3uKQ85Lpsnwb5D6CgUntoMyJX3tSaeGb4pjUoMaMyNVqQNPp5PRG1kJEEEk3YNdWLYEMZGmoJ5Rowgon8hZzwL9D
而在 Account Input 中则列出了当前交易涉及到的所有账户以及相关账户在交易中的特征。我们可以看到除了发送者、程序账户等地址外,两个 Native Programs 和 Sysvar 账户也被包含进去了。
由于该交易是一个简单的程序创建交易,所以它只包含了两条指令,第一条指令的接收者是System Program,负责创建程序账户;第二条指令的接受者则是BPF Loader,负责将实际部署的 eBPF 代码写入到数据账户中,并将其地址写入到程序账户的 Data 字段。
总结
Solana 上的智能合约采用 Rust 语言开发,并在 eBPF 虚拟机上运行。它遵循账户模型,链上的账户需要维持租金才能保证数据的可用性。交易由一条或多条指令组成,明确定义了依赖的所有账户,从而使得交易能够被并行处理,提高了吞吐量并降低了响应延迟。这些特点共同促进了 Solana 的快速发展,使其成为备受青睐的区块链平台之一。
Twitter:https://twitter.com/BlockSecTeam
Phalcon: https://phalcon.xyz/
MetaSleuth: https://metasleuth.io/
MetaSuites: https://blocksec.com/metasuites