Substrate Babe分析
Substrate Babe分析
BABE(Blind Assignment for Blockchain Extension)共识模块,收集在链上VRF输出的随机数,并且管理epoch的交易。Polkadot的BABE共识引擎借鉴了很多Cardano的Ouroboros算法,可以查看我之前对该算法的解读。BABE算法不同于Polkadot上简单的Arua算法,BABE要做到下一个出块节点不是按部就班的,而是需要随机挑选出来,而且这个过程要是不可预知的。
基于slot的区块生产机制,使用一个VRF(可验证随机函数)这样的伪随机数生成器来随机执行slot分配给出块节点。在每个slot上,所有出块节点都使用VRF函数生成一个新的随机数,如果它低于给定的阈值(与他们的weight/stake成比例),他们有权生成一个块。VRF函数执行的证明将被其他同行用来验证插槽声明的合法性。
在BABE共识引擎中,时间被分为slot,每个slot只能生产一个区块。多个slot为一个epoch,上面的weight的计算以每个epoch开始前的历史来计算。每个epoch的开头有个genesis block,不会上链,而是当前这个矿工自己在内存中生成,这个genesis block会记录好当前这个epoch中的可能参与出块的stakeholder候选人。每个节点独立运行代码,根据当前epoch中genesis block的权益,slot的index为输入,根据概率获得当前这个slot应该由谁出块。假如发现自己出块,执行打包交易等操作,还要生成一个随机数。假如不是自己出块,等待出块节点出块并广播。在当前epoch中不断重复上面这个出块逻辑,直到这个epoch中的所有slot结束。在整个epoch的过程中会生成一个在这个epoch参与出块者们(slot leaders)都共同认可的随机数。在自己的内存记录好这个随机数以及下一个epoch参与的stakeholders,开启下一个epoch周期。
BABE引擎还负责在链上收集entropy,用于给定的VRF生成伪随机数的seed。epoch是一个连续的槽数,我们将在其中使用相同的出块者集合。在epoch期间,由于块产生而产生的所有VRF输出将在链上随机池中收集。epoch的变化提前一个epoch宣布,即在结束epoch N时,我们宣布epoch N+2的参数(随机性、出块者集合等)。
由于插槽分配是随机的,所以可能将一个插槽分配给多个验证器(在这种情况下,我们将拥有一个临时fork),或者将一个插槽分配给没有验证器(在这种情况下,不会产生任何块)。这意味着块时间是不确定的。
协议有一个参数c[0,1],其中 1 - c
是一个插槽为空的概率。此参数的选择将影响与最大可容忍网络延迟相关的协议的安全性。
除了上述基于vrf的插槽分配(我们将其称为主插槽)之外,该引擎还支持确定性的辅助插槽分配。主插槽优先于辅助插槽,在出块时,节点首先尝试声明主插槽,然后返回到辅助插槽申明尝试。辅助插槽分配是通过挑选出块节点集合索引来完成的:
blake2_256(epoch_randomness ++ slot_number) % authorities_len。
fork选择规则是基于重量的,其中重量等于链中主块的数量。我们将选择最重的链(更多的主要块),在平局时并将采取最长的。
在BABE中,所有验证出块者拥有相同的抵押,所以其被选为slot leaders的概率是相同的。关键是随机,需要随机选出出块节点,并且被全网认可,并且还不能被操控,也不能被预测。
const
- epochDuration 200 // 一个slot的总时长,一个epoch包含200个区块时间
- expectedBlockTime 3000 // BABE出块的时长,预期的平均时长,3秒钟
BABE需要一些逻辑在每一个区块中通过询问是否结束这个epoch,然后把交易放到下一个交易中去,这样的询问就是一些触发机制。
一般上,外部触发机制应该被用到,当没有其他模块对于改变出块节点集合响应的时候,一个内部触发机制会被用到。
BABE有一个epoch的概念,是一个固定数量slots组成的,其validator集合是一样的,在运行时上下文中,一个epoch跟一个session是一样的。
每一个epoch有其自己的randomness,其完全提前取决于一个epoch之前,但在叉上可能不同于1个epoch。
出块节点通过计算slot number和epoch随机性来证明它赢得了一个slot,并证明它落在一个阈值以下,该阈值是根据它们在总权值中所占的比例来动态计算的。
Storage
- authorities(): Vec<(AuthorityId,BabeAuthorityWeight)> // 现在epoch中的出块认证者们
- currentSlot(): u64 // 现在slot的数量
- epochIndex(): u64 // 现在epoch索引
- genesisSlot(): u64 // 初始slot数字
- initialized(): Option<MaybeVrf> // 临时值(在块结束时清除),如果当前块已经调用了每个块的初始化,则为“Some”。
- nextRandomness(): [u8;32] // 下一个epoch的随机数
- randomness(): [u8;32] // 现在epoch的随机数
- segmentIndex(): u32 // 在构建时的随机数索引
- underConstruction(u32): Vec<[u8;32]> //
在链初始化的时候,可以添加进去一些初始的出块节点。
add_extra_genesis {
config(authorities): Vec<(AuthorityId, BabeAuthorityWeight)>;
build(|config| Module::<T>::initialize_authorities(&config.authorities))
}
结束一个session,是session模块中调用 should_end_session()
在其on_initialize()
处理器中。
如果一个出块认证节点,在一个相同的slot中,遗漏了两个或更多的区块,就会被惩罚slash到。
决定BABE中slot时间长短是Timestamp模块中的配置。
我们将最小的区块周期延长一倍,这样每个出块节点都可以在大部分时间内提出建议。
当每一个节点should_epoch_change
返回true时,就需要执行epoch change,enact_epoch_change
.