
Solana 共识机制
可行洞见
- 历史证明(PoH)在同步中的作用:PoH 并非共识算法,而是 Solana 共识机制用于同步的工具。同样,权益证明(PoS)也不是共识,而是一种女巫攻击抵抗机制。
- 共识需要投票交易:区块内的投票并非用来人为抬高 TPS 指标的多余交易。如果投票仅通过 gossip(非正式的点对点通信)传播,验证者对 Tower(投票)状态的认知可能会出现差异。
- Solana 有两条主要确认规则:一条用于短期分叉选择(乐观确认),另一条用于实现最终性的完整 PoS 共识(已最终确认/已生根)。客户端和用户可以根据所需的安全属性与自定义 UX 选择遵循相应的确认规则。这体现为两个承诺级别:“confirmed”和“finalized”。
- 理解审查风险:验证者和开发者应了解审查攻击的潜在风险。在这类攻击中,验证者会试图扰乱区块生产顺序。还应理解此类攻击的运作机制,以及计算能力和质押在实施攻击时的作用。
- 即将到来的协议升级:验证者和开发者应积极准备应对 Solana 共识机制即将发生的变化,例如异步执行和程序化罚没。
引言
随着 Solana 上的活动日益活跃,其技术栈的各个层面都在经受前所未有的考验。关于本地费用市场等“热门”话题,已经有大量文章和讨论,但 Solana 共识长期以来却未受到足够重视。然而,随着活动和关注度上升,恶意攻击的动机及潜在利用行为的收益也在增加,因此社区需要透彻理解共识机制。
共识是 Solana 社区最需要广泛理解的重要方面之一,因为它决定了数千名验证者如何就交易的规范顺序达成一致。
分布式系统领域对共识的研究已有多年。Lamport、Shostak 和 Pease 撰写的拜占庭将军问题发表于 20 世纪 80 年代初。Web2 长期采用 RAFT 等共识算法。在加密领域,大多数共识算法都是 BFT 共识的不同实现,包括 Gasper(Ethereum)、Tendermint(Cosmos)、MonadBFT(Monad)、HotShot(Espresso)以及 Narwhal/Tusk(Sui)。
本文不尝试对 Solana 的共识机制(TowerBFT)进行形式化证明。本文旨在向开发者和更广泛的社区解释 Solana 共识的运作方式,因为截至目前,这一主题主要掌握在 Solana Labs 和 Firedancer 贡献者手中。本文还会讨论相关权衡与局限。
共识简介
共识协议的目标,是就区块内的交易及其相对顺序达成一致。网络中的验证者主要通过两类共识协议,就交易的规范顺序达成一致:
- 最长链协议:此类协议(例如 Bitcoin 的 Nakamoto 共识)会将构建时所需计算工作最多的链认定为规范链。虽然这通常与区块数量最多的链相关,但更准确的描述是:该链体现了最大的累计工作量或计算能力。
- BFT 类协议:大多数 PoS 协议都会实现某种版本的 BFT 共识算法。此类协议(例如 pBFT)依赖活性和安全性阈值。CAP 定理提供的保证包括一致性和可用性。该定理指出,任何分布式数据存储都只能同时提供以下三种保证中的两种:一致性、可用性和分区容错性。
最长链和 BFT 类共识协议都通过各自的确认规则获得安全性。安全性由安全保障和活性共同构成,它源自特定的确认规则,而不是链本身的属性。根据 Ethereum Foundation 的定义,确认规则是“一种由节点运行的算法,用于输出某个区块是否已确认。区块一旦确认,在特定假设下便保证永远不会发生重组。这些假设主要涉及网络同步性以及诚实质押所占的比例。”
归根结底,底层始终是社会共识,最终由编写客户端代码的人通过特定确认规则来定义什么构成安全性。
权益证明为 BFT 模型引入了额外层次,要求参与者投入自己的质押。参与者遵守一组规则即可获得奖励,但如果存在经证实的不当行为(例如双重签名),则可能受到罚没。这称为可问责安全性,使协议能够识别并惩罚恶意节点,同时不会给诚实节点带来外部性。这并不会取代 BFT 共识协议的基本安全机制:为避免网络停摆,不诚实节点的比例仍需低于三分之一;为防止虚假交易通过验证,该比例则需低于三分之二。基于质押的系统会对可能扰乱网络或降低网络效率的行为施加后果。
此类 BFT 网络存在两个关键阈值:
- 1/3:如果不诚实节点占总数的三分之一或更多,网络可能会“停摆”。在这种情况下,这些节点可以直接选择不参与,使其余节点无法达到共识所需的三分之二绝对多数。因此,网络不会产生错误交易,而是彻底停止产生任何交易。一个常用但较为粗略的指标是 Nakamoto 系数,它表示造成活性故障(停止区块生产)所需的最少节点数。
- 2/3:如果不诚实节点占总数的三分之二或更多,它们可以串通验证任意交易。这是最坏情况:网络不再正常运行,而是按照不诚实绝对多数的意愿处理交易。如果攻击方控制超过 67% 的质押,就可能隔离某个诚实节点,例如大型交易所的节点(如 Binance)。它们可以与数据中心串通,限制该节点的网络流量,从而实现隔离。随后,恶意实体可以让这个被隔离的节点最终确认其构造的区块,同时向网络其余部分分发一个冲突区块。此类攻击利用了被隔离节点有限的视角。由于网络其余部分与被隔离节点对链上状态的认知不同,可能会导致双花。
即使质押比例较低但超过 33%,也可以实施类似攻击,不过这需要制造网络分区,而不只是隔离单个节点。在这种情况下,拜占庭利益相关者可以利用网络分区,向网络的不同部分提供相互冲突的信息,从而操纵它们,并再次带来双花及其他安全保障失效的风险。
通常,节点数量越多,任何一方要破坏足够大比例的节点并达到这些阈值就越困难(假设节点在地理上相互分散)。然而,扩大网络规模的诉求往往与效率相冲突。节点数量增加会提高数据传输需求(投票传播需要更长时间),从而减慢共识速度,因此部分协议会严格限制最大节点数。
历史证明(PoH)
权益证明(PoS)确保网络达成共识,而 Solana 则将历史证明(PoH)纳入其 PoS 共识机制,从而为持续生产区块提供同步能力。Solana 无需等待一轮同步共识,而是直接跳过 slot leader 缓慢或无响应的 slot,以此实现同步。PoH 的重点并非证明事件发生的确切时间,而是证明事件的顺序以及事件之间经过的时间。
与普遍认知不同,历史证明(PoH)本身并不是共识机制或算法。虽然当前的共识实现使用了 PoH 的部分特性,但理论上可以移除 PoH,并对实现稍作修改,使 Solana 共识继续运行。
PoH 的核心是一种简单的哈希算法,类似于可验证延迟函数(VDF),但严格来说并不是 VDF。Solana 使用持续运行的顺序抗原像哈希函数(SHA-256)实现这一机制,每次迭代的输出都会用作下一次迭代的输入。此计算在每个验证者的单个 CPU 核心上运行。
虽然序列生成是顺序、单线程执行的,但其输出可以并行验证,因此能够在多核系统上实现高效验证。尽管哈希速度存在上限,但硬件进步可能会进一步提升性能。
假设 Solana 网络中有四个验证者:验证者 A、B、C 和 D。在这个示例中,leader 调度可能规定区块生产顺序为 A - B - C - D。首先轮到验证者 A 生成区块。为此,验证者 A 使用 PoH 机制,迭代运行 SHA-256 哈希函数,形成以“tick”为单位的时间尺度。此哈希过程会创建唯一且可验证的耗时记录,确保验证者 A 的区块能够反映准确的时间流逝。验证者 A 完成区块后,轮到验证者 B 生成下一个区块,随后是验证者 C。
假设验证者 C 试图在不属于自己的轮次发出区块,以扰乱顺序。它希望直接接在验证者 A 之后,从而绕过验证者 B。要令人信服地取代验证者 B,验证者 C 需要复制验证者 B 本应生成的 PoH 哈希序列,也就是生成一组能体现验证者 B 生产区块所需时间的哈希序列。单核哈希性能存在物理上限,也就是说,由于计算机在给定时间间隔内可以生成的哈希数量存在上限,我们能够确定已经过了一定时间。
验证者 C 可以从前一个合法区块(验证者 A 的区块)末尾开始生成一条不含任何交易的空区块链,尝试审查 leader 调度中的验证者 B。要成功审查 B,C 必须满足两个条件。首先,C 需要足够的计算能力来暴力计算空 PoH 链。其次,C 必须迅速将自己的区块传播给足够多具有质押的节点,确保该区块在 C 自己指定的 slot 内被接受,从而有效审查 B。得益于 Turbine,质押权重较高的验证者可以更快完成传播,因为 Turbine 会根据验证者的质押权重确定信息流的优先级。
这种攻击场景确实可行,但成功实施审查的时间窗口有限。攻击节点需要拥有大量计算资源(用于 SHA-256 哈希)和可观的质押,因为它能生成的区块数量与其质押成正比。
除了需要比节点 A 更快触达网络外,C 还必须高速生成哈希。这种攻击主要针对以下场景:在 slot n 被指定为 leader 的验证者,试图审查此前 leader slot(n 之前)中的验证者。该验证者能够实施多大程度的审查取决于其质押,因为它创建 leader slot 的能力与其持有的质押直接相关。
PoH 机制还确保区块以稳定速率生成。由于每个验证者都能独立验证 PoH 序列,因此无需外部时间同步。例如,Ethereum 在每个区块中使用网络时间协议(NTP)来达成共识。在 Solana 上,每个验证者都会在不使用外部协议的情况下,独立验证每个区块是否在正确的时间 slot 内生成。
Tower BFT(Solana 的共识机制)
Solana 的共识机制 Tower BFT 会在 shred(部分区块)传播给其他验证者后运行:
如前所述,Solana 会同时运行 Tower BFT 与历史证明。Tower BFT 是一种类似 pBFT 的共识算法,旨在利用历史证明提供的同步时钟计算。这会在整个网络中建立统一时钟,使网络能够高效跳过分配给缓慢或无响应 leader 的 slot。因此,网络无需针对每个 slot 进行一轮同步共识,并可以持续生产区块,因为验证者在构建下一个区块前不必等待前一个区块到达。
**另一个误解是,Solana 的共识机制目前已经实现程序化罚没。**虽然罚没已列入路线图,但目前发生安全保障违规后,网络会停止运行,并在必要时依赖社会共识实施罚没。
Solana 的共识机制赋予不同节点不同程度的影响力。网络中的投票并不平等,而是根据每个节点的质押进行加权,其运作原则与质押加权 QoS 和 Turbine 相同。在其他条件相同的情况下,质押更多的节点比质押更少的节点更能影响规范共识的确定。
例如,在一个由四个节点组成、总质押为 100 的网络中,质押分布及影响力可能如下:
- 节点 A 有 10 个单位的质押。
- 节点 B 有 20 个单位的质押。
- 节点 C 有 30 个单位的质押。
- 节点 D 有 40 个单位的质押。
在这种设置下,每组不诚实节点的能力各不相同。像节点 D 这样的单一节点虽然只占节点总数的 25%,但由于质押较多,仍可让网络停摆。同样,节点 C 和 D 的组合虽然只占节点数的 50%,却持有足以影响结果的质押,可以批准错误交易。
不诚实、遭入侵或被阻断的节点都可能使网络达到三分之一的停摆阈值。但要达到验证错误交易的三分之二阈值,则需要主动串通,不能只靠阻断诚实节点。因此,后者要困难得多,因为攻击者必须腐化或入侵节点,使其主动参与不诚实计划,而不是仅仅阻断诚实节点。
slot 中的运作背景
在 Solana 上,slot leader 会连续获得四个 slot,总时长约为 1.6 秒(4 个区块 × 每个区块 400 毫秒)。每个 epoch 开始时会选出 leader(432,000 个 slot,约为 ~2-3 天)。leader 调度根据验证者的质押权重随机确定。
leader 负责为分配给自己的每个 slot 构建并提议新区块(不存在 Ethereum 生态中的提议者与构建者分离)。其他验证者会根据自己对 Solana 网络链头的本地视图应用分叉选择规则,并通过投票交易证明给定区块的有效性。
leader 必须在给定 PoH tick 范围内发布区块,该区块才有效;未在此范围内发布的区块将被视为已跳过。
以包含四个参与者(A、B、C、D)的 leader 调度为例,其中 D 试图扰乱顺序。如果 D 尝试在 C 的轮次介入,就必须创建一段能够有效跳过 C 区块的 PoH tick 序列,形成如下链:
A - B - [缺少 C] - D。
如果 C 的区块缺失,诚实的 D 必须先生成覆盖 C 整个 slot 时长的 PoH 序列,然后才能开始自己的 slot。这样,D 的区块会在分配给 C 的 slot 时间结束后接续 B 的区块,因此看起来有效。
在 D 为 C 的 slot 生成 PoH 序列时,C 通常会流式传输自己的区块,并通过适当的 PoH 序列连接到 B 的区块。这会产生两种可能结果:
- 如果 D 的 PoH 计算速度不比 C 快:C 完成区块的时间与 D 开始广播自己区块的时间大致相同。由于网络已经看到 C 的区块,因此会拒绝 D 的区块。
- 如果 D 计算 PoH 的速度比 C 快:D 会在 C 完成区块前开始广播自己的区块。即便如此,D 也必须比 C 快得多,才能显著影响区块顺序。考虑到验证者用于 SHA-256 计算的 CPU 速度很高,且能力上限(相对)接近,这种情况不太可能发生。
在这两种情况下,D 成功取代 C 的可能性都很低。此外,如果 D 取代 C 的尝试失败,它将失去在自己 slot 内发出区块的机会。先紧接 B 发出一个区块,再尝试在 C 之后发出另一个区块,属于违规行为(为 D 的 slot 创建两个区块),将构成可罚没行为。每次失败都会使 D 错失自己生产区块的机会,因此 D 不太可能采用这种策略。
从网络的角度看,无法确定 D 的意图。很难甚至不可能区分 D 只是没有看到 C 的区块(并无审查 C 的意图),还是 D 有意审查 B。因此,网络无法轻易检测或惩罚这种行为。
投票交易
Solana 的共识机制依靠投票交易达成共识。投票交易包含在区块中,但具有较高优先级,以免被普通交易淹没。目前,给定区块中的大多数交易都是投票交易:
不过,情况未必永远如此,因为给定区块中投票交易的比例反映了网络活动与参与共识的验证者数量之比。未来,投票交易可能只占区块中的少数。
**投票交易是共识的必要条件,并非用来人为抬高 TPS 指标的多余交易。**如果投票仅通过 gossip(非正式的点对点通信)传播,验证者对 Tower(投票)状态的认知可能会出现差异。这些差异可能导致验证者对哪个分叉更有可能正确产生不同看法。随着验证者根据不完整或不一致的信息做出贪婪选择,网络可能出现分歧。持续生产区块需要持续投票,尤其是在前序区块仍在确认时。这就要求验证者对 Tower 状态持有可靠且一致的认知。
与用户发起的普通交易一样,投票交易也必须支付基础费用(0.000005 SOL)。这笔费用由验证者身份支付。该身份必须存放在热钱包中以便持续签名,而投票账户则用于账户查询和委托。
每一票都由验证者签名,其中包含该验证者的公钥以及其投票支持区块的哈希。
当验证者收到同一 slot 的多个区块时,它会跟踪所有可能的分叉,直到能够确定“最佳”分叉。验证者在本地运行相关的状态转换函数(异步执行后这一点将发生变化),随后在重放后对新区块进行投票。验证者通过链上投票交易表达对给定分叉的支持。
在每笔投票交易中,验证者发布的投票都带有锁定期。此锁定期是一种承诺机制,它将验证者绑定到选定的分叉,并使其决策承担机会成本。目前,运行时并不强制执行锁定,而是通过社会共识执行——持续违反投票锁定规则极有可能遭到手动罚没。由于无法获得投票积分不足以形成足够的威慑,近期可能会加入针对违反锁定期的程序化罚没。
验证者还会为其上一次投票与已生根/已最终确认区块之间的每个祖先区块签署区块哈希。这是检测重复区块所必需的。如果 leader 向每个节点发送不同区块,每个节点最终都会为同一 slot 的不同前序区块投票。然而,在包含 65,000 个 slot 的 epoch 中,最极端情况下,每一票的大小可能达到 2 MB。原因是每个 slot 可能都需要一个 32 字节的哈希,总计 65,000 个 slot。未来的文章将进一步探讨这一问题。
验证者会管理一个“Vote Tower”,即按顺序排列的投票栈。每一票都会强化某个分叉,并且是 Tower 中其上方分叉的祖先。向 Tower 加入新投票后,栈中所有先前投票的锁定期都会翻倍,从而逐步提高早期决策的承诺程度并延长锁定期。投票过程本身受多项检查约束:验证者必须遵守先前投票的锁定期;需要确保网络中的相当大一部分(通常为三分之二)也承诺支持同一分叉;在切换到新分叉前,还必须有相当比例(超过 38%)的投票支持其他分叉。
对于 slot n 的区块,来自 leader 以外验证者的投票最早会在 n+1 时出现在链上。系统不存在投票子委员会——所有验证者都有资格对所有区块投票。这些投票首先出现在 Turbine 树中距离较近(相隔一跳)的其他验证者所生成的区块中。由于验证者可以对“slot 哈希”仍然已知的任何 slot 投票,而 slot 哈希会保留 512 个 slot,因此 slot n 的投票最晚可在 slot n+512 出现(感谢 Shinobi)。对于 slot n,并不存在以某个区块为界的预定上限;不过,在至少 32 个连续区块之上构建的已生根 slot 将不再接受投票,并成为已最终确认状态。
投票机制中存在一些未由运行时完全强制执行的特殊行为。例如,目前验证者有动机只为“已生根”slot 投票(忽略链的顶端),且共识机制不会惩罚这种行为。这样一来,验证者可以不断从极有可能成为规范链的分叉中赚取投票积分,却不参与链顶端的实际共识运作。这种情况目前已经存在,某个验证者的平均“投票延迟”超过 68 个 slot。一项名为及时投票积分的拟议功能旨在缓解这种激励错配。
Solana 的分叉选择规则
与 Ethereum(LMD Ghost 和 Casper FFG)类似,Solana 有两条确认规则:一条用于短期分叉选择,另一条用于实现最终性的完整 PoS 共识。这样,用户和客户端便可以根据不同的确认规则自定义 UX 选择。这体现为两个承诺级别:“confirmed”和“finalized”。
“confirmed”区块(也称为乐观确认)要求至少 2/3 的验证者通过投票交易对特定区块投票。要使最终性违规成为可能,当前约 ~4.6% 的质押必须遭到罚没。“finalized”区块要求至少对后续 32 个 slot 投票,或获得绝对多数(>2/3)投票。恶意攻击将导致超过 1/3 的质押遭到罚没。与“confirmed”区块相比,这需要更长时间,因为必须等待此前 32 个区块成为已生根区块,才能获得完整最终性。
分叉选择规则使网络能够就链头达成共识。Solana Labs 的实现主要通过一个命名贴切、约有 4600 行的 Rust 文件 heaviest_subtree_fork_choice.rs 处理分叉选择。它提供验证者判断哪个分叉最有可能成为规范分叉所需的逻辑。未来的文章将详细拆解其代码,但从非常高的层面来看,分叉选择的运作机制如下:
- 使用
add_votes()添加投票:
此过程会遍历新投票,在需要时从旧 slot 中减去投票账户的质押,并将质押添加到新投票支持的 slot。
- 调用
generate_update_operations()创建一批分叉更新操作:
此过程会:
- 从旧分叉中减去质押
- 向新分叉添加质押
- 沿每个分叉向上聚合质押,直至根节点
process_update_operations():
- 在需要时调用
mark_fork_valid()/invalid() - 沿每个分叉向上调用
aggregate_slot(),更新分叉权重 - 调用
add_slot_stake()/subtract_slot_stake()更新质押
aggregate_slot():
- 汇总所有子 slot 的质押
- 按质押权重找出最重的子分叉
- 按高度找出最深的子分叉
- 向上传播这些值,找出从当前 slot 开始最重/最深的分叉
select_forks():
- 返回用于区块生产的全局最重分叉
- 返回从上一次投票向下延伸的最重分叉
系统会根据投票增加或减去质押,聚合过程从下到上重新计算每个分叉的权重,并选择拥有最重加权子树的根作为用于继续构建和投票的最佳分叉。
被纳入的可能性
交易满足相应确认规则的必要要求后,其被纳入的可能性会随交易生命周期而变化。
虽然 slot 可能被“跳过”,例如 leader 离线时,但该交易仍可能进入未来的区块,也可能永远不会上链。
此处的 slot 数量只是粗略估算,旨在帮助读者了解投票通常会在何时累积。此外,每个区块的情况都不同(区块可能经过不同时长后达到“confirmed”或“finalized”确认级别)。在交易的整个生命周期中,回滚风险会单调递减。即使区块已最终确认(已生根),理论上仍可通过社会共识创建分叉,将其从“规范”链中移除。
未来研究方向与结论
本文重点介绍了 Solana 共识机制 Tower BFT 的内部运作方式及其他相关机制。我们探讨了历史证明、投票交易和分叉选择在 Solana 中的作用,以及它们如何创建用于确定交易顺序的规范分叉。
这些机制还有许多值得进一步研究和形式化的方向,例如:
- Ethereum 研究人员已经量化了参与时机博弈的价值。在这种博弈中,区块生产者会等到 slot 即将结束时才提交区块。此类博弈在 Solana 上的量化表现如何?目前是否正在发生?
- 异步执行已列入路线图,将成为 2024 年的主要协议变更。如果节点只是对现有分叉投票,而不是在本地计算状态转换,可能会面临哪些攻击途径?需要考虑哪些相关安全问题?
- 目前,相当一部分验证者(<20%)运行着经过修改的 Solana Labs 或 Jito-Solana 客户端。它们修改了哪些不受运行时或确认规则约束的阈值?
- 实现程序化罚没。目前,除社会共识外,几乎没有经济激励能够阻止参与者在不受罚没的情况下执行利润极高的交易策略。
- 当运行两个完全不同的客户端时(即使它们试图遵循相同的确认规则),Solana 共识机制的性能表现如何?
- 投票子委员会预计能带来哪些性能提升和状态缩减收益?
- 与从已最终确认的 slot 重启相比,从乐观确认的 slot 重启存在哪些潜在攻击途径?交易所等第三方链下解决方案应如何考虑使用乐观确认或完整最终性?
- 被罚没的资金是否应作为安全保障违规所涉及交易的保险?以 SOL 计价的保险池应采用怎样的机制和激励?
本文探讨了 Solana 共识机制采用的部分机制和阈值,以及它们与 leader 在给定 slot 中进行常规区块生产的关系。近期 Solana 活动量上升,为这些机制提供了真实环境下的活性与安全保障测试,同时也提高了恶意攻击的动机。
感谢 anoushk(Tinydancer)、dubbel06(Overclock)、Prithvi(Helius)、Mert(Helius)和 Jarry(Ellipsis Labs)参与讨论并提出意见。
相关文章
订阅 Helius
及时了解 Solana 开发的最新动态,并在我们发布新内容时收到更新


