diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 6752b4d97a..31a336f50b 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -941,7 +941,6 @@ struct controller_impl { block_handle chain_head; block_state_ptr chain_head_trans_svnn_block; // chain_head's Savanna representation during transition fork_database fork_db; - large_atomic if_irreversible_block_id; resource_limits_manager resource_limits; subjective_billing subjective_bill; authorization_manager authorization; @@ -1050,35 +1049,18 @@ struct controller_impl { } // --------------- access fork_db head ---------------------------------------------------------------------- - bool fork_db_has_head() const { - return fork_db.apply([&](const auto& forkdb) { return !!forkdb.head(); }); - } - - template - typename ForkDB::bsp_t fork_db_head_or_pending(const ForkDB& forkdb) const { - if (irreversible_mode()) { - // When in IRREVERSIBLE mode fork_db blocks are marked valid when they become irreversible so that - // fork_db.head() returns irreversible block - // Use pending_head since this method should return the chain head and not last irreversible. - return forkdb.pending_head(); - } else { - return forkdb.head(); - } - } - uint32_t fork_db_head_block_num() const { return fork_db.apply( - [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->block_num(); }); + [&](const auto& forkdb) { + return forkdb.head(include_root_t::yes)->block_num(); + }); } block_id_type fork_db_head_block_id() const { return fork_db.apply( - [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->id(); }); - } - - uint32_t fork_db_head_irreversible_blocknum() const { - return fork_db.apply( - [&](const auto& forkdb) { return fork_db_head_or_pending(forkdb)->irreversible_blocknum(); }); + [&](const auto& forkdb) { + return forkdb.head(include_root_t::yes)->id(); + }); } // --------------- access fork_db root ---------------------------------------------------------------------- @@ -1306,6 +1288,7 @@ struct controller_impl { } } + // When in IRREVERSIBLE mode fork_db blocks are applied and marked valid when they become irreversible template bool apply_irreversible_block(ForkDB& forkdb, const BSP& bsp) { if (read_mode != db_read_mode::IRREVERSIBLE) @@ -1356,7 +1339,9 @@ struct controller_impl { // Create the valid structure for producing new_bsp->valid = prev->new_valid(*new_bsp, *legacy->action_mroot_savanna, new_bsp->strong_digest); } - forkdb.add(new_bsp, legacy->is_valid() ? mark_valid_t::yes : mark_valid_t::no, ignore_duplicate_t::yes); + if (legacy->is_valid()) + new_bsp->set_valid(true); + forkdb.add(new_bsp, ignore_duplicate_t::yes); } void transition_to_savanna() { @@ -1368,7 +1353,7 @@ struct controller_impl { block_state_legacy_ptr legacy_root; fork_db.apply_l([&](const auto& forkdb) { legacy_root = forkdb.root(); - legacy_branch = forkdb.fetch_branch(fork_db_head_or_pending(forkdb)->id()); + legacy_branch = forkdb.fetch_branch(forkdb.head()->id()); }); assert(!!legacy_root); @@ -1381,6 +1366,8 @@ struct controller_impl { assert(prev); for (auto bitr = legacy_branch.rbegin(); bitr != legacy_branch.rend(); ++bitr) { assert(read_mode == db_read_mode::IRREVERSIBLE || (*bitr)->action_mroot_savanna.has_value()); + if (!irreversible_mode() && !(*bitr)->is_valid()) + break; const bool skip_validate_signee = true; // validated already auto new_bsp = block_state::create_transition_block( *prev, @@ -1391,9 +1378,9 @@ struct controller_impl { transition_add_to_savanna_fork_db(forkdb, *bitr, new_bsp, prev); prev = new_bsp; } - assert(read_mode == db_read_mode::IRREVERSIBLE || forkdb.head()->id() == legacy_branch.front()->id()); + assert(read_mode == db_read_mode::IRREVERSIBLE || chain_head.id() == legacy_branch.front()->id()); if (read_mode != db_read_mode::IRREVERSIBLE) - chain_head = block_handle{forkdb.head()}; + chain_head = block_handle{prev}; ilog("Transition to instant finality happening after block ${b}, First IF Proper Block ${pb}", ("b", prev->block_num())("pb", prev->block_num()+1)); }); @@ -1430,24 +1417,24 @@ struct controller_impl { ("lib_num", lib_num)("bn", fork_db_root_block_num()) ); } - const block_id_type irreversible_block_id = if_irreversible_block_id.load(); - const uint32_t savanna_lib_num = block_header::num_from_id(irreversible_block_id); - const bool savanna = savanna_lib_num > 0; - const uint32_t new_lib_num = savanna ? savanna_lib_num : fork_db_head_irreversible_blocknum(); + // maintain legacy only advancing LIB via validated blocks, hence pass in chain_head id for use + const block_id_type new_lib_id = fork_db.pending_lib_id(irreversible_mode() ? block_id_type{} : chain_head.id()); + const block_num_type new_lib_num = block_header::num_from_id(new_lib_id); if( new_lib_num <= lib_num ) return; - bool savanna_transistion_required = false; + bool savanna_transition_required = false; auto mark_branch_irreversible = [&, this](auto& forkdb) { - auto branch = savanna ? forkdb.fetch_branch( fork_db_head_or_pending(forkdb)->id(), irreversible_block_id) - : forkdb.fetch_branch( fork_db_head_or_pending(forkdb)->id(), new_lib_num ); + assert(!irreversible_mode() || forkdb.head()); + const auto& head_id = irreversible_mode() ? forkdb.head()->id() : chain_head.id(); + auto branch = forkdb.fetch_branch( head_id, new_lib_id); try { auto should_process = [&](auto& bsp) { // Only make irreversible blocks that have been validated. Blocks in the fork database may not be on our current best head // and therefore have not been validated. // An alternative more complex implementation would be to do a fork switch here and validate all blocks so they can be then made - // irreversible. Instead this moves irreversible as much as possible and allows the next maybe_switch_forks call to apply these + // irreversible. Instead, this moves irreversible as much as possible and allows the next maybe_switch_forks call to apply these // non-validated blocks. After the maybe_switch_forks call (before next produced block or on next received block), irreversible // can then move forward on the then validated blocks. return read_mode == db_read_mode::IRREVERSIBLE || bsp->is_valid(); @@ -1477,7 +1464,7 @@ struct controller_impl { if constexpr (std::is_same_v>) { if ((*bitr)->header.contains_header_extension(instant_finality_extension::extension_id())) { - savanna_transistion_required = true; + savanna_transition_required = true; // Do not advance irreversible past IF Genesis Block break; } @@ -1511,7 +1498,7 @@ struct controller_impl { }; fork_db.apply(mark_branch_irreversible); - if (savanna_transistion_required) { + if (savanna_transition_required) { transition_to_savanna(); } } @@ -1663,7 +1650,7 @@ struct controller_impl { } if (startup == startup_t::existing_state) { - EOS_ASSERT(fork_db_has_head(), fork_database_exception, + EOS_ASSERT(fork_db_has_root(), fork_database_exception, "No existing fork database despite existing chain state. Replay required." ); uint32_t lib_num = fork_db_root_block_num(); auto first_block_num = blog.first_block_num(); @@ -1684,11 +1671,9 @@ struct controller_impl { auto do_startup = [&](auto& forkdb) { if( read_mode == db_read_mode::IRREVERSIBLE) { - auto head = forkdb.head(); auto root = forkdb.root(); - if (head && root && head->id() != root->id()) { - forkdb.rollback_head_to_root(); - chain_head = block_handle{forkdb.head()}; + if (root && chain_head.id() != root->id()) { + chain_head = block_handle{forkdb.root()}; // rollback db to LIB while( db.revision() > chain_head.block_num() ) { db.undo(); @@ -1702,7 +1687,7 @@ struct controller_impl { auto fork_db_reset_root_to_chain_head = [&]() { fork_db.apply([&](auto& forkdb) { block_handle_accessor::apply(chain_head, [&](const auto& head) { - if constexpr (std::is_same_v, std::decay_t>) + if constexpr (std::is_same_v, std::decay_t>) forkdb.reset_root(head); }); }); @@ -1721,15 +1706,9 @@ struct controller_impl { switch_from_legacy_if_needed(); auto do_startup = [&](auto& forkdb) { if( forkdb.head() ) { - if( read_mode == db_read_mode::IRREVERSIBLE && forkdb.head()->id() != forkdb.root()->id() ) { - forkdb.rollback_head_to_root(); - } wlog( "No existing chain state. Initializing fresh blockchain state." ); } else { wlog( "No existing chain state or fork database. Initializing fresh blockchain state and resetting fork database."); - } - - if( !forkdb.head() ) { fork_db_reset_root_to_chain_head(); } }; @@ -1742,9 +1721,9 @@ struct controller_impl { } auto replay_fork_db = [&](auto& forkdb) { - using BSP = std::decay_t; + using BSP = std::decay_t; - auto pending_head = forkdb.pending_head(); + auto pending_head = forkdb.head(); if( pending_head && blog_head && start_block_num <= blog_head->block_num() ) { ilog("fork database head ${hn}:${h}, root ${rn}:${r}", ("hn", pending_head->block_num())("h", pending_head->id()) @@ -1758,7 +1737,7 @@ struct controller_impl { "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()) ); - forkdb.mark_valid( new_root ); + new_root->set_valid(true); forkdb.advance_root( new_root->id() ); } } @@ -1766,15 +1745,16 @@ struct controller_impl { if (snapshot_head_block != 0 && !blog.head()) { // loading from snapshot without a block log so fork_db can't be considered valid fork_db_reset_root_to_chain_head(); - } else if( !except_ptr && !check_shutdown() && forkdb.head() ) { + } else if( !except_ptr && !check_shutdown() && !irreversible_mode() && forkdb.head()) { auto head_block_num = chain_head.block_num(); - auto branch = fork_db.fetch_branch_from_head(); + auto branch = forkdb.fetch_branch(forkdb.head()->id()); int rev = 0; for( auto i = branch.rbegin(); i != branch.rend(); ++i ) { if( check_shutdown() ) break; // needed on every loop for terminate-at-block + if( !(*i)->is_valid() ) break; if( (*i)->block_num() <= head_block_num ) continue; ++rev; - replay_push_block( *i, controller::block_status::validated ); + replay_push_block( (*i)->block, controller::block_status::validated ); } ilog( "${n} reversible blocks replayed", ("n",rev) ); } @@ -1938,18 +1918,17 @@ struct controller_impl { if( check_shutdown() ) return; - // At this point head != nullptr && fork_db.head() != nullptr && fork_db.root() != nullptr. + // At this point chain_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. auto finish_init = [&](auto& forkdb) { if( read_mode != db_read_mode::IRREVERSIBLE ) { - auto pending_head = forkdb.pending_head(); - auto head = forkdb.head(); - if ( head && pending_head && pending_head->id() != head->id() && head->id() == forkdb.root()->id() ) { + auto pending_head = forkdb.head(); + if ( pending_head && pending_head->id() != chain_head.id() && chain_head.id() == forkdb.root()->id() ) { ilog( "read_mode has changed from irreversible: applying best branch from fork database" ); - for( ; pending_head->id() != forkdb.head()->id(); pending_head = forkdb.pending_head() ) { + for( ; pending_head->id() != chain_head.id(); pending_head = forkdb.head() ) { ilog( "applying branch from fork database ending with block: ${id}", ("id", pending_head->id()) ); controller::block_report br; maybe_switch_forks( br, pending_head, controller::block_status::complete, {}, trx_meta_cache_lookup{} ); @@ -3255,15 +3234,18 @@ struct controller_impl { if (s != controller::block_status::irreversible) { auto add_completed_block = [&](auto& forkdb) { - assert(std::holds_alternative>(cb.bsp.internal())); - const auto& bsp = std::get>(cb.bsp.internal()); + assert(std::holds_alternative>(cb.bsp.internal())); + const auto& bsp = std::get>(cb.bsp.internal()); if( s == controller::block_status::incomplete ) { - forkdb.add( bsp, mark_valid_t::yes, ignore_duplicate_t::no ); + bsp->set_valid(true); + forkdb.add( bsp, ignore_duplicate_t::no ); emit( accepted_block_header, std::tie(bsp->block, bsp->id()), __FILE__, __LINE__ ); vote_processor.notify_new_block(async_aggregation); } else { assert(s != controller::block_status::irreversible); - forkdb.mark_valid( bsp ); + auto existing = forkdb.get_block(bsp->id()); + assert(existing); + existing->set_valid(true); } }; fork_db.apply(add_completed_block); @@ -3274,8 +3256,8 @@ struct controller_impl { if( s == controller::block_status::incomplete ) { fork_db.apply_s([&](auto& forkdb) { - assert(std::holds_alternative>(cb.bsp.internal())); - const auto& bsp = std::get>(cb.bsp.internal()); + assert(std::holds_alternative>(cb.bsp.internal())); + const auto& bsp = std::get>(cb.bsp.internal()); uint16_t if_ext_id = instant_finality_extension::extension_id(); assert(bsp->header_exts.count(if_ext_id) > 0); // in all instant_finality block headers @@ -3285,7 +3267,7 @@ struct controller_impl { auto claimed = forkdb.search_on_branch(bsp->id(), if_ext.qc_claim.block_num); if (claimed) { auto& final_on_strong_qc_block_ref = claimed->core.get_block_reference(claimed->core.final_on_strong_qc_block_num); - set_if_irreversible_block_id(final_on_strong_qc_block_ref.block_id); + set_savanna_lib_id(final_on_strong_qc_block_ref.block_id); } } }); @@ -3893,7 +3875,7 @@ struct controller_impl { } if (conf.terminate_at_block == 0 || bsp->block_num() <= conf.terminate_at_block) { - forkdb.add(bsp, mark_valid_t::no, ignore_duplicate_t::yes); + forkdb.add(bsp, ignore_duplicate_t::yes); if constexpr (savanna_mode) vote_processor.notify_new_block(async_aggregation); } @@ -3992,7 +3974,7 @@ struct controller_impl { // just acting as a carrier of this info. It doesn't matter if the block // is actually valid as it simply is used as a network message for this data. const auto& final_on_strong_qc_block_ref = claimed->core.get_block_reference(claimed->core.final_on_strong_qc_block_num); - set_if_irreversible_block_id(final_on_strong_qc_block_ref.block_id); + set_savanna_lib_id(final_on_strong_qc_block_ref.block_id); // Update finalizer safety information based on vote evidence my_finalizers.maybe_update_fsi(claimed, received_qc); } @@ -4035,8 +4017,12 @@ struct controller_impl { consider_voting(bsp, use_thread_pool_t::yes); auto do_accept_block = [&](auto& forkdb) { - if constexpr (std::is_same_v>) - forkdb.add( bsp, mark_valid_t::no, ignore_duplicate_t::yes ); + if constexpr (std::is_same_v>) { + forkdb.add( bsp, ignore_duplicate_t::yes ); + } else { + EOS_ASSERT( false, unlinkable_block_exception, + "wrong block state type, unlinkable block ${id} previous ${p}", ("id", bsp->id())("p", bsp->previous()) ); + } emit( accepted_block_header, std::tie(bsp->block, bsp->id()), __FILE__, __LINE__ ); }; @@ -4062,8 +4048,11 @@ struct controller_impl { const auto& b = bsp->block; auto do_push = [&](auto& forkdb) { - if constexpr (std::is_same_v>) { - forkdb.add( bsp, mark_valid_t::no, ignore_duplicate_t::yes ); + if constexpr (std::is_same_v>) { + forkdb.add( bsp, ignore_duplicate_t::yes ); + } else { + EOS_ASSERT( false, unlinkable_block_exception, + "unexpected block state type, unlinkable block ${id} previous ${p}", ("id", bsp->id())("p", b->previous) ); } if (is_trusted_producer(b->producer)) { @@ -4073,8 +4062,8 @@ struct controller_impl { emit( accepted_block_header, std::tie(bsp->block, bsp->id()), __FILE__, __LINE__ ); if( read_mode != db_read_mode::IRREVERSIBLE ) { - if constexpr (std::is_same_v>) - maybe_switch_forks( br, forkdb.pending_head(), s, forked_branch_cb, trx_lookup ); + if constexpr (std::is_same_v>) + maybe_switch_forks( br, forkdb.head(include_root_t::yes), s, forked_branch_cb, trx_lookup ); } else { log_irreversible(); } @@ -4108,8 +4097,8 @@ struct controller_impl { if (s != controller::block_status::irreversible) { fork_db.apply([&](auto& forkdb) { - if constexpr (std::is_same_v, std::decay_t>) - forkdb.add(bsp, mark_valid_t::no, ignore_duplicate_t::yes); + if constexpr (std::is_same_v, std::decay_t>) + forkdb.add(bsp, ignore_duplicate_t::yes); }); } @@ -4141,8 +4130,8 @@ struct controller_impl { void maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { auto maybe_switch = [&](auto& forkdb) { if (read_mode != db_read_mode::IRREVERSIBLE) { - auto pending_head = forkdb.pending_head(); - if (chain_head.id() != pending_head->id() && pending_head->id() != forkdb.head()->id()) { + auto pending_head = forkdb.head(include_root_t::yes); + if (chain_head.id() != pending_head->id()) { dlog("switching forks on controller->maybe_switch_forks call"); controller::block_report br; maybe_switch_forks(br, pending_head, @@ -4161,6 +4150,8 @@ struct controller_impl { const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) { auto do_maybe_switch_forks = [&](auto& forkdb) { + dlog("maybe switch forks chain_head ${chn} : ${chid}, new_head ${nhn} : ${nhid}, previous ${p}", + ("chn", chain_head.block_num())("chid", chain_head.id())("nhn", new_head->block_num())("nhid", new_head->id())("p", new_head->header.previous)); if( new_head->header.previous == chain_head.id() ) { try { apply_block( br, new_head, s, trx_lookup ); @@ -4524,14 +4515,12 @@ struct controller_impl { return is_trx_transient ? nullptr : deep_mind_logger; } - void set_if_irreversible_block_id(const block_id_type& id) { - const block_num_type id_num = block_header::num_from_id(id); - auto accessor = if_irreversible_block_id.make_accessor(); - const block_num_type current_num = block_header::num_from_id(accessor.value()); - if( id_num > current_num ) { - dlog("set irreversible block ${bn}: ${id}, old ${obn}: ${oid}", ("bn", id_num)("id", id)("obn", current_num)("oid", accessor.value())); - accessor.value() = id; - } + void set_savanna_lib_id(const block_id_type& id) { + fork_db.apply_s([&](auto& forkdb) { + if (forkdb.set_pending_savanna_lib_id(id)) { + dlog("set irreversible block ${bn}: ${id}", ("bn", block_header::num_from_id(id))("id", id)); + } + }); } // Returns corresponding Transition Savanna block for a given Legacy block. @@ -5186,12 +5175,8 @@ std::optional controller::pending_producer_block_id()const { return my->pending_producer_block_id(); } -void controller::set_if_irreversible_block_id(const block_id_type& id) { - my->set_if_irreversible_block_id(id); -} - -uint32_t controller::if_irreversible_block_num() const { - return block_header::num_from_id(my->if_irreversible_block_id.load()); +void controller::set_savanna_lib_id(const block_id_type& id) { + my->set_savanna_lib_id(id); } uint32_t controller::last_irreversible_block_num() const { diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index cc23473973..77980185fb 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -22,20 +22,9 @@ namespace eosio::chain { * root is full `block_state`, not just the header. */ - struct block_state_accessor { - static bool is_valid(const block_state& bs) { return bs.is_valid(); } - static void set_valid(block_state& bs, bool v) { bs.validated.store(v); } - }; - - struct block_state_legacy_accessor { - static bool is_valid(const block_state_legacy& bs) { return bs.is_valid(); } - static void set_valid(block_state_legacy& bs, bool v) { bs.validated = v; } - }; - std::string log_fork_comparison(const block_state& bs) { std::string r; - r += "[ valid: " + std::to_string(block_state_accessor::is_valid(bs)) + ", "; - r += "last_final_block_timestamp: " + bs.last_final_block_timestamp().to_time_point().to_iso_string() + ", "; + r += "[ last_final_block_timestamp: " + bs.last_final_block_timestamp().to_time_point().to_iso_string() + ", "; r += "latest_qc_block_timestamp: " + bs.latest_qc_block_timestamp().to_time_point().to_iso_string() + ", "; r += "timestamp: " + bs.timestamp().to_time_point().to_iso_string(); r += "id: " + bs.id().str(); @@ -45,8 +34,7 @@ namespace eosio::chain { std::string log_fork_comparison(const block_state_legacy& bs) { std::string r; - r += "[ valid: " + std::to_string(block_state_legacy_accessor::is_valid(bs)) + ", "; - r += "irreversible_blocknum: " + std::to_string(bs.irreversible_blocknum()) + ", "; + r += "[ irreversible_blocknum: " + std::to_string(bs.irreversible_blocknum()) + ", "; r += "block_num: " + std::to_string(bs.block_num()) + ", "; r += "timestamp: " + bs.timestamp().to_time_point().to_iso_string(); r += "id: " + bs.id().str(); @@ -58,21 +46,10 @@ namespace eosio::chain { struct by_best_branch; struct by_prev; - // match comparison of by_best_branch - bool first_preferred( const block_state& lhs, const block_state& rhs ) { - return std::make_tuple(lhs.last_final_block_timestamp(), lhs.latest_qc_block_timestamp(), lhs.timestamp(), lhs.id()) > - std::make_tuple(rhs.last_final_block_timestamp(), rhs.latest_qc_block_timestamp(), rhs.timestamp(), rhs.id()); - } - bool first_preferred( const block_state_legacy& lhs, const block_state_legacy& rhs ) { - return std::make_tuple(lhs.irreversible_blocknum(), lhs.block_num()) > - std::make_tuple(rhs.irreversible_blocknum(), rhs.block_num()); - } - template // either [block_state_legacy_ptr, block_state_ptr], same with block_header_state_ptr struct fork_database_impl { using bsp_t = BSP; using bs_t = bsp_t::element_type; - using bs_accessor_t = bs_t::fork_db_block_state_accessor_t; using bhsp_t = bs_t::bhsp_t; using bhs_t = bhsp_t::element_type; @@ -84,22 +61,20 @@ namespace eosio::chain { using by_best_branch_legacy_t = ordered_unique< tag, composite_key, const_mem_fun, const_mem_fun, const_mem_fun>, - composite_key_compare, std::greater, std::greater, std::greater>>; + composite_key_compare, std::greater, std::greater>>; using by_best_branch_if_t = ordered_unique< tag, composite_key, const_mem_fun, const_mem_fun, const_mem_fun, const_mem_fun>, - composite_key_compare, std::greater, - std::greater, std::greater, std::greater>>; + composite_key_compare, std::greater, + std::greater, std::greater>>; using by_best_branch_t = std::conditional_t, by_best_branch_if_t, @@ -114,30 +89,29 @@ namespace eosio::chain { std::mutex mtx; bsp_t root; - bsp_t head; + block_id_type pending_savanna_lib_id; // under Savanna the id of what will become root fork_multi_index_type index; explicit fork_database_impl() = default; void open_impl( const char* desc, const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ); void close_impl( std::ofstream& out ); - void add_impl( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate, bool validate, validator_t& validator ); + void add_impl( const bsp_t& n, ignore_duplicate_t ignore_duplicate, bool validate, validator_t& validator ); bool is_valid() const; bsp_t get_block_impl( const block_id_type& id, include_root_t include_root = include_root_t::no ) const; bool block_exists_impl( const block_id_type& id ) const; bool validated_block_exists_impl( const block_id_type& id ) const; void reset_root_impl( const bsp_t& root_bs ); - void rollback_head_to_root_impl(); void advance_root_impl( const block_id_type& id ); void remove_impl( const block_id_type& id ); + bsp_t head_impl(include_root_t include_root) const; branch_t fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; block_branch_t fetch_block_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; branch_t fetch_branch_impl( const block_id_type& h, const block_id_type& b ) const; full_branch_t fetch_full_branch_impl(const block_id_type& h) const; bsp_t search_on_branch_impl( const block_id_type& h, uint32_t block_num, include_root_t include_root ) const; bsp_t search_on_head_branch_impl( uint32_t block_num, include_root_t include_root ) const; - void mark_valid_impl( const bsp_t& h ); branch_pair_t fetch_branch_from_impl( const block_id_type& first, const block_id_type& second ) const; }; @@ -159,6 +133,7 @@ namespace eosio::chain { template void fork_database_impl::open_impl( const char* desc, const std::filesystem::path& fork_db_file, fc::cfile_datastream& ds, validator_t& validator ) { bsp_t _root = std::make_shared(); + fc::raw::unpack( ds, pending_savanna_lib_id ); fc::raw::unpack( ds, *_root ); reset_root_impl( _root ); @@ -168,32 +143,7 @@ namespace eosio::chain { fc::raw::unpack( ds, s ); // do not populate transaction_metadatas, they will be created as needed in apply_block with appropriate key recovery s.header_exts = s.block->validate_and_extract_header_extensions(); - add_impl( std::make_shared( std::move( s ) ), mark_valid_t::no, ignore_duplicate_t::no, true, validator ); - } - block_id_type head_id; - fc::raw::unpack( ds, head_id ); - - if( root->id() == head_id ) { - head = root; - } else { - head = get_block_impl( head_id ); - if (!head) - std::filesystem::remove( fork_db_file ); - EOS_ASSERT( head, fork_database_exception, - "could not find head while reconstructing fork database from file; ${d} " - "'${filename}' is likely corrupted and has been removed", - ("d", desc)("filename", fork_db_file) ); - } - - auto candidate = index.template get().begin(); - if( candidate == index.template get().end() || !bs_accessor_t::is_valid(**candidate) ) { - EOS_ASSERT( head->id() == root->id(), fork_database_exception, - "head not set to root despite no better option available; ${d} '${filename}' is likely corrupted", - ("d", desc)("filename", fork_db_file) ); - } else { - EOS_ASSERT( !first_preferred( **candidate, *head ), fork_database_exception, - "head not set to the best available option available; ${d} '${filename}' is likely corrupted", - ("d", desc)("filename", fork_db_file) ); + add_impl( std::make_shared( std::move( s ) ), ignore_duplicate_t::no, true, validator ); } } @@ -205,11 +155,17 @@ namespace eosio::chain { template void fork_database_impl::close_impl(std::ofstream& out) { - assert(!!head && !!root); // if head or root are null, we don't save and shouldn't get here + assert(!!root); // if head or root are null, we don't save and shouldn't get here - ilog("Writing fork_database ${b} blocks with root ${rn}:${r} and head ${hn}:${h}", - ("b", head->block_num() - root->block_num())("rn", root->block_num())("r", root->id())("hn", head->block_num())("h", head->id())); + auto head = head_impl(include_root_t::no); + if (head) { + ilog("Writing fork_database ${b} blocks with root ${rn}:${r} and head ${hn}:${h}", + ("b", head->block_num() - root->block_num())("rn", root->block_num())("r", root->id())("hn", head->block_num())("h", head->id())); + } else { + ilog("Writing empty fork_database with root ${rn}:${r}", ("rn", root->block_num())("r", root->id())); + } + fc::raw::pack( out, pending_savanna_lib_id ); fc::raw::pack( out, *root ); uint32_t num_blocks_in_fork_db = index.size(); @@ -217,41 +173,10 @@ namespace eosio::chain { const auto& indx = index.template get(); - auto unvalidated_itr = indx.rbegin(); - auto unvalidated_end = boost::make_reverse_iterator( indx.lower_bound( false ) ); - - auto validated_itr = unvalidated_end; - auto validated_end = indx.rend(); - - for( bool unvalidated_remaining = (unvalidated_itr != unvalidated_end), - validated_remaining = (validated_itr != validated_end); - - unvalidated_remaining || validated_remaining; - - unvalidated_remaining = (unvalidated_itr != unvalidated_end), - validated_remaining = (validated_itr != validated_end) - ) - { - auto itr = (validated_remaining ? validated_itr : unvalidated_itr); - - if( unvalidated_remaining && validated_remaining ) { - if( first_preferred( **validated_itr, **unvalidated_itr ) ) { - itr = unvalidated_itr; - ++unvalidated_itr; - } else { - ++validated_itr; - } - } else if( unvalidated_remaining ) { - ++unvalidated_itr; - } else { - ++validated_itr; - } - + for (auto itr = indx.rbegin(); itr != indx.rend(); ++itr) { fc::raw::pack( out, *(*itr) ); } - fc::raw::pack( out, head->id() ); - index.clear(); } @@ -264,28 +189,9 @@ namespace eosio::chain { template void fork_database_impl::reset_root_impl( const bsp_t& root_bsp ) { index.clear(); + assert(root_bsp); root = root_bsp; - bs_accessor_t::set_valid(*root, true); - head = root; - } - - template - void fork_database_t::rollback_head_to_root() { - std::lock_guard g( my->mtx ); - my->rollback_head_to_root_impl(); - } - - template - void fork_database_impl::rollback_head_to_root_impl() { - auto& by_id_idx = index.template get(); - auto itr = by_id_idx.begin(); - while (itr != by_id_idx.end()) { - by_id_idx.modify( itr, []( auto& i ) { - bs_accessor_t::set_valid(*i, false); - } ); - ++itr; - } - head = root; + root->set_valid(true); } template @@ -301,7 +207,7 @@ namespace eosio::chain { auto new_root = get_block_impl( id ); EOS_ASSERT( new_root, fork_database_exception, "cannot advance root to a block that does not exist in the fork database" ); - EOS_ASSERT( bs_accessor_t::is_valid(*new_root), fork_database_exception, + EOS_ASSERT( new_root->is_valid(), fork_database_exception, "cannot advance root to a block that has not yet been validated" ); @@ -330,7 +236,7 @@ namespace eosio::chain { } template - void fork_database_impl::add_impl(const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate, + void fork_database_impl::add_impl(const bsp_t& n, ignore_duplicate_t ignore_duplicate, bool validate, validator_t& validator) { EOS_ASSERT( root, fork_database_exception, "root not yet set" ); EOS_ASSERT( n, fork_database_exception, "attempt to add null block state" ); @@ -355,26 +261,12 @@ namespace eosio::chain { auto inserted = index.insert(n); EOS_ASSERT(ignore_duplicate == ignore_duplicate_t::yes || inserted.second, fork_database_exception, "duplicate block added: ${id}", ("id", n->id())); - - if (mark_valid == mark_valid_t::yes) { - // if just inserted and was inserted already valid then no update needed - if (!inserted.second || !bs_accessor_t::is_valid(*n)) { - index.modify( inserted.first, []( auto& i ) { - bs_accessor_t::set_valid(*i, true); - } ); - } - } - - auto candidate = index.template get().begin(); - if (bs_accessor_t::is_valid(**candidate)) { - head = *candidate; - } } template - void fork_database_t::add( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate ) { + void fork_database_t::add( const bsp_t& n, ignore_duplicate_t ignore_duplicate ) { std::lock_guard g( my->mtx ); - my->add_impl( n, mark_valid, ignore_duplicate, false, + my->add_impl( n, ignore_duplicate, false, []( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -390,7 +282,7 @@ namespace eosio::chain { template bool fork_database_impl::is_valid() const { - return !!root && !!head && (root->id() == head->id() || get_block_impl(head->id())); + return !!root; } template @@ -405,27 +297,42 @@ namespace eosio::chain { } template - BSP fork_database_t::head() const { + BSP fork_database_t::head(include_root_t include_root) const { std::lock_guard g( my->mtx ); - return my->head; + return my->head_impl(include_root); + } + + template + BSP fork_database_impl::head_impl(include_root_t include_root) const { + if (index.empty()) { + if (include_root == include_root_t::yes) + return root; + return {}; + } + const auto& indx = index.template get(); + return *indx.begin(); } template - BSP fork_database_t::pending_head() const { + block_id_type fork_database_t::pending_savanna_lib_id() const { std::lock_guard g( my->mtx ); - const auto& indx = my->index.template get(); + return my->pending_savanna_lib_id; + } - auto itr = indx.lower_bound( false ); - if( itr != indx.end() && !fork_database_impl::bs_accessor_t::is_valid(**itr) ) { - if( first_preferred( **itr, *my->head ) ) - return *itr; + template + bool fork_database_t::set_pending_savanna_lib_id(const block_id_type& id) { + block_num_type new_lib = block_header::num_from_id(id); + std::lock_guard g( my->mtx ); + block_num_type old_lib = block_header::num_from_id(my->pending_savanna_lib_id); + if (new_lib > old_lib) { + my->pending_savanna_lib_id = id; + return true; } - - return my->head; + return false; } template - fork_database_t::branch_t + eosio::chain::fork_database_t::branch_t fork_database_t::fetch_branch(const block_id_type& h, uint32_t trim_after_block_num) const { std::lock_guard g(my->mtx); return my->fetch_branch_impl(h, trim_after_block_num); @@ -533,6 +440,9 @@ namespace eosio::chain { template BSP fork_database_impl::search_on_head_branch_impl( uint32_t block_num, include_root_t include_root ) const { + auto head = head_impl(include_root); + if (!head) + return head; return search_on_branch_impl(head->id(), block_num, include_root); } @@ -618,12 +528,8 @@ namespace eosio::chain { void fork_database_impl::remove_impl( const block_id_type& id ) { deque remove_queue{id}; const auto& previdx = index.template get(); - const auto& head_id = head->id(); for( uint32_t i = 0; i < remove_queue.size(); ++i ) { - EOS_ASSERT( remove_queue[i] != head_id, fork_database_exception, - "removing the block and its descendants would remove the current head block ${id}", ("id", head_id) ); - auto previtr = previdx.lower_bound( remove_queue[i] ); while( previtr != previdx.end() && (*previtr)->previous() == remove_queue[i] ) { remove_queue.emplace_back( (*previtr)->id() ); @@ -636,33 +542,6 @@ namespace eosio::chain { } } - template - void fork_database_t::mark_valid( const bsp_t& h ) { - std::lock_guard g( my->mtx ); - my->mark_valid_impl( h ); - } - - template - void fork_database_impl::mark_valid_impl( const bsp_t& h ) { - if( bs_accessor_t::is_valid(*h) ) return; - - auto& by_id_idx = index.template get(); - - auto itr = by_id_idx.find( h->id() ); - EOS_ASSERT( itr != by_id_idx.end(), fork_database_exception, - "block state not in fork database; cannot mark as valid", - ("id", h->id()) ); - - by_id_idx.modify( itr, []( auto& i ) { - bs_accessor_t::set_valid(*i, true); - } ); - - auto candidate = index.template get().begin(); - if( first_preferred( **candidate, *head ) ) { - head = *candidate; - } - } - template BSP fork_database_t::get_block(const block_id_type& id, include_root_t include_root /* = include_root_t::no */) const { @@ -702,7 +581,7 @@ namespace eosio::chain { template bool fork_database_impl::validated_block_exists_impl(const block_id_type& id) const { auto itr = index.find( id ); - return itr != index.end() && bs_accessor_t::is_valid(*(*itr)); + return itr != index.end() && (*itr)->is_valid(); } // ------------------ fork_database ------------------------- @@ -842,10 +721,33 @@ namespace eosio::chain { block_branch_t fork_database::fetch_branch_from_head() const { return apply([&](auto& forkdb) { - return forkdb.fetch_block_branch(forkdb.head()->id()); + auto head = forkdb.head(); + if (head) + return forkdb.fetch_block_branch(head->id()); + return block_branch_t{}; }); } + block_id_type fork_database::pending_lib_id(const block_id_type& head_id) const { + if (in_use.load() == in_use_t::legacy) { + block_state_legacy_ptr head; + if (head_id.empty()) { + head = fork_db_l.head(); + } else { + head = fork_db_l.get_block(head_id); + } + if (!head) + return {}; + block_num_type lib_num = head->irreversible_blocknum(); + auto lib = fork_db_l.search_on_branch(head->id(), lib_num, include_root_t::no); + if (!lib) + return {}; + return lib->id(); + } else { + return fork_db_s.pending_savanna_lib_id(); + } + } + // do class instantiations template class fork_database_t; template class fork_database_t; diff --git a/libraries/chain/include/eosio/chain/block_handle.hpp b/libraries/chain/include/eosio/chain/block_handle.hpp index 81f8b6e2b4..586c2cb13c 100644 --- a/libraries/chain/include/eosio/chain/block_handle.hpp +++ b/libraries/chain/include/eosio/chain/block_handle.hpp @@ -24,7 +24,7 @@ struct block_handle { explicit block_handle(block_state_legacy_ptr bsp) : _bsp(std::move(bsp)) {} explicit block_handle(block_state_ptr bsp) : _bsp(std::move(bsp)) {} - bool is_valid() const { return _bsp.index() != std::variant_npos && std::visit([](const auto& bsp) { return !!bsp; }, _bsp); } + bool is_valid() const { return !_bsp.valueless_by_exception() && std::visit([](const auto& bsp) { return !!bsp; }, _bsp); } uint32_t block_num() const { return std::visit([](const auto& bsp) { return bsp->block_num(); }, _bsp); } block_timestamp_type block_time() const { return std::visit([](const auto& bsp) { return bsp->timestamp(); }, _bsp); }; diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 857acab1b6..87a4d5983c 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -92,15 +92,17 @@ struct block_state : public block_header_state { // block_header_state provi std::optional base_digest; // For finality_data sent to SHiP, computed on demand in get_finality_data() // ------ private methods ----------------------------------------------------------- + void set_valid(bool v) { validated.store(v); } bool is_valid() const { return validated.load(); } bool is_pub_keys_recovered() const { return pub_keys_recovered; } deque extract_trxs_metas(); void set_trxs_metas(deque&& trxs_metas, bool keys_recovered); const deque& trxs_metas() const { return cached_trxs; } - friend struct block_state_accessor; + friend struct test_block_state_accessor; friend struct fc::reflector; friend struct controller_impl; + template friend struct fork_database_impl; friend struct completed_block; friend struct building_block; public: @@ -151,7 +153,6 @@ struct block_state : public block_header_state { // block_header_state provi using bhs_t = block_header_state; using bhsp_t = block_header_state_ptr; - using fork_db_block_state_accessor_t = block_state_accessor; block_state() = default; block_state(const block_state&) = delete; diff --git a/libraries/chain/include/eosio/chain/block_state_legacy.hpp b/libraries/chain/include/eosio/chain/block_state_legacy.hpp index bdc0fb645d..a42760c095 100644 --- a/libraries/chain/include/eosio/chain/block_state_legacy.hpp +++ b/libraries/chain/include/eosio/chain/block_state_legacy.hpp @@ -59,10 +59,14 @@ namespace eosio::chain { friend struct block_state_legacy_accessor; friend struct fc::reflector; friend struct controller_impl; + template friend struct fork_database_impl; friend struct completed_block; friend struct block_state; + // not thread safe, expected to only be called from main thread + void set_valid(bool v) { validated = v; } bool is_valid() const { return validated; } + bool is_pub_keys_recovered()const { return _pub_keys_recovered; } deque extract_trxs_metas() { diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 30bbf4b6ae..bd637161ea 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -277,8 +277,7 @@ namespace eosio::chain { // returns nullptr pre-savanna, thread-safe, block_num according to branch curresponding to id finalizer_policy_ptr active_finalizer_policy(const block_id_type& id, block_num_type block_num)const; - void set_if_irreversible_block_id(const block_id_type& id); - uint32_t if_irreversible_block_num() const; + void set_savanna_lib_id(const block_id_type& id); uint32_t last_irreversible_block_num() const; block_id_type last_irreversible_block_id() const; diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index 139ee0c89b..7c96073ccd 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -10,7 +10,6 @@ namespace eosio::chain { struct fork_database_impl; using block_branch_t = std::vector; - enum class mark_valid_t { no, yes }; enum class ignore_duplicate_t { no, yes }; enum class include_root_t { no, yes }; @@ -59,11 +58,6 @@ namespace eosio::chain { */ void reset_root( const bsp_t& root_bhs ); - /** - * Removes validated flag from all blocks in fork database and resets head to point to the root. - */ - void rollback_head_to_root(); - /** * Advance root block forward to some other block in the tree. */ @@ -72,18 +66,32 @@ namespace eosio::chain { /** * Add block state to fork database. * Must link to existing block in fork database or the root. - * @param mark_valid if true also mark next_block valid */ - void add( const bsp_t& next_block, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate ); + void add( const bsp_t& next_block, ignore_duplicate_t ignore_duplicate ); void remove( const block_id_type& id ); bool is_valid() const; // sanity checks on this fork_db - bool has_root() const; - bsp_t root() const; // undefined if !has_root() - bsp_t head() const; - bsp_t pending_head() const; + bool has_root() const; + + /** + * Root of the fork database, not part of the index. Corresponds to head of the block log. Is an irreversible block. + * Undefined if !has_root() + */ + bsp_t root() const; + + /** + * The best branch head of blocks in the fork database, can be null if include_root_t::no and forkdb is empty + * @param include_root yes if root should be returned if no blocks in fork database + */ + bsp_t head(include_root_t include_root = include_root_t::no) const; + + /** + * The calculated pending savanna LIB ID that will become LIB or is currently LIB + */ + block_id_type pending_savanna_lib_id() const; + bool set_pending_savanna_lib_id( const block_id_type& id ); /** * Returns the sequence of block states resulting from trimming the branch from the @@ -127,8 +135,6 @@ namespace eosio::chain { */ branch_pair_t fetch_branch_from(const block_id_type& first, const block_id_type& second) const; - void mark_valid( const bsp_t& h ); - private: unique_ptr> my; }; @@ -171,6 +177,11 @@ namespace eosio::chain { // see fork_database_t::fetch_branch(forkdb->head()->id()) block_branch_t fetch_branch_from_head() const; + /// The pending LIB. + /// - legacy returns dpos_irreversible_blocknum according to head_id or pending head if head_id is empty + /// - savanna returns the current pending_savanna_lib_id + block_id_type pending_lib_id(const block_id_type& head_id) const; + template R apply(const F& f) const { if constexpr (std::is_same_v) { diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 59ccf6a9b5..c8fdbc9d39 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1289,13 +1289,17 @@ const string read_only::KEYi64 = "i64"; read_only::get_info_results read_only::get_info(const read_only::get_info_params&, const fc::time_point&) const { const auto& rm = db.get_resource_limits_manager(); + auto head_id = db.head_block_id(); + auto lib_id = db.last_irreversible_block_id(); + auto fhead_id = db.fork_db_head_block_id(); + return { itoh(static_cast(app().version())), db.get_chain_id(), - db.head_block_num(), - db.last_irreversible_block_num(), - db.last_irreversible_block_id(), - db.head_block_id(), + block_header::num_from_id(head_id), + block_header::num_from_id(lib_id), + lib_id, + head_id, db.head_block_time(), db.head_block_producer(), rm.get_virtual_block_cpu_limit(), @@ -1305,8 +1309,8 @@ read_only::get_info_results read_only::get_info(const read_only::get_info_params //std::bitset<64>(db.get_dynamic_global_properties().recent_slots_filled).to_string(), //__builtin_popcountll(db.get_dynamic_global_properties().recent_slots_filled) / 64.0, app().version_string(), - db.fork_db_head_block_num(), - db.fork_db_head_block_id(), + block_header::num_from_id(fhead_id), + fhead_id, app().full_version_string(), rm.get_total_cpu_weight(), rm.get_total_net_weight(), diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index f415b1398a..a35bdb4baa 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -971,8 +971,8 @@ namespace eosio { std::optional last_req GUARDED_BY(conn_mtx); handshake_message last_handshake_recv GUARDED_BY(conn_mtx); handshake_message last_handshake_sent GUARDED_BY(conn_mtx); - block_id_type fork_head GUARDED_BY(conn_mtx); - uint32_t fork_head_num GUARDED_BY(conn_mtx) {0}; + block_id_type conn_fork_head GUARDED_BY(conn_mtx); + uint32_t conn_fork_head_num GUARDED_BY(conn_mtx) {0}; fc::time_point last_close GUARDED_BY(conn_mtx); std::string p2p_address GUARDED_BY(conn_mtx); std::string unique_conn_node_id GUARDED_BY(conn_mtx); @@ -1061,7 +1061,7 @@ namespace eosio { /** @} */ void blk_send_branch( const block_id_type& msg_head_id ); - void blk_send_branch( uint32_t msg_head_num, uint32_t lib_num, uint32_t fork_head_num ); + void blk_send_branch( uint32_t msg_head_num, uint32_t lib_num, uint32_t head_num ); void blk_send(const block_id_type& blkid); void enqueue( const net_message &msg ); @@ -1498,10 +1498,10 @@ namespace eosio { // called from connection strand void connection::blk_send_branch( const block_id_type& msg_head_id ) { - uint32_t fork_head_num = my_impl->get_fork_head_num(); + uint32_t head_num = my_impl->get_chain_head_num(); - peer_dlog(this, "fhead_num = ${h}",("h",fork_head_num)); - if(fork_head_num == 0) { + peer_dlog(this, "head_num = ${h}",("h",head_num)); + if(head_num == 0) { notice_message note; note.known_blocks.mode = normal; note.known_blocks.pending = 0; @@ -1540,18 +1540,18 @@ namespace eosio { } else { if( on_fork ) msg_head_num = 0; // if peer on fork, start at their last lib, otherwise we can start at msg_head+1 - blk_send_branch( msg_head_num, lib_num, fork_head_num ); + blk_send_branch( msg_head_num, lib_num, head_num ); } } // called from connection strand - void connection::blk_send_branch( uint32_t msg_head_num, uint32_t lib_num, uint32_t fork_head_num ) { + void connection::blk_send_branch( uint32_t msg_head_num, uint32_t lib_num, uint32_t head_num ) { if( !peer_requested ) { auto last = msg_head_num != 0 ? msg_head_num : lib_num; - peer_requested = peer_sync_state( last+1, fork_head_num, last ); + peer_requested = peer_sync_state( last+1, head_num, last ); } else { auto last = msg_head_num != 0 ? msg_head_num : std::min( peer_requested->last, lib_num ); - uint32_t end = std::max( peer_requested->end_block, fork_head_num ); + uint32_t end = std::max( peer_requested->end_block, head_num ); peer_requested = peer_sync_state( last+1, end, last ); } if( peer_requested->start_block <= peer_requested->end_block ) { @@ -2365,7 +2365,7 @@ namespace eosio { req.req_blocks.mode = catch_up; auto is_fork_head_greater = [num, &id, &req]( const auto& cc ) { fc::lock_guard g_conn( cc->conn_mtx ); - if( cc->fork_head_num > num || cc->fork_head == id ) { + if( cc->conn_fork_head_num > num || cc->conn_fork_head == id ) { req.req_blocks.mode = none; return true; } @@ -2390,8 +2390,8 @@ namespace eosio { set_state( head_catchup ); { fc::lock_guard g_conn( c->conn_mtx ); - c->fork_head = id; - c->fork_head_num = num; + c->conn_fork_head = id; + c->conn_fork_head_num = num; } req.req_blocks.ids.emplace_back( chain_info.fork_head_id ); @@ -2399,8 +2399,8 @@ namespace eosio { peer_ilog( c, "none notice while in ${s}, fhead = ${hn}, id ${id}...", ("s", stage_str( sync_state ))("hn", num)("id", id.str().substr(8,16)) ); fc::lock_guard g_conn( c->conn_mtx ); - c->fork_head = block_id_type(); - c->fork_head_num = 0; + c->conn_fork_head = block_id_type(); + c->conn_fork_head_num = 0; } req.req_trx.mode = none; c->enqueue( req ); @@ -2489,15 +2489,15 @@ namespace eosio { bool set_state_to_head_catchup = false; my_impl->connections.for_each_block_connection( [&null_id, blk_num, &blk_id, &c, &set_state_to_head_catchup]( const auto& cp ) { fc::unique_lock g_cp_conn( cp->conn_mtx ); - uint32_t fork_head_num = cp->fork_head_num; - block_id_type fork_head_id = cp->fork_head; + uint32_t fork_head_num = cp->conn_fork_head_num; + block_id_type fork_head_id = cp->conn_fork_head; g_cp_conn.unlock(); if( fork_head_id == null_id ) { // continue } else if( fork_head_num < blk_num || fork_head_id == blk_id ) { fc::lock_guard g_conn( c->conn_mtx ); - c->fork_head = null_id; - c->fork_head_num = 0; + c->conn_fork_head = null_id; + c->conn_fork_head_num = 0; } else { set_state_to_head_catchup = true; } diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 65c764bc53..6f92808fe6 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -765,6 +765,7 @@ class producer_plugin_impl : public std::enable_shared_from_thisset_valid(v); + } + + static bool is_valid(const block_state_ptr& bsp) { + return bsp->is_valid(); + } + }; +} + // --------------------------------------------------------------------------------------- struct bls_keys_t { bls_private_key privkey; @@ -136,7 +150,7 @@ struct simulator_t { } vote_result propose(const proposal_t& p, std::optional _claim = {}) { - bsp h = forkdb.head(); + bsp h = forkdb.head(include_root_t::yes); qc_claim_t old_claim = _claim ? *_claim : h->core.latest_qc_claim(); bsp new_bsp = make_bsp(p, h, finpol, old_claim); bsp_vec.push_back(new_bsp); @@ -145,11 +159,12 @@ struct simulator_t { } result add(const proposal_t& p, std::optional _claim = {}, const bsp& parent = {}) { - bsp h = parent ? parent : forkdb.head(); + bsp h = parent ? parent : forkdb.head(include_root_t::yes); qc_claim_t old_claim = _claim ? *_claim : h->core.latest_qc_claim(); bsp new_bsp = make_bsp(p, h, finpol, old_claim); bsp_vec.push_back(new_bsp); - forkdb.add(new_bsp, mark_valid_t::yes, ignore_duplicate_t::no); + test_block_state_accessor::set_valid(new_bsp, true); + forkdb.add(new_bsp, ignore_duplicate_t::no); auto v = vote(new_bsp); return { new_bsp, v }; diff --git a/unittests/fork_db_tests.cpp b/unittests/fork_db_tests.cpp index 77e8652b54..f9525ca907 100644 --- a/unittests/fork_db_tests.cpp +++ b/unittests/fork_db_tests.cpp @@ -17,7 +17,7 @@ inline block_id_type make_block_id(block_num_type block_num) { } // Used to access privates of block_state -struct block_state_accessor { +struct test_block_state_accessor { static auto make_genesis_block_state() { block_state_ptr root = std::make_shared(); block_id_type genesis_id = make_block_id(10); @@ -43,7 +43,11 @@ struct block_state_accessor { } static void reset_valid(block_state_ptr& bsp) { - bsp->validated.store(false); + bsp->set_valid(false); + } + + static bool is_valid(const block_state_ptr& bsp) { + return bsp->is_valid(); } }; @@ -58,41 +62,41 @@ BOOST_AUTO_TEST_CASE(add_remove_test) try { // Setup fork database with blocks based on a root of block 10 // Add a number of forks in the fork database - auto root = block_state_accessor::make_genesis_block_state(); - auto bsp11a = block_state_accessor::make_unique_block_state(11, root); - auto bsp12a = block_state_accessor::make_unique_block_state(12, bsp11a); - auto bsp13a = block_state_accessor::make_unique_block_state(13, bsp12a); - auto bsp11b = block_state_accessor::make_unique_block_state(11, root); - auto bsp12b = block_state_accessor::make_unique_block_state(12, bsp11b); - auto bsp13b = block_state_accessor::make_unique_block_state(13, bsp12b); - auto bsp14b = block_state_accessor::make_unique_block_state(14, bsp13b); - auto bsp12bb = block_state_accessor::make_unique_block_state(12, bsp11b); - auto bsp13bb = block_state_accessor::make_unique_block_state(13, bsp12bb); - auto bsp13bbb = block_state_accessor::make_unique_block_state(13, bsp12bb); - auto bsp12bbb = block_state_accessor::make_unique_block_state(12, bsp11b); - auto bsp11c = block_state_accessor::make_unique_block_state(11, root); - auto bsp12c = block_state_accessor::make_unique_block_state(12, bsp11c); - auto bsp13c = block_state_accessor::make_unique_block_state(13, bsp12c); + auto root = test_block_state_accessor::make_genesis_block_state(); + auto bsp11a = test_block_state_accessor::make_unique_block_state(11, root); + auto bsp12a = test_block_state_accessor::make_unique_block_state(12, bsp11a); + auto bsp13a = test_block_state_accessor::make_unique_block_state(13, bsp12a); + auto bsp11b = test_block_state_accessor::make_unique_block_state(11, root); + auto bsp12b = test_block_state_accessor::make_unique_block_state(12, bsp11b); + auto bsp13b = test_block_state_accessor::make_unique_block_state(13, bsp12b); + auto bsp14b = test_block_state_accessor::make_unique_block_state(14, bsp13b); + auto bsp12bb = test_block_state_accessor::make_unique_block_state(12, bsp11b); + auto bsp13bb = test_block_state_accessor::make_unique_block_state(13, bsp12bb); + auto bsp13bbb = test_block_state_accessor::make_unique_block_state(13, bsp12bb); + auto bsp12bbb = test_block_state_accessor::make_unique_block_state(12, bsp11b); + auto bsp11c = test_block_state_accessor::make_unique_block_state(11, root); + auto bsp12c = test_block_state_accessor::make_unique_block_state(12, bsp11c); + auto bsp13c = test_block_state_accessor::make_unique_block_state(13, bsp12c); // keep track of all those added for easy verification std::vector all { bsp11a, bsp12a, bsp13a, bsp11b, bsp12b, bsp12bb, bsp12bbb, bsp13b, bsp13bb, bsp13bbb, bsp14b, bsp11c, bsp12c, bsp13c }; auto reset = [&]() { forkdb.reset_root(root); - forkdb.add(bsp11a, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp11b, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp11c, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp12a, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp13a, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp12b, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp12bb, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp12bbb, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp12c, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp13b, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp13bb, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp13bbb, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp14b, mark_valid_t::no, ignore_duplicate_t::no); - forkdb.add(bsp13c, mark_valid_t::no, ignore_duplicate_t::no); + forkdb.add(bsp11a, ignore_duplicate_t::no); + forkdb.add(bsp11b, ignore_duplicate_t::no); + forkdb.add(bsp11c, ignore_duplicate_t::no); + forkdb.add(bsp12a, ignore_duplicate_t::no); + forkdb.add(bsp13a, ignore_duplicate_t::no); + forkdb.add(bsp12b, ignore_duplicate_t::no); + forkdb.add(bsp12bb, ignore_duplicate_t::no); + forkdb.add(bsp12bbb, ignore_duplicate_t::no); + forkdb.add(bsp12c, ignore_duplicate_t::no); + forkdb.add(bsp13b, ignore_duplicate_t::no); + forkdb.add(bsp13bb, ignore_duplicate_t::no); + forkdb.add(bsp13bbb, ignore_duplicate_t::no); + forkdb.add(bsp14b, ignore_duplicate_t::no); + forkdb.add(bsp13c, ignore_duplicate_t::no); }; reset(); @@ -106,9 +110,9 @@ BOOST_AUTO_TEST_CASE(add_remove_test) try { BOOST_TEST(!forkdb.get_block(bsp12b->id())); BOOST_TEST(!forkdb.get_block(bsp13b->id())); BOOST_TEST(!forkdb.get_block(bsp14b->id())); - forkdb.add(bsp12b, mark_valid_t::no, ignore_duplicate_t::no); // will throw if already exists - forkdb.add(bsp13b, mark_valid_t::no, ignore_duplicate_t::no); // will throw if already exists - forkdb.add(bsp14b, mark_valid_t::no, ignore_duplicate_t::no); // will throw if already exists + forkdb.add(bsp12b, ignore_duplicate_t::no); // will throw if already exists + forkdb.add(bsp13b, ignore_duplicate_t::no); // will throw if already exists + forkdb.add(bsp14b, ignore_duplicate_t::no); // will throw if already exists // test search BOOST_TEST(forkdb.search_on_branch( bsp13bb->id(), 11) == bsp11b); @@ -126,23 +130,12 @@ BOOST_AUTO_TEST_CASE(add_remove_test) try { BOOST_TEST(branch[2] == bsp11b); // test fetch branch providing head and lib - BOOST_TEST(forkdb.head()->id() == root->id()); - forkdb.mark_valid(bsp13a); - BOOST_TEST(forkdb.head()->id() == bsp13a->id()); - branch = forkdb.fetch_branch(forkdb.head()->id(), bsp11c->id()); + branch = forkdb.fetch_branch(bsp13a->id(), bsp11c->id()); BOOST_TEST(branch.empty()); // bsp11c not on bsp13a branch - branch = forkdb.fetch_branch(forkdb.head()->id(), bsp12a->id()); + branch = forkdb.fetch_branch(bsp13a->id(), bsp12a->id()); BOOST_REQUIRE(branch.size() == 2); BOOST_TEST(branch[0] == bsp12a); BOOST_TEST(branch[1] == bsp11a); - - // test mark valid via add - block_state_accessor::reset_valid(bsp13a); - reset(); - BOOST_TEST(forkdb.head()->id() == root->id()); - forkdb.add(bsp13a, mark_valid_t::yes, ignore_duplicate_t::yes); - BOOST_TEST(forkdb.head()->id() == bsp13a->id()); - } FC_LOG_AND_RETHROW(); BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/fork_test_utilities.cpp b/unittests/fork_test_utilities.cpp index ea1fec89c0..16b8ed8ee9 100644 --- a/unittests/fork_test_utilities.cpp +++ b/unittests/fork_test_utilities.cpp @@ -9,10 +9,10 @@ public_key_type get_public_key( name keyname, string role ){ } void push_blocks( tester& from, tester& to, uint32_t block_num_limit ) { - while( to.control->fork_db_head_block_num() - < std::min( from.control->fork_db_head_block_num(), block_num_limit ) ) - { - auto fb = from.control->fetch_block_by_number( to.control->fork_db_head_block_num()+1 ); + block_num_type from_head_num = std::min( from.control->fork_db_head_block_num(), block_num_limit ); + block_num_type to_head_num = to.control->fork_db_head_block_num(); + while( to_head_num < from_head_num) { + auto fb = from.control->fetch_block_by_number( ++to_head_num ); to.push_block( fb ); } } diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 0258d8ac65..e1a17b4100 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -92,21 +92,35 @@ BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { offset = fc::milliseconds(config::block_interval_ms); } + // forkdb is sorted on block id which can cause fork switch on the second to last block or last block depending + // on block id. Allow exception on either one so that test is not sensitive to block id hash. + auto push_last_two = [&](const fork_tracker& fork) { + if (fork.blocks.size() > 1) { + const auto& b = fork.blocks.at(fork.blocks.size() - 2); + if (!bios.control->fetch_block_by_id(b->calculate_id())) { + bios.push_block(b); + } + } + bios.push_block(fork.blocks.back()); + }; + // go from most corrupted fork to least for (size_t i = 0; i < forks.size(); i++) { BOOST_TEST_CONTEXT("Testing Fork: " << i) { const auto& fork = forks.at(i); // push the fork to the original node - for (size_t fidx = 0; fidx < fork.blocks.size() - 1; fidx++) { - const auto& b = fork.blocks.at(fidx); - // push the block only if its not known already - if (!bios.control->fetch_block_by_id(b->calculate_id())) { - bios.push_block(b); + if (fork.blocks.size() > 1) { + for (size_t fidx = 0; fidx < fork.blocks.size() - 2; fidx++) { + const auto& b = fork.blocks.at(fidx); + // push the block only if its not known already + if (!bios.control->fetch_block_by_id(b->calculate_id())) { + bios.push_block(b); + } } } // push the block which should attempt the corrupted fork and fail - BOOST_REQUIRE_EXCEPTION( bios.push_block(fork.blocks.back()), fc::exception, + BOOST_REQUIRE_EXCEPTION( push_last_two(fork), fc::exception, fc_exception_message_starts_with( "Block ID does not match" ) ); }