25 个版本 (主要破坏)

23.0.0 2024年7月18日
21.0.0 2024年7月13日
20.0.0 2024年6月24日
19.0.0 2024年5月24日
0.0.0 2023年2月28日

#1 in #beefy

Download history 504/week @ 2024-05-03 528/week @ 2024-05-10 747/week @ 2024-05-17 900/week @ 2024-05-24 862/week @ 2024-05-31 1128/week @ 2024-06-07 538/week @ 2024-06-14 906/week @ 2024-06-21 742/week @ 2024-06-28 264/week @ 2024-07-05 595/week @ 2024-07-12 498/week @ 2024-07-19 367/week @ 2024-07-26 415/week @ 2024-08-02 488/week @ 2024-08-09 675/week @ 2024-08-16

2,010 个月下载量
用于 13 个 crate (4 直接)

GPL-3.0-or-later…

3MB
67K SLoC

BEEFY

BEEFY (Bridge Efficiency Enabling Finality Yielder) 是一个在 GRANDPA 最终性协议上运行的二级协议,旨在支持与非 Substrate 区块链的高效桥接,目前主要用于 ETH 主网。

它可以被视为 GRANDPA 最终性协议的(可选)特定桥接 Gadget。该协议依赖于 GRANDPA 提供的许多假设,并且必须在它之上构建才能正确运行。

BEEFY 是一个以高效无信任桥接为设计目标的共识协议。这意味着 BEEFY 协议的轻客户端应该针对像 Ethereum 智能合约或链上状态转换函数(例如 Substrate 运行时)这样的受限环境进行优化。请注意,BEEFY 不是一个独立的协议,它旨在与 GRANDPA 一起运行,GRANDPA 是为 Substrate/Polkadot 生态系统创建的最终性 Gadget。更多关于 GRANDPA 的细节可以在 白皮书 中找到。

背景

桥梁

我们希望能够“桥梁”不同的区块链。我们通过安全地共享和验证每个区块链状态的信息来实现这一点,即区块链 A 应该能够验证区块链 B 位于区块 #X。

最终性

在区块链中,最终性是一个概念,意味着在给定区块 #X 最终化之后,它将不会被回滚(例如,由于重组)。因此,我们可以确信该区块中存在的任何交易都将不会被回滚。

GRANDPA

GRANDPA 是我们的最终性工具。它允许一组节点就主链达成 BFT 协议。它要求 2/3 的验证者集就主链的前缀达成一致,然后该前缀将最终化。

img

GRANDPA 最终性证明的困难之处

struct Justification<Block: BlockT> {
    round: u64,
    commit: Commit<Block>,
    votes_ancestries: Vec<Block::Header>,
}

struct Commit<Hash, Number, Signature, Id> {
    target_hash: Hash,
    target_number: Number,
    precommits: Vec<SignedPrecommit<Hash, Number, Signature, Id>>,
}

struct SignedPrecommit<Hash, Number, Signature, Id> {
    precommit: Precommit<Hash, Number>,
    signature: Signature,
    id: Id,
}

struct Precommit<Hash, Number> {
    target_hash: Hash,
    target_number: Number,
}

验证 GRANDPA 最终性证明的主要困难来自于投票者投票的内容不同。在 GRANDPA 中,每个投票者将投票支持他们认为的最新区块,协议将就具有 > 2/3 支持的公共祖先达成一致。

这导致了两个不效率的问题

  • 我们可能需要每个验证者的投票数据,因为它们可能都不同(即仅签名是不够的)。
  • 我们可能需要在最终性证明中附加一些头部信息,以便能够验证所有投票的祖先。

此外,鉴于我们的中间目标是连接到以太坊,也存在与“不兼容”的加密方案相关的困难。我们在 GRANDPA 中使用 `ed25519` 签名,在 EVM 中无法有效地验证。

因此,

BEEFY 的目标

  1. 允许定制加密以适应不同的目标。最终支持阈值签名。
  2. 最小化“签名有效载荷”和最终性证明的大小。
  3. 统一数据类型并使用向后兼容的版本控制,以便可以在不破坏现有轻客户端的情况下扩展协议(附加有效载荷、不同的加密)。

由于 BEEFY 需要运行在 GRANDPA 之上,这使我们能够采取一些捷径

  1. BEEFY 验证者集与 GRANDPA 的 相同(即相同的受委托参与者),他们可能由不同的会话密钥识别。
  2. BEEFY 在 最终化 的主链上运行,即没有分叉(注意行为部分)。
  3. 从单个验证者的角度来看,BEEFY 至多有一个活跃的投票轮次。由于 GRANDPA 验证者正在达到最终性,我们假设他们是在线的、连接良好的,并且对区块链的状态有相似的看法。

BEEFY 协议

心智模型

BEEFY 应该被视为 GRANDPA 验证者对当前最佳最终化区块进行的额外投票轮次。类似于 GRANDPA 落后于最佳生产(非最终化)区块,BEEFY 将落后于最佳 GRANDPA(最终化)区块。

                       ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
                       │      │ │      │ │      │ │      │ │      │
                       │  B1  │ │  B2  │ │  B3  │ │  B4  │ │  B5  │
                       │      │ │      │ │      │ │      │ │      │
                       └──────┘ └───▲──┘ └──────┘ └───▲──┘ └───▲──┘
                                    │                 │        │
     Best BEEFY block───────────────┘                 │        │
                                                      │        │
     Best GRANDPA block───────────────────────────────┘        │
                                                               │
     Best produced block───────────────────────────────────────┘

一个完全同步的 BEEFY 验证者的行为伪算法

loop {
  let (best_beefy, best_grandpa) = wait_for_best_blocks();

  let block_to_vote_on = choose_next_beefy_block(
    best_beefy,
    best_grandpa
  );

  let payload_to_vote_on = retrieve_payload(block_to_vote_on);

  let commitment = (block_to_vote_on, payload_to_vote_on);

  let signature = sign_with_current_session_key(commitment);

  broadcast_vote(commitment, signature);
}

细节

在我们详细描述BEEFY如何工作之前,让我们先同意我们将要使用的术语和系统中的参与者。网络中的所有节点都需要参与BEEFY网络协议,但我们可以识别出两个不同的参与者:普通节点BEEFY验证者。验证者预计将积极参与协议,通过产生和广播投票。投票简单地是他们关于《承诺》的签名。《承诺》由一个有效负载(从一个区块或该区块的状态中提取的不可见字节数据,预期是一种加密累加器(如Merkle树哈希或Merkle山脉根哈希))和一个来自该有效负载的区块编号组成。此外,《承诺》还包含该特定区块的BEEFY 验证者集ID。请注意,区块已经最终确定,因此即使使用区块编号而不是哈希,也没有歧义。一组投票,或者更确切地说,一个《承诺》和一组签名将被称为《已签名承诺》。有效的(见下文了解规则)已签名承诺也称为《BEEFY证明》或《BEEFY最终性证明》。有关实际数据结构的更多详细信息,请参阅BEEFY原始定义

《轮次》是BEEFY验证者尝试产生《BEEFY证明》的一次尝试。《轮次编号》简单地定义为验证者正在投票的区块编号,或者更确切地说,是该区块编号的《承诺》。当以下事件之一发生时,轮次结束,开始下一轮。

  1. 节点收集了该轮次2/3 + 1个有效投票。
  2. 或者节点收到了一个比当前最佳BEEFY区块更大的区块的《BEEFY证明》。

在这两种情况下,节点将使用“轮次选择”程序确定新的轮次编号。

普通节点预计将

  1. 接收并验证当前轮次的投票,并将它们广播给对等节点。
  2. 接收并验证《BEEFY证明》,并将它们广播给对等节点。
  3. 根据要求返回《BEEFY证明》。
  4. 可选地根据要求返回非强制区块的《BEEFY证明》。

验证者预计还将

  1. 产生并广播当前轮次的投票。

只有当这两种类型的参与者都相信他们与网络的其他部分保持同步时,即他们完全同步,他们才应完全参与协议。在此发生之前,节点应继续处理导入的《BEEFY证明》和投票,而不进行积极投票。

轮次选择

每个节点(普通节点和验证者)都需要本地确定他们认为当前轮次编号是什么。选择基于他们对以下内容的了解:

  1. 最佳GRANDPA最终化区块编号(best_grandpa)。
  2. 最佳BEEFY最终化区块编号(best_beefy)。
  3. 当前会话的起始区块(session_start)。

会话是指一个时间段(或者说是区块数量)内,验证者集(密钥)不发生变化的时期。有关在FRAME上下文中的实现细节,请参阅pallet_session。由于我们依赖于GRANDPA,BEEFY的会话边界与GRANDPA的边界完全相同。

从BEEFY协议的角度,我们定义两种类型的区块:

  1. 强制区块
  2. 非强制区块

强制区块是必须要有《BEEFY证明》的区块。这意味着验证者将始终在强制区块开始和结束一轮。对于非强制区块,可能存在也可能不存在证明,并且验证者可能永远不会选择这些区块开始一轮。

每个会话的第一个块被视为强制性块。会话中的其他块都是非强制性的,然而验证者被鼓励尽可能完成尽可能多的块,以实现轻客户端的更低延迟,从而最终用户。由于 GRANDPA 也将会话边界块视为强制性,因此 session_start 块将始终具有 GRANDPA 和 BEEFY 证明。

因此,节点使用以下公式确定当前轮次编号

round_number =
      (1 - M) * session_start
   +        M * (best_beefy + NEXT_POWER_OF_TWO((best_grandpa - best_beefy + 1) / 2))

其中

  • M 如果当前会话中的强制性块已经最终确定则为 1,否则为 0
  • NEXT_POWER_OF_TWO(x) 返回大于或等于 x 的最小的2的幂。

换句话说,下一个轮次编号应该是没有证明的最老强制性块,或者与 best_beefy 块的块号差是2的幂的最高GRANDPA最终化块。轮次选择的心理模型是首先最终确定强制性块,然后尝试选择一个块,考虑到BEEFY追赶GRANDPA的速度。在GRANDPA取得进展,但BEEFY似乎落后时,验证者应减少轮次更改的频率,以增加完成轮次的几率。

如前所述,每当节点选择新的 round_number(验证者投票)时,它就会结束上一个轮次,无论是否达到最终确定性(即轮次已结论)。

请注意,由于BEEFY只为GRANDPA最终化块投票,因此这里的 session_start 实际上意味着:“最新一个其开始被GRANDPA最终化的会话”,即块生产可能已经进展,但BEEFY需要首先最终化较老会话中的强制性块。

在良好的网络条件下,BEEFY最终可能会最终化每个块(如果GRANDPA也这样做)。实际上,由于块时间短,这将是罕见的,可能是过度的,因此建议实现引入一个 min_delta 参数,该参数将限制新轮次启动的频率。公式中受影响的组件将是:best_beefy + MAX(min_delta, NEXT_POWER_OF_TWO(...)),因此只有在2的幂部分大于最小delta时才启动新轮次。请注意,如果 round_number > best_grandpa,则验证者不应该启动任何轮次。

追赶

每个会话都保证至少有一个BEEFY最终化块。然而,这也意味着即使已经开始了一个新的会话(即链上组件已选择新的验证者集,GRANDPA可能已经最终化了过渡),强制性块的轮次也必须结束。在这种情况下,BEEFY必须“追赶”之前的会话,并确保为强制性块完成轮次。请注意,较老的会话显然必须由当时的时间点的验证者集最终化,而不是最新的/当前的验证者集。

初始同步

当节点与网络完全同步时,一切都很美好。然而,在冷启动期间,节点将很难确定当前轮次编号。因此,未完全同步的节点根本不应参与BEEFY协议。

在同步过程中,我们应确保同时获取所有强制块的BEEFY证明。这可以异步进行,但在验证者开始投票之前,需要确保最后一个包含强制块结束轮次的会话,以便启动追赶程序。

八卦

参与BEEFY协议的节点应八卦消息。该协议定义以下消息

  1. 当前轮次的投票,
  2. 最近结束轮次的BEEFY证明,
  3. 最新强制块的BEEFY证明,

每个消息还与一个主题相关联,该主题可以是

  1. 轮次编号(即与特定轮次关联的主题),
  2. 或全局主题(独立于轮次)。

轮次特定的主题应仅用于八卦投票,其他消息则在全球主题上定期八卦。现在让我们深入了解消息的描述。

  • 投票

    • 投票是在轮次特定的主题上发送的。
    • 当满足以下条件时,投票被视为有效
      • 承诺与本地承诺匹配。
      • 验证者当前是验证者集的一部分。
      • 签名是正确的。
  • BEEFY证明

    • 证明是在全局主题上发送的。
    • 当满足以下条件时,证明值得八卦
      • 是最近一轮(实现特定)或最新强制轮次。
      • 所有签名都是有效的,并且至少有2/3rd + 1是有效的。
      • 签字人是当前验证者集的一部分。
    • 强制证明应定期宣布。

不当行为

与其他PoS协议类似,BEEFY认为在相同轮次投两个不同的票是不当行为。即对于特定的round_number,验证者为两个不同的Commitment生成签名。这是所谓的equivocation

未来

基于BEEFY的智能合约将使用以太坊虚拟机(EVM)编写,并使用ERC-20代币作为代币。

代币

代币将用于支付验证者的奖励,并用于执行协议的操作。

验证者

验证者将负责验证交易,并确保网络的正确性。

奖励

BEEFY 当前使用的加密方案是 ecdsa。这与其他方案(如 sr25519ed25519)不同,这些方案通常用于其他 pallet(如 BABE、GRANDPA、AuRa 等)的 Substrate 配置。最明显的区别是,一个 ecdsa 公钥长度为 33 字节,而基于 sr25519 的公钥长度为 32 字节。因此,BEEFY 密钥在其他公钥中显得有点突出。

对于其他加密(使用默认的 Substrate 配置),AccountId(32 字节)与 PublicKey 匹配,但请注意,这对于 BEEFY 不是一个情况。因此,您不能将 AccountId 的原始字节转换为 BEEFY 的 PublicKey

生成或查看 BEEFY 公钥的十六进制编码或 SS58 编码的最简单方法是使用 Subkey 工具。使用以下命令生成 BEEFY 密钥:

subkey generate --scheme ecdsa

输出将类似于

Secret phrase `sunset anxiety liberty mention dwarf actress advice stove peasant olive kite rebuild` is account:
  Secret seed:       0x9f844e21444683c8fcf558c4c11231a14ed9dea6f09a8cc505604368ef204a61
  Public key (hex):  0x02d69740c3bbfbdbb365886c8270c4aafd17cbffb2e04ecef581e6dced5aded2cd
  Public key (SS58): KW7n1vMENCBLQpbT5FWtmYWHNvEyGjSrNL4JE32mDds3xnXTf
  Account ID:        0x295509ae9a9b04ade5f1756b5f58f4161cf57037b4543eac37b3b555644f6aed
  SS58 Address:      5Czu5hudL79ETnQt6GAkVJHGhDQ6Qv3VWq54zN1CPKzKzYGu

如果您的 BEEFY 密钥使用错误的加密方案,您将在节点启动时看到无效的公钥格式消息。基本上就像这样:

...
2021-05-28 12:37:51  [Relaychain] Invalid BEEFY PublicKey format!
...

BEEFY 轻客户端

待办事项

依赖关系

~73–115MB
~2M SLoC