diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3293976672..3552f219f4 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -699,7 +699,7 @@ struct building_block { qc_data->qc_claim }; - assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input), + assembled_block::assembled_block_if ab{bb.active_producer_authority, bb.parent.next(bhs_input), std::move(bb.pending_trx_metas), std::move(bb.pending_trx_receipts), qc_data ? std::move(qc_data->qc) : std::optional{}}; @@ -1605,9 +1605,9 @@ struct controller_impl { auto set_finalizer_defaults = [&](auto& forkdb) -> void { auto lib = forkdb.root(); my_finalizers.set_default_safety_information( - finalizer::safety_information{ .last_vote_range_start = block_timestamp_type(0), - .last_vote = {}, - .lock = finalizer::proposal_ref(lib) }); + finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), + .last_vote = {}, + .lock = proposal_ref(lib->id(), lib->timestamp()) }); }; fork_db.apply_if(set_finalizer_defaults); } else { @@ -1615,9 +1615,9 @@ struct controller_impl { auto set_finalizer_defaults = [&](auto& forkdb) -> void { auto lib = forkdb.root(); my_finalizers.set_default_safety_information( - finalizer::safety_information{ .last_vote_range_start = block_timestamp_type(0), - .last_vote = {}, - .lock = finalizer::proposal_ref(lib) }); + finalizer_safety_information{ .last_vote_range_start = block_timestamp_type(0), + .last_vote = {}, + .lock = proposal_ref(lib->id(), lib->timestamp()) }); }; fork_db.apply_if(set_finalizer_defaults); } @@ -2834,7 +2834,7 @@ struct controller_impl { log_irreversible(); } - fork_db.apply_if([&](auto& forkdb) { create_and_send_vote_msg(forkdb.chain_head, forkdb); }); + fork_db.apply_if([&](auto& forkdb) { create_and_send_vote_msg(forkdb.chain_head); }); // TODO: temp transition to instant-finality, happens immediately after block with new_finalizer_policy auto transition = [&](auto& forkdb) -> bool { @@ -2864,9 +2864,9 @@ struct controller_impl { auto start_block = forkdb.chain_head; auto lib_block = forkdb.chain_head; my_finalizers.set_default_safety_information( - finalizer::safety_information{ .last_vote_range_start = block_timestamp_type(0), - .last_vote = finalizer::proposal_ref(start_block), - .lock = finalizer::proposal_ref(lib_block) }); + 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()) }); } log_irreversible(); @@ -3155,7 +3155,7 @@ struct controller_impl { 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) { + void create_and_send_vote_msg(const block_state_ptr& bsp) { auto finalizer_digest = bsp->compute_finalizer_digest(); // Each finalizer configured on the node which is present in the active finalizer policy @@ -3165,7 +3165,7 @@ struct controller_impl { // off the main thread. net_plugin is fine for this to be emitted from any thread. // Just need to update the comment in net_plugin my_finalizers.maybe_vote( - *bsp->active_finalizer_policy, bsp, fork_db, finalizer_digest, [&](const vote_message& vote) { + *bsp->active_finalizer_policy, bsp, finalizer_digest, [&](const vote_message& vote) { // net plugin subscribed to this signal. it will broadcast the vote message // on receiving the signal emit(voted_block, vote); diff --git a/libraries/chain/finality_core.cpp b/libraries/chain/finality_core.cpp index 5a85bdda2a..2568ea2037 100644 --- a/libraries/chain/finality_core.cpp +++ b/libraries/chain/finality_core.cpp @@ -78,6 +78,29 @@ qc_claim_t finality_core::latest_qc_claim() const return qc_claim_t{.block_num = links.back().target_block_num, .is_strong_qc = links.back().is_link_strong}; } +/** + * @pre all finality_core invariants + * @post same + * @returns timestamp of latest qc_claim made by the core + */ +block_time_type finality_core::latest_qc_block_timestamp() const { + return get_block_reference(links.back().target_block_num).timestamp; +} + +/** + * @pre all finality_core invariants + * @post same + * @returns boolean indicating whether `id` is an ancestor of this block + */ +bool finality_core::extends(const block_id_type& id) const { + uint32_t block_num = block_header::num_from_id(id); + if (block_num >= last_final_block_num() && block_num < current_block_num()) { + const block_ref& ref = get_block_reference(block_num); + return ref.block_id == id; + } + return false; +} + /** * @pre last_final_block_num() <= block_num < current_block_num() * @@ -260,10 +283,8 @@ finality_core finality_core::next(const block_ref& current_block, const qc_claim 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)); + next_core.links = { links.cbegin() + links_index, links.cend() }; assert(next_core.last_final_block_num() == new_last_final_block_num); // Satisfied by choice of links_index. @@ -297,11 +318,8 @@ finality_core finality_core::next(const block_ref& current_block, const qc_claim 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)); - + next_core.refs = {refs.cbegin() + refs_index, refs.cend()}; assert(refs.empty() || (next_core.refs.front().block_num() == new_last_final_block_num)); // Satisfied by choice of refs_index. // Add new block reference diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index b1fa2397ac..5c5e4e29b9 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -147,7 +147,7 @@ namespace eosio::chain { {} template - fork_database_t::~fork_database_t() = default; + fork_database_t::~fork_database_t() = default; // close is performed in fork_database::~fork_database() template void fork_database_t::open( const std::filesystem::path& fork_db_file, validator_t& validator ) { diff --git a/libraries/chain/hotstuff/finalizer.cpp b/libraries/chain/hotstuff/finalizer.cpp index b20fbda074..a0d17dca67 100644 --- a/libraries/chain/hotstuff/finalizer.cpp +++ b/libraries/chain/hotstuff/finalizer.cpp @@ -5,98 +5,75 @@ namespace eosio::chain { // ---------------------------------------------------------------------------------------- -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{}; +finalizer::vote_result finalizer::decide_vote(const finality_core& core, const block_id_type &proposal_id, + const block_timestamp_type proposal_timestamp) { + vote_result res; - // a branch always contains consecutive block numbers, starting with the highest - uint32_t first = branch[0]->block_num(); - uint32_t dist = first - *block_num; - return dist < branch.size() ? branch[dist] : block_state_ptr{}; -} - -// ---------------------------------------------------------------------------------------- -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; }); -} - -// ---------------------------------------------------------------------------------------- -finalizer::vote_decision finalizer::decide_vote(const block_state_ptr& proposal, const fork_database_if_t& fork_db) { - bool safety_check = false; - bool liveness_check = false; - - bool monotony_check = !fsi.last_vote || proposal->timestamp() > fsi.last_vote.timestamp; - // !fsi.last_vote means we have never voted on a proposal, so the protocol feature just activated and we can proceed + res.monotony_check = fsi.last_vote.empty() || proposal_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 (!monotony_check) { - dlog("monotony check failed for proposal ${p}, cannot vote", ("p", proposal->id())); - return vote_decision::no_vote; + if (!res.monotony_check) { + dlog("monotony check failed for proposal ${p}, cannot vote", ("p", proposal_id)); + return res; } - 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 // than the height of the proposal I'm locked on. // This allows restoration of liveness if a replica is locked on a stale proposal // ------------------------------------------------------------------------------- - liveness_check = proposal->last_qc_block_timestamp() > fsi.lock.timestamp; + res.liveness_check = core.latest_qc_block_timestamp() > fsi.lock.timestamp; - if (!liveness_check) { + if (!res.liveness_check) { // Safety check : check if this proposal extends the proposal we're locked on - p_branch = fork_db.fetch_full_branch(proposal->id()); - safety_check = extends(*p_branch, fsi.lock.id); + res.safety_check = core.extends(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. // ------------------------------------------------------------------------------------- - liveness_check = false; - safety_check = false; + res.liveness_check = false; + res.safety_check = false; } + bool can_vote = res.liveness_check || res.safety_check; + dlog("liveness_check=${l}, safety_check=${s}, monotony_check=${m}, can vote=${can_vote}", + ("l",res.liveness_check)("s",res.safety_check)("m",res.monotony_check)("can_vote",can_vote)); + // 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 // ----------------------------------------------------------------------------------- - vote_decision decision = vote_decision::no_vote; - - if (liveness_check || safety_check) { - auto [p_start, p_end] = std::make_pair(proposal->last_qc_block_timestamp(), proposal->timestamp()); + if (can_vote) { + auto [p_start, p_end] = std::make_pair(core.latest_qc_block_timestamp(), proposal_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) { - if (!p_branch) - p_branch = fork_db.fetch_full_branch(proposal->id()); - voting_strong = extends(*p_branch, fsi.last_vote.id); + 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); } - fsi.last_vote = proposal_ref(proposal); + fsi.last_vote = proposal_ref(proposal_id, proposal_timestamp); fsi.last_vote_range_start = p_start; - if (!p_branch) - p_branch = fork_db.fetch_full_branch(proposal->id()); - auto bsp_final_on_strong_qc = get_block_by_num(*p_branch, proposal->final_on_strong_qc_block_num()); - if (voting_strong && bsp_final_on_strong_qc && bsp_final_on_strong_qc->timestamp() > fsi.lock.timestamp) - fsi.lock = proposal_ref(bsp_final_on_strong_qc); + 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); - decision = voting_strong ? vote_decision::strong_vote : vote_decision::weak_vote; - } 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())); - dlog("last_qc_block_num=${lqc}", ("lqc", proposal->last_qc_block_num())); + res.decision = voting_strong ? vote_decision::strong_vote : vote_decision::weak_vote; } 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; + ("l",res.liveness_check)("s",res.safety_check)("m",res.monotony_check)("can_vote",can_vote)("v", res.decision)); + return res; } // ---------------------------------------------------------------------------------------- -std::optional finalizer::maybe_vote(const bls_public_key& pub_key, const block_state_ptr& p, - const digest_type& digest, const fork_database_if_t& fork_db) { - finalizer::vote_decision decision = decide_vote(p, fork_db); +std::optional finalizer::maybe_vote(const bls_public_key& pub_key, + const block_header_state_ptr& p, + const digest_type& digest) { + finalizer::vote_decision decision = decide_vote(p->core, p->id(), p->timestamp()).decision; if (decision == vote_decision::strong_vote || decision == vote_decision::weak_vote) { bls_signature sig; if (decision == vote_decision::weak_vote) { diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 16f0150cb5..49a2701c2d 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -59,6 +59,8 @@ struct block_state : public block_header_state { // block_header_state provi 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; + + protocol_feature_activation_set_ptr get_activated_protocol_features() const { return block_header_state::activated_protocol_features; } 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; } diff --git a/libraries/chain/include/eosio/chain/block_timestamp.hpp b/libraries/chain/include/eosio/chain/block_timestamp.hpp index a20f609ddc..0c5305ebb7 100644 --- a/libraries/chain/include/eosio/chain/block_timestamp.hpp +++ b/libraries/chain/include/eosio/chain/block_timestamp.hpp @@ -17,7 +17,10 @@ namespace eosio { namespace chain { template class block_timestamp { public: - explicit block_timestamp( uint32_t s=0 ) :slot(s){} + + block_timestamp() : slot(0) {} + + explicit block_timestamp( uint32_t s ) :slot(s){} block_timestamp(const fc::time_point& t) { set_time_point(t); @@ -51,12 +54,11 @@ namespace eosio { namespace chain { set_time_point(t); } - bool operator > ( const block_timestamp& t )const { return slot > t.slot; } - bool operator >=( const block_timestamp& t )const { return slot >= t.slot; } - bool operator < ( const block_timestamp& t )const { return slot < t.slot; } - bool operator <=( const block_timestamp& t )const { return slot <= t.slot; } - bool operator ==( const block_timestamp& t )const { return slot == t.slot; } - bool operator !=( const block_timestamp& t )const { return slot != t.slot; } + // needed, otherwise deleted because of above version of operator=() + block_timestamp& operator=(const block_timestamp&) = default; + + auto operator<=>(const block_timestamp&) const = default; + uint32_t slot; private: @@ -76,6 +78,13 @@ namespace eosio { namespace chain { } } /// eosio::chain +namespace std { + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::block_timestamp_type& t) { + os << "tstamp(" << t.slot << ")"; + return os; + } +} + #include FC_REFLECT(eosio::chain::block_timestamp_type, (slot)) diff --git a/libraries/chain/include/eosio/chain/finality_core.hpp b/libraries/chain/include/eosio/chain/finality_core.hpp index 81e142c399..53b83746e2 100644 --- a/libraries/chain/include/eosio/chain/finality_core.hpp +++ b/libraries/chain/include/eosio/chain/finality_core.hpp @@ -13,7 +13,11 @@ struct block_ref block_id_type block_id; block_time_type timestamp; + bool empty() const { return block_id.empty(); } block_num_type block_num() const; // Extract from block_id. + + auto operator<=>(const block_ref&) const = default; + bool operator==(const block_ref& o) const = default; }; struct qc_link @@ -92,6 +96,20 @@ struct finality_core */ qc_claim_t latest_qc_claim() const; + /** + * @pre all finality_core invariants + * @post same + * @returns timestamp of latest qc_claim made by the core + */ + block_time_type latest_qc_block_timestamp() const; + + /** + * @pre all finality_core invariants + * @post same + * @returns boolean indicating whether `id` is an ancestor of this block + */ + bool extends(const block_id_type& id) const; + /** * @pre last_final_block_num() <= block_num < current_block_num() * @@ -121,7 +139,10 @@ struct finality_core * @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() + * @pre this->latest_qc_claim() <= most_recent_ancestor_with_qc (i.e. + * 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 @@ -133,6 +154,31 @@ struct finality_core } /// eosio::chain +// ----------------------------------------------------------------------------- +namespace std { + // define std ostream output so we can use BOOST_CHECK_EQUAL in tests + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::block_ref& br) { + os << "block_ref(" << br.block_id << ", " << br.timestamp << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::qc_link& l) { + os << "qc_link(" << l.source_block_num << ", " << l.target_block_num << ", " << l.is_link_strong << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::qc_claim_t& c) { + os << "qc_claim_t(" << c.block_num << ", " << c.is_strong_qc << ")"; + return os; + } + + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::core_metadata& cm) { + os << "core_metadata(" << cm.last_final_block_num << ", " << cm.final_on_strong_qc_block_num << + ", " << cm.latest_qc_claim_block_num << ")"; + return os; + } +} + 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) ) diff --git a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp index d7750a99d9..4aec61c1f0 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/finalizer.hpp @@ -1,13 +1,8 @@ #pragma once -#include -#include -#include +#include "eosio/chain/block_state.hpp" #include #include -#include -#include #include -#include // ------------------------------------------------------------------------------------------- // this file defines the classes: @@ -32,71 +27,48 @@ namespace eosio::chain { // ---------------------------------------------------------------------------------------- - struct finalizer { - enum class vote_decision { strong_vote, weak_vote, no_vote }; - - struct proposal_ref { - block_id_type id; - block_timestamp_type timestamp; - - proposal_ref() = default; - - template - explicit proposal_ref(const BSP& p) : - id(p->id()), - timestamp(p->timestamp()) - {} - - proposal_ref(const block_id_type& id, block_timestamp_type t) : - id(id), timestamp(t) - {} + using proposal_ref = block_ref; - void reset() { - id = block_id_type(); - timestamp = block_timestamp_type(); - } - - bool empty() const { return id.empty(); } - - explicit operator bool() const { return !id.empty(); } - - auto operator==(const proposal_ref& o) const { - return id == o.id && timestamp == o.timestamp; - } - }; + // ---------------------------------------------------------------------------------------- + struct finalizer_safety_information { + block_timestamp_type last_vote_range_start; + proposal_ref last_vote; + proposal_ref lock; - struct safety_information { - block_timestamp_type last_vote_range_start; - proposal_ref last_vote; - proposal_ref lock; + static constexpr uint64_t magic = 0x5AFE11115AFE1111ull; - static constexpr uint64_t magic = 0x5AFE11115AFE1111ull; + static finalizer_safety_information unset_fsi() { return {}; } - static safety_information unset_fsi() { return {block_timestamp_type(), {}, {}}; } + auto operator==(const finalizer_safety_information& o) const { + return last_vote_range_start == o.last_vote_range_start && + last_vote == o.last_vote && + lock == o.lock; + } + }; - auto operator==(const safety_information& o) const { - return last_vote_range_start == o.last_vote_range_start && - last_vote == o.last_vote && - lock == o.lock; - } + // ---------------------------------------------------------------------------------------- + struct finalizer { + enum class vote_decision { no_vote, strong_vote, weak_vote }; + struct vote_result { + vote_decision decision {vote_decision::no_vote}; + bool safety_check {false}; + bool liveness_check {false}; + bool monotony_check {false}; }; - bls_private_key priv_key; - safety_information fsi; + bls_private_key priv_key; + finalizer_safety_information fsi; - private: - 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); + vote_result decide_vote(const finality_core& core, const block_id_type &id, + const block_timestamp_type timestamp); - public: - std::optional maybe_vote(const bls_public_key& pub_key, const block_state_ptr& bsp, - const digest_type& digest, const fork_database_if_t& fork_db); + std::optional maybe_vote(const bls_public_key& pub_key, const block_header_state_ptr& bhsp, + const digest_type& digest); }; // ---------------------------------------------------------------------------------------- struct my_finalizers_t { - using fsi_t = finalizer::safety_information; + using fsi_t = finalizer_safety_information; using fsi_map = std::map; const block_timestamp_type t_startup; // nodeos startup time, used for default safety_information @@ -109,8 +81,7 @@ namespace eosio::chain { template void maybe_vote(const finalizer_policy& fin_pol, - const block_state_ptr& bsp, - const fork_database_if_t& fork_db, + const block_header_state_ptr& bhsp, const digest_type& digest, F&& process_vote) { std::vector votes; @@ -119,7 +90,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, bsp, digest, fork_db); + std::optional vote_msg = it->second.maybe_vote(it->first, bhsp, digest); if (vote_msg) votes.push_back(std::move(*vote_msg)); } @@ -148,17 +119,24 @@ namespace eosio::chain { } namespace std { - inline std::ostream& operator<<(std::ostream& os, const eosio::chain::finalizer::proposal_ref& r) { - os << "proposal_ref(id(" << r.id.str() << "), tstamp(" << r.timestamp.slot << "))"; + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::finalizer_safety_information& fsi) { + os << "fsi(" << fsi.last_vote_range_start.slot << ", " << fsi.last_vote << ", " << fsi.lock << ")"; return os; } - inline std::ostream& operator<<(std::ostream& os, const eosio::chain::finalizer::safety_information& fsi) { - os << "fsi(" << fsi.last_vote_range_start.slot << ", " << fsi.last_vote << ", " << fsi.lock << ")"; + inline std::ostream& operator<<(std::ostream& os, const eosio::chain::finalizer::vote_result& vr) { + os << "vote_result(\""; + using vote_decision = eosio::chain::finalizer::vote_decision; + switch(vr.decision) { + case vote_decision::strong_vote: os << "strong_vote"; break; + case vote_decision::weak_vote: os << "weak_vote"; break; + case vote_decision::no_vote: os << "no_vote"; break; + } + os << "\", monotony_check(" << vr.monotony_check << "), liveness_check(" << vr.liveness_check << + "), safety_check(" << vr.safety_check<< "))"; return os; } } -FC_REFLECT(eosio::chain::finalizer::proposal_ref, (id)(timestamp)) +FC_REFLECT(eosio::chain::finalizer_safety_information, (last_vote_range_start)(last_vote)(lock)) 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/libfc/include/fc/crypto/elliptic.hpp b/libraries/libfc/include/fc/crypto/elliptic.hpp index c16e645a4a..cf1fef4305 100644 --- a/libraries/libfc/include/fc/crypto/elliptic.hpp +++ b/libraries/libfc/include/fc/crypto/elliptic.hpp @@ -127,13 +127,8 @@ namespace fc { { return a.get_secret() == b.get_secret(); } - inline friend bool operator!=( const private_key& a, const private_key& b ) - { - return a.get_secret() != b.get_secret(); - } - inline friend bool operator<( const private_key& a, const private_key& b ) - { - return a.get_secret() < b.get_secret(); + inline friend std::strong_ordering operator<=>( const private_key& a, const private_key& b ) { + return a.get_secret() <=> b.get_secret(); } unsigned int fingerprint() const { return get_public_key().fingerprint(); } diff --git a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp index 1ada2a9d64..44a6cb9608 100644 --- a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp +++ b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp @@ -117,13 +117,8 @@ namespace fc { { return a.get_secret() == b.get_secret(); } - inline friend bool operator!=( const private_key& a, const private_key& b ) - { - return a.get_secret() != b.get_secret(); - } - inline friend bool operator<( const private_key& a, const private_key& b ) - { - return a.get_secret() < b.get_secret(); + inline friend std::strong_ordering operator<=>( const private_key& a, const private_key& b ) { + return a.get_secret() <=> b.get_secret(); } private: diff --git a/libraries/libfc/include/fc/crypto/sha256.hpp b/libraries/libfc/include/fc/crypto/sha256.hpp index 4e9c8615b3..39042a9cc3 100644 --- a/libraries/libfc/include/fc/crypto/sha256.hpp +++ b/libraries/libfc/include/fc/crypto/sha256.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -72,12 +73,10 @@ class sha256 } friend sha256 operator << ( const sha256& h1, uint32_t i ); friend sha256 operator >> ( const sha256& h1, uint32_t i ); - friend bool operator == ( const sha256& h1, const sha256& h2 ); - friend bool operator != ( const sha256& h1, const sha256& h2 ); friend sha256 operator ^ ( const sha256& h1, const sha256& h2 ); - friend bool operator >= ( const sha256& h1, const sha256& h2 ); - friend bool operator > ( const sha256& h1, const sha256& h2 ); - friend bool operator < ( const sha256& h1, const sha256& h2 ); + + friend bool operator == ( const sha256& h1, const sha256& h2 ); + friend std::strong_ordering operator <=> ( const sha256& h1, const sha256& h2 ); uint32_t pop_count()const { @@ -130,6 +129,11 @@ namespace std } }; + inline std::ostream& operator<<(std::ostream& os, const fc::sha256& r) { + os << "sha256(" << r.str() << ")"; + return os; + } + } namespace boost diff --git a/libraries/libfc/src/crypto/sha256.cpp b/libraries/libfc/src/crypto/sha256.cpp index 5e6f226d8c..0a1f05a5f4 100644 --- a/libraries/libfc/src/crypto/sha256.cpp +++ b/libraries/libfc/src/crypto/sha256.cpp @@ -86,17 +86,8 @@ namespace fc { result._hash[3] = h1._hash[3] ^ h2._hash[3]; return result; } - bool operator >= ( const sha256& h1, const sha256& h2 ) { - return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; - } - bool operator > ( const sha256& h1, const sha256& h2 ) { - return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; - } - bool operator < ( const sha256& h1, const sha256& h2 ) { - return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; - } - bool operator != ( const sha256& h1, const sha256& h2 ) { - return !(h1 == h2); + std::strong_ordering operator <=> ( const sha256& h1, const sha256& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) <=> 0; } bool operator == ( const sha256& h1, const sha256& h2 ) { // idea to not use memcmp, from: diff --git a/unittests/finalizer_tests.cpp b/unittests/finalizer_tests.cpp index 27948c2e7d..dfe8ce6580 100644 --- a/unittests/finalizer_tests.cpp +++ b/unittests/finalizer_tests.cpp @@ -1,15 +1,16 @@ #include + #include #include #include +#include using namespace eosio; using namespace eosio::chain; using namespace eosio::testing; -using fsi_t = finalizer::safety_information; -using proposal_ref = finalizer::proposal_ref; -using tstamp = block_timestamp_type; +using tstamp = block_timestamp_type; +using fsi_t = finalizer_safety_information; struct bls_keys_t { bls_private_key privkey; @@ -24,19 +25,31 @@ struct bls_keys_t { } }; -std::vector create_random_fsi(size_t count) { - std::vector res; +template +std::vector create_random_fsi(size_t count) { + std::vector res; res.reserve(count); for (size_t i=0; i create_proposal_refs(size_t count) { + std::vector res; + res.reserve(count); + for (size_t i=0; i create_keys(size_t count) { std::vector res; res.reserve(count); @@ -57,8 +70,8 @@ bls_pub_priv_key_map_t create_local_finalizers(const std::vector& ke return res; } -template -void set_fsi(my_finalizers_t& fset, const std::vector& keys, const std::vector& fsi) { +template +void set_fsi(my_finalizers_t& fset, const std::vector& keys, const FSI_VEC& fsi) { ((fset.set_fsi(keys[I].pubkey, fsi[I])), ...); } @@ -67,10 +80,11 @@ BOOST_AUTO_TEST_SUITE(finalizer_tests) BOOST_AUTO_TEST_CASE( basic_finalizer_safety_file_io ) try { fc::temp_directory tempdir; auto safety_file_path = tempdir.path() / "finalizers" / "safety.dat"; + auto proposals { create_proposal_refs(10) }; - fsi_t fsi { tstamp(0), - proposal_ref{sha256::hash((const char *)"vote"), tstamp(7)}, - proposal_ref{sha256::hash((const char *)"lock"), tstamp(3)} }; + fsi_t fsi { .last_vote_range_start = tstamp(0), + .last_vote = proposals[6], + .lock = proposals[2] }; bls_keys_t k("alice"_n); bls_pub_priv_key_map_t local_finalizers = { { k.pubkey_str, k.privkey_str } }; @@ -96,11 +110,52 @@ BOOST_AUTO_TEST_CASE( basic_finalizer_safety_file_io ) try { } FC_LOG_AND_RETHROW() +BOOST_AUTO_TEST_CASE( corrupt_finalizer_safety_file ) try { + fc::temp_directory tempdir; + auto safety_file_path = tempdir.path() / "finalizers" / "safety.dat"; + auto proposals { create_proposal_refs(10) }; + + fsi_t fsi { .last_vote_range_start = tstamp(0), + .last_vote = proposals[6], + .lock = proposals[2] }; + + bls_keys_t k("alice"_n); + bls_pub_priv_key_map_t local_finalizers = { { k.pubkey_str, k.privkey_str } }; + + { + my_finalizers_t fset{.t_startup = block_timestamp_type{}, .persist_file_path = safety_file_path}; + fset.set_keys(local_finalizers); + + fset.set_fsi(k.pubkey, fsi); + fset.save_finalizer_safety_info(); + + // at this point we have saved the finalizer safety file + // corrupt it, so we can check that we throw an exception when reading it later. + + fc::datastream f; + f.set_file_path(safety_file_path); + f.open(fc::cfile::truncate_rw_mode); + size_t junk_data = 0xf0f0f0f0f0f0f0f0ull; + fc::raw::pack(f, junk_data); + } + + { + my_finalizers_t fset{.t_startup = block_timestamp_type{}, .persist_file_path = safety_file_path}; + BOOST_REQUIRE_THROW(fset.set_keys(local_finalizers), // that's when the finalizer safety file is read + finalizer_safety_exception); + + // make sure the safety info for our finalizer that we saved above is restored correctly + BOOST_CHECK_NE(fset.get_fsi(k.pubkey), fsi); + BOOST_CHECK_EQUAL(fset.get_fsi(k.pubkey), fsi_t()); + } + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_CASE( finalizer_safety_file_io ) try { fc::temp_directory tempdir; auto safety_file_path = tempdir.path() / "finalizers" / "safety.dat"; - std::vector fsi = create_random_fsi(10); + std::vector fsi = create_random_fsi(10); std::vector keys = create_keys(10); { @@ -108,7 +163,7 @@ BOOST_AUTO_TEST_CASE( finalizer_safety_file_io ) try { bls_pub_priv_key_map_t local_finalizers = create_local_finalizers<1, 3, 5, 6>(keys); fset.set_keys(local_finalizers); - set_fsi<1, 3, 5, 6>(fset, keys, fsi); + set_fsi(fset, keys, fsi); fset.save_finalizer_safety_info(); // at this point we have saved the finalizer safety file, containing a specific fsi for finalizers <1, 3, 5, 6> @@ -155,7 +210,4 @@ BOOST_AUTO_TEST_CASE( finalizer_safety_file_io ) try { } FC_LOG_AND_RETHROW() - - - BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/finalizer_vote_tests.cpp b/unittests/finalizer_vote_tests.cpp new file mode 100644 index 0000000000..999f4241cf --- /dev/null +++ b/unittests/finalizer_vote_tests.cpp @@ -0,0 +1,332 @@ +#include +#include + +#include +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; + +using bs = eosio::chain::block_state; +using bsp = eosio::chain::block_state_ptr; +using bhs = eosio::chain::block_header_state; +using bhsp = eosio::chain::block_header_state_ptr; +using vote_decision = finalizer::vote_decision; +using vote_result = finalizer::vote_result; +using tstamp = block_timestamp_type; +using fsi_t = finalizer_safety_information; + +// --------------------------------------------------------------------------------------- +struct bls_keys_t { + bls_private_key privkey; + bls_public_key pubkey; + std::string privkey_str; + std::string pubkey_str; + + bls_keys_t(name n) { + bls_signature pop; + std::tie(privkey, pubkey, pop) = eosio::testing::get_bls_key(n); + std::tie(privkey_str, pubkey_str) = std::pair{ privkey.to_string(), pubkey.to_string() }; + } +}; + +// --------------------------------------------------------------------------------------- +inline block_id_type calc_id(block_id_type id, uint32_t block_number) { + id._hash[0] &= 0xffffffff00000000; + id._hash[0] += fc::endian_reverse_u32(block_number); + return id; +} + +// --------------------------------------------------------------------------------------- +struct proposal_t { + uint32_t block_number; + std::string proposer_name; + block_timestamp_type block_timestamp; + + proposal_t(uint32_t block_number, const char* proposer, std::optional timestamp = {}) : + block_number(block_number), proposer_name(proposer), block_timestamp(timestamp ? *timestamp : block_number) + {} + + const std::string& proposer() const { return proposer_name; } + block_timestamp_type timestamp() const { return block_timestamp; } + uint32_t block_num() const { return block_number; } + + block_id_type calculate_id() const + { + std::string id_str = proposer_name + std::to_string(block_number); + return calc_id(fc::sha256::hash(id_str.c_str()), block_number); + } + + explicit operator block_ref() const { + return block_ref{calculate_id(), timestamp()}; + } +}; + +// --------------------------------------------------------------------------------------- +bsp make_bsp(const proposal_t& p, const bsp& previous, finalizer_policy_ptr finpol, + std::optional claim = {}) { + auto makeit = [](bhs &&h) { + bs new_bs; + dynamic_cast(new_bs) = std::move(h); + return std::make_shared(std::move(new_bs)); + }; + + if (p.block_num() == 0) { + // special case of genesis block + block_ref ref{calc_id(fc::sha256::hash("genesis"), 0), block_timestamp_type{0}}; + bhs new_bhs { ref.block_id, block_header{ref.timestamp}, {}, + finality_core::create_core_for_genesis_block(0), {}, {}, std::move(finpol) }; + return makeit(std::move(new_bhs)); + } + + assert(claim); + block_ref ref{previous->id(), previous->timestamp()}; + bhs new_bhs { p.calculate_id(), block_header{p.block_timestamp, {}, {}, previous->id()}, {}, previous->core.next(ref, *claim), + {}, {}, std::move(finpol) }; + return makeit(std::move(new_bhs)); +} + +// --------------------------------------------------------------------------------------- +// simulates one finalizer voting on its own proposals "n0", and other proposals received +// from the network. +struct simulator_t { + using core = finality_core; + + bls_keys_t keys; + finalizer my_finalizer; + fork_database_if_t forkdb; + finalizer_policy_ptr finpol; + std::vector bsp_vec; + + struct result { + bsp new_bsp; + vote_result vote; + + qc_claim_t new_claim() const { + if (vote.decision == vote_decision::no_vote) + return new_bsp->core.latest_qc_claim(); + return { new_bsp->block_num(), vote.decision == vote_decision::strong_vote }; + } + }; + + simulator_t() : + keys("alice"_n), + my_finalizer(keys.privkey) { + + finalizer_policy fin_policy; + fin_policy.threshold = 0; + fin_policy.finalizers.push_back({"n0", 1, keys.pubkey}); + finpol = std::make_shared(fin_policy); + + auto genesis = make_bsp(proposal_t{0, "n0"}, bsp(), finpol); + bsp_vec.push_back(genesis); + forkdb.reset_root(*genesis); + + block_ref genesis_ref(genesis->id(), genesis->timestamp()); + my_finalizer.fsi = fsi_t{block_timestamp_type(0), genesis_ref, genesis_ref}; + } + + vote_result vote(const bhsp& p) { + auto vote_res = my_finalizer.decide_vote(p->core, p->id(), p->timestamp()); + return vote_res; + } + + vote_result propose(const proposal_t& p, std::optional _claim = {}) { + bsp h = forkdb.head(); + qc_claim_t old_claim = _claim ? *_claim : h->core.latest_qc_claim(); + bsp new_bsp = make_bsp(p, h, finpol, old_claim); + bsp_vec.push_back(new_bsp); + auto v = vote(new_bsp); + return v; + } + + result add(const proposal_t& p, std::optional _claim = {}, const bsp& parent = {}) { + bsp h = parent ? parent : forkdb.head(); + qc_claim_t old_claim = _claim ? *_claim : h->core.latest_qc_claim(); + bsp new_bsp = make_bsp(p, h, finpol, old_claim); + bsp_vec.push_back(new_bsp); + forkdb.add(new_bsp, mark_valid_t::yes, ignore_duplicate_t::no); + + auto v = vote(new_bsp); + return { new_bsp, v }; + } +}; + +BOOST_AUTO_TEST_SUITE(finalizer_vote_tests) + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_basic ) try { + simulator_t sim; + // this proposal verifies all properties and extends genesis => expect strong vote + auto res = sim.add({1, "n0"}); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); +} FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_no_vote_if_finalizer_safety_lock_empty ) try { + simulator_t sim; + sim.my_finalizer.fsi.lock = {}; // force lock empty... finalizer should not vote + auto res = sim.add({1, "n0"}); + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); +} FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_normal_vote_sequence ) try { + simulator_t sim; + qc_claim_t new_claim { 0, true }; + for (uint32_t i=1; i<10; ++i) { + auto res = sim.add({i, "n0"}, new_claim); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(new_claim, res.new_bsp->core.latest_qc_claim()); + new_claim = { res.new_bsp->block_num(), res.vote.decision == vote_decision::strong_vote }; + + auto lib { res.new_bsp->core.last_final_block_num() }; + BOOST_CHECK_EQUAL(lib, i <= 2 ? 0 : i - 3); + + auto final_on_strong_qc { res.new_bsp->core.final_on_strong_qc_block_num }; + BOOST_CHECK_EQUAL(final_on_strong_qc, i <= 1 ? 0 : i - 2); + } +} FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_monotony_check ) try { + simulator_t sim; + + auto res = sim.add({1, "n0", 1}); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + + auto res2 = sim.add({2, "n0", 1}); + BOOST_CHECK_EQUAL(res2.vote.monotony_check, false); + BOOST_CHECK(res2.vote.decision == vote_decision::no_vote); // use same timestamp as previous proposal => should not vote + +} FC_LOG_AND_RETHROW() + + +// --------------------------------------------------------------------------------------- +BOOST_AUTO_TEST_CASE( decide_vote_liveness_and_safety_check ) try { + simulator_t sim; + qc_claim_t new_claim { 0, true }; + for (uint32_t i=1; i<10; ++i) { + auto res = sim.add({i, "n0", i}, new_claim); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(new_claim, res.new_bsp->core.latest_qc_claim()); + new_claim = res.new_claim(); + + auto lib { res.new_bsp->core.last_final_block_num() }; + BOOST_CHECK_EQUAL(lib, i <= 2 ? 0 : i - 3); + + auto final_on_strong_qc { res.new_bsp->core.final_on_strong_qc_block_num }; + BOOST_CHECK_EQUAL(final_on_strong_qc, i <= 1 ? 0 : i - 2); + + if (i > 2) + BOOST_CHECK_EQUAL(sim.my_finalizer.fsi.lock.block_id, sim.bsp_vec[i-2]->id()); + } + + // we just issued proposal #9. Verify we are locked on proposal #7 and our last_vote is #9 + BOOST_CHECK_EQUAL(sim.my_finalizer.fsi.lock.block_id, sim.bsp_vec[7]->id()); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 9u); + + // proposal #6 from "n0" is final (although "n1" may not know it yet). + // proposal #7 would be final if it receives a strong QC + + // let's have "n1" build on proposal #6. Default will use timestamp(7) so we will fail the monotony check + auto res = sim.add({7, "n1"}, {}, sim.bsp_vec[6]); + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); + BOOST_CHECK_EQUAL(res.vote.monotony_check, false); + + // let's vote for a couple more proposals, and finally when we'll reach timestamp 10 the + // monotony check will pass (both liveness and safety check should still fail) + // ------------------------------------------------------------------------------------ + res = sim.add({8, "n1"}, {}, res.new_bsp); + BOOST_CHECK_EQUAL(res.vote.monotony_check, false); + + res = sim.add({9, "n1"}, {}, res.new_bsp); + BOOST_CHECK_EQUAL(res.vote.monotony_check, false); + + res = sim.add({10, "n1"}, {}, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, false); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); + + // No matter how long we keep voting on this branch without a new qc claim, we will never achieve + // liveness or safety again + // ---------------------------------------------------------------------------------------------- + for (uint32_t i=11; i<20; ++i) { + res = sim.add({i, "n1"}, {}, res.new_bsp); + + BOOST_CHECK(res.vote.decision == vote_decision::no_vote); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, false); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); + } + + // Now suppose we receive a qc in a block that was created in the "n0" branch, for example the qc from + // proposal 8. We can get it from sim.bsp_vec[9]->core.latest_qc_claim(). + // liveness should be restored, because core.latest_qc_block_timestamp() > fsi.lock.timestamp + // --------------------------------------------------------------------------------------------------- + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 9u); + new_claim = sim.bsp_vec[9]->core.latest_qc_claim(); + res = sim.add({20, "n1"}, new_claim, res.new_bsp); + + BOOST_CHECK(res.vote.decision == vote_decision::weak_vote); // because !time_range_disjoint and fsi.last_vote == 9 + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 20u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + + new_claim = res.new_claim(); + res = sim.add({21, "n1"}, new_claim, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); // because core.extends(fsi.last_vote.block_id); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 21u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + + // this new proposal we just voted strong on was just building on proposal #6 and we had not advanced + // the core until the last proposal which provided a new qc_claim_t. + // as a result we now have a final_on_strong_qc = 5 (because the vote on 20 was weak) + // -------------------------------------------------------------------------------------------------- + auto final_on_strong_qc = res.new_bsp->core.final_on_strong_qc_block_num; + BOOST_CHECK_EQUAL(final_on_strong_qc, 5u); + + // Our finalizer should still be locked on the initial proposal 7 (we have not updated our lock because + // `(final_on_strong_qc_block_ref.timestamp > fsi.lock.timestamp)` is false + // ---------------------------------------------------------------------------------------------------- + BOOST_CHECK_EQUAL(sim.my_finalizer.fsi.lock.block_id, sim.bsp_vec[7]->id()); + + // this new strong vote will finally advance the final_on_strong_qc thanks to the chain + // weak 20 - strong 21 (meaning that if we get a strong QC on 22, 20 becomes final, so the core of + // 22 has a final_on_strong_qc = 20. + // ----------------------------------------------------------------------------------------------- + new_claim = res.new_claim(); + res = sim.add({22, "n1"}, new_claim, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 22u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + final_on_strong_qc = res.new_bsp->core.final_on_strong_qc_block_num; + BOOST_CHECK_EQUAL(final_on_strong_qc, 20u); + BOOST_CHECK_EQUAL(res.new_bsp->core.last_final_block_num(), 4u); + + // OK, add one proposal + strong vote. This should finally move lib to 20 + // ---------------------------------------------------------------------- + new_claim = res.new_claim(); + res = sim.add({23, "n1"}, new_claim, res.new_bsp); + BOOST_CHECK(res.vote.decision == vote_decision::strong_vote); + BOOST_CHECK_EQUAL(block_header::num_from_id(sim.my_finalizer.fsi.last_vote.block_id), 23u); + BOOST_CHECK_EQUAL(res.vote.monotony_check, true); + BOOST_CHECK_EQUAL(res.vote.liveness_check, true); + BOOST_CHECK_EQUAL(res.vote.safety_check, false); // because liveness_check is true, safety is not checked. + final_on_strong_qc = res.new_bsp->core.final_on_strong_qc_block_num; + BOOST_CHECK_EQUAL(final_on_strong_qc, 21u); + BOOST_CHECK_EQUAL(res.new_bsp->core.last_final_block_num(), 20u); + +} FC_LOG_AND_RETHROW() + + +BOOST_AUTO_TEST_SUITE_END()