☰
Polkadot 争议解决协议(Disputes)全解析:从链上罚没到节点参与机制
2026/10/7 1:51:26 网站建设 项目流程
  • 区块链

【免费下载链接】polkadot

Polkadot Node Implementation

项目地址:https://gitcode.com/gh_mirrors/po/polkadot
点击查看免费下载

本文聚焦 Polkadot 中继链的争议解决(Disputes)协议,这是与 Approval Checking(审批检查)并列的第二道防无效区块进入最终化链的防线。文章以 implementers-guide 中的 Disputes 协议说明 为骨架,结合争议流程(Disputes Flows)、争议协调器(Dispute Coordinator)、争议分发(Dispute Distribution) 等设计文档以及node/core/dispute-coordinator、node/network/dispute-distribution等子系统源码,系统讲解争议的动机、发起、参与、结论、惩罚机制与 DoS 防护设计。读完本文,你将掌握 Polkadot 争议协议的完整工作流,理解 local/remote 争议的区别、⅔ 绝对多数的结论规则、垃圾争议(spam)防护的工程手段,以及争议如何在所有链分支间“移植”以保证作恶者必被罚没。

一、动机与背景:为什么需要独立于分叉的争议协议

在 Polkadot 的安全性模型中,进入最终化(finalized)中继链的所有平行链区块都必须是有效的;这个约束不适用于“仅被 backing(担保)但未被包含(included)”的区块。系统用两大组件共同保证这一性质:

  1. 审批检查(Approval Checking):详见 审批协议 及其子系统 Approval Voting。该协议能够在“尝试次数有限”的前提下,证明无效区块无法进入最终化链。
  2. 争议解决(Disputes):即本文主题,确保每一次把坏东西包含进链的尝试都会被抓住,并惩罚作恶验证人。

争议与 backing、approval 的本质区别在于:backing 和 approval 都作用于特定分叉,而争议独立于特定分叉。这一区别至关重要:

  • 当一条替代分叉(可能不含当前已批准候选区块)被最终化时,approval 投票会停止——这对 approval 而言完全合理,因为它的唯一目的是保证无效区块不被最终化;
  • 但对争议而言,即使“危险”已经过去、攻击者没能让无效区块获批,我们仍然要对他们进行罚没(slash)。否则攻击者等于获得了一次“免费尝试”。这违背了 Polkadot 安全模型的基本假设——该模型建立在“无效区块被最终化的概率极低,攻击者在尝试足够多次之前就会破产”(gambler's ruin,赌徒破产性质)之上。

关注的攻击场景

协议明确要识别并威慑的攻击场景为:

  • 场景一(核心):中继链某一分支上被包含的平行链区块是坏的;
  • 场景二(附加):中继链某一分支上被 backing 的平行链区块是坏的;
  • 场景三(附加):在某条分支上被 seconded(第二签字)但从未被 backing 的区块是坏的。

对后两个场景的惩罚不会影响安全保证,却会引入大量技术挑战(详见 Dispute Coordinator 文档 中 “No Disputes for Non Included Candidates” 一节)。因此协议主动搁置(punt)后两类争议,以换取协议简化——只惩罚第一个场景。

数据可用性要求

如协议总览所述,检查一个平行链区块需要三份数据:

  • 平行链校验代码(Validation Code):链上可用、提前发布,保证中继链任何两条分支对某平行链的校验代码视图一致;
  • AvailableData(可用数据);
  • CandidateReceipt(候选收据)。

注意:只有场景一(区块已在某分支被包含)才保证数据必然可用。因此争议流程应从可用性(availability)过程开始,确保拿到AvailableData。若数据已可用,该过程会迅速结束;若不可用,则必须由争议发起者提供。

链上与链下双组件、Local 与 Remote 争议

争议同时有链上和链下两部分:

  • 罚没与惩罚在链上处理,因此争议双方的验证人投票必须上链;
  • 一条分支上的争议应移植(transpose)到中继链的所有其他活跃分支。“在所有历史中发生罚没”对威慑至关重要——攻击者不能因为网络已转向另一条未发生攻击的分支而携款逃脱。

由此引入local(本地)与 remote(远程)争议的区分——相对于某条特定分支而言:

  • Local 争议:处理场景一,即平行链区块在“我们正查看的这条分支”上被包含。此时这条链自该区块被 backing 的位置起已腐化,必须被丢弃;
  • Remote 争议:除此之外的所有争议。在链上处理一个“未在当前分叉中被包含”的区块时,无法区分三种攻击场景(可能在别处被包含、被 backing、或从未被 backing),链上处理逻辑完全相同。

二、争议发起(Initiation)

争议由任何发现自己对区块有效性的判断与另一份已发布声明相悖的验证人发起。由于中继链收集的所有声明(backing、approval)都隐含“有效”含义,争议只可能由“认为区块是坏的”的节点发起。

发起过程始于链下:验证人签署一条声明自己质疑该平行链区块有效性的消息,并通过链下通道通知所有其他验证人自己已知的与该区块相关的全部声明(可能是 backing 声明或审批检查声明)。值得注意的是:

没有专门的“发起争议”消息类型——发起与参与争议、投反对票使用的是同一条消息。因此共识不要求“谁发起了争议”,只要求“存在一个进行中的争议”。

在实际中,发起者通常是该区块的backer(担保者)或 approval checker(审批检查者):

  • 若执行结果被判定无效,验证人按上述方式发起争议;
  • 若争议发生在backing 阶段,发起者必须让数据对其他验证人可用;
  • 若争议发生在approval checking 阶段,数据已经可用。

关于 backing 阶段争议的 DoS 风险:当数据尚未在所有验证人间可用时,攻击者可能对少数正在检查区块的验证人发起 DoS,阻止他们把数据分发给参与争议的其他验证人。但这种攻击只可能在“包含前”(pre-inclusion)发生,危害有限,不构成安全关键问题。协议对此的假设是:

  • 攻击者只能限制验证人在有限时间内无法发出消息;
  • 存在一个侧信道:中继链的治理机制可以通过手动在链上提供完整 PoV(Proof of Validity,有效性证明)和候选收据来触发争议。

三、争议参与(Dispute Participation)

一旦得知争议存在,所有验证人都有义务参与。具体职责包括:

  1. 流通所有已知的关于该候选区块的声明——backing 声明、approval checking 声明、争议声明;
  2. 若我们已经就该候选区块发出过任何类型的声明,则不再继续;
  3. 下载AvailableData:优先从其他争议参与者或 backing 验证人处获取,其次通过 erasure-coding(纠删码)恢复 从所有验证人处获取;
  4. 提取校验代码:从任意最近的(recent)中继链区块提取。代码保证始终在链上可用,因此无需下载特定分叉;
  5. 执行区块校验:在校验代码下、用AvailableData执行该区块,检查所有输出是否正确,包括CandidateReceipt中的erasure-root;
  6. 发出争议参与声明,表明候选区块的有效性判断。

结论条件:当任意一方达到⅔ 绝对多数(supermajority)时,争议结束。

链上组件的行为

争议的链上组件具有以下行为:

  • 可被任意两条冲突的投票触发启动;
  • 同样等待任意一方达到 ⅔ 绝对多数;
  • 跟踪哪些平行链区块已被争议过:同一区块在任意特定分支上只能被争议一次;
  • 跟踪当前分支上已被包含的区块;
  • 当某平行链的争议被发起后,该平行链的包含(inclusion)被暂停,直到争议结束。

中继链区块作者应通过**内在函数(inherents)**为所有链尚未知晓的争议启动链上组件,并同时把所有相关声明提交给链上组件。

验证人获取争议声明的两条途径

  1. **通过 gossip(闲聊协议)**从其他验证人处接收;
  2. 从已导入的中继链区块中抓取(scraping)——这一途径同样用于跟踪 backing 等其它类型声明。

验证人为向链提供声明以及参与争议(无论哪一方)获得奖励;而争议的失败方将被罚没。

四、争议结论(Dispute Conclusion)

  • 争议大致在一方达到 ⅔ 绝对多数时结束;也可能永远不结束——仅当大多数验证人因某种原因无法投票时才会发生;
  • 链上争议即使在结论后也会保持开放一段时间以接收新投票;
  • 迟到的投票(在争议已达到 ⅔ 绝对多数之后才到达)也必须获得奖励,只是金额更少。

在 disputes-flow.md 中,这些规则以 if-then 条件形式被精确定义,并包含了几张状态机图。以下关键规则直接来自该文档:

  • 投票资格集合:在 backing 时刻负有职责的验证人集合,加上 backing 验证人的 backing 投票;
  • 验证人收到初始争议消息(包含至少两条对立投票的声明集合)、且本地无法重建 PoV 或代码时,必须向对等节点请求数据;
  • 争议可用性消息必须包含代码、持久化验证数据与有效性证明(PoV);
  • 只向已经投票的对等节点查询争议可用性数据;被查询的对等节点必须随机挑选;
  • 验证人必须保留代码、持久化验证数据和 PoV,直到包含争议决议的区块被最终化后再额外保留 24 小时;
  • 争议可用性 gossip 必须持续到争议决议之后,直到“决议后超时”(等同于接受额外迟到投票的超时时间)到期;
  • 远程争议定义为与“不在本地验证人活跃头部(active heads)中的链”相关的争议;
  • 所有收到的投票必须持久化;持久化投票保留N个 session,并逐 session 清理;
  • 投票必须可按签名公钥标识的验证人查询,也必须可按(session index,该 session 内的验证人 index)查询;
  • 若某区块同时存在反对票与赞成票,则检测到争议;一旦检测到争议,必须 gossip 该区块当前所有可用投票;
  • 收到争议投票后,验证人必须用自己的验证结果投票——用 backing 时刻的代码对 PoV 进行校验来决定;若验证人本人是 backer,则跳过重复校验与投票;
  • 赞成或反对票数(含 backing 投票)达到⅔ 绝对多数时,必须在链上记录结论,并对失败方与未投票者(no-shows)进行相应罚没;
  • 若争议决议判定区块无效,必须将该区块列入黑名单,避免在存在其他可用链时重新同步或继续在其上构建(细节见 GRANDPA 分叉选择规则);
  • 争议在决议后继续接受投票 1 天;决议后收到的投票仍会记录进状态根,只是奖励更少;状态根记录可以批量在超时到期时执行;
  • 若出现新的活跃头/链且该链尚未记录争议决议,必须把争议决议或进行中的争议也记录/移植到该链——争议必须在所有链上存在以确保作恶者被惩罚;
  • 验证人在两个对立方向上投票构成双重投票(double vote),与 backing、approval 情形相同;
  • 若争议未在规定时间内解决:所有验证人被罚少量金额,且进入治理模式由人工裁决;
  • 验证人意外重启后,争议应基于持久存储中的已投投票继续。

争议判定阶段状态机

文档用 Mermaid 状态图描述了争议从开启到结论的完整状态流转:

校验与投票阶段的状态机:

五、节点侧实现:Dispute Coordinator 子系统

Dispute Coordinator 文档 定义的协调器是节点侧参与争议的中央子系统,其核心实现在 node/core/dispute-coordinator/src/lib.rs。它封装了一个数据库,用于跟踪所有验证人在某个 session 窗口内观察到的声明,早于该窗口的投票被剪枝(prune)。

在源码层面,该子系统结构如下(node/core/dispute-coordinator/src/lib.rs):

  • DisputeCoordinatorSubsystem持有config(数据列col_dispute_data)、store(KV 数据库)、keystore与metrics;
  • 内部模块包括backend(数据库后端抽象)、db(版本化存储)、import(投票导入的纯处理逻辑)、participation(参与队列)、scraping(链上声明抓取)、spam_slots(垃圾争议防护)、status(争议状态跟踪)与initialized(收到第一个活跃叶子后的初始化)。

协调器的主要职责:

  1. 确保节点能在审批检查发现无效候选时提出争议:approval-voting子系统会发送DisputeCoordinatorMessage::IssueLocalStatement,指示投显式无效票;协调器负责创建并签名该显式无效票,并在该候选尚无进行中争议时触发争议;
  2. 确保 backing 与 approval 投票被记录上链:只有被协调器记录的投票才会被用于罚没;上链的投票保证已结论争议有可用的罚没目标,同时抓取这些投票对垃圾防护至关重要;
  3. 确保 backing 投票永远不会被显式投票覆盖;
  4. 协调实际争议参与:确保节点以能推动网络争议解决的方式参与任何合理争议,即使在大量争议并发(flood/DoS)时也能逐个解决;
  5. 确保争议即使在废弃分叉(abandoned forks)上的候选也能尽可能解决,以排除“免费尝试”、保证赌徒破产性质;
  6. 为链选择(chain selection)提供 API:防止任何包含“争议进行中或已判定无效候选”的链被最终化,避免在含无效候选的链上继续构建;
  7. 提供获取(已解决)争议及其全部投票(隐式的 approval/backing 与显式争议投票)的 API,使验证人能相应获得奖励或被罚没。

通过链抓取保证争议可被提出

提出争议要求节点能提供两条对立的投票。由于 backing 阶段的目的是让验证人“有切身利益(skin in the game)”,对立的有效票很可能就是 backing 投票(也可能是已投出的 approval 投票)。关键点在于:只要 backing 投票可用,任何节点都能提出争议。

因此协调器的一项核心职责是确保“所有可能被争议的候选”的 backing 投票都可用,实现方式是链抓取(chain scraping):每当候选在链上被 backing,就把该区块导入的 backing 投票记录到链存储中;协调器为所有未最终化区块查询这些投票,漏块时做必要的链回溯。

链抓取的高效性体现在两点:

  1. 投票已批量:一个候选的全部可用 backing 投票一次性导入;若像 candidate-backing 那样逐票导入,在当时的协调器实现下会退化为二次方复杂度;
  2. 总量更少:避免了为“从未在任何链上成功 backing 的候选”导入声明。

它在安全上也是成立的:争议只在 approval 阶段提出,节点只有在某链上看到候选被包含后才开始审批流程,而被包含的前提是曾被 backing——因此那时 backing 投票必然可用。信号按序处理,即使跳过一个区块、只在包含区块时才导入 backing 投票,在处理 approval 消息前也必然已看到它们。这意味着:记录链上 backing 投票足以支撑争议提出,无需预防性导入 approval 投票(后者被证明是极低效的过程——每候选约 35 票,二次方复杂度会累积)。

确保 approval 投票被记录:防止“懒惰的审批检查者”

只有协调器记录的投票会被用于罚没。虽然无需预防性记录 approval 投票,但协议仍努力在争议实际发生时记录 approval 阶段收到的投票:

  • 这不是结论争议所必需的(节点反正会发出自己的投票,无论是显式有效票还是已有 approval 票);
  • 真正的原因是防止**“懒惰审批检查者”**:若审批检查者能轻松地在争议发生后改投显式无效票,且只有这一票被记录,就能规避罚没;这样他们就没有动力真正运行校验函数,永远无风险地投“有效”票。

设计取舍与实现要求:

  1. 只在有争议时导入 approval 投票(否则日常开销浪费在例外情况上);
  2. 尽量批量导入,避免二次方复杂度;
  3. 考虑审批与争议并发进行(争议进行中 approval 投票仍在零星到达)。

因此由协调器主动向 approval-voting 索取争议候选的投票,而非让 approval-voting 感知争议后转发。索取时机的结论是:在争议结论的那一刻查询,同时为缓解 approval-voting 过早删除投票的竞态,在争议发起时和结论时各导入一次 approval 投票(发起时导入尽量覆盖废弃分叉上的投票,结论时导入最大化恶意 approval 投票的记录量)。诚实验证人可能因此校验两次(一次在 approval、一次在争议参与),但这在争议这种例外场景下是值得的。

确保链上导入:跨 session 边界的罚没可达性

  • Dispute Distribution 保证所有显式争议投票分发给当前出块者(当前权威集合),这对跨 era(session)变更很重要:若争议跨越验证人集合换届,新集合必须知道争议与投票才能上链;
  • 但分发只保证至少一条 backing/approval 票到达出块者,不保证所有 backer 的票都到达,approval 检查者的票更无保证——因为 approval 投票与最终化完全是链下过程,只在与最终化相关节点间传播,era 变更后新权威集合未必能见到上一 era 的 approval 投票;
  • 系统关键性质仍然成立:分发至少把一条“有效”票送到当前权威集合,故判定“无效”时至少有一个节点会被罚没;且现实中验证人集合很少 100% 更换,新旧集合常有重叠。文档同时指出,为保证最大程度的可追责,上一权威集合向下一集合传递投票(不依赖任何链)的能力仍在规划中。

争议参与协调与双队列设计

协调器通过链抓取或dispute-distribution导入投票得知争议;发现对立投票后记录争议存在,若本地尚无该争议的投票,则触发参与(恢复可用性并重新校验 PoV,结果成为节点的投票,交 dispute-distribution 分发)。

参与决策并不是“盲目参与一切”:

  • 只对真实争议投入大量工作,防止廉价 DoS 放大攻击——单条消息触发全网大规模校验与网络流量,使争议系统极易成为 DoS 放大器;
  • 提出争议本身是昂贵的:若你声称候选无效而它实际有效,你将被罚没——但这只在争议对象真实存在时才成立(不存在候选的争议永不结论、无人被罚没,好在参与这种争议相对便宜:各节点只发出数百条微小的可用性块请求,换来 “NoSuchChunk” 小响应)。

触发参与的前提(任一成立):

  • 看到争议候选被包含在至少一条链的未最终化区块中;
  • 看到争议候选被 backing 在至少一条链的未最终化区块中(至少说明候选不是凭空捏造);
  • 争议已被确认(confirmed):已有 1/3+1 节点参与——按威胁模型,至少有一个诚实节点已投票,争议必然真实。

注意节点可能暂时落后于链、先知道争议后才知道包含区块,因此必须在区块导入时重新评估参与决策。

处理顺序:参与需要(大致)全局有序,使多数验证人按相近顺序逐个解决争议:

  • 优先级队列(priority queue):对“已见被包含”的候选,按候选的 relay parent 区块号排序,同高度再按CandidateHash排序。此顺序全局唯一且优先处理更老的候选——若老候选无效可一次回滚整条链;若先解决新争议再回滚,则可能需回滚多次。这也让攻击者无法通过不断对新候选提争议来阻止回滚老候选;
  • 尽力队列(best-effort queue):对“未包含但已 backing 或 1/3+1 已参与(确认争议)”的候选,使用与优先级队列相同排序的尽力而为队列;一旦得知包含区块,会把相应参与请求提升到优先级队列(该提升机制在文档中标注为进行中工作)。

废弃分叉与最终化深度:被争议的链不会最终化,但文档强调仍要关注“落后于最终化链的分叉”上的候选(否则攻击者得知审批检查者后制造更优分叉即可逃脱罚没);同时也关注最终化链本身到一定深度为止。深度必须明显大于 approval-voting 的执行超时,否则在执行完成前信息已被丢弃,争议无法结论。

垃圾投票防护:Spam Slots

即使延迟参与,也不应盲目把到达的投票全量导入数据库(可能慢慢耗尽磁盘)。因此协调器实现了spam slots机制(源码见 node/core/dispute-coordinator/src/spam_slots.rs 所在目录):每次导入含无效票、且无法判断是否垃圾的投票时,为每个显式invalid投票签名参与者递增计数器。

计为潜在垃圾(potential spam)的条件(全部满足才递增):

  • 被争议候选在任何链上既未被包含也未被 backing;
  • 争议未被确认;
  • 我们尚未为该争议投票。

每当导入任何争议投票时检查这些条件:若发现争议并非潜在垃圾,则清除该争议候选哈希的 spam slots(为每个投过 invalid 的验证人递减计数);候选被 backing/包含时也会自然清除(链上 backing 投票导入时处理)。此设计的合理性在于:真正需要担心的只有实际争议投票——backing 投票导入本身有速率限制且只针对真实候选;approval 投票要到争议结论才导入;而真实争议投票必须包含显式invalid票,恶意节点至多占验证人的 1/3,因此垃圾磁盘占用上限为2 * vote_size * n/3 * NUM_SPAM_SLOTS(n为验证人数)。

特殊的 Backing 投票

Backing 投票有两重特殊性:

  1. 它是任何有效争议能发起时唯一保证存在的有效票;
  2. 它是唯一承诺更短执行超时的投票(BACKING_EXECUTION_TIMEOUT)——相比 approval 投票更宽松的超时。为妥善处理跨机器执行时间差异,罚没可能对 backing 投票比其他valid投票更激进。

因此导入时绝不能用另一张有效票覆盖 backing 票——它们不可互换。源码中 node/core/candidate-validation/src/lib.rs 定义了DEFAULT_BACKING_EXECUTION_TIMEOUT = Duration::from_secs(2),即 backing 阶段默认 2 秒的执行时限,从代码层面印证了这一差异。

数据库 Schema 与运行逻辑

Dispute Coordinator 文档 定义了基于 KV 数据库(提供write、read、iter_with_prefix操作)的存储结构:

("candidate-votes", SessionIndex, CandidateHash) -> Option<CandidateVotes> "recent-disputes" -> RecentDisputes "earliest-session" -> Option<SessionIndex>

每候选跟踪的元信息为CandidateVotes结构(对应 types/disputes.md 中的争议声明类型,源码实现在 node/primitives/src/disputes/mod.rs):

/// Tracked votes on candidates, for the purposes of dispute resolution. pub struct CandidateVotes { /// The receipt of the candidate itself. pub candidate_receipt: CandidateReceipt, /// Votes of validity, sorted by validator index. pub valid: Vec<(ValidDisputeStatementKind, ValidatorIndex, ValidatorSignature)>, /// Votes of invalidity, sorted by validator index. pub invalid: Vec<(InvalidDisputeStatementKind, ValidatorIndex, ValidatorSignature)>, } /// The mapping for recent disputes; any which have not yet been pruned for being ancient. pub type RecentDisputes = std::collections::BTreeMap<(SessionIndex, CandidateHash), DisputeStatus>; /// The status of dispute. This is a state machine which can be altered by the /// helper methods. pub enum DisputeStatus { /// The dispute is active and unconcluded. Active, /// The dispute has been concluded in favor of the candidate /// since the given timestamp. ConcludedFor(Timestamp), /// The dispute has been concluded against the candidate /// since the given timestamp. ConcludedAgainst(Timestamp), /// Dispute has been confirmed (more than `byzantine_threshold` have already participated/ or /// we have seen the candidate included already/participated successfully ourselves). Confirmed, }

DisputeStatus的源码实现在 node/primitives/src/disputes/status.rs,其迁移方法(confirm、conclude_for、conclude_against、is_possibly_invalid)体现了“结论优先于确认、反对结论优先于赞成结论”的状态机语义;ConcludedAgainst会覆盖ConcludedFor(除非大量验证人在双方同时参与,这被认为是不可达的)。

关键常量(源码可查):

  • DISPUTE_WINDOW: SessionWindowSize = 6(node/primitives/src/lib.rs)——争议投票的 session 窗口,文档要求对应至少 1 天;RuntimeInfo的 session 缓存 LRU 大小与之对齐(node/core/dispute-coordinator/src/lib.rs);
  • ACTIVE_DURATION_SECS: Timestamp = 180(node/primitives/src/disputes/status.rs)——ActiveDisputes查询返回“最近ACTIVE_DURATION_SECS秒内结论的争议”;
  • MAX_PARALLEL_PARTICIPATIONS(node/core/dispute-coordinator/src/participation/mod.rs)——同时进行的参与任务数上限,测试配置下为 1、常规为 3,防止参与管线被并发争议压垮。

主循环与消息处理(对应 Dispute Coordinator 文档 的 Functionality 一节):

  • 启动时:等待第一个OverseerSignal::ActiveLeaves,初始化RollingSessionWindow(含叶子哈希与DISPUTE_WINDOW),从 DB 加载活跃争议并初始化 spam slots;对每个加载的争议,若有本地投票则发DisputeDistribution::SendDispute,否则视情况入队参与;
  • 主循环(run_until_error)持续运行直到收到OverseerSignal::Conclude;
  • ActiveLeaves:把ActiveLeavesUpdate交给 ordering provider、更新 session 信息缓存与highest_session、session 窗口推进时剪枝 spam slots、抓取链上投票;
  • ImportStatements:三个主要职责——发起争议参与并发送已有本地 approval 票、把全部新投票持久化到 DB、对全部DisputeStatement::Invalid投票做垃圾防护;
  • RecentDisputes/ActiveDisputes:返回 DB 中全部近期争议 / 最近ACTIVE_DURATION_SECS内结论的争议;
  • QueryCandidateVotes:按(SessionIndex, CandidateHash)查询candidate-votes;
  • IssueLocalStatement:构造DisputeStatement(Valid/Invalid)→ 用 keystore 中 session 的平行链验证密钥签名(跳过voted_indices中的索引)→ 写入 DB → 发送DisputeDistributionMessage::SendDispute;
  • DetermineUndisputedChain:从block_descriptions起始逐一检查RecentDisputes,遇到进行中或结论为负的争议即停止,返回可视为“无争议”的最长链前缀。

六、网络层:Dispute Distribution 子系统

Dispute Distribution 文档 负责确保所有相关验证人知晓争议并持有相关投票,核心实现在 node/network/dispute-distribution/src/lib.rs。设计目标:对节点暂时不可用具有韧性、快速传播、网络开销合理、对垃圾具有韧性,并且“简单无聊”——争议真正发生时必须可靠工作。

关键设计选择:不是 gossip,而是请求/响应协议。为确保投票被可靠投递到所有相关验证人,该子系统采用请求/响应协议(请求为实际投票负载,响应为确认),获得应用层确认。

线上格式(Wire Format)

争议协议标识:"/<genesis_hash>/<fork_id>/send_dispute/1",请求包含:

struct DisputeRequest { /// The candidate being disputed. pub candidate_receipt: CandidateReceipt, /// The session the candidate appears in. pub session_index: SessionIndex, /// The invalid vote data that makes up this dispute. pub invalid_vote: InvalidDisputeVote, /// The valid vote that makes this dispute request valid. pub valid_vote: ValidDisputeVote, } /// Any invalid vote (currently only explicit). pub struct InvalidDisputeVote { pub validator_index: ValidatorIndex, pub signature: ValidatorSignature, pub kind: InvalidDisputeStatementKind, } /// Any valid vote (backing, approval, explicit). pub struct ValidDisputeVote { pub validator_index: ValidatorIndex, pub signature: ValidatorSignature, pub kind: ValidDisputeStatementKind, }

响应仅为enum DisputeResponse { Confirmed }。

投票恢复(Vote Recovery)协议:"/<genesis_hash>/<fork_id>/req_votes/1":

struct IHaveVotesRequest { candidate_hash: CandidateHash, session: SessionIndex, valid_votes: Bitfield, invalid_votes: Bitfield, } struct VotesResponse { /// All votes we have, but the requester was missing. missing: Vec<(DisputeStatement, ValidatorIndex, ValidatorSignature)>, }

发起与参与

  • 发起:节点发出第一条DisputeRequest即视为发起争议,消息必须同时包含一条invalid 票与一条 valid 票。SendDispute指令由协调器发出,携带本地节点的 invalid 票和某条 valid 票(如 backing 声明)。包含 valid 票是为了让任何节点(无论是否与链同步、是否见过 backing/approval 票)都能看出存在冲突投票、即存在有效争议——节点仍需检查争议投票是否当前有效而非过期;
  • 参与:收到DisputeRequest后,dispute-distribution 通过ImportStatements触发协调器导入投票;协调器决定是否参与并发出自己的SendDispute。若本地判定候选有效,SendDispute将携带本地签名的 valid 票与最初收到的 invalid 票。垃圾防护的有效性检查依赖dispute-coordinator完成。

发送、可靠性与速率限制

  • 目标节点:消息发送给争议发生 session 的全部平行链验证人(他们要参与争议),外加当前 session 的全部权威(他们不参与,但需要把声明写进区块);
  • 可靠性:只有收到确认才认为消息已送达,否则只要争议仍存活就持续重试;每次重试前通过ActiveDisputes询问协调器争议是否仍活跃;
  • 顺序:SendDispute消息按重要性顺序发送,重试时保持同序;
  • 速率限制(源码常量):接收侧RECEIVE_RATE_LIMIT = 100ms,发送侧SEND_RATE_LIMIT = 150ms(多 50ms 安全边际以避免触发接收侧限制导致消息被丢弃、声誉下降),见 node/network/dispute-distribution/src/lib.rs。

接收侧:防垃圾与批处理

接收侧的目标:尽快把新争议交给协调器(以便优先级排序)、尽量按争议批量投票(导入性能)、防止恶意节点耗尽资源、防止恶意节点阻止好争议结论、限制恶意节点通过批处理逻辑延迟投票导入。

  • 即时导入 + 批处理折中:新争议的首条投票立即导入,让协调器即时知晓并反馈有效性;确认有效后,后续投票批量收集再整体提交;
  • 批收集条件:直到最近BATCH_COLLECTING_INTERVAL内收到的唯一投票数少于MIN_KEEP_BATCH_ALIVE_VOTES才关闭批次发送(兼顾延迟与批量目标);
  • 诚实行为推断:诚实验证人每候选/争议只发一条消息,且投票前必须完整恢复可用性并校验候选,因此发送频率天然不高,可施加保守速率限制;
  • 攻击成本分析(文档给出的量化示例):以 1000 验证人为例、1/3 恶意(约 330 个),每人每 100ms 可发一条消息;若MIN_KEEP_BATCH_ALIVE_VOTES = 10、BATCH_COLLECTING_INTERVAL = 500ms、RATE_LIMIT = 100ms,则首批可开约 3300 个批次(每批 2 票),但为维持每批存活每 500ms 需 10 条新票流入,即每批需 10 个攻击者,批次数量被压制到约 330;下一秒所有消息都用于维持批次,因此内存硬上限约为330 批 × 330 票 × 100 字节/签名 ≈ 10 MiB。若验证人规模达到 10,000,最坏情况进入 GB 级,需更严格速率限制或提高批存活所需投票率。对千级验证人,约 1000 的批上限在实践中不会触及,从而几乎不会因资源限制丢弃有效争议;
  • 启动:节点启动无需特殊处理,协调器会通过SendDispute告知进行中的争议。

韧性(Resiliency)

针对三类缺口——非验证人节点可能对未上链投票感兴趣、节点可能错过 backing/approval 投票(从链上恢复因 runtime 升级与无类型 extrinsics 而困难昂贵)、era 变更后新权威集合看不到旧 approval 投票——引入低优先级的req_votes请求/响应协议:

  • 接收方:看发送方缺哪些已知投票并回复;若发送方有己方没有的投票,则回发IHaveVotesRequest并记录对方的知识;
  • 发送时机:收到FetchMissingVotes指令时,或争议活跃期间约每区块一次向某个随机验证人发送;
  • 垃圾防护:每验证人每 slot 接受一次此类请求,更频繁的请求或陈旧数据请求可丢弃;非验证人节点的请求按尽力而为处理。

七、争议声明类型(链上类型层)

争议的链上数据模型定义在 roadmap/implementers-guide/src/types/disputes.md:

/// A set of statements about a specific candidate. struct DisputeStatementSet { candidate_hash: CandidateHash, session: SessionIndex, statements: Vec<(DisputeStatement, ValidatorIndex, ValidatorSignature)>, } /// A statement about a candidate, to be used within some dispute resolution process. enum DisputeStatement { /// A valid statement, of the given kind Valid(ValidDisputeStatementKind), /// An invalid statement, of the given kind. Invalid(InvalidDisputeStatementKind), } /// 各类声明与候选哈希、session 索引、验证人公钥与签名组合即可复现并校验原始声明。 enum ValidDisputeStatementKind { Explicit, BackingSeconded(Hash), BackingValid(Hash), ApprovalChecking, } enum InvalidDisputeStatementKind { Explicit, }

这解释了“发起与参与共用同一消息”的设计:DisputeStatement只有Valid/Invalid两个方向,有效票内部再细分Explicit(显式争议票)、BackingSeconded/BackingValid(backing 阶段的两种 attestation)与ApprovalChecking(审批检查票);无效票目前只有Explicit一种——这与“只有投显式无效票才可能构成垃圾”的 spam slot 设计相呼应。DisputeState(validators_for/validators_against位图、start、concluded_at)与ScrapedOnChainVotes(含 session、各候选的 backing 验证人列表、链上内在函数记录的已结论争议集合)则定义了链上状态与链下协调器之间的桥接数据结构。

八、与最终化(GRANDPA)的联动及范围外设计

审批协议中明确要求对 GRANDPA 投票策略做配套修改(见 protocol-approval.md 的 “Finality GRANDPA Voting Rule” 一节):诚实节点只投票给所有平行链候选都已获批的链,且不投票给存在进行中争议、或争议已判定候选无效的链。这样最终化链只包含“多数人认为其内每个区块都得到充分批准”的区块。

明确不做的事(Out of Scope),见 Dispute Coordinator 文档:

  1. 不对未包含候选发起争议:只关心已在至少某条链上包含(即可用性达成)的候选。可用性系统就是为“可靠检查并罚没恶意 backer”设计的;未包含候选不构成威胁,对其争议会非常脆弱(无可用性保障)且垃圾防护远更难——所有上文策略都会失效。可能提出此类争议的也只有诚实 backing 节点或 collator(collator 数量无限、无法对其收费,垃圾问题更严重),而诚实 backer 等可用性达成后再争议也更有意义;
  2. 不对已最终化区块发起争议:对最终化区块争议为时已晚(桥已处理该区块、损害已成事实),只能靠治理善后。允许这种争议只带来两个价值——至少仍能罚没攻击者、以及冻结链到治理模式,但两者都收益有限且会引入巨大的 DoS 面。禁止争议已最终化区块极大减少了可被争议的候选数量,是 DoS 防护的基石:它配合“争议负载高时限制包含”“最终性滞后时出块自然减速”等措施,保证系统能跟上争议处理。

结语

Polkadot 的争议协议是“检测—升级—后果”三段式安全链的收尾环节:检测与升级由 Approval 协议完成,争议则确保每一次作恶尝试都被记账并受到罚没。它的关键设计——分叉无关性、local/remote 争议区分、所有历史罚没、⅔ 绝对多数结论、垃圾争议多重防护(spam slots、速率限制、批处理上限、双参与队列)——共同保证了“无效区块永远无法免费进入最终化链,攻击者在赌徒破产之前无法积累足够尝试”。读者可以进一步在源码中追踪本文提到的各个组件:协调器主循环见 node/core/dispute-coordinator/src/lib.rs,争议状态机见 node/primitives/src/disputes/status.rs,分发协议与速率限制见 node/network/dispute-distribution/src/lib.rs,声明类型见 types/disputes.md,完整流程条件化描述见 disputes-flow.md。

  • 区块链

【免费下载链接】polkadot

Polkadot Node Implementation

项目地址:https://gitcode.com/gh_mirrors/po/polkadot
点击查看免费下载
上一篇:Vitis AI量化工具使用指南:提升模型性能的关键步骤
下一篇:Playwright-Skill 战略指南:AI驱动的浏览器自动化架构深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询