---
title: "什么是 Solana 虚拟机(SVM),它是如何工作的?"
description: "深入解析支撑 Solana 的架构,以及它对开发者的意义。"
---

# 什么是 Solana 虚拟机(SVM),它是如何工作的?

<ImageBlock
  src="https://media.alchemy.com/1766069216-blog-solana-virtual-machine-1.png"
  alt="虚拟机的可视化展示"
  width={1920}
  height={900}
  priority
/>

Solana 已成为加密领域使用最活跃的区块链之一,按日活跃地址数和交易量计算持续排名前五。仅在 2025 年 10 月,Solana 网络就处理了约[7000 万笔日交易,促成了 1430 亿美元](https://liquidityfinder.com/news/solanas-transaction-volume-and-revenue-surpass-all-major-layer-1-blockchains-c3df7)的 DEX 交易量,峰值时期的吞吐量足以让大多数其他链陷入停滞。

实现这一点的原因不仅仅是快速共识或强悍的硬件要求,Solana 性能的核心在于一个根本上不同的执行引擎:Solana 虚拟机(SVM)。

Solana 虚拟机是一个基于寄存器的运行时,构建在 eBPF 字节码之上,通过要求预先声明所有状态依赖关系来并行执行交易。与[以太坊虚拟机](https://www.alchemy.com/overviews/what-is-the-ethereum-virtual-machine-evm)的顺序交易模型不同,SVM 从一开始就是围绕并行执行设计的,能够跨 CPU 核心同时处理数千笔交易,而不是逐笔处理。

这一架构选择影响到方方面面:程序如何存储状态、交易如何构造、费用如何计算,以及开发者如何思考构建链上应用。

本指南从基本原理出发拆解 SVM。我们将从核心组件入手,解释它们各自的工作方式,然后展示它们如何组合起来实现 Solana 的性能。读完之后,你不仅会理解 SVM _做什么_,还会理解它 _为什么_ 是这样构建的。

## 什么是虚拟机?

在深入探讨 SVM 之前,先来搞清楚虚拟机究竟是什么,以及区块链为什么需要它们。

虚拟机(VM)是运行在另一台计算机内部的基于软件的计算机。它提供了一个受控环境,让代码可以执行而无需直接访问宿主系统的资源。VM 无处不在:Java 虚拟机运行 Java 字节码,你的浏览器在 VM 中运行 JavaScript,云服务器通常也运行在 VM 内部。

区块链需要 VM 有一个具体原因:对不可信代码进行确定性执行。当你部署一个智能合约时,全球数千个验证者需要运行这段代码并得出 _完全一致_ 的结果。VM 提供:

- **沙箱化**:不可信代码无法访问宿主系统或其他程序的内存
- **确定性**:相同输入始终产生相同输出,与硬件无关
- **计量**:执行过程可被衡量和限制(gas/计算单元),以防止滥用
- **可移植性**:代码在不同机器和操作系统上运行结果一致

**以太坊虚拟机(EVM)**是第一个被广泛采用的区块链 VM。它使用基于栈的架构,顺序执行交易,并将代码和状态一起存储在合约中。它能work,但其设计选择带来了根本性的吞吐量限制。

**Solana 虚拟机(SVM)**采取了不同的方法。它使用基于寄存器的架构(更类似于物理 CPU),将代码与状态分离,最重要的是,它并行执行交易。

## 什么是 Solana 虚拟机(SVM)?

从核心来看,SVM 是 Solana 的执行引擎,负责运行程序逻辑、处理交易和更新状态的系统。如果你来自以太坊生态,可以把它理解为 Solana 对 EVM 的回应,但在架构上有几处关键的不同。

要理解 SVM,首先需要理解它操作的对象:

- **账户(Accounts)**:Solana 的通用数据结构。一切都是账户:用户钱包、代币余额、程序状态,甚至程序本身。账户持有数据并有一个所有者(被允许修改它们的程序)。与将代码和存储捆绑在一起的 EVM 合约不同,Solana 将两者完全分离,所有状态都存在于账户中。
- **程序(Programs)**:这是 Solana 对智能合约的称呼。程序是 _无状态_ 的:它们只包含可执行代码,编译为一种称为 sBPF 的字节码格式。大多数 Solana 程序用 Rust 编写(尽管也支持 C 和 C\+\+)。程序运行时,会读写传入的账户,但内部不存储任何东西。
- **交易(Transactions)**:一个或多个指令的集合,每个指令针对一个程序,并指定它需要读取或写入哪些账户。这种对状态依赖关系的预先声明是一切的关键,也是并行执行得以实现的原因。

有了这些基础,理解 SVM 最简单的方式是:它是一个沙箱化的运行时环境,接收编译后的程序字节码,针对一组账户执行它,并确定由此产生的状态变化。Solana 上的每一笔交易都要经过 SVM。

SVM 与 EVM 的区别不仅仅是实现细节——而是基础设计的不同。SVM 从一开始就是围绕并行执行构建的。交易预先声明其状态依赖关系,使运行时能够识别不冲突的工作,并跨 CPU 核心并发处理。EVM 是顺序处理交易的;SVM 则是同时处理数千笔。

_关于术语的说明:"SVM"一词根据上下文有不同含义。狭义定义特指执行程序代码的字节码解释器和 JIT 编译器(稍后我们会介绍字节码格式 sBPF)。广义定义是 Anza(Solana 的核心验证者团队)官方使用的定义,涵盖整个交易执行管线:调度、计算预算、程序加载、执行和状态更新。开发者说"SVM"时,通常指的是这个更广义的系统。本指南两者都会涵盖,我们会在讲解过程中说明具体所指。_

## 理解 SVM 架构

现在我们已经知道 SVM _是什么_,以及它与其他区块链 VM 有何不同,接下来理解它 _实际上是如何工作的_。本节将完整走一遍架构流程,从交易到达的那一刻到最终的状态变化。

我们将涵盖四个相互关联的部分:

1. 交易如何在系统中流转(管线)
1. 运行你代码的执行引擎(eBPF、编译、VM 本身)
1. 程序操作的数据模型(账户、PDA、rent、CPI)
1. 并行执行,以及 Sealevel 如何使其成为可能

每一部分都建立在前一部分的基础上。读完之后,你不仅会理解各个组件,还会理解它们如何组合在一起,实现 Solana 的性能特性。

## 第一部分:交易如何在 SVM 中流转

理解 SVM 的第一步是追踪一笔交易的旅程,从你点击"发送"的那一刻到区块确认。这条管线解释了 _为什么_ Solana 程序是这样结构的,_为什么_ 你必须预先声明账户,以及并行处理 _实际发生在哪里_。

当你向 Solana 提交一笔交易时,它不会简单地进入一个队列排队等待。它会流经一系列专门的子系统,每个子系统解决一个具体问题:

<CodeSnippet
  language="markdown"
  code={`Transaction submitted
        ↓
   Banking Stage
   (conflict detection, parallel scheduling)
        ↓
   The Bank
   (state snapshot for this slot)
        ↓
   BPF Loader
   (provisions sBPF VM instance)
        ↓
   Program executes
   (reads/writes accounts within budget)
        ↓
   State updates committed`}
/>

让我们逐一走一遍每个阶段:

### Banking Stage:并行处理发生的地方

这是交通调度员。当验证过的交易到达时,Banking Stage 会分析每一笔:它需要读取哪些账户?需要写入哪些账户?

有了这些信息,它就能确定哪些交易可以安全地同时运行:

- 涉及完全不同账户的交易 → 并行运行
- 都读取同一账户的交易 → 并行运行(读操作不冲突)
- 都写入同一账户的交易 → 必须顺序运行

调度器使用账户级别的锁来强制执行这些规则。工作线程同时处理不冲突的交易批次。这正是 Solana 并行执行模型的核心,称为 Sealevel(我们会在第四部分深入探讨 Sealevel)。

### Bank:某一时刻的状态

Banking Stage 负责 _调度_,而 Bank 负责 _状态_。可以把它理解为 Solana 在某个特定 slot(Solana 的时间单位,约为 400ms)上整个状态的快照。

Bank 管理账户数据,协调执行,并追踪哪个版本的状态是权威的。每个 Bank 会经历三个生命周期阶段:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Stage ", dataType: "object" },
      { key: "2", width: 200, title: "What's Happening", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>Active</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>正在为该 slot 处理交易</p>",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        "1": { title: "<p>Frozen</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>slot 已完成:不再允许更改</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
      {
        "1": { title: "<p>Rooted</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>已最终确定并提交至持久化存储</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
    ],
  }}
/>

当 Banking Stage 执行交易时,它是针对一个特定的 Bank,也就是世界的一个特定版本进行操作的。这就是 Solana 在并行处理数千笔交易的同时保持一致性的方式。

### BPF Loader:启动程序执行

当一笔交易调用某个程序时,需要有东西真正 _运行_ 那个程序的代码。这就是 BPF Loader 的工作。

BPF Loader 负责处理整个程序生命周期:

- **部署**:将新的程序字节码上传到网络
- **JIT 编译**:将字节码转换为原生机器码以加快执行速度
- **升级**:推送现有程序的新版本
- **执行**:在程序被调用时配置隔离的 VM 实例

每次程序调用都会得到自己独立沙箱化的 sBPF VM 实例,包含:

- 专用内存区域
- 一个计算预算(类似于 gas limit)
- 与其他程序严格隔离

如果程序超出了计算预算?执行终止。试图访问不该访问的内存?执行终止。这种隔离性是设计上刻意做得很严格的。这正是 Solana 能够安全运行来自数千名开发者的不可信代码的方式。

### 典型的交易流程

以下是一笔典型交易的完整流程:

1. 你提交一笔交易,指定要调用哪个程序以及它需要哪些账户
1. Banking Stage 接收该交易,检查它与其他待处理交易是否有冲突,并进行相应调度
1. Bank 为这个 slot 提供当前状态快照
1. BPF Loader 为目标程序配置一个 sBPF VM 实例
1. 程序执行,读取并写入指定的账户
1. 状态变化提交至 Bank
1. 最终,Bank 冻结并被根化(root),使这些变化永久生效

每个组件解决一个具体的问题:Banking Stage 实现并行性,Bank 提供一致的状态,BPF Loader 确保安全执行。它们共同构成了广义上的"SVM"。

想更深入了解交易吗?查看以下资源:

- [交易生命周期](https://solana.com/docs/core/transactions):交易如何被构造和处理
- [Solana 上的费用](https://solana.com/docs/core/fees):计算单元、优先费和预算规划
- [集群与端点](https://solana.com/docs/core/clusters):理解 Solana 的网络架构

## 第二部分:执行引擎

BPF Loader 会配置 VM 实例来运行程序代码。但在该 VM 实例内部,实际发生了什么?

大多数区块链 VM 都是从零设计的。Solana 走了一条不同的路:它采用了 [eBPF](https://ebpf.io/)(extended Berkeley Packet Filter),这项技术最初是为 Linux 内核构建的。

BPF 起源于 1992 年劳伦斯伯克利实验室,用于高效的网络数据包过滤。随着时间推移,它演变为 eBPF,一个通用的、沙箱化的 VM,能在 Linux 内核内部安全运行。如果你用过 [bpftrace](https://github.com/bpftrace/bpftrace) 之类的可观测性工具,或者使用过基于 eBPF 的网络技术,你已经接触过这项技术。它是经过实战检验的基础设施,支撑着大规模的关键系统。

Solana 创始人 Anatoly Yakovenko 在操作系统方面的背景(在高通工作超过 13 年)让他得出了一个关键洞见:既然已有一个 VM 解决了这些难题,为什么还要从零构建一个新的?

eBPF 恰好提供了 Solana 所需要的:

- **无开销的安全性:** eBPF 程序在一个受限环境中运行,不会使宿主系统崩溃或破坏内存。字节码在加载前经过静态验证——不允许非法内存访问、越界跳转或未经授权的操作。这种安全性不带来运行时开销,不像需要垃圾回收的托管运行时那样。
- **接近原生的性能:** 基于寄存器的架构(不同于 EVM 基于栈的设计)使得 JIT 编译到原生机器码成为可能。程序运行速度接近原生水平。
- **成熟的工具链:** LLVM 已经内置了 eBPF 后端。Rust、C、C\+\+ 以及其他 LLVM 支持的语言可以直接编译为 eBPF 字节码。你用自己熟悉的语言编写代码,工具链处理剩下的事情。

### sBPF:Solana 定制的变体

Solana 并没有使用原版 eBPF。它也无法这么做,因为标准 eBPF 是为内核空间操作设计的,存在严格约束,无法映射到区块链执行场景。所以 Solana 对其进行了分叉。

结果就是 sBPF(Solana Bytecode Format),一个针对链上程序执行定制的修改版本:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Feature", dataType: "object" },
      { key: "2", width: 200, title: "Standard eBPF", dataType: "object" },
      { key: "3", width: 200, title: "Solana sBPF", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>Environment</p>", tooltip: "", icon: "" },
        "2": { title: "<p>内核空间</p>", tooltip: "", icon: "" },
        "3": { title: "<p>用户空间</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>Stack size</p>", tooltip: "", icon: "" },
        "2": { title: "<p>512 字节</p>", tooltip: "", icon: "" },
        "3": { title: "<p>每帧 4KB</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>Loops</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>受限(必须可证明终止)</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title: "<p>允许(受计算单元限制)</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        "1": { title: "<p>Custom syscalls</p>", tooltip: "", icon: "" },
        "2": { title: "<p>不支持</p>", tooltip: "", icon: "" },
        "3": {
          title: "<p>支持(日志、CPI、加密操作)</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
    ],
  }}
/>

那些让 eBPF 在内核空间中保持安全的约束——极小的栈空间、禁止循环——如果照搬到 Solana 上会严重限制智能合约开发。sBPF 放宽了这些限制,同时通过计算单元预算维持安全性:你可以循环,但最终会耗尽计算量。

自定义系统调用同样重要。程序需要记录日志、调用其他程序(跨程序调用)以及执行加密操作。标准 eBPF 没有这些概念,而 sBPF 将它们作为一等原语加入进来。

如果你在编译 Solana 程序,会在工具链中看到目标三元组 `sbf-solana-solana`。这就是这个 Solana 专用变体的标识符。

### 从 Rust 到字节码:编译管线

当你运行 `cargo build-sbf` 时,你的 Rust 代码会经过一个多阶段的编译管线:

**1. Rust 前端:** 编译器解析你的代码,展开宏,执行类型检查,并运行借用检查器来验证内存安全性。代码离开这个阶段时,Rust 已经消除了整整一类 bug(悬垂指针、数据竞争、空指针),这些问题困扰着其他系统级语言。这是 Solana 一个未被充分认识到的优势:内存安全在编译时就得到强制执行,早在程序接触链之前。

**2. LLVM 优化:** 代码转换为 LLVM IR,一种与平台无关的表示形式,真正的优化在这里发生:常量折叠、函数内联、死代码消除、循环展开。这些优化很重要,因为计算单元是要花钱的。更小、更紧凑的字节码意味着更便宜的交易。

**3. sBPF 后端:** Solana 对 LLVM BPF 后端的自定义分支将优化后的 IR 转换为 sBPF 字节码,打包为一个 ELF 文件(`.so`)。这就是你部署到网络上的东西——也是程序被调用时 BPF Loader 配置进 VM 实例的内容。

<CodeSnippet
  language="text"
  code={`   Rust source (.rs)
          ↓
   Rust compiler (type checking, borrow checker)
          ↓
   LLVM IR (optimizations)
          ↓
   sBPF bytecode (.so)
          ↓
   Deployed to Solana`}
/>

### sBPF VM 内部

现在我们来到最底层:实际执行你字节码的虚拟机。

sBPF 指令集使用 64 位指令(每条 8 字节),采用类似 RISC 的设计,大约有 100 个操作码。程序可以访问 11 个 64 位寄存器:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Column ", dataType: "object" },
      { key: "2", width: 200, title: "Purpose", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>R0</p>", tooltip: "", icon: "" },
        "2": { title: "<p>返回值</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>R1-R5</p>", tooltip: "", icon: "" },
        "2": { title: "<p>函数参数</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>R6-R9</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>被调用者保存(跨调用保留)</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        "1": { title: "<p>R10</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>帧指针(只读)</p>",
          tooltip: "",
          icon: "",
        },
        id: 4,
      },
    ],
  }}
/>

#### 内存布局

VM 将内存组织为固定区域,每个区域从特定地址开始:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Address", dataType: "object" },
      { key: "2", width: 200, title: "Region", dataType: "object" },
      { key: "3", width: 200, title: "Purpose", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>0x000000000</p>", tooltip: "", icon: "" },
        "2": { title: "<p>.text</p>", tooltip: "", icon: "" },
        "3": { title: "<p>程序代码</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>0x100000000</p>", tooltip: "", icon: "" },
        "2": { title: "<p>.rodata</p>", tooltip: "", icon: "" },
        "3": {
          title: "<p>常量和只读数据</p>",
          tooltip: "",
          icon: "",
        },
        id: 4,
      },
      {
        "1": { title: "<p>0x200000000</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Stack</p>", tooltip: "", icon: "" },
        "3": { title: "<p>每个调用帧 4KB</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>0x300000000</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Heap</p>", tooltip: "", icon: "" },
        "3": { title: "<p>默认分配 32KB</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": { title: "<p>0x400000000</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Input</p>", tooltip: "", icon: "" },
        "3": {
          title: "<p>账户和指令数据</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
    ],
  }}
/>

这种严格的布局让 VM 能够强制执行严格的边界,让你的程序不能意外(或恶意)地访问不该访问的内存。

#### JIT 编译与安全性

sBPF VM 支持两种执行模式:

- **解释器:** 逐条遍历指令。启动快,运行慢。适用于本地测试和调试。
- **JIT 编译:** 在执行前将 sBPF 字节码转换为原生 x86_64 机器码。启动较慢,但运行时速度大幅提升。这是验证者在生产环境中使用的方式。

Agave 验证者会将 JIT 编译后的程序缓存在 `ProgramCacheForTxBatch` 中以便复用。像 Token Program 这类高频使用的程序不会在每笔交易上重新编译,它们已经在缓存中了。

安全强制执行发生在两个层面:

1. 静态验证(执行前):

- 拒绝格式错误或无效的指令
- 验证所有内存访问模式
- 确保跳转目标落在有效的指令边界上
- 捕获不可达的代码路径

如果你的程序未通过验证,它就无法部署,没有例外。

1. 运行时计量(执行期间):

- 每条指令消耗计算单元
- VM 在每一步之后检查预算
- 超出预算会立即终止执行

这种双层方法可以防止无限循环、资源耗尽攻击和失控程序。即使恶意字节码不知怎么通过了静态验证,它也不能永远运行下去,一旦达到计算限制就会终止。

如果你想进一步了解执行引擎,可以查看以下资源:

- [程序概览](https://solana.com/docs/core/programs):Solana 程序的工作原理
- [用 Rust 开发程序](https://solana.com/docs/programs/rust):官方 Rust 开发指南

## 第三部分:数据模型

我们已经追踪了交易如何流经 SVM,以及字节码如何在 sBPF VM 内执行,现在来看看程序操作的对象是什么。

在大多数编程环境中,你有变量、数据库、文件系统,以及各种存储和检索数据的方式。区块链需要类似的东西:一种在交易之间持久化状态的方法。EVM 的解决方式是为每个合约分配自己的存储,一个内置在合约本身的键值存储。

Solana 采取了一种截然不同的方法,理解这种差异至关重要,因为它塑造了你在该平台上构建的一切。

### Solana 上一切都是账户

可以把 Solana 想象成一个巨大的键值数据库:

- **键(Key)**:一个 32 字节的地址(通常是公钥或衍生地址)
- **值(Value)**:一个账户(持有字节数据、余额和元数据的数据结构)

这种一致性乍看起来可能有点奇怪,但正是这一点使得 Solana 的性能特性成为可能。每一份状态都有明确的地址、明确的所有者,以及关于谁能修改它的明确规则。运行时不需要遍历嵌套的合约调用来搞清楚哪些状态可能会改变,它从交易的账户列表中就能提前知道。

我们来看看账户内部实际包含什么,每个账户都包含相同的五个字段:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Field", dataType: "object" },
      { key: "2", width: 200, title: "Description", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>lamports</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>以 Solana 最小单位表示的余额(1 SOL = 10 亿 lamports)</p>",
          tooltip: "",
          icon: "",
        },
        id: 5,
      },
      {
        "1": { title: "<p>data</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>存储账户状态的任意字节数组</p>",
          tooltip: "",
          icon: "",
        },
        id: 4,
      },
      {
        "1": { title: "<p>owner</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>被允许修改该账户数据的程序 ID</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        "1": { title: "<p>executable</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>布尔标志——程序账户为 true</p>",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        "1": { title: "<p>rent_epoch</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>用于 rent 追踪的历史遗留字段(大部分已弃用)</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
    ],
  }}
/>

`owner` 字段至关重要。只有拥有该账户的程序才能修改其 `data` 字段或扣除其 lamports。任何人都可以读取任何账户(Solana 上所有状态都是公开的),任何人都可以向任何账户增加 lamports。但写操作受所有权限制。

这种所有权模型正是并行执行得以实现的原因。运行时确切地知道哪个程序可以修改哪些账户,因此它能够安全地同时运行不冲突的交易。

### 程序:设计上的无状态

Solana 程序是包含已编译 sBPF 字节码的可执行账户。但这里的关键洞见是:程序不能在内部存储状态。它们是处理指令并修改传入账户的纯函数。

每个 Solana 程序共享相同的入口点签名:

<CodeSnippet
  language="rust"
  code={`pub fn process_instruction(
    program_id: &Pubkey,           // This program's address
    accounts: &[AccountInfo],      // All accounts passed to this instruction
    instruction_data: &[u8],       // Arbitrary data (parameters, method selectors, etc.)
) -> ProgramResult`}
/>

当你的程序运行时,它会收到:

1. 自身的地址(以便验证它衍生出的 PDA)
1. 一个它被允许读写的账户数组
1. 包含调用者传入参数的指令数据

程序执行其逻辑,修改它被允许修改的账户,然后返回成功或错误。它在调用之间不存储任何内部状态。

为什么无状态?因为这能实现清晰的所有权边界。如果程序自行存储状态,你就需要复杂的规则来决定哪些程序可以访问哪些存储。通过强制所有状态进入有明确所有者的账户,这个模型保持简单,并行化也就得以保持可能。

_设计说明:与默认不可变的 EVM 合约不同,Solana 程序默认是可升级的。升级权限可以随时推送新的字节码。开发者可以撤销升级权限来使程序不可变,但这是一个显式的选择。这反映了 Solana 的实用主义方法——大多数程序需要 bug 修复和功能更新。_

### 程序衍生地址(PDA)

如果程序是无状态的,所有数据都存放在账户中,那么程序如何创建和管理自己的账户?它们不能持有私钥。这正是程序衍生地址(Program Derived Addresses,PDA)发挥作用的地方。

PDA 是落在 Ed25519 椭圆曲线之外的特殊地址,这意味着不存在与之对应的有效私钥。这种密码学特性使得程序能够为这些地址"签名"而无需持有私钥,运行时会在内部验证衍生过程。

PDA 的衍生方式是对种子(seeds)加程序 ID 加一个"bump seed"进行哈希:

<CodeSnippet
  language="rust"
  code={`// Find a PDA for storing a user's balance
let (user_balance_pda, bump) = Pubkey::find_program_address(
    &[
        b"balance",           // Static seed
        user.key().as_ref()   // User's public key as seed
    ],
    program_id
);`}
/>

该算法从 255 开始尝试递减 bump 值,直到找到一个能产生曲线外地址的值。第一个有效的 bump 值就是"canonical bump"。

#### 为什么 PDA 很重要

- **确定性寻址**:给定相同的种子,你总是能得到相同的地址,无需单独存储地址。
- **程序控制的账户**:程序可以在其衍生出的 PDA 处创建账户,并且只有它们能为这些 PDA 签名。
- **键值模式**:种子可以编码关系(用户 \+ 代币类型 → 余额账户),从而实现类似映射的数据结构。

<CodeSnippet language="rust" code={`// Common PDA patterns

// User-specific data
let (user*profile, *) = Pubkey::find_program_address(
&[b"profile", user.key().as_ref()],
program_id
);

// Token account for a specific mint
let (token*vault, *) = Pubkey::find_program_address(
&[b"vault", mint.key().as_ref()],
program_id
);

// Unique item with incrementing ID
let (item, \_) = Pubkey::find_program_address(
&[b"item", &item_id.to_le_bytes()],
program_id
);`} />

### Rent:为状态付费

区块链状态不是免费的。每个账户都占用验证者磁盘上的空间,而这些空间是有实际成本的。不同的链有不同的处理方式。以太坊为存储操作收取 gas,但一旦写入,状态就永久存在,导致状态不断膨胀。

Solana 采取了不同的方法。有一个称为"rent"的机制,网络大约按每字节每年 3480 lamports 的标准收取费用,以保持数据留在链上。实际操作中,所有主网账户都必须"免租"(rent-exempt),即预先持有至少两年的租金:

<CodeSnippet
  language="rust"
  code={`rent_exempt_minimum = (account_size + 128 bytes overhead) × 3,480 × 2`}
/>

对于一个典型的代币账户(165 字节)来说,这大约相当于 0.002 SOL。

但这里与其他链的关键区别是:关闭账户会返还全部租金押金。这创造了清理未使用状态的经济激励,而这在存储永久存在的模型中是不存在的。

<CodeSnippet
  language="rust"
  code={`// Closing an account returns lamports to a recipient
ctx.accounts.account_to_close.close(ctx.accounts.recipient.to_account_info())?;`}
/>

### 跨程序调用(CPI)

程序不是孤立存在的。一个 DeFi 协议可能会调用 Token Program 来转移代币,而后者可能会调用 Associated Token Account Program。这些跨程序调用(Cross-Program Invocations)正是 Solana 生态系统实现组合性的方式。

两个函数使 CPI 成为可能:

<CodeSnippet language="rust" code={`// Standard CPI - passes existing signers through
invoke(
    &instruction,
    &[account1, account2, ...]
)?;

// CPI with PDA signing - program "signs" for a PDA it controls
invoke_signed(
&instruction,
&[account1, account2, ...],
&[&[b"seed", &[bump]]] // Seeds that derive the PDA
)?;`} />

当你使用 PDA 种子调用 `invoke\_signed` 时,运行时会验证该衍生结果与预期地址匹配,并为该次调用授予签名权限。这正是程序在不持有私钥的情况下也能控制资产的方式。

重要的约束条件:

- 最大调用深度为 5(从初始交易起最多 4 层嵌套 CPI)
- 签名者权限会沿调用链级联
- 计算预算在一笔交易中的所有 CPI 之间共享

想进一步了解账户,可以查看以下内容:

- [账户概览](https://solana.com/docs/core/accounts):Solana 账户完整指南
- [PDA](https://solana.com/docs/core/pda):程序衍生地址详解
- [CPI](https://solana.com/docs/core/cpi):跨程序调用指南

## 第四部分:并行执行(Sealevel)

到目前为止,我们已经涵盖了所有基础部分:交易如何流经管线,sBPF VM 如何执行字节码,以及账户模型如何以明确的所有权存储状态。

现在我们终于可以回答一直在铺垫的那个问题了:Solana 究竟是如何实现并行执行的?

我们在整篇文章中一直有所暗示:Banking Stage 调度不冲突的交易,账户预先声明所有者,交易指定它们将涉及哪些账户。这些并不是独立的功能特性,它们都是一个称为 Sealevel 的单一系统的组成部分。

Sealevel 是 Solana 的并行智能合约运行时,是使 SVM 吞吐量得以实现的核心创新。我们所讨论过的一切——账户模型、所有权规则、预先的账户声明——都是为了使 Sealevel 成为可能而存在的。

其根本洞见看似简单:

> 如果交易在执行开始前就声明它们要读取和写入哪些账户,运行时就能识别出不重叠的交易并并发执行它们。

就是这样。这就是整个诀窍。但要在实践中让它运作起来,需要围绕这一约束设计系统的其他每一部分。

### 并行执行的规则

Sealevel 的调度遵循简单直接的规则:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Scenario", dataType: "object" },
      { key: "2", width: 200, title: "Execution", dataType: "object" },
    ],
    data: [
      {
        "1": {
          title:
            "<p>涉及<strong>不同账户</strong>的交易</p>",
          tooltip: "",
          icon: "",
        },
        "2": { title: "<p>并行</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": {
          title:
            "<p><strong>只读</strong>同一账户的交易</p>",
          tooltip: "",
          icon: "",
        },
        "2": { title: "<p>并行</p>", tooltip: "", icon: "" },
        id: 2,
      },
      {
        "1": {
          title:
            "<p><strong>写入</strong>同一账户的交易</p>",
          tooltip: "",
          icon: "",
        },
        "2": { title: "<p>顺序</p>", tooltip: "", icon: "" },
        id: 1,
      },
    ],
  }}
/>

就是这样。读操作与读操作不冲突。写操作与一切都冲突。

<CodeSnippet language="text" code={`TX1: [Account A (write), Account B (read)]
TX2: [Account C (write), Account D (read)]     ──► PARALLEL
TX3: [Account B (read), Account E (write)]

TX4: [Account A (write), Account F (read)] ──► SEQUENTIAL with TX1
(conflicts with TX1 on Account A write)`} />

这就是为什么你必须在 Solana 交易中预先声明所有账户。这不是官僚形式主义:这是运行时并行化你的交易所需要的信息。

### 调度算法

Banking Stage 通过账户级别的锁定来实现 Sealevel:

1. 交易到达,指定带有读写标志的账户
1. 对每个可写账户:获取独占锁
1. 对每个可读账户:获取共享锁(允许多个读者)
1. 如果所有锁都已获取:安排执行
1. 如果有任何锁被阻塞:重新入队并稍后重试

当前实现使用 6 个线程:4 个用于非投票交易,2 个用于投票交易。每个线程维护一个按优先费(每计算单元费用)和到达时间排序的队列。

一个更新的 Central Scheduler 架构使用单个调度线程配合 Prio-Graph 算法,这是一种惰性填充的依赖图,通过前瞻窗口来识别冲突。这提高了高争用工作负载下的调度效率。

### 实践中的性能

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Metric", dataType: "object" },
      { key: "2", width: 200, title: "Value", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>理论最大 TPS</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>约 65,000(简单转账)</p>",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        "1": { title: "<p>测试网基准</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>47,370 TPS(200 个节点,23 个地区)</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        "1": { title: "<p>主网典型范围</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>800–3,600 TPS(真实用户交易)</p>",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        "1": { title: "<p>目标出块时间</p>", tooltip: "", icon: "" },
        "2": { title: "<p>400ms</p>", tooltip: "", icon: "" },
        id: 1,
      },
    ],
  }}
/>

真实世界中的吞吐量在很大程度上取决于交易组合。涉及独立账户的简单转账能完美并行化。都命中同一流动性池的复杂 DeFi 交易则会产生争用并被串行化。

这实际上是一个特性:争用是局部化的。某个去中心化交易所交易量激增,并不会拖慢一个新型银行上的代币转账速度,因为它们涉及不同的账户。这与所有交易都争夺同一个全局吞吐量的链有着根本性的不同。

### 为什么顺序 VM 不能简单地采用这种方式

你可能会想:为什么 EVM 不能直接加上并行执行?

根本问题在于,EVM 的状态访问是在**执行时**决定的,而不是提前决定的。当你调用一个 [Solidity](https://www.alchemy.com/overviews/solidity) 合约时,在你实际运行它之前,没有办法知道它会读取或写入哪些存储槽。该合约可能会调用另一个合约,后者又调用另一个,每次访问都是不可预测的状态。

这种"读写无感知"模型使得在没有推测执行的情况下无法实现安全的并行化。一些较新的链实现了"乐观并行执行":假设没有冲突进行推测性执行,然后在冲突发生时检测冲突并顺序重新执行。这种方式可行,但增加了复杂性和开销。

Solana 的方法不同:冲突是被避免的,而不是被解决的。通过要求预先声明,运行时在执行开始之前就确切知道每笔交易需要什么。这种约束正是实现优化的原因。

EIP-2930 为以太坊引入了可选的"访问列表"以获得 gas 折扣,但这不是强制性的。Solana 使声明成为强制性和普遍性的要求,这就是关键区别。

## SVM 生态系统:超越 Solana 主网

SVM 已经不再仅仅是 Solana 了。2024 年 6 月,Anza 推出了 SVM API,将执行引擎与 Solana 的验证者客户端解耦。这种模块化推动了全新的应用场景。

### Eclipse:以太坊上的 SVM

Eclipse 于 2024 年 11 月上线,是第一个在以太坊上运行的生产级 SVM rollup:

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Layer", dataType: "object" },
      { key: "2", width: 200, title: "Provider", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>结算层</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Ethereum</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": { title: "<p>执行层</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Solana 的 SVM</p>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        "1": { title: "<p>数据可用性</p>", tooltip: "", icon: "" },
        "2": { title: "<p>Celestia</p>", tooltip: "", icon: "" },
        id: 3,
      },
      {
        "1": { title: "<p>欺诈证明</p>", tooltip: "", icon: "" },
        "2": {
          title: "<p>RISC Zero(ZK 加速)</p>",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
    ],
  }}
/>

上线时已有超过 60 个应用部署,包括 Orca。Eclipse 筹集了 6500 万美元来构建这座桥梁,证明了 SVM 具有独立于 Solana 共识层的价值。

### SOON 网络

SOON(Solana Optimistic Network)于 2025 年初实现了 alpha 主网,成为第一个"解耦 SVM"rollup。与简单的分叉不同,SOON 将执行与共识完全分离,采用 Merkle Patricia Trie 进行状态管理(不同于 Solana 的 AccountsDB)。目标:借助 Firedancer 集成实现 5K–600K TPS。

### Sonic SVM

Sonic 于 2025 年 1 月上线,是 Solana 上第一个用于游戏的原子级 SVM Layer-2。由 [Mirror World](https://www.alchemy.com/dapps/mirror-world) Labs 构建,包含 HyperGrid Framework,用于为每个游戏启动定制化的 SVM 链。测试网处理了超过 6 亿笔交易,来自超过 200 万个月活跃钱包。

### MagicBlock:临时性 rollup

MagicBlock 引入了"临时性 rollup"(ephemeral rollups),即临时的、按需的 SVM 执行环境。可以正常在 Solana 上部署合约,然后在需要低于 50ms 延迟时将特定账户委托给 MagicBlock。写操作在临时会话中进行,然后提交回 Solana。

这非常适合游戏和实时应用场景,因为 400ms 的出块时间对它们来说仍然太慢。

### Firedancer:性能突破

Jump Crypto 的 Firedancer 客户端可能是最重要的 SVM 发展。它完全用 C 编写,不与 Agave 共享任何组件,采用基于 tile 的架构,每个功能运行在专用的 CPU 核心上,并使用内核旁路网络技术。

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Benchmark", dataType: "object" },
      { key: "2", width: 200, title: "Result", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>数据包接收</p>", tooltip: "", icon: "" },
        "2": { title: "<p>100 万 TPS</p>", tooltip: "", icon: "" },
        id: 0,
      },
      {
        "1": {
          title: "<p>SVM 执行(简单程序)</p>",
          tooltip: "",
          icon: "",
        },
        "2": { title: "<p>5 亿 TPS</p>", tooltip: "", icon: "" },
        id: 1,
      },
    ],
  }}
/>

这些是合成基准测试数据,但它们表明当前主网性能之上还有巨大的提升空间。

时间线:

- **2024 年 9 月**:Frankendancer(混合版)在主网上线
- **Breakpoint 2024**:完整版 Firedancer 以非投票模式运行
- **2025 年**:预计完整生产版本发布

## 在 SVM 上构建

我们已经介绍了架构,现在来讲点实际的。如果你想开始在 Solana 上构建,实际需要什么?

### 工具链一览

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Tool", dataType: "object" },
      { key: "2", width: 200, title: "What It Does", dataType: "object" },
    ],
    data: [
      {
        "1": { title: "<p>Solana CLI</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>核心工具包,负责密钥对管理、部署以及与集群交互</p>",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        "1": { title: "<p>Anchor</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>用于构建程序的主流框架——处理样板代码、序列化、账户验证和测试</p>",
          tooltip: "",
          icon: "",
        },
        id: 5,
      },
      {
        "1": { title: "<p>Rust + cargo-build-sbf</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>将你的 Rust 代码编译为用于部署的 sBPF 字节码</p>",
          tooltip: "",
          icon: "",
        },
        id: 4,
      },
      {
        "1": { title: "<p>Bankrun / LiteSVM</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>无需启动完整验证者即可进行快速本地测试</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        "1": { title: "<p>solana-test-validator</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>用于结合主网状态进行集成测试的完整本地验证者</p>",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        "1": { title: "<p>Alchemy</p>", tooltip: "", icon: "" },
        "2": {
          title:
            "<p>为 Solana 提供高性能 RPC 节点和 API,提供可靠的区块链数据访问、增强的历史查询,以及用于开发、测试和交互的工具</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
    ],
  }}
/>

对大多数开发者来说,路径是:Solana CLI \+ Anchor \+ Bankrun。这个组合覆盖了 90% 的使用场景。

### 快速上手:5 分钟设置

<CodeSnippet language="bash" code={`# Install Solana CLI
sh -c "\$(curl -sSfL <https://release.anza.xyz/stable/install>)"

# Install anchor

cargo install --git <https://github.com/coral-xyz/anchor> anchor-cli

# Create a new project

anchor init my_project && cd my_project

# Build and test

anchor build
anchor test`} />

就是这样。你现在拥有了一个可用的 Solana 开发环境:一个初始程序、测试套件,以及可用的本地验证者集成。

从这里开始,[Anchor Book](https://www.anchor-lang.com/docs/installation) 会带你构建你的第一个真正的程序,[Solana Cookbook](https://solanacookbook.com/) 则为你会遇到的常见模式提供了配方。

想深入了解,可以参考:

- [Solana 开发者文档](https://solana.com/docs):官方文档
- [Anchor Book](https://www.anchor-lang.com/):完整的框架指南
- [Solana Cookbook](https://solanacookbook.com/):实用配方与模式
- [Alchemy Solana API](https://www.alchemy.com/solana):RPC 基础设施与开发者工具

## 结论:架构上的赌注

SVM 代表了一种连贯的架构理念:接受预先的约束(明确的账户声明、无状态程序、复杂的交易构造)以换取并行性、吞吐量和局部化的费用市场。这些约束并非偶然产生的附带结果——它们正是实现这种性能的机制。

如果你想探索其中的可能性,[Alchemy 的 Solana API](https://www.alchemy.com/solana) 让你能从一个平台访问 Solana 主网、devnet 以及更广泛的 SVM 生态系统。开始构建,亲自体验一下。

## 常见问题

### 什么是 Solana 虚拟机(SVM)?

SVM 是 Solana 的执行引擎,一个基于寄存器的运行时,构建在 eBPF 字节码之上,通过要求预先声明所有状态依赖关系来并行执行交易,从而实现跨 CPU 核心的数千笔并发交易。

### SVM 与以太坊虚拟机(EVM)有何不同?

与 EVM 基于栈的顺序架构不同,SVM 采用基于寄存器的设计并支持并行执行,通过账户将代码与状态分离,并要求预先声明账户以实现并发处理。

### 什么是 Sealevel,为什么它很重要?

Sealevel 是 Solana 的并行智能合约运行时,它调度不冲突的交易在多个 CPU 核心上同时执行,通过并行处理涉及不同账户的交易实现数千 TPS 的吞吐量。

### 我可以用哪些编程语言在 SVM 上构建?

程序主要用 Rust 编写,通过 LLVM 编译为 sBPF 字节码,不过其他 LLVM 兼容语言,如 C、C\+\+ 和 Zig 也受支持。

### 什么是程序衍生地址(PDA)?

PDA 是落在 Ed25519 曲线之外、没有有效私钥的特殊地址,使程序能够确定性地生成并为它们控制的地址"签名",而无需持有私钥,从而使程序能够管理自己的账户。

### SVM 的账户模型是如何工作的?

Solana 上一切都是账户,一种包含 lamports(余额)、任意数据字节、所有者程序 ID 和元数据的数据结构。程序是无状态的,只能修改它们所拥有的账户,这为并行执行提供了清晰的所有权边界。

### 什么是 sBPF,它与 eBPF 有什么关系?

sBPF(Solana Bytecode Format)是 Solana 定制的 eBPF 变体,针对区块链使用场景做了修改,拥有更大的栈空间(4KB 对比 512 字节)、支持受计算单元限制的循环,以及用于日志记录、CPI 和加密操作的自定义系统调用。

### 什么是跨程序调用(CPI)?

CPI 使 Solana 程序能够在执行期间调用其他程序,最大调用深度为 5,在保持共享计算预算和级联签名者权限的同时,实现了整个生态系统的可组合性。

### SVM 可以在 Solana 主网之外使用吗?

可以,模块化的 SVM API 使其能够在 Solana 之外使用:Eclipse 将 SVM 作为以太坊 rollup 运行,SOON Network 作为解耦的 SVM rollup 运行,而 Sonic 和 MagicBlock 等项目则构建了专门化的 SVM 执行环境。

### 我该如何开始在 SVM 上构建?

安装 Solana CLI 和 Anchor 框架,然后使用 `anchor init` 创建一个包含初始程序、测试套件和本地验证者集成的项目,Anchor Book 和 Solana Cookbook 提供了全面的开发指南。
