Raft 共识协议
Raft 共识具有更快的出块时间、交易确定性和按需创建块。
GoQuorum 使用 etcd implementation . 进行 Raft 共识。
将 Raft 用于封闭成员 / 联盟设置,其中:
Raft 共识不会创建不必要的空块,并有效地按需创建块。
Raft 节点
Raft 节点 和以太坊节点的概念是不同的。
一个 Raft 节点可以是:
领导
追随者(也称为验证者或同行)
学习者。
一个集群有一个领导者,所有的日志条目都流经领导者。
可以将其他跟随者节点或学习者节点添加到正在运行的网络中。
领导
领导节点:
铸造并向跟随者和学习者节点发送块。
连任时参与投票,未获得多数选票则成为追随者。
可以添加和删除学习者和跟随者节点。
可以将学习者节点提升为跟随者。
如果领导节点失败,则触发重新选举。
追随者
一个跟随节点:
跟随领导。
应用领导者铸造的块。
连任时参与投票,获得多数票即成为领导者。
向领导发送确认。
可以添加和删除学习者和跟随者节点。
可以将学习者节点提升为跟随者。
学习者
学习者节点:
跟随领导。
应用领导者铸造的块。
连任期间不能参加投票。
必须被领导者或追随者提升为追随者。
无法添加学习者和跟随者节点或将学习者节点提升为跟随者。
无法删除其他学习者和跟随者节点。
可以自行去除。
Raft quorum
添加或删除关注者,会改变 Raft 法定人数。 添加或删除学习者不会改变 Raft quorum
将节点添加到长时间运行的网络时,我们建议将该节点添加为学习者。 一旦节点与网络完全同步,就可以提升学习者节点。
Raft 和 Ethereum 节点
在使用工作量证明共识的以太坊网络中,网络中的任何节点都可以挖掘新区块。 也就是说,没有领导者、追随者或学习者节点。
在 Raft 中, Raft 和以太坊节点之间是对应的。 每个以太坊节点也是一个 Raft 节点。 Raft 集群的领导者是唯一一个铸造新区块的以太坊节点。 铸币工负责像以太坊矿工一样在区块中包含交易,但不提供工作证明。
学习者节点是同步区块并提交交易的被动节点。
将领导者和铸币者放在一起的原因包括:
方便。 Raft 确保一次只有一个领导者。
为了避免从节点铸造块到所有 Raft 写入必须流经的领导者的网络跳跃。
GoQuorum 实现监视 Raft 领导层的变化。如果一个节点成为领导者,它就会开始铸造。如果一个节点失去领导权,它就会停止铸造。
Raft 通讯
Raft 使用 Ethereum P2P 传输层在节点之间传播交易。块仅通过 Raft 传输层进行通信。块由铸币机创建,并从铸币机流向集群的其余部分,始终以相同的顺序通过 Raft 。
当铸币者创建一个区块时,该区块不会被插入并设置为链的新头,直到该区块通过 Raft 。所有节点在应用 Raft 日志时以锁步的方式将链扩展到一起。
Raft 配置
常问问题
常见问题的答案可以在 GoQuorum FAQ page 主页上找到。
Raft 中交易的生命周期
在任何节点上(铸币者、追随者或学习者)
1. 使用对 GoQuorum 的 RPC 调用提交交易。
2. 使用 P2P 协议,将交易通知给所有对等方。 Raft 集群使用静态节点,因此每个交易都会发送到集群中的所有对等点。
在铸币者
1. 当交易到达铸币者时,交易通过交易池包含在下一个区块中(见 mintNewBlock )。
2. 区块创建会触发 NewMinedBlockEvent . 。 Raft 协议管理器通过订阅 minedBlockSub 来接收新的区块事件。 minedBroadcastLoop (在 raft/handler.go 中)将新块放入 ProtocolManager.blockProposalC 通道。
3. serveLocalProposals 正在通道的另一端等待。 serveLocalProposals 编码块并将块提议给 Raft 。 一旦区块流经 Raft ,这个区块很可能成为区块链的新头(在所有节点上)。
在每个节点上
1. Raft 达成共识并将包含该块的日志条目附加到 Raft 日志中。在 Raft 层, leader 向所有 follower 发送一个 AppendEntries ,所有 follower 都确认收到消息。一旦领导者收到了 quorum 的确认,领导者就会通知每个节点新条目已永久提交到日志中。
通过 Raft 穿过网络后,区块到达 eventLoop 。 eventLoop 处理 Raft 日志条目。该块通过 pm.transport ( rafthttp.Transport 的一个实例)从领导者到达。
该块由 applyNewChainHead 处理。 applyNewChainHead 检查区块是否延伸链(即父级区块是否为链的当前头)。如果该块没有扩展链,则该块将作为无操作被忽略。如果该块确实扩展了链,则该块被 InsertChain 验证并写入为链的新头。
发布 ChainHeadEvent 以通知侦听器已接受新块。 ChainHeadEvent :
从交易池中移除相关交易。
如果有更多交易未决,则在 (minter.go) 中触发 requestMinting 以安排新区块的铸造。
该交易现在在集群中的所有节点上都可用,并具有最终性。 Raft 保证日志条目的单一排序。承诺的所有内容都保证保留,因此不会分叉基于 Raft 的区块链。
以太坊中的 Raft 实现
链条延伸和竞争
Raft 负责就哪些区块应该被接受到链中达成共识。 在最简单的场景中,通过 Raft 的每个块都成为链的新头。
偶尔会有一个新的区块通过 Raft ,但它不能成为链的新头。 如果 Raft 在应用 Raft 日志排序时遇到其父级节点不是链头的块,则该日志条目将作为无操作跳过。
在 Raft 中, Raft 集群的领导者是唯一一个铸造新区块的以太坊节点。 在领导层变更期间,两个节点可能会在短时间内铸造区块。 如果同时铸造两个节点,则存在竞争,第一个成功扩展链的区块将获胜。 丢失的块被忽略。
竞争示例
尝试扩展链的 Raft 条目表示为:
在哪里:
0xbeda 是新区块的 ID
0xacaa 是新区块的父级 ID 。
初始铸币者(节点 1 )被分区,节点 2 接管作为铸币者。
一旦分区恢复,节点 1 在 Raft 层重新提交块 0x2c52 。 生成的序列化日志是:
因为 0x2c52 (输家)在 0xf0ec (赢家)之后被序列化,所以 0x2c52 不延伸链。应用条目时, 0x2c52 的父级不是链的头部。 0xf0ec 扩展了同一个父级节点 (0xbeda) ,并用 0x839c 再次扩展了链。
每个块都被 Raft 接受并在日志中序列化。 Extends 或 No-op 指定出现在实现中的更高级别。对于 Raft ,每个日志条目都是有效的。在 GoQuorum Raft 实现中,并非所有条目都用于扩展链。链扩展逻辑是确定性的。也就是说,在集群中的每个节点上都会发生完 全相同的行为,从而保持区块链同步。
与工作量证明不同,使用 Raft 共识的区块链状态是一致的,不会分叉。当在新链头添加一个区块时,该区块是为整个集群添加的,并且是永久性的。
投机铸币
投机铸造是一种优化,可在块之间提供更低的延迟(即更快的交易终结)。
在创建下一个区块之前同步等待一个区块成为新的链头会增加交易进入链所需的时间。
在投机铸造中,在父级区块通过 Raft 进入区块链之前,会创建一个新区块并将其提交给 Raft 。投机区块可以重复创建,形成投机链。
当投机链形成时,池中已包含在投机链中尚未进入区块链的区块中的交易子集将被跟踪。交易的子集称为提议交易(参见 speculative_chain.go )。
当提议的交易进入链时,会发生 core.ChainHeadEvent 。
由于竞争,投机链中的区块并不总是包含在链中。当投机链中的块没有进入链时,会发生 InvalidRaftOrdering 事件。当 InvalidRaftOrdering 事件发生时,推测链的状态被更新。
投机链中的状态
head: 最后创建的投机区块。如果最后创建的块已包含在区块链中,则为 nil 。
proposedTxes :在某个区块中已向 Raft 提出但尚未包含在区块链中的一组交易。
unappliedBlocks :已提交给 Raft 但尚未提交给区块链的区块队列。
当铸造一个新块时,我们将它排在这个队列的末尾。
当它被区块链接受时, accept 被调用以删除最旧的投机块。
当 InvalidRaftOrdering 事件发生时,队列通过从队列的新末端弹出最近的块来展开,直到我们找到无效块。较新的投机区块被反复删除,因为它们都依赖于尚未包含在链中的区块。
expectedInvalidBlockHashes :建立在无效块上但尚未通过 Raft 的块集。当非扩展块通过 Raft 返回时,它们将从投机链中删除。 expectedInvalidBlockHashes 是防止在不应该修剪投机链时试图修剪它。