diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index de40ba5874..bdf68f0cab 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -32,7 +32,7 @@ block_state::block_state(const block_header_state& bhs, dequefinalizers.size(), bhs.active_finalizer_policy->threshold, bhs.active_finalizer_policy->max_weak_sum_before_weak_final()) - , pub_keys_recovered(true) // probably not needed + , pub_keys_recovered(true) // called by produce_block so signature recovery of trxs must have been done , cached_trxs(std::move(trx_metas)) { block->transactions = std::move(trx_receipts); @@ -83,7 +83,7 @@ void block_state::set_trxs_metas( deque&& trxs_metas, } // Called from net threads -std::pair> block_state::aggregate_vote(const vote_message& vote) { +vote_status block_state::aggregate_vote(const vote_message& vote) { const auto& finalizers = active_finalizer_policy->finalizers; auto it = std::find_if(finalizers.begin(), finalizers.end(), @@ -92,20 +92,16 @@ std::pair> block_state::aggregate_vote(cons if (it != finalizers.end()) { auto index = std::distance(finalizers.begin(), it); auto digest = vote.strong ? strong_digest.to_uint8_span() : std::span(weak_digest); - auto [status, strong] = pending_qc.add_vote(vote.strong, + return pending_qc.add_vote(block_num(), + vote.strong, digest, index, vote.finalizer_key, vote.sig, finalizers[index].weight); - - std::optional new_lib{}; - if (status == vote_status::success && strong) - new_lib = core.final_on_strong_qc_block_num; - return {status, new_lib}; } else { wlog( "finalizer_key (${k}) in vote is not in finalizer policy", ("k", vote.finalizer_key) ); - return {vote_status::unknown_public_key, {}}; + return vote_status::unknown_public_key; } } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4adbfbb007..dac27d7136 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -971,7 +971,7 @@ struct controller_impl { // --------------- access fork_db root ---------------------------------------------------------------------- bool fork_db_has_root() const { - return fork_db.apply([&](const auto& forkdb) { return !!forkdb.root(); }); + return fork_db.apply([&](const auto& forkdb) { return !!forkdb.has_root(); }); } block_id_type fork_db_root_block_id() const { @@ -1004,9 +1004,9 @@ struct controller_impl { return prev->block_num(); } - void fork_db_reset_to_head() { + void fork_db_reset_root_to_chain_head() { return fork_db.apply([&](auto& forkdb) { - forkdb.reset(*forkdb.chain_head); + forkdb.reset_root(*forkdb.chain_head); }); } @@ -1017,29 +1017,29 @@ struct controller_impl { }); } - signed_block_ptr fork_db_fetch_block_by_num(uint32_t block_num) const { + signed_block_ptr fetch_block_on_head_branch_by_num(uint32_t block_num) const { return fork_db.apply([&](const auto& forkdb) { - auto bsp = forkdb.search_on_branch(forkdb.head()->id(), block_num); + auto bsp = forkdb.search_on_head_branch(block_num); if (bsp) return bsp->block; return signed_block_ptr{}; }); } - std::optional fork_db_fetch_block_id_by_num(uint32_t block_num) const { + std::optional fetch_block_id_on_head_branch_by_num(uint32_t block_num) const { return fork_db.apply>([&](const auto& forkdb) -> std::optional { - auto bsp = forkdb.search_on_branch(forkdb.head()->id(), block_num); + auto bsp = forkdb.search_on_head_branch(block_num); if (bsp) return bsp->id(); return {}; }); } // search on the branch of head - block_state_ptr fork_db_fetch_bsp_by_num(uint32_t block_num) const { + block_state_ptr fetch_bsp_on_head_branch_by_num(uint32_t block_num) const { return fork_db.apply( overloaded{ [](const fork_database_legacy_t&) -> block_state_ptr { return nullptr; }, [&](const fork_database_if_t&forkdb) -> block_state_ptr { - auto bsp = forkdb.search_on_branch(forkdb.head()->id(), block_num); + auto bsp = forkdb.search_on_head_branch(block_num); return bsp; } } @@ -1047,7 +1047,7 @@ struct controller_impl { } // search on the branch of given id - block_state_ptr fork_db_fetch_bsp_by_num(const block_id_type& id, uint32_t block_num) const { + block_state_ptr fetch_bsp_on_branch_by_num(const block_id_type& id, uint32_t block_num) const { return fork_db.apply( overloaded{ [](const fork_database_legacy_t&) -> block_state_ptr { return nullptr; }, @@ -1300,7 +1300,7 @@ struct controller_impl { void replay(std::function check_shutdown) { auto blog_head = blog.head(); if( !fork_db_has_root() ) { - fork_db_reset_to_head(); + fork_db_reset_root_to_chain_head(); if (!blog_head) return; } @@ -1335,7 +1335,7 @@ struct controller_impl { ilog( "fork database head ${h}, root ${r}", ("h", pending_head->block_num())( "r", forkdb.root()->block_num() ) ); if( pending_head->block_num() < head->block_num() || head->block_num() < forkdb.root()->block_num() ) { ilog( "resetting fork database with new last irreversible block as the new root: ${id}", ("id", head->id()) ); - forkdb.reset( *head ); + forkdb.reset_root( *head ); } else if( head->block_num() != forkdb.root()->block_num() ) { auto new_root = forkdb.search_on_branch( pending_head->id(), head->block_num() ); EOS_ASSERT( new_root, fork_database_exception, @@ -1357,22 +1357,22 @@ 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 - forkdb.reset( *head ); + forkdb.reset_root( *head ); } else if( !except_ptr && !check_shutdown() && forkdb.head() ) { auto head_block_num = head->block_num(); - auto branch = forkdb.fetch_branch( forkdb.head()->id() ); + auto branch = fork_db.fetch_branch_from_head(); int rev = 0; for( auto i = branch.rbegin(); i != branch.rend(); ++i ) { if( check_shutdown() ) break; if( (*i)->block_num() <= head_block_num ) continue; ++rev; - replay_push_block( (*i)->block, controller::block_status::validated ); + replay_push_block( *i, controller::block_status::validated ); } ilog( "${n} reversible blocks replayed", ("n",rev) ); } if( !forkdb.head() ) { - forkdb.reset( *head ); + forkdb.reset_root( *head ); } auto end = fc::time_point::now(); @@ -1440,7 +1440,7 @@ struct controller_impl { initialize_blockchain_state(genesis); // sets head to genesis state if( !forkdb.head() ) { - forkdb.reset( *forkdb.chain_head ); + forkdb.reset_root( *forkdb.chain_head ); } }; @@ -2780,10 +2780,8 @@ struct controller_impl { const auto& bsp = std::get>(cb.bsp); if( s == controller::block_status::incomplete ) { - forkdb.add( bsp ); - forkdb.mark_valid( bsp ); + forkdb.add( bsp, mark_valid_t::yes, ignore_duplicate_t::no ); emit( accepted_block_header, std::tie(bsp->block, bsp->id()) ); - EOS_ASSERT( bsp == forkdb.head(), fork_database_exception, "committed block did not become the new head in fork database"); } else if (s != controller::block_status::irreversible) { forkdb.mark_valid( bsp ); } @@ -2802,6 +2800,21 @@ struct controller_impl { }}); if( s == controller::block_status::incomplete ) { + fork_db.apply_if([&](auto& forkdb) { + const auto& bsp = std::get>(cb.bsp); + + 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 + const auto& if_ext = std::get(bsp->header_exts.lower_bound(if_ext_id)->second); + if (if_ext.qc_claim.is_strong_qc) { + // claim has already been verified + auto claimed = forkdb.search_on_branch(bsp->id(), if_ext.qc_claim.block_num); + if (claimed) { + set_if_irreversible_block_num(claimed->core.final_on_strong_qc_block_num); + } + } + }); + log_irreversible(); } @@ -3111,22 +3124,19 @@ struct controller_impl { // called from net threads and controller's thread pool vote_status process_vote_message( const vote_message& vote ) { - auto aggregate_vote = [&vote](auto& forkdb) -> std::pair> { - auto bsp = forkdb.get_block(vote.proposal_id); - if (bsp) - return bsp->aggregate_vote(vote); - return {vote_status::unknown_block, {}}; + auto aggregate_vote = [&vote](auto& forkdb) -> vote_status { + auto bsp = forkdb.get_block(vote.proposal_id); + if (bsp) { + return bsp->aggregate_vote(vote); + } + return vote_status::unknown_block; }; // TODO: https://github.com/AntelopeIO/leap/issues/2057 // TODO: Do not aggregate votes on block_state if in legacy block fork_db - auto aggregate_vote_legacy = [](auto&) -> std::pair> { - return {vote_status::unknown_block, {}}; + auto aggregate_vote_legacy = [](auto&) -> vote_status { + return vote_status::unknown_block; }; - auto [status, new_lib] = fork_db.apply>>(aggregate_vote_legacy, aggregate_vote); - if (new_lib) { - set_if_irreversible_block_num(*new_lib); - } - return status; + return fork_db.apply(aggregate_vote_legacy, aggregate_vote); } void create_and_send_vote_msg(const block_state_ptr& bsp, const fork_database_if_t& fork_db) { @@ -3162,7 +3172,7 @@ struct controller_impl { const auto& qc_ext = std::get(block_exts.lower_bound(qc_ext_id)->second); const auto& received_qc = qc_ext.qc.qc; - const auto bsp = fork_db_fetch_bsp_by_num( bsp_in->previous(), qc_ext.qc.block_num ); + const auto bsp = fetch_bsp_on_branch_by_num( bsp_in->previous(), qc_ext.qc.block_num ); if( !bsp ) { return; } @@ -3292,7 +3302,7 @@ struct controller_impl { ("s1", qc_proof.qc.is_strong())("s2", new_qc_claim.is_strong_qc)("b", block_num) ); // find the claimed block's block state on branch of id - auto bsp = fork_db_fetch_bsp_by_num( prev.id(), new_qc_claim.block_num ); + auto bsp = fetch_bsp_on_branch_by_num( prev.id(), new_qc_claim.block_num ); EOS_ASSERT( bsp, invalid_qc_claim, "Block state was not found in forkdb for block_num ${q}. Block number: ${b}", @@ -3422,8 +3432,9 @@ struct controller_impl { } auto do_push = [&](auto& forkdb) { - if constexpr (std::is_same_v>) - forkdb.add( bsp ); + if constexpr (std::is_same_v>) { + forkdb.add( bsp, mark_valid_t::no, ignore_duplicate_t::no ); + } if (is_trusted_producer(b->producer)) { trusted_producer_light_validation = true; @@ -3473,7 +3484,7 @@ struct controller_impl { *forkdb.chain_head, b, protocol_features.get_protocol_feature_set(), validator, skip_validate_signee); if (s != controller::block_status::irreversible) { - forkdb.add(bsp, true); + forkdb.add(bsp, mark_valid_t::no, ignore_duplicate_t::yes); } emit(accepted_block_header, std::tie(bsp->block, bsp->id())); @@ -3502,6 +3513,21 @@ struct controller_impl { } FC_LOG_AND_RETHROW( ) } + 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 fork_head = forkdb.head(); + if (forkdb.chain_head->id() != fork_head->id()) { + controller::block_report br; + maybe_switch_forks(br, fork_head, fork_head->is_valid() ? controller::block_status::validated : controller::block_status::complete, + cb, trx_lookup); + } + } + }; + + fork_db.apply(maybe_switch); + } + template void maybe_switch_forks( controller::block_report& br, const BSP& new_head, controller::block_status s, const forked_callback_t& forked_cb, const trx_meta_cache_lookup& trx_lookup ) @@ -3517,8 +3543,9 @@ struct controller_impl { throw; } } else if( new_head->id() != head->id() ) { - ilog("switching forks from ${current_head_id} (block number ${current_head_num}) to ${new_head_id} (block number ${new_head_num})", - ("current_head_id", head->id())("current_head_num", head_block_num())("new_head_id", new_head->id())("new_head_num", new_head->block_num()) ); + ilog("switching forks from ${current_head_id} (block number ${current_head_num}) ${c} to ${new_head_id} (block number ${new_head_num}) ${n}", + ("current_head_id", head->id())("current_head_num", head_block_num())("new_head_id", new_head->id())("new_head_num", new_head->block_num()) + ("c", log_fork_comparison(*head))("n", log_fork_comparison(*new_head))); // not possible to log transaction specific infor when switching forks if (auto dm_logger = get_deep_mind_logger(false)) { @@ -4267,6 +4294,12 @@ void controller::commit_block() { my->commit_block(block_status::incomplete); } +void controller::maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { + validate_db_available_size(); + my->maybe_switch_forks(cb, trx_lookup); +} + + deque controller::abort_block() { return my->abort_block(); } @@ -4478,7 +4511,7 @@ std::optional controller::fetch_block_header_by_id( const b } signed_block_ptr controller::fetch_block_by_number( uint32_t block_num )const { try { - auto b = my->fork_db_fetch_block_by_num( block_num ); + auto b = my->fetch_block_on_head_branch_by_num( block_num ); if (b) return b; @@ -4486,7 +4519,7 @@ signed_block_ptr controller::fetch_block_by_number( uint32_t block_num )const { } FC_CAPTURE_AND_RETHROW( (block_num) ) } std::optional controller::fetch_block_header_by_number( uint32_t block_num )const { try { - auto b = my->fork_db_fetch_block_by_num(block_num); + auto b = my->fetch_block_on_head_branch_by_num(block_num); if (b) return *b; @@ -4500,7 +4533,7 @@ block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try bool find_in_blog = (blog_head && block_num <= blog_head->block_num()); if( !find_in_blog ) { - std::optional id = my->fork_db_fetch_block_id_by_num(block_num); + std::optional id = my->fetch_block_id_on_head_branch_by_num(block_num); if (id) return *id; } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 1f35b71511..9fce0f19fb 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -21,79 +20,142 @@ namespace eosio::chain { * Version 1: initial version of the new refactored fork database portable format */ + 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 = 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_num: " + std::to_string(bs.last_final_block_num()) + ", "; + r += "last_qc_block_num: " + std::to_string(bs.last_qc_block_num()) + ", "; + r += "timestamp: " + bs.timestamp().to_time_point().to_iso_string() + " ]"; + return r; + } + + 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 += "block_num: " + std::to_string(bs.block_num()) + ", "; + r += "timestamp: " + bs.timestamp().to_time_point().to_iso_string() + " ]"; + return r; + } + struct by_block_id; - struct by_lib_block_num; + struct by_best_branch; struct by_prev; - template - bool first_preferred( const bs& lhs, const bs& rhs ) { - return std::pair(lhs.irreversible_blocknum(), lhs.block_num()) > std::pair(rhs.irreversible_blocknum(), rhs.block_num()); + // 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_num(), lhs.last_qc_block_num(), lhs.timestamp()) > + std::make_tuple(rhs.last_final_block_num(), rhs.last_qc_block_num(), rhs.timestamp()); + } + 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 + template // either [block_state_legacy_ptr, block_state_ptr], same with block_header_state_ptr struct fork_database_impl { - using bs = bsp::element_type; - using bhsp = bs::bhsp_t; - using bhs = bhsp::element_type; - - using fork_db_t = fork_database_t; - using branch_type = fork_db_t::branch_type; - using full_branch_type = fork_db_t::full_branch_type; - using branch_type_pair = fork_db_t::branch_type_pair; + 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; + + using fork_db_t = fork_database_t; + using branch_t = fork_db_t::branch_t; + using full_branch_t = fork_db_t::full_branch_t; + using branch_pair_t = fork_db_t::branch_pair_t; + + using by_best_branch_legacy_t = + ordered_unique, + composite_key, + const_mem_fun, + const_mem_fun, + const_mem_fun + >, + composite_key_compare, std::greater, std::greater, sha256_less + >>; + + using by_best_branch_if_t = + ordered_unique, + composite_key, + const_mem_fun, + const_mem_fun, + const_mem_fun, + const_mem_fun + >, + composite_key_compare, std::greater, std::greater, + std::greater, sha256_less> + >; + + using by_best_branch_t = std::conditional_t, + by_best_branch_if_t, + by_best_branch_legacy_t>; using fork_multi_index_type = multi_index_container< - bsp, + bsp_t, indexed_by< - hashed_unique, BOOST_MULTI_INDEX_CONST_MEM_FUN(bs, const block_id_type&, id), std::hash>, - ordered_non_unique, const_mem_fun>, - ordered_unique, - composite_key, - composite_key_compare, std::greater, std::greater, sha256_less>>>>; + hashed_unique, BOOST_MULTI_INDEX_CONST_MEM_FUN(bs_t, const block_id_type&, id), std::hash>, + ordered_non_unique, const_mem_fun>, + by_best_branch_t + > + >; std::mutex mtx; fork_multi_index_type index; - bsp root; // Only uses the block_header_state_legacy portion - bsp head; + bsp_t root; // Only uses the block_header_state portion of block_state + bsp_t head; const uint32_t magic_number; explicit fork_database_impl(uint32_t magic_number) : magic_number(magic_number) {} void open_impl( const std::filesystem::path& fork_db_file, validator_t& validator ); void close_impl( const std::filesystem::path& fork_db_file ); - void add_impl( const bsp& n, bool ignore_duplicate, bool validate, validator_t& validator ); + void add_impl( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate, bool validate, validator_t& validator ); - bhsp get_block_header_impl( const block_id_type& id ) const; - bsp get_block_impl( const block_id_type& id ) const; - void reset_impl( const bhs& root_bhs ); + bhsp_t get_block_header_impl( const block_id_type& id ) const; + bsp_t get_block_impl( const block_id_type& id ) const; + void reset_root_impl( const bhs_t& root_bhs ); void rollback_head_to_root_impl(); void advance_root_impl( const block_id_type& id ); void remove_impl( const block_id_type& id ); - branch_type fetch_branch_impl( const block_id_type& h, uint32_t trim_after_block_num ) const; - full_branch_type fetch_full_branch_impl(const block_id_type& h) const; - bsp search_on_branch_impl( const block_id_type& h, uint32_t block_num ) const; - void mark_valid_impl( const bsp& h ); - branch_type_pair fetch_branch_from_impl( const block_id_type& first, const block_id_type& second ) 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; + 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 ) const; + bsp_t search_on_head_branch_impl( uint32_t block_num ) 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; }; - template - fork_database_t::fork_database_t(uint32_t magic_number) - :my( new fork_database_impl(magic_number) ) + template + fork_database_t::fork_database_t(uint32_t magic_number) + :my( new fork_database_impl(magic_number) ) {} + template + fork_database_t::~fork_database_t() = default; - template - void fork_database_t::open( const std::filesystem::path& fork_db_file, validator_t& validator ) { + template + void fork_database_t::open( const std::filesystem::path& fork_db_file, validator_t& validator ) { std::lock_guard g( my->mtx ); my->open_impl( fork_db_file, validator ); } - template - void fork_database_impl::open_impl( const std::filesystem::path& fork_db_file, validator_t& validator ) { + template + void fork_database_impl::open_impl( const std::filesystem::path& fork_db_file, validator_t& validator ) { if( std::filesystem::exists( fork_db_file ) ) { try { string content; @@ -122,17 +184,17 @@ namespace eosio::chain { ("max", fork_database::max_supported_version) ); - bhs state; + bhs_t state; fc::raw::unpack( ds, state ); - reset_impl( state ); + reset_root_impl( state ); unsigned_int size; fc::raw::unpack( ds, size ); for( uint32_t i = 0, n = size.value; i < n; ++i ) { - bs s; + bs_t s; 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 ) ), false, true, validator ); + 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 ); @@ -146,8 +208,8 @@ namespace eosio::chain { ("filename", fork_db_file) ); } - auto candidate = index.template get().begin(); - if( candidate == index.template get().end() || !(*candidate)->is_valid() ) { + 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; '${filename}' is likely corrupted", ("filename", fork_db_file) ); @@ -162,14 +224,14 @@ namespace eosio::chain { } } - template - void fork_database_t::close(const std::filesystem::path& fork_db_file) { + template + void fork_database_t::close(const std::filesystem::path& fork_db_file) { std::lock_guard g( my->mtx ); my->close_impl(fork_db_file); } - template - void fork_database_impl::close_impl(const std::filesystem::path& fork_db_file) { + template + void fork_database_impl::close_impl(const std::filesystem::path& fork_db_file) { if( !root ) { if( index.size() > 0 ) { elog( "fork_database is in a bad state when closing; not writing out '${filename}'", @@ -181,11 +243,11 @@ namespace eosio::chain { std::ofstream out( fork_db_file.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc ); fc::raw::pack( out, magic_number ); fc::raw::pack( out, fork_database::max_supported_version ); // write out current version which is always max_supported_version - fc::raw::pack( out, *static_cast(&*root) ); + fc::raw::pack( out, *static_cast(&*root) ); uint32_t num_blocks_in_fork_db = index.size(); fc::raw::pack( out, unsigned_int{num_blocks_in_fork_db} ); - const auto& indx = index.template get(); + const auto& indx = index.template get(); auto unvalidated_itr = indx.rbegin(); auto unvalidated_end = boost::make_reverse_iterator( indx.lower_bound( false ) ); @@ -230,54 +292,54 @@ namespace eosio::chain { index.clear(); } - template - void fork_database_t::reset( const bhs& root_bhs ) { + template + void fork_database_t::reset_root( const bhs_t& root_bhs ) { std::lock_guard g( my->mtx ); - my->reset_impl(root_bhs); + my->reset_root_impl(root_bhs); } - template - void fork_database_impl::reset_impl( const bhs& root_bhs ) { + template + void fork_database_impl::reset_root_impl( const bhs_t& root_bhs ) { index.clear(); - root = std::make_shared(); - static_cast(*root) = root_bhs; - root->set_valid(true); + root = std::make_shared(); + static_cast(*root) = root_bhs; + bs_accessor_t::set_valid(*root, true); head = root; } - template - void fork_database_t::rollback_head_to_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() { + 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, []( bsp& _bsp ) { - _bsp->set_valid(false); + by_id_idx.modify( itr, []( auto& i ) { + bs_accessor_t::set_valid(*i, false); } ); ++itr; } head = root; } - template - void fork_database_t::advance_root( const block_id_type& id ) { + template + void fork_database_t::advance_root( const block_id_type& id ) { std::lock_guard g( my->mtx ); my->advance_root_impl( id ); } - template - void fork_database_impl::advance_root_impl( const block_id_type& id ) { + template + void fork_database_impl::advance_root_impl( const block_id_type& id ) { EOS_ASSERT( root, fork_database_exception, "root not yet set" ); 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( new_root->is_valid(), fork_database_exception, + EOS_ASSERT( bs_accessor_t::is_valid(*new_root), fork_database_exception, "cannot advance root to a block that has not yet been validated" ); @@ -305,14 +367,14 @@ namespace eosio::chain { root = new_root; } - template - fork_database_t::bhsp fork_database_t::get_block_header( const block_id_type& id ) const { + template + fork_database_t::bhsp_t fork_database_t::get_block_header( const block_id_type& id ) const { std::lock_guard g( my->mtx ); return my->get_block_header_impl( id ); } - template - fork_database_impl::bhsp fork_database_impl::get_block_header_impl( const block_id_type& id ) const { + template + fork_database_impl::bhsp_t fork_database_impl::get_block_header_impl( const block_id_type& id ) const { if( root->id() == id ) { return root; } @@ -321,16 +383,15 @@ namespace eosio::chain { if( itr != index.end() ) return *itr; - return bhsp(); + return bhsp_t(); } - template - void fork_database_impl::add_impl(const bsp& n, bool ignore_duplicate, bool validate, validator_t& validator) { + template + void fork_database_impl::add_impl(const bsp_t& n, mark_valid_t mark_valid, 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" ); auto prev_bh = get_block_header_impl( n->previous() ); - EOS_ASSERT( prev_bh, unlinkable_block_exception, "unlinkable block", ("id", n->id())("previous", n->previous()) ); @@ -341,31 +402,31 @@ namespace eosio::chain { if (exts.count(protocol_feature_activation::extension_id()) > 0) { const auto& pfa = exts.lower_bound(protocol_feature_activation::extension_id())->second; const auto& new_protocol_features = std::get(pfa).protocol_features; - validator(n->timestamp(), - static_cast(prev_bh.get())->get_activated_protocol_features()->protocol_features, - new_protocol_features); + validator(n->timestamp(), prev_bh->get_activated_protocol_features()->protocol_features, new_protocol_features); } } - EOS_RETHROW_EXCEPTIONS(fork_database_exception, - "serialized fork database is incompatible with configured protocol features") + EOS_RETHROW_EXCEPTIONS( fork_database_exception, "serialized fork database is incompatible with configured protocol features" ) } + if (mark_valid == mark_valid_t::yes) + bs_accessor_t::set_valid(*n, true); + auto inserted = index.insert(n); if( !inserted.second ) { - if( ignore_duplicate ) return; + if( ignore_duplicate == ignore_duplicate_t::yes ) return; EOS_THROW( fork_database_exception, "duplicate block added", ("id", n->id()) ); } - auto candidate = index.template get().begin(); - if( (*candidate)->is_valid() ) { + auto candidate = index.template get().begin(); + if( bs_accessor_t::is_valid(**candidate) ) { head = *candidate; } } - template - void fork_database_t::add( const bsp& n, bool ignore_duplicate ) { + template + void fork_database_t::add( const bsp_t& n, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate ) { std::lock_guard g( my->mtx ); - my->add_impl( n, ignore_duplicate, false, + my->add_impl( n, mark_valid, ignore_duplicate, false, []( block_timestamp_type timestamp, const flat_set& cur_features, const vector& new_features ) @@ -373,25 +434,30 @@ namespace eosio::chain { ); } - template - bsp fork_database_t::root() const { + template + bool fork_database_t::has_root() const { + return !!my->root; + } + + template + BSP fork_database_t::root() const { std::lock_guard g( my->mtx ); return my->root; } - template - bsp fork_database_t::head() const { + template + BSP fork_database_t::head() const { std::lock_guard g( my->mtx ); return my->head; } - template - bsp fork_database_t::pending_head() const { + template + BSP fork_database_t::pending_head() const { std::lock_guard g( my->mtx ); - const auto& indx = my->index.template get(); + const auto& indx = my->index.template get(); auto itr = indx.lower_bound( false ); - if( itr != indx.end() && !(*itr)->is_valid() ) { + if( itr != indx.end() && !fork_database_impl::bs_accessor_t::is_valid(**itr) ) { if( first_preferred( **itr, *my->head ) ) return *itr; } @@ -399,76 +465,107 @@ namespace eosio::chain { return my->head; } - template - fork_database_t::branch_type - fork_database_t::fetch_branch(const block_id_type& h, uint32_t trim_after_block_num) const { + template + 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); } - template - fork_database_t::branch_type - fork_database_impl::fetch_branch_impl(const block_id_type& h, uint32_t trim_after_block_num) const { - branch_type result; + template + fork_database_t::branch_t + fork_database_impl::fetch_branch_impl(const block_id_type& h, uint32_t trim_after_block_num) const { + branch_t result; result.reserve(index.size()); - for (auto s = get_block_impl(h); s; s = get_block_impl(s->previous())) { - if (s->block_num() <= trim_after_block_num) - result.push_back(s); + for (auto i = index.find(h); i != index.end(); i = index.find((*i)->previous())) { + if ((*i)->block_num() <= trim_after_block_num) + result.push_back(*i); } return result; } - template - fork_database_t::full_branch_type - fork_database_t::fetch_full_branch(const block_id_type& h) const { + template + block_branch_t + fork_database_t::fetch_block_branch(const block_id_type& h, uint32_t trim_after_block_num) const { + std::lock_guard g(my->mtx); + return my->fetch_block_branch_impl(h, trim_after_block_num); + } + + template + block_branch_t + fork_database_impl::fetch_block_branch_impl(const block_id_type& h, uint32_t trim_after_block_num) const { + block_branch_t result; + result.reserve(index.size()); + for (auto i = index.find(h); i != index.end(); i = index.find((*i)->previous())) { + if ((*i)->block_num() <= trim_after_block_num) + result.push_back((*i)->block); + } + + return result; + } + + template + fork_database_t::full_branch_t + fork_database_t::fetch_full_branch(const block_id_type& h) const { std::lock_guard g(my->mtx); return my->fetch_full_branch_impl(h); } - template - fork_database_t::full_branch_type - fork_database_impl::fetch_full_branch_impl(const block_id_type& h) const { - full_branch_type result; + template + fork_database_t::full_branch_t + fork_database_impl::fetch_full_branch_impl(const block_id_type& h) const { + full_branch_t result; result.reserve(index.size()); - for (auto s = get_block_impl(h); s; s = get_block_impl(s->previous())) { - result.push_back(s); + for (auto i = index.find(h); i != index.end(); i = index.find((*i)->previous())) { + result.push_back(*i); } result.push_back(root); return result; } - template - bsp fork_database_t::search_on_branch( const block_id_type& h, uint32_t block_num ) const { + template + BSP fork_database_t::search_on_branch( const block_id_type& h, uint32_t block_num ) const { std::lock_guard g( my->mtx ); return my->search_on_branch_impl( h, block_num ); } - template - bsp fork_database_impl::search_on_branch_impl( const block_id_type& h, uint32_t block_num ) const { - for( auto s = get_block_impl(h); s; s = get_block_impl( s->previous() ) ) { - if( s->block_num() == block_num ) - return s; + template + BSP fork_database_impl::search_on_branch_impl( const block_id_type& h, uint32_t block_num ) const { + for( auto i = index.find(h); i != index.end(); i = index.find( (*i)->previous() ) ) { + if ((*i)->block_num() == block_num) + return *i; } return {}; } + template + BSP fork_database_t::search_on_head_branch( uint32_t block_num ) const { + std::lock_guard g(my->mtx); + return my->search_on_head_branch_impl(block_num); + } + + template + BSP fork_database_impl::search_on_head_branch_impl( uint32_t block_num ) const { + return search_on_branch_impl(head->id(), block_num); + } + /** * Given two head blocks, return two branches of the fork graph that * end with a common ancestor (same prior block) */ - template - fork_database_t::branch_type_pair - fork_database_t::fetch_branch_from(const block_id_type& first, const block_id_type& second) const { + template + fork_database_t::branch_pair_t + fork_database_t::fetch_branch_from(const block_id_type& first, const block_id_type& second) const { std::lock_guard g(my->mtx); return my->fetch_branch_from_impl(first, second); } - template - fork_database_t::branch_type_pair - fork_database_impl::fetch_branch_from_impl(const block_id_type& first, const block_id_type& second) const { - pair result; + template + fork_database_t::branch_pair_t + fork_database_impl::fetch_branch_from_impl(const block_id_type& first, const block_id_type& second) const { + branch_pair_t result; auto first_branch = (first == root->id()) ? root : get_block_impl(first); auto second_branch = (second == root->id()) ? root : get_block_impl(second); @@ -526,14 +623,14 @@ namespace eosio::chain { } /// fetch_branch_from_impl /// remove all of the invalid forks built off of this id including this id - template - void fork_database_t::remove( const block_id_type& id ) { + template + void fork_database_t::remove( const block_id_type& id ) { std::lock_guard g( my->mtx ); return my->remove_impl( id ); } - template - void fork_database_impl::remove_impl( const block_id_type& id ) { + template + 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(); @@ -554,15 +651,15 @@ namespace eosio::chain { } } - template - void fork_database_t::mark_valid( const bsp& h ) { + 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& h ) { - if( h->is_valid() ) return; + 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(); @@ -571,28 +668,28 @@ namespace eosio::chain { "block state not in fork database; cannot mark as valid", ("id", h->id()) ); - by_id_idx.modify( itr, []( bsp& _bsp ) { - _bsp->set_valid(true); + by_id_idx.modify( itr, []( auto& i ) { + bs_accessor_t::set_valid(*i, true); } ); - auto candidate = index.template get().begin(); + 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) const { + template + BSP fork_database_t::get_block(const block_id_type& id) const { std::lock_guard g( my->mtx ); return my->get_block_impl(id); } - template - bsp fork_database_impl::get_block_impl(const block_id_type& id) const { + template + BSP fork_database_impl::get_block_impl(const block_id_type& id) const { auto itr = index.find( id ); if( itr != index.end() ) return *itr; - return bsp(); + return {}; } // ------------------ fork_database ------------------------- @@ -662,19 +759,14 @@ namespace eosio::chain { legacy = false; apply_if([&](auto& forkdb) { forkdb.chain_head = new_head; - forkdb.reset(*new_head); + forkdb.reset_root(*new_head); }); } - std::vector fork_database::fetch_branch_from_head() { - std::vector r; - apply([&](auto& forkdb) { - auto branch = forkdb.fetch_branch(forkdb.head()->id()); - r.reserve(branch.size()); - for (auto& b : branch) - r.push_back(b->block); + block_branch_t fork_database::fetch_branch_from_head() const { + return apply([&](auto& forkdb) { + return forkdb.fetch_block_branch(forkdb.head()->id()); }); - return r; } // do class instantiations diff --git a/libraries/chain/hotstuff/block_construction_data_flow.md b/libraries/chain/hotstuff/block_construction_data_flow.md index ca63780ccd..9169b50fc5 100644 --- a/libraries/chain/hotstuff/block_construction_data_flow.md +++ b/libraries/chain/hotstuff/block_construction_data_flow.md @@ -72,7 +72,7 @@ The new storage for IF is: struct block_header_state_core { uint32_t last_final_block_num = 0; // last irreversible (final) block. std::optional final_on_strong_qc_block_num; // will become final if this header achives a strong QC. - std::optional last_qc_block_num; // + uint32_t last_qc_block_num; // uint32_t finalizer_policy_generation; block_header_state_core next(uint32_t last_qc_block_num, bool is_last_qc_strong) const; @@ -110,7 +110,7 @@ struct block_header_state { // block descending from this need the provided qc in the block extension bool is_needed(const quorum_certificate& qc) const { - return !core.last_qc_block_num || qc.block_num > *core.last_qc_block_num; + return qc.block_num > core.last_qc_block_num; } block_header_state next(const block_header_state_input& data) const; diff --git a/libraries/chain/hotstuff/finalizer.cpp b/libraries/chain/hotstuff/finalizer.cpp index 8145d9d1db..b20fbda074 100644 --- a/libraries/chain/hotstuff/finalizer.cpp +++ b/libraries/chain/hotstuff/finalizer.cpp @@ -5,7 +5,7 @@ namespace eosio::chain { // ---------------------------------------------------------------------------------------- -block_header_state_ptr get_block_by_num(const fork_database_if_t::full_branch_type& branch, std::optional block_num) { +block_header_state_ptr get_block_by_num(const fork_database_if_t::full_branch_t& branch, std::optional block_num) { if (!block_num || branch.empty()) return block_state_ptr{}; @@ -16,7 +16,7 @@ block_header_state_ptr get_block_by_num(const fork_database_if_t::full_branch_ty } // ---------------------------------------------------------------------------------------- -bool extends(const fork_database_if_t::full_branch_type& branch, const block_id_type& id) { +bool extends(const fork_database_if_t::full_branch_t& branch, const block_id_type& id) { return !branch.empty() && std::any_of(++branch.cbegin(), branch.cend(), [&](const auto& h) { return h->id() == id; }); } @@ -34,7 +34,7 @@ finalizer::vote_decision finalizer::decide_vote(const block_state_ptr& proposal, return vote_decision::no_vote; } - std::optional p_branch; // a branch that includes the root. + std::optional p_branch; // a branch that includes the root. if (!fsi.lock.empty()) { // Liveness check : check if the height of this proposal's justification is higher @@ -56,9 +56,6 @@ finalizer::vote_decision finalizer::decide_vote(const block_state_ptr& proposal, safety_check = false; } - dlog("liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote = {can_vote}", - ("l",liveness_check)("s",safety_check)("m",monotony_check)("can_vote",(liveness_check || safety_check))); - // Figure out if we can vote and wether our vote will be strong or weak // If we vote, update `fsi.last_vote` and also `fsi.lock` if we have a newer commit qc // ----------------------------------------------------------------------------------- @@ -90,8 +87,9 @@ finalizer::vote_decision finalizer::decide_vote(const block_state_ptr& proposal, ("lqc",!!proposal->last_qc_block_num())("f",fork_db.root()->block_num())); dlog("last_qc_block_num=${lqc}", ("lqc", proposal->last_qc_block_num())); } - if (decision != vote_decision::no_vote) - dlog("Voting ${s}", ("s", decision == vote_decision::strong_vote ? "strong" : "weak")); + + dlog("liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote=${can_vote}, voting=${v}", + ("l",liveness_check)("s",safety_check)("m",monotony_check)("can_vote",(liveness_check || safety_check))("v", decision)); return decision; } diff --git a/libraries/chain/hotstuff/hotstuff.cpp b/libraries/chain/hotstuff/hotstuff.cpp index 3a7830d501..7c9fe225cf 100644 --- a/libraries/chain/hotstuff/hotstuff.cpp +++ b/libraries/chain/hotstuff/hotstuff.cpp @@ -23,7 +23,6 @@ inline std::vector bitset_to_vector(const hs_bitset& bs) { vote_status pending_quorum_certificate::votes_t::add_vote(std::span proposal_digest, size_t index, const bls_public_key& pubkey, const bls_signature& new_sig) { if (_bitset[index]) { - dlog("duplicated vote"); return vote_status::duplicate; // shouldn't be already present } if (!fc::crypto::blslib::verify(pubkey, proposal_digest, new_sig)) { @@ -64,7 +63,6 @@ vote_status pending_quorum_certificate::add_strong_vote(std::span const bls_public_key& pubkey, const bls_signature& sig, uint64_t weight) { if (auto s = _strong_votes.add_vote(proposal_digest, index, pubkey, sig); s != vote_status::success) { - dlog("add_strong_vote returned failure"); return s; } _strong_sum += weight; @@ -127,15 +125,16 @@ vote_status pending_quorum_certificate::add_weak_vote(std::span p return vote_status::success; } -// thread safe, -std::pair pending_quorum_certificate::add_vote(bool strong, std::span proposal_digest, size_t index, - const bls_public_key& pubkey, const bls_signature& sig, - uint64_t weight) { +// thread safe, status, pre state , post state +vote_status pending_quorum_certificate::add_vote(block_num_type block_num, bool strong, std::span proposal_digest, size_t index, + const bls_public_key& pubkey, const bls_signature& sig, uint64_t weight) { std::lock_guard g(*_mtx); + auto pre_state = _state; vote_status s = strong ? add_strong_vote(proposal_digest, index, pubkey, sig, weight) : add_weak_vote(proposal_digest, index, pubkey, sig, weight); - dlog("status: ${s}, _state: ${state}, quorum_met: ${q}", ("s", s ==vote_status::success ? "success":"failure") ("state", _state==state_t::strong ? "strong":"weak")("q", is_quorum_met_no_lock()?"yes":"no")); - return {s, _state == state_t::strong}; + dlog("block_num: ${bn}, vote strong: ${sv}, status: ${s}, pre-state: ${pre}, post-state: ${state}, quorum_met: ${q}", + ("bn", block_num)("sv", strong)("s", s)("pre", pre_state)("state", _state)("q", is_quorum_met_no_lock())); + return s; } // thread safe @@ -158,7 +157,7 @@ valid_quorum_certificate pending_quorum_certificate::to_valid_quorum_certificate } bool pending_quorum_certificate::is_quorum_met_no_lock() const { - return _state == state_t::weak_achieved || _state == state_t::weak_final || _state == state_t::strong; + return is_quorum_met(_state); } valid_quorum_certificate::valid_quorum_certificate( diff --git a/libraries/chain/hotstuff/test/finality_misc_tests.cpp b/libraries/chain/hotstuff/test/finality_misc_tests.cpp index eb4644117c..ba58a06ed5 100644 --- a/libraries/chain/hotstuff/test/finality_misc_tests.cpp +++ b/libraries/chain/hotstuff/test/finality_misc_tests.cpp @@ -51,11 +51,11 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try { pubkey.push_back(k.get_public_key()); auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index, uint64_t weight) { - return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest), weight).first; + return qc.add_vote(0, false, digest, index, pubkey[index], sk[index].sign(digest), weight); }; auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index, uint64_t weight) { - return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest), weight).first; + return qc.add_vote(0, true, digest, index, pubkey[index], sk[index].sign(digest), weight); }; constexpr uint64_t weight = 1; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index f89257e147..bee4855c94 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -37,7 +37,7 @@ struct block_header_state { block_header header; protocol_feature_activation_set_ptr activated_protocol_features; - finality_core core; + finality_core core; // thread safe, not modified after creation incremental_merkle_tree proposal_mtree; incremental_merkle_tree finality_mtree; @@ -63,8 +63,10 @@ struct block_header_state { uint32_t block_num() const { return block_header::num_from_id(previous()) + 1; } block_timestamp_type last_qc_block_timestamp() const { auto last_qc_block_num = core.latest_qc_claim().block_num; - return core.get_block_reference(last_qc_block_num).timestamp; } + return core.get_block_reference(last_qc_block_num).timestamp; + } const producer_authority_schedule& active_schedule_auth() const { return active_proposer_policy->proposer_schedule; } + const protocol_feature_activation_set_ptr& get_activated_protocol_features() const { return activated_protocol_features; } block_header_state next(block_header_state_input& data) const; block_header_state next(const signed_block_header& h, validator_t& validator) const; @@ -74,7 +76,6 @@ struct block_header_state { return qc.block_num > core.latest_qc_claim().block_num; } - flat_set get_activated_protocol_features() const { return activated_protocol_features->protocol_features; } const vector& get_new_protocol_feature_activations() const; const producer_authority& get_scheduled_producer(block_timestamp_type t) const; }; diff --git a/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp b/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp index ee534a4954..160c4680f6 100644 --- a/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp @@ -170,6 +170,7 @@ struct block_header_state_legacy : public detail::block_header_state_legacy_comm uint32_t calc_dpos_last_irreversible( account_name producer_of_next_block )const; + const protocol_feature_activation_set_ptr& get_activated_protocol_features() const { return activated_protocol_features; } const producer_authority& get_scheduled_producer( block_timestamp_type t )const; const block_id_type& previous()const { return header.previous; } digest_type sig_digest()const; diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 1644c0e8a1..16f0150cb5 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -20,46 +20,59 @@ inline weak_digest_t create_weak_digest(const digest_type& digest) { } struct block_state_legacy; +struct block_state_accessor; struct block_state : public block_header_state { // block_header_state provides parent link // ------ data members ------------------------------------------------------------- signed_block_ptr block; - bool validated = false; // We have executed the block's trxs and verified that action merkle root (block id) matches. digest_type strong_digest; // finalizer_digest (strong, cached so we can quickly validate votes) weak_digest_t weak_digest; // finalizer_digest (weak, cached so we can quickly validate votes) pending_quorum_certificate pending_qc; // where we accumulate votes we receive std::optional valid_qc; // best qc received from the network inside block extension + // ------ updated for votes, used for fork_db ordering ------------------------------ +private: + bool validated = false; // We have executed the block's trxs and verified that action merkle root (block id) matches. + // ------ data members caching information available elsewhere ---------------------- bool pub_keys_recovered = false; deque cached_trxs; + // ------ private methods ----------------------------------------------------------- + bool is_valid() const { return validated; } + 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 fc::reflector; + friend struct controller_impl; + friend struct completed_block; +public: // ------ functions ----------------------------------------------------------------- const block_id_type& id() const { return block_header_state::id(); } const block_id_type& previous() const { return block_header_state::previous(); } uint32_t block_num() const { return block_header_state::block_num(); } block_timestamp_type timestamp() const { return block_header_state::timestamp(); } const extensions_type& header_extensions() const { return block_header_state::header.header_extensions; } - bool is_valid() const { return validated; } - void set_valid(bool b) { validated = b; } - uint32_t irreversible_blocknum() const { return core.last_final_block_num(); } + uint32_t irreversible_blocknum() const { return core.last_final_block_num(); } // backwards compatibility + uint32_t last_final_block_num() const { return core.last_final_block_num(); } std::optional get_best_qc() const; uint32_t last_qc_block_num() const { return core.latest_qc_claim().block_num; } uint32_t final_on_strong_qc_block_num() const { return core.final_on_strong_qc_block_num; } - protocol_feature_activation_set_ptr get_activated_protocol_features() const { return block_header_state::activated_protocol_features; } - 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; } - - std::pair> aggregate_vote(const vote_message& vote); // aggregate vote into pending_qc + // vote_status + vote_status aggregate_vote(const vote_message& vote); // aggregate vote into pending_qc void verify_qc(const valid_quorum_certificate& qc) const; // verify given qc is valid with respect block_state 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; + block_state(block_state&&) = default; block_state(const block_header_state& prev, signed_block_ptr b, const protocol_feature_set& pfs, const validator_t& validator, bool skip_validate_signee); @@ -79,4 +92,4 @@ using block_state_ptr = std::shared_ptr; } // namespace eosio::chain // not exporting pending_qc or valid_qc -FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(validated)(strong_digest)(weak_digest)(pending_qc)(valid_qc) ) +FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(strong_digest)(weak_digest)(pending_qc)(valid_qc)(validated) ) diff --git a/libraries/chain/include/eosio/chain/block_state_legacy.hpp b/libraries/chain/include/eosio/chain/block_state_legacy.hpp index be72ada54f..91c4b47085 100644 --- a/libraries/chain/include/eosio/chain/block_state_legacy.hpp +++ b/libraries/chain/include/eosio/chain/block_state_legacy.hpp @@ -7,6 +7,8 @@ namespace eosio::chain { + struct block_state_legacy_accessor; + struct block_state_legacy : public block_header_state_legacy { using bhs_t = block_header_state_legacy; using bhsp_t = block_header_state_legacy_ptr; @@ -39,21 +41,21 @@ namespace eosio::chain { block_timestamp_type timestamp() const { return header.timestamp; } account_name producer() const { return header.producer; } const extensions_type& header_extensions() const { return header.header_extensions; } - bool is_valid() const { return validated; } - void set_valid(bool b) { validated = b; } - - protocol_feature_activation_set_ptr get_activated_protocol_features() const { return activated_protocol_features; } + const producer_authority_schedule& active_schedule_auth() const { return block_header_state_legacy_common::active_schedule; } const producer_authority_schedule* pending_schedule_auth() const { return &block_header_state_legacy::pending_schedule.schedule; } const deque& trxs_metas() const { return _cached_trxs; } + using fork_db_block_state_accessor_t = block_state_legacy_accessor; private: // internal use only, not thread safe + friend struct block_state_legacy_accessor; friend struct fc::reflector; friend struct controller_impl; friend struct completed_block; friend struct block_state; + 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 9a5dd18634..937268a70d 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -189,6 +188,7 @@ namespace eosio::chain { void assemble_and_complete_block( block_report& br, const signer_callback_type& signer_callback ); void sign_block( const signer_callback_type& signer_callback ); void commit_block(); + void maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup); // thread-safe std::future create_block_handle_future( const block_id_type& id, const signed_block_ptr& b ); diff --git a/libraries/chain/include/eosio/chain/finality_core.hpp b/libraries/chain/include/eosio/chain/finality_core.hpp index 10d3268bdd..81e142c399 100644 --- a/libraries/chain/include/eosio/chain/finality_core.hpp +++ b/libraries/chain/include/eosio/chain/finality_core.hpp @@ -33,9 +33,9 @@ struct qc_claim_t struct core_metadata { - block_num_type last_final_block_num; - block_num_type final_on_strong_qc_block_num; - block_num_type latest_qc_claim_block_num; + block_num_type last_final_block_num {0}; + block_num_type final_on_strong_qc_block_num {0}; + block_num_type latest_qc_claim_block_num {0}; }; struct finality_core @@ -136,4 +136,5 @@ struct finality_core FC_REFLECT( eosio::chain::block_ref, (block_id)(timestamp) ) FC_REFLECT( eosio::chain::qc_link, (source_block_num)(target_block_num)(is_link_strong) ) FC_REFLECT( eosio::chain::qc_claim_t, (block_num)(is_strong_qc) ) +FC_REFLECT( eosio::chain::core_metadata, (last_final_block_num)(final_on_strong_qc_block_num)(latest_qc_claim_block_num)) FC_REFLECT( eosio::chain::finality_core, (links)(refs)(final_on_strong_qc_block_num)) diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index abbaa0f878..dfe75e3e33 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -1,15 +1,20 @@ #pragma once #include #include -#include namespace eosio::chain { - using boost::signals2::signal; - - template + template struct fork_database_impl; + using block_branch_t = std::vector; + enum class mark_valid_t { no, yes }; + enum class ignore_duplicate_t { no, yes }; + + // Used for logging of comparison values used for best fork determination + std::string log_fork_comparison(const block_state& bs); + std::string log_fork_comparison(const block_state_legacy& bs); + /** * @class fork_database_t * @brief manages light-weight state for all potential unconfirmed forks @@ -24,33 +29,34 @@ namespace eosio::chain { * fork_database should be used instead of fork_database_t directly as it manages * the different supported types. */ - template // either block_state_legacy_ptr or block_state_ptr + template // either block_state_legacy_ptr or block_state_ptr class fork_database_t { public: static constexpr uint32_t legacy_magic_number = 0x30510FDB; static constexpr uint32_t magic_number = 0x4242FDB; - using bs = bsp::element_type; - using bhsp = bs::bhsp_t; - using bhs = bhsp::element_type; - using bsp_t = bsp; - using branch_type = std::vector; - using full_branch_type = std::vector; - using branch_type_pair = pair; + using bsp_t = BSP; + using bs_t = bsp_t::element_type; + using bhsp_t = bs_t::bhsp_t; + using bhs_t = bhsp_t::element_type; + using branch_t = std::vector; + using full_branch_t = std::vector; + using branch_pair_t = pair; explicit fork_database_t(uint32_t magic_number = legacy_magic_number); + ~fork_database_t(); void open( const std::filesystem::path& fork_db_file, validator_t& validator ); void close( const std::filesystem::path& fork_db_file ); - bhsp get_block_header( const block_id_type& id ) const; - bsp get_block( const block_id_type& id ) const; + bhsp_t get_block_header( const block_id_type& id ) const; + bsp_t get_block( const block_id_type& id ) const; /** * Purges any existing blocks from the fork database and resets the root block_header_state to the provided value. * The head will also be reset to point to the root. */ - void reset( const bhs& root_bhs ); + void reset_root( const bhs_t& root_bhs ); /** * Removes validated flag from all blocks in fork database and resets head to point to the root. @@ -65,17 +71,19 @@ 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& next_block, bool ignore_duplicate = false ); + void add( const bsp_t& next_block, mark_valid_t mark_valid, ignore_duplicate_t ignore_duplicate ); void remove( const block_id_type& id ); - bsp root() const; - bsp head() const; - bsp pending_head() const; + bool has_root() const; + bsp_t root() const; // undefined if !has_root() + bsp_t head() const; + bsp_t pending_head() const; // only accessed by main thread, no mutex protection - bsp chain_head; + bsp_t chain_head; /** * Returns the sequence of block states resulting from trimming the branch from the @@ -85,31 +93,37 @@ namespace eosio::chain { * The order of the sequence is in descending block number order. * A block with an id of `h` must exist in the fork database otherwise this method will throw an exception. */ - branch_type fetch_branch( const block_id_type& h, uint32_t trim_after_block_num = std::numeric_limits::max() ) const; + branch_t fetch_branch( const block_id_type& h, uint32_t trim_after_block_num = std::numeric_limits::max() ) const; + block_branch_t fetch_block_branch( const block_id_type& h, uint32_t trim_after_block_num = std::numeric_limits::max() ) const; /** * eturns full branch of block_header_state pointers including the root. * The order of the sequence is in descending block number order. * A block with an id of `h` must exist in the fork database otherwise this method will throw an exception. */ - full_branch_type fetch_full_branch( const block_id_type& h ) const; + full_branch_t fetch_full_branch( const block_id_type& h ) const; /** * Returns the block state with a block number of `block_num` that is on the branch that * contains a block with an id of`h`, or the empty shared pointer if no such block can be found. */ - bsp search_on_branch( const block_id_type& h, uint32_t block_num ) const; + bsp_t search_on_branch( const block_id_type& h, uint32_t block_num ) const; + + /** + * search_on_branch( head()->id(), block_num) + */ + bsp_t search_on_head_branch( uint32_t block_num ) const; /** * Given two head blocks, return two branches of the fork graph that * end with a common ancestor (same prior block) */ - branch_type_pair fetch_branch_from(const block_id_type& first, const block_id_type& second) const; + branch_pair_t fetch_branch_from(const block_id_type& first, const block_id_type& second) const; - void mark_valid( const bsp& h ); + void mark_valid( const bsp_t& h ); private: - unique_ptr> my; + unique_ptr> my; }; using fork_database_legacy_t = fork_database_t; @@ -141,7 +155,7 @@ namespace eosio::chain { bool fork_db_legacy_present() const { return !!fork_db_legacy; } // see fork_database_t::fetch_branch(forkdb->head()->id()) - std::vector fetch_branch_from_head(); + block_branch_t fetch_branch_from_head() const; template R apply(const F& f) { diff --git a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp index 01fee6e411..d7750a99d9 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp @@ -85,8 +85,8 @@ namespace eosio::chain { safety_information fsi; private: - using branch_type = fork_database_if_t::branch_type; - using full_branch_type = fork_database_if_t::full_branch_type; + using branch_t = fork_database_if_t::branch_t; + using full_branch_t = fork_database_if_t::full_branch_t; vote_decision decide_vote(const block_state_ptr& proposal, const fork_database_if_t& fork_db); public: @@ -160,4 +160,5 @@ namespace std { } FC_REFLECT(eosio::chain::finalizer::proposal_ref, (id)(timestamp)) -FC_REFLECT(eosio::chain::finalizer::safety_information, (last_vote_range_start)(last_vote)(lock)) \ No newline at end of file +FC_REFLECT_ENUM(eosio::chain::finalizer::vote_decision, (strong_vote)(weak_vote)(no_vote)) +FC_REFLECT(eosio::chain::finalizer::safety_information, (last_vote_range_start)(last_vote)(lock)) diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp index 3b366a9692..0fa20f4bfa 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -93,14 +93,18 @@ namespace eosio::chain { // thread safe bool is_quorum_met() const; + static bool is_quorum_met(state_t s) { + return s == state_t::strong || s == state_t::weak_achieved || s == state_t::weak_final; + } // thread safe - std::pair add_vote(bool strong, - std::span proposal_digest, - size_t index, - const bls_public_key& pubkey, - const bls_signature& sig, - uint64_t weight); + vote_status add_vote(block_num_type block_num, + bool strong, + std::span proposal_digest, + size_t index, + const bls_public_key& pubkey, + const bls_signature& sig, + uint64_t weight); state_t state() const { std::lock_guard g(*_mtx); return _state; }; valid_quorum_certificate to_valid_quorum_certificate() const; @@ -137,7 +141,9 @@ namespace eosio::chain { FC_REFLECT(eosio::chain::vote_message, (proposal_id)(strong)(finalizer_key)(sig)); +FC_REFLECT_ENUM(eosio::chain::vote_status, (success)(duplicate)(unknown_public_key)(invalid_signature)(unknown_block)) FC_REFLECT(eosio::chain::valid_quorum_certificate, (_strong_votes)(_weak_votes)(_sig)); FC_REFLECT(eosio::chain::pending_quorum_certificate, (_quorum)(_max_weak_sum_before_weak_final)(_state)(_strong_sum)(_weak_sum)(_weak_votes)(_strong_votes)); +FC_REFLECT_ENUM(eosio::chain::pending_quorum_certificate::state_t, (unrestricted)(restricted)(weak_achieved)(weak_final)(strong)); FC_REFLECT(eosio::chain::pending_quorum_certificate::votes_t, (_bitset)(_sig)); FC_REFLECT(eosio::chain::quorum_certificate, (block_num)(qc)); diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 45f52a6bac..3387814c89 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 5e8f0681e0..500094f9a8 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4001,6 +4001,10 @@ namespace eosio { buffer_factory buff_factory; auto send_buffer = buff_factory.get_send_buffer( msg ); + fc_dlog(logger, "bcast vote: block #${bn}:${id}.., ${t}, key ${k}..", + ("bn", block_header::num_from_id(msg.proposal_id))("id", msg.proposal_id.str().substr(8,16)) + ("t", msg.strong ? "strong" : "weak")("k", msg.finalizer_key.to_string().substr(8,16))); + dispatcher->strand.post( [this, exclude_peer, msg{std::move(send_buffer)}]() mutable { dispatcher->bcast_vote_msg( exclude_peer, std::move(msg) ); }); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index ebd3a6d27f..f798c584ea 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1779,6 +1779,11 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { if (!chain_plug->accept_transactions()) return start_block_result::waiting_for_block; + abort_block(); + + chain.maybe_switch_forks([this](const transaction_metadata_ptr& trx) { _unapplied_transactions.add_forked(trx); }, + [this](const transaction_id_type& id) { return _unapplied_transactions.get_trx(id); }); + uint32_t head_block_num = chain.head_block_num(); if (chain.get_terminate_at_block() > 0 && chain.get_terminate_at_block() <= head_block_num) { @@ -1903,8 +1908,6 @@ producer_plugin_impl::start_block_result producer_plugin_impl::start_block() { blocks_to_confirm = (uint16_t)(std::min(blocks_to_confirm, (head_block_num - block_state->dpos_irreversible_blocknum))); } - abort_block(); - auto features_to_activate = chain.get_preactivated_protocol_features(); if (in_producing_mode() && _protocol_features_to_activate.size() > 0) { bool drop_features_to_activate = false; diff --git a/programs/leap-util/actions/blocklog.cpp b/programs/leap-util/actions/blocklog.cpp index 1807081b59..869d25732f 100644 --- a/programs/leap-util/actions/blocklog.cpp +++ b/programs/leap-util/actions/blocklog.cpp @@ -266,7 +266,7 @@ int blocklog_actions::read_log() { opt->first_block = block_logger.first_block_num(); } - std::vector fork_db_branch; + block_branch_t fork_db_branch; if(std::filesystem::exists(std::filesystem::path(opt->blocks_dir) / config::reversible_blocks_dir_name / config::forkdb_filename)) { ilog("opening fork_db"); diff --git a/programs/leap-util/actions/snapshot.cpp b/programs/leap-util/actions/snapshot.cpp index bc4b55f006..328fb1107a 100644 --- a/programs/leap-util/actions/snapshot.cpp +++ b/programs/leap-util/actions/snapshot.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include diff --git a/tests/trx_finality_status_forked_test.py b/tests/trx_finality_status_forked_test.py index d501d1dd01..820c65d38e 100755 --- a/tests/trx_finality_status_forked_test.py +++ b/tests/trx_finality_status_forked_test.py @@ -163,12 +163,11 @@ def getState(status): Print("Wait for LIB to move, which indicates prodD may have forked out the branch") assert prodD.waitForLibToAdvance(60), \ "ERROR: Network did not reach consensus after bridge node was restarted." - if prodD.getTransactionStatus(transId)['state'] == forkedOutState: + retStatus = prodD.getTransactionStatus(transId) + state = getState(retStatus) + if state == forkedOutState: break - retStatus = prodD.getTransactionStatus(transId) - state = getState(retStatus) - assert state == forkedOutState, \ f"ERROR: getTransactionStatus didn't return \"{forkedOutState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" @@ -184,8 +183,8 @@ def getState(status): retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) - assert state == inBlockState, \ - f"ERROR: getTransactionStatus didn't return \"{inBlockState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ + assert state == inBlockState or state == irreversibleState, \ + f"ERROR: getTransactionStatus didn't return \"{inBlockState}\" or \"{irreversibleState}\" state.\n\nstatus: {json.dumps(retStatus, indent=1)}" + \ f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" afterForkInBlockState = retStatus diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index 3dae182532..0fb1b3c789 100644 --- a/unittests/block_state_tests.cpp +++ b/unittests/block_state_tests.cpp @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bool strong = (i % 2 == 0); // alternate strong and weak auto sig = strong ? private_key[i].sign(strong_digest.to_uint8_span()) : private_key[i].sign(weak_digest); vote_message vote{ block_id, strong, public_key[i], sig }; - BOOST_REQUIRE(bsp->aggregate_vote(vote).first == vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) == vote_status::success); } } @@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; vote_message vote {block_id, true, public_key[0], private_key[1].sign(strong_digest.to_uint8_span()) }; - BOOST_REQUIRE(bsp->aggregate_vote(vote).first != vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) != vote_status::success); } { // duplicate votes @@ -68,8 +68,8 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1, bsp->active_finalizer_policy->max_weak_sum_before_weak_final() }; vote_message vote {block_id, true, public_key[0], private_key[0].sign(strong_digest.to_uint8_span()) }; - BOOST_REQUIRE(bsp->aggregate_vote(vote).first == vote_status::success); - BOOST_REQUIRE(bsp->aggregate_vote(vote).first != vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) == vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) != vote_status::success); } { // public key does not exit in finalizer set @@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { bls_public_key new_public_key{ new_private_key.get_public_key() }; vote_message vote {block_id, true, new_public_key, private_key[0].sign(strong_digest.to_uint8_span()) }; - BOOST_REQUIRE(bsp->aggregate_vote(vote).first != vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) != vote_status::success); } } FC_LOG_AND_RETHROW(); @@ -125,7 +125,7 @@ void do_quorum_test(const std::vector& weights, if( to_vote[i] ) { auto sig = strong ? private_key[i].sign(strong_digest.to_uint8_span()) : private_key[i].sign(weak_digest); vote_message vote{ block_id, strong, public_key[i], sig }; - BOOST_REQUIRE(bsp->aggregate_vote(vote).first == vote_status::success); + BOOST_REQUIRE(bsp->aggregate_vote(vote) == vote_status::success); } } diff --git a/unittests/finality_test_cluster.cpp b/unittests/finality_test_cluster.cpp index dff037028e..1af4cd4fb8 100644 --- a/unittests/finality_test_cluster.cpp +++ b/unittests/finality_test_cluster.cpp @@ -91,6 +91,7 @@ bool finality_test_cluster::produce_blocks_and_verify_lib_advancing() { for (auto i = 0; i < 3; ++i) { produce_and_push_block(); process_node1_vote(); + produce_and_push_block(); if (!node0_lib_advancing() || !node1_lib_advancing() || !node2_lib_advancing()) { return false; } diff --git a/unittests/finality_tests.cpp b/unittests/finality_tests.cpp index 132dc8c26a..6d68774fe5 100644 --- a/unittests/finality_tests.cpp +++ b/unittests/finality_tests.cpp @@ -14,6 +14,7 @@ BOOST_AUTO_TEST_CASE(two_votes) { try { cluster.produce_and_push_block(); // process node1's votes only cluster.process_node1_vote(); + cluster.produce_and_push_block(); // all nodes advance LIB BOOST_REQUIRE(cluster.node0_lib_advancing()); @@ -22,16 +23,38 @@ BOOST_AUTO_TEST_CASE(two_votes) { try { } } FC_LOG_AND_RETHROW() } -// verify LIB advances with all of the three finalizers voting -BOOST_AUTO_TEST_CASE(all_votes) { try { +// verify LIB does not advances with finalizers not voting. +BOOST_AUTO_TEST_CASE(no_votes) { try { finality_test_cluster cluster; + cluster.produce_and_push_block(); + cluster.node0_lib_advancing(); // reset + cluster.node1_lib_advancing(); // reset + cluster.node2_lib_advancing(); // reset for (auto i = 0; i < 3; ++i) { // node0 produces a block and pushes to node1 and node2 cluster.produce_and_push_block(); + // process no votes + cluster.produce_and_push_block(); + + // all nodes don't advance LIB + BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + } +} FC_LOG_AND_RETHROW() } + +// verify LIB advances with all of the three finalizers voting +BOOST_AUTO_TEST_CASE(all_votes) { try { + finality_test_cluster cluster; + + cluster.produce_and_push_block(); + for (auto i = 0; i < 3; ++i) { // process node1 and node2's votes cluster.process_node1_vote(); cluster.process_node2_vote(); + // node0 produces a block and pushes to node1 and node2 + cluster.produce_and_push_block(); // all nodes advance LIB BOOST_REQUIRE(cluster.node0_lib_advancing()); @@ -44,10 +67,11 @@ BOOST_AUTO_TEST_CASE(all_votes) { try { BOOST_AUTO_TEST_CASE(conflicting_votes_strong_first) { try { finality_test_cluster cluster; + cluster.produce_and_push_block(); for (auto i = 0; i < 3; ++i) { - cluster.produce_and_push_block(); cluster.process_node1_vote(); // strong cluster.process_node2_vote(finality_test_cluster::vote_mode::weak); // weak + cluster.produce_and_push_block(); BOOST_REQUIRE(cluster.node0_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); @@ -59,10 +83,11 @@ BOOST_AUTO_TEST_CASE(conflicting_votes_strong_first) { try { BOOST_AUTO_TEST_CASE(conflicting_votes_weak_first) { try { finality_test_cluster cluster; + cluster.produce_and_push_block(); for (auto i = 0; i < 3; ++i) { - cluster.produce_and_push_block(); cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); // weak cluster.process_node2_vote(); // strong + cluster.produce_and_push_block(); BOOST_REQUIRE(cluster.node0_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); @@ -76,26 +101,28 @@ BOOST_AUTO_TEST_CASE(one_delayed_votes) { try { // hold the vote for the first block to simulate delay cluster.produce_and_push_block(); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // LIB advanced on node1 because a new block was received + // LIB advanced on nodes because a new block was received + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); cluster.produce_and_push_block(); // vote block 0 (index 0) to make it have a strong QC, - // prompting LIB advacing on node0 + // prompting LIB advacing on node2 cluster.process_node1_vote(0); - BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // block 1 (index 1) has the same QC claim as block 0. It cannot move LIB cluster.process_node1_vote(1); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); // producing, pushing, and voting a new block makes LIB moving - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -110,27 +137,33 @@ BOOST_AUTO_TEST_CASE(three_delayed_votes) { try { for (auto i = 0; i < 4; ++i) { cluster.produce_and_push_block(); } - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // LIB advanced on node1 because a new block was received + // LIB advanced on nodes because a new block was received + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + // vote block 0 (index 0) to make it have a strong QC, - // prompting LIB advacing on node0 + // prompting LIB advacing on nodes cluster.process_node1_vote(0); - BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // blocks 1 to 3 have the same QC claim as block 0. It cannot move LIB for (auto i=1; i < 4; ++i) { cluster.process_node1_vote(i); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); } // producing, pushing, and voting a new block makes LIB moving - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -150,22 +183,25 @@ BOOST_AUTO_TEST_CASE(out_of_order_votes) { try { // vote block 2 (index 2) to make it have a strong QC, // prompting LIB advacing cluster.process_node1_vote(2); + cluster.produce_and_push_block(); BOOST_REQUIRE(cluster.node0_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); // block 1 (index 1) has the same QC claim as block 2. It will not move LIB cluster.process_node1_vote(1); + cluster.produce_and_push_block(); BOOST_REQUIRE(!cluster.node0_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); // block 0 (index 0) has the same QC claim as block 2. It will not move LIB cluster.process_node1_vote(0); + cluster.produce_and_push_block(); BOOST_REQUIRE(!cluster.node0_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); // producing, pushing, and voting a new block makes LIB moving - cluster.produce_and_push_block(); cluster.process_node1_vote(); + cluster.produce_and_push_block(); BOOST_REQUIRE(cluster.node0_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); @@ -179,20 +215,23 @@ BOOST_AUTO_TEST_CASE(long_delayed_votes) { try { // Produce and push a block, vote on it after a long delay. constexpr uint32_t delayed_vote_index = 0; cluster.produce_and_push_block(); - // The block is not voted, so no strong QC is created and LIB does not advance on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // The strong QC extension for prior block makes LIB advance on node1 + // The strong QC extension for prior block makes LIB advance on nodes + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); cluster.produce_and_push_block(); - cluster.process_node1_vote(); - // the vote makes a strong QC for the current block, prompting LIB advance on node0 - BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.process_node1_vote(); + cluster.produce_and_push_block(); + // the vote makes a strong QC for the current block, prompting LIB advance on nodes + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + for (auto i = 2; i < 100; ++i) { - cluster.produce_and_push_block(); cluster.process_node1_vote(); + cluster.produce_and_push_block(); BOOST_REQUIRE(cluster.node0_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); } @@ -210,17 +249,21 @@ BOOST_AUTO_TEST_CASE(lost_votes) { try { // The block contains a strong QC extension for prior block cluster.produce_and_push_block(); - // The block is not voted, so no strong QC is created and LIB does not advance on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // The strong QC extension for prior block makes LIB advance on node1 + // The strong QC extension for prior block makes LIB advance on nodes BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); cluster.produce_and_push_block(); + // The block is not voted, so no strong QC is created and LIB does not advance on nodes + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + cluster.process_node1_vote(); + cluster.produce_and_push_block(); - // the vote makes a strong QC for the current block, prompting LIB advance on node0 - BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + // vote causes lib to advance + BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); } FC_LOG_AND_RETHROW() } @@ -232,28 +275,27 @@ BOOST_AUTO_TEST_CASE(one_weak_vote) { try { cluster.produce_and_push_block(); // Change the vote to a weak vote and process it cluster.process_node1_vote(0, finality_test_cluster::vote_mode::weak); - // A weak QC is created and LIB does not advance on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); // The strong QC extension for prior block makes LIB advance on node1 BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); cluster.produce_and_push_block(); - cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + // A weak QC is created and LIB does not advance on node2 + BOOST_REQUIRE(!cluster.node2_lib_advancing()); // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); - cluster.produce_and_push_block(); cluster.process_node1_vote(); + cluster.produce_and_push_block(); // the vote makes a strong QC and a higher final_on_strong_qc, - // prompting LIB advance on node0 - BOOST_REQUIRE(cluster.node0_lib_advancing()); + // prompting LIB advance on nodes BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); // now a 3 chain has formed. BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -264,29 +306,32 @@ BOOST_AUTO_TEST_CASE(two_weak_votes) { try { // Produce and push a block cluster.produce_and_push_block(); - // Change the vote to a weak vote and process it - cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); - // A weak QC cannot advance LIB on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // The strong QC extension for prior block makes LIB advance on node1 + // The strong QC extension for prior block makes LIB advance on nodes BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); - cluster.produce_and_push_block(); + // Change the vote to a weak vote and process it cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); - // A weak QC cannot advance LIB on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // no 2-chain was formed as prior block was not a strong block + cluster.produce_and_push_block(); + // A weak QC cannot advance LIB on nodes + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); cluster.produce_and_push_block(); - cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + // A weak QC cannot advance LIB on node2 + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.process_node1_vote(); cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + cluster.process_node1_vote(); - // the vote makes a strong QC for the current block, prompting LIB advance on node0 - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); // now a 3 chain has formed. @@ -296,40 +341,42 @@ BOOST_AUTO_TEST_CASE(two_weak_votes) { try { BOOST_AUTO_TEST_CASE(intertwined_weak_votes) { try { finality_test_cluster cluster; - // Weak vote cluster.produce_and_push_block(); - cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); - // A weak QC cannot advance LIB on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - // The strong QC extension for prior block makes LIB advance on node1 + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); - // Strong vote + // Weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); cluster.produce_and_push_block(); - cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); - // no 2-chain was formed as prior block was not a strong block + + // The strong QC extension for prior block makes LIB advance on nodes + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); - // Weak vote + // Strong vote + cluster.process_node1_vote(); cluster.produce_and_push_block(); - cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); - // A weak QC cannot advance LIB on node0 - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); - // Strong vote + // Weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); cluster.produce_and_push_block(); - cluster.process_node1_vote(); - // the vote makes a strong QC for the current block, prompting LIB advance on node0 - BOOST_REQUIRE(cluster.node0_lib_advancing()); - // no 2-chain was formed as prior block was not a strong block + // A weak QC cannot advance LIB on nodes + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); // Strong vote + cluster.process_node1_vote(); cluster.produce_and_push_block(); + // the vote makes a strong QC for the current block, prompting LIB advance on node0 + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); + + // Strong vote cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -339,42 +386,43 @@ BOOST_AUTO_TEST_CASE(intertwined_weak_votes) { try { BOOST_AUTO_TEST_CASE(weak_delayed_lost_vote) { try { finality_test_cluster cluster; - // A weak vote cluster.produce_and_push_block(); - cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); + // A weak vote + cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); + // A delayed vote (index 1) constexpr uint32_t delayed_index = 1; cluster.produce_and_push_block(); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); // A strong vote - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); - - // A lost vote cluster.produce_and_push_block(); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); - // The delayed vote arrives - cluster.process_node1_vote(delayed_index); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + // A lost vote + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); + // The delayed vote arrives, does not advance lib because it is weak + cluster.process_node1_vote(delayed_index); cluster.produce_and_push_block(); - cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); - cluster.produce_and_push_block(); + // strong vote advances lib cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -387,45 +435,41 @@ BOOST_AUTO_TEST_CASE(delayed_strong_weak_lost_vote) { try { // A delayed vote (index 0) constexpr uint32_t delayed_index = 0; cluster.produce_and_push_block(); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); // A strong vote - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // A weak vote - cluster.produce_and_push_block(); cluster.process_node1_vote(finality_test_cluster::vote_mode::weak); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - BOOST_REQUIRE(cluster.node1_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); // A strong vote - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // A lost vote cluster.produce_and_push_block(); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - BOOST_REQUIRE(cluster.node1_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); + BOOST_REQUIRE(!cluster.node1_lib_advancing()); // The delayed vote arrives cluster.process_node1_vote(delayed_index); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); - cluster.produce_and_push_block(); - cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(!cluster.node2_lib_advancing()); BOOST_REQUIRE(!cluster.node1_lib_advancing()); - cluster.produce_and_push_block(); cluster.process_node1_vote(); - BOOST_REQUIRE(cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -435,15 +479,15 @@ BOOST_AUTO_TEST_CASE(delayed_strong_weak_lost_vote) { try { BOOST_AUTO_TEST_CASE(duplicate_votes) { try { finality_test_cluster cluster; + cluster.produce_and_push_block(); for (auto i = 0; i < 5; ++i) { - cluster.produce_and_push_block(); cluster.process_node1_vote(i); - // vote again to make it duplicate BOOST_REQUIRE(cluster.process_node1_vote(i) == eosio::chain::vote_status::duplicate); + cluster.produce_and_push_block(); // verify duplicate votes do not affect LIB advancing - BOOST_REQUIRE(cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node2_lib_advancing()); BOOST_REQUIRE(cluster.node1_lib_advancing()); } } FC_LOG_AND_RETHROW() } @@ -454,19 +498,20 @@ BOOST_AUTO_TEST_CASE(unknown_proposal_votes) { try { // node0 produces a block and pushes to node1 cluster.produce_and_push_block(); - // intentionally corrupt proposal_id in node1's vote cluster.node1_corrupt_vote_proposal_id(); - // process the corrupted vote. LIB should not advance + // process the corrupted vote cluster.process_node1_vote(0); BOOST_REQUIRE(cluster.process_node1_vote(0) == eosio::chain::vote_status::unknown_block); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + cluster.produce_and_push_block(); + BOOST_REQUIRE(cluster.node2_lib_advancing()); // restore to original vote cluster.node1_restore_to_original_vote(); // process the original vote. LIB should advance + cluster.produce_and_push_block(); cluster.process_node1_vote(0); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -485,7 +530,6 @@ BOOST_AUTO_TEST_CASE(unknown_finalizer_key_votes) { try { // process the corrupted vote. LIB should not advance cluster.process_node1_vote(0); BOOST_REQUIRE(cluster.process_node1_vote(0) == eosio::chain::vote_status::unknown_public_key); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); // restore to original vote cluster.node1_restore_to_original_vote(); @@ -508,7 +552,6 @@ BOOST_AUTO_TEST_CASE(corrupted_signature_votes) { try { // process the corrupted vote. LIB should not advance BOOST_REQUIRE(cluster.process_node1_vote(0) == eosio::chain::vote_status::invalid_signature); - BOOST_REQUIRE(!cluster.node0_lib_advancing()); // restore to original vote cluster.node1_restore_to_original_vote(); diff --git a/unittests/fork_db_tests.cpp b/unittests/fork_db_tests.cpp new file mode 100644 index 0000000000..655b3e9544 --- /dev/null +++ b/unittests/fork_db_tests.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + + +namespace eosio::chain { + +inline block_id_type make_block_id(block_num_type block_num) { + static uint32_t nonce = 0; + ++nonce; + block_id_type id = fc::sha256::hash(std::to_string(block_num) + "-" + std::to_string(nonce)); + id._hash[0] &= 0xffffffff00000000; + id._hash[0] += fc::endian_reverse_u32(block_num); // store the block num in the ID, 160 bits is plenty for the hash + return id; +} + +// Used to access privates of block_state +struct 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); + root->block_id = genesis_id; + root->header.timestamp = block_timestamp_type{10}; + root->core = finality_core::create_core_for_genesis_block(10); + return root; + } + + // use block_num > 10 + static auto make_unique_block_state(block_num_type block_num, const block_state_ptr& prev) { + block_state_ptr bsp = std::make_shared(); + bsp->block_id = make_block_id(block_num); + bsp->header.timestamp.slot = prev->header.timestamp.slot + 1; + bsp->header.previous = prev->id(); + block_ref parent_block { + .block_id = prev->id(), + .timestamp = prev->timestamp() + }; + bsp->core = prev->core.next(parent_block, prev->core.latest_qc_claim()); + return bsp; + } +}; + +} // namespace eosio::chain + +using namespace eosio::chain; + +BOOST_AUTO_TEST_SUITE(fork_database_tests) + +BOOST_AUTO_TEST_CASE(add_remove_test) try { + fork_database_if_t forkdb; + + // 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); + + // 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 }; + + 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); + + // test get_block + for (auto& i : all) { + BOOST_TEST(forkdb.get_block(i->id()) == i); + } + + // test remove, should remove descendants + forkdb.remove(bsp12b->id()); + 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 + + // test search + BOOST_TEST(forkdb.search_on_branch( bsp13bb->id(), 11) == bsp11b); + BOOST_TEST(forkdb.search_on_branch( bsp13bb->id(), 9) == block_state_ptr{}); + + // test fetch branch + auto branch = forkdb.fetch_branch( bsp13b->id(), 12); + BOOST_REQUIRE(branch.size() == 2); + BOOST_TEST(branch[0] == bsp12b); + BOOST_TEST(branch[1] == bsp11b); + branch = forkdb.fetch_branch( bsp13bbb->id(), 13); + BOOST_REQUIRE(branch.size() == 3); + BOOST_TEST(branch[0] == bsp13bbb); + BOOST_TEST(branch[1] == bsp12bb); + BOOST_TEST(branch[2] == bsp11b); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 8ac11285e0..9041a74baf 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -1,5 +1,4 @@ #include -#include #include #include diff --git a/unittests/unapplied_transaction_queue_tests.cpp b/unittests/unapplied_transaction_queue_tests.cpp index f8c76fe63c..bb4bbeb838 100644 --- a/unittests/unapplied_transaction_queue_tests.cpp +++ b/unittests/unapplied_transaction_queue_tests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -78,7 +79,7 @@ auto create_test_block_state( deque trx_metas ) { return bsp; } -using branch_type_legacy = fork_database_t::branch_type; +using branch_legacy_t = fork_database_legacy_t::branch_t; template void add_forked( unapplied_transaction_queue& queue, const BRANCH_TYPE& forked_branch ) { @@ -150,7 +151,7 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { auto bs1 = create_test_block_state( { trx1, trx2 } ); auto bs2 = create_test_block_state( { trx3, trx4, trx5 } ); auto bs3 = create_test_block_state( { trx6 } ); - add_forked( q, branch_type_legacy{ bs3, bs2, bs1, bs1 } ); // bs1 duplicate ignored + add_forked( q, branch_legacy_t{ bs3, bs2, bs1, bs1 } ); // bs1 duplicate ignored BOOST_CHECK( q.size() == 6u ); BOOST_REQUIRE( next( q ) == trx1 ); BOOST_CHECK( q.size() == 5u ); @@ -169,9 +170,9 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { // fifo forked auto bs4 = create_test_block_state( { trx7 } ); - add_forked( q, branch_type_legacy{ bs1 } ); - add_forked( q, branch_type_legacy{ bs3, bs2 } ); - add_forked( q, branch_type_legacy{ bs4 } ); + add_forked( q, branch_legacy_t{ bs1 } ); + add_forked( q, branch_legacy_t{ bs3, bs2 } ); + add_forked( q, branch_legacy_t{ bs4 } ); BOOST_CHECK( q.size() == 7u ); BOOST_REQUIRE( next( q ) == trx1 ); BOOST_CHECK( q.size() == 6u ); @@ -203,10 +204,10 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { // fifo forked, multi forks auto bs5 = create_test_block_state( { trx11, trx12, trx13 } ); auto bs6 = create_test_block_state( { trx11, trx15 } ); - add_forked( q, branch_type_legacy{ bs3, bs2, bs1 } ); - add_forked( q, branch_type_legacy{ bs4 } ); - add_forked( q, branch_type_legacy{ bs3, bs2 } ); // dups ignored - add_forked( q, branch_type_legacy{ bs6, bs5 } ); + add_forked( q, branch_legacy_t{ bs3, bs2, bs1 } ); + add_forked( q, branch_legacy_t{ bs4 } ); + add_forked( q, branch_legacy_t{ bs3, bs2 } ); // dups ignored + add_forked( q, branch_legacy_t{ bs6, bs5 } ); BOOST_CHECK_EQUAL( q.size(), 11u ); BOOST_REQUIRE( next( q ) == trx1 ); BOOST_CHECK( q.size() == 10u ); @@ -234,10 +235,10 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { BOOST_CHECK( q.empty() ); // altogether, order fifo: forked, aborted - add_forked( q, branch_type_legacy{ bs3, bs2, bs1 } ); + add_forked( q, branch_legacy_t{ bs3, bs2, bs1 } ); q.add_aborted( { trx9, trx14 } ); q.add_aborted( { trx18, trx19 } ); - add_forked( q, branch_type_legacy{ bs6, bs5, bs4 } ); + add_forked( q, branch_legacy_t{ bs6, bs5, bs4 } ); // verify order verify_order( q, q.begin(), 15 ); // verify type order @@ -303,7 +304,7 @@ BOOST_AUTO_TEST_CASE( unapplied_transaction_queue_test ) try { BOOST_REQUIRE( next( q ) == trx22 ); BOOST_CHECK( q.empty() ); - add_forked( q, branch_type_legacy{ bs3, bs2, bs1 } ); + add_forked( q, branch_legacy_t{ bs3, bs2, bs1 } ); q.add_aborted( { trx9, trx11 } ); q.clear(); BOOST_CHECK( q.empty() );