diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index e0f17e9a4a..c612870b21 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 @@ -156,20 +183,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 +233,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)); 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 f2aa9d872c..9273264e0a 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; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 777743bbaf..bb0474b588 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, @@ -3164,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; @@ -3186,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; @@ -5283,6 +5283,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; @@ -5301,6 +5303,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 22c11318f8..1c4f2718fc 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; @@ -132,6 +132,9 @@ struct block_header_state { 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/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/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..e7ffcfebea --- /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 == 1); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 8d975d4e79..40bacee8a9 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -828,11 +828,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 == 1u); - 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 @@ -861,7 +861,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/unittests/api_tests.cpp b/unittests/api_tests.cpp index 9c3a8eac9f..136d81b99a 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/finalizer_update_tests.cpp b/unittests/finalizer_update_tests.cpp index 0f5d009221..db71dfa21c 100644 --- a/unittests/finalizer_update_tests.cpp +++ b/unittests/finalizer_update_tests.cpp @@ -71,6 +71,18 @@ BOOST_AUTO_TEST_CASE(savanna_set_finalizer_multiple_test) { try { 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); @@ -96,27 +108,56 @@ BOOST_AUTO_TEST_CASE(savanna_set_finalizer_multiple_test) { try { // take to become active // ------------------------------------------------------------------------------ auto pubkeys3 = fin_keys.set_finalizer_policy(3u).pubkeys; - t.produce_block(); + auto b = t.produce_block(); + verify_block_finality_policy_diff(b, 3, pubkeys3.back()); auto pubkeys4 = fin_keys.set_finalizer_policy(4u).pubkeys; - t.produce_block(); + b = t.produce_block(); + verify_block_finality_policy_diff(b, 4, pubkeys4.back()); t.produce_block(); auto pubkeys5 = fin_keys.set_finalizer_policy(5u).pubkeys; - t.produce_blocks(3); - 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, 5, pubkeys5.back()); t.produce_block(); + 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 - t.produce_block(); + 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 - - t.produce_block(); + 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 - t.produce_block(); + 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 // --------------------- for (size_t i=0; i<10; ++i) - ensure_next_block_finalizer_policy(t, 5u, pubkeys5); + 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/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 27b61a8185..477c945d33 100644 --- a/unittests/svnn_ibc_tests.cpp +++ b/unittests/svnn_ibc_tests.cpp @@ -97,14 +97,15 @@ 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, 1u); // compute the digest of the 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();