在区块链上, 分叉是一个老话题了, EOS 也会出现分叉的情况, 下面我们来讲讲在分叉情况它是如何切换到最长链的。

EOS 的生产者是根据 active_schedule 的顺序来生产区块的,但是在决定当前区块生产者的时候是根据 block_time 来的选择的。 假如当前 BP1 生产了一个 block_num 为 100 的块,而BP2和BP3都接受到了,此时根据block_time是 BP2 生产 block_num 为 101 的块, 但 BP3 因为网络原因没收到 BP2 产生的块, 导致它也开始生产 block_num 为 101 的块了, 这时候出现了分叉。 那么哪条链会被最终确认呢,换句话说 2个 block_num 为 101 的块谁会被不可逆,谁会被抛弃, 这要看两条链上的生产者数量, 生产者多的那条链增长速度会比较快, 所以区块更快被确认,比另一条链先不可逆, 这时候就会切换到长的链,而丢弃短的链了。 一般情况下,其他节点接受完BP2的块后都在等待BP3的出块,BP3的出块将BP2的分支覆盖掉了, 基本所有节点都切换到了BP3的链上并开始生产,所以 BP2 的块被丢弃的可能性很大。

先讲下分叉的大致流程,当一个节点收到同个 block_num 的会都 insert 进 fork_multi_index_type 对应的表里,根据 block_state 的 header.previous 形成多叉树(不了解多叉树的读者可以百度一下哈)。每进一个需要分叉的块都会先切换到该块对应的链上。一旦 block_num 最小的确定是哪个块不可逆后,其他分叉下相同 block_num 的块就会被删除。至于为什么不直接把其他分叉全删了。 如果该分叉还有人在生产块,那么该块在接入节点得时候就会出现 unlinkable_block 的错误了,这块不详解,感兴趣的可以自行了解。

下面分析代码,因为分叉只有接受别人的块才会出现,所以从 push_block 开始看起。

void push_block( const signed_block_ptr& b, controller::block_status s ) {
   // ...

      if ( read_mode != db_read_mode::IRREVERSIBLE ) {
         maybe_switch_forks( s );
      }

   // ...   
   } FC_LOG_AND_RETHROW( )
}
void maybe_switch_forks( controller::block_status s = controller::block_status::complete ) {
   auto new_head = fork_db.head();

   // 无分叉情况,正常叠加块
   if( new_head->header.previous == head->id ) {
      try {
         apply_block( new_head->block, s );
         fork_db.mark_in_current_chain( new_head, true );
         fork_db.set_validity( new_head, true );
         head = new_head;
      } catch ( const fc::exception& e ) {
         fork_db.set_validity( new_head, false ); // Removes new_head from fork_db index, so no need to mark it as not in the current chain.
         throw;
      }
   // 头 id 不同,出现分叉情况
   } else if( new_head->id != head->id ) {
      ilog("switching forks from ${current_head_id} (block number ${current_head_num}) to ${new_head_id} (block number ${new_head_num})",
           ("current_head_id", head->id)("current_head_num", head->block_num)("new_head_id", new_head->id)("new_head_num", new_head->block_num) );
      // 获取两个分支上对应的块
      auto branches = fork_db.fetch_branch_from( new_head->id, head->id );

      // 将前一分支的块都标记成不在当前使用分支。
      for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) {
         fork_db.mark_in_current_chain( *itr , false );
         pop_block();
      }
      EOS_ASSERT( self.head_block_id() == branches.second.back()->header.previous, fork_database_exception,
                 "loss of sync between fork_db and chainbase during fork switch" ); // _should_ never fail
      // 切换分支, apply 当前分支的块
      for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr) {
         optional<fc::exception> except;
         try {
            apply_block( (*ritr)->block, (*ritr)->validated ? controller::block_status::validated : controller::block_status::complete );
            head = *ritr;
            fork_db.mark_in_current_chain( *ritr, true );
            (*ritr)->validated = true;
         }
         catch (const fc::exception& e) { except = e; }
         if (except) {
            elog("exception thrown while switching forks ${e}", ("e",except->to_detail_string()));

            // ritr currently points to the block that threw
            // if we mark it invalid it will automatically remove all forks built off it.
            fork_db.set_validity( *ritr, false );

            // pop all blocks from the bad fork
            // ritr base is a forward itr to the last block successfully applied
            auto applied_itr = ritr.base();
            for( auto itr = applied_itr; itr != branches.first.end(); ++itr ) {
               fork_db.mark_in_current_chain( *itr , false );
               pop_block();
            }
            EOS_ASSERT( self.head_block_id() == branches.second.back()->header.previous, fork_database_exception,
                       "loss of sync between fork_db and chainbase during fork switch reversal" ); // _should_ never fail

            // re-apply good blocks
            for( auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr ) {
               apply_block( (*ritr)->block, controller::block_status::validated /* we previously validated these blocks*/ );
               head = *ritr;
               fork_db.mark_in_current_chain( *ritr, true );
            }
            // 抛出一个切换分支的异常。
            throw *except;
         } // end if exception
      } /// end for each block in branch
      ilog("successfully switched fork to new head ${new_head_id}", ("new_head_id", new_head->id));
   }
} /// push_block
void fork_database::prune( const block_state_ptr& h ) {
   auto num = h->block_num;

   auto& by_bn = my->index.get<by_block_num>();
   auto bni = by_bn.begin();
   while( bni != by_bn.end() && (*bni)->block_num < num ) {
      prune( *bni );
      bni = by_bn.begin();
   }

   auto itr = my->index.find( h->id );
   if( itr != my->index.end() ) {
      irreversible(*itr);
      my->index.erase(itr);
   }

   // 当 num 对应的块不可逆化, 其他分支下同等 block_num 就会被删除
   auto& numidx = my->index.get<by_block_num>();
   auto nitr = numidx.lower_bound( num );
   while( nitr != numidx.end() && (*nitr)->block_num == num ) {
      auto itr_to_remove = nitr;
      ++nitr;
      auto id = (*itr_to_remove)->id;
      remove( id );
   }
} 

其实分叉处理就是一个塑造出一个多叉树, 当多叉树上有节点成为不可逆的时候就可以裁剪。
转载自:https://eos.live/detail/16153