diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index b3a1afe621..786a0972ff 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -99,6 +99,7 @@ add_library( eosio_chain block_state.cpp block_header_state_legacy.cpp block_state_legacy.cpp + finality_core.cpp fork_database.cpp controller.cpp authorization_manager.cpp diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index f1a257a67a..d945f55fc0 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -22,50 +22,6 @@ const vector& block_header_state::get_new_protocol_feature_activati #warning Add last_proposed_finalizer_policy_generation to snapshot_block_header_state_v3, see header file TODO -block_header_state_core block_header_state_core::next(qc_claim_t incoming) const { - // no state change if last_qc_block_num is the same - if (incoming.last_qc_block_num == this->last_qc_block_num) { - return {*this}; - } - - EOS_ASSERT(incoming.last_qc_block_num > this->last_qc_block_num && - incoming.last_qc_block_timestamp > this->last_qc_block_timestamp, block_validate_exception, - "new last_qc_block_num ${new} must be greater than old last_qc_block_num ${old}", - ("new", incoming.last_qc_block_num)("old", this->last_qc_block_num)); - - auto old_last_qc_block_num = this->last_qc_block_num; - auto old_final_on_strong_qc_block_num = this->final_on_strong_qc_block_num; - - block_header_state_core result{*this}; - - if (incoming.is_last_qc_strong) { - // last QC is strong. We can progress forward. - - // block with old final_on_strong_qc_block_num becomes irreversible - if (old_final_on_strong_qc_block_num.has_value()) { - result.last_final_block_num = *old_final_on_strong_qc_block_num; - } - - // next block which can become irreversible is the block with - // old last_qc_block_num - if (old_last_qc_block_num.has_value()) { - result.final_on_strong_qc_block_num = *old_last_qc_block_num; - } - } else { - // new final_on_strong_qc_block_num should not be present - result.final_on_strong_qc_block_num.reset(); - - // new last_final_block_num should be the same as the old last_final_block_num - } - - // new last_qc_block_num is always the input last_qc_block_num. - result.last_qc_block_num = incoming.last_qc_block_num; - result.last_qc_block_timestamp = incoming.last_qc_block_timestamp; - - return result; -} - - block_header_state block_header_state::next(block_header_state_input& input) const { block_header_state result; @@ -125,33 +81,19 @@ block_header_state block_header_state::next(block_header_state_input& input) con // ++input.new_finalizer_policy->generation; - qc_claim_t qc_claim; - uint16_t if_ext_id = instant_finality_extension::extension_id(); - - if (input.qc_claim) { - qc_claim = *input.qc_claim; - dlog("qc_claim from input -> final value: ${qci}",("qci", qc_claim)); - } else { - // copy previous qc_claim if we are not provided with a new one - // ------------------------------------------------------------ - auto if_entry = header_exts.lower_bound(if_ext_id); - if (if_entry != header_exts.end()) { - const auto& qci = std::get(if_entry->second).qc_claim; - qc_claim = qci; - dlog("qc_claim from existing extension -> final value: ${qci}",("qci",qc_claim)); - } else { - assert(0); // we should always get a previous if extension when in IF mode. - } - } - - instant_finality_extension new_if_ext {qc_claim, + instant_finality_extension new_if_ext {input.most_recent_ancestor_with_qc, std::move(input.new_finalizer_policy), std::move(input.new_proposer_policy)}; - // block_header_state_core + // finality_core // ----------------------- - result.core = core.next(new_if_ext.qc_claim); + block_ref parent_block { + .block_id = input.parent_id, + .timestamp = input.parent_timestamp + }; + result.core = core.next(parent_block, input.most_recent_ancestor_with_qc); + uint16_t if_ext_id = instant_finality_extension::extension_id(); emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); result.header_exts.emplace(if_ext_id, std::move(new_if_ext)); @@ -207,9 +149,10 @@ block_header_state block_header_state::next(const signed_block_header& h, const auto& if_ext = std::get(if_entry->second); building_block_input bb_input{ - .parent_id = block_id, - .timestamp = h.timestamp, - .producer = producer, + .parent_id = block_id, + .parent_timestamp = timestamp(), + .timestamp = h.timestamp, + .producer = producer, .new_protocol_feature_activations = std::move(new_protocol_feature_activations) }; @@ -220,4 +163,4 @@ block_header_state block_header_state::next(const signed_block_header& h, const return next(bhs_input); } -} // namespace eosio::chain \ No newline at end of file +} // namespace eosio::chain diff --git a/libraries/chain/block_header_state_legacy.cpp b/libraries/chain/block_header_state_legacy.cpp index 0266ec75f6..cd4ee71cc4 100644 --- a/libraries/chain/block_header_state_legacy.cpp +++ b/libraries/chain/block_header_state_legacy.cpp @@ -210,9 +210,8 @@ namespace eosio::chain { if (new_finalizer_policy) { new_finalizer_policy->generation = 1; // set current block_num as qc_claim.last_qc_block_num in the IF extension - qc_claim_t initial_if_claim { .last_qc_block_num = block_num, - .last_qc_block_timestamp = timestamp, - .is_last_qc_strong = false }; + qc_claim_t initial_if_claim { .block_num = block_num, + .is_strong_qc = false }; emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), fc::raw::pack(instant_finality_extension{ initial_if_claim, std::move(new_finalizer_policy), {} })); } diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 13c8979036..980049fba9 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -38,7 +38,7 @@ block_state::block_state(const block_header_state& bhs, deque([&](const auto& forkdb) { auto branch = forkdb.fetch_branch(parent_id()); + std::optional qc; for( auto it = branch.begin(); it != branch.end(); ++it ) { - auto qc = (*it)->get_best_qc(); + qc = (*it)->get_best_qc(); if( 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, (*it)->timestamp(), qc->qc.is_strong() }; + auto qc_claim = qc_claim_t { qc->block_num, qc->qc.is_strong() }; if( bb.parent.is_needed(*qc) ) { qc_data = qc_data_t{ *qc, qc_claim }; } else { @@ -681,11 +682,20 @@ struct building_block { 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. + qc_data = qc_data_t{ {}, bb.parent.core.latest_qc_claim() }; + } }); + } building_block_input bb_input { .parent_id = parent_id(), + .parent_timestamp = bb.parent.timestamp(), .timestamp = timestamp(), .producer = producer(), .new_protocol_feature_activations = new_protocol_feature_activations() @@ -694,7 +704,7 @@ struct building_block { block_header_state_input bhs_input{ bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy), - qc_data ? qc_data->qc_claim : std::optional{} + qc_data->qc_claim }; assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input), @@ -2555,7 +2565,7 @@ struct controller_impl { }, [&](auto& forkdb) { // instant-finality maybe_session session = skip_db_sessions(s) ? maybe_session() : maybe_session(db); - building_block_input bbi{forkdb.chain_head->id(), when, forkdb.chain_head->get_scheduled_producer(when).producer_name, + building_block_input bbi{forkdb.chain_head->id(), forkdb.chain_head->timestamp(), when, forkdb.chain_head->get_scheduled_producer(when).producer_name, new_protocol_feature_activations}; pending.emplace(std::move(session), *forkdb.chain_head, bbi); }); @@ -3139,14 +3149,14 @@ struct controller_impl { // Save the QC. This is safe as the function is called by push_block from application thread. bsp->valid_qc = received_qc; - // advance LIB if QC is strong and final_on_strong_qc_block_num has value - if( received_qc.is_strong() && bsp->core.final_on_strong_qc_block_num ) { + // advance LIB if QC is strong + if( received_qc.is_strong() ) { // We evaluate a block extension qc and advance lib if strong. // This is done before evaluating the block. It is possible the block // will not be valid or forked out. This is safe because the block is // just acting as a carrier of this info. It doesn't matter if the block // is actually valid as it simply is used as a network message for this data. - set_if_irreversible_block_num(*bsp->core.final_on_strong_qc_block_num); + set_if_irreversible_block_num(bsp->core.final_on_strong_qc_block_num); } } @@ -3185,14 +3195,14 @@ struct controller_impl { assert(header_ext); const auto& if_ext = std::get(*header_ext); - const auto qc_claim = if_ext.qc_claim; + const auto new_qc_claim = if_ext.qc_claim; // If there is a header extension, but the previous block does not have a header extension, - // ensure the block does not have a QC and the QC claim of the current block has a last_qc_block_num + // ensure the block does not have a QC and the QC claim of the current block has a block_num // of the current block’s number and that it is a claim of a weak QC. Then return early. // ------------------------------------------------------------------------------------------------- if (!prev_header_ext) { - EOS_ASSERT( !qc_extension_present && qc_claim.last_qc_block_num == block_num && qc_claim.is_last_qc_strong == false, + EOS_ASSERT( !qc_extension_present && new_qc_claim.block_num == block_num && new_qc_claim.is_strong_qc == false, invalid_qc_claim, "Block #${b}, which is the finality transition block, doesn't have the expected extensions", ("b", block_num) ); @@ -3210,14 +3220,13 @@ struct controller_impl { // validate QC claim against previous block QC info // new claimed QC block number cannot be smaller than previous block's - EOS_ASSERT( qc_claim.last_qc_block_num >= prev_qc_claim.last_qc_block_num && - qc_claim.last_qc_block_timestamp >= prev_qc_claim.last_qc_block_timestamp, + EOS_ASSERT( new_qc_claim.block_num >= prev_qc_claim.block_num, invalid_qc_claim, - "Block #${b} claims a last_qc_block_num (${n1}) less than the previous block's (${n2})", - ("n1", qc_claim.last_qc_block_num)("n2", prev_qc_claim.last_qc_block_num)("b", block_num) ); + "Block #${b} claims a block_num (${n1}) less than the previous block's (${n2})", + ("n1", new_qc_claim.block_num)("n2", prev_qc_claim.block_num)("b", block_num) ); - if( qc_claim.last_qc_block_num == prev_qc_claim.last_qc_block_num ) { - if( qc_claim.is_last_qc_strong == prev_qc_claim.is_last_qc_strong ) { + if( new_qc_claim.block_num == prev_qc_claim.block_num ) { + if( new_qc_claim.is_strong_qc == prev_qc_claim.is_strong_qc ) { // QC block extension is redundant EOS_ASSERT( !qc_extension_present, invalid_qc_claim, @@ -3230,10 +3239,10 @@ struct controller_impl { } // new claimed QC must be stronger than previous if the claimed block number is the same - EOS_ASSERT( qc_claim.is_last_qc_strong, + EOS_ASSERT( new_qc_claim.is_strong_qc, invalid_qc_claim, "claimed QC (${s1}) must be stricter than previous block's (${s2}) if block number is the same. Block number: ${b}", - ("s1", qc_claim.is_last_qc_strong)("s2", prev_qc_claim.is_last_qc_strong)("b", block_num) ); + ("s1", new_qc_claim.is_strong_qc)("s2", prev_qc_claim.is_strong_qc)("b", block_num) ); } // At this point, we are making a new claim in this block, so it better include a QC to justify this claim. @@ -3245,23 +3254,23 @@ struct controller_impl { const auto& qc_proof = qc_ext.qc; // Check QC information in header extension and block extension match - EOS_ASSERT( qc_proof.block_num == qc_claim.last_qc_block_num, + EOS_ASSERT( qc_proof.block_num == new_qc_claim.block_num, invalid_qc_claim, - "Block #${b}: Mismatch between qc.block_num (${n1}) in block extension and last_qc_block_num (${n2}) in header extension", - ("n1", qc_proof.block_num)("n2", qc_claim.last_qc_block_num)("b", block_num) ); + "Block #${b}: Mismatch between qc.block_num (${n1}) in block extension and block_num (${n2}) in header extension", + ("n1", qc_proof.block_num)("n2", new_qc_claim.block_num)("b", block_num) ); // Verify claimed strictness is the same as in proof - EOS_ASSERT( qc_proof.qc.is_strong() == qc_claim.is_last_qc_strong, + EOS_ASSERT( qc_proof.qc.is_strong() == new_qc_claim.is_strong_qc, invalid_qc_claim, - "QC is_strong (${s1}) in block extension does not match is_last_qc_strong (${s2}) in header extension. Block number: ${b}", - ("s1", qc_proof.qc.is_strong())("s2", qc_claim.is_last_qc_strong)("b", block_num) ); + "QC is_strong (${s1}) in block extension does not match is_strong_qc (${s2}) in header extension. Block number: ${b}", + ("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(), qc_claim.last_qc_block_num ); + auto bsp = fork_db_fetch_bsp_by_num( prev.id(), new_qc_claim.block_num ); EOS_ASSERT( bsp, invalid_qc_claim, - "Block state was not found in forkdb for last_qc_block_num ${q}. Block number: ${b}", - ("q", qc_claim.last_qc_block_num)("b", block_num) ); + "Block state was not found in forkdb for block_num ${q}. Block number: ${b}", + ("q", new_qc_claim.block_num)("b", block_num) ); // verify the QC proof against the claimed block bsp->verify_qc(qc_proof.qc); diff --git a/libraries/chain/finality_core.cpp b/libraries/chain/finality_core.cpp new file mode 100644 index 0000000000..5a85bdda2a --- /dev/null +++ b/libraries/chain/finality_core.cpp @@ -0,0 +1,338 @@ +#warning Remove undef NDEBUG for assert before RC +//Undefine NDEBUG to enable assertions in CICD. +#undef NDEBUG +#include + +#include +#include + +namespace eosio::chain { + +/** + * @pre block_id is not null + * @returns the extracted block_num from block_id + */ +block_num_type block_ref::block_num() const { + return block_header::num_from_id(block_id); +} + +/** + * @pre none + * + * @post returned core has current_block_num() == block_num + * @post returned core has latest_qc_claim() == {.block_num=block_num, .is_strong_qc=false} + * @post returned core has final_on_strong_qc_block_num == block_num + * @post returned core has last_final_block_num() == block_num + */ +finality_core finality_core::create_core_for_genesis_block(block_num_type block_num) +{ + return finality_core { + .links = { + qc_link{ + .source_block_num = block_num, + .target_block_num = block_num, + .is_link_strong = false, + }, + }, + .refs = {}, + .final_on_strong_qc_block_num = block_num, + }; + + // Invariants 1 to 7 can be easily verified to be satisfied for the returned core. + // (And so, remaining invariants are also automatically satisfied.) +} + +/** + * @pre this->links.empty() == false + * @post none + * @returns block number of the core + */ +block_num_type finality_core::current_block_num() const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + return links.back().source_block_num; +} + +/** + * @pre this->links.empty() == false + * @post none + * @returns last final block_num in respect to the core + */ +block_num_type finality_core::last_final_block_num() const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + return links.front().target_block_num; +} + +/** + * @pre this->links.empty() == false + * @post none + * @returns latest qc_claim made by the core + */ +qc_claim_t finality_core::latest_qc_claim() const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + return qc_claim_t{.block_num = links.back().target_block_num, .is_strong_qc = links.back().is_link_strong}; +} + +/** + * @pre last_final_block_num() <= block_num < current_block_num() + * + * @post returned block_ref has block_num() == block_num + */ +const block_ref& finality_core::get_block_reference(block_num_type block_num) const +{ + assert(last_final_block_num() <= block_num); // Satisfied by precondition. + assert(block_num < current_block_num()); // Satisfied by precondition. + + // If refs.empty() == true, then by invariant 3, current_block_num() == last_final_block_num(), + // and therefore it is impossible to satisfy the precondition. So going forward, it is safe to assume refs.empty() == false. + + const size_t ref_index = block_num - last_final_block_num(); + + // By the precondition, 0 <= ref_index < (current_block_num() - last_final_block_num()). + // Then, by invariant 8, 0 <= ref_index < refs.size(). + + assert(ref_index < refs.size()); // Satisfied by justification above. + + return refs[ref_index]; + // By invariants 4 and 6, tail[ref_index].block_num() == block_num, which satisfies the post-condition. +} + +/** + * @pre links.front().source_block_num <= block_num <= current_block_num() + * + * @post returned qc_link has source_block_num == block_num + */ +const qc_link& finality_core::get_qc_link_from(block_num_type block_num) const +{ + assert(!links.empty()); // Satisfied by invariant 1. + + assert(links.front().source_block_num <= block_num); // Satisfied by precondition. + assert(block_num <= current_block_num()); // Satisfied by precondition. + + const size_t link_index = block_num - links.front().source_block_num; + + // By the precondition, 0 <= link_index <= (current_block_num() - links.front().source_block_num). + // Then, by invariant 9, 0 <= link_index <= links.size() - 1 + + assert(link_index < links.size()); // Satisfied by justification above. + + return links[link_index]; + // By invariants 7, links[link_index].source_block_num == block_num, which satisfies the post-condition. +} + +/** + * @pre c.latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= c.current_block_num() + * + * @post std::get<0>(returned_value) <= std::get<1>(returned_value) <= std::get<2>(returned_value) <= most_recent_ancestor_with_qc.block_num + * @post c.last_final_block_num() <= std::get<0>(returned_value) + * @post c.links.front().source_block_num <= std::get<1>(returned_value) + * @post c.final_on_strong_qc_block_num <= std::get<2>(returned_value) + */ +std::tuple get_new_block_numbers(const finality_core& c, const qc_claim_t& most_recent_ancestor_with_qc) +{ + assert(most_recent_ancestor_with_qc.block_num <= c.current_block_num()); // Satisfied by the precondition. + + // Invariant 2 of core guarantees that: + // c.last_final_block_num() <= c.links.front().source_block_num <= c.final_on_strong_qc_block_num <= c.latest_qc_claim().block_num + + assert(c.links.front().source_block_num <= most_recent_ancestor_with_qc.block_num); // Satisfied by invariant 2 of core and the precondition. + + // No changes on new claim of weak QC. + if (!most_recent_ancestor_with_qc.is_strong_qc) { + return {c.last_final_block_num(), c.links.front().source_block_num, c.final_on_strong_qc_block_num}; + } + + const auto& link1 = c.get_qc_link_from(most_recent_ancestor_with_qc.block_num); + + // By the post-condition of get_qc_link_from, link1.source_block_num == most_recent_ancestor_with_qc.block_num. + // By the invariant on qc_link, link1.target_block_num <= link1.source_block_num. + // Therefore, link1.target_block_num <= most_recent_ancestor_with_qc.block_num. + // And also by the precondition, link1.target_block_num <= c.current_block_num(). + + // If c.refs.empty() == true, then by invariant 3 of core, link1 == c.links.front() == c.links.back() and so + // link1.target_block_num == c.current_block_num(). + + // Otherwise, if c.refs.empty() == false, consider two cases. + // Case 1: link1 != c.links.back() + // In this case, link1.target_block_num <= link1.source_block_num < c.links.back().source_block_num. + // The strict inequality is justified by invariant 7 of core. + // Therefore, link1.target_block_num < c.current_block_num(). + // Case 2: link1 == c.links.back() + // In this case, link1.target_block_num < link1.source_block_num == c.links.back().source_block_num. + // The strict inequality is justified because target_block_num and source_block_num of a qc_link can only be equal for a + // genesis block. And a link mapping genesis block number to genesis block number can only possibly exist for c.links.front(). + // Therefore, link1.target_block_num < c.current_block_num(). + + // There must exist some link, call it link0, within c.links where + // link0.target_block_num == c.final_on_strong_qc_block_num and link0.source_block_num <= c.latest_qc_claim().block_num. + // By the precondition, link0.source_block_num <= most_recent_ancestor_with_qc.block_num. + // If c.links.size() > 1, then by invariant 7 of core, link0.target_block_num <= link1.target_block_num. + // Otherwise if c.links.size() == 1, then link0 == link1 and so link0.target_block_num == link1.target_block_num. + // Therefore, c.final_on_strong_qc_block_num <= link1.target_block_num. + + assert(c.final_on_strong_qc_block_num <= link1.target_block_num); // Satisfied by justification above. + + // Finality does not advance if a better 3-chain is not found. + if (!link1.is_link_strong || (link1.target_block_num < c.links.front().source_block_num)) { + return {c.last_final_block_num(), c.links.front().source_block_num, link1.target_block_num}; + } + + const auto& link2 = c.get_qc_link_from(link1.target_block_num); + + // By the post-condition of get_qc_link_from, link2.source_block_num == link1.target_block_num. + // By the invariant on qc_link, link2.target_block_num <= link2.source_block_num. + // Therefore, link2.target_block_num <= link1.target_block_num. + + // Wherever link2 is found within c.links, it must be the case that c.links.front().target_block_num <= link2.target_block_num. + // This is obvious if c.links.size() == 1 (even though the code would even not get to this point if c.links.size() == 1), and + // for the case where c.links.size() > 1, it is justified by invariant 7 of core. + // Therefore, c.last_final_block_num() <= link2.target_block_num. + + return {link2.target_block_num, link2.source_block_num, link1.target_block_num}; +} + +core_metadata finality_core::next_metadata(const qc_claim_t& most_recent_ancestor_with_qc) const +{ + assert(most_recent_ancestor_with_qc.block_num <= current_block_num()); // Satisfied by precondition 1. + assert(latest_qc_claim() <= most_recent_ancestor_with_qc); // Satisfied by precondition 2. + + const auto [new_last_final_block_num, new_links_front_source_block_num, new_final_on_strong_qc_block_num] = + get_new_block_numbers(*this, most_recent_ancestor_with_qc); + + (void)new_links_front_source_block_num; + + return core_metadata { + .last_final_block_num = new_last_final_block_num, + .final_on_strong_qc_block_num = new_final_on_strong_qc_block_num, + .latest_qc_claim_block_num = most_recent_ancestor_with_qc.block_num, + }; + // Post-conditions satisfied by post-conditions of get_new_block_numbers. +} + +/** + * @pre current_block.block_num() == this->current_block_num() + * @pre If this->refs.empty() == false, then current_block is the block after the one referenced by this->refs.back() + * @pre this->latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= this->current_block_num() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc + * + * @post returned core has current_block_num() == this->current_block_num() + 1 + * @post returned core has latest_qc_claim() == most_recent_ancestor_with_qc + * @post returned core has final_on_strong_qc_block_num >= this->final_on_strong_qc_block_num + * @post returned core has last_final_block_num() >= this->last_final_block_num() + */ +finality_core finality_core::next(const block_ref& current_block, const qc_claim_t& most_recent_ancestor_with_qc) const +{ + assert(current_block.block_num() == current_block_num()); // Satisfied by precondition 1. + + assert(refs.empty() || (refs.back().block_num() + 1 == current_block.block_num())); // Satisfied by precondition 2. + assert(refs.empty() || (refs.back().timestamp < current_block.timestamp)); // Satisfied by precondition 2. + + assert(most_recent_ancestor_with_qc.block_num <= current_block_num()); // Satisfied by precondition 3. + + assert(latest_qc_claim() <= most_recent_ancestor_with_qc); // Satisfied by precondition 4. + + finality_core next_core; + + const auto [new_last_final_block_num, new_links_front_source_block_num, new_final_on_strong_qc_block_num] = + get_new_block_numbers(*this, most_recent_ancestor_with_qc); + + assert(new_last_final_block_num <= new_links_front_source_block_num); // Satisfied by post-condition 1 of get_new_block_numbers. + assert(new_links_front_source_block_num <= new_final_on_strong_qc_block_num); // Satisfied by post-condition 1 of get_new_block_numbers. + assert(new_final_on_strong_qc_block_num <= most_recent_ancestor_with_qc.block_num); // Satisfied by post-condition 1 of get_new_block_numbers. + + assert(last_final_block_num() <= new_last_final_block_num); // Satisfied by post-condition 2 of get_new_block_numbers. + assert(links.front().source_block_num <= new_links_front_source_block_num); // Satisfied by post-condition 3 of get_new_block_numbers. + assert(final_on_strong_qc_block_num <= new_final_on_strong_qc_block_num); // Satisfied by post-condition 4 of get_new_block_numbers. + + next_core.final_on_strong_qc_block_num = new_final_on_strong_qc_block_num; + // Post-condition 3 is satisfied, assuming next_core will be returned without further modifications to next_core.final_on_strong_qc_block_num. + + // Post-condition 4 and invariant 2 will be satisfied when next_core.last_final_block_num() is updated to become new_last_final_block_num. + + // Setup next_core.links by garbage collecting unnecessary links and then adding the new QC link. + { + const size_t links_index = new_links_front_source_block_num - links.front().source_block_num; + + assert(links_index < links.size()); // Satisfied by justification in this->get_qc_link_from(new_links_front_source_block_num). + + next_core.links.reserve(links.size() - links_index + 1); + + // Garbage collect unnecessary links + std::copy(links.cbegin() + links_index, links.cend(), std::back_inserter(next_core.links)); + + assert(next_core.last_final_block_num() == new_last_final_block_num); // Satisfied by choice of links_index. + + // Also, by choice of links_index, at this point, next_core.links.back() == this->links.back(). + assert(next_core.links.back().source_block_num == current_block_num()); // Satisfied because last item in links has not yet changed. + assert(next_core.links.back().target_block_num <= most_recent_ancestor_with_qc.block_num); // Satisfied because of above and precondition 3. + + // Add new link + next_core.links.emplace_back( + qc_link{ + .source_block_num = current_block_num() + 1, + .target_block_num = most_recent_ancestor_with_qc.block_num, // Guaranteed to be less than current_block_num() + 1. + .is_link_strong = most_recent_ancestor_with_qc.is_strong_qc, + }); + + // Post-conditions 1, 2, and 4 are satisfied, assuming next_core will be returned without further modifications to next_core.links. + + // Invariants 1, 2, and 7 are satisfied for next_core. + } + + // Setup next_core.refs by garbage collecting unnecessary block references in the refs and then adding the new block reference. + { + const size_t refs_index = new_last_final_block_num - last_final_block_num(); + + // Using the justifications in new_block_nums, 0 <= ref_index <= (current_block_num() - last_final_block_num). + // If refs.empty() == true, then by invariant 3, current_block_num() == last_final_block_num, and therefore ref_index == 0. + // Otherwise if refs.empty() == false, the justification in new_block_nums provides the stronger inequality + // 0 <= ref_index < (current_block_num() - last_final_block_num), which, using invariant 8, can be simplified to + // 0 <= ref_index < refs.size(). + + assert(!refs.empty() || (refs_index == 0)); // Satisfied by justification above. + assert(refs.empty() || (refs_index < refs.size())); // Satisfied by justification above. + + next_core.refs.reserve(refs.size() - refs_index + 1); + + // Garbage collect unnecessary block references + std::copy(refs.cbegin() + refs_index, refs.cend(), std::back_inserter(next_core.refs)); + + assert(refs.empty() || (next_core.refs.front().block_num() == new_last_final_block_num)); // Satisfied by choice of refs_index. + + // Add new block reference + next_core.refs.emplace_back(current_block); + + // Invariant 3 is trivially satisfied for next_core because next_core.refs.empty() == false. + + // Invariant 5 is clearly satisfied for next_core because next_core.refs.back().block_num() == this->current_block_num() + // and next_core.links.back().source_block_num == this->current_block_num() + 1. + + // Invariant 6 is also clearly satisfied for next_core because invariant 6 is satisfied for *this and the only + // additional requirements needed are the ones provided by precondition 2. + + // If this->refs.empty() == true, then new_last_final_block_num == this->last_final_block_num() == this->current_block_num(), + // and next_core.refs.size() == 1 and next_core.refs.front() == current_block. + // And so, next_core.refs.front().block_num() == new_last_final_block_num. + // If this->refs.empty() == false, then adding the current_block to the end does not change the fact that + // next_core.refs.front().block_num() is still equal to new_last_final_block_num. + + assert(next_core.refs.front().block_num() == new_last_final_block_num); // Satisfied by justification above. + + // Because it was also already shown earlier that links.front().target_block_num == new_last_final_block_num, + // then the justification above satisfies the remaining equalities needed to satisfy invariant 4 for next_core. + + // So, invariants 3 to 6 are now satisfied for next_core in addition to the invariants 1, 2, and 7 that were shown to be satisfied + // earlier (and still remain satisfied since next_core.links and next_core.final_on_strong_qc_block_num have not changed). + } + + return next_core; + // Invariants 1 to 7 were verified to be satisfied for the current value of next_core at various points above. + // (And so, the remaining invariants for next_core are also automatically satisfied.) +} + +} /// eosio::chain diff --git a/libraries/chain/hotstuff/finalizer.cpp b/libraries/chain/hotstuff/finalizer.cpp index d9b8d6e74a..8145d9d1db 100644 --- a/libraries/chain/hotstuff/finalizer.cpp +++ b/libraries/chain/hotstuff/finalizer.cpp @@ -88,8 +88,7 @@ finalizer::vote_decision finalizer::decide_vote(const block_state_ptr& proposal, } else { dlog("last_qc_block_num=${lqc}, fork_db root block_num=${f}", ("lqc",!!proposal->last_qc_block_num())("f",fork_db.root()->block_num())); - if (proposal->last_qc_block_num()) - dlog("last_qc_block_num=${lqc}", ("lqc", *proposal->last_qc_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")); @@ -260,4 +259,4 @@ void my_finalizers_t::set_default_safety_information(const fsi_t& fsi) { default_fsi = fsi; } -} // namespace eosio::chain \ No newline at end of file +} // namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/block_header.hpp b/libraries/chain/include/eosio/chain/block_header.hpp index aadf04bc29..4bd4332dda 100644 --- a/libraries/chain/include/eosio/chain/block_header.hpp +++ b/libraries/chain/include/eosio/chain/block_header.hpp @@ -74,6 +74,7 @@ namespace eosio::chain { block_id_type calculate_id() const; uint32_t block_num() const { return num_from_id(previous) + 1; } static uint32_t num_from_id(const block_id_type& id); + uint32_t protocol_version() const { return 0; } header_extension_multimap validate_and_extract_header_extensions()const; std::optional extract_header_extension(uint16_t extension_id)const; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 5fb0d4ef45..12b1e65aac 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -14,6 +15,7 @@ namespace detail { struct schedule_info; }; struct building_block_input { block_id_type parent_id; + block_timestamp_type parent_timestamp; block_timestamp_type timestamp; account_name producer; vector new_protocol_feature_activations; @@ -25,27 +27,17 @@ struct block_header_state_input : public building_block_input { digest_type action_mroot; // Compute root from building_block::action_receipt_digests std::shared_ptr new_proposer_policy; // Comes from building_block::new_proposer_policy std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy - std::optional qc_claim; // Comes from traversing branch from parent and calling get_best_qc() + qc_claim_t most_recent_ancestor_with_qc; // Comes from traversing branch from parent and calling get_best_qc() // assert(qc->block_num <= num_from_id(previous)); }; -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; // The block number of the most recent ancestor block that has a QC justification - block_timestamp_type last_qc_block_timestamp; // The block timestamp of the most recent ancestor block that has a QC justification - uint32_t finalizer_policy_generation{0}; // - - block_header_state_core next(qc_claim_t incoming) const; -}; - struct block_header_state { // ------ data members ------------------------------------------------------------ block_id_type block_id; block_header header; protocol_feature_activation_set_ptr activated_protocol_features; - block_header_state_core core; + finality_core core; incremental_merkle_tree proposal_mtree; incremental_merkle_tree finality_mtree; @@ -69,7 +61,9 @@ struct block_header_state { account_name producer() const { return header.producer; } const block_id_type& previous() const { return header.previous; } uint32_t block_num() const { return block_header::num_from_id(previous()) + 1; } - block_timestamp_type last_qc_block_timestamp() const { return core.last_qc_block_timestamp; } + 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; } const producer_authority_schedule& active_schedule_auth() const { return active_proposer_policy->proposer_schedule; } block_header_state next(block_header_state_input& data) const; @@ -78,7 +72,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.latest_qc_claim().block_num; } flat_set get_activated_protocol_features() const { return activated_protocol_features->protocol_features; } @@ -96,8 +90,6 @@ using block_header_state_ptr = std::shared_ptr; } -FC_REFLECT( eosio::chain::block_header_state_core, - (last_final_block_num)(final_on_strong_qc_block_num)(last_qc_block_num)(last_qc_block_timestamp)(finalizer_policy_generation)) FC_REFLECT( eosio::chain::block_header_state, (block_id)(header)(activated_protocol_features)(core)(proposal_mtree)(finality_mtree) (active_finalizer_policy)(active_proposer_policy)(proposer_policies)(finalizer_policies)(header_exts)) diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index c628741fae..48a8079712 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -40,10 +40,10 @@ struct block_state : public block_header_state { // block_header_state provi 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(); } std::optional get_best_qc() const; - std::optional last_qc_block_num() const { return core.last_qc_block_num; } - std::optional final_on_strong_qc_block_num() const { return core.final_on_strong_qc_block_num; } + 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; } diff --git a/libraries/chain/include/eosio/chain/finality_core.hpp b/libraries/chain/include/eosio/chain/finality_core.hpp new file mode 100644 index 0000000000..10d3268bdd --- /dev/null +++ b/libraries/chain/include/eosio/chain/finality_core.hpp @@ -0,0 +1,139 @@ +#pragma once + +#include +#include + +namespace eosio::chain { + +using block_num_type = uint32_t; +using block_time_type = chain::block_timestamp_type; + +struct block_ref +{ + block_id_type block_id; + block_time_type timestamp; + + block_num_type block_num() const; // Extract from block_id. +}; + +struct qc_link +{ + block_num_type source_block_num {0}; + block_num_type target_block_num {0}; // Must be less than or equal to source_block_num (only equal for genesis block). + bool is_link_strong {false}; +}; + +struct qc_claim_t +{ + block_num_type block_num {0}; + bool is_strong_qc {false}; + + auto operator<=>(const qc_claim_t&) const = default; +}; + +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; +}; + +struct finality_core +{ + std::vector links; // Captures all relevant links sorted in order of ascending source_block_num. + std::vector refs; // Covers ancestor blocks with block numbers greater than or equal to last_final_block_num. + // Sorted in order of ascending block_num. + block_num_type final_on_strong_qc_block_num {0}; + + // Invariants: + // 1. links.empty() == false + // 2. last_final_block_num() <= links.front().source_block_num <= final_on_strong_qc_block_num <= latest_qc_claim().block_num + // 3. If refs.empty() == true, then (links.size() == 1) and + // (links.back().target_block_num == links.back().source_block_num == final_on_strong_qc_block_num == last_final_block_num()) + // 4. If refs.empty() == false, then refs.front().block_num() == links.front().target_block_num == last_final_block_num() + // 5. If refs.empty() == false, then refs.back().block_num() + 1 == links.back().source_block_num == current_block_num() + // 6. If refs.size() > 1, then: + // For i = 0 to refs.size() - 2: + // (refs[i].block_num() + 1 == refs[i+1].block_num()) and (refs[i].timestamp < refs[i+1].timestamp) + // 7. If links.size() > 1, then: + // For i = 0 to links.size() - 2: + // (links[i].source_block_num + 1 == links[i+1].source_block_num) and (links[i].target_block_num <= links[i+1].target_block_num) + // 8. current_block_num() - last_final_block_num() == refs.size() (always implied by invariants 3 to 6) + // 9. current_block_num() - links.front().source_block_num == links.size() - 1 (always implied by invariants 1 and 7) + + /** + * @pre none + * + * @post returned core has current_block_num() == block_num + * @post returned core has latest_qc_claim() == {.block_num=block_num, .is_strong_qc=false} + * @post returned core has final_on_strong_qc_block_num == block_num + * @post returned core has last_final_block_num() == block_num + */ + static finality_core create_core_for_genesis_block(block_num_type block_num); + + /** + * @pre this->links.empty() == false + * @post none + * @returns block number of the core + */ + block_num_type current_block_num() const; + + /** + * @pre this->links.empty() == false + * @post none + * @returns last final block_num in respect to the core + */ + block_num_type last_final_block_num() const; + + /** + * @pre this->links.empty() == false + * @post none + * @returns latest qc_claim made by the core + */ + qc_claim_t latest_qc_claim() const; + + /** + * @pre last_final_block_num() <= block_num < current_block_num() + * + * @post returned block_ref has block_num() == block_num + */ + const block_ref& get_block_reference(block_num_type block_num) const; + + /** + * @pre links.front().source_block_num <= block_num <= current_block_num() + * + * @post returned qc_link has source_block_num == block_num + */ + const qc_link& get_qc_link_from(block_num_type block_num) const; + + /** + * @pre this->latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= this->current_block_num() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc + * + * @post returned core_metadata has last_final_block_num <= final_on_strong_qc_block_num <= latest_qc_claim_block_num + * @post returned core_metadata has latest_qc_claim_block_num == most_recent_ancestor_with_qc.block_num + * @post returned core_metadata has final_on_strong_qc_block_num >= this->final_on_strong_qc_block_num + * @post returned core_metadata has last_final_block_num >= this->last_final_block_num() + */ + core_metadata next_metadata(const qc_claim_t& most_recent_ancestor_with_qc) const; + + /** + * @pre current_block.block_num() == this->current_block_num() + * @pre If this->refs.empty() == false, then current_block is the block after the one referenced by this->refs.back() + * @pre this->latest_qc_claim().block_num <= most_recent_ancestor_with_qc.block_num <= this->current_block_num() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc ( (this->latest_qc_claim().block_num == most_recent_ancestor_with_qc.block_num) && most_recent_ancestor_with_qc.is_strong_qc ). When block_num is the same, most_recent_ancestor_with_qc must be stronger than latest_qc_claim() + * + * @post returned core has current_block_num() == this->current_block_num() + 1 + * @post returned core has latest_qc_claim() == most_recent_ancestor_with_qc + * @post returned core has final_on_strong_qc_block_num >= this->final_on_strong_qc_block_num + * @post returned core has last_final_block_num() >= this->last_final_block_num() + */ + finality_core next(const block_ref& current_block, const qc_claim_t& most_recent_ancestor_with_qc) const; +}; + +} /// eosio::chain + +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::finality_core, (links)(refs)(final_on_strong_qc_block_num)) diff --git a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp index f09ff770d1..3cdcc7b2df 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/instant_finality_extension.hpp @@ -2,15 +2,10 @@ #include #include +#include namespace eosio::chain { -struct qc_claim_t { - uint32_t last_qc_block_num; // The block height of the most recent ancestor block that has a QC justification - block_timestamp_type last_qc_block_timestamp; // The block timestamp of the most recent ancestor block that has a QC justification - bool is_last_qc_strong; // Whether the QC for the block referenced by last_qc_block_height is strong or weak. -}; - struct instant_finality_extension : fc::reflect_init { static constexpr uint16_t extension_id() { return 2; } static constexpr bool enforce_unique() { return true; } @@ -33,5 +28,4 @@ struct instant_finality_extension : fc::reflect_init { } /// eosio::chain -FC_REFLECT( eosio::chain::qc_claim_t, (last_qc_block_num)(last_qc_block_timestamp)(is_last_qc_strong) ) FC_REFLECT( eosio::chain::instant_finality_extension, (qc_claim)(new_finalizer_policy)(new_proposer_policy) ) diff --git a/unittests/block_header_state_tests.cpp b/unittests/block_header_state_tests.cpp deleted file mode 100644 index 2133a66802..0000000000 --- a/unittests/block_header_state_tests.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include -#include - -#include - -using namespace eosio::chain; - -BOOST_AUTO_TEST_SUITE(block_header_state_tests) - -// test for block_header_state_core constructor -BOOST_AUTO_TEST_CASE(block_header_state_core_constructor_test) -{ - // verifies members are constructed correctly - block_header_state_core bhs_core1(1, 2, 3); - BOOST_REQUIRE_EQUAL(bhs_core1.last_final_block_num, 1u); - BOOST_REQUIRE_EQUAL(*bhs_core1.final_on_strong_qc_block_num, 2u); - BOOST_REQUIRE_EQUAL(*bhs_core1.last_qc_block_num, 3u); - - // verifies optional arguments work as expected - block_header_state_core bhs_core2(10, std::nullopt, {}); - BOOST_REQUIRE_EQUAL(bhs_core2.last_final_block_num, 10u); - BOOST_REQUIRE(!bhs_core2.final_on_strong_qc_block_num.has_value()); - BOOST_REQUIRE(!bhs_core2.last_qc_block_num.has_value()); -} - -// comprehensive state transition test -BOOST_AUTO_TEST_CASE(block_header_state_core_state_transition_test) -{ - constexpr auto old_last_final_block_num = 1u; - constexpr auto old_final_on_strong_qc_block_num = 2u; - constexpr auto old_last_qc_block_num = 3u; - const block_timestamp_type old_last_qc_block_timestamp(3); - block_header_state_core old_bhs_core(old_last_final_block_num, old_final_on_strong_qc_block_num, old_last_qc_block_num); - - // verifies the state is kept the same when old last_final_block_num - // and new last_final_block_num are the same - for (bool is_last_qc_strong: { true, false }) { - auto new_bhs_core = old_bhs_core.next({old_last_qc_block_num, old_last_qc_block_timestamp, is_last_qc_strong}); - BOOST_REQUIRE_EQUAL(new_bhs_core.last_final_block_num, old_bhs_core.last_final_block_num); - BOOST_REQUIRE_EQUAL(*new_bhs_core.final_on_strong_qc_block_num, *old_bhs_core.final_on_strong_qc_block_num); - BOOST_REQUIRE_EQUAL(*new_bhs_core.last_qc_block_num, *old_bhs_core.last_qc_block_num); - BOOST_REQUIRE(new_bhs_core.last_qc_block_timestamp == old_bhs_core.last_qc_block_timestamp); - } - - // verifies state cannot be transitioned to a smaller last_qc_block_num - for (bool is_last_qc_strong: { true, false }) { - BOOST_REQUIRE_THROW(old_bhs_core.next({old_last_qc_block_num - 1, old_last_qc_block_timestamp, is_last_qc_strong}), - block_validate_exception); - } - - // verifies state transition works when is_last_qc_strong is true - constexpr auto input_last_qc_block_num = 4u; - const block_timestamp_type input_last_qc_block_timestamp(4); - auto new_bhs_core = old_bhs_core.next({input_last_qc_block_num, input_last_qc_block_timestamp, true}); - // old final_on_strong_qc block became final - BOOST_REQUIRE_EQUAL(new_bhs_core.last_final_block_num, old_final_on_strong_qc_block_num); - // old last_qc block became final_on_strong_qc block - BOOST_REQUIRE_EQUAL(*new_bhs_core.final_on_strong_qc_block_num, old_last_qc_block_num); - // new last_qc_block_num is the same as input - BOOST_REQUIRE_EQUAL(*new_bhs_core.last_qc_block_num, input_last_qc_block_num); - BOOST_REQUIRE(new_bhs_core.last_qc_block_timestamp == input_last_qc_block_timestamp); - - // verifies state transition works when is_last_qc_strong is false - new_bhs_core = old_bhs_core.next({input_last_qc_block_num, input_last_qc_block_timestamp, false}); - // last_final_block_num should not change - BOOST_REQUIRE_EQUAL(new_bhs_core.last_final_block_num, old_last_final_block_num); - // new final_on_strong_qc_block_num should not be present - BOOST_REQUIRE(!new_bhs_core.final_on_strong_qc_block_num.has_value()); - // new last_qc_block_num is the same as input - BOOST_REQUIRE_EQUAL(*new_bhs_core.last_qc_block_num, input_last_qc_block_num); - BOOST_REQUIRE(new_bhs_core.last_qc_block_timestamp == input_last_qc_block_timestamp); -} - -// A test to demonstrate 3-chain state transitions from the first -// block after hotstuff activation -BOOST_AUTO_TEST_CASE(block_header_state_core_3_chain_transition_test) -{ - // block2: initial setup - constexpr auto block2_last_final_block_num = 1u; - block_header_state_core block2_bhs_core(block2_last_final_block_num, {}, {}); - - // block2 --> block3 - constexpr auto block3_input_last_qc_block_num = 2u; - const block_timestamp_type block3_input_last_qc_block_timestamp(2); - auto block3_bhs_core = block2_bhs_core.next({block3_input_last_qc_block_num, block3_input_last_qc_block_timestamp, true}); - // last_final_block_num should be the same as old one - BOOST_REQUIRE_EQUAL(block3_bhs_core.last_final_block_num, block2_last_final_block_num); - // final_on_strong_qc_block_num should be same as old one - BOOST_REQUIRE(!block3_bhs_core.final_on_strong_qc_block_num.has_value()); - // new last_qc_block_num is the same as input - BOOST_REQUIRE_EQUAL(*block3_bhs_core.last_qc_block_num, block3_input_last_qc_block_num); - BOOST_REQUIRE(block3_bhs_core.last_qc_block_timestamp == block3_input_last_qc_block_timestamp); - auto block3_last_qc_block_num = *block3_bhs_core.last_qc_block_num; - - // block3 --> block4 - constexpr auto block4_input_last_qc_block_num = 3u; - const block_timestamp_type block4_input_last_qc_block_timestamp(3); - auto block4_bhs_core = block3_bhs_core.next({block4_input_last_qc_block_num, block4_input_last_qc_block_timestamp, true}); - // last_final_block_num should not change - BOOST_REQUIRE_EQUAL(block4_bhs_core.last_final_block_num, block2_last_final_block_num); - // final_on_strong_qc_block_num should be block3's last_qc_block_num - BOOST_REQUIRE_EQUAL(*block4_bhs_core.final_on_strong_qc_block_num, block3_last_qc_block_num); - // new last_qc_block_num is the same as input - BOOST_REQUIRE_EQUAL(*block4_bhs_core.last_qc_block_num, block4_input_last_qc_block_num); - BOOST_REQUIRE(block4_bhs_core.last_qc_block_timestamp == block4_input_last_qc_block_timestamp); - auto block4_final_on_strong_qc_block_num = *block4_bhs_core.final_on_strong_qc_block_num; - auto block4_last_qc_block_num = *block4_bhs_core.last_qc_block_num; - - // block4 --> block5 - constexpr auto block5_input_last_qc_block_num = 4u; - const block_timestamp_type block5_input_last_qc_block_timestamp(4); - auto block5_bhs_core = block4_bhs_core.next({block5_input_last_qc_block_num, block5_input_last_qc_block_timestamp, true}); - // last_final_block_num should have a new value - BOOST_REQUIRE_EQUAL(block5_bhs_core.last_final_block_num, block4_final_on_strong_qc_block_num); - // final_on_strong_qc_block_num should be block4's last_qc_block_num - BOOST_REQUIRE_EQUAL(*block5_bhs_core.final_on_strong_qc_block_num, block4_last_qc_block_num); - // new last_qc_block_num is the same as input - BOOST_REQUIRE_EQUAL(*block5_bhs_core.last_qc_block_num, block5_input_last_qc_block_num); - BOOST_REQUIRE(block5_bhs_core.last_qc_block_timestamp == block5_input_last_qc_block_timestamp); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/block_header_tests.cpp b/unittests/block_header_tests.cpp index 6277803266..ed2c051591 100644 --- a/unittests/block_header_tests.cpp +++ b/unittests/block_header_tests.cpp @@ -1,5 +1,4 @@ #include - #include using namespace eosio::chain; @@ -19,12 +18,12 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) { block_header header; constexpr uint32_t last_qc_block_num {0}; - constexpr bool is_last_qc_strong {false}; + constexpr bool is_last_strong_qc {false}; emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, block_timestamp_type(0), is_last_qc_strong}, + fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_last_strong_qc}, std::optional{}, std::shared_ptr{}} ) ); @@ -32,8 +31,8 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) BOOST_REQUIRE( !!ext ); const auto& if_extension = std::get(*ext); - BOOST_REQUIRE_EQUAL( if_extension.qc_claim.last_qc_block_num, last_qc_block_num ); - BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_last_qc_strong, is_last_qc_strong ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_strong_qc, is_last_strong_qc ); BOOST_REQUIRE( !if_extension.new_finalizer_policy ); BOOST_REQUIRE( !if_extension.new_proposer_policy ); } @@ -46,7 +45,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_uniqueness_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_claim_t{0, block_timestamp_type(0), false}, {std::nullopt}, + fc::raw::pack( instant_finality_extension{qc_claim_t{0, false}, {std::nullopt}, std::shared_ptr{}} ) ); @@ -61,7 +60,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_uniqueness_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_claim_t{100, block_timestamp_type(100), true}, new_finalizer_policy, new_proposer_policy} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{100, true}, new_finalizer_policy, new_proposer_policy} ) ); BOOST_CHECK_THROW(header.validate_and_extract_header_extensions(), invalid_block_header_extension); @@ -72,8 +71,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) { block_header header; constexpr uint32_t last_qc_block_num {10}; - const block_timestamp_type last_qc_block_timestamp(10); - constexpr bool is_last_qc_strong {true}; + constexpr bool is_strong_qc {true}; std::vector finalizers { {"test description", 50, fc::crypto::blslib::bls_public_key{"PUB_BLS_MPPeebAPxt/ibL2XPuZVGpADjGn+YEVPPoYmTZeBD6Ok2E19M8SnmDGSdZBf2qwSuJim+8H83EsTpEn3OiStWBiFeJYfVRLlEsZuSF0SYYwtVteY48n+KeE1IWzlSAkSyBqiGA==" }} }; finalizer_policy new_finalizer_policy; @@ -86,7 +84,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, last_qc_block_timestamp, is_last_qc_strong}, new_finalizer_policy, new_proposer_policy} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_strong_qc}, new_finalizer_policy, new_proposer_policy} ) ); std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); @@ -94,9 +92,8 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) const auto& if_extension = std::get(*ext); - BOOST_REQUIRE_EQUAL( if_extension.qc_claim.last_qc_block_num, last_qc_block_num ); - BOOST_REQUIRE( if_extension.qc_claim.last_qc_block_timestamp == last_qc_block_timestamp ); - BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_last_qc_strong, is_last_qc_strong ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.block_num, last_qc_block_num ); + BOOST_REQUIRE_EQUAL( if_extension.qc_claim.is_strong_qc, is_strong_qc ); BOOST_REQUIRE( !!if_extension.new_finalizer_policy ); BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->generation, 1u); diff --git a/unittests/finality_tests.cpp b/unittests/finality_tests.cpp index 6ee03919fb..132dc8c26a 100644 --- a/unittests/finality_tests.cpp +++ b/unittests/finality_tests.cpp @@ -239,17 +239,14 @@ BOOST_AUTO_TEST_CASE(one_weak_vote) { try { cluster.produce_and_push_block(); cluster.process_node1_vote(); - // Even though the vote makes a strong QC for the current block, - // its final_on_strong_qc_block_num is nullopt due to previous QC was weak. - // Cannot advance LIB. - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node0_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()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); cluster.produce_and_push_block(); cluster.process_node1_vote(); @@ -283,15 +280,14 @@ BOOST_AUTO_TEST_CASE(two_weak_votes) { try { 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.node0_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.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // now a 3 chain has formed. BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); @@ -311,10 +307,7 @@ BOOST_AUTO_TEST_CASE(intertwined_weak_votes) { try { // Strong vote cluster.produce_and_push_block(); cluster.process_node1_vote(); - // Even though the vote makes a strong QC for the current block, - // its final_on_strong_qc_block_num is nullopt due to previous QC was weak. - // Cannot advance LIB. - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + BOOST_REQUIRE(cluster.node0_lib_advancing()); // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); @@ -323,13 +316,13 @@ BOOST_AUTO_TEST_CASE(intertwined_weak_votes) { try { 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.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // Strong vote 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.node0_lib_advancing()); // no 2-chain was formed as prior block was not a strong block BOOST_REQUIRE(!cluster.node1_lib_advancing()); @@ -337,7 +330,7 @@ BOOST_AUTO_TEST_CASE(intertwined_weak_votes) { try { cluster.produce_and_push_block(); cluster.process_node1_vote(); BOOST_REQUIRE(cluster.node0_lib_advancing()); - BOOST_REQUIRE(!cluster.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); BOOST_REQUIRE(cluster.produce_blocks_and_verify_lib_advancing()); } FC_LOG_AND_RETHROW() } @@ -361,15 +354,13 @@ BOOST_AUTO_TEST_CASE(weak_delayed_lost_vote) { try { // A strong vote cluster.produce_and_push_block(); cluster.process_node1_vote(); - // The vote makes a strong QC, but final_on_strong_qc is null. - // Do not advance LIB - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + 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.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // The delayed vote arrives cluster.process_node1_vote(delayed_index); @@ -414,15 +405,13 @@ BOOST_AUTO_TEST_CASE(delayed_strong_weak_lost_vote) { try { // A strong vote cluster.produce_and_push_block(); cluster.process_node1_vote(); - // The vote makes a strong QC, but final_on_strong_qc is null. - // LIB did not advance. - BOOST_REQUIRE(!cluster.node0_lib_advancing()); + 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.node1_lib_advancing()); + BOOST_REQUIRE(cluster.node1_lib_advancing()); // The delayed vote arrives cluster.process_node1_vote(delayed_index);