From 8967d471c9a7de33973ce58f6b3b3a30594b99a7 Mon Sep 17 00:00:00 2001 From: Kayan Date: Wed, 27 Feb 2019 16:41:52 +0800 Subject: [PATCH 01/10] fix switching between speculative and irreversible mode --- libraries/chain/controller.cpp | 111 +++++++++++------- libraries/chain/fork_database.cpp | 31 ++++- .../include/eosio/chain/fork_database.hpp | 6 + 3 files changed, 105 insertions(+), 43 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index b2a390fe648..4deec8a04b3 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -414,9 +414,11 @@ struct controller_impl { } int rev = 0; - while( auto obj = reversible_blocks.find(head->block_num+1) ) { + auto next_block_num = head->block_num+1; // need to cater the irreversible case that head is not advancing + while( auto obj = reversible_blocks.find(read_mode != db_read_mode::IRREVERSIBLE ? head->block_num+1 : next_block_num) ) { ++rev; replay_push_block( obj->get_block(), controller::block_status::validated ); + ++next_block_num; } ilog( "${n} reversible blocks replayed", ("n",rev) ); @@ -520,6 +522,28 @@ struct controller_impl { const auto hash = calculate_integrity_hash(); ilog( "database initialized with hash: ${hash}", ("hash", hash) ); } + + if (head && fork_db.pending_head() && fork_db.root()) { + if (read_mode != db_read_mode::IRREVERSIBLE) { + if (head->block_num < fork_db.pending_head()->block_num) { + // irreversible mode => speculative mode + wlog("db_read_mode has been changed: forwarding state from block ${h} to fork pending head ${fh}", ("h", head->block_num)("fh", fork_db.pending_head()->block_num)); + // let's go forward from lib to pending head, and set head as pending head + maybe_switch_forks( fork_db.pending_head(), controller::block_status::validated ); + } + } else { + // speculative mode => irreversible mode + uint32_t target_lib = fork_db.root()->block_num; + if (head->block_num > target_lib) { + wlog("db_read_mode has been changed, rolling back state from block ${o} to lib block ${l}", ("o", head->block_num)("l", target_lib)); + // let's go backward to lib(fork_db root) + while (head->block_num > target_lib) { + pop_block(); + } + fork_db.rollback_head_to_root(); + } + } + } } ~controller_impl() { @@ -1478,7 +1502,8 @@ struct controller_impl { emit( self.pre_accepted_block, b ); const bool skip_validate_signee = !conf.force_all_checks; - auto bsp = std::make_shared( *head, b, skip_validate_signee ); + // need to cater the irreversible mode case where head is not advancing + auto bsp = std::make_shared((read_mode == db_read_mode::IRREVERSIBLE && s != controller::block_status::irreversible && fork_db.pending_head()) ? *fork_db.pending_head() : *head, b, skip_validate_signee ); if( s != controller::block_status::irreversible ) { fork_db.add( bsp ); @@ -1519,48 +1544,52 @@ struct controller_impl { ("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 ) { - 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 - - for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { - optional except; - try { - apply_block( (*ritr)->block, (*ritr)->is_valid() ? controller::block_status::validated : controller::block_status::complete ); - fork_db.mark_valid( *ritr ); - head = *ritr; - } catch (const fc::exception& e) { - except = e; + if (branches.second.size()) { // cater a case of switching from fork pending head to lib + for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { + pop_block(); } - if( except ) { - elog("exception thrown while switching forks ${e}", ("e", except->to_detail_string())); - - // ritr currently points to the block that threw - // Remove the block that threw and all forks built off it. - fork_db.remove( (*ritr)->id ); - - EOS_ASSERT( head->id == fork_db.head()->id, fork_database_exception, - "loss of sync between fork_db and controller head during fork switch error" ); - - // 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 ) { - 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 + 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 + } - // 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*/ ); + if (branches.first.size()) { + for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { + optional except; + try { + apply_block( (*ritr)->block, (*ritr)->is_valid() ? controller::block_status::validated : controller::block_status::complete ); + fork_db.mark_valid( *ritr ); head = *ritr; + } catch (const fc::exception& e) { + except = e; } - throw *except; - } // end if exception - } /// end for each block in branch + if( except ) { + elog("exception thrown while switching forks ${e}", ("e", except->to_detail_string())); + + // ritr currently points to the block that threw + // Remove the block that threw and all forks built off it. + fork_db.remove( (*ritr)->id ); + + EOS_ASSERT( head->id == fork_db.head()->id, fork_database_exception, + "loss of sync between fork_db and controller head during fork switch error" ); + + // 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 ) { + 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; + } + 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)); } else { head_changed = false; @@ -2071,7 +2100,7 @@ const vector& controller::get_pending_trx_receipts()const { uint32_t controller::last_irreversible_block_num() const { uint32_t lib_num = (my->read_mode == db_read_mode::IRREVERSIBLE) - ? my->fork_db.pending_head()->dpos_irreversible_blocknum + ? std::max(my->fork_db.pending_head()->dpos_irreversible_blocknum, my->fork_db.root()->block_num) : my->head->dpos_irreversible_blocknum; return std::max( lib_num, my->snapshot_head_block ); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 68238ccddda..e4816a5d3a3 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -218,6 +218,21 @@ namespace eosio { namespace chain { my->head = my->root; } + void fork_database::rollback_head_to_root() { + auto& by_id_idx = my->index.get(); + + // root probably not exist in index + auto itr = by_id_idx.begin(); + while (itr != by_id_idx.end()) { + by_id_idx.modify( itr, [&]( block_state_ptr& bsp ) { + bsp->validated = (bsp->id == my->root->id); + } ); + ++itr; + } + + my->head = my->root; + } + void fork_database::advance_root( const block_id_type& id ) { EOS_ASSERT( my->root, fork_database_exception, "root not yet set" ); @@ -326,24 +341,36 @@ namespace eosio { namespace chain { auto first_branch = get_block(first); auto second_branch = get_block(second); + // need to handle a case where first or second is the root + if (!first_branch && my->root && first == my->root->id) first_branch = my->root; + if (!second_branch && my->root && second == my->root->id) second_branch = my->root; + while( first_branch->block_num > second_branch->block_num ) { result.first.push_back(first_branch); + auto prev = first_branch->header.previous; first_branch = get_block( first_branch->header.previous ); + + if (!first_branch && my->root && prev == my->root->id) first_branch = my->root; + EOS_ASSERT( first_branch, fork_db_block_not_found, "block ${id} does not exist", - ("id", first_branch->header.previous) ); + ("id", prev) ); } while( second_branch->block_num > first_branch->block_num ) { result.second.push_back( second_branch ); + auto prev = second_branch->header.previous; second_branch = get_block( second_branch->header.previous ); + if (!second_branch && my->root && prev == my->root->id) second_branch = my->root; EOS_ASSERT( second_branch, fork_db_block_not_found, "block ${id} does not exist", - ("id", second_branch->header.previous) ); + ("id", prev) ); } + if (first_branch->id == second_branch->id) return result; + while( first_branch->header.previous != second_branch->header.previous ) { result.first.push_back(first_branch); diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 8e4d9176431..1e723d054ca 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -36,6 +36,12 @@ namespace eosio { namespace chain { */ void reset( const block_header_state& root_bhs ); + /** + * rollback head to root if read_mode changed from speculative to irreversible + * valid flag need to set to false to avoid head advancing + */ + void rollback_head_to_root(); + /** * Advance root block forward to some other block in the tree. */ From 2ae9f4e568c53510e8af5da7670058a7243a5400 Mon Sep 17 00:00:00 2001 From: Kayan Date: Fri, 1 Mar 2019 15:43:35 +0800 Subject: [PATCH 02/10] fix replay & transit --- libraries/chain/controller.cpp | 177 +++++++++++++++++------------- libraries/chain/fork_database.cpp | 20 ++-- 2 files changed, 110 insertions(+), 87 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4deec8a04b3..a8802590edb 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -436,7 +436,14 @@ struct controller_impl { const auto& rbi = reversible_blocks.get_index(); - { + if (read_mode == db_read_mode::IRREVERSIBLE) { + // ensure there's no reversible_blocks in irreversible mode + auto rbitr = rbi.begin(); + while ( rbitr != rbi.end() ) { + reversible_blocks.remove( *rbitr ); + rbitr = rbi.begin(); + } + } else { auto rbitr = rbi.rbegin(); if( rbitr != rbi.rend() ) { EOS_ASSERT( blog_head, fork_database_exception, @@ -465,10 +472,12 @@ struct controller_impl { if( !head ) { initialize_blockchain_state(); // set head to genesis state } else { - EOS_ASSERT( last_block_num == head->block_num, fork_database_exception, - "reversible block database is inconsistent with fork database, replay blockchain", - ("head", head->block_num)("last_block_num", last_block_num) - ); + if (read_mode != db_read_mode::IRREVERSIBLE) { + EOS_ASSERT( last_block_num == head->block_num, fork_database_exception, + "reversible block database is inconsistent with fork database, replay blockchain", + ("head", head->block_num)("last_block_num", last_block_num) + ); + } } if( !blog_head ) { @@ -485,6 +494,52 @@ struct controller_impl { } } + // handle read_mode transition (probably reconstruct reversible_blocks) + if (read_mode != db_read_mode::IRREVERSIBLE) { // speculative mode + if (head->block_num < fork_db.pending_head()->block_num) { + // irreversible mode => speculative mode + wlog("db_read_mode has been changed: reconstruct reversible_blocks & forwarding state from #${h} to fork pending head #${fh}", ("h", head->block_num)("fh", fork_db.pending_head()->block_num)); + + // rebuild reversible_blocks from fork_db + auto rbitr = rbi.begin(); + while ( rbitr != rbi.end() ) { + reversible_blocks.remove( *rbitr ); + rbitr = rbi.begin(); + } + + block_state_ptr bsp; + vector head_to_lib; + auto id = fork_db.pending_head()->id; + while (id != head->id && (bsp = fork_db.get_block(id))) { + head_to_lib.push_back(bsp); + id = bsp->header.previous; + } + + auto blog_head = blog.head(); + auto blog_head_time = blog_head->timestamp.to_time_point(); + replay_head_time = blog_head_time; + for (auto itr = head_to_lib.rbegin(); itr != head_to_lib.rend(); ++itr) { + reversible_blocks.create( [&]( auto& ubo ) { + ubo.blocknum = (*itr)->block_num; + ubo.set_block( (*itr)->block ); + }); + replay_push_block( (*itr)->block, controller::block_status::validated, false); + } + replay_head_time.reset(); + } + } else { // irreversible mode + uint32_t target_lib = fork_db.root()->block_num; + if (head->block_num > target_lib) { + // speculative mode => irreversible mode + wlog("db_read_mode has been changed, rolling back state from block #${o} to lib block #${l}", ("o", head->block_num)("l", target_lib)); + // let's go backward to lib(fork_db root) + while (head->block_num > target_lib) { + pop_block(); + } + fork_db.rollback_head_to_root(); + } + } + bool report_integrity_hash = !!snapshot || (lib_num > head->block_num); // Trim any irreversible blocks from start of reversible blocks database @@ -521,29 +576,7 @@ struct controller_impl { if( report_integrity_hash ) { const auto hash = calculate_integrity_hash(); ilog( "database initialized with hash: ${hash}", ("hash", hash) ); - } - - if (head && fork_db.pending_head() && fork_db.root()) { - if (read_mode != db_read_mode::IRREVERSIBLE) { - if (head->block_num < fork_db.pending_head()->block_num) { - // irreversible mode => speculative mode - wlog("db_read_mode has been changed: forwarding state from block ${h} to fork pending head ${fh}", ("h", head->block_num)("fh", fork_db.pending_head()->block_num)); - // let's go forward from lib to pending head, and set head as pending head - maybe_switch_forks( fork_db.pending_head(), controller::block_status::validated ); - } - } else { - // speculative mode => irreversible mode - uint32_t target_lib = fork_db.root()->block_num; - if (head->block_num > target_lib) { - wlog("db_read_mode has been changed, rolling back state from block ${o} to lib block ${l}", ("o", head->block_num)("l", target_lib)); - // let's go backward to lib(fork_db root) - while (head->block_num > target_lib) { - pop_block(); - } - fork_db.rollback_head_to_root(); - } - } - } + } } ~controller_impl() { @@ -1489,7 +1522,7 @@ struct controller_impl { } FC_LOG_AND_RETHROW( ) } - void replay_push_block( const signed_block_ptr& b, controller::block_status s ) { + void replay_push_block( const signed_block_ptr& b, controller::block_status s, bool add_to_fork_db = true ) { self.validate_db_available_size(); self.validate_reversible_available_size(); @@ -1505,7 +1538,7 @@ struct controller_impl { // need to cater the irreversible mode case where head is not advancing auto bsp = std::make_shared((read_mode == db_read_mode::IRREVERSIBLE && s != controller::block_status::irreversible && fork_db.pending_head()) ? *fork_db.pending_head() : *head, b, skip_validate_signee ); - if( s != controller::block_status::irreversible ) { + if (add_to_fork_db && s != controller::block_status::irreversible ) { fork_db.add( bsp ); } @@ -1544,52 +1577,49 @@ struct controller_impl { ("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 ); - if (branches.second.size()) { // cater a case of switching from fork pending head to lib - for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { - 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 + for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { + 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 + + for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { + optional except; + try { + apply_block( (*ritr)->block, (*ritr)->is_valid() ? controller::block_status::validated : controller::block_status::complete ); + fork_db.mark_valid( *ritr ); + head = *ritr; + } 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 + // Remove the block that threw and all forks built off it. + fork_db.remove( (*ritr)->id ); + + EOS_ASSERT( head->id == fork_db.head()->id, fork_database_exception, + "loss of sync between fork_db and controller head during fork switch error" ); - if (branches.first.size()) { - for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { - optional except; - try { - apply_block( (*ritr)->block, (*ritr)->is_valid() ? controller::block_status::validated : controller::block_status::complete ); - fork_db.mark_valid( *ritr ); + // 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 ) { + 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; - } 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 - // Remove the block that threw and all forks built off it. - fork_db.remove( (*ritr)->id ); - - EOS_ASSERT( head->id == fork_db.head()->id, fork_database_exception, - "loss of sync between fork_db and controller head during fork switch error" ); - - // 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 ) { - 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; - } - throw *except; - } // end if exception - } /// end for each block in branch - } + 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)); } else { head_changed = false; @@ -2100,8 +2130,7 @@ const vector& controller::get_pending_trx_receipts()const { uint32_t controller::last_irreversible_block_num() const { uint32_t lib_num = (my->read_mode == db_read_mode::IRREVERSIBLE) - ? std::max(my->fork_db.pending_head()->dpos_irreversible_blocknum, my->fork_db.root()->block_num) - : my->head->dpos_irreversible_blocknum; + ? my->fork_db.root()->block_num : my->head->dpos_irreversible_blocknum; return std::max( lib_num, my->snapshot_head_block ); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index e4816a5d3a3..f7f9530b835 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -220,16 +220,13 @@ namespace eosio { namespace chain { void fork_database::rollback_head_to_root() { auto& by_id_idx = my->index.get(); - - // root probably not exist in index auto itr = by_id_idx.begin(); while (itr != by_id_idx.end()) { by_id_idx.modify( itr, [&]( block_state_ptr& bsp ) { - bsp->validated = (bsp->id == my->root->id); + bsp->validated = false; } ); ++itr; } - my->head = my->root; } @@ -338,21 +335,18 @@ namespace eosio { namespace chain { pair< branch_type, branch_type > fork_database::fetch_branch_from( const block_id_type& first, const block_id_type& second )const { pair result; - auto first_branch = get_block(first); - auto second_branch = get_block(second); + auto first_branch = (first == my->root->id) ? my->root : get_block(first); + auto second_branch = (second == my->root->id) ? my->root : get_block(second); - // need to handle a case where first or second is the root - if (!first_branch && my->root && first == my->root->id) first_branch = my->root; - if (!second_branch && my->root && second == my->root->id) second_branch = my->root; + EOS_ASSERT(first_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", first)); + EOS_ASSERT(second_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", second)); while( first_branch->block_num > second_branch->block_num ) { result.first.push_back(first_branch); - auto prev = first_branch->header.previous; + const auto &prev = first_branch->header.previous; first_branch = get_block( first_branch->header.previous ); - if (!first_branch && my->root && prev == my->root->id) first_branch = my->root; - EOS_ASSERT( first_branch, fork_db_block_not_found, "block ${id} does not exist", ("id", prev) ); @@ -361,7 +355,7 @@ namespace eosio { namespace chain { while( second_branch->block_num > first_branch->block_num ) { result.second.push_back( second_branch ); - auto prev = second_branch->header.previous; + const auto &prev = second_branch->header.previous; second_branch = get_block( second_branch->header.previous ); if (!second_branch && my->root && prev == my->root->id) second_branch = my->root; EOS_ASSERT( second_branch, fork_db_block_not_found, From 1d2330b3df39d07fec2cac51a460113e69133d1a Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 4 Mar 2019 01:15:12 -0500 Subject: [PATCH 03/10] major changes to controller_impl::init to fix #6842 --- libraries/chain/controller.cpp | 321 +++++++++--------- libraries/chain/fork_database.cpp | 34 +- .../include/eosio/chain/fork_database.hpp | 5 +- .../eosio/chain/reversible_block_object.hpp | 9 + unittests/forked_tests.cpp | 4 +- 5 files changed, 194 insertions(+), 179 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 13158b9f790..c8e5213763a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -307,19 +307,7 @@ struct controller_impl { if( fork_head->dpos_irreversible_blocknum <= lib_num ) return; - /* - const auto& rbi = reversible_blocks.get_index(); - auto libitr = rbi.find( fork_head->dpos_irreversible_blocknum ); - EOS_ASSERT( libitr != rbi.end(), fork_database_exception, - "new LIB according to fork database is not in reversible block database" ); - - fc::datastream ds( libitr->packedblock.data(), libitr->packedblock.size() ); - block_header h; - fc::raw::unpack( ds, h ); - auto lib_id = h.id(); - */ - - const auto branch = fork_db.fetch_branch( fork_head->id, fork_head->dpos_irreversible_blocknum ); //fork_db.fetch_branch( lib_id ); + const auto branch = fork_db.fetch_branch( fork_head->id, fork_head->dpos_irreversible_blocknum ); try { const auto& rbi = reversible_blocks.get_index(); @@ -402,7 +390,15 @@ struct controller_impl { std::cerr<< "\n"; ilog( "${n} irreversible blocks replayed", ("n", 1 + head->block_num - start_block_num) ); - fork_db.reset( *head ); + auto pending_head = fork_db.pending_head(); + if( pending_head->block_num < head->block_num || head->block_num <= fork_db.root()->block_num ) { + fork_db.reset( *head ); + } else { + auto new_root = fork_db.search_on_branch( pending_head->id, head->block_num ); + EOS_ASSERT( new_root, fork_database_exception, "unexpected error: could not find new LIB in fork database" ); + fork_db.mark_valid( new_root ); + fork_db.advance_root( new_root->id ); + } // if the irreverible log is played without undo sessions enabled, we need to sync the // revision ordinal to the appropriate expected value here. @@ -429,142 +425,63 @@ struct controller_impl { } void init(std::function shutdown, const snapshot_reader_ptr& snapshot) { - auto blog_head = blog.head(); - auto lib_num = (blog_head ? blog_head->block_num() : 1); - auto last_block_num = lib_num; - - const auto& rbi = reversible_blocks.get_index(); - - if (read_mode == db_read_mode::IRREVERSIBLE) { - // ensure there's no reversible_blocks in irreversible mode - auto rbitr = rbi.begin(); - while ( rbitr != rbi.end() ) { - reversible_blocks.remove( *rbitr ); - rbitr = rbi.begin(); - } - } else { - auto rbitr = rbi.rbegin(); - if( rbitr != rbi.rend() ) { - EOS_ASSERT( blog_head, fork_database_exception, - "non-empty reversible blocks despite empty irreversible block log" ); - EOS_ASSERT( rbitr->blocknum > lib_num, fork_database_exception, - "reversible block database is inconsistent with the block log" ); - last_block_num = rbitr->blocknum; - } - } - - // Setup state if necessary (or in the default case stay with already loaded state) + // Setup state if necessary (or in the default case stay with already loaded state): + auto lib_num = 1; if( snapshot ) { - EOS_ASSERT( !head, fork_database_exception, "" ); snapshot->validate(); - - read_from_snapshot( snapshot ); - - if( !blog_head ) { + if( blog.head() ) { + lib_num = blog.head()->block_num(); + read_from_snapshot( snapshot, blog.first_block_num(), lib_num ); + } else { + read_from_snapshot( snapshot, 0, std::numeric_limits::max() ); lib_num = head->block_num; blog.reset( conf.genesis, signed_block_ptr(), lib_num + 1 ); } - - EOS_ASSERT( lib_num >= head->block_num, fork_database_exception, - "Block log is provided with snapshot but does not contain the head block from the snapshot" ); } else { - if( !head ) { + if( !fork_db.head() ) { + ilog( "No head block in fork datbase. Initializing fresh blockchain state." ); initialize_blockchain_state(); // set head to genesis state - } else { - if (read_mode != db_read_mode::IRREVERSIBLE) { - EOS_ASSERT( last_block_num == head->block_num, fork_database_exception, - "reversible block database is inconsistent with fork database, replay blockchain", - ("head", head->block_num)("last_block_num", last_block_num) - ); - } - } - - if( !blog_head ) { - if( blog.first_block_num() > 1 ) { - lib_num = blog.first_block_num() - 1; - last_block_num = lib_num; - EOS_ASSERT( lib_num == head->block_num, fork_database_exception, - "Empty block log requires the next block to be appended to it to be at height ${first_block_num} which is not compatible with the head block", - ("first_block_num", blog.first_block_num())("head", head->block_num) - ); + if( blog.head() ) { + EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, + "block log does not start with genesis block" + ); } else { blog.reset( conf.genesis, head->block ); } - } - } - - // handle read_mode transition (probably reconstruct reversible_blocks) - if (read_mode != db_read_mode::IRREVERSIBLE) { // speculative mode - if (head->block_num < fork_db.pending_head()->block_num) { - // irreversible mode => speculative mode - wlog("db_read_mode has been changed: reconstruct reversible_blocks & forwarding state from #${h} to fork pending head #${fh}", ("h", head->block_num)("fh", fork_db.pending_head()->block_num)); - - // rebuild reversible_blocks from fork_db - auto rbitr = rbi.begin(); - while ( rbitr != rbi.end() ) { - reversible_blocks.remove( *rbitr ); - rbitr = rbi.begin(); + } else { + lib_num = fork_db.root()->block_num; + auto first_block_num = blog.first_block_num(); + if( blog.head() ) { + EOS_ASSERT( first_block_num <= lib_num && lib_num <= blog.head()->block_num(), + block_log_exception, + "block log does not contain last irreversible block", + ("block_log_first_num", first_block_num) + ("block_log_last_num", blog.head()->block_num()) + ("fork_db_lib", lib_num) + ); + lib_num = blog.head()->block_num(); + } else { + lib_num = fork_db.root()->block_num; + if( first_block_num != (lib_num + 1) ) { + blog.reset( conf.genesis, signed_block_ptr(), lib_num + 1 ); + } } - block_state_ptr bsp; - vector head_to_lib; - auto id = fork_db.pending_head()->id; - while (id != head->id && (bsp = fork_db.get_block(id))) { - head_to_lib.push_back(bsp); - id = bsp->header.previous; - } - - auto blog_head = blog.head(); - auto blog_head_time = blog_head->timestamp.to_time_point(); - replay_head_time = blog_head_time; - for (auto itr = head_to_lib.rbegin(); itr != head_to_lib.rend(); ++itr) { - reversible_blocks.create( [&]( auto& ubo ) { - ubo.blocknum = (*itr)->block_num; - ubo.set_block( (*itr)->block ); - }); - replay_push_block( (*itr)->block, controller::block_status::validated, false); - } - replay_head_time.reset(); - } - } else { // irreversible mode - uint32_t target_lib = fork_db.root()->block_num; - if (head->block_num > target_lib) { - // speculative mode => irreversible mode - wlog("db_read_mode has been changed, rolling back state from block #${o} to lib block #${l}", ("o", head->block_num)("l", target_lib)); - // let's go backward to lib(fork_db root) - while (head->block_num > target_lib) { - pop_block(); + if( read_mode == db_read_mode::IRREVERSIBLE && fork_db.head()->id != fork_db.root()->id ) { + fork_db.rollback_head_to_root(); } - fork_db.rollback_head_to_root(); - } - } - - bool report_integrity_hash = !!snapshot || (lib_num > head->block_num); - - // Trim any irreversible blocks from start of reversible blocks database - if( lib_num >= last_block_num ) { - last_block_num = lib_num; - auto rbitr = rbi.begin(); - while( rbitr != rbi.end() && rbitr->blocknum <= lib_num ) { - reversible_blocks.remove( *rbitr ); - rbitr = rbi.begin(); + head = fork_db.head(); } } - - if( lib_num > head->block_num ) { - replay( shutdown ); // replay irreversible blocks and any reversible blocks - } else if( last_block_num > lib_num ) { - replay( shutdown ); // replay reversible blocks - } - - if( shutdown() ) return; + // At this point head == fork_db.head() != nullptr && fork_db.root() != nullptr && fork_db.root()->block_num <= lib_num. + // Also, even though blog.head() may still be nullptr, blog.first_block_num() is guaranteed to be lib_num + 1. EOS_ASSERT( db.revision() >= head->block_num, fork_database_exception, - "fork database is inconsistent with state", + "fork database head is inconsistent with state", ("db",db.revision())("head",head->block_num) ); if( db.revision() > head->block_num ) { - wlog( "warning: database revision (${db}) is greater than head block number (${head}), " + wlog( "database revision (${db}) is greater than head block number (${head}), " "attempting to undo pending changes", ("db",db.revision())("head",head->block_num) ); } @@ -572,10 +489,102 @@ struct controller_impl { db.undo(); } + const auto& rbi = reversible_blocks.get_index(); + auto last_block_num = lib_num; + + if (read_mode == db_read_mode::IRREVERSIBLE) { + // ensure there are no reversible blocks + auto itr = rbi.begin(); + if( itr != rbi.end() ) { + wlog( "read_mode has changed to irreversible: erasing reversible blocks" ); + } + for( ; itr != rbi.end(); itr = rbi.begin() ) + reversible_blocks.remove( *itr ); + } else { + auto itr = rbi.begin(); + for( ; itr != rbi.end() && itr->blocknum <= lib_num; itr = rbi.begin() ) + reversible_blocks.remove( *itr ); + + EOS_ASSERT( itr == rbi.end() || itr->blocknum == lib_num + 1, reversible_blocks_exception, + "gap exists between last irreversible block and first reversible block", + ("lib", lib_num)("first_reversible_block_num", itr->blocknum) + ); + + auto ritr = rbi.rbegin(); + + if( ritr != rbi.rend() ) { + last_block_num = ritr->blocknum; + EOS_ASSERT( blog.head(), reversible_blocks_exception, + "non-empty reversible blocks despite empty irreversible block log" + ); + } + + EOS_ASSERT( head->block_num <= last_block_num, reversible_blocks_exception, + "head block (${head_num}) is greater than the last locally stored block (${last_block_num})", + ("head_num", head->block_num)("last_block_num", last_block_num) + ); + + auto pending_head = fork_db.pending_head(); + + if( ritr != rbi.rend() + && blog.head()->block_num() < pending_head->block_num + && pending_head->block_num <= last_block_num + ) { + auto rbitr = rbi.find( pending_head->block_num ); + EOS_ASSERT( rbitr != rbi.end(), reversible_blocks_exception, "pending head block not found in reversible blocks"); + auto rev_id = rbitr->get_block_id(); + EOS_ASSERT( rev_id == pending_head->id, + reversible_blocks_exception, + "mismatch in block id of pending head block ${num} in reversible blocks database: " + "expected: ${expected}, actual: ${actual}", + ("num", pending_head->block_num)("expected", pending_head->id)("actual", rev_id) + ); + } else if( last_block_num < pending_head->block_num ) { + const auto& branch = fork_db.fetch_branch( pending_head->id ); + auto num_blocks_prior_to_pending_head = pending_head->block_num - last_block_num; + FC_ASSERT( 1 <= num_blocks_prior_to_pending_head && num_blocks_prior_to_pending_head <= branch.size(), + "unexpected violation of invariants" ); + + if( ritr != rbi.rend() ) { + FC_ASSERT( num_blocks_prior_to_pending_head < branch.size(), "unexpected violation of invariants" ); + auto fork_id = branch[branch.size() - num_blocks_prior_to_pending_head - 1]->id; + auto rev_id = ritr->get_block_id(); + EOS_ASSERT( rev_id == fork_id, + reversible_blocks_exception, + "mismatch in block id of last block (${num}) in reversible blocks database: " + "expected: ${expected}, actual: ${actual}", + ("num", last_block_num)("expected", fork_id)("actual", rev_id) + ); + } + + if( pending_head->id != head->id ) { + wlog( "read_mode has changed from irreversible: reconstructing reversible blocks from fork database" ); + + for( auto n = branch.size() - num_blocks_prior_to_pending_head; n < branch.size(); ++n ) { + reversible_blocks.create( [&]( auto& rbo ) { + rbo.blocknum = branch[n]->block_num; + rbo.set_block( branch[n]->block ); + }); + } + + last_block_num = pending_head->block_num; + } + } + // else no checks needed since fork_db will be completely reset on replay anyway + } + + bool report_integrity_hash = !!snapshot || (lib_num > head->block_num); + + if( last_block_num > head->block_num ) { + replay( shutdown ); // replay any irreversible and reversible blocks ahead of current head + } + + if( shutdown() ) return; + if( report_integrity_hash ) { const auto hash = calculate_integrity_hash(); ilog( "database initialized with hash: ${hash}", ("hash", hash) ); - } + } } ~controller_impl() { @@ -696,7 +705,7 @@ struct controller_impl { resource_limits.add_to_snapshot(snapshot); } - void read_from_snapshot( const snapshot_reader_ptr& snapshot ) { + void read_from_snapshot( const snapshot_reader_ptr& snapshot, uint32_t blog_start, uint32_t blog_end ) { snapshot->read_section([this]( auto §ion ){ chain_snapshot_header header; section.read_row(header, db); @@ -704,14 +713,22 @@ struct controller_impl { }); - snapshot->read_section([this]( auto §ion ){ + snapshot->read_section([this, blog_start, blog_end]( auto §ion ){ block_header_state head_header_state; section.read_row(head_header_state, db); + snapshot_head_block = head_header_state.block_num; + auto next_block_after_snapshot_head = snapshot_head_block + 1; + EOS_ASSERT( blog_start <= next_block_after_snapshot_head && next_block_after_snapshot_head <= blog_end, + block_log_exception, + "Block log is provided with snapshot but does not contain the block after the head block from the snapshot", + ("next_block_after_snapshot_head_num", next_block_after_snapshot_head) + ("block_log_first_num", blog_start) + ("block_log_last_num", blog_end) + ); + fork_db.reset( head_header_state ); - auto head_state = std::make_shared(); - static_cast(*head_state) = head_header_state; - head = head_state; + head = fork_db.head(); snapshot_head_block = head->block_num; }); @@ -1521,7 +1538,7 @@ struct controller_impl { } FC_LOG_AND_RETHROW( ) } - void replay_push_block( const signed_block_ptr& b, controller::block_status s, bool add_to_fork_db = true ) { + void replay_push_block( const signed_block_ptr& b, controller::block_status s ) { self.validate_db_available_size(); self.validate_reversible_available_size(); @@ -1537,8 +1554,8 @@ struct controller_impl { // need to cater the irreversible mode case where head is not advancing auto bsp = std::make_shared((read_mode == db_read_mode::IRREVERSIBLE && s != controller::block_status::irreversible && fork_db.pending_head()) ? *fork_db.pending_head() : *head, b, skip_validate_signee ); - if (add_to_fork_db && s != controller::block_status::irreversible ) { - fork_db.add( bsp ); + if( s != controller::block_status::irreversible ) { + fork_db.add( bsp, true ); } emit( self.accepted_block_header, bsp ); @@ -1576,6 +1593,7 @@ struct controller_impl { ("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 ); + EOS_ASSERT( branches.second.size() > 0, fork_database_exception, "fork switch does not require popping blocks" ); for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { pop_block(); } @@ -1598,9 +1616,6 @@ struct controller_impl { // Remove the block that threw and all forks built off it. fork_db.remove( (*ritr)->id ); - EOS_ASSERT( head->id == fork_db.head()->id, fork_database_exception, - "loss of sync between fork_db and controller head during fork switch error" ); - // pop all blocks from the bad fork // ritr base is a forward itr to the last block successfully applied auto applied_itr = ritr.base(); @@ -1922,14 +1937,9 @@ void controller::add_indices() { } void controller::startup( std::function shutdown, const snapshot_reader_ptr& snapshot ) { - my->head = my->fork_db.head(); if( snapshot ) { ilog( "Starting initialization from snapshot, this may take a significant amount of time" ); } - else if( !my->head ) { - elog( "No head block in fork db, perhaps we need to replay" ); - } - try { my->init(shutdown, snapshot); } catch (boost::interprocess::bad_alloc& e) { @@ -2141,9 +2151,7 @@ const vector& controller::get_pending_trx_receipts()const { } uint32_t controller::last_irreversible_block_num() const { - uint32_t lib_num = (my->read_mode == db_read_mode::IRREVERSIBLE) - ? my->fork_db.root()->block_num : my->head->dpos_irreversible_blocknum; - return std::max( lib_num, my->snapshot_head_block ); + return my->fork_db.root()->block_num; } block_id_type controller::last_irreversible_block_id() const { @@ -2202,13 +2210,7 @@ block_state_ptr controller::fetch_block_state_by_number( uint32_t block_num )con } } - fc::datastream ds( objitr->packedblock.data(), objitr->packedblock.size() ); - block_header h; - fc::raw::unpack( ds, h ); - // Only need the block id to then look up the block state in fork database, so just unpack the block_header from the stored packed data. - // Avoid calling objitr->get_block() since that constructs a new signed_block in heap memory and unpacks the full signed_block from the stored packed data. - - return my->fork_db.get_block( h.id() ); + return my->fork_db.get_block( objitr->get_block_id() ); } FC_CAPTURE_AND_RETHROW( (block_num) ) } block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try { @@ -2221,10 +2223,7 @@ block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try const auto& rev_blocks = my->reversible_blocks.get_index(); auto objitr = rev_blocks.find(block_num); if( objitr != rev_blocks.end() ) { - fc::datastream ds( objitr->packedblock.data(), objitr->packedblock.size() ); - block_header h; - fc::raw::unpack( ds, h ); - return h.id(); + return objitr->get_block_id(); } } else { auto bsp = my->fork_db.search_on_branch( my->fork_db.pending_head()->id, block_num ); diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index f7f9530b835..6b8dc149775 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -277,7 +277,7 @@ namespace eosio { namespace chain { return block_header_state_ptr(); } - void fork_database::add( const block_state_ptr& n ) { + void fork_database::add( const block_state_ptr& n, bool ignore_duplicate ) { EOS_ASSERT( my->root, fork_database_exception, "root not yet set" ); EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" ); @@ -285,7 +285,10 @@ namespace eosio { namespace chain { "unlinkable block", ("id", n->id)("previous", n->header.previous) ); auto inserted = my->index.insert(n); - EOS_ASSERT( inserted.second, fork_database_exception, "duplicate block added", ("id", n->id) ); + if( !inserted.second ) { + if( ignore_duplicate ) return; + EOS_THROW( fork_database_exception, "duplicate block added", ("id", n->id) ); + } auto candidate = my->index.get().begin(); if( (*candidate)->is_valid() ) { @@ -344,23 +347,23 @@ namespace eosio { namespace chain { while( first_branch->block_num > second_branch->block_num ) { result.first.push_back(first_branch); - const auto &prev = first_branch->header.previous; - first_branch = get_block( first_branch->header.previous ); - if (!first_branch && my->root && prev == my->root->id) first_branch = my->root; + const auto& prev = first_branch->header.previous; + first_branch = (prev == my->root->id) ? my->root : get_block( prev ); EOS_ASSERT( first_branch, fork_db_block_not_found, "block ${id} does not exist", - ("id", prev) ); + ("id", prev) + ); } while( second_branch->block_num > first_branch->block_num ) { result.second.push_back( second_branch ); - const auto &prev = second_branch->header.previous; - second_branch = get_block( second_branch->header.previous ); - if (!second_branch && my->root && prev == my->root->id) second_branch = my->root; + const auto& prev = second_branch->header.previous; + second_branch = (prev == my->root->id) ? my->root : get_block( prev ); EOS_ASSERT( second_branch, fork_db_block_not_found, "block ${id} does not exist", - ("id", prev) ); + ("id", prev) + ); } if (first_branch->id == second_branch->id) return result; @@ -371,10 +374,13 @@ namespace eosio { namespace chain { result.second.push_back(second_branch); first_branch = get_block( first_branch->header.previous ); second_branch = get_block( second_branch->header.previous ); - EOS_ASSERT( first_branch && second_branch, fork_db_block_not_found, - "either block ${fid} or ${sid} does not exist", - ("fid", first_branch->header.previous) - ("sid", second_branch->header.previous) + EOS_ASSERT( first_branch, fork_db_block_not_found, + "block ${id} does not exist", + ("id", first_branch->header.previous) + ); + EOS_ASSERT( second_branch, fork_db_block_not_found, + "block ${id} does not exist", + ("id", second_branch->header.previous) ); } diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 1e723d054ca..900e06466f2 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -37,8 +37,7 @@ namespace eosio { namespace chain { void reset( const block_header_state& root_bhs ); /** - * rollback head to root if read_mode changed from speculative to irreversible - * valid flag need to set to false to avoid head advancing + * Removes validated flag from all blocks in fork database and resets head to point to the root. */ void rollback_head_to_root(); @@ -51,7 +50,7 @@ namespace eosio { namespace chain { * Add block state to fork database. * Must link to existing block in fork database or the root. */ - void add( const block_state_ptr& next_block ); + void add( const block_state_ptr& next_block, bool ignore_duplicate = false ); void remove( const block_id_type& id ); diff --git a/libraries/chain/include/eosio/chain/reversible_block_object.hpp b/libraries/chain/include/eosio/chain/reversible_block_object.hpp index ea9a4c9e122..daaac00a71b 100644 --- a/libraries/chain/include/eosio/chain/reversible_block_object.hpp +++ b/libraries/chain/include/eosio/chain/reversible_block_object.hpp @@ -32,6 +32,15 @@ namespace eosio { namespace chain { fc::raw::unpack( ds, *result ); return result; } + + block_id_type get_block_id()const { + fc::datastream ds( packedblock.data(), packedblock.size() ); + block_header h; + fc::raw::unpack( ds, h ); + // Only need the block id to then look up the block state in fork database, so just unpack the block_header from the stored packed data. + // Avoid calling get_block() since that constructs a new signed_block in heap memory and unpacks the full signed_block from the stored packed data. + return h.id(); + } }; struct by_num; diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 07c1d48c1aa..0e2d03cdd2f 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -154,7 +154,9 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { } // push the block which should attempt the corrupted fork and fail - BOOST_REQUIRE_THROW(bios.push_block(fork.blocks.back()), fc::exception); + BOOST_REQUIRE_EXCEPTION( bios.push_block(fork.blocks.back()), fc::exception, + fc_exception_message_is( "Block ID does not match" ) + ); } } From cbf86878b1941dc0644a6e0b1cc459b2ea7bf103 Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 4 Mar 2019 02:09:43 -0500 Subject: [PATCH 04/10] fix bug in init that caused restart-scenarios-test-hard_replay to fail --- libraries/chain/controller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index c8e5213763a..072bc76385a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -445,6 +445,7 @@ struct controller_impl { EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, "block log does not start with genesis block" ); + lib_num = blog.head()->block_num(); } else { blog.reset( conf.genesis, head->block ); } From 7ff3d092d1b5a403c41348c9e3331ae6207262f1 Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 4 Mar 2019 12:09:09 -0500 Subject: [PATCH 05/10] fix block log validation bug in read_from_snapshot --- libraries/chain/controller.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 072bc76385a..718cbbb1260 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -419,7 +419,7 @@ struct controller_impl { ilog( "${n} reversible blocks replayed", ("n",rev) ); auto end = fc::time_point::now(); ilog( "replayed ${n} blocks in ${duration} seconds, ${mspb} ms/block", - ("n", head->block_num - start_block_num)("duration", (end-start).count()/1000000) + ("n", head->block_num + 1 - start_block_num)("duration", (end-start).count()/1000000) ("mspb", ((end-start).count()/1000.0)/(head->block_num-start_block_num)) ); replay_head_time.reset(); } @@ -719,11 +719,10 @@ struct controller_impl { section.read_row(head_header_state, db); snapshot_head_block = head_header_state.block_num; - auto next_block_after_snapshot_head = snapshot_head_block + 1; - EOS_ASSERT( blog_start <= next_block_after_snapshot_head && next_block_after_snapshot_head <= blog_end, + EOS_ASSERT( blog_start <= (snapshot_head_block + 1) && snapshot_head_block <= blog_end, block_log_exception, - "Block log is provided with snapshot but does not contain the block after the head block from the snapshot", - ("next_block_after_snapshot_head_num", next_block_after_snapshot_head) + "Block log is provided with snapshot but does not contain the head block from the snapshot nor a block right after it", + ("snapshot_head_block", snapshot_head_block) ("block_log_first_num", blog_start) ("block_log_last_num", blog_end) ); From c982dd6bf6840d68edc01880996fd3264b6c0b26 Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 4 Mar 2019 21:16:31 -0500 Subject: [PATCH 06/10] Replay no longer removed fork_db; avoids losing info on irreversible replay. No longer reconstruct reversible blocks when switching from irreversible to speculative mode since that can be dangerous. Instead use maybe switch forks after the replay is complete. --- libraries/chain/controller.cpp | 76 ++++++++++++--------------- plugins/chain_plugin/chain_plugin.cpp | 10 +++- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 718cbbb1260..919b14b7cc0 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -438,8 +438,8 @@ struct controller_impl { blog.reset( conf.genesis, signed_block_ptr(), lib_num + 1 ); } } else { - if( !fork_db.head() ) { - ilog( "No head block in fork datbase. Initializing fresh blockchain state." ); + if( db.revision() < 1 /* !fork_db.head() */) { + ilog( "No head block in fork database. Initializing fresh blockchain state." ); initialize_blockchain_state(); // set head to genesis state if( blog.head() ) { EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, @@ -493,7 +493,7 @@ struct controller_impl { const auto& rbi = reversible_blocks.get_index(); auto last_block_num = lib_num; - if (read_mode == db_read_mode::IRREVERSIBLE) { + if( read_mode == db_read_mode::IRREVERSIBLE ) { // ensure there are no reversible blocks auto itr = rbi.begin(); if( itr != rbi.end() ) { @@ -515,9 +515,6 @@ struct controller_impl { if( ritr != rbi.rend() ) { last_block_num = ritr->blocknum; - EOS_ASSERT( blog.head(), reversible_blocks_exception, - "non-empty reversible blocks despite empty irreversible block log" - ); } EOS_ASSERT( head->block_num <= last_block_num, reversible_blocks_exception, @@ -528,7 +525,7 @@ struct controller_impl { auto pending_head = fork_db.pending_head(); if( ritr != rbi.rend() - && blog.head()->block_num() < pending_head->block_num + && lib_num < pending_head->block_num && pending_head->block_num <= last_block_num ) { auto rbitr = rbi.find( pending_head->block_num ); @@ -540,36 +537,16 @@ struct controller_impl { "expected: ${expected}, actual: ${actual}", ("num", pending_head->block_num)("expected", pending_head->id)("actual", rev_id) ); - } else if( last_block_num < pending_head->block_num ) { - const auto& branch = fork_db.fetch_branch( pending_head->id ); - auto num_blocks_prior_to_pending_head = pending_head->block_num - last_block_num; - FC_ASSERT( 1 <= num_blocks_prior_to_pending_head && num_blocks_prior_to_pending_head <= branch.size(), - "unexpected violation of invariants" ); - - if( ritr != rbi.rend() ) { - FC_ASSERT( num_blocks_prior_to_pending_head < branch.size(), "unexpected violation of invariants" ); - auto fork_id = branch[branch.size() - num_blocks_prior_to_pending_head - 1]->id; - auto rev_id = ritr->get_block_id(); - EOS_ASSERT( rev_id == fork_id, - reversible_blocks_exception, - "mismatch in block id of last block (${num}) in reversible blocks database: " - "expected: ${expected}, actual: ${actual}", - ("num", last_block_num)("expected", fork_id)("actual", rev_id) - ); - } - - if( pending_head->id != head->id ) { - wlog( "read_mode has changed from irreversible: reconstructing reversible blocks from fork database" ); - - for( auto n = branch.size() - num_blocks_prior_to_pending_head; n < branch.size(); ++n ) { - reversible_blocks.create( [&]( auto& rbo ) { - rbo.blocknum = branch[n]->block_num; - rbo.set_block( branch[n]->block ); - }); - } - - last_block_num = pending_head->block_num; - } + } else if( ritr != rbi.rend() && last_block_num < pending_head->block_num ) { + const auto b = fork_db.search_on_branch( pending_head->id, last_block_num ); + FC_ASSERT( b, "unexpected violation of invariants" ); + auto rev_id = ritr->get_block_id(); + EOS_ASSERT( rev_id == b->id, + reversible_blocks_exception, + "mismatch in block id of last block (${num}) in reversible blocks database: " + "expected: ${expected}, actual: ${actual}", + ("num", last_block_num)("expected", b->id)("actual", rev_id) + ); } // else no checks needed since fork_db will be completely reset on replay anyway } @@ -582,6 +559,20 @@ struct controller_impl { if( shutdown() ) return; + if( read_mode != db_read_mode::IRREVERSIBLE + && fork_db.pending_head()->id != fork_db.head()->id + && fork_db.head()->id == fork_db.root()->id + ) { + wlog( "read_mode has changed from irreversible: applying best branch from fork database" ); + + for( auto pending_head = fork_db.pending_head(); + pending_head->id != fork_db.head()->id; + pending_head = fork_db.pending_head() + ) { + maybe_switch_forks( pending_head, controller::block_status::complete ); + } + } + if( report_integrity_hash ) { const auto hash = calculate_integrity_hash(); ilog( "database initialized with hash: ${hash}", ("hash", hash) ); @@ -1593,12 +1584,13 @@ struct controller_impl { ("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 ); - EOS_ASSERT( branches.second.size() > 0, fork_database_exception, "fork switch does not require popping blocks" ); - for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { - pop_block(); + if( branches.second.size() > 0 ) { + for( auto itr = branches.second.begin(); itr != branches.second.end(); ++itr ) { + 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 } - 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 for( auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr ) { optional except; diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 4e80263b028..06e664dfc39 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -329,6 +329,14 @@ void clear_directory_contents( const fc::path& p ) { } } +void clear_chainbase_files( const fc::path& p ) { + if( !fc::is_directory( p ) ) + return; + + fc::remove( p / "shared_memory.bin" ); + fc::remove( p / "shared_memory.meta" ); +} + void chain_plugin::plugin_initialize(const variables_map& options) { ilog("initializing chain plugin"); @@ -512,7 +520,7 @@ void chain_plugin::plugin_initialize(const variables_map& options) { ilog( "Replay requested: deleting state database" ); if( options.at( "truncate-at-block" ).as() > 0 ) wlog( "The --truncate-at-block option does not work for a regular replay of the blockchain." ); - clear_directory_contents( my->chain_config->state_dir ); + clear_chainbase_files( my->chain_config->state_dir ); if( options.at( "fix-reversible-blocks" ).as()) { if( !recover_reversible_blocks( my->chain_config->blocks_dir / config::reversible_blocks_dir_name, my->chain_config->reversible_cache_size )) { From 5f4287070954d97f79eeee66850080e98ea4d5cd Mon Sep 17 00:00:00 2001 From: arhag Date: Mon, 4 Mar 2019 21:53:31 -0500 Subject: [PATCH 07/10] correct ilog message on fresh state database; better error message when applying best branch from fork_db --- libraries/chain/controller.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 919b14b7cc0..88e135bf136 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -438,9 +438,9 @@ struct controller_impl { blog.reset( conf.genesis, signed_block_ptr(), lib_num + 1 ); } } else { - if( db.revision() < 1 /* !fork_db.head() */) { - ilog( "No head block in fork database. Initializing fresh blockchain state." ); - initialize_blockchain_state(); // set head to genesis state + if( db.revision() < 1 ) { + ilog( "No existing chain state. Initializing fresh blockchain state." ); + initialize_blockchain_state(); // sets head to genesis state if( blog.head() ) { EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, "block log does not start with genesis block" @@ -569,6 +569,7 @@ struct controller_impl { pending_head->id != fork_db.head()->id; pending_head = fork_db.pending_head() ) { + wlog( "applying branch from fork database ending with block id '${id}'", ("id", pending_head->id) ); maybe_switch_forks( pending_head, controller::block_status::complete ); } } From 256c8a46f6c73c6946fe5d18e965a531efb418b1 Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 5 Mar 2019 12:26:43 -0500 Subject: [PATCH 08/10] more fixes for irreversible mode --- libraries/chain/controller.cpp | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 88e135bf136..801870e866d 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -364,9 +364,7 @@ struct controller_impl { head = std::make_shared(); static_cast(*head) = genheader; head->block = std::make_shared(genheader.header); - fork_db.reset( *head ); db.set_revision( head->block_num ); - initialize_database(); } @@ -391,11 +389,15 @@ struct controller_impl { ilog( "${n} irreversible blocks replayed", ("n", 1 + head->block_num - start_block_num) ); auto pending_head = fork_db.pending_head(); - if( pending_head->block_num < head->block_num || head->block_num <= fork_db.root()->block_num ) { + if( pending_head->block_num < head->block_num || head->block_num < fork_db.root()->block_num ) { + ilog( "resetting fork database with new last irreversible block as the new root: ${id}", + ("id", head->id) ); fork_db.reset( *head ); - } else { + } else if( head->block_num != fork_db.root()->block_num ) { auto new_root = fork_db.search_on_branch( pending_head->id, head->block_num ); EOS_ASSERT( new_root, fork_database_exception, "unexpected error: could not find new LIB in fork database" ); + ilog( "advancing fork database root to new last irreversible block within existing fork database: ${id}", + ("id", new_root->id) ); fork_db.mark_valid( new_root ); fork_db.advance_root( new_root->id ); } @@ -438,9 +440,23 @@ struct controller_impl { blog.reset( conf.genesis, signed_block_ptr(), lib_num + 1 ); } } else { - if( db.revision() < 1 ) { - ilog( "No existing chain state. Initializing fresh blockchain state." ); + if( db.revision() < 1 || !fork_db.head() ) { + if( fork_db.head() ) { + if( read_mode == db_read_mode::IRREVERSIBLE && fork_db.head()->id != fork_db.root()->id ) { + fork_db.rollback_head_to_root(); + } + wlog( "No existing chain state. Initializing fresh blockchain state." ); + } else { + EOS_ASSERT( db.revision() < 1, database_exception, + "No existing fork database despite existing chain state. Replay required." ); + wlog( "No existing chain state or fork database. Initializing fresh blockchain state and resetting fork database."); + } initialize_blockchain_state(); // sets head to genesis state + + if( !fork_db.head() ) { + fork_db.reset( *head ); + } + if( blog.head() ) { EOS_ASSERT( blog.first_block_num() == 1, block_log_exception, "block log does not start with genesis block" @@ -474,7 +490,8 @@ struct controller_impl { head = fork_db.head(); } } - // At this point head == fork_db.head() != nullptr && fork_db.root() != nullptr && fork_db.root()->block_num <= lib_num. + // At this point head != nullptr && fork_db.head() != nullptr && fork_db.root() != nullptr. + // Furthermore, fork_db.root()->block_num <= lib_num. // Also, even though blog.head() may still be nullptr, blog.first_block_num() is guaranteed to be lib_num + 1. EOS_ASSERT( db.revision() >= head->block_num, fork_database_exception, @@ -569,7 +586,7 @@ struct controller_impl { pending_head->id != fork_db.head()->id; pending_head = fork_db.pending_head() ) { - wlog( "applying branch from fork database ending with block id '${id}'", ("id", pending_head->id) ); + wlog( "applying branch from fork database ending with block: ${id}", ("id", pending_head->id) ); maybe_switch_forks( pending_head, controller::block_status::complete ); } } From 580361fbae198eee501dca1d24e06acf2c6e57ad Mon Sep 17 00:00:00 2001 From: arhag Date: Tue, 5 Mar 2019 19:44:54 -0500 Subject: [PATCH 09/10] simply logic now that there is a guarantee that no reversible blocks will exist while in irreversible mode --- libraries/chain/controller.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 801870e866d..d205b2ad86a 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -411,11 +411,9 @@ struct controller_impl { } int rev = 0; - auto next_block_num = head->block_num+1; // need to cater the irreversible case that head is not advancing - while( auto obj = reversible_blocks.find(read_mode != db_read_mode::IRREVERSIBLE ? head->block_num+1 : next_block_num) ) { + while( auto obj = reversible_blocks.find(head->block_num+1) ) { ++rev; replay_push_block( obj->get_block(), controller::block_status::validated ); - ++next_block_num; } ilog( "${n} reversible blocks replayed", ("n",rev) ); @@ -1560,8 +1558,7 @@ struct controller_impl { emit( self.pre_accepted_block, b ); const bool skip_validate_signee = !conf.force_all_checks; - // need to cater the irreversible mode case where head is not advancing - auto bsp = std::make_shared((read_mode == db_read_mode::IRREVERSIBLE && s != controller::block_status::irreversible && fork_db.pending_head()) ? *fork_db.pending_head() : *head, b, skip_validate_signee ); + auto bsp = std::make_shared( *head, b, skip_validate_signee ); if( s != controller::block_status::irreversible ) { fork_db.add( bsp, true ); @@ -1576,10 +1573,10 @@ struct controller_impl { // On replay, log_irreversible is not called and so no irreversible_block signal is emittted. // So emit it explicitly here. emit( self.irreversible_block, bsp ); - } else if( read_mode != db_read_mode::IRREVERSIBLE ) { - maybe_switch_forks( bsp, s ); } else { - log_irreversible(); + EOS_ASSERT( read_mode != db_read_mode::IRREVERSIBLE, block_validate_exception, + "invariant failure: cannot replay reversible blocks while in irreversible mode" ); + maybe_switch_forks( bsp, s ); } } FC_LOG_AND_RETHROW( ) From ad2f5344fa027568e2a70971dce6e21c7e6b4a80 Mon Sep 17 00:00:00 2001 From: Kayan Date: Wed, 6 Mar 2019 10:29:04 +0800 Subject: [PATCH 10/10] fix potential crash on assert --- libraries/chain/fork_database.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 6b8dc149775..0cb2485e23b 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -372,15 +372,17 @@ namespace eosio { namespace chain { { result.first.push_back(first_branch); result.second.push_back(second_branch); - first_branch = get_block( first_branch->header.previous ); - second_branch = get_block( second_branch->header.previous ); + const auto &first_prev = first_branch->header.previous; + first_branch = get_block( first_prev ); + const auto &second_prev = second_branch->header.previous; + second_branch = get_block( second_prev ); EOS_ASSERT( first_branch, fork_db_block_not_found, "block ${id} does not exist", - ("id", first_branch->header.previous) + ("id", first_prev) ); EOS_ASSERT( second_branch, fork_db_block_not_found, "block ${id} does not exist", - ("id", second_branch->header.previous) + ("id", second_prev) ); }