见证人节点起来之后, 会周期行的检查是否轮到自己出块了, 判断自己是否能出块的逻辑在 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
之类的名字会好些 :P
(PS: 这篇的内容貌似已经有点涉及 DPoS 啦)