14 个主要重大版本发布
18.0.0 | 2023年2月26日 |
---|---|
17.0.0 | 2023年2月19日 |
16.0.0 | 2023年2月12日 |
15.0.0 | 2023年2月5日 |
0.0.0 |
|
#4 in #finality
62 每月下载量
用于 beefy-gadget-rpc
320KB
6K SLoC
BEEFY
BEEFY (Bridge Efficiency Enabling Finality Yielder) 是一个在 GRANDPA Finality 上运行的辅助协议,用于支持与非 Substrate 区块链的高效桥接,目前主要针对 ETH 主网。
它可以被认为是一个针对 GRANDPA Finality 协议的可选桥接设备。它依赖于 GRANDPA 的许多假设,并且必须构建在其上才能正确运行。
BEEFY 是一个共识协议,旨在考虑高效的信任桥接。这意味着构建 BEEFY 协议的轻客户端应针对受限环境进行优化,例如 Ethereum 智能合约或链上状态转换功能(例如 Substrate 运行时)。请注意,BEEFY 不是一个独立的协议,它旨在与 GRANDPA 一起运行,GRANDPA 是为 Substrate/Polkadot 生态系统创建的最终性设备。更多关于 GRANDPA 的信息,请参阅 白皮书。
上下文
桥接
我们希望能够“桥接”不同的区块链。我们通过安全地共享和验证有关每个链状态的信息来实现这一点,即区块链 A
应该能够验证区块链 B
是否在块 #X 上。
最终性
区块链中的最终性是一个概念,意味着在给定块 #X 被最终确定后,它将不会被撤销(例如,由于重组)。因此,我们可以确保该块中存在的任何交易都不会被撤销。
GRANDPA
GRANDPA 是我们的最终性设备。它允许一组节点就主链达成 BFT 协议。它要求 2/3 的验证者集就主链的前缀达成一致,然后它将变得最终确定。
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的目标
- 允许自定义加密以适应不同的目标。最终支持阈值签名。
- 最小化“签名有效负载”和最终性证明的大小。
- 统一数据类型,并使用向后兼容的版本控制,以便协议可以扩展(附加有效负载、不同的加密)而不会破坏现有的轻客户端。
由于BEEFY需要在GRANDPA之上运行。这使得我们可以采取一些捷径
- BEEFY验证者集与GRANDPA的相同(即相同的抵押演员),它们可能由不同的会话密钥来标识。
- BEEFY运行在已最终确定的规范链上,即没有分叉(注意行为不当部分)。
- 从单个验证者的角度来看,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证明的一次尝试。《轮次号》简单地定义为验证者正在投票的区块号,或者更准确地说,是该区块号的承诺。当下一个轮次开始时,轮次结束,这可能会在以下事件之一发生时发生
- 节点收集了该轮次的2/3+1个有效投票。
- 或者节点接收到对当前最佳BEEFY块更大的块的BEEFY证明。
在这两种情况下,节点将使用“轮选择”程序来确定新的轮数。
普通节点应
- 接收并验证当前轮次的投票,并将它们广播给其对等节点。
- 接收并验证BEEFY证明,并将它们广播给其对等节点。
- 根据要求返回强制块的BEEFY证明。
- 可选地,根据要求返回非强制块的BEEFY证明。
验证者预期还必须
- 产生并广播当前轮次的投票。
只有当这些参与者相信他们与网络的其余部分保持最新,即他们完全同步时,他们才应完全参与协议。在此发生之前,节点应继续处理导入的BEEFY证明和投票,而不会主动投票。
轮选择
每个节点(包括普通节点和验证者)都需要本地确定他们认为的当前轮数。选择基于他们对以下内容的了解:
- 最佳GRANDPA最终化块编号(
best_grandpa
)。 - 最佳BEEFY最终化块编号(
best_beefy
)。 - 当前会话的起始块(
session_start
)。
会话是指一个时间段(或者说块的数量),在此期间验证者集(密钥)不会改变。有关在FRAME
上下文中的实现细节,请参阅pallet_session
。由于我们依赖于GRANDPA,BEEFY的会话边界与GRANDPA的边界完全相同。
从BEEFY协议的角度,我们定义两种类型的块
- 强制块
- 非强制块
强制块是必须具有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的幂。
换句话说,下一个轮数应该是没有证明的最旧强制块,或者是最高的GRANDPA最终化块,其块编号与best_beefy
块的差是2的幂。轮选择的心智模型是首先最终化强制块,然后尝试选择一个块,考虑到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的幂次部分大于最小增量时,我们才启动新的阶段。请注意,如果round_number > best_grandpa
,验证者不应启动任何阶段。
追赶
每个会话都保证至少有一个BEEFY最终化的区块。然而,这也意味着,即使已经开始了新的会话(即链上组件已选择新的验证者集,GRANDPA可能已经最终化了过渡),也必须结束强制区块的阶段。在这种情况下,BEEFY必须“追赶”之前的会话,并确保结束强制区块的阶段。请注意,较旧的会话必须显然由当时的时间点上的验证者集最终化,而不是最新的/当前的验证者集。
初始同步
当节点与网络完全同步时,一切都很顺利。然而,在冷启动期间,它将很难确定当前的阶段编号。因此,没有完全同步的节点不应参与BEEFY协议。
在同步过程中,我们还应确保检索所有强制块的BEEFY证明。这可以异步完成,但验证者在开始投票之前,需要确信包含在强制区块上有结论的阶段的最新会话,以便启动追赶过程。
八卦
参与BEEFY协议的节点应传播消息。该协议定义以下消息
- 当前阶段的投票
- 最近结束阶段的BEEFY证明
- 最新强制块的BEEFY证明
每条消息都与一个主题相关联,该主题可以是
- 阶段编号(即与特定阶段相关联的主题)
- 或全局主题(与阶段无关)
特定于阶段的主题应仅用于传播投票,其他消息应定期在全球主题上传播。让我们深入了解消息的描述。
-
投票
- 投票在特定于阶段的主题上发送。
- 当以下条件满足时,投票被认为是有效的
- 承诺与本地承诺相匹配。
- 验证者是当前验证者集的一部分。
- 签名是正确的。
-
BEEFY证明
- 证明在全球主题上发送。
- 当以下条件满足时,证明被认为是值得传播的
- 它是最近(实现特定)阶段或最新强制阶段。
- 所有签名都是有效的,并且至少有
2/3rd + 1
是有效的。 - 签名者属于当前验证者集。
- 必须的正当理由应定期公布。
不当行为
与其他PoS协议类似,BEEFY认为同一轮次投两个不同的票是不当行为。也就是说,对于特定的 round_number
,验证者为两个不同的 Commitment
生成签名并将它们广播出去。这被称为 equivocation。
除此之外,对错误的 payload 进行投票也被认为是不当行为,并且由于我们依赖GRANDPA,因此在分叉时验证者应该投票给哪个方面没有歧义。
不当行为应受到惩罚。如果更多的验证者在相同的 round
中不当行为,那么惩罚应该更严重,最多达到我们达到 1/3rd + 1
验证者不当行为时整个抵押的代币。
Ethereum
BEEFY的初始版本是为了实现与Ethereum的高效桥接而制作的,其中轻客户端是一个编译为EVM字节的Solidity智能合约。因此,BEEFY初始加密方案的选择是:secp256k1
和使用 keccak256
哈希函数。
未来:支持多种加密
虽然BEEFY目前使用 secp256k1
签名,但我们打算在未来支持多种签名方案。这意味着可能存在多种类型的 SignedCommitment
,并且只有它们一起才能形成一个完整的 BEEFY Justification
。
BEEFY密钥
BEEFY目前使用的加密方案是 ecdsa
。这与常用的其他方案(如 sr25519
和 ed25519
)不同,这些方案通常用于Substrate配置中的其他组件(BABE、GRANDPA、AuRa等)。最明显的区别是,一个 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轻客户端
待办事项
依赖关系
约54–94MB
约1.5M SLoC