Graphene 源码阅读 – 番外篇 – 出块判断逻辑

见证人节点起来之后, 会周期行的检查是否轮到自己出块了, 判断自己是否能出块的逻辑在 maybe_produce_block()方法中, 这里面的第一步就是判断自己是不是和网络同步了, 同步了的话就继续下面的判断, 没有同步的话就直接返回 not_synced 错误告诉外面不要出块.

211 block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block( fc::limited_mutable_variant_object& capture )
212 {
213    chain::database& db = database();
214    fc::time_point now_fine = fc::time_point::now();
215    fc::time_point_sec now = now_fine + fc::microseconds( 500000 );
216
217    // If the next block production opportunity is in the present or future, we're synced.
218    if( !_production_enabled )
219    {
220       if( db.get_slot_time(1) >= now )
221          _production_enabled = true;
222       else
223          return block_production_condition::not_synced;
224    }

正常情况下, 第一次启动时 _production_enabled 的值是 false, 因此这段逻辑确保了在同步到最新块之前本节点肯定不会出块的, 否则就分叉了; 而一旦达到过一次同步状态, _production_enabled 就会置为 true, 我搜索了全部代码, 程序运行周期中没有其它地方会将 _production_enabled 再置为 false 了, 而且 maybe_produce_block() 方法中后面也没有对节点同步状态进行检查的逻辑了, 这就意味着只要见证人节点曾经同步到最新过, 并且始终没有宕机没有重启, 那么就算后来节点不再处于同步状态, 照样也能出块?

这个行为我诈一看感觉不太合理, 按理说节点如果没同步的话那肯定也得直接返回 not_synced 不能让出块呀! 所以 maybe_produce_block() 这段逻辑得改改, 于是我计划写个 issue 然后提个 patch, 修改方案已经想好了, head_block_time() + block_interval()now 比较, 如果前者大就说明节点已经同步到最新状态了, 否则就说明节点显然已经落后了 (这也是上述代码中 db.get_slot_time() 方法的逻辑). 这样一来上面这段代码可以改成类似如下这样就可以了:

211 block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block( fc::limited_mutable_variant_object& capture )
212 {
213    chain::database& db = database();
214    fc::time_point now_fine = fc::time_point::now();
215    fc::time_point_sec now = now_fine + fc::microseconds( 500000 );
216
217    // If the next block production opportunity is in the present or future, we're synced.
218    if( db.get_slot_time(1) < now )
219       return block_production_condition::not_synced;

本来 issue 都已经写完了, 然而在写完的时候我才意识到自己错了, 如果按照上面的说法做的话, 可能会导致从某个时间起所有见证人都不再出块, 用一个简单的例子说明. 假设有 A, B, C 三个见证人, 他们先按照 A -> B -> C 的顺序每人出了一个块, 然后顺序变成 B -> A -> C, 所以这时轮到 B 出块了, 然而不幸的是 B miss 了, 于是到 A, A 这时判断最新块的时间加上出块间隔发现小于当前时间, 按照上面的策略, 这个认为自己 out of sync 了, 所以 A 也不会出块, 到 C 时也是一样的情况, C 也认为自己 out of sync 于是 C 也不出块. 于是.. 就达到了一个没有节点出块的局面…

所以说 _production_enabled 实际上正是避免了出现这个局面, 只是它的名字可能有点让人困惑, 实际上它就是一个标识, 标识着当节点第一次达到了与全网同步的状态时, 它就初步具备了出块资格.

也许改叫 once-synced 之类的名字会好些 😛

(PS: 这篇的内容貌似已经有点涉及 DPoS 啦)

赞赏

微信赞赏支付宝赞赏

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.