在区块链上, 分叉是一个老话题了, 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