diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index e0f17e9a4a..3677d1d48c 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -67,6 +67,29 @@ const vector& block_header_state::get_new_protocol_feature_activati return detail::get_new_protocol_feature_activations(header_exts); } +// The last proposed finalizer policy if none proposed or pending is the active finalizer policy +const finalizer_policy& block_header_state::get_last_proposed_finalizer_policy() const { + if (!finalizer_policies.empty()) { + for (auto ritr = finalizer_policies.rbegin(); ritr != finalizer_policies.rend(); ++ritr) { + if (ritr->second.state == finalizer_policy_tracker::state_t::proposed) + return *ritr->second.policy; + } + return *finalizer_policies.rbegin()->second.policy; + } + return *active_finalizer_policy; +} + +// The last proposed proposer policy, if none proposed then the active proposer policy +const proposer_policy& block_header_state::get_last_proposed_proposer_policy() const { + if (proposer_policies.empty()) { + assert(active_proposer_policy); + return *active_proposer_policy; + } + auto it = proposer_policies.rbegin(); + assert(it != proposer_policies.rend()); + return *it->second; +} + // ------------------------------------------------------------------------------------------------- // `finish_next` updates the next `block_header_state` according to the contents of the // header extensions (either new protocol_features or instant_finality_extension) applicable to this @@ -103,10 +126,14 @@ void finish_next(const block_header_state& prev, } } - if (if_ext.new_proposer_policy) { + std::optional new_proposer_policy; + if (if_ext.new_proposer_policy_diff) { + new_proposer_policy = prev.get_last_proposed_proposer_policy().apply_diff(*if_ext.new_proposer_policy_diff); + } + if (new_proposer_policy) { // called when assembling the block - next_header_state.proposer_policies[if_ext.new_proposer_policy->active_time] = - std::move(if_ext.new_proposer_policy); + next_header_state.proposer_policies[new_proposer_policy->active_time] = + std::make_shared(std::move(*new_proposer_policy)); } // finality_core @@ -131,6 +158,8 @@ void finish_next(const block_header_state& prev, // --------------------------------------------------------------------------- next_header_state.finalizer_policies = prev.finalizer_policies; } else { + auto next_block_num = next_header_state.block_num(); + while (it != prev.finalizer_policies.end() && it->first <= lib) { const finalizer_policy_tracker& tracker = it->second; if (tracker.state == finalizer_policy_tracker::state_t::pending) { @@ -139,11 +168,33 @@ void finish_next(const block_header_state& prev, next_header_state.active_finalizer_policy.reset(new finalizer_policy(*tracker.policy)); } else { assert(tracker.state == finalizer_policy_tracker::state_t::proposed); - // block where finalizer_policy was proposed became final. The finalizer policy will - // become active when next block becomes final. + + // The block where finalizer_policy was proposed has became final. The finalizer + // policy will become active when `next_block_num` becomes final. + // + // So `tracker.policy` should become `pending` at `next_block_num`. + // + // Either insert a new `finalizer_policy_tracker` value, or update the `pending` + // policy if there is already one at `next_block_num` (which can happen when + // finality advances multiple block at a time, and more than one policy move from + // proposed to pending. + // + // Since we iterate finalizer_policies which is a multimap sorted by block number, + // the last one we add will be for the highest block number, which is what we want. // --------------------------------------------------------------------------------- - finalizer_policy_tracker t { finalizer_policy_tracker::state_t::pending, tracker.policy }; - next_header_state.finalizer_policies.emplace(next_header_state.block_num(), std::move(t)); + auto range = next_header_state.finalizer_policies.equal_range(next_block_num); + auto itr = range.first; + for (; itr != range.second; ++itr) { + if (itr->second.state == finalizer_policy_tracker::state_t::pending) { + itr->second.policy = tracker.policy; + break; + } + } + if (itr == range.second) { + // there wasn't already a pending one for `next_block_num`, add a new tracker + finalizer_policy_tracker t { finalizer_policy_tracker::state_t::pending, tracker.policy }; + next_header_state.finalizer_policies.emplace(next_block_num, std::move(t)); + } } ++it; } @@ -156,20 +207,21 @@ void finish_next(const block_header_state& prev, } } - if (if_ext.new_finalizer_policy) { + if (if_ext.new_finalizer_policy_diff) { + finalizer_policy new_finalizer_policy = prev.get_last_proposed_finalizer_policy().apply_diff(*if_ext.new_finalizer_policy_diff); + // a new `finalizer_policy` was proposed in the previous block, and is present in the previous // block's header extensions. // Add this new proposal to the `finalizer_policies` multimap which tracks the in-flight proposals, // increment the generation number, and log that proposal (debug level). // ------------------------------------------------------------------------------------------------ - dlog("New finalizer policy proposed in block ${id}: ${pol}", - ("id", prev.block_id)("pol", *if_ext.new_finalizer_policy)); - next_header_state.finalizer_policy_generation = if_ext.new_finalizer_policy->generation; + dlog("New finalizer policy proposed in block ${id}..: ${pol}", + ("id", prev.block_id.str().substr(8,16))("pol", new_finalizer_policy)); + next_header_state.finalizer_policy_generation = new_finalizer_policy.generation; next_header_state.finalizer_policies.emplace( next_header_state.block_num(), finalizer_policy_tracker{finalizer_policy_tracker::state_t::proposed, - std::make_shared(std::move(*if_ext.new_finalizer_policy))}); - + std::make_shared(std::move(new_finalizer_policy))}); } else { next_header_state.finalizer_policy_generation = prev.finalizer_policy_generation; } @@ -205,9 +257,17 @@ block_header_state block_header_state::next(block_header_state_input& input) con // finality extension // ------------------ + std::optional new_finalizer_policy_diff; + if (input.new_finalizer_policy) { + new_finalizer_policy_diff = get_last_proposed_finalizer_policy().create_diff(*input.new_finalizer_policy); + } + std::optional new_proposer_policy_diff; + if (input.new_proposer_policy) { + new_proposer_policy_diff = get_last_proposed_proposer_policy().create_diff(*input.new_proposer_policy); + } instant_finality_extension new_if_ext { input.most_recent_ancestor_with_qc, - std::move(input.new_finalizer_policy), - std::move(input.new_proposer_policy) }; + std::move(new_finalizer_policy_diff), + std::move(new_proposer_policy_diff) }; uint16_t if_ext_id = instant_finality_extension::extension_id(); emplace_extension(next_header_state.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext)); @@ -285,5 +345,37 @@ block_header_state block_header_state::next(const signed_block_header& h, valida return next_header_state; } +// ------------------------------------------------------------------------------- +// do some sanity checks on block_header_state +// ------------------------------------------------------------------------------- +bool block_header_state::sanity_check() const { + // check that we have at most *one* proposed and *one* pending `finalizer_policy` + // for any block number + // ----------------------------------------------------------------------------- + block_num_type block_num(0); + bool pending{false}, proposed{false}; + + for (auto it = finalizer_policies.begin(); it != finalizer_policies.end(); ++it) { + if (block_num != it->first) { + pending = proposed = false; + block_num = it->first; + } + const auto& tracker = it->second; + if (tracker.state == finalizer_policy_tracker::state_t::proposed) { + if (proposed) + return false; + else + proposed = true; + } + if (tracker.state == finalizer_policy_tracker::state_t::pending) { + if (pending) + return false; + else + pending = true; + } + } + return true; +} + } // namespace eosio::chain diff --git a/libraries/chain/block_header_state_legacy.cpp b/libraries/chain/block_header_state_legacy.cpp index a47ca2282f..01cbe4113e 100644 --- a/libraries/chain/block_header_state_legacy.cpp +++ b/libraries/chain/block_header_state_legacy.cpp @@ -220,8 +220,10 @@ namespace eosio::chain { // set current block_num as qc_claim.last_qc_block_num in the IF extension qc_claim_t initial_if_claim { .block_num = block_num, .is_strong_qc = false }; + finalizer_policy no_policy; + auto new_fin_policy_diff = no_policy.create_diff(*new_finalizer_policy); emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack(instant_finality_extension{ initial_if_claim, std::move(new_finalizer_policy), {} })); + fc::raw::pack(instant_finality_extension{ initial_if_claim, std::move(new_fin_policy_diff), {} })); } else if (qc_claim) { emplace_extension(h.header_extensions, instant_finality_extension::extension_id(), fc::raw::pack(instant_finality_extension{ *qc_claim, {}, {} })); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 5d84d2f60a..f33d5345f1 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -72,8 +72,8 @@ block_state_ptr block_state::create_if_genesis_block(const block_state_legacy& b assert(bsp.block->contains_header_extension(instant_finality_extension::extension_id())); // required by transition mechanism instant_finality_extension if_ext = bsp.block->extract_header_extension(); - assert(if_ext.new_finalizer_policy); // required by transition mechanism - result.active_finalizer_policy = std::make_shared(*if_ext.new_finalizer_policy); + assert(if_ext.new_finalizer_policy_diff); // required by transition mechanism + result.active_finalizer_policy = std::make_shared(finalizer_policy{}.apply_diff(std::move(*if_ext.new_finalizer_policy_diff))); result.active_proposer_policy = std::make_shared(); result.active_proposer_policy->active_time = bsp.timestamp(); result.active_proposer_policy->proposer_schedule = bsp.active_schedule; @@ -337,11 +337,28 @@ finality_data_t block_state::get_finality_data() { if (!base_digest) { base_digest = compute_base_digest(); // cache it } + + // Check if there is any proposed finalizer policy in the block + std::optional proposed_finalizer_policy; + if (is_savanna_genesis_block()) { + // For Genesis Block, use the active finalizer policy which was proposed in the block. + proposed_finalizer_policy = *active_finalizer_policy; + } else { + auto range = finalizer_policies.equal_range(block_num()); + for (auto itr = range.first; itr != range.second; ++itr) { + if (itr->second.state == finalizer_policy_tracker::state_t::proposed) { + proposed_finalizer_policy = *itr->second.policy; + break; + } + } + } + return { // other fields take the default values set by finality_data_t definition .active_finalizer_policy_generation = active_finalizer_policy->generation, - .action_mroot = action_mroot, - .base_digest = *base_digest + .action_mroot = action_mroot, + .base_digest = *base_digest, + .proposed_finalizer_policy = std::move(proposed_finalizer_policy) }; } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 56f892e6cc..3d2b0ee868 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -706,7 +706,7 @@ struct building_block { assembled_block assemble_block(boost::asio::io_context& ioc, const protocol_feature_set& pfs, fork_database& fork_db, - std::unique_ptr new_proposer_policy, + std::optional new_proposer_policy, std::optional new_finalizer_policy, bool validating, std::optional validating_qc_data, @@ -2942,6 +2942,8 @@ struct controller_impl { handle_exception(wrapper); } + // this code is hit if an exception was thrown, and handled by `handle_exception` + // ------------------------------------------------------------------------------ if (!trx->is_transient()) { dmlog_applied_transaction(trace); emit( applied_transaction, std::tie(trace, trx->packed_trx()), __FILE__, __LINE__ ); @@ -3162,13 +3164,13 @@ struct controller_impl { resource_limits.process_block_usage(bb.block_num()); // Any proposer policy? - auto process_new_proposer_policy = [&](auto&) -> std::unique_ptr { - std::unique_ptr new_proposer_policy; + auto process_new_proposer_policy = [&](auto&) -> std::optional { + std::optional new_proposer_policy; const auto& gpo = db.get(); if (gpo.proposed_schedule_block_num) { std::optional version = pending->get_next_proposer_schedule_version(gpo.proposed_schedule.producers); if (version) { - new_proposer_policy = std::make_unique(); + new_proposer_policy.emplace(); new_proposer_policy->active_time = detail::get_next_next_round_block_time(bb.timestamp()); new_proposer_policy->proposer_schedule = producer_authority_schedule::from_shared(gpo.proposed_schedule); new_proposer_policy->proposer_schedule.version = *version; @@ -3184,7 +3186,7 @@ struct controller_impl { } return new_proposer_policy; }; - auto new_proposer_policy = apply_s>(chain_head, process_new_proposer_policy); + auto new_proposer_policy = apply_s>(chain_head, process_new_proposer_policy); // Any finalizer policy? std::optional new_finalizer_policy = std::nullopt; @@ -5109,6 +5111,10 @@ block_state_legacy_ptr controller::head_block_state_legacy()const { }); } +bool controller::head_sanity_check()const { + return apply(my->chain_head, [](const auto& head) { return head->sanity_check(); }); +} + const signed_block_ptr& controller::head_block()const { return my->chain_head.block(); } @@ -5281,6 +5287,8 @@ int64_t controller_impl::set_proposed_producers( vector prod if (producers.empty()) return -1; // INSTANT_FINALITY depends on DISALLOW_EMPTY_PRODUCER_SCHEDULE + EOS_ASSERT(producers.size() <= config::max_proposers, wasm_execution_error, + "Producer schedule exceeds the maximum proposer count for this chain"); assert(pending); producer_authority_schedule sch; @@ -5299,6 +5307,8 @@ int64_t controller_impl::set_proposed_producers( vector prod } int64_t controller_impl::set_proposed_producers_legacy( vector producers ) { + EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, + "Producer schedule exceeds the maximum producer count for this chain"); const auto& gpo = db.get(); auto cur_block_num = chain_head.block_num() + 1; diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 7fd43afe32..d978754dc1 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -46,8 +46,8 @@ struct finality_digest_data_v1 { // ------------------------------------------------------------------------------------------ struct finalizer_policy_tracker { enum class state_t { proposed = 0, pending }; - state_t state; - finalizer_policy_ptr policy; + state_t state; + finalizer_policy_ptr policy; }; struct building_block_input { @@ -61,7 +61,7 @@ struct building_block_input { // this struct can be extracted from a building block struct block_header_state_input : public building_block_input { digest_type transaction_mroot; // Comes from std::get(building_block::trx_mroot_or_receipt_digests) - std::shared_ptr new_proposer_policy; // Comes from building_block::new_proposer_policy + std::optional new_proposer_policy; // Comes from building_block::new_proposer_policy std::optional new_finalizer_policy; // Comes from building_block::new_finalizer_policy qc_claim_t most_recent_ancestor_with_qc; // Comes from traversing branch from parent and calling get_best_qc() digest_type finality_mroot_claim; @@ -119,6 +119,10 @@ struct block_header_state { digest_type compute_base_digest() const; digest_type compute_finality_digest() const; + // Returns true if the block is a Savanna Genesis Block. + // This method is applicable to any transition block which is re-classified as a Savanna block. + bool is_savanna_genesis_block() const { return core.is_genesis_block_num(block_num()); } + // Returns true if the block is a Proper Savanna Block bool is_proper_svnn_block() const { return header.is_proper_svnn_block(); } @@ -127,8 +131,13 @@ struct block_header_state { return qc_claim > core.latest_qc_claim(); } + bool sanity_check() const; // does sanity check of block_header_state, returns true if successful + const vector& get_new_protocol_feature_activations() const; const producer_authority& get_scheduled_producer(block_timestamp_type t) const; + + const finalizer_policy& get_last_proposed_finalizer_policy() const; + const proposer_policy& get_last_proposed_proposer_policy() const; }; using block_header_state_ptr = std::shared_ptr; diff --git a/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp b/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp index 722125ff26..5c2c6d05e9 100644 --- a/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state_legacy.hpp @@ -148,6 +148,8 @@ struct block_header_state_legacy : public detail::block_header_state_legacy_comm void sign( const signer_callback_type& signer ); void verify_signee()const; + bool sanity_check() const { return true; } + const vector& get_new_protocol_feature_activations()const; }; diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 35cfcf098e..04039e864a 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -64,6 +64,7 @@ struct finality_data_t { uint32_t active_finalizer_policy_generation{0}; digest_type action_mroot{}; digest_type base_digest{}; + std::optional proposed_finalizer_policy; // finalizer policy, if proposed in the block }; struct block_state : public block_header_state { // block_header_state provides parent link @@ -176,5 +177,5 @@ using block_state_pair = std::pair, blo // not exporting pending_qc or valid_qc FC_REFLECT( eosio::chain::valid_t::finality_leaf_node_t, (major_version)(minor_version)(block_num)(finality_digest)(action_mroot) ) FC_REFLECT( eosio::chain::valid_t, (validation_tree)(validation_mroots)) -FC_REFLECT( eosio::chain::finality_data_t, (major_version)(minor_version)(active_finalizer_policy_generation)(action_mroot)(base_digest)) +FC_REFLECT( eosio::chain::finality_data_t, (major_version)(minor_version)(active_finalizer_policy_generation)(action_mroot)(base_digest)(proposed_finalizer_policy) ) FC_REFLECT_DERIVED( eosio::chain::block_state, (eosio::chain::block_header_state), (block)(strong_digest)(weak_digest)(pending_qc)(valid)(validated) ) diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 74af7b59ca..41d663f2eb 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -126,7 +126,8 @@ const static uint32_t default_abi_serializer_max_time_us = 15*1000; ///< defau * The number of sequential blocks produced by a single producer */ const static int producer_repetitions = 12; -const static int max_producers = 125; +const static int max_producers = 125; // pre-savanna producer (proposer) limit +const static int max_proposers = 64*1024; // savanna proposer (producer) limit const static size_t maximum_tracked_dpos_confirmations = 1024; ///< static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); @@ -134,7 +135,7 @@ static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1 /** * Maximum number of finalizers in the finalizer set */ -const static size_t max_finalizers = 64*1024; +const static size_t max_finalizers = 64*1024; // largest allowed finalizer policy diff const static size_t max_finalizer_description_size = 256; /** diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index fe129b8e9a..44d22ede85 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -240,6 +240,7 @@ namespace eosio::chain { account_name head_block_producer()const; const block_header& head_block_header()const; const signed_block_ptr& head_block()const; + bool head_sanity_check()const; // returns nullptr after instant finality enabled block_state_legacy_ptr head_block_state_legacy()const; // returns finality_data associated with chain head for SHiP when in Savanna, diff --git a/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp b/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp index 1fa27d14ac..dc3d03c869 100644 --- a/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp +++ b/libraries/chain/include/eosio/chain/finality/finalizer_policy.hpp @@ -2,13 +2,41 @@ #include #include +#include namespace eosio::chain { + static_assert(std::numeric_limits::max() >= config::max_finalizers - 1); + using finalizers_differ = fc::ordered_diff; + using finalizers_diff_t = finalizers_differ::diff_result; + + struct finalizer_policy_diff { + uint32_t generation = 0; ///< sequentially incrementing version number + uint64_t threshold = 0; ///< vote weight threshold to finalize blocks + finalizers_diff_t finalizers_diff; + }; + struct finalizer_policy { uint32_t generation = 0; ///< sequentially incrementing version number uint64_t threshold = 0; ///< vote weight threshold to finalize blocks - std::vector finalizers; ///< Instant Finality voter set + std::vector finalizers; ///< Instant Finality voter set + + finalizer_policy_diff create_diff(const finalizer_policy& target) const { + return {.generation = target.generation, + .threshold = target.threshold, + .finalizers_diff = finalizers_differ::diff(finalizers, target.finalizers)}; + } + + template + requires std::same_as, finalizer_policy_diff> + [[nodiscard]] finalizer_policy apply_diff(X&& diff) const { + finalizer_policy result; + result.generation = diff.generation; + result.threshold = diff.threshold; + auto copy = finalizers; + result.finalizers = finalizers_differ::apply_diff(std::move(copy), std::forward(diff).finalizers_diff); + return result; + } // max accumulated weak weight before becoming weak_final uint64_t max_weak_sum_before_weak_final() const { @@ -23,7 +51,10 @@ namespace eosio::chain { }; using finalizer_policy_ptr = std::shared_ptr; + using finalizer_policy_diff_ptr = std::shared_ptr; } /// eosio::chain FC_REFLECT( eosio::chain::finalizer_policy, (generation)(threshold)(finalizers) ) +FC_REFLECT( eosio::chain::finalizers_diff_t, (remove_indexes)(insert_indexes) ) +FC_REFLECT( eosio::chain::finalizer_policy_diff, (generation)(threshold)(finalizers_diff) ) diff --git a/libraries/chain/include/eosio/chain/finality/instant_finality_extension.hpp b/libraries/chain/include/eosio/chain/finality/instant_finality_extension.hpp index 3b78770a5b..e3c893dc9c 100644 --- a/libraries/chain/include/eosio/chain/finality/instant_finality_extension.hpp +++ b/libraries/chain/include/eosio/chain/finality/instant_finality_extension.hpp @@ -12,11 +12,11 @@ struct instant_finality_extension : fc::reflect_init { instant_finality_extension() = default; instant_finality_extension(qc_claim_t qc_claim, - std::optional new_finalizer_policy, - std::shared_ptr new_proposer_policy) : + std::optional&& new_finalizer_policy_diff, + std::optional&& new_proposer_policy_diff) : qc_claim(qc_claim), - new_finalizer_policy(std::move(new_finalizer_policy)), - new_proposer_policy(std::move(new_proposer_policy)) + new_finalizer_policy_diff(std::move(new_finalizer_policy_diff)), + new_proposer_policy_diff(std::move(new_proposer_policy_diff)) {} void reflector_init() const { @@ -25,11 +25,11 @@ struct instant_finality_extension : fc::reflect_init { static_assert( extension_id() == 2, "instant_finality_extension extension id must be 2" ); } - qc_claim_t qc_claim; - std::optional new_finalizer_policy; - std::shared_ptr new_proposer_policy; + qc_claim_t qc_claim; + std::optional new_finalizer_policy_diff; + std::optional new_proposer_policy_diff; }; } /// eosio::chain -FC_REFLECT( eosio::chain::instant_finality_extension, (qc_claim)(new_finalizer_policy)(new_proposer_policy) ) +FC_REFLECT( eosio::chain::instant_finality_extension, (qc_claim)(new_finalizer_policy_diff)(new_proposer_policy_diff) ) diff --git a/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp b/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp index 9c0a10dd5e..a390773885 100644 --- a/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp +++ b/libraries/chain/include/eosio/chain/finality/proposer_policy.hpp @@ -5,16 +5,44 @@ namespace eosio::chain { +static_assert(std::numeric_limits::max() >= config::max_proposers - 1); +using producer_auth_differ = fc::ordered_diff; +using producer_auth_diff_t = producer_auth_differ::diff_result; + +struct proposer_policy_diff { + uint32_t version = 0; ///< sequentially incrementing version number of producer_authority_schedule + block_timestamp_type active_time; // block when schedule will become active + producer_auth_diff_t producer_auth_diff; +}; + struct proposer_policy { - constexpr static uint8_t current_schema_version = 1; - uint8_t schema_version {current_schema_version}; // Useful for light clients, not necessary for nodeos block_timestamp_type active_time; // block when schedule will become active producer_authority_schedule proposer_schedule; + + proposer_policy_diff create_diff(const proposer_policy& target) const { + return {.version = target.proposer_schedule.version, + .active_time = target.active_time, + .producer_auth_diff = producer_auth_differ::diff(proposer_schedule.producers, target.proposer_schedule.producers)}; + } + + template + requires std::same_as, proposer_policy_diff> + [[nodiscard]] proposer_policy apply_diff(X&& diff) const { + proposer_policy result; + result.proposer_schedule.version = diff.version; + result.active_time = diff.active_time; + auto copy = proposer_schedule.producers; + result.proposer_schedule.producers = producer_auth_differ::apply_diff(std::move(copy), + std::forward(diff).producer_auth_diff); + return result; + } }; using proposer_policy_ptr = std::shared_ptr; } /// eosio::chain -FC_REFLECT( eosio::chain::proposer_policy, (schema_version)(active_time)(proposer_schedule) ) +FC_REFLECT( eosio::chain::proposer_policy, (active_time)(proposer_schedule) ) +FC_REFLECT( eosio::chain::producer_auth_diff_t, (remove_indexes)(insert_indexes) ) +FC_REFLECT( eosio::chain::proposer_policy_diff, (version)(active_time)(producer_auth_diff) ) diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 5149421d39..88c5c92540 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -44,7 +44,6 @@ namespace eosio { namespace chain { namespace webassembly { } int64_t set_proposed_producers_common( apply_context& context, vector && producers, bool validate_keys ) { - EOS_ASSERT(producers.size() <= config::max_producers, wasm_execution_error, "Producer schedule exceeds the maximum producer count for this chain"); EOS_ASSERT( producers.size() > 0 || !context.control.is_builtin_activated( builtin_protocol_feature_t::disallow_empty_producer_schedule ), wasm_execution_error, diff --git a/libraries/libfc/include/fc/container/ordered_diff.hpp b/libraries/libfc/include/fc/container/ordered_diff.hpp new file mode 100644 index 0000000000..e2440d6ab9 --- /dev/null +++ b/libraries/libfc/include/fc/container/ordered_diff.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include +#include + +namespace fc { + +/** + * @class ordered_diff + * @brief Provides ability to generate and apply diff of containers of type T + * + * Example use: + * std::vector source = { 'a', 'b', 'f', 'c', 'd' }; + * std::vector target = { 'b', 'f', 'c', 'd', 'e', 'h' }; + * ordered_diff::diff_result diff = ordered_diff::diff(source, target); + * ordered_diff::apply_diff(source, std::move(diff)); + * assert(source == target); + * + * @param T type stored in Container, must provide == + * @param SizeType numeric type used for index into diff_result, for non-unique Containers a larger type may be required + * @param Container container type for ordered diff and for diff_result + */ +template typename Container = std::vector> +requires std::equality_comparable && std::random_access_iterator::iterator> +class ordered_diff { +public: + struct diff_result { + Container remove_indexes; + Container> insert_indexes; + }; + + /// Generate diff_result that when `apply_diff(source, diff_result)` will modify source to be equal to target. + static diff_result diff(const Container& source, const Container& target) { + size_t s = 0; + size_t t = 0; + + diff_result result; + while (s < source.size() || t < target.size()) { + if (s < source.size() && t < target.size()) { + if (source[s] == target[t]) { + // nothing to do, skip over + assert(s <= std::numeric_limits::max()); + assert(t <= std::numeric_limits::max()); + ++s; + ++t; + } else { // not equal + if (s == source.size() - 1 && t == target.size() - 1) { + // both at end, insert target and remove source + assert(s <= std::numeric_limits::max()); + assert(t <= std::numeric_limits::max()); + result.remove_indexes.push_back(s); + result.insert_indexes.emplace_back(t, target[t]); + ++s; + ++t; + } else if (s + 1 < source.size() && t + 1 < target.size() && source[s + 1] == target[t + 1]) { + // misalignment, but next value equal, insert and remove + assert(s <= std::numeric_limits::max()); + assert(t <= std::numeric_limits::max()); + result.remove_indexes.push_back(s); + result.insert_indexes.emplace_back(t, target[t]); + ++s; + ++t; + } else if (t + 1 < target.size() && source[s] == target[t + 1]) { + // source equals next target, insert current target + assert(t <= std::numeric_limits::max()); + result.insert_indexes.emplace_back(t, target[t]); + ++t; + } else { // source[s + 1] == target[t] + // target matches next source, remove current source + assert(s <= std::numeric_limits::max()); + result.remove_indexes.push_back(s); + ++s; + } + } + } else if (s < source.size()) { + // remove extra in source + assert(s <= std::numeric_limits::max()); + result.remove_indexes.push_back(s); + ++s; + } else if (t < target.size()) { + // insert extra in target + assert(t <= std::numeric_limits::max()); + result.insert_indexes.emplace_back(t, target[t]); + ++t; + } + } + + return result; + } + + /// @param diff the diff_result created from diff(source, target), apply_diff(std::move(source), diff_result) => target + /// @param container the source of diff(source, target) to modify using the diff_result to produce original target + /// @return the modified container now equal to original target + template + requires std::same_as, diff_result> + static Container apply_diff(Container&& container, X&& diff) { + // Remove from the source based on diff.remove_indexes + std::ptrdiff_t offset = 0; + for (SizeType index : diff.remove_indexes) { + container.erase(container.begin() + index + offset); + --offset; + } + + // Insert into the source based on diff.insert_indexes + for (auto& [index, value] : diff.insert_indexes) { + container.insert(container.begin() + index, std::move(value)); + } + return container; + } + +}; // class ordered_diff + +} // namespace fc diff --git a/libraries/libfc/test/CMakeLists.txt b/libraries/libfc/test/CMakeLists.txt index 4b079be425..ee728d2f13 100644 --- a/libraries/libfc/test/CMakeLists.txt +++ b/libraries/libfc/test/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable( test_fc test_base64.cpp test_escape_str.cpp test_bls.cpp + test_ordered_diff.cpp main.cpp ) target_link_libraries( test_fc fc ) diff --git a/libraries/libfc/test/test_ordered_diff.cpp b/libraries/libfc/test/test_ordered_diff.cpp new file mode 100644 index 0000000000..6477c3f8a3 --- /dev/null +++ b/libraries/libfc/test/test_ordered_diff.cpp @@ -0,0 +1,200 @@ +#include + +#include +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(ordered_diff_tests) + +BOOST_AUTO_TEST_CASE(ordered_diff_test) try { + using namespace std; + + { // Basic case + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = {'a', 'c', 'e', 'f'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Basic case, deque + using ordered_deque_char_diff = ordered_diff; + deque source = {'a', 'x', 'c', 'd', 'e'}; + deque target = {'z', 'c', 'y', 'f'}; + auto result = ordered_deque_char_diff::diff(source, target); + source = ordered_deque_char_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Empty vectors + vector source; + vector target; + ordered_diff::diff_result result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // All elements removed + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // All elements inserted + vector source; + vector target = {'a', 'b', 'c', 'd', 'e'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // No change + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = source; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Mix of removals and inserts + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = {'a', 'c', 'e', 'f', 'g', 'h'}; + ordered_diff::diff_result result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Mix of removals and inserts + vector source = {1, 2, 3, 4, 5}; + vector target = {3, 4, 6, 2, 0}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Complete change + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = {'f', 'g', 'h', 'i'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // Diff order + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = {'e', 'd', 'c', 'b', 'a'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // shift left + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = {'b', 'c', 'd', 'e', 'f'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // shift right + vector source = {'a', 'b', 'c', 'd', 'e'}; + vector target = {'z', 'a', 'b', 'c', 'd'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // non-unique + vector source = {'a', 'b', 'c', 'd', 'e', 'c', 'a', 'q'}; + vector target = {'z', 'a', 'b', 'c', 'd', 'a'}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // full + vector source(std::numeric_limits::max()+1); + std::iota(source.begin(), source.end(), 0); + vector target(source.size()); + std::reverse_copy(source.begin(), source.end(), target.begin()); + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + target.clear(); + result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + source.clear(); + result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { // non-unique full + vector source(std::numeric_limits::max()*2); + std::iota(source.begin(), source.begin()+std::numeric_limits::max(), 0); + std::iota(source.begin()+std::numeric_limits::max(), source.end(), 0); + vector target(source.size()); + std::reverse_copy(source.begin(), source.end(), target.begin()); + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + target.clear(); + result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + source.clear(); + result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(ordered_diff_string_test) try { + using namespace std; + { + vector source = {"hello", "how", "are", "you", "today"}; + vector target = {"hi", "are", "you", "here"}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { + vector source = {"prod1", "prod2", "prod3", "prod4", "prod5"}; + vector target = {"prod2", "prod1", "prod3", "prod4", "prod5"}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), result); + BOOST_TEST(source == target); + } + { + vector source = {"prod1", "prod2", "prod3", "prod4", "prod5"}; + vector target = {"prod5", "prod1", "prod2", "prod3", "prod4"}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), std::move(result)); + BOOST_TEST(source == target); + } + { + vector source = {"prod1", "prod2", "prod3", "prod4", "prod5"}; + vector target = {"prod2", "prod3", "prod4", "prod5", "prod6"}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), std::move(result)); + BOOST_TEST(source == target); + } + +} FC_LOG_AND_RETHROW(); + +class count_moves { + std::string s; +public: + inline static size_t num_moves = 0; + count_moves(const count_moves& m) : s(m.s) {}; + count_moves(count_moves&& m) noexcept : s(std::move(m.s)) { ++num_moves; }; + count_moves& operator=(const count_moves& rhs) = default; + explicit count_moves(std::string s) : s(s) {}; + auto operator<=>(const count_moves&) const = default; + bool operator==(const count_moves&) const = default; +}; + +BOOST_AUTO_TEST_CASE(ordered_diff_moveable_test) try { + using namespace std; + { + vector source = {count_moves{"hello"}, count_moves{"there"}}; + vector target = {count_moves{"hi"}, count_moves{"there"}}; + auto result = ordered_diff::diff(source, target); + source = ordered_diff::apply_diff(std::move(source), std::move(result)); + BOOST_TEST(source == target); + BOOST_TEST(count_moves::num_moves == 1u); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 2a75d0c55d..c9378593f3 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -148,7 +148,7 @@ namespace eosio::testing { struct produce_block_result_t { signed_block_ptr block; transaction_trace_ptr onblock_trace; - std::vector traces; // transaction traces + std::vector unapplied_transaction_traces; // only traces of any unapplied transactions }; /** @@ -272,20 +272,6 @@ namespace eosio::testing { transaction_trace_ptr set_producer_schedule(const vector& schedule); transaction_trace_ptr set_producers_legacy(const vector& producer_names); - // libtester uses 1 as weight of each of the finalizer, sets (2/3 finalizers + 1) - // as threshold, and makes all finalizers vote QC - std::pair> - set_finalizers(std::span finalizer_names); - - std::pair> - set_finalizers(const std::vector& names) { - return set_finalizers(std::span{names.begin(), names.end()}); - } - - void set_node_finalizers(std::span finalizer_names); - - std::vector set_active_finalizers(std::span finalizer_names); - // Finalizer policy input to set up a test: weights, threshold and local finalizers // which participate voting. struct finalizer_policy_input { @@ -298,7 +284,32 @@ namespace eosio::testing { uint64_t threshold {0}; std::vector local_finalizers; }; - std::pair> set_finalizers(const finalizer_policy_input& input); + + struct set_finalizers_output_t { + transaction_trace_ptr setfinalizer_trace; + std::vector privkeys; // private keys of **local** finalizers + std::vector pubkeys; // public keys of all finalizers in the policy + }; + + set_finalizers_output_t set_finalizers(const finalizer_policy_input& input); + + void set_node_finalizers(std::span finalizer_names); + + set_finalizers_output_t set_active_finalizers(std::span finalizer_names); + + // Useful when using a single node. + // Set a finalizer policy with a few finalizers, all local to the current node. + // All have weight == 1, threshold is `num_finalizers * 2 / 3 + 1` + // ----------------------------------------------------------------------------- + set_finalizers_output_t set_finalizers(std::span finalizer_names); + + // Useful when using a single node. + // Set a finalizer policy with a few finalizers, all local to the current node. + // All have weight == 1, threshold is `num_finalizers * 2 / 3 + 1` + // ----------------------------------------------------------------------------- + set_finalizers_output_t set_finalizers(const std::vector& names) { + return set_finalizers(std::span{names.begin(), names.end()}); + } std::optional active_finalizer_policy(const block_id_type& id) const { return control->active_finalizer_policy(id); @@ -491,6 +502,7 @@ namespace eosio::testing { // ----------------------------------------------------------------- void check_head_finalizer_policy(uint32_t generation, std::span keys_span) { + BOOST_REQUIRE_EQUAL(control->head_sanity_check(), true); auto finpol = active_finalizer_policy(control->head_block_header().calculate_id()); BOOST_REQUIRE(!!finpol); BOOST_REQUIRE_EQUAL(finpol->generation, generation); @@ -784,11 +796,11 @@ namespace eosio::testing { // updates the finalizer_policy to the `fin_policy_size` keys starting at `first_key` // ---------------------------------------------------------------------------------- - std::vector set_finalizer_policy(size_t first_key) { + base_tester::set_finalizers_output_t set_finalizer_policy(size_t first_key) { return t.set_active_finalizers({&key_names.at(first_key), fin_policy_size}); } - std::vector set_finalizer_policy(std::span indices) { + base_tester::set_finalizers_output_t set_finalizer_policy(std::span indices) { assert(indices.size() == fin_policy_size); vector names; names.reserve(fin_policy_size); @@ -819,11 +831,11 @@ namespace eosio::testing { // Do some sanity checks on the genesis block // ------------------------------------------ const auto& ext = genesis_block->template extract_header_extension(); - const auto& fin_policy = ext.new_finalizer_policy; - BOOST_TEST(!!fin_policy); - BOOST_TEST(fin_policy->finalizers.size() == fin_policy_size); - BOOST_TEST(fin_policy->generation == 1); - BOOST_TEST(fin_policy->threshold == (fin_policy_size * 2) / 3 + 1); + const auto& fin_policy_diff = ext.new_finalizer_policy_diff; + BOOST_TEST(!!fin_policy_diff); + BOOST_TEST(fin_policy_diff->finalizers_diff.insert_indexes.size() == fin_policy_size); + BOOST_TEST(fin_policy_diff->generation == 1u); + BOOST_TEST(fin_policy_diff->threshold == (fin_policy_size * 2) / 3 + 1); // wait till the genesis_block becomes irreversible. // The critical block is the block that makes the genesis_block irreversible @@ -852,7 +864,7 @@ namespace eosio::testing { auto b = produce_block(); BOOST_REQUIRE_EQUAL(t.lib_block->block_num(), pt_block->block_num()); - return *fin_policy; + return finalizer_policy{}.apply_diff(*fin_policy_diff); } Tester& t; diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index a003f3273c..3db4bb75db 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -423,14 +423,13 @@ namespace eosio::testing { trace->except->dynamic_rethrow_exception(); } itr = unapplied_transactions.erase( itr ); - res.traces.emplace_back( std::move(trace) ); + res.unapplied_transaction_traces.emplace_back( std::move(trace) ); } vector scheduled_trxs; while ((scheduled_trxs = get_scheduled_transactions()).size() > 0 ) { for( const auto& trx : scheduled_trxs ) { auto trace = control->push_scheduled_transaction( trx, fc::time_point::maximum(), fc::microseconds::maximum(), DEFAULT_BILLED_CPU_TIME_US, true ); - res.traces.emplace_back( trace ); if( !no_throw && trace->except ) { // this always throws an fc::exception, since the original exception is copied into an fc::exception trace->except->dynamic_rethrow_exception(); @@ -1171,6 +1170,7 @@ namespace eosio::testing { vector base_tester::get_producer_authorities( const vector& producer_names )const { // Create producer schedule vector schedule; + schedule.reserve(producer_names.size()); for (auto& producer_name: producer_names) { schedule.emplace_back(producer_authority{ producer_name, block_signing_authority_v0{1, {{ get_public_key( producer_name, "active" ), 1}} } }); } @@ -1193,7 +1193,6 @@ namespace eosio::testing { return push_action( config::system_account_name, "setprods"_n, config::system_account_name, fc::mutable_variant_object()("schedule", schedule_variant)); - } transaction_trace_ptr base_tester::set_producers_legacy(const vector& producer_names) { @@ -1210,11 +1209,9 @@ namespace eosio::testing { return push_action( config::system_account_name, "setprods"_n, config::system_account_name, fc::mutable_variant_object()("schedule", legacy_keys)); - } - std::pair> - base_tester::set_finalizers(std::span finalizer_names) { + base_tester::set_finalizers_output_t base_tester::set_finalizers(std::span finalizer_names) { auto num_finalizers = finalizer_names.size(); std::vector finalizers_info; finalizers_info.reserve(num_finalizers); @@ -1231,11 +1228,11 @@ namespace eosio::testing { return set_finalizers(policy_input); } - std::pair> - base_tester::set_finalizers(const finalizer_policy_input& input) { + base_tester::set_finalizers_output_t base_tester::set_finalizers(const finalizer_policy_input& input) { + set_finalizers_output_t res; + chain::bls_pub_priv_key_map_t local_finalizer_keys; fc::variants finalizer_auths; - std::vector priv_keys; for (const auto& f: input.finalizers) { auto [privkey, pubkey, pop] = get_bls_key( f.name ); @@ -1244,9 +1241,11 @@ namespace eosio::testing { if( auto it = std::ranges::find_if(input.local_finalizers, [&](const auto& name) { return name == f.name; }); it != input.local_finalizers.end()) { local_finalizer_keys[pubkey.to_string()] = privkey.to_string(); - priv_keys.emplace_back(privkey); + res.privkeys.emplace_back(privkey); }; + res.pubkeys.emplace_back(pubkey); + finalizer_auths.emplace_back( fc::mutable_variant_object() ("description", f.name.to_string() + " description") @@ -1261,14 +1260,14 @@ namespace eosio::testing { fin_policy_variant("threshold", input.threshold); fin_policy_variant("finalizers", std::move(finalizer_auths)); - return { push_action( config::system_account_name, "setfinalizer"_n, config::system_account_name, - fc::mutable_variant_object()("finalizer_policy", std::move(fin_policy_variant))), - priv_keys }; + res.setfinalizer_trace = + push_action( config::system_account_name, "setfinalizer"_n, config::system_account_name, + fc::mutable_variant_object()("finalizer_policy", std::move(fin_policy_variant))); + return res; } void base_tester::set_node_finalizers(std::span names) { - - chain::bls_pub_priv_key_map_t local_finalizer_keys; + bls_pub_priv_key_map_t local_finalizer_keys; for (auto name: names) { auto [privkey, pubkey, pop] = get_bls_key(name); local_finalizer_keys[pubkey.to_string()] = privkey.to_string(); @@ -1276,20 +1275,15 @@ namespace eosio::testing { control->set_node_finalizer_keys(local_finalizer_keys); } - std::vector base_tester::set_active_finalizers(std::span names) { - std::vector pubkeys; - pubkeys.reserve(names.size()); + base_tester::set_finalizers_output_t base_tester::set_active_finalizers(std::span names) { finalizer_policy_input input; input.finalizers.reserve(names.size()); - for (auto name : names) { - auto [privkey, pubkey, pop] = get_bls_key(name); - pubkeys.push_back(pubkey); + for (auto name : names) input.finalizers.emplace_back(name, 1); - } + // same as reference-contracts/.../contracts/eosio.system/src/finalizer_key.cpp#L73 input.threshold = (names.size() * 2) / 3 + 1; - set_finalizers(input); - return pubkeys; + return set_finalizers(input); } const table_id_object* base_tester::find_table( name code, name scope, name table ) { diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 01d9207881..2675a41dc0 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -3878,12 +3878,12 @@ BOOST_AUTO_TEST_CASE(initial_set_finalizer_test) { try { std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); BOOST_TEST(!!ext); - std::optional fin_policy = std::get(*ext).new_finalizer_policy; - BOOST_TEST(!!fin_policy); - BOOST_TEST(fin_policy->finalizers.size() == num_finalizers); - BOOST_TEST(fin_policy->generation == 1); + std::optional fin_policy_diff = std::get(*ext).new_finalizer_policy_diff; + BOOST_TEST(!!fin_policy_diff); + BOOST_TEST(fin_policy_diff->finalizers_diff.insert_indexes.size() == num_finalizers); + BOOST_TEST(fin_policy_diff->generation == 1); // same as reference-contracts/.../contracts/eosio.system/src/finalizer_key.cpp#L73 - BOOST_TEST(fin_policy->threshold == (num_finalizers * 2) / 3 + 1); + BOOST_TEST(fin_policy_diff->threshold == (num_finalizers * 2) / 3 + 1); block_id_type if_genesis_block_id = block->calculate_id(); for (block_num_type active_block_num = block->block_num(); active_block_num > t.lib_block->block_num(); t.produce_block()) { @@ -3926,10 +3926,10 @@ void test_finality_transition(const vector& accounts, std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); BOOST_TEST(!!ext); - std::optional fin_policy = std::get(*ext).new_finalizer_policy; - BOOST_TEST(!!fin_policy); - BOOST_TEST(fin_policy->finalizers.size() == accounts.size()); - BOOST_TEST(fin_policy->generation == 1); + std::optional fin_policy_diff = std::get(*ext).new_finalizer_policy_diff; + BOOST_TEST(!!fin_policy_diff); + BOOST_TEST(fin_policy_diff->finalizers_diff.insert_indexes.size() == accounts.size()); + BOOST_TEST(fin_policy_diff->generation == 1); block_id_type if_genesis_block_id = block->calculate_id(); block_num_type active_block_num = block->block_num(); diff --git a/unittests/block_header_tests.cpp b/unittests/block_header_tests.cpp index 22b68345c9..05551a7bc3 100644 --- a/unittests/block_header_tests.cpp +++ b/unittests/block_header_tests.cpp @@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) header.header_extensions, instant_finality_extension::extension_id(), fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_last_strong_qc}, - std::optional{}, std::shared_ptr{}} ) + std::optional{}, std::optional{}} ) ); std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); @@ -33,8 +33,8 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_empty_values_test) const auto& if_extension = std::get(*ext); 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 ); + BOOST_REQUIRE( !if_extension.new_finalizer_policy_diff ); + BOOST_REQUIRE( !if_extension.new_proposer_policy_diff ); } // test for instant_finality_extension uniqueness @@ -46,21 +46,19 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_uniqueness_test) header.header_extensions, instant_finality_extension::extension_id(), fc::raw::pack( instant_finality_extension{qc_claim_t{0, false}, {std::nullopt}, - std::shared_ptr{}} ) + std::optional{}} ) ); std::vector finalizers { {"test description", 50, fc::crypto::blslib::bls_public_key{"PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q" }} }; - finalizer_policy new_finalizer_policy; - new_finalizer_policy.generation = 1; - new_finalizer_policy.threshold = 100; - new_finalizer_policy.finalizers = finalizers; + auto fin_policy = std::make_shared(); + finalizer_policy_diff new_finalizer_policy_diff = fin_policy->create_diff(finalizer_policy{.generation = 1, .threshold = 100, .finalizers = finalizers}); - proposer_policy_ptr new_proposer_policy = std::make_shared(1, block_timestamp_type{200}, producer_authority_schedule{} ); + proposer_policy_diff new_proposer_policy_diff = proposer_policy_diff{.version = 1, .active_time = block_timestamp_type{200}, .producer_auth_diff = {}}; emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_claim_t{100, true}, new_finalizer_policy, new_proposer_policy} ) + fc::raw::pack( instant_finality_extension{qc_claim_t{100, true}, new_finalizer_policy_diff, new_proposer_policy_diff} ) ); BOOST_CHECK_THROW(header.validate_and_extract_header_extensions(), invalid_block_header_extension); @@ -74,17 +72,15 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) constexpr bool is_strong_qc {true}; std::vector finalizers { {"test description", 50, fc::crypto::blslib::bls_public_key{"PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q" }} }; - finalizer_policy new_finalizer_policy; - new_finalizer_policy.generation = 1; - new_finalizer_policy.threshold = 100; - new_finalizer_policy.finalizers = finalizers; + auto fin_policy = std::make_shared(); + finalizer_policy_diff new_finalizer_policy_diff = fin_policy->create_diff(finalizer_policy{.generation = 1, .threshold = 100, .finalizers = finalizers}); - proposer_policy_ptr new_proposer_policy = std::make_shared(1, block_timestamp_type{200}, producer_authority_schedule{} ); + proposer_policy_diff new_proposer_policy_diff = proposer_policy_diff{.version = 1, .active_time = block_timestamp_type{200}, .producer_auth_diff = {}}; emplace_extension( header.header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack( instant_finality_extension{qc_claim_t{last_qc_block_num, is_strong_qc}, 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_diff, new_proposer_policy_diff} ) ); std::optional ext = header.extract_header_extension(instant_finality_extension::extension_id()); @@ -95,16 +91,15 @@ BOOST_AUTO_TEST_CASE(instant_finality_extension_with_values_test) 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); - BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->threshold, 100u); - BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->finalizers[0].description, "test description"); - BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->finalizers[0].weight, 50u); - BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy->finalizers[0].public_key.to_string(), "PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q"); + BOOST_REQUIRE( !!if_extension.new_finalizer_policy_diff ); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy_diff->generation, 1u); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy_diff->threshold, 100u); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy_diff->finalizers_diff.insert_indexes[0].second.description, "test description"); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy_diff->finalizers_diff.insert_indexes[0].second.weight, 50u); + BOOST_REQUIRE_EQUAL(if_extension.new_finalizer_policy_diff->finalizers_diff.insert_indexes[0].second.public_key.to_string(), "PUB_BLS_qVbh4IjYZpRGo8U_0spBUM-u-r_G0fMo4MzLZRsKWmm5uyeQTp74YFaMN9IDWPoVVT5rj_Tw1gvps6K9_OZ6sabkJJzug3uGfjA6qiaLbLh5Fnafwv-nVgzzzBlU2kwRrcHc8Q"); - BOOST_REQUIRE( !!if_extension.new_proposer_policy ); - BOOST_REQUIRE_EQUAL(if_extension.new_proposer_policy->schema_version, 1u); - fc::time_point t = (fc::time_point)(if_extension.new_proposer_policy->active_time); + BOOST_REQUIRE( !!if_extension.new_proposer_policy_diff ); + fc::time_point t = (fc::time_point)(if_extension.new_proposer_policy_diff->active_time); BOOST_REQUIRE_EQUAL(t.time_since_epoch().to_seconds(), 946684900ll); } diff --git a/unittests/finality_test_cluster.hpp b/unittests/finality_test_cluster.hpp index 6a24013c75..775eb4c62f 100644 --- a/unittests/finality_test_cluster.hpp +++ b/unittests/finality_test_cluster.hpp @@ -156,7 +156,7 @@ class finality_test_cluster { // ---------------------------- for (size_t i=0; i) { try { // verify LIB does not advances with finalizers not voting. // -------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(no_votes, finality_test_cluster<4>) { try { - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); produce_and_push_block(); for (auto i = 0; i < 3; ++i) { produce_and_push_block(); // don't process votes // when only node0 votes, LIB shouldn't advance - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); } } FC_LOG_AND_RETHROW() } @@ -38,14 +38,14 @@ BOOST_FIXTURE_TEST_CASE(no_votes, finality_test_cluster<4>) { try { // verify LIB does not advances when one less than the quorum votes // ---------------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(quorum_minus_one, finality_test_cluster<4>) { try { - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); produce_and_push_block(); for (auto i = 0; i < 3; ++i) { produce_and_push_block(); process_votes(1, num_needed_for_quorum - 1); // when one less than required vote, LIB shouldn't advance - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); } } FC_LOG_AND_RETHROW() } @@ -111,7 +111,7 @@ BOOST_FIXTURE_TEST_CASE(one_delayed_votes, finality_test_cluster<4>) { try { // block 1 (index 1) has the same QC claim as block 0. It cannot move LIB process_votes(1, num_needed_for_quorum, 1); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // producing, pushing, and voting a new block makes LIB moving process_votes(1, num_needed_for_quorum); @@ -131,7 +131,7 @@ BOOST_FIXTURE_TEST_CASE(three_delayed_votes, finality_test_cluster<4>) { try { produce_and_push_block(); // LIB did not advance - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // vote block 0 (index 0) to make it have a strong QC, // prompting LIB advacing on nodes @@ -143,7 +143,7 @@ BOOST_FIXTURE_TEST_CASE(three_delayed_votes, finality_test_cluster<4>) { try { for (auto i=1; i < 4; ++i) { process_votes(1, num_needed_for_quorum, i); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); } // Now send votes for the last block that node0 produced (block 8). It will be @@ -175,12 +175,12 @@ BOOST_FIXTURE_TEST_CASE(out_of_order_votes, finality_test_cluster<4>) { try { // block 1 (index 1) has the same QC claim as block 2. It will not move LIB process_votes(1, num_needed_for_quorum, 1); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // block 0 (index 0) has the same QC claim as block 2. It will not move LIB process_votes(1, num_needed_for_quorum, 0); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // producing, pushing, and voting a new block makes LIB moving process_votes(1, num_needed_for_quorum); @@ -229,7 +229,7 @@ BOOST_FIXTURE_TEST_CASE(lost_votes, finality_test_cluster<4>) { try { clear_votes_and_reset_lib(); produce_and_push_block(); // Produce another block - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // LIB doesn't advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // LIB doesn't advance process_votes(1, num_needed_for_quorum); // and propagate the votes for this new block to node0 produce_and_push_block(); @@ -247,7 +247,7 @@ BOOST_FIXTURE_TEST_CASE(one_weak_vote, finality_test_cluster<4>) { try { auto next_idx = process_votes(1, num_needed_for_quorum -1); // one less strong vote than needed for quorum process_vote(next_idx, -1, vote_mode::weak); // and one weak vote produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // weak QC (1 shy of strong) => LIB does not advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // weak QC (1 shy of strong) => LIB does not advance process_votes(1, num_needed_for_quorum); // now this provides enough strong votes for quorum produce_and_push_block(); @@ -263,7 +263,7 @@ BOOST_FIXTURE_TEST_CASE(quorum_minus_one_weak_vote, finality_test_cluster<4>) { process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // weak QC => LIB does not advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // weak QC => LIB does not advance process_votes(1, num_needed_for_quorum); produce_and_push_block(); @@ -279,7 +279,7 @@ BOOST_FIXTURE_TEST_CASE(weak_strong_weak_strong, finality_test_cluster<4>) { try process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // weak QC => LIB does not advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // weak QC => LIB does not advance process_votes(1, num_needed_for_quorum); produce_and_push_block(); @@ -287,7 +287,7 @@ BOOST_FIXTURE_TEST_CASE(weak_strong_weak_strong, finality_test_cluster<4>) { try process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // weak QC => LIB does not advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // weak QC => LIB does not advance process_votes(1, num_needed_for_quorum); produce_and_push_block(); @@ -303,11 +303,11 @@ BOOST_FIXTURE_TEST_CASE(weak_weak_strong_strong, finality_test_cluster<4>) { try process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // weak QC => LIB does not advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // weak QC => LIB does not advance process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // weak QC => LIB does not advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // weak QC => LIB does not advance process_votes(1, num_needed_for_quorum); produce_and_push_block(); @@ -329,12 +329,12 @@ BOOST_FIXTURE_TEST_CASE(weak_delayed_lost_vote, finality_test_cluster<4>) { try // quorum of weak votes process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // delay votes at index 1 constexpr uint32_t delayed_index = 1; produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // quorum of strong votes process_votes(1, num_needed_for_quorum); @@ -343,12 +343,12 @@ BOOST_FIXTURE_TEST_CASE(weak_delayed_lost_vote, finality_test_cluster<4>) { try // A lost vote produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // The delayed vote arrives, does not advance lib process_votes(1, num_needed_for_quorum, delayed_index); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // strong vote advances lib process_votes(1, num_needed_for_quorum); @@ -366,7 +366,7 @@ BOOST_FIXTURE_TEST_CASE(delayed_strong_weak_lost_vote, finality_test_cluster<4>) // delay votes at index 1 constexpr uint32_t delayed_index = 0; produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // quorum of strong votes process_votes(1, num_needed_for_quorum); @@ -376,7 +376,7 @@ BOOST_FIXTURE_TEST_CASE(delayed_strong_weak_lost_vote, finality_test_cluster<4>) // quorum of weak votes process_votes(1, num_needed_for_quorum, -1, vote_mode::weak); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // quorum of strong votes process_votes(1, num_needed_for_quorum); @@ -385,12 +385,12 @@ BOOST_FIXTURE_TEST_CASE(delayed_strong_weak_lost_vote, finality_test_cluster<4>) // A lost vote produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // The delayed vote arrives, does not advance lib process_votes(1, num_needed_for_quorum, delayed_index); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // strong vote advances lib process_votes(1, num_needed_for_quorum); @@ -432,9 +432,9 @@ BOOST_FIXTURE_TEST_CASE(unknown_proposal_votes, finality_test_cluster<4>) { try process_votes(2, num_needed_for_quorum - 1); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); - node1.restore_to_original_vote(0); // restore node1's vote at index 0 to original vote + node1.restore_to_original_vote(0u); // restore node1's vote at index 0 to original vote process_votes(1, 1, 0, vote_mode::strong); // send restored vote to node0 produce_and_push_block(); // produce a block so the new QC can propagate BOOST_REQUIRE_EQUAL(num_lib_advancing(), num_nodes); @@ -457,7 +457,7 @@ BOOST_FIXTURE_TEST_CASE(unknown_finalizer_key_votes, finality_test_cluster<4>) { BOOST_REQUIRE(process_vote(1, 0) == eosio::chain::vote_status::unknown_public_key); // restore to original vote - node1.restore_to_original_vote(0); + node1.restore_to_original_vote(0u); // process the original vote. LIB should advance process_vote(1, 0); @@ -478,9 +478,9 @@ BOOST_FIXTURE_TEST_CASE(corrupted_signature_votes, finality_test_cluster<4>) { t process_votes(2, num_needed_for_quorum - 1); produce_and_push_block(); - BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0); // because of the one corrupted vote, quorum is not reached + BOOST_REQUIRE_EQUAL(num_lib_advancing(), 0u); // because of the one corrupted vote, quorum is not reached - node1.restore_to_original_vote(0); // restore node1's vote at index 0 to original vote + node1.restore_to_original_vote(0u); // restore node1's vote at index 0 to original vote process_votes(1, 1, 0, vote_mode::strong); // send restored vote to node0 produce_and_push_block(); // produce a block so the new QC can propagate BOOST_REQUIRE_EQUAL(num_lib_advancing(), num_nodes); @@ -504,9 +504,9 @@ BOOST_FIXTURE_TEST_CASE(second_set_finalizers, finality_test_cluster<4>) { try { assert(fin_policy_0); // current finalizer policy from transition to Savanna auto indices1 = fin_policy_indices_0; // start from original set of indices - assert(indices1[0] == 0); // we used index 0 for node0 in original policy + assert(indices1[0] == 0u); // we used index 0 for node0 in original policy indices1[0] = 1; // update key used for node0 in policy - auto pubkeys1 = node0.finkeys.set_finalizer_policy(indices1); + auto pubkeys1 = node0.finkeys.set_finalizer_policy(indices1).pubkeys; // we need two 3-chains for the new finalizer policy to be activated for (size_t i=0; i<6; ++i) { @@ -517,9 +517,83 @@ BOOST_FIXTURE_TEST_CASE(second_set_finalizers, finality_test_cluster<4>) { try { // we just completed the two 3-chains, so the next block we produce will have the new finalizer policy activated produce_and_push_block(); - node0.check_head_finalizer_policy(2, pubkeys1); - node1.check_head_finalizer_policy(2, pubkeys1); + node0.check_head_finalizer_policy(2u, pubkeys1); + node1.check_head_finalizer_policy(2u, pubkeys1); } FC_LOG_AND_RETHROW() } +// verify issue https://github.com/AntelopeIO/spring/issues/130 is fixed +// --------------------------------------------------------------------- +BOOST_FIXTURE_TEST_CASE(finality_skip, finality_test_cluster<4>) { try { + produce_and_push_block(); + process_votes(1, num_needed_for_quorum); + produce_and_push_block(); + + // when a quorum of nodes vote, LIB should advance + BOOST_REQUIRE_EQUAL(num_lib_advancing(), num_nodes); + BOOST_REQUIRE(produce_blocks_and_verify_lib_advancing()); + + auto add_set_finalizers = [&](size_t start_idx) { + assert(fin_policy_0); // current finalizer policy from transition to Savanna + auto indices = fin_policy_indices_0; // start from original set of indices + assert(indices[0] == 0u); // we used index 0 for node0 in original policy + indices[0] = start_idx; // update key used for node0 in policy + auto pubkeys = node0.finkeys.set_finalizer_policy(indices).pubkeys; + produce_and_push_block(); + return pubkeys; + }; + + clear_votes_and_reset_lib(); + + // produce 2 blocks that will be made final after the three `add_set_finalizers` below + // ------------------------------------------------------------------------------------ + for (size_t i=0; i<4; ++i) { + produce_and_push_block(); + process_votes(1, num_nodes - 1); + } + + // run three set_finalizers in 3 blocks without voting + // they will be in `proposed` state with different block numbers. + // ------------------------------------------------------------- + auto pubkeys1 = add_set_finalizers(1); // will be generation == 2 + auto pubkeys2 = add_set_finalizers(2); // will be generation == 3 + auto pubkeys3 = add_set_finalizers(3); // will be generation == 4 + + // produce_and_push 3 blocks. The last one will make finality skip over the three + // `add_set_finalizers` blocks above, so that they all become `pending` on the same block. + // --------------------------------------------------------------------------------------- + for (size_t i=0; i<3; ++i) { + produce_and_push_block(); + process_votes(1, num_nodes - 1); + + // make sure we don't have duplicate finalizer policies for the same block number + // in either `proposed` or `pending` state + // ------------------------------------------------------------------------------ + node0.check_head_finalizer_policy(1u, fin_policy_pubkeys_0); + } + + // now *only* the third `set_finalizers` should be `pending`, the one with + // `generation == 4`. The other policies must have been overwritten since they all + // became `pending` at the same block. + // + // we need another 3-chain to make that block final. + // ------------------------------------------------------------------------------- + for (size_t i=0; i<3; ++i) { + produce_and_push_block(); + process_votes(1, num_nodes - 1); + node0.check_head_finalizer_policy(1u, fin_policy_pubkeys_0); + } + + // when we receive the votes of that last block finishing the 3-chain, the active + // `finalizer_policy` finally changes. + // ------------------------------------------------------------------------------ + produce_and_push_block(); + process_votes(1, num_nodes - 1); + node0.check_head_finalizer_policy(4u, pubkeys3); + +} FC_LOG_AND_RETHROW() } + + + + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/finalizer_update_tests.cpp b/unittests/finalizer_update_tests.cpp index 73e7b6d2af..76ca529589 100644 --- a/unittests/finalizer_update_tests.cpp +++ b/unittests/finalizer_update_tests.cpp @@ -32,33 +32,33 @@ static void ensure_next_block_finalizer_policy(validating_tester& t, // --------------------------------------------------------------------- BOOST_AUTO_TEST_CASE(savanna_set_finalizer_single_test) { try { validating_tester t{ {}, nullptr, setup_policy::full_pre_savanna }; - size_t num_keys = 22; - size_t finset_size = 21; + size_t num_keys = 22u; + size_t finset_size = 21u; // Create finalizer keys finalizer_keys fin_keys(t, num_keys, finset_size); // set finalizers on current node - fin_keys.set_node_finalizers(0, num_keys); + fin_keys.set_node_finalizers(0u, num_keys); // run initial set_finalizer_policy() and waits until transition is complete - auto pubkeys0 = fin_keys.set_finalizer_policy(0); + auto pubkeys0 = fin_keys.set_finalizer_policy(0u).pubkeys; fin_keys.transition_to_savanna(); // run set_finalizers(), verify it becomes active after exactly two 3-chains // ------------------------------------------------------------------------- - auto pubkeys1 = fin_keys.set_finalizer_policy(1); + auto pubkeys1 = fin_keys.set_finalizer_policy(1u).pubkeys; t.produce_block(); - t.check_head_finalizer_policy(1, pubkeys0); // new policy should only be active until after two 3-chains + t.check_head_finalizer_policy(1u, pubkeys0); // new policy should only be active until after two 3-chains t.produce_blocks(3); - t.check_head_finalizer_policy(1, pubkeys0); // one 3-chain - new policy still should not be active + t.check_head_finalizer_policy(1u, pubkeys0); // one 3-chain - new policy still should not be active t.produce_blocks(2); - t.check_head_finalizer_policy(1, pubkeys0); // one 3-chain + 2 blocks - new policy still should not be active + t.check_head_finalizer_policy(1u, pubkeys0); // one 3-chain + 2 blocks - new policy still should not be active t.produce_block(); - t.check_head_finalizer_policy(2, pubkeys1); // two 3-chain - new policy *should* be active + t.check_head_finalizer_policy(2u, pubkeys1); // two 3-chain - new policy *should* be active } FC_LOG_AND_RETHROW() } @@ -68,57 +68,96 @@ BOOST_AUTO_TEST_CASE(savanna_set_finalizer_single_test) { try { // --------------------------------------------------------------------------- BOOST_AUTO_TEST_CASE(savanna_set_finalizer_multiple_test) { try { validating_tester t{ {}, nullptr, setup_policy::full_pre_savanna }; - size_t num_keys = 50; - size_t finset_size = 21; + size_t num_keys = 50u; + size_t finset_size = 21u; + + auto verify_block_finality_policy_diff = [](const signed_block_ptr& block, uint32_t gen, const bls_public_key& key) { + std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional fin_policy_diff = std::get(*ext).new_finalizer_policy_diff; + BOOST_TEST(!!fin_policy_diff); + BOOST_TEST(fin_policy_diff->generation == gen); + // each set_finalizer_policy in this test removes one and adds one + BOOST_TEST(fin_policy_diff->finalizers_diff.remove_indexes.size() == 1); + BOOST_TEST_REQUIRE(fin_policy_diff->finalizers_diff.insert_indexes.size() == 1); + BOOST_TEST(fin_policy_diff->finalizers_diff.insert_indexes[0].second.public_key == key); + }; // Create finalizer keys finalizer_keys fin_keys(t, num_keys, finset_size); // set finalizers on current node - fin_keys.set_node_finalizers(0, num_keys); + fin_keys.set_node_finalizers(0u, num_keys); // run initial set_finalizer_policy() and waits until transition is complete - auto pubkeys0 = fin_keys.set_finalizer_policy(0); + auto pubkeys0 = fin_keys.set_finalizer_policy(0u).pubkeys; fin_keys.transition_to_savanna(); // run set_finalizers() twice in same block, verify only latest one becomes active // ------------------------------------------------------------------------------- - (void)fin_keys.set_finalizer_policy(1); - auto pubkeys2 = fin_keys.set_finalizer_policy(2); + (void)fin_keys.set_finalizer_policy(1u); + auto pubkeys2 = fin_keys.set_finalizer_policy(2u).pubkeys; t.produce_block(); - t.check_head_finalizer_policy(1, pubkeys0); // new policy should only be active until after two 3-chains + t.check_head_finalizer_policy(1u, pubkeys0); // new policy should only be active until after two 3-chains t.produce_blocks(5); - t.check_head_finalizer_policy(1, pubkeys0); // new policy should only be active until after two 3-chains + t.check_head_finalizer_policy(1u, pubkeys0); // new policy should only be active until after two 3-chains t.produce_block(); - t.check_head_finalizer_policy(2, pubkeys2); // two 3-chain - new policy pubkeys2 *should* be active + t.check_head_finalizer_policy(2u, pubkeys2); // two 3-chain - new policy pubkeys2 *should* be active - // run a test with multiple set_finlizers in-flight during the two 3-chains they + // run a test with multiple set_finalizers in-flight during the two 3-chains they // take to become active - // ----------------------------------------------------------------------------- - auto pubkeys3 = fin_keys.set_finalizer_policy(3); + // ------------------------------------------------------------------------------ + auto pubkeys3 = fin_keys.set_finalizer_policy(3u).pubkeys; + auto b = t.produce_block(); + verify_block_finality_policy_diff(b, 3, pubkeys3.back()); + auto pubkeys4 = fin_keys.set_finalizer_policy(4u).pubkeys; + b = t.produce_block(); + verify_block_finality_policy_diff(b, 4, pubkeys4.back()); t.produce_block(); - auto pubkeys4 = fin_keys.set_finalizer_policy(4); + auto pubkeys5 = fin_keys.set_finalizer_policy(5u).pubkeys; + b = t.produce_block(); + verify_block_finality_policy_diff(b, 5, pubkeys5.back()); t.produce_block(); - t.produce_block(); - auto pubkeys5 = fin_keys.set_finalizer_policy(5); - t.produce_blocks(3); - t.check_head_finalizer_policy(2, pubkeys2); // 5 blocks after pubkeys3 (b5 - b0), pubkeys2 should still be active - t.produce_block(); - t.check_head_finalizer_policy(3, pubkeys3); // 6 blocks after pubkeys3 (b6 - b0), pubkeys3 should be active - t.produce_block(); - t.check_head_finalizer_policy(4, pubkeys4); // 6 blocks after pubkeys4 (b7 - b1), pubkeys4 should be active - - t.produce_block(); - t.check_head_finalizer_policy(4, pubkeys4); // 7 blocks after pubkeys4, pubkeys4 should still be active - t.produce_block(); - t.check_head_finalizer_policy(5, pubkeys5); // 6 blocks after pubkeys5 (b9 - b3), pubkeys5 should be active + auto pubkeys6 = fin_keys.set_finalizer_policy(6u).pubkeys; + b = t.produce_block(); + verify_block_finality_policy_diff(b, 6, pubkeys6.back()); + auto pubkeys7 = fin_keys.set_finalizer_policy(7u).pubkeys; + t.check_head_finalizer_policy(2u, pubkeys2); // 5 blocks after pubkeys3 (b5 - b0), pubkeys2 should still be active + b = t.produce_block(); + verify_block_finality_policy_diff(b, 7, pubkeys7.back()); + auto pubkeys8 = fin_keys.set_finalizer_policy(8u).pubkeys; + t.check_head_finalizer_policy(3u, pubkeys3); // 6 blocks after pubkeys3 (b6 - b0), pubkeys3 should be active + b = t.produce_block(); + verify_block_finality_policy_diff(b, 8, pubkeys8.back()); + auto pubkeys9 = fin_keys.set_finalizer_policy(9u).pubkeys; + t.check_head_finalizer_policy(4u, pubkeys4); // 6 blocks after pubkeys4 (b7 - b1), pubkeys4 should be active + b = t.produce_block(); + verify_block_finality_policy_diff(b, 9, pubkeys9.back()); + auto pubkeys10 = fin_keys.set_finalizer_policy(10u).pubkeys; + t.check_head_finalizer_policy(4u, pubkeys4); // 7 blocks after pubkeys4, pubkeys4 should still be active + b = t.produce_block(); + verify_block_finality_policy_diff(b, 10, pubkeys10.back()); + auto pubkeys11 = fin_keys.set_finalizer_policy(11u).pubkeys; + t.check_head_finalizer_policy(5u, pubkeys5); // 6 blocks after pubkeys5 (b9 - b3), pubkeys5 should be active + b = t.produce_block(); + verify_block_finality_policy_diff(b, 11, pubkeys11.back()); + t.produce_block(); // two blocks between 5 & 6 proposals + t.check_head_finalizer_policy(6u, pubkeys6); // the rest are all one block apart, tests pending with propsed + auto b12 = t.produce_block(); + t.check_head_finalizer_policy(7u, pubkeys7); + auto b13 = t.produce_block(); + t.check_head_finalizer_policy(8u, pubkeys8); + auto b14 = t.produce_block(); + t.check_head_finalizer_policy(9u, pubkeys9); + auto b15 = t.produce_block(); + t.check_head_finalizer_policy(10u, pubkeys10); + auto b16 = t.produce_block(); + t.check_head_finalizer_policy(11u, pubkeys11); // and no further change - ensure_next_block_finalizer_policy(t, 5, pubkeys5); - ensure_next_block_finalizer_policy(t, 5, pubkeys5); - ensure_next_block_finalizer_policy(t, 5, pubkeys5); - ensure_next_block_finalizer_policy(t, 5, pubkeys5); - ensure_next_block_finalizer_policy(t, 5, pubkeys5); + // --------------------- + for (size_t i=0; i<10; ++i) + ensure_next_block_finalizer_policy(t, 11u, pubkeys11); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 9fb3f3959a..c1598fdc96 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -568,47 +568,47 @@ BOOST_AUTO_TEST_CASE( reopen_forkdb ) try { } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { - tester c(setup_policy::full_pre_savanna); - while (c.control->head_block_num() < 3) { - c.produce_block(); + tester c1(setup_policy::full_pre_savanna); + while (c1.control->head_block_num() < 3) { + c1.produce_block(); } - auto r = c.create_accounts( {"dan"_n,"sam"_n,"pam"_n} ); - c.produce_block(); - auto res = c.set_producers( {"dan"_n,"sam"_n,"pam"_n} ); + auto r = c1.create_accounts( {"dan"_n,"sam"_n,"pam"_n} ); + c1.produce_block(); + auto res = c1.set_producers( {"dan"_n,"sam"_n,"pam"_n} ); wlog("set producer schedule to [dan,sam,pam]"); - c.produce_blocks(40); + c1.produce_blocks(40); tester c2(setup_policy::none); wlog( "push c1 blocks to c2" ); - push_blocks(c, c2); + push_blocks(c1, c2); wlog( "c1 blocks:" ); signed_block_ptr cb; - c.produce_blocks(3); + c1.produce_blocks(3); signed_block_ptr b; - cb = b = c.produce_block(); + cb = b = c1.produce_block(); account_name expected_producer = "dan"_n; BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); - b = c.produce_block(); + b = c1.produce_block(); expected_producer = "sam"_n; BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); - c.produce_blocks(10); - c.create_accounts( {"cam"_n} ); - c.set_producers( {"dan"_n,"sam"_n,"pam"_n,"cam"_n} ); + c1.produce_blocks(10); + c1.create_accounts( {"cam"_n} ); + c1.set_producers( {"dan"_n,"sam"_n,"pam"_n,"cam"_n} ); wlog("set producer schedule to [dan,sam,pam,cam]"); - c.produce_block(); + c1.produce_block(); // The next block should be produced by pam. // Sync second chain with first chain. wlog( "push c1 blocks to c2" ); - push_blocks(c, c2); + push_blocks(c1, c2); wlog( "end push c1 blocks to c2" ); // Now sam and pam go on their own fork while dan is producing blocks by himself. wlog( "sam and pam go off on their own fork on c2 while dan produces blocks by himself in c1" ); - auto fork_block_num = c.control->head_block_num(); + auto fork_block_num = c1.control->head_block_num(); signed_block_ptr c2b; wlog( "c2 blocks:" ); @@ -624,11 +624,11 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { wlog( "c1 blocks:" ); - b = c.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // dan skips over pam's blocks + b = c1.produce_block( fc::milliseconds(config::block_interval_ms * 13) ); // dan skips over pam's blocks expected_producer = "dan"_n; BOOST_REQUIRE_EQUAL( b->producer.to_string(), expected_producer.to_string() ); // create accounts on c1 which will be forked out - c.produce_block(); + c1.produce_block(); transaction_trace_ptr trace1, trace2, trace3, trace4; { // create account the hard way so we can set reference block and expiration @@ -642,12 +642,12 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { .owner = owner_auth, .active = active_auth, }); - trx.expiration = fc::time_point_sec{c.control->head_block_time() + fc::seconds( 60 )}; + trx.expiration = fc::time_point_sec{c1.control->head_block_time() + fc::seconds( 60 )}; trx.set_reference_block( cb->calculate_id() ); - trx.sign( get_private_key( config::system_account_name, "active" ), c.control->get_chain_id() ); - trace1 = c.push_transaction( trx ); + trx.sign( get_private_key( config::system_account_name, "active" ), c1.control->get_chain_id() ); + trace1 = c1.push_transaction( trx ); } - c.produce_block(); + c1.produce_block(); { signed_transaction trx; authority active_auth( get_public_key( "test2"_n, "active" ) ); @@ -659,10 +659,10 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { .owner = owner_auth, .active = active_auth, }); - trx.expiration = fc::time_point_sec{c.control->head_block_time() + fc::seconds( 60 )}; + trx.expiration = fc::time_point_sec{c1.control->head_block_time() + fc::seconds( 60 )}; trx.set_reference_block( cb->calculate_id() ); - trx.sign( get_private_key( config::system_account_name, "active" ), c.control->get_chain_id() ); - trace2 = c.push_transaction( trx ); + trx.sign( get_private_key( config::system_account_name, "active" ), c1.control->get_chain_id() ); + trace2 = c1.push_transaction( trx ); } { signed_transaction trx; @@ -675,10 +675,10 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { .owner = owner_auth, .active = active_auth, }); - trx.expiration = fc::time_point_sec{c.control->head_block_time() + fc::seconds( 60 )}; + trx.expiration = fc::time_point_sec{c1.control->head_block_time() + fc::seconds( 60 )}; trx.set_reference_block( cb->calculate_id() ); - trx.sign( get_private_key( config::system_account_name, "active" ), c.control->get_chain_id() ); - trace3 = c.push_transaction( trx ); + trx.sign( get_private_key( config::system_account_name, "active" ), c1.control->get_chain_id() ); + trace3 = c1.push_transaction( trx ); } { signed_transaction trx; @@ -691,18 +691,18 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { .owner = owner_auth, .active = active_auth, }); - trx.expiration = fc::time_point_sec{c.control->head_block_time() + fc::seconds( 60 )}; + trx.expiration = fc::time_point_sec{c1.control->head_block_time() + fc::seconds( 60 )}; trx.set_reference_block( b->calculate_id() ); // tapos to dan's block should be rejected on fork switch - trx.sign( get_private_key( config::system_account_name, "active" ), c.control->get_chain_id() ); - trace4 = c.push_transaction( trx ); + trx.sign( get_private_key( config::system_account_name, "active" ), c1.control->get_chain_id() ); + trace4 = c1.push_transaction( trx ); BOOST_CHECK( trace4->receipt->status == transaction_receipt_header::executed ); } - c.produce_block(); - c.produce_blocks(9); + c1.produce_block(); + c1.produce_blocks(9); // test forked blocks signal accepted_block in order, required by trace_api_plugin std::vector accepted_blocks; - auto conn = c.control->accepted_block().connect( [&]( block_signal_params t ) { + auto conn = c1.control->accepted_block().connect( [&]( block_signal_params t ) { const auto& [ block, id ] = t; accepted_blocks.emplace_back( block ); } ); @@ -711,10 +711,10 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { wlog( "push c2 blocks to c1" ); for( uint32_t start = fork_block_num + 1, end = c2.control->head_block_num(); start <= end; ++start ) { auto fb = c2.control->fetch_block_by_number( start ); - c.push_block( fb ); + c1.push_block( fb ); } - { // verify forked blocks where signaled in order + { // verify forked blocks were signaled in order auto itr = std::find( accepted_blocks.begin(), accepted_blocks.end(), c2b ); BOOST_CHECK( itr != accepted_blocks.end() ); ++itr; @@ -726,32 +726,32 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { BOOST_CHECK( i == 11 + 12 ); } // verify transaction on fork is reported by push_block in order - BOOST_REQUIRE_EQUAL( 4u, c.get_unapplied_transaction_queue().size() ); - BOOST_REQUIRE_EQUAL( trace1->id, c.get_unapplied_transaction_queue().begin()->id() ); - BOOST_REQUIRE_EQUAL( trace2->id, (++c.get_unapplied_transaction_queue().begin())->id() ); - BOOST_REQUIRE_EQUAL( trace3->id, (++(++c.get_unapplied_transaction_queue().begin()))->id() ); - BOOST_REQUIRE_EQUAL( trace4->id, (++(++(++c.get_unapplied_transaction_queue().begin())))->id() ); + BOOST_REQUIRE_EQUAL( 4u, c1.get_unapplied_transaction_queue().size() ); + BOOST_REQUIRE_EQUAL( trace1->id, c1.get_unapplied_transaction_queue().begin()->id() ); + BOOST_REQUIRE_EQUAL( trace2->id, (++c1.get_unapplied_transaction_queue().begin())->id() ); + BOOST_REQUIRE_EQUAL( trace3->id, (++(++c1.get_unapplied_transaction_queue().begin()))->id() ); + BOOST_REQUIRE_EQUAL( trace4->id, (++(++(++c1.get_unapplied_transaction_queue().begin())))->id() ); - BOOST_REQUIRE_EXCEPTION(c.control->get_account( "test1"_n ), fc::exception, + BOOST_REQUIRE_EXCEPTION(c1.control->get_account( "test1"_n ), fc::exception, [a="test1"_n] (const fc::exception& e)->bool { return std::string( e.what() ).find( a.to_string() ) != std::string::npos; }) ; - BOOST_REQUIRE_EXCEPTION(c.control->get_account( "test2"_n ), fc::exception, + BOOST_REQUIRE_EXCEPTION(c1.control->get_account( "test2"_n ), fc::exception, [a="test2"_n] (const fc::exception& e)->bool { return std::string( e.what() ).find( a.to_string() ) != std::string::npos; }) ; - BOOST_REQUIRE_EXCEPTION(c.control->get_account( "test3"_n ), fc::exception, + BOOST_REQUIRE_EXCEPTION(c1.control->get_account( "test3"_n ), fc::exception, [a="test3"_n] (const fc::exception& e)->bool { return std::string( e.what() ).find( a.to_string() ) != std::string::npos; }) ; - BOOST_REQUIRE_EXCEPTION(c.control->get_account( "test4"_n ), fc::exception, + BOOST_REQUIRE_EXCEPTION(c1.control->get_account( "test4"_n ), fc::exception, [a="test4"_n] (const fc::exception& e)->bool { return std::string( e.what() ).find( a.to_string() ) != std::string::npos; }) ; // produce block which will apply the unapplied transactions - produce_block_result_t produce_block_result = c.produce_block_ex(fc::milliseconds(config::block_interval_ms), true); - std::vector& traces = produce_block_result.traces; + produce_block_result_t produce_block_result = c1.produce_block_ex(fc::milliseconds(config::block_interval_ms), true); + std::vector& traces = produce_block_result.unapplied_transaction_traces; BOOST_REQUIRE_EQUAL( 4u, traces.size() ); BOOST_CHECK_EQUAL( trace1->id, traces.at(0)->id ); @@ -766,12 +766,12 @@ BOOST_AUTO_TEST_CASE( push_block_returns_forked_transactions ) try { BOOST_CHECK( traces.at(3)->except ); // verify unapplied transactions ran - BOOST_REQUIRE_EQUAL( c.control->get_account( "test1"_n ).name, "test1"_n ); - BOOST_REQUIRE_EQUAL( c.control->get_account( "test2"_n ).name, "test2"_n ); - BOOST_REQUIRE_EQUAL( c.control->get_account( "test3"_n ).name, "test3"_n ); + BOOST_REQUIRE_EQUAL( c1.control->get_account( "test1"_n ).name, "test1"_n ); + BOOST_REQUIRE_EQUAL( c1.control->get_account( "test2"_n ).name, "test2"_n ); + BOOST_REQUIRE_EQUAL( c1.control->get_account( "test3"_n ).name, "test3"_n ); // failed because of tapos to forked out block - BOOST_REQUIRE_EXCEPTION(c.control->get_account( "test4"_n ), fc::exception, + BOOST_REQUIRE_EXCEPTION(c1.control->get_account( "test4"_n ), fc::exception, [a="test4"_n] (const fc::exception& e)->bool { return std::string( e.what() ).find( a.to_string() ) != std::string::npos; }) ; diff --git a/unittests/producer_schedule_if_tests.cpp b/unittests/producer_schedule_if_tests.cpp index 6f83e19992..5a1c4ef391 100644 --- a/unittests/producer_schedule_if_tests.cpp +++ b/unittests/producer_schedule_if_tests.cpp @@ -122,6 +122,19 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t fc::mutable_variant_object()("schedule", schedule_variant), DEFAULT_EXPIRATION_DELTA + (++unique)); }; + auto verify_block_if_ext_producer = [](const signed_block_ptr& block, uint32_t version, account_name new_producer) { + std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional policy_diff = std::get(*ext).new_proposer_policy_diff; + BOOST_TEST_REQUIRE(!!policy_diff); + BOOST_TEST(policy_diff->version == version); + bool new_producer_in_insert = std::ranges::find_if(policy_diff->producer_auth_diff.insert_indexes, + [&](const auto& e) { + return e.second.producer_name == new_producer; + }) != policy_diff->producer_auth_diff.insert_indexes.end(); + BOOST_TEST(new_producer_in_insert); + }; + while (control->head_block_num() < 3) { produce_block(); } @@ -139,12 +152,14 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t // set a new proposer policy sch1 set_producers( {"alice"_n} ); + auto b = produce_block(); + verify_block_if_ext_producer(b, 1, "alice"_n); vector alice_sch = { producer_authority{"alice"_n, block_signing_authority_v0{1, {{get_public_key("alice"_n, "active"), 1}}}} }; // start a round of production - produce_blocks(config::producer_repetitions); + produce_blocks(config::producer_repetitions-1); // sch1 cannot become active before one round of production BOOST_CHECK_EQUAL( 0u, control->active_producers().version ); @@ -156,7 +171,8 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{get_public_key("bob"_n, "active"),1}}}}, producer_authority{"carol"_n, block_signing_authority_v0{ 1, {{get_public_key("carol"_n, "active"),1}}}} }; - produce_block(); + b = produce_block(); + verify_block_if_ext_producer(b, 2u, "bob"_n); // set another ploicy should replace sch2 set_producers( {"bob"_n,"alice"_n} ); @@ -164,9 +180,11 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{get_public_key("bob"_n, "active"),1}}}}, producer_authority{"alice"_n, block_signing_authority_v0{ 1, {{get_public_key("alice"_n, "active"),1}}}} }; + b = produce_block(); + verify_block_if_ext_producer(b, 3u, "alice"_n); // another round - produce_blocks(config::producer_repetitions-1); // -1, already produced one of the round above + produce_blocks(config::producer_repetitions-2); // -2, already produced tow of the round above // sch1 must become active no later than 2 rounds but sch2 cannot become active yet BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); @@ -184,7 +202,13 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t // test no change to active schedule set_producers( {"bob"_n,"alice"_n} ); // same as before, so no change - produce_blocks(config::producer_repetitions); + b = produce_block(); + std::optional ext = b->extract_header_extension(instant_finality_extension::extension_id()); + BOOST_TEST(!!ext); + std::optional policy_diff = std::get(*ext).new_proposer_policy_diff; + BOOST_TEST_REQUIRE(!policy_diff); // no diff + + produce_blocks(config::producer_repetitions-1); produce_blocks(config::producer_repetitions); BOOST_CHECK_EQUAL( 3u, control->active_producers().version ); // should be 3 as not different so no change BOOST_CHECK_EQUAL( true, compare_schedules( bob_alice_sch, control->active_producers() ) ); @@ -208,10 +232,13 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t // test change in same block where there is an existing proposed that is the same set_producers( {"bob"_n,"alice"_n} ); - produce_block(); + b = produce_block(); + verify_block_if_ext_producer(b, 5u, "alice"_n); set_producers( {"bob"_n,"carol"_n} ); set_producers_force({"bob"_n,"carol"_n} ); - produce_blocks(config::producer_repetitions-1); + b = produce_block(); + verify_block_if_ext_producer(b, 6u, "carol"_n); + produce_blocks(config::producer_repetitions-2); produce_blocks(config::producer_repetitions); BOOST_CHECK_EQUAL( 6u, control->active_producers().version ); // should be 6 now as bob,carol now active BOOST_CHECK_EQUAL( true, compare_schedules( bob_carol_sch, control->active_producers() ) ); @@ -238,9 +265,12 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t produce_blocks(config::producer_repetitions-1); // A12 produce_block(); set_producers({"bob"_n,"carol"_n}); // B2 - produce_block(); + b = produce_block(); + verify_block_if_ext_producer(b, 8u, "bob"_n); set_producers({"bob"_n, "alice"_n} ); // P3 - produce_blocks(config::producer_repetitions-2); // B12 + b = produce_block(); + verify_block_if_ext_producer(b, 9u, "alice"_n); + produce_blocks(config::producer_repetitions-3); // B12 produce_block(); // C1 BOOST_CHECK_EQUAL( 7u, control->active_producers().version ); BOOST_CHECK_EQUAL( true, compare_schedules( alice_sch, control->active_producers() ) ); @@ -259,12 +289,17 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t // propose P3 in B3, active in D1, replaces P2 produce_block(); set_producers({"bob"_n,"carol"_n}); // A2, P1 - produce_blocks(config::producer_repetitions-1); // A12 + b = produce_block(); + verify_block_if_ext_producer(b, 10u, "carol"_n); + produce_blocks(config::producer_repetitions-2); // A12 produce_block(); set_producers({"alice"_n}); // B2 - produce_block(); + b = produce_block(); + verify_block_if_ext_producer(b, 11u, "alice"_n); set_producers({"bob"_n,"carol"_n}); // P3 == P1 - produce_blocks(config::producer_repetitions-2); // B12 + b = produce_block(); + verify_block_if_ext_producer(b, 12u, "bob"_n); + produce_blocks(config::producer_repetitions-3); // B12 produce_block(); // C1 BOOST_CHECK_EQUAL( 10u, control->active_producers().version ); BOOST_CHECK_EQUAL( true, compare_schedules( bob_carol_sch, control->active_producers() ) ); @@ -281,25 +316,34 @@ BOOST_FIXTURE_TEST_CASE( proposer_policy_progression_test, validating_tester ) t set_producers({"bob"_n,"carol"_n}); produce_block(); // 2 set_producers({"alice"_n}); - produce_block(); // 3 + b = produce_block(); // 3 + verify_block_if_ext_producer(b, 13u, "alice"_n); set_producers({"carol"_n,"alice"_n}); - produce_block(); // 4 + b = produce_block(); // 4 + verify_block_if_ext_producer(b, 14u, "carol"_n); set_producers({"carol"_n}); produce_block(); // 5 set_producers({"alice"_n}); - produce_block(); // 6 + b = produce_block(); // 6 + verify_block_if_ext_producer(b, 16u, "alice"_n); set_producers({"bob"_n,"carol"_n}); - produce_blocks(config::producer_repetitions-6); + b = produce_block(); + verify_block_if_ext_producer(b, 17u, "bob"_n); + produce_blocks(config::producer_repetitions-7); set_producers({"bob"_n}); produce_block(); // 2 set_producers({"bob"_n,"carol"_n}); - produce_block(); // 3 + b = produce_block(); // 3 + verify_block_if_ext_producer(b, 19u, "carol"_n); set_producers({"carol"_n,"bob"_n}); produce_block(); // 4 set_producers({"alice"_n} ); - produce_block(); // 5 + b = produce_block(); // 5 + verify_block_if_ext_producer(b, 21u, "alice"_n); set_producers({"bob"_n,"carol"_n}); - produce_blocks(config::producer_repetitions-5); // B12 + b = produce_block(); + verify_block_if_ext_producer(b, 22u, "bob"_n); + produce_blocks(config::producer_repetitions-6); // B12 BOOST_CHECK_EQUAL( 17u, control->active_producers().version ); BOOST_CHECK_EQUAL( true, compare_schedules( bob_carol_sch, control->active_producers() ) ); produce_blocks(config::producer_repetitions); diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index 3342019a8c..dc9cf3b433 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1104,9 +1104,9 @@ BOOST_AUTO_TEST_CASE( protocol_activatation_works_after_transition_to_savanna ) std::optional ext = block->extract_header_extension(instant_finality_extension::extension_id()); BOOST_TEST(!!ext); - std::optional fin_policy = std::get(*ext).new_finalizer_policy; - BOOST_TEST(!!fin_policy); - BOOST_TEST(fin_policy->finalizers.size() == accounts.size()); + std::optional fin_policy_diff = std::get(*ext).new_finalizer_policy_diff; + BOOST_TEST(!!fin_policy_diff); + BOOST_TEST(fin_policy_diff->finalizers_diff.insert_indexes.size() == accounts.size()); block = c.produce_block(); // savanna now active auto fb = c.control->fetch_block_by_id(block->calculate_id()); diff --git a/unittests/svnn_ibc_tests.cpp b/unittests/svnn_ibc_tests.cpp index 60ee4062ca..477c945d33 100644 --- a/unittests/svnn_ibc_tests.cpp +++ b/unittests/svnn_ibc_tests.cpp @@ -84,7 +84,7 @@ BOOST_AUTO_TEST_SUITE(svnn_ibc) auto genesis_block = cluster.produce_and_push_block(); // ensure out of scope setup and initial cluster wiring is consistent - BOOST_CHECK_EQUAL(genesis_block->block_num(), 4); + BOOST_CHECK_EQUAL(genesis_block->block_num(), 4u); // check if IF Genesis block contains an IF extension std::optional maybe_genesis_if_ext = @@ -97,15 +97,16 @@ BOOST_AUTO_TEST_SUITE(svnn_ibc) BOOST_CHECK(std::holds_alternative(genesis_if_ext)); // and that it has the expected initial finalizer_policy - std::optional maybe_active_finalizer_policy = - std::get(genesis_if_ext).new_finalizer_policy; + std::optional maybe_active_finalizer_policy_diff = + std::get(genesis_if_ext).new_finalizer_policy_diff; - BOOST_CHECK(maybe_active_finalizer_policy.has_value()); + BOOST_CHECK(maybe_active_finalizer_policy_diff.has_value()); - eosio::chain::finalizer_policy active_finalizer_policy = maybe_active_finalizer_policy.value(); + eosio::chain::finalizer_policy_diff active_finalizer_policy_diff = maybe_active_finalizer_policy_diff.value(); + eosio::chain::finalizer_policy active_finalizer_policy = + eosio::chain::finalizer_policy{}.apply_diff(active_finalizer_policy_diff); - BOOST_CHECK_EQUAL(active_finalizer_policy.finalizers.size(), cluster.num_nodes); - BOOST_CHECK_EQUAL(active_finalizer_policy.generation, 1); + BOOST_CHECK_EQUAL(active_finalizer_policy.generation, 1u); // compute the digest of the finalizer policy auto active_finalizer_policy_digest = fc::sha256::hash(active_finalizer_policy); diff --git a/unittests/vote_processor_tests.cpp b/unittests/vote_processor_tests.cpp index 85c2ca0598..99f3cec753 100644 --- a/unittests/vote_processor_tests.cpp +++ b/unittests/vote_processor_tests.cpp @@ -42,13 +42,14 @@ auto create_genesis_block_state() { // block 2 std::vector finalizers; finalizers.push_back(finalizer_authority{.description = "first", .weight = 1, .public_key = bls_priv_keys.at(0).get_public_key()}); - finalizers.push_back(finalizer_authority{.description = "first", .weight = 1, .public_key = bls_priv_keys.at(1).get_public_key()}); - finalizers.push_back(finalizer_authority{.description = "first", .weight = 1, .public_key = bls_priv_keys.at(2).get_public_key()}); + finalizers.push_back(finalizer_authority{.description = "second", .weight = 1, .public_key = bls_priv_keys.at(1).get_public_key()}); + finalizers.push_back(finalizer_authority{.description = "third", .weight = 1, .public_key = bls_priv_keys.at(2).get_public_key()}); finalizer_policy new_finalizer_policy{.finalizers = finalizers}; + finalizer_policy_diff new_finalizer_policy_diff = finalizer_policy{}.create_diff(new_finalizer_policy); qc_claim_t initial_if_claim { .block_num = 2, .is_strong_qc = false }; emplace_extension(block->header_extensions, instant_finality_extension::extension_id(), - fc::raw::pack(instant_finality_extension{ initial_if_claim, new_finalizer_policy, {} })); + fc::raw::pack(instant_finality_extension{ initial_if_claim, new_finalizer_policy_diff, {} })); producer_authority_schedule schedule = { 0, { producer_authority{block->producer, block_signing_authority_v0{ 1, {{pub_key, 1}} } } } }; auto genesis = std::make_shared();