diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index dea432c0ac..d3904e2362 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -39,6 +39,7 @@ block_state::block_state(const block_header_state& bhs, dequetransactions = std::move(trx_receipts); if( qc ) { + dlog("integrate qc ${qc} into block ${bn} ${id}", ("qc", qc->to_qc_claim())("bn", block_num())("id", id())); emplace_extension(block->block_extensions, quorum_certificate_extension::extension_id(), fc::raw::pack( *qc )); } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index de00676a29..140534ec72 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -690,27 +690,25 @@ struct building_block { } else { fork_db.apply_s([&](const auto& forkdb) { auto branch = forkdb.fetch_branch(parent_id()); - std::optional qc; for( auto it = branch.begin(); it != branch.end(); ++it ) { - qc = (*it)->get_best_qc(); - if( qc ) { + if( auto qc = (*it)->get_best_qc(); qc ) { EOS_ASSERT( qc->block_num <= block_header::num_from_id(parent_id()), block_validate_exception, "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", ("a", qc->block_num)("p", block_header::num_from_id(parent_id())) ); - auto qc_claim = qc_claim_t { qc->block_num, qc->qc.is_strong() }; - if( bb.parent.is_needed(*qc) ) { + auto qc_claim = qc->to_qc_claim(); + if( bb.parent.is_needed(qc_claim) ) { qc_data = qc_data_t{ *qc, qc_claim }; } else { - qc_data = qc_data_t{ {}, qc_claim }; + // no new qc info, repeat existing + qc_data = qc_data_t{ {}, bb.parent.core.latest_qc_claim() }; } break; } } - if (!qc) { - // This only happens when parent block is the IF genesis block. - // There is no ancestor block which has a QC. - // Construct a default QC claim. + if (!qc_data) { + // This only happens when parent block is the IF genesis block or starting from snapshot. + // There is no ancestor block which has a QC. Construct a default QC claim. qc_data = qc_data_t{ {}, bb.parent.core.latest_qc_claim() }; } }); @@ -1062,9 +1060,8 @@ struct controller_impl { 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_head_branch(block_num, include_root_t::yes); - return bsp; + [&](const fork_database_if_t& forkdb) -> block_state_ptr { + return forkdb.search_on_head_branch(block_num, include_root_t::yes); } } ); @@ -1075,9 +1072,19 @@ struct controller_impl { 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(id, block_num, include_root_t::yes); - return bsp; + [&](const fork_database_if_t& forkdb) -> block_state_ptr { + return forkdb.search_on_branch(id, block_num, include_root_t::yes); + } + } + ); + } + + block_state_ptr fetch_bsp(const block_id_type& id) 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 { + return forkdb.get_block(id, include_root_t::yes); } } ); @@ -1664,7 +1671,7 @@ struct controller_impl { my_finalizers.set_default_safety_information( finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), .last_vote = {}, - .lock = proposal_ref(lib->id(), lib->timestamp()) }); + .lock = {lib->id(), lib->timestamp()} }); }; fork_db.apply_s(set_finalizer_defaults); } else { @@ -1674,7 +1681,7 @@ struct controller_impl { my_finalizers.set_default_safety_information( finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), .last_vote = {}, - .lock = proposal_ref(lib->id(), lib->timestamp()) }); + .lock = {lib->id(), lib->timestamp()} }); }; fork_db.apply_s(set_finalizer_defaults); } @@ -2949,8 +2956,8 @@ struct controller_impl { auto lib_block = head; my_finalizers.set_default_safety_information( finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), - .last_vote = proposal_ref(start_block->id(), start_block->timestamp()), - .lock = proposal_ref(lib_block->id(), lib_block->timestamp()) }); + .last_vote = {start_block->id(), start_block->timestamp()}, + .lock = {lib_block->id(), lib_block->timestamp()} }); } if ( (s != controller::block_status::irreversible && read_mode != db_read_mode::IRREVERSIBLE) && s != controller::block_status::ephemeral) @@ -3232,7 +3239,7 @@ 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) -> vote_status { - auto bsp = forkdb.get_block(vote.proposal_id); + auto bsp = forkdb.get_block(vote.block_id); if (bsp) { return bsp->aggregate_vote(vote); } @@ -3469,15 +3476,21 @@ struct controller_impl { const auto claimed = fetch_bsp_on_branch_by_num( bsp_in->previous(), qc_ext.qc.block_num ); if( !claimed ) { + dlog("qc not found in forkdb, qc: ${qc} for block ${bn} ${id}, previous ${p}", + ("qc", qc_ext.qc.to_qc_claim())("bn", bsp_in->block_num())("id", bsp_in->id())("p", bsp_in->previous())); return; } // Don't save the QC from block extension if the claimed block has a better valid_qc. if (claimed->valid_qc && (claimed->valid_qc->is_strong() || received_qc.is_weak())) { + dlog("qc not better, claimed->valid: ${qbn} ${qid}, strong=${s}, received: ${rqc}, for block ${bn} ${id}", + ("qbn", claimed->block_num())("qid", claimed->id())("s", claimed->valid_qc->is_strong()) + ("rqc", qc_ext.qc.to_qc_claim())("bn", bsp_in->block_num())("id", bsp_in->id())); return; } // Save the QC. This is safe as the function is called by push_block & accept_block from application thread. + dlog("setting valid qc: ${rqc} into claimed block ${bn} ${id}", ("rqc", qc_ext.qc.to_qc_claim())("bn", claimed->block_num())("id", claimed->id())); claimed->valid_qc = received_qc; // advance LIB if QC is strong @@ -3495,14 +3508,13 @@ struct controller_impl { void consider_voting(const block_state_ptr& bsp) { // 1. Get the `core.final_on_strong_qc_block_num` for the block you are considering to vote on and use that to find the actual block ID // of the ancestor block that has that block number. - // 2. If that block ID is not an ancestor of the current head block, then do not vote for that block. + // 2. If that block ID is for a non validated block, then do not vote for that block. // 3. Otherwise, consider voting for that block according to the decide_vote rules. if (bsp->core.final_on_strong_qc_block_num > 0) { const auto& final_on_strong_qc_block_ref = bsp->core.get_block_reference(bsp->core.final_on_strong_qc_block_num); - auto final = fetch_bsp_on_head_branch_by_num(final_on_strong_qc_block_ref.block_num()); - if (final) { - assert(final->is_valid()); // if found on head branch then it must be validated + auto final = fetch_bsp(final_on_strong_qc_block_ref.block_id); + if (final && final->is_valid()) { create_and_send_vote_msg(bsp); } } @@ -3643,10 +3655,11 @@ struct controller_impl { void maybe_switch_forks(const forked_callback_t& cb, const trx_meta_cache_lookup& trx_lookup) { auto maybe_switch = [&](auto& forkdb) { if (read_mode != db_read_mode::IRREVERSIBLE) { - auto fork_head = forkdb.head(); - if (chain_head.id() != fork_head->id()) { + auto pending_head = forkdb.pending_head(); + if (chain_head.id() != pending_head->id() && pending_head->id() != forkdb.head()->id()) { + dlog("switching forks on controller->maybe_switch_forks call"); controller::block_report br; - maybe_switch_forks(br, fork_head, fork_head->is_valid() ? controller::block_status::validated : controller::block_status::complete, + maybe_switch_forks(br, pending_head, pending_head->is_valid() ? controller::block_status::validated : controller::block_status::complete, cb, trx_lookup); } } diff --git a/libraries/chain/hotstuff/finalizer.cpp b/libraries/chain/hotstuff/finalizer.cpp index e454cf2cb7..8fd52936ff 100644 --- a/libraries/chain/hotstuff/finalizer.cpp +++ b/libraries/chain/hotstuff/finalizer.cpp @@ -5,16 +5,24 @@ namespace eosio::chain { // ---------------------------------------------------------------------------------------- -finalizer::vote_result finalizer::decide_vote(const finality_core& core, const block_id_type& proposal_id, - const block_timestamp_type proposal_timestamp) { +finalizer::vote_result finalizer::decide_vote(const block_state_ptr& bsp) { vote_result res; - res.monotony_check = fsi.last_vote.empty() || proposal_timestamp > fsi.last_vote.timestamp; + res.monotony_check = fsi.last_vote.empty() || bsp->timestamp() > fsi.last_vote.timestamp; // fsi.last_vote.empty() means we have never voted on a proposal, so the protocol feature // just activated and we can proceed if (!res.monotony_check) { - dlog("monotony check failed for proposal ${bn} ${p}, cannot vote", ("bn", block_header::num_from_id(proposal_id))("p", proposal_id)); + if (fsi.last_vote.empty()) { + dlog("monotony check failed, block ${bn} ${p}, cannot vote, fsi.last_vote empty", ("bn", bsp->block_num())("p", bsp->id())); + } else { + if (fc::logger::get(DEFAULT_LOGGER).is_enabled(fc::log_level::debug)) { + if (bsp->id() != fsi.last_vote.block_id) { // we may have already voted when we received the block + dlog("monotony check failed, block ${bn} ${p}, cannot vote, ${t} <= ${lt}, fsi.last_vote ${lbn} ${lid}", + ("bn", bsp->block_num())("p", bsp->id())("t", bsp->timestamp())("lt", fsi.last_vote.timestamp)("lbn", fsi.last_vote.block_num())("lid", fsi.last_vote.block_id)); + } + } + } return res; } @@ -23,16 +31,24 @@ finalizer::vote_result finalizer::decide_vote(const finality_core& core, const b // than the height of the proposal I'm locked on. // This allows restoration of liveness if a replica is locked on a stale proposal // ------------------------------------------------------------------------------- - res.liveness_check = core.latest_qc_block_timestamp() > fsi.lock.timestamp; + res.liveness_check = bsp->core.latest_qc_block_timestamp() > fsi.lock.timestamp; if (!res.liveness_check) { + dlog("liveness check failed, block ${bn} ${id}: ${c} <= ${l}, fsi.lock ${lbn} ${lid}, latest_qc_claim: ${qc}", + ("bn", bsp->block_num())("id", bsp->id())("c", bsp->core.latest_qc_block_timestamp())("l", fsi.lock.timestamp) + ("lbn", fsi.lock.block_num())("lid", fsi.lock.block_id)("qc", bsp->core.latest_qc_claim())); // Safety check : check if this proposal extends the proposal we're locked on - res.safety_check = core.extends(fsi.lock.block_id); + res.safety_check = bsp->core.extends(fsi.lock.block_id); + if (!res.safety_check) { + dlog("safety check failed, block ${bn} ${id} did not extend fsi.lock ${lbn} ${lid}", + ("bn", bsp->block_num())("id", bsp->id())("lbn", fsi.lock.block_num())("lid", fsi.lock.block_id)); + } } } else { // Safety and Liveness both fail if `fsi.lock` is empty. It should not happen. // `fsi.lock` is initially set to `lib` when switching to IF or starting from a snapshot. // ------------------------------------------------------------------------------------- + wlog("liveness check & safety check failed, block ${bn} ${id}, fsi.lock is empty", ("bn", bsp->block_num())("id", bsp->id())); res.liveness_check = false; res.safety_check = false; } @@ -43,36 +59,37 @@ finalizer::vote_result finalizer::decide_vote(const finality_core& core, const b // If we vote, update `fsi.last_vote` and also `fsi.lock` if we have a newer commit qc // ----------------------------------------------------------------------------------- if (can_vote) { - auto [p_start, p_end] = std::make_pair(core.latest_qc_block_timestamp(), proposal_timestamp); + auto [p_start, p_end] = std::make_pair(bsp->core.latest_qc_block_timestamp(), bsp->timestamp()); bool time_range_disjoint = fsi.last_vote_range_start >= p_end || fsi.last_vote.timestamp <= p_start; bool voting_strong = time_range_disjoint; if (!voting_strong && !fsi.last_vote.empty()) { // we can vote strong if the proposal is a descendant of (i.e. extends) our last vote id - voting_strong = core.extends(fsi.last_vote.block_id); + voting_strong = bsp->core.extends(fsi.last_vote.block_id); } - fsi.last_vote = proposal_ref(proposal_id, proposal_timestamp); + fsi.last_vote = { bsp->id(), bsp->timestamp() }; fsi.last_vote_range_start = p_start; - auto& final_on_strong_qc_block_ref = core.get_block_reference(core.final_on_strong_qc_block_num); - if (voting_strong && final_on_strong_qc_block_ref.timestamp > fsi.lock.timestamp) - fsi.lock = proposal_ref(final_on_strong_qc_block_ref.block_id, final_on_strong_qc_block_ref.timestamp); + auto& final_on_strong_qc_block_ref = bsp->core.get_block_reference(bsp->core.final_on_strong_qc_block_num); + if (voting_strong && final_on_strong_qc_block_ref.timestamp > fsi.lock.timestamp) { + fsi.lock = { final_on_strong_qc_block_ref.block_id, final_on_strong_qc_block_ref.timestamp }; + } res.decision = voting_strong ? vote_decision::strong_vote : vote_decision::weak_vote; } - dlog("block=${bn}, liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote=${can_vote}, voting=${v}", - ("bn", block_header::num_from_id(proposal_id))("l",res.liveness_check)("s",res.safety_check)("m",res.monotony_check) - ("can_vote",can_vote)("v", res.decision)); + dlog("block=${bn} ${id}, liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote=${can_vote}, voting=${v}, locked=${lbn} ${lid}", + ("bn", bsp->block_num())("id", bsp->id())("l",res.liveness_check)("s",res.safety_check)("m",res.monotony_check) + ("can_vote",can_vote)("v", res.decision)("lbn", fsi.lock.block_num())("lid", fsi.lock.block_id)); return res; } // ---------------------------------------------------------------------------------------- std::optional finalizer::maybe_vote(const bls_public_key& pub_key, - const block_header_state_ptr& p, + const block_state_ptr& bsp, const digest_type& digest) { - finalizer::vote_decision decision = decide_vote(p->core, p->id(), p->timestamp()).decision; + finalizer::vote_decision decision = decide_vote(bsp).decision; if (decision == vote_decision::strong_vote || decision == vote_decision::weak_vote) { bls_signature sig; if (decision == vote_decision::weak_vote) { @@ -82,7 +99,7 @@ std::optional finalizer::maybe_vote(const bls_public_key& pub_key, } else { sig = priv_key.sign({(uint8_t*)digest.data(), (uint8_t*)digest.data() + digest.data_size()}); } - return vote_message{ p->id(), decision == vote_decision::strong_vote, pub_key, sig }; + return vote_message{ bsp->id(), decision == vote_decision::strong_vote, pub_key, sig }; } return {}; } diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 995887a4e1..858d71c3c5 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -76,8 +76,8 @@ struct block_header_state { block_header_state next(const signed_block_header& h, validator_t& validator) const; // block descending from this need the provided qc in the block extension - bool is_needed(const quorum_certificate& qc) const { - return qc.block_num > core.latest_qc_claim().block_num; + bool is_needed(const qc_claim_t& qc_claim) const { + return qc_claim > core.latest_qc_claim(); } const vector& get_new_protocol_feature_activations() const; diff --git a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp index 4aec61c1f0..e44ca30fb2 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp @@ -26,14 +26,12 @@ // ------------------------------------------------------------------------------------------- namespace eosio::chain { - // ---------------------------------------------------------------------------------------- - using proposal_ref = block_ref; // ---------------------------------------------------------------------------------------- struct finalizer_safety_information { block_timestamp_type last_vote_range_start; - proposal_ref last_vote; - proposal_ref lock; + block_ref last_vote; + block_ref lock; static constexpr uint64_t magic = 0x5AFE11115AFE1111ull; @@ -59,10 +57,9 @@ namespace eosio::chain { bls_private_key priv_key; finalizer_safety_information fsi; - vote_result decide_vote(const finality_core& core, const block_id_type &id, - const block_timestamp_type timestamp); + vote_result decide_vote(const block_state_ptr& bsp); - std::optional maybe_vote(const bls_public_key& pub_key, const block_header_state_ptr& bhsp, + std::optional maybe_vote(const bls_public_key& pub_key, const block_state_ptr& bsp, const digest_type& digest); }; @@ -81,7 +78,7 @@ namespace eosio::chain { template void maybe_vote(const finalizer_policy& fin_pol, - const block_header_state_ptr& bhsp, + const block_state_ptr& bsp, const digest_type& digest, F&& process_vote) { std::vector votes; @@ -90,7 +87,7 @@ namespace eosio::chain { // first accumulate all the votes for (const auto& f : fin_pol.finalizers) { if (auto it = finalizers.find(f.public_key); it != finalizers.end()) { - std::optional vote_msg = it->second.maybe_vote(it->first, bhsp, digest); + std::optional vote_msg = it->second.maybe_vote(it->first, bsp, digest); if (vote_msg) votes.push_back(std::move(*vote_msg)); } diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp index 0fa20f4bfa..b5ab8fc0bb 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -19,7 +20,7 @@ namespace eosio::chain { using bls_key_map_t = std::map; struct vote_message { - fc::sha256 proposal_id; //vote on proposal + block_id_type block_id; bool strong{false}; bls_public_key finalizer_key; bls_signature sig; @@ -60,6 +61,10 @@ namespace eosio::chain { struct quorum_certificate { uint32_t block_num; valid_quorum_certificate qc; + + qc_claim_t to_qc_claim() const { + return {.block_num = block_num, .is_strong_qc = qc.is_strong()}; + } }; @@ -140,7 +145,7 @@ namespace eosio::chain { } //eosio::chain -FC_REFLECT(eosio::chain::vote_message, (proposal_id)(strong)(finalizer_key)(sig)); +FC_REFLECT(eosio::chain::vote_message, (block_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)); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index c9ffb5d73f..9fa34f758f 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -532,6 +532,7 @@ namespace eosio { public: void update_chain_info(); + void update_chain_info(const block_id_type& lib); chain_info_t get_chain_info() const; uint32_t get_chain_lib_num() const; uint32_t get_chain_head_num() const; @@ -3100,7 +3101,7 @@ namespace eosio { if( my_impl->dispatcher->have_block( blk_id ) ) { peer_dlog( this, "canceling wait, already received block ${num}, id ${id}...", ("num", blk_num)("id", blk_id.str().substr(8,16)) ); - my_impl->sync_master->sync_recv_block( shared_from_this(), blk_id, blk_num, false ); + my_impl->sync_master->sync_recv_block( shared_from_this(), blk_id, blk_num, true ); cancel_wait(); pending_message_buffer.advance_read_ptr( message_length ); @@ -3235,14 +3236,29 @@ namespace eosio { uint32_t lib_num = 0, head_num = 0; { fc::lock_guard g( chain_info_mtx ); - chain_info.lib_num = lib_num = cc.last_irreversible_block_num(); chain_info.lib_id = cc.last_irreversible_block_id(); - chain_info.head_num = head_num = cc.fork_db_head_block_num(); + chain_info.lib_num = lib_num = block_header::num_from_id(chain_info.lib_id); chain_info.head_id = cc.fork_db_head_block_id(); + chain_info.head_num = head_num = block_header::num_from_id(chain_info.head_id); } fc_dlog( logger, "updating chain info lib ${lib}, fork ${fork}", ("lib", lib_num)("fork", head_num) ); } + // call only from main application thread + void net_plugin_impl::update_chain_info(const block_id_type& lib) { + controller& cc = chain_plug->chain(); + uint32_t lib_num = 0, head_num = 0; + { + fc::lock_guard g( chain_info_mtx ); + chain_info.lib_num = lib_num = block_header::num_from_id(lib); + chain_info.lib_id = lib; + chain_info.head_id = cc.fork_db_head_block_id(); + chain_info.head_num = head_num = block_header::num_from_id(chain_info.head_id); + } + fc_dlog( logger, "updating chain info lib ${lib}, fork ${fork}", ("lib", lib_num)("fork", head_num) ); + } + + net_plugin_impl::chain_info_t net_plugin_impl::get_chain_info() const { fc::lock_guard g( chain_info_mtx ); return chain_info; @@ -3700,9 +3716,9 @@ namespace eosio { } void connection::handle_message( const vote_message& msg ) { - peer_dlog(this, "received 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))); + peer_dlog(this, "received vote: block #${bn}:${id}.., ${v}, key ${k}..", + ("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16)) + ("v", msg.strong ? "strong" : "weak")("k", msg.finalizer_key.to_string().substr(8, 16))); controller& cc = my_impl->chain_plug->chain(); switch( cc.process_vote_message(msg) ) { @@ -3991,8 +4007,8 @@ namespace eosio { // called from application thread void net_plugin_impl::on_voted_block(const vote_message& msg) { - fc_dlog(logger, "on voted signal: block #${bn}:${id}.., ${t}, key ${k}..", - ("bn", block_header::num_from_id(msg.proposal_id))("id", msg.proposal_id.str().substr(8,16)) + fc_dlog(logger, "on voted signal: block #${bn} ${id}.., ${t}, key ${k}..", + ("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16)) ("t", msg.strong ? "strong" : "weak")("k", msg.finalizer_key.to_string().substr(8, 16))); bcast_vote_message(std::nullopt, msg); } @@ -4001,9 +4017,9 @@ 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))); + fc_dlog(logger, "bcast ${t} vote: block #${bn} ${id}.., ${v}, key ${k}..", + ("t", exclude_peer ? "received" : "our")("bn", block_header::num_from_id(msg.block_id))("id", msg.block_id.str().substr(8,16)) + ("v", 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) ); @@ -4017,7 +4033,7 @@ namespace eosio { // called from application thread void net_plugin_impl::on_irreversible_block( const block_id_type& id, uint32_t block_num) { fc_dlog( logger, "on_irreversible_block, blk num = ${num}, id = ${id}", ("num", block_num)("id", id) ); - update_chain_info(); + update_chain_info(id); } // called from application thread diff --git a/tests/lib_advance_test.py b/tests/lib_advance_test.py index d4d8fa992f..cb2ddd104c 100755 --- a/tests/lib_advance_test.py +++ b/tests/lib_advance_test.py @@ -72,7 +72,7 @@ prodNodes=[ prodNode0, prodNode1, prodNode2, prodNode3 ] prodA=prodNode0 # defproducera - prodD=prodNode3 # defproducerc + prodD=prodNode3 # defproducerd # *** Identify a block where production is stable *** diff --git a/tests/trx_finality_status_forked_test.py b/tests/trx_finality_status_forked_test.py index 820c65d38e..813c3a3fde 100755 --- a/tests/trx_finality_status_forked_test.py +++ b/tests/trx_finality_status_forked_test.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 - +import sys import time import decimal import json @@ -82,7 +82,7 @@ prodNodes=[ prodNode0, prodNode1, prodNode2, prodNode3 ] prodA=prodNode0 # defproducera - prodD=prodNode3 # defproducerc + prodD=prodNode3 # defproducerd # *** Identify a block where production is stable *** @@ -119,6 +119,14 @@ def getState(status): f"ERROR: getTransactionStatus returned a status object that didn't have a \"state\" field. state: {json.dumps(status, indent=1)}" return status["state"] + def getBlockNum(status): + assert status is not None, "ERROR: getTransactionStatus failed to return any status" + assert "head_number" in status, \ + f"ERROR: getTransactionStatus returned a status object that didn't have a \"head_number\" field. state: {json.dumps(status, indent=1)}" + if "block_number" in status: + return status["block_number"] + return status["head_number"] + transferAmount = 10 # Does not use transaction retry (not needed) transfer = prodD.transferFunds(cluster.eosioAccount, cluster.defproduceraAccount, f"{transferAmount}.0000 {CORE_SYMBOL}", "fund account") @@ -159,15 +167,19 @@ def getState(status): if not nonProdNode.relaunch(): errorExit(f"Failure - (non-production) node {nonProdNode.nodeNum} should have restarted") - while prodD.getInfo()['last_irreversible_block_num'] < transBlockNum: - 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." + Print("Repeatedly check status looking for forked out state until after LIB moves and defproducerd") + while True: + info = prodD.getInfo() retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) - if state == forkedOutState: + blockNum = getBlockNum(retStatus) + if state == forkedOutState or ( info['head_block_producer'] == 'defproducerd' and info['last_irreversible_block_num'] > blockNum ): break + if state == irreversibleState: + Print(f"Transaction became irreversible before it could be found forked out: {json.dumps(retStatus, indent=1)}") + sys.exit(0) + 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)}" @@ -177,12 +189,22 @@ def getState(status): Print(f"node info: {json.dumps(info, indent=1)}") assert prodD.waitForProducer("defproducerd"), \ - f"Waiting for prodC to produce, but it never happened" + \ + f"Waiting for prodD to produce, but it never happened" + \ f"\n\nprod A info: {json.dumps(prodA.getInfo(), indent=1)}\n\nprod D info: {json.dumps(prodD.getInfo(), indent=1)}" retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) + # it is possible for another fork switch to cause the trx to be forked out again + if state == forkedOutState: + while True: + info = prodD.getInfo() + retStatus = prodD.getTransactionStatus(transId) + state = getState(retStatus) + blockNum = getBlockNum(retStatus) + if (state == inBlockState or state == irreversibleState) or ( info['head_block_producer'] == 'defproducerd' and info['last_irreversible_block_num'] > blockNum ): + break + 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)}" @@ -206,6 +228,16 @@ def getState(status): retStatus = prodD.getTransactionStatus(transId) state = getState(retStatus) + # it is possible for another fork switch to cause the trx to be forked out again + if state == forkedOutState: + while True: + info = prodD.getInfo() + retStatus = prodD.getTransactionStatus(transId) + state = getState(retStatus) + blockNum = getBlockNum(retStatus) + if state == irreversibleState or ( info['head_block_producer'] == 'defproducerd' and info['last_irreversible_block_num'] > blockNum ): + break + assert state == irreversibleState, \ f"ERROR: getTransactionStatus didn't return \"{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)}" diff --git a/unittests/finality_test_cluster.cpp b/unittests/finality_test_cluster.cpp index 1af4cd4fb8..8b4824c3d1 100644 --- a/unittests/finality_test_cluster.cpp +++ b/unittests/finality_test_cluster.cpp @@ -103,10 +103,10 @@ bool finality_test_cluster::produce_blocks_and_verify_lib_advancing() { void finality_test_cluster::node1_corrupt_vote_proposal_id() { node1_orig_vote = node1.votes[0]; - if( node1.votes[0].proposal_id.data()[0] == 'a' ) { - node1.votes[0].proposal_id.data()[0] = 'b'; + if( node1.votes[0].block_id.data()[0] == 'a' ) { + node1.votes[0].block_id.data()[0] = 'b'; } else { - node1.votes[0].proposal_id.data()[0] = 'a'; + node1.votes[0].block_id.data()[0] = 'a'; } } @@ -185,7 +185,7 @@ eosio::chain::vote_status finality_test_cluster::process_vote(node_info& node, s vote.strong = false; // fetch the strong digest - auto strong_digest = node.node.control->get_strong_digest_by_id(vote.proposal_id); + auto strong_digest = node.node.control->get_strong_digest_by_id(vote.block_id); // convert the strong digest to weak and sign it vote.sig = node.priv_key.sign(eosio::chain::create_weak_digest(strong_digest)); } diff --git a/unittests/finalizer_tests.cpp b/unittests/finalizer_tests.cpp index dfe8ce6580..45aa349231 100644 --- a/unittests/finalizer_tests.cpp +++ b/unittests/finalizer_tests.cpp @@ -31,21 +31,21 @@ std::vector create_random_fsi(size_t count) { res.reserve(count); for (size_t i=0; i create_proposal_refs(size_t count) { - std::vector res; +std::vector create_proposal_refs(size_t count) { + std::vector res; res.reserve(count); for (size_t i=0; icore, p->id(), p->timestamp()); + vote_result vote(const bsp& p) { + auto vote_res = my_finalizer.decide_vote(p); return vote_res; }