From e8b2aa24e61f223915e591b8e3dde60125a9ee8f Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 6 Sep 2024 11:23:22 -0400 Subject: [PATCH 01/50] Add test verifying spring 1.0 compatibility. --- unittests/savanna_misc_tests.cpp | 100 +++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index cf92726c83..4a1a1831ba 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -595,4 +595,104 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste } FC_LOG_AND_RETHROW() + +// --------------------------------------------------------------------------------------------------- +// For issue #694, we need to change the finality core of the `block_header_state`, but we want to +// insure that this doesn't create a consensus incompatibility with spring 1.0, so the blocks created +// with newer versions remain compatible (and linkable) by spring 1.0. +// --------------------------------------------------------------------------------------------------- +BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster::cluster_t) try { + using namespace savanna_cluster; + auto& A=_nodes[0]; + const auto& tester_account = "tester"_n;\ + _debug_mode = true; + + // update finalizer_policy with a new key for B + // -------------------------------------------- + base_tester::finalizer_policy_input input; + for (size_t i=0; igeneration; + + A.create_account("currency"_n); // do something so the block is not empty + auto b2 = A.produce_block(); + print("b2", b2); + BOOST_REQUIRE_EQUAL(qc_s(qc(b2)), strong_qc(b1)); // b2 claims a strong QC on b1 + + A.create_account("tester"_n); // do something so the block is not empty + A.create_account("tester2"_n); + + auto b3 = A.produce_block(); + print("b3", b3); + BOOST_REQUIRE_EQUAL(qc_s(qc(b3)), strong_qc(b2)); // b3 claims a strong QC on b2 + BOOST_REQUIRE_EQUAL(A.lib_number, b1->block_num()); // b3 makes B1 final + + auto pending = A.head_pending_finalizer_policy(); + BOOST_REQUIRE(!!pending); // check that we have a pending finalizer policy + auto p2 = pending->generation; // and its generation is higher than the active one + BOOST_REQUIRE_EQUAL(p2, p1 + 1); // b3 has new pending finalizer policy p2 + + const std::vector partition {0}; // partition A so that B, C and D don't see b4 (yet) + set_partition(partition); // and don't vote on it + + // push action so that the block is not empty + A.push_action(config::system_account_name, updateauth::get_name(), tester_account, + fc::mutable_variant_object()("account", "tester")("permission", "first")("parent", "active")( + "auth", authority(A.get_public_key(tester_account, "first")))); + + auto b4 = A.produce_block(); + print("b4", b4); + BOOST_REQUIRE_EQUAL(qc_s(qc(b4)), strong_qc(b3)); // b4 claims a strong QC on b3 + BOOST_REQUIRE_EQUAL(A.lib_number, b2->block_num()); // b4 makes B2 final + + auto b5 = A.produce_block(); + print("b5", b5); + BOOST_REQUIRE(!qc(b5)); // b5 doesn't include a new qc (duplicates b4's strong claim on b3) + BOOST_REQUIRE_EQUAL(A.lib_number, b2->block_num()); // finality unchanged stays at b2 + + set_partition({}); // remove partition so A will receive votes on b4 and b5 + + push_block(0, b4); // other nodes receive b4 and vote on it, so A forms a qc on b4 + auto b6 = A.produce_block(); + print("b6", b6); + BOOST_REQUIRE_EQUAL(qc_s(qc(b6)), strong_qc(b4)); // b6 claims a strong QC on b4 + BOOST_REQUIRE_EQUAL(A.lib_number, b3->block_num()); // b6 makes b3 final. + + push_block(0, b5); + + auto b7 = A.produce_block(); + print("b7", b7); + BOOST_REQUIRE_EQUAL(qc_s(qc(b7)), strong_qc(b5)); // b7 claims a strong QC on b5 + BOOST_REQUIRE_EQUAL(A.lib_number, b3->block_num()); // lib is still b3 + + push_block(0, b6); // push b6 again as it was unlinkable until the other + // nodes received b5 + + auto b8 = A.produce_block(); + print("b8", b8); + BOOST_REQUIRE_EQUAL(qc_s(qc(b8)), strong_qc(b6)); // b8 claims a strong QC on b6 + BOOST_REQUIRE_EQUAL(A.lib_number, b4->block_num()); // b8 makes B4 final + + push_block(0, b7); // push b7 and b8 as they were unlinkable until the other + push_block(0, b8); // nodes received b6 + + auto b9 = A.produce_block(); + print("b9", b9); + BOOST_REQUIRE_EQUAL(qc_s(qc(b9)), strong_qc(b8)); // b9 claims a strong QC on b8 + BOOST_REQUIRE_EQUAL(A.lib_number, b6->block_num()); // b9 makes B6 final + + auto b9_id = b9->calculate_id(); + // std::cout << b9_id << '\n'; + // check that the block id of b9 match what we got with spring 1.0 + BOOST_REQUIRE_EQUAL(b9_id, block_id_type{"0000001396389f4eb681171774e56f7344f0d503a0742617ccc19d91c7db0915"}); + +} FC_LOG_AND_RETHROW() + + BOOST_AUTO_TEST_SUITE_END() From 2ec1e1037c3f87955a99626ebdd041e2ff381680 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Fri, 6 Sep 2024 11:37:54 -0400 Subject: [PATCH 02/50] Minor test update --- unittests/savanna_misc_tests.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index 4a1a1831ba..107d252f45 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -625,9 +625,7 @@ BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster:: print("b2", b2); BOOST_REQUIRE_EQUAL(qc_s(qc(b2)), strong_qc(b1)); // b2 claims a strong QC on b1 - A.create_account("tester"_n); // do something so the block is not empty - A.create_account("tester2"_n); - + A.create_account(tester_account); // do something so the block is not empty auto b3 = A.produce_block(); print("b3", b3); BOOST_REQUIRE_EQUAL(qc_s(qc(b3)), strong_qc(b2)); // b3 claims a strong QC on b2 @@ -687,10 +685,9 @@ BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster:: BOOST_REQUIRE_EQUAL(qc_s(qc(b9)), strong_qc(b8)); // b9 claims a strong QC on b8 BOOST_REQUIRE_EQUAL(A.lib_number, b6->block_num()); // b9 makes B6 final - auto b9_id = b9->calculate_id(); - // std::cout << b9_id << '\n'; // check that the block id of b9 match what we got with spring 1.0 - BOOST_REQUIRE_EQUAL(b9_id, block_id_type{"0000001396389f4eb681171774e56f7344f0d503a0742617ccc19d91c7db0915"}); + auto b9_id = b9->calculate_id(); + BOOST_REQUIRE_EQUAL(b9_id, block_id_type{"00000013725f3d79bd4dd4091d0853d010a320f95240981711a942673ad87918"}); } FC_LOG_AND_RETHROW() From c5826741a02dd1517e6d080f3da780f9a0afba4d Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 11:45:15 -0400 Subject: [PATCH 03/50] Add `active` and `pending` finalize policy generations to `block_ref` --- .../chain/include/eosio/chain/block_header_state.hpp | 5 ++++- libraries/chain/include/eosio/chain/block_state.hpp | 6 +++++- .../chain/include/eosio/chain/finality/finality_core.hpp | 9 +++++++-- unittests/finality_core_tests.cpp | 2 +- unittests/finalizer_tests.cpp | 8 +++++--- unittests/finalizer_vote_tests.cpp | 5 +++-- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 75a02ace84..5f9d7de593 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -164,7 +164,10 @@ struct block_header_state { digest_type compute_base_digest() const; digest_type compute_finality_digest() const; - block_ref make_block_ref() const { return block_ref{block_id, timestamp(), compute_finality_digest() }; } + block_ref make_block_ref() const { + return block_ref{block_id, timestamp(), compute_finality_digest(), finalizer_policy_generation, + pending_finalizer_policy ? pending_finalizer_policy->second->generation : 0}; + } // 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. diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 389caecc36..d1528fdabe 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -127,7 +127,11 @@ struct block_state : public block_header_state { // block_header_state provi return timestamp() > fc::time_point::now() - fc::seconds(30); } - block_ref make_block_ref() const { return block_ref{block_id, timestamp(), strong_digest }; } // use the cached `finality_digest` + // use the cached `finality_digest` + block_ref make_block_ref() const { + return block_ref{block_id, timestamp(), strong_digest, finalizer_policy_generation, + pending_finalizer_policy ? pending_finalizer_policy->second->generation : 0}; + } protocol_feature_activation_set_ptr get_activated_protocol_features() const { return block_header_state::activated_protocol_features; } // build next valid structure from current one with input of next diff --git a/libraries/chain/include/eosio/chain/finality/finality_core.hpp b/libraries/chain/include/eosio/chain/finality/finality_core.hpp index 2ffa3ff54b..da3f57d1fa 100644 --- a/libraries/chain/include/eosio/chain/finality/finality_core.hpp +++ b/libraries/chain/include/eosio/chain/finality/finality_core.hpp @@ -10,16 +10,21 @@ using block_time_type = chain::block_timestamp_type; struct block_ref { - block_ref(const block_id_type& block_id, block_time_type timestamp, const digest_type& finality_digest) + block_ref(const block_id_type& block_id, block_time_type timestamp, const digest_type& finality_digest, + uint32_t active_policy_generation, uint32_t pending_policy_generation) : block_id(block_id) , timestamp(timestamp) - , finality_digest(finality_digest) {} + , finality_digest(finality_digest) + , active_policy_generation(active_policy_generation) + , pending_policy_generation(pending_policy_generation) {} block_ref() = default; // creates `empty` representation where `block_id.empty() == true` block_id_type block_id; block_time_type timestamp; digest_type finality_digest; // finality digest associated with the block + uint32_t active_policy_generation{0}; + uint32_t pending_policy_generation{0}; bool empty() const { return block_id.empty(); } block_num_type block_num() const; // Extract from block_id. diff --git a/unittests/finality_core_tests.cpp b/unittests/finality_core_tests.cpp index 2a2048fcaa..1ab3cee032 100644 --- a/unittests/finality_core_tests.cpp +++ b/unittests/finality_core_tests.cpp @@ -38,7 +38,7 @@ struct test_core { auto prev_block_num = core.current_block_num(); timestamp = timestamp.next(); auto id = id_from_num(prev_block_num); - core = core.next(block_ref {id, timestamp, id}, qc_claim); + core = core.next(block_ref {id, timestamp, id, 1, 0}, qc_claim); // bogus generation numbers, but unused // next block num is previous block number + 1, qc_claim becomes latest_qc_claim BOOST_REQUIRE_EQUAL(core.current_block_num(), prev_block_num + 1); BOOST_REQUIRE(core.latest_qc_claim() == qc_claim); diff --git a/unittests/finalizer_tests.cpp b/unittests/finalizer_tests.cpp index 9ed35fb4ee..81daf66e9b 100644 --- a/unittests/finalizer_tests.cpp +++ b/unittests/finalizer_tests.cpp @@ -39,14 +39,15 @@ template std::vector create_random_fsi(size_t count) { std::vector res; res.reserve(count); + // we use bogus generation numbers in `block_ref` constructor, but these are unused in the test for (size_t i = 0; i < count; ++i) { res.push_back(FSI{ .last_vote = block_ref{sha256::hash("vote"s + std::to_string(i)), tstamp(i * 100 + 3), - sha256::hash("vote_digest"s + std::to_string(i))}, + sha256::hash("vote_digest"s + std::to_string(i)), 1, 0}, .lock = block_ref{sha256::hash("lock"s + std::to_string(i)), tstamp(i * 100), - sha256::hash("lock_digest"s + std::to_string(i))}, + sha256::hash("lock_digest"s + std::to_string(i)), 1, 0}, .other_branch_latest_time = block_timestamp_type{} }); if (i) @@ -62,7 +63,8 @@ std::vector create_proposal_refs(size_t count) { std::string id_str {"vote"}; id_str += std::to_string(i); auto id = sha256::hash(id_str.c_str()); - res.push_back(block_ref{id, tstamp(i), id}); + // we use bogus generation numbers in `block_ref` constructor, but these are unused in the test + res.push_back(block_ref{id, tstamp(i), id, 1, 0}); } return res; } diff --git a/unittests/finalizer_vote_tests.cpp b/unittests/finalizer_vote_tests.cpp index a9fe942b02..15641de0cc 100644 --- a/unittests/finalizer_vote_tests.cpp +++ b/unittests/finalizer_vote_tests.cpp @@ -76,7 +76,8 @@ struct proposal_t { explicit operator block_ref() const { auto id = calculate_id(); - return block_ref{id, timestamp(), id}; // reuse id for the finality_digest which is not used in this test + // we use bogus generation numbers in `block_ref` constructor, but these are unused in the test + return block_ref{id, timestamp(), id, 1, 0}; // reuse id for the finality_digest which is not used in this test } }; @@ -142,7 +143,7 @@ struct simulator_t { bsp_vec.push_back(genesis); forkdb.reset_root(genesis); - block_ref genesis_ref(genesis->id(), genesis->timestamp(), genesis->id()); + block_ref genesis_ref(genesis->id(), genesis->timestamp(), genesis->id(), 1, 0); my_finalizer.fsi = fsi_t{genesis_ref, genesis_ref, {}}; } From d72477bc68c340d0884364982622afe1cb9d7b89 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 11:46:27 -0400 Subject: [PATCH 04/50] Add `pack_for_digest` finction to preserve compatibility with spring 1.0 consensus --- .../eosio/chain/finality/finality_core.hpp | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/finality/finality_core.hpp b/libraries/chain/include/eosio/chain/finality/finality_core.hpp index da3f57d1fa..bac599760e 100644 --- a/libraries/chain/include/eosio/chain/finality/finality_core.hpp +++ b/libraries/chain/include/eosio/chain/finality/finality_core.hpp @@ -191,6 +191,24 @@ struct finality_core * @post returned core has last_final_block_num() >= this->last_final_block_num() */ finality_core next(const block_ref& current_block, const qc_claim_t& most_recent_ancestor_with_qc) const; + + // should match the serialization provided by FC_REFLECT below, except that for compatibility with + // spring 1.0 consensus we do not pack the two new members of `block_ref` which were added in + // spring 1.0.1 (the finalizer policy generations) + // ------------------------------------------------------------------------------------------------ + template + void pack_for_digest(Stream& s) const { + fc::raw::pack(s, links); + + // manually pack the vector of refs since we don't want to pack the generation numbers + fc::raw::pack( s, unsigned_int((uint32_t)refs.size()) ); + for( const auto& ref : refs ) { + fc::raw::pack(s, ref.block_id); + fc::raw::pack(s, ref.timestamp); + fc::raw::pack(s, ref.finality_digest); + } + fc::raw::pack(s, genesis_timestamp); + } }; } /// eosio::chain @@ -220,7 +238,7 @@ namespace std { } } -FC_REFLECT( eosio::chain::block_ref, (block_id)(timestamp)(finality_digest) ) +FC_REFLECT( eosio::chain::block_ref, (block_id)(timestamp)(finality_digest)(active_policy_generation)(pending_policy_generation) ) FC_REFLECT( eosio::chain::block_ref_digest_data, (block_num)(timestamp)(finality_digest)(parent_timestamp) ) FC_REFLECT( eosio::chain::qc_link, (source_block_num)(target_block_num)(is_link_strong) ) FC_REFLECT( eosio::chain::qc_claim_t, (block_num)(is_strong_qc) ) From 926f8059ac2f5392cbc0542634fa87207834363f Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 11:47:55 -0400 Subject: [PATCH 05/50] Add new member `block_header_state::latest_qc_claim_block_active_finalizer_policy` --- libraries/chain/block_header_state.cpp | 2 +- .../chain/include/eosio/chain/block_header_state.hpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 82ac65153b..535aa1dc81 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -16,7 +16,7 @@ digest_type block_header_state::compute_base_digest() const { digest_type::encoder enc; fc::raw::pack( enc, header ); - fc::raw::pack( enc, core ); + core.pack_for_digest( enc ); fc::raw::pack( enc, proposed_finalizer_policies ); fc::raw::pack( enc, pending_finalizer_policy ); diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 5f9d7de593..1e451c0bc1 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -121,12 +121,22 @@ struct block_header_state { // When the block associated with a proposed finalizer policy becomes final, // it becomes pending. std::vector> proposed_finalizer_policies; + // Track in-flight pending finalizer policy. At most one pending // finalizer policy at any moment. // When the block associated with the pending finalizer policy becomes final, // it becomes active. std::optional> pending_finalizer_policy; + // It may be that the `finality_core` references a finalizer policy generation which is neither the active + // or pending one. This can happen when a pending policy became active, replacing the previously active + // policy which would be lost if not tracked in the below member variable. + // When starting from a snapshot, it is critical that all finalizer policies referenced by the finality core + // can still be accessed, since they are needed for validating QCs for blocks as far back as core.latest_qc_claim(). + // This pointer can (and will often) be nullptr, which means that a pending finalizer policy did not + // become active between `core.latest_qc_claim().block_num` and `core.current_block_num()` (inclusive). + finalizer_policy_ptr latest_qc_claim_block_active_finalizer_policy; + // generation increases by one each time a new finalizer_policy is proposed in a block // It matches the finalizer policy generation most recently included in this block's `finality_extension` or its ancestors uint32_t finalizer_policy_generation{1}; @@ -210,7 +220,7 @@ using block_header_state_ptr = std::shared_ptr; FC_REFLECT( eosio::chain::block_header_state, (block_id)(header) (activated_protocol_features)(core)(active_finalizer_policy) (active_proposer_policy)(latest_proposed_proposer_policy)(latest_pending_proposer_policy)(proposed_finalizer_policies) - (pending_finalizer_policy)(finalizer_policy_generation) + (pending_finalizer_policy)(latest_qc_claim_block_active_finalizer_policy)(finalizer_policy_generation) (last_pending_finalizer_policy_digest)(last_pending_finalizer_policy_start_timestamp)) FC_REFLECT( eosio::chain::level_3_commitments_t, (reversible_blocks_mroot)(latest_qc_claim_block_num )(latest_qc_claim_finality_digest)(latest_qc_claim_timestamp)(timestamp)(base_digest)) From 3177015ea195359c0684e3946525bd5d28aa771c Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 11:51:17 -0400 Subject: [PATCH 06/50] Update `std::ostream` output function to display new members. --- .../chain/include/eosio/chain/finality/finality_core.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/chain/include/eosio/chain/finality/finality_core.hpp b/libraries/chain/include/eosio/chain/finality/finality_core.hpp index bac599760e..b1a8f50431 100644 --- a/libraries/chain/include/eosio/chain/finality/finality_core.hpp +++ b/libraries/chain/include/eosio/chain/finality/finality_core.hpp @@ -201,8 +201,8 @@ struct finality_core fc::raw::pack(s, links); // manually pack the vector of refs since we don't want to pack the generation numbers - fc::raw::pack( s, unsigned_int((uint32_t)refs.size()) ); - for( const auto& ref : refs ) { + fc::raw::pack(s, unsigned_int((uint32_t)refs.size())); + for(const auto& ref : refs) { fc::raw::pack(s, ref.block_id); fc::raw::pack(s, ref.timestamp); fc::raw::pack(s, ref.finality_digest); @@ -217,7 +217,8 @@ struct finality_core namespace std { // define std ostream output so we can use BOOST_CHECK_EQUAL in tests inline std::ostream& operator<<(std::ostream& os, const eosio::chain::block_ref& br) { - os << "block_ref(" << br.block_id << ", " << br.timestamp << ", " << br.finality_digest << ")"; + os << "block_ref(" << br.block_id << ", " << br.timestamp << ", " << br.finality_digest << ", " + << br.active_policy_generation << ", " << br.pending_policy_generation << ")"; return os; } From 1f6447e1d0f60fea0259771a1c92e0ff6ead3348 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 12:27:34 -0400 Subject: [PATCH 07/50] Implement `get_finalizer_policies` --- libraries/chain/block_header_state.cpp | 38 +++++++++++++++++++ .../eosio/chain/block_header_state.hpp | 10 +++++ 2 files changed, 48 insertions(+) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 535aa1dc81..cd073cfa78 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -141,6 +141,44 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() return *active_finalizer_policy; } +// Only defined for core.latest_qc_claim().block_num <= block_num <= core.current_block_num() +// ------------------------------------------------------------------------------------------ +finalizer_policies_t block_header_state::get_finalizer_policies(block_num_type block_num) { + finalizer_policies_t res; + + const block_ref& ref = core.get_block_reference(block_num); + res.finality_digest = ref.finality_digest; + + auto active_gen = ref.active_policy_generation; + if (active_finalizer_policy->generation == active_gen) + res.active_finalizer_policy = active_finalizer_policy; + else { + // cannot be the pending one as it never was active + assert(!pending_finalizer_policy || pending_finalizer_policy->second->generation > active_gen); + + // has to be the one in latest_qc_claim_block_active_finalizer_policy + assert(latest_qc_claim_block_active_finalizer_policy != nullptr); + assert(latest_qc_claim_block_active_finalizer_policy->generation == active_gen); + EOS_ASSERT(latest_qc_claim_block_active_finalizer_policy->generation == active_gen, chain_exception, + "Logic error in finalizer policy retrieval"); // just in case + res.active_finalizer_policy = latest_qc_claim_block_active_finalizer_policy; + } + + auto pending_gen = ref.pending_policy_generation; + if (active_finalizer_policy->generation == pending_gen) + res.pending_finalizer_policy = active_finalizer_policy; // policy pending at block_num became active + else { + // cannot be the one in latest_qc_claim_block_active_finalizer_policy since it was active at + // core.latest_qc_claim().block_num + assert(pending_finalizer_policy && pending_finalizer_policy->second->generation == pending_gen); + EOS_ASSERT(pending_finalizer_policy && pending_finalizer_policy->second->generation == pending_gen, chain_exception, + "Logic error in finalizer policy retrieval"); // just in case + res.pending_finalizer_policy = pending_finalizer_policy->second; + } + + return res; +} + // 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 (latest_proposed_proposer_policy) { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 1e451c0bc1..10e53e3095 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -78,6 +78,13 @@ struct block_header_state_input : public building_block_input { digest_type finality_mroot_claim; }; + +struct finalizer_policies_t { + digest_type finality_digest; + finalizer_policy_ptr active_finalizer_policy; // Never null + finalizer_policy_ptr pending_finalizer_policy; // Only null if the block has no pending finalizer policy +}; + struct block_header_state { // ------ data members ------------------------------------------------------------ block_id_type block_id; @@ -205,6 +212,9 @@ struct block_header_state { const finalizer_policy& get_last_pending_finalizer_policy() const; const proposer_policy& get_last_proposed_proposer_policy() const; + // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() + finalizer_policies_t get_finalizer_policies(block_num_type num); + template const Ext* header_extension() const { if (auto itr = header_exts.find(Ext::extension_id()); itr != header_exts.end()) { return &std::get(itr->second); From ac6552acb7464d3bb7f79da4064491ce2e0d6089 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 12:34:49 -0400 Subject: [PATCH 08/50] Check for zero `pending` generation and minor edits. --- libraries/chain/block_header_state.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index cd073cfa78..16b264d3c9 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -150,8 +150,10 @@ finalizer_policies_t block_header_state::get_finalizer_policies(block_num_type b res.finality_digest = ref.finality_digest; auto active_gen = ref.active_policy_generation; + assert(active_gen != 0); // we should always have an active policy + if (active_finalizer_policy->generation == active_gen) - res.active_finalizer_policy = active_finalizer_policy; + res.active_finalizer_policy = active_finalizer_policy; // the one active at block_num is still active else { // cannot be the pending one as it never was active assert(!pending_finalizer_policy || pending_finalizer_policy->second->generation > active_gen); @@ -160,16 +162,18 @@ finalizer_policies_t block_header_state::get_finalizer_policies(block_num_type b assert(latest_qc_claim_block_active_finalizer_policy != nullptr); assert(latest_qc_claim_block_active_finalizer_policy->generation == active_gen); EOS_ASSERT(latest_qc_claim_block_active_finalizer_policy->generation == active_gen, chain_exception, - "Logic error in finalizer policy retrieval"); // just in case + "Logic error in finalizer policy retrieval"); // just in case res.active_finalizer_policy = latest_qc_claim_block_active_finalizer_policy; } auto pending_gen = ref.pending_policy_generation; - if (active_finalizer_policy->generation == pending_gen) - res.pending_finalizer_policy = active_finalizer_policy; // policy pending at block_num became active + if (pending_gen == 0) + res.pending_finalizer_policy = nullptr; // no pending policy at block_num. + else if (pending_gen == active_finalizer_policy->generation) + res.pending_finalizer_policy = active_finalizer_policy; // policy pending at block_num became active else { // cannot be the one in latest_qc_claim_block_active_finalizer_policy since it was active at - // core.latest_qc_claim().block_num + // core.latest_qc_claim().block_num. So it must be the one still pending. assert(pending_finalizer_policy && pending_finalizer_policy->second->generation == pending_gen); EOS_ASSERT(pending_finalizer_policy && pending_finalizer_policy->second->generation == pending_gen, chain_exception, "Logic error in finalizer policy retrieval"); // just in case From af6aced57a713c550c97053b865e88b4b9447642 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 13:06:48 -0400 Subject: [PATCH 09/50] Update `verify_qc` to use `get_finalizer_policies` --- libraries/chain/block_header_state.cpp | 2 +- libraries/chain/block_state.cpp | 5 ++++- libraries/chain/include/eosio/chain/block_header_state.hpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 16b264d3c9..3452bb9d7f 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -143,7 +143,7 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() // Only defined for core.latest_qc_claim().block_num <= block_num <= core.current_block_num() // ------------------------------------------------------------------------------------------ -finalizer_policies_t block_header_state::get_finalizer_policies(block_num_type block_num) { +finalizer_policies_t block_header_state::get_finalizer_policies(block_num_type block_num) const { finalizer_policies_t res; const block_ref& ref = core.get_block_reference(block_num); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index ac7f51652f..0a2af03132 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -187,7 +187,10 @@ vote_status_t block_state::has_voted(const bls_public_key& key) const { // Called from net threads void block_state::verify_qc(const qc_t& qc) const { - aggregating_qc.verify_qc(qc, strong_digest, weak_digest); + finalizer_policies_t policies = get_finalizer_policies(qc.block_num); + aggregating_qc_t aggregating_qc(policies.active_finalizer_policy, policies.pending_finalizer_policy); + + aggregating_qc.verify_qc(qc, policies.finality_digest, create_weak_digest(policies.finality_digest)); } qc_claim_t block_state::extract_qc_claim() const { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 10e53e3095..e338f72e28 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -213,7 +213,7 @@ struct block_header_state { const proposer_policy& get_last_proposed_proposer_policy() const; // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() - finalizer_policies_t get_finalizer_policies(block_num_type num); + finalizer_policies_t get_finalizer_policies(block_num_type num) const; template const Ext* header_extension() const { if (auto itr = header_exts.find(Ext::extension_id()); itr != header_exts.end()) { From dc4e11e0571bb1d21cf14b916896de98bb439c13 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 13:19:46 -0400 Subject: [PATCH 10/50] Support the case where `block_num` is current clock (use `make_block_ref`). --- libraries/chain/block_header_state.cpp | 3 +-- libraries/chain/controller.cpp | 11 ++--------- .../chain/include/eosio/chain/block_header_state.hpp | 2 +- libraries/chain/include/eosio/chain/block_state.hpp | 9 ++++++++- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 3452bb9d7f..de9a3a5cd6 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -143,10 +143,9 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() // Only defined for core.latest_qc_claim().block_num <= block_num <= core.current_block_num() // ------------------------------------------------------------------------------------------ -finalizer_policies_t block_header_state::get_finalizer_policies(block_num_type block_num) const { +finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& ref) const { finalizer_policies_t res; - const block_ref& ref = core.get_block_reference(block_num); res.finality_digest = ref.finality_digest; auto active_gen = ref.active_policy_generation; diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3b7a4f6747..19d36f438e 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3882,7 +3882,7 @@ struct controller_impl { // and quorum_certificate_extension in block extension are valid. // Called from net-threads. It is thread safe as signed_block is never modified after creation. // ----------------------------------------------------------------------------- - void verify_proper_block_exts( const block_id_type& id, const signed_block_ptr& b, const block_header_state& prev ) { + void verify_proper_block_exts( const block_id_type& id, const signed_block_ptr& b, const block_state& prev ) { assert(b->is_proper_svnn_block()); auto qc_ext_id = quorum_certificate_extension::extension_id(); @@ -3970,15 +3970,8 @@ struct controller_impl { "QC is_strong (${s1}) in block extension does not match is_strong_qc (${s2}) in header extension. Block number: ${b}", ("s1", qc_proof.is_strong())("s2", new_qc_claim.is_strong_qc)("b", block_num) ); - // find the claimed block's block state on branch of id - auto bsp = fork_db_fetch_bsp_on_branch_by_num( prev.id(), new_qc_claim.block_num ); - EOS_ASSERT( bsp, - invalid_qc_claim, - "Block state was not found in forkdb for block_num ${q}. Block number: ${b}", - ("q", new_qc_claim.block_num)("b", block_num) ); - // verify the QC proof against the claimed block - bsp->verify_qc(qc_proof); + prev.verify_qc(qc_proof); } void verify_legacy_block_exts( const signed_block_ptr& b, const block_header_state_legacy& prev ) { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index e338f72e28..74deb2a1dc 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -213,7 +213,7 @@ struct block_header_state { const proposer_policy& get_last_proposed_proposer_policy() const; // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() - finalizer_policies_t get_finalizer_policies(block_num_type num) const; + finalizer_policies_t get_finalizer_policies(const block_ref& ref) const; template const Ext* header_extension() const { if (auto itr = header_exts.find(Ext::extension_id()); itr != header_exts.end()) { diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index d1528fdabe..01a29690d9 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -185,7 +185,14 @@ struct block_state : public block_header_state { // block_header_state provi explicit block_state(snapshot_detail::snapshot_block_state_v7&& sbs); void sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority); - void verify_signee(const std::vector& additional_signatures, const block_signing_authority& valid_block_signing_authority) const; + void verify_signee(const std::vector& additional_signatures, + const block_signing_authority& valid_block_signing_authority) const; + + finalizer_policies_t get_finalizer_policies(block_num_type num) const { + if (num == block_num()) + return block_header_state::get_finalizer_policies(make_block_ref()); + return block_header_state::get_finalizer_policies(core.get_block_reference(num)); + } }; using block_state_ptr = std::shared_ptr; From 27725de4c6b36bb8cd50628ad8ec62e12bc317f4 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 17:54:00 -0400 Subject: [PATCH 11/50] Update `latest_qc_claim_block_active_finalizer_policy` and implement `get_active_finalizer_policy_generation` --- libraries/chain/block_header_state.cpp | 22 +++++++++++++++++++ libraries/chain/controller.cpp | 16 +++++--------- .../eosio/chain/block_header_state.hpp | 3 ++- .../chain/include/eosio/chain/block_state.hpp | 2 +- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index de9a3a5cd6..9d3f0a64f7 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -182,6 +182,13 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& return res; } +// Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() +uint32_t block_header_state::get_active_finalizer_policy_generation(block_num_type block_num) const { + const block_ref& ref = core.get_block_reference(block_num); + return ref.active_policy_generation; +} + + // 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 (latest_proposed_proposer_policy) { @@ -366,6 +373,21 @@ void finish_next(const block_header_state& prev, next_header_state.finalizer_policy_generation = prev.finalizer_policy_generation; } + // now populate next_header_state.latest_qc_claim_block_active_finalizer_policy + // this keeps track of the finalizer policy which was active @ latest_qc_claim().block_num, but which + // can be overwritten by a previously pending police (member `active_finalizer_policy`) + // -------------------------------------------------------------------------------------------------- + const auto& next_core = next_header_state.core; + auto latest_qc_claim_block_num = next_core.latest_qc_claim().block_num; + const auto active_generation_num = next_header_state.active_finalizer_policy->generation; + if (next_header_state.get_active_finalizer_policy_generation(latest_qc_claim_block_num) != active_generation_num) { + const auto& latest_qc_claim_block_ref = next_core.get_block_reference(latest_qc_claim_block_num); + next_header_state.latest_qc_claim_block_active_finalizer_policy = + prev.get_finalizer_policies(latest_qc_claim_block_ref).active_finalizer_policy; + } else { + next_header_state.latest_qc_claim_block_active_finalizer_policy = nullptr; + } + // Finally update block id from header // ----------------------------------- next_header_state.block_id = next_header_state.header.calculate_id(); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 6c665f0e8d..ac4f87eab9 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3881,7 +3881,7 @@ struct controller_impl { // Verify basic proper_block invariants. // Called from net-threads. It is thread safe as signed_block is never modified after creation. // ----------------------------------------------------------------------------- - std::optional verify_basic_proper_block_invariants( const signed_block_ptr& b, const block_state& prev ) { + std::optional verify_basic_proper_block_invariants( const signed_block_ptr& b, const block_header_state& prev ) { assert(b->is_proper_svnn_block()); auto qc_ext_id = quorum_certificate_extension::extension_id(); @@ -4070,14 +4070,8 @@ struct controller_impl { } // This verifies BLS signatures and is expensive. - void verify_qc( const signed_block_ptr& b, const block_header_state& prev, const qc_t& qc ) { - // find the claimed block's block state on branch of id - auto bsp = fork_db_fetch_bsp_on_branch_by_num( prev.id(), qc.block_num ); - EOS_ASSERT( bsp, invalid_qc_claim, - "Block state was not found in forkdb for claimed block ${bn}. Current block number: ${b}", - ("bn", qc.block_num)("b", b->block_num()) ); - - bsp->verify_qc(qc); + void verify_qc( const block_state& prev, const qc_t& qc ) { + prev.verify_qc(qc); } // thread safe, expected to be called from thread other than the main thread @@ -4090,7 +4084,7 @@ struct controller_impl { if constexpr (is_proper_savanna_block) { if (qc) { - verify_qc(b, prev, *qc); + verify_qc(prev, *qc); const auto qc_claim = qc->to_qc_claim(); dlog("received block: #${bn} ${t} ${prod} ${id}, qc claim: ${qc_claim}, previous: ${p}", @@ -4336,7 +4330,7 @@ struct controller_impl { if constexpr (std::is_same_v, block_state_ptr>) { if (conf.force_all_checks && qc) { - verify_qc(b, *head, *qc); + verify_qc(*head, *qc); } } diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index e0ce86cdbb..76d6f99d16 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -182,7 +182,7 @@ struct block_header_state : fc::reflect_init { digest_type compute_finality_digest() const; block_ref make_block_ref() const { - return block_ref{block_id, timestamp(), compute_finality_digest(), finalizer_policy_generation, + return block_ref{block_id, timestamp(), compute_finality_digest(), active_finalizer_policy->generation, pending_finalizer_policy ? pending_finalizer_policy->second->generation : 0}; } @@ -214,6 +214,7 @@ struct block_header_state : fc::reflect_init { // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() finalizer_policies_t get_finalizer_policies(const block_ref& ref) const; + uint32_t get_active_finalizer_policy_generation(block_num_type block_num) const; template const Ext* header_extension() const { if (auto itr = header_exts.find(Ext::extension_id()); itr != header_exts.end()) { diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 8556473fd1..3b200a3f2e 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -129,7 +129,7 @@ struct block_state : public block_header_state { // block_header_state provi // use the cached `finality_digest` block_ref make_block_ref() const { - return block_ref{block_id, timestamp(), strong_digest, finalizer_policy_generation, + return block_ref{block_id, timestamp(), strong_digest, active_finalizer_policy->generation, pending_finalizer_policy ? pending_finalizer_policy->second->generation : 0}; } From 8b6a095c4514a489b3a7d62926823365892a1c1c Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 17:56:50 -0400 Subject: [PATCH 12/50] Uncomment `validate_qc_after_restart_from_snapshot` which now passes --- unittests/savanna_misc_tests.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index 107d252f45..b1f001f93a 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -397,7 +397,6 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_after_restart_from_snapshot, savanna_cluster set_partition({0}); // partition A so it doesn't receive blocks on `open()` A.open_from_snapshot(b3_snapshot); -#if 0 // uncomment when issue #694 is fixed. // After starting up from the snapshot, their node receives block b4 from the P2P network. // Since b4 advances the QC claim relative to its parent (from a strong QC claimed on b1 // to a strong QC claimed on b2), it must include a QC attached to justify its claim. @@ -409,7 +408,6 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_after_restart_from_snapshot, savanna_cluster A.push_block(b5); // before b3, we will fail with a `verify_qc_claim` A.push_block(b6); // exception, which is what will happens until issue // #694 is addressed. -#endif } FC_LOG_AND_RETHROW() From 1cf1c5675d964813217240d110ff969e0a7b506f Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 18:28:35 -0400 Subject: [PATCH 13/50] re-enable `validate_qc_requiring_finalizer_policies` testcase. --- libraries/chain/block_header_state.cpp | 11 +++++++---- unittests/savanna_misc_tests.cpp | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 9d3f0a64f7..f1d7112924 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -156,6 +156,7 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& else { // cannot be the pending one as it never was active assert(!pending_finalizer_policy || pending_finalizer_policy->second->generation > active_gen); + std::cout << ref << '\n'; // has to be the one in latest_qc_claim_block_active_finalizer_policy assert(latest_qc_claim_block_active_finalizer_policy != nullptr); @@ -183,8 +184,10 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& } // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() -uint32_t block_header_state::get_active_finalizer_policy_generation(block_num_type block_num) const { - const block_ref& ref = core.get_block_reference(block_num); +uint32_t block_header_state::get_active_finalizer_policy_generation(block_num_type num) const { + if (num == block_num()) + return active_finalizer_policy->generation; + const block_ref& ref = core.get_block_reference(num); return ref.active_policy_generation; } @@ -380,8 +383,8 @@ void finish_next(const block_header_state& prev, const auto& next_core = next_header_state.core; auto latest_qc_claim_block_num = next_core.latest_qc_claim().block_num; const auto active_generation_num = next_header_state.active_finalizer_policy->generation; - if (next_header_state.get_active_finalizer_policy_generation(latest_qc_claim_block_num) != active_generation_num) { - const auto& latest_qc_claim_block_ref = next_core.get_block_reference(latest_qc_claim_block_num); + if (prev.get_active_finalizer_policy_generation(latest_qc_claim_block_num) != active_generation_num) { + const auto& latest_qc_claim_block_ref = prev.core.get_block_reference(latest_qc_claim_block_num); next_header_state.latest_qc_claim_block_active_finalizer_policy = prev.get_finalizer_policies(latest_qc_claim_block_ref).active_finalizer_policy; } else { diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index b1f001f93a..3db92630e7 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -583,7 +583,7 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste set_partition({0}); // partition A so it doesn't receive blocks on `open()` A.open_from_snapshot(b6_snapshot); -#if 0 // uncomment when issue #694 is fixed. +#if 1 // uncomment when issue #694 is fixed. A.push_block(b7); // when pushing b7, if we try to access any block state A.push_block(b8); // before b6, we will fail with a `verify_qc_claim` A.push_block(b9); // exception, which is what will happens until issue From 2419d7dcea965ff1efaf95328380b10f5fc76ff8 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 20:59:48 -0400 Subject: [PATCH 14/50] Update snapshot format to v8 --- libraries/chain/block_header_state.cpp | 1 - libraries/chain/block_state.cpp | 25 ++++++ libraries/chain/controller.cpp | 10 +-- .../eosio/chain/block_header_state.hpp | 1 + .../chain/include/eosio/chain/block_state.hpp | 1 + .../include/eosio/chain/chain_snapshot.hpp | 6 +- .../include/eosio/chain/snapshot_detail.hpp | 88 ++++++++++++++++++- unittests/savanna_misc_tests.cpp | 6 +- 8 files changed, 126 insertions(+), 12 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index f1d7112924..2fc00fc7b9 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -156,7 +156,6 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& else { // cannot be the pending one as it never was active assert(!pending_finalizer_policy || pending_finalizer_policy->second->generation > active_gen); - std::cout << ref << '\n'; // has to be the one in latest_qc_claim_block_active_finalizer_policy assert(latest_qc_claim_block_active_finalizer_policy != nullptr); diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 8d31b8aadd..fdc9732e63 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -208,6 +208,31 @@ block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs) header_exts = header.validate_and_extract_header_extensions(); } +block_state::block_state(snapshot_detail::snapshot_block_state_v8&& sbs) + : block_header_state { + .block_id = sbs.block_id, + .header = std::move(sbs.header), + .activated_protocol_features = std::move(sbs.activated_protocol_features), + .core = std::move(sbs.core), + .active_finalizer_policy = std::move(sbs.active_finalizer_policy), + .active_proposer_policy = std::move(sbs.active_proposer_policy), + .latest_proposed_proposer_policy = std::move(sbs.latest_proposed_proposer_policy), + .latest_pending_proposer_policy = std::move(sbs.latest_pending_proposer_policy), + .proposed_finalizer_policies = std::move(sbs.proposed_finalizer_policies), + .pending_finalizer_policy = std::move(sbs.pending_finalizer_policy), + .latest_qc_claim_block_active_finalizer_policy = std::move(sbs.latest_qc_claim_block_active_finalizer_policy), + .finalizer_policy_generation = sbs.finalizer_policy_generation, + .last_pending_finalizer_policy_digest = sbs.last_pending_finalizer_policy_digest, + .last_pending_finalizer_policy_start_timestamp = sbs.last_pending_finalizer_policy_start_timestamp + } + , strong_digest(compute_finality_digest()) + , weak_digest(create_weak_digest(strong_digest)) + , aggregating_qc(active_finalizer_policy, pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{}) // just in case we receive votes + , valid(std::move(sbs.valid)) +{ + header_exts = header.validate_and_extract_header_extensions(); +} + deque block_state::extract_trxs_metas() { pub_keys_recovered = false; auto result = std::move(cached_trxs); diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index ac4f87eab9..3d0fe930f0 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2206,7 +2206,7 @@ struct controller_impl { }); snapshot->write_section("eosio::chain::block_state", [&]( auto& section ) { - section.add_row(snapshot_detail::snapshot_block_state_data_v7(get_block_state_to_snapshot()), db); + section.add_row(snapshot_detail::snapshot_block_state_data_v8(get_block_state_to_snapshot()), db); }); controller_index_set::walk_indices([this, &snapshot, &row_counter]( auto utils ){ @@ -2255,15 +2255,15 @@ struct controller_impl { }); using namespace snapshot_detail; - using v7 = snapshot_block_state_data_v7; + using v8 = snapshot_block_state_data_v8; block_state_pair result; - if (header.version >= v7::minimum_version) { + if (header.version >= v8::minimum_version) { // loading a snapshot saved by Spring 1.0 and above. // ----------------------------------------------- - if (std::clamp(header.version, v7::minimum_version, v7::maximum_version) == header.version ) { + if (std::clamp(header.version, v8::minimum_version, v8::maximum_version) == header.version ) { snapshot->read_section("eosio::chain::block_state", [this, &result]( auto §ion ){ - v7 block_state_data; + v8 block_state_data; section.read_row(block_state_data, db); assert(block_state_data.bs_l || block_state_data.bs); if (block_state_data.bs_l) { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 76d6f99d16..eb6cfd4df4 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -12,6 +12,7 @@ namespace eosio::chain { namespace snapshot_detail { struct snapshot_block_state_v7; + struct snapshot_block_state_v8; } namespace detail { struct schedule_info; }; diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 3b200a3f2e..15d86b9685 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -183,6 +183,7 @@ struct block_state : public block_header_state { // block_header_state provi const std::optional& action_mroot_savanna); explicit block_state(snapshot_detail::snapshot_block_state_v7&& sbs); + explicit block_state(snapshot_detail::snapshot_block_state_v8&& sbs); void sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority); diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index 680264e20a..642d8389d6 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -26,10 +26,14 @@ struct chain_snapshot_header { * 7: Updated for Spring v1.0.0 release: * - Savanna consensus support * - Each chainbase contract table placed in individual snapshot section instead of commingled "contract_tables" section + * 8: Updated for Spring v1.0.1 release: + * - new member `latest_qc_claim_block_active_finalizer_policy` in `block_header_state` + * - 2 new members (`pending` and `active` policy generations in every `block_ref` of the `finality_core` + * - Spring v1.0.1 is incompatible with v7 format, but can read previous formats */ static constexpr uint32_t minimum_compatible_version = 2; - static constexpr uint32_t current_version = 7; + static constexpr uint32_t current_version = 8; static constexpr uint32_t first_version_with_split_table_sections = 7; diff --git a/libraries/chain/include/eosio/chain/snapshot_detail.hpp b/libraries/chain/include/eosio/chain/snapshot_detail.hpp index 03942caad3..db92c370a5 100644 --- a/libraries/chain/include/eosio/chain/snapshot_detail.hpp +++ b/libraries/chain/include/eosio/chain/snapshot_detail.hpp @@ -152,8 +152,71 @@ namespace eosio::chain::snapshot_detail { } }; -} + /** + * Snapshot V8 Data structures + * --------------------------- + */ + struct snapshot_block_state_v8 { + // from block_header_state + block_id_type block_id; + block_header header; + protocol_feature_activation_set_ptr activated_protocol_features; + finality_core core; + finalizer_policy_ptr active_finalizer_policy; + proposer_policy_ptr active_proposer_policy; + proposer_policy_ptr latest_proposed_proposer_policy; + proposer_policy_ptr latest_pending_proposer_policy; + std::vector> proposed_finalizer_policies; + std::optional> pending_finalizer_policy; + finalizer_policy_ptr latest_qc_claim_block_active_finalizer_policy; + uint32_t finalizer_policy_generation; + digest_type last_pending_finalizer_policy_digest; + block_timestamp_type last_pending_finalizer_policy_start_timestamp; + // from block_state + std::optional valid; + + snapshot_block_state_v8() = default; + + // When adding a member initialization here also update block_state(snapshot_block_state_v8) constructor + explicit snapshot_block_state_v8(const block_state& bs) + : block_id(bs.block_id) + , header(bs.header) + , activated_protocol_features(bs.activated_protocol_features) + , core(bs.core) + , active_finalizer_policy(bs.active_finalizer_policy) + , active_proposer_policy(bs.active_proposer_policy) + , latest_proposed_proposer_policy(bs.latest_proposed_proposer_policy) + , latest_pending_proposer_policy(bs.latest_pending_proposer_policy) + , proposed_finalizer_policies(bs.proposed_finalizer_policies) + , pending_finalizer_policy(bs.pending_finalizer_policy) + , latest_qc_claim_block_active_finalizer_policy(bs.latest_qc_claim_block_active_finalizer_policy) + , finalizer_policy_generation(bs.finalizer_policy_generation) + , last_pending_finalizer_policy_digest(bs.last_pending_finalizer_policy_digest) + , last_pending_finalizer_policy_start_timestamp(bs.last_pending_finalizer_policy_start_timestamp) + , valid(bs.valid) + {} + }; + + struct snapshot_block_state_data_v8 { + static constexpr uint32_t minimum_version = 8; + static constexpr uint32_t maximum_version = 8; + + std::optional bs_l; + std::optional bs; + + snapshot_block_state_data_v8() = default; + + explicit snapshot_block_state_data_v8(const block_state_pair& p) + { + if (p.first) + bs_l = snapshot_block_header_state_legacy_v3(*p.first); + if (p.second) + bs = snapshot_block_state_v8(*p.second); + } + }; + +} FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v2::schedule_info, ( schedule_lib_num ) @@ -216,3 +279,26 @@ FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_data_v7, (bs_l) (bs) ) + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v8, + (block_id) + (header) + (activated_protocol_features) + (core) + (active_finalizer_policy) + (active_proposer_policy) + (latest_proposed_proposer_policy) + (latest_pending_proposer_policy) + (proposed_finalizer_policies) + (pending_finalizer_policy) + (latest_qc_claim_block_active_finalizer_policy) + (finalizer_policy_generation) + (last_pending_finalizer_policy_digest) + (last_pending_finalizer_policy_start_timestamp) + (valid) + ) + +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_data_v8, + (bs_l) + (bs) + ) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index 3db92630e7..4b1b45793e 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -583,12 +583,10 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste set_partition({0}); // partition A so it doesn't receive blocks on `open()` A.open_from_snapshot(b6_snapshot); -#if 1 // uncomment when issue #694 is fixed. A.push_block(b7); // when pushing b7, if we try to access any block state A.push_block(b8); // before b6, we will fail with a `verify_qc_claim` A.push_block(b9); // exception, which is what will happens until issue // #694 is addressed. -#endif } FC_LOG_AND_RETHROW() @@ -602,8 +600,8 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster::cluster_t) try { using namespace savanna_cluster; auto& A=_nodes[0]; - const auto& tester_account = "tester"_n;\ - _debug_mode = true; + const auto& tester_account = "tester"_n; + //_debug_mode = true; // update finalizer_policy with a new key for B // -------------------------------------------- From 3349fd6b8850689625495955b422a90870e7047a Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 21:04:37 -0400 Subject: [PATCH 15/50] Fix issue with accessing current block's `block_ref` in `finality_core`. --- libraries/chain/block_header_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 2fc00fc7b9..f38f802b2d 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -383,7 +383,7 @@ void finish_next(const block_header_state& prev, auto latest_qc_claim_block_num = next_core.latest_qc_claim().block_num; const auto active_generation_num = next_header_state.active_finalizer_policy->generation; if (prev.get_active_finalizer_policy_generation(latest_qc_claim_block_num) != active_generation_num) { - const auto& latest_qc_claim_block_ref = prev.core.get_block_reference(latest_qc_claim_block_num); + const auto& latest_qc_claim_block_ref = next_header_state.core.get_block_reference(latest_qc_claim_block_num); next_header_state.latest_qc_claim_block_active_finalizer_policy = prev.get_finalizer_policies(latest_qc_claim_block_ref).active_finalizer_policy; } else { From dfff2bf2d57bd9a6e21ae3c52e431eed22d97b37 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sat, 7 Sep 2024 21:11:18 -0400 Subject: [PATCH 16/50] Minor changes (whitespace and comment). --- libraries/chain/block_state.cpp | 21 ++++++++++--------- .../include/eosio/chain/chain_snapshot.hpp | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index fdc9732e63..cc066c62ce 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -210,24 +210,25 @@ block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs) block_state::block_state(snapshot_detail::snapshot_block_state_v8&& sbs) : block_header_state { - .block_id = sbs.block_id, - .header = std::move(sbs.header), - .activated_protocol_features = std::move(sbs.activated_protocol_features), - .core = std::move(sbs.core), - .active_finalizer_policy = std::move(sbs.active_finalizer_policy), - .active_proposer_policy = std::move(sbs.active_proposer_policy), + .block_id = sbs.block_id, + .header = std::move(sbs.header), + .activated_protocol_features = std::move(sbs.activated_protocol_features), + .core = std::move(sbs.core), + .active_finalizer_policy = std::move(sbs.active_finalizer_policy), + .active_proposer_policy = std::move(sbs.active_proposer_policy), .latest_proposed_proposer_policy = std::move(sbs.latest_proposed_proposer_policy), .latest_pending_proposer_policy = std::move(sbs.latest_pending_proposer_policy), - .proposed_finalizer_policies = std::move(sbs.proposed_finalizer_policies), - .pending_finalizer_policy = std::move(sbs.pending_finalizer_policy), + .proposed_finalizer_policies = std::move(sbs.proposed_finalizer_policies), + .pending_finalizer_policy = std::move(sbs.pending_finalizer_policy), .latest_qc_claim_block_active_finalizer_policy = std::move(sbs.latest_qc_claim_block_active_finalizer_policy), - .finalizer_policy_generation = sbs.finalizer_policy_generation, + .finalizer_policy_generation = sbs.finalizer_policy_generation, .last_pending_finalizer_policy_digest = sbs.last_pending_finalizer_policy_digest, .last_pending_finalizer_policy_start_timestamp = sbs.last_pending_finalizer_policy_start_timestamp } , strong_digest(compute_finality_digest()) , weak_digest(create_weak_digest(strong_digest)) - , aggregating_qc(active_finalizer_policy, pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{}) // just in case we receive votes + , aggregating_qc(active_finalizer_policy, + pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{}) // just in case we receive votes , valid(std::move(sbs.valid)) { header_exts = header.validate_and_extract_header_extensions(); diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index 642d8389d6..fea6764e7e 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -28,7 +28,7 @@ struct chain_snapshot_header { * - Each chainbase contract table placed in individual snapshot section instead of commingled "contract_tables" section * 8: Updated for Spring v1.0.1 release: * - new member `latest_qc_claim_block_active_finalizer_policy` in `block_header_state` - * - 2 new members (`pending` and `active` policy generations in every `block_ref` of the `finality_core` + * - 2 new members (`pending` and `active` policy generations in every `block_ref` of the `finality_core`) * - Spring v1.0.1 is incompatible with v7 format, but can read previous formats */ From f3d2a71722658bdd560afb356a97e47a86315825 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 8 Sep 2024 10:37:44 -0400 Subject: [PATCH 17/50] Better error message when loading v7 snapshot. --- libraries/chain/controller.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3d0fe930f0..4ed453f813 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2285,8 +2285,10 @@ struct controller_impl { } else { EOS_THROW(snapshot_exception, "Unsupported block_state version"); } + } else if (header.version == 7) { + EOS_THROW(snapshot_exception, "v7 snapshots are not supported anymore in Spring 1.01 and above"); } else { - // loading a snapshot saved by Leap up to version 5. + // loading a snapshot saved by Leap up to version 6. // ------------------------------------------------- auto head_header_state = std::make_shared(); using v2 = snapshot_block_header_state_legacy_v2; From 33c011f8599226604e631e77cee93f877f485ae5 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 8 Sep 2024 10:47:35 -0400 Subject: [PATCH 18/50] Update `snapshot_unit_test` for v8 snapshot, and add reference files. --- unittests/snapshot_tests.cpp | 4 ++-- unittests/snapshots/CMakeLists.txt | 4 ++++ unittests/snapshots/snap_v8.bin.gz | Bin 0 -> 9746 bytes unittests/snapshots/snap_v8.bin.json.gz | Bin 0 -> 29453 bytes unittests/snapshots/snap_v8.json.gz | Bin 0 -> 29137 bytes 5 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 unittests/snapshots/snap_v8.bin.gz create mode 100644 unittests/snapshots/snap_v8.bin.json.gz create mode 100644 unittests/snapshots/snap_v8.json.gz diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 032c018acf..1601abf89a 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -382,10 +382,10 @@ void compatible_versions_test() std::filesystem::copy(source_log_dir / "blocks.index", config.blocks_dir / "blocks.index"); TESTER base_chain(config, *genesis); - std::string current_version = "v7"; + std::string current_version = "v8"; int ordinal = 0; - for(std::string version : {"v2", "v3", "v4" , "v5", "v6", "v7"}) + for(std::string version : {"v2", "v3", "v4", "v5", "v6", "v8"}) // v7 version not supported in spring 1.01 and above { if(save_snapshot && version == current_version) continue; static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt index 1f3735bc6c..c024d5dd78 100644 --- a/unittests/snapshots/CMakeLists.txt +++ b/unittests/snapshots/CMakeLists.txt @@ -23,3 +23,7 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.bin.json.gz ${CMAKE_CURRENT_ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.json.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.json.gz COPYONLY ) + diff --git a/unittests/snapshots/snap_v8.bin.gz b/unittests/snapshots/snap_v8.bin.gz new file mode 100644 index 0000000000000000000000000000000000000000..41ed2848ca391bb1c2fc277e81ac8ae6facfccf5 GIT binary patch literal 9746 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 d#cwRxqpm+LDJd1codS=pA`JJ{{s_c3h@8{ literal 0 HcmV?d00001 diff --git a/unittests/snapshots/snap_v8.json.gz b/unittests/snapshots/snap_v8.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..614de0b72f8160acb4139fdaf275edc24387b218 GIT binary patch literal 29137 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 So)-15jxW6r$3_42U;h^fTT`(B literal 0 HcmV?d00001 From e6fc1f5041937c77330f6fbad5c06190fd26e123 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 8 Sep 2024 18:11:15 -0400 Subject: [PATCH 19/50] Create correct weak digests in tests (they need to be derived from the strong digest). --- unittests/block_state_tests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index 7a02bc5b10..9b71b63968 100644 --- a/unittests/block_state_tests.cpp +++ b/unittests/block_state_tests.cpp @@ -17,7 +17,7 @@ BOOST_AUTO_TEST_SUITE(block_state_tests) BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { digest_type block_id(fc::sha256("0000000000000000000000000000001")); digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); - weak_digest_t weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + weak_digest_t weak_digest(create_weak_digest(strong_digest)); const size_t num_finalizers = 3; @@ -147,7 +147,7 @@ void do_quorum_test(const std::vector& weights, bool include_pending) { digest_type block_id(fc::sha256("0000000000000000000000000000001")); digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); - auto weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + auto weak_digest(create_weak_digest(strong_digest)); // initialize a set of private keys std::vector active_private_keys { @@ -310,7 +310,7 @@ BOOST_AUTO_TEST_CASE(quorum_test) try { BOOST_AUTO_TEST_CASE(verify_qc_test) try { // prepare digests digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); - auto weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + auto weak_digest(create_weak_digest(strong_digest)); // initialize a set of private keys std::vector active_private_keys { @@ -556,7 +556,7 @@ BOOST_AUTO_TEST_CASE(verify_qc_test) try { BOOST_AUTO_TEST_CASE(verify_qc_test_with_pending) try { // prepare digests digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); - auto weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + auto weak_digest(create_weak_digest(strong_digest)); // initialize a set of private keys std::vector active_private_keys { @@ -845,7 +845,7 @@ BOOST_AUTO_TEST_CASE(verify_qc_test_with_pending) try { BOOST_AUTO_TEST_CASE(verify_qc_dual_finalizers) try { // prepare digests digest_type strong_digest(fc::sha256("0000000000000000000000000000002")); - auto weak_digest(create_weak_digest(fc::sha256("0000000000000000000000000000003"))); + auto weak_digest(create_weak_digest(strong_digest)); // initialize a set of private keys std::vector active_private_keys { From 549dd7f0820e2cdf9ed51c76b4ed3030b6923b4a Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 8 Sep 2024 18:24:56 -0400 Subject: [PATCH 20/50] Fix incorrect initialization of pending policy generation in `block_state_tests.cpp` --- unittests/block_state_tests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index 9b71b63968..fc877d9e12 100644 --- a/unittests/block_state_tests.cpp +++ b/unittests/block_state_tests.cpp @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { { // all finalizers can aggregate votes with pending block_state_ptr bsp = std::make_shared(); bsp->active_finalizer_policy = std::make_shared( 10, 15, active_finalizers ); - bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( 10, 15, pending_finalizers ) }; + bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( 11, 15, pending_finalizers ) }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; @@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try { { // public key does not exist in active & pending finalizer sets block_state_ptr bsp = std::make_shared(); bsp->active_finalizer_policy = std::make_shared( 10, 15, active_finalizers ); - bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( 10, 15, pending_finalizers ) }; + bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( 11, 15, pending_finalizers ) }; bsp->strong_digest = strong_digest; bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; @@ -595,7 +595,7 @@ BOOST_AUTO_TEST_CASE(verify_qc_test_with_pending) try { constexpr uint32_t generation = 1; constexpr uint64_t threshold = 4; // 2/3 of total weights of 6 bsp->active_finalizer_policy = std::make_shared( generation, threshold, active_finalizers ); - bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation, threshold, pending_finalizers ) }; + bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation+1, threshold, pending_finalizers ) }; bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; @@ -617,7 +617,7 @@ BOOST_AUTO_TEST_CASE(verify_qc_test_with_pending) try { qc_sig_t active_qc_sig{strong_votes, {}, active_agg_sig}; qc_sig_t pending_qc_sig{strong_votes, {}, pending_agg_sig}; qc_t qc{bsp->block_num(), active_qc_sig, pending_qc_sig}; - + bsp->verify_qc(qc); BOOST_CHECK_NO_THROW( bsp->verify_qc(qc) ); } @@ -886,7 +886,7 @@ BOOST_AUTO_TEST_CASE(verify_qc_dual_finalizers) try { constexpr uint32_t generation = 1; constexpr uint64_t threshold = 8; // 2/3 of total weights of 12 bsp->active_finalizer_policy = std::make_shared( generation, threshold, active_finalizers ); - bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation, threshold, pending_finalizers ) }; + bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation+1, threshold, pending_finalizers ) }; bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; From dafa750b2faf9c7dc895574e8d32c9e0c278a1e7 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Sun, 8 Sep 2024 18:37:57 -0400 Subject: [PATCH 21/50] No need to populate `bsp->aggregating_qc` for `verify_qc` --- unittests/block_state_tests.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index fc877d9e12..4a65e69d38 100644 --- a/unittests/block_state_tests.cpp +++ b/unittests/block_state_tests.cpp @@ -334,7 +334,6 @@ BOOST_AUTO_TEST_CASE(verify_qc_test) try { constexpr uint32_t generation = 1; constexpr uint64_t threshold = 4; // 2/3 of total weights of 6 bsp->active_finalizer_policy = std::make_shared( generation, threshold, active_finalizers ); - bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, {} }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; @@ -596,7 +595,6 @@ BOOST_AUTO_TEST_CASE(verify_qc_test_with_pending) try { constexpr uint64_t threshold = 4; // 2/3 of total weights of 6 bsp->active_finalizer_policy = std::make_shared( generation, threshold, active_finalizers ); bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation+1, threshold, pending_finalizers ) }; - bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; @@ -887,7 +885,6 @@ BOOST_AUTO_TEST_CASE(verify_qc_dual_finalizers) try { constexpr uint64_t threshold = 8; // 2/3 of total weights of 12 bsp->active_finalizer_policy = std::make_shared( generation, threshold, active_finalizers ); bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation+1, threshold, pending_finalizers ) }; - bsp->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; From a1979b5ae5e819028e740fc5bc8a8839acaa767c Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 09:12:19 -0400 Subject: [PATCH 22/50] Maintain fsi serialization file format unchanged (not saving generation numbers) --- libraries/chain/finality/finalizer.cpp | 78 +++++++++++++++++++------- unittests/finalizer_tests.cpp | 9 +-- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/libraries/chain/finality/finalizer.cpp b/libraries/chain/finality/finalizer.cpp index 75790288ac..2ef20ec618 100644 --- a/libraries/chain/finality/finalizer.cpp +++ b/libraries/chain/finality/finalizer.cpp @@ -190,6 +190,43 @@ void my_finalizers_t::maybe_update_fsi(const block_state_ptr& bsp, const qc_t& r } } + +// ------------------------------------------------------------------------------------------------- +// Finalizer Safety File i/o +// ------------------------------------------------------------------------------------------------- + +// pack/unpack block_ref (omitting generation numbers) +// --------------------------------------------------- +template +void pack_v0(Stream& s, const block_ref& ref) { + fc::raw::pack(s, ref.block_id); + fc::raw::pack(s, ref.timestamp); + fc::raw::pack(s, ref.finality_digest); +} + +template +void unpack_v0(Stream& s, block_ref& ref) { + fc::raw::unpack(s, ref.block_id); + fc::raw::unpack(s, ref.timestamp); + fc::raw::unpack(s, ref.finality_digest); +} + +// pack/unpack v1 fsi (last_vote and lock omitting generation numbers) +// ------------------------------------------------------------------- +template +void pack_v1(Stream& s, const finalizer_safety_information& fsi) { + pack_v0(s, fsi.last_vote); + pack_v0(s, fsi.lock); + fc::raw::pack(s, fsi.other_branch_latest_time); +} + +template +void unpack_v1(Stream& s, finalizer_safety_information& fsi) { + unpack_v0(s, fsi.last_vote); + unpack_v0(s, fsi.lock); + fc::raw::unpack(s, fsi.other_branch_latest_time); +} + bool my_finalizers_t::save_finalizer_safety_info() const { try { if (!cfile_ds.is_open()) { @@ -210,7 +247,7 @@ bool my_finalizers_t::save_finalizer_safety_info() const { // finalizers not configured anymore. for (const auto& [pub_key, fsi] : inactive_safety_info) { fc::raw::pack(persist_file, pub_key); - fc::raw::pack(persist_file, fsi); + pack_v1(persist_file, fsi); } inactive_safety_info_written_pos = persist_file.tellp(); inactive_crc32 = persist_file.crc(); @@ -221,7 +258,7 @@ bool my_finalizers_t::save_finalizer_safety_info() const { // active finalizers for (const auto& [pub_key, f] : finalizers) { fc::raw::pack(persist_file, pub_key); - fc::raw::pack(persist_file, f.fsi); + pack_v1(persist_file, f.fsi); } uint32_t cs = persist_file.checksum(); @@ -233,29 +270,25 @@ bool my_finalizers_t::save_finalizer_safety_info() const { return false; } -// ---------------------------------------------------------------------------------------- - -// Corresponds to safety_file_version_0 -struct finalizer_safety_information_v0 { - block_ref last_vote; - block_ref lock; - bool votes_forked_since_latest_strong_vote {false}; -}; void my_finalizers_t::load_finalizer_safety_info_v0(fsi_map& res) { uint64_t num_finalizers {0}; fc::raw::unpack(persist_file, num_finalizers); for (size_t i=0; i& finalizer_keys) { if (finalizer_keys.empty()) @@ -398,5 +436,3 @@ void my_finalizers_t::set_default_safety_information(const fsi_t& fsi) { } } // namespace eosio::chain - -FC_REFLECT(eosio::chain::finalizer_safety_information_v0, (last_vote)(lock)(votes_forked_since_latest_strong_vote)); diff --git a/unittests/finalizer_tests.cpp b/unittests/finalizer_tests.cpp index 81daf66e9b..fe7f820d0d 100644 --- a/unittests/finalizer_tests.cpp +++ b/unittests/finalizer_tests.cpp @@ -39,15 +39,16 @@ template std::vector create_random_fsi(size_t count) { std::vector res; res.reserve(count); - // we use bogus generation numbers in `block_ref` constructor, but these are unused in the test + // generation numbers in `block_ref` constructor have to be 0 as they are not saved in fsi the file, + // but compared to loaded ones which get the default values of 0. for (size_t i = 0; i < count; ++i) { res.push_back(FSI{ .last_vote = block_ref{sha256::hash("vote"s + std::to_string(i)), tstamp(i * 100 + 3), - sha256::hash("vote_digest"s + std::to_string(i)), 1, 0}, + sha256::hash("vote_digest"s + std::to_string(i)), 0, 0}, .lock = block_ref{sha256::hash("lock"s + std::to_string(i)), tstamp(i * 100), - sha256::hash("lock_digest"s + std::to_string(i)), 1, 0}, + sha256::hash("lock_digest"s + std::to_string(i)), 0, 0}, .other_branch_latest_time = block_timestamp_type{} }); if (i) @@ -64,7 +65,7 @@ std::vector create_proposal_refs(size_t count) { id_str += std::to_string(i); auto id = sha256::hash(id_str.c_str()); // we use bogus generation numbers in `block_ref` constructor, but these are unused in the test - res.push_back(block_ref{id, tstamp(i), id, 1, 0}); + res.push_back(block_ref{id, tstamp(i), id, 0, 0}); // generation numbers both 0 as not saved in fsi file } return res; } From 33cd6abf78a7fa207fde59b5b3b71c63f386d351 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 10:50:08 -0400 Subject: [PATCH 23/50] Fix typo in `unittests/snapshots/CMakeLists.txt` --- unittests/snapshots/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt index c024d5dd78..3d8d35e1d2 100644 --- a/unittests/snapshots/CMakeLists.txt +++ b/unittests/snapshots/CMakeLists.txt @@ -23,7 +23,7 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.bin.json.gz ${CMAKE_CURRENT_ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.json.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.json.gz COPYONLY ) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.gz COPYONLY ) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.json.gz COPYONLY ) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v8.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v8.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v8.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.json.gz COPYONLY ) From fd087fe8b920d6948b8170f9693d4b43cb1d525b Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 11:25:50 -0400 Subject: [PATCH 24/50] Update comments. --- libraries/chain/controller.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4ed453f813..5f66fba081 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2259,8 +2259,8 @@ struct controller_impl { block_state_pair result; if (header.version >= v8::minimum_version) { - // loading a snapshot saved by Spring 1.0 and above. - // ----------------------------------------------- + // loading a snapshot saved by Spring 1.0.1 and above. + // --------------------------------------------------- if (std::clamp(header.version, v8::minimum_version, v8::maximum_version) == header.version ) { snapshot->read_section("eosio::chain::block_state", [this, &result]( auto §ion ){ v8 block_state_data; @@ -2286,6 +2286,9 @@ struct controller_impl { EOS_THROW(snapshot_exception, "Unsupported block_state version"); } } else if (header.version == 7) { + // snapshot created with Spring 1.0, which was very soon superseded by Spring 1.0.1 + // and a new snapshot format. + // -------------------------------------------------------------------------------- EOS_THROW(snapshot_exception, "v7 snapshots are not supported anymore in Spring 1.01 and above"); } else { // loading a snapshot saved by Leap up to version 6. From 6dad5bb1e2b88c6627e453d7e8c2273c5d1689ee Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 11:36:30 -0400 Subject: [PATCH 25/50] Add comment for `block_state::verify_qc()`. --- libraries/chain/block_state.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index cc066c62ce..547206cebd 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -259,6 +259,10 @@ vote_status_t block_state::has_voted(const bls_public_key& key) const { // Called from net threads void block_state::verify_qc(const qc_t& qc) const { + // Do not use block_state::aggregating_qc which applies only for `this` block. + // verify_qc() can be called on a descendant `block_state` of `qc.block_num`, so we need + // to create a new `aggregating_qc_t` with the finalizer policies of the claimed block. + // ------------------------------------------------------------------------------------- finalizer_policies_t policies = get_finalizer_policies(qc.block_num); aggregating_qc_t aggregating_qc(policies.active_finalizer_policy, policies.pending_finalizer_policy); From c1a760bc8841dfd2ba669f95a98dc21cc4d18aab Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 11:59:19 -0400 Subject: [PATCH 26/50] Update some comments. --- libraries/chain/block_header_state.cpp | 10 ++++++++-- libraries/chain/block_state.cpp | 9 ++++++--- .../chain/include/eosio/chain/snapshot_detail.hpp | 2 ++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index f38f802b2d..e259ffba57 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -141,8 +141,10 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() return *active_finalizer_policy; } -// Only defined for core.latest_qc_claim().block_num <= block_num <= core.current_block_num() -// ------------------------------------------------------------------------------------------ +// Only defined for core.latest_qc_claim().block_num <= ref.block_num() <= core.current_block_num() +// Retrieves the finalizer policies applicatble for the block referenced by `ref`. +// See full explanation in issue #694. +// ------------------------------------------------------------------------------------------------ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& ref) const { finalizer_policies_t res; @@ -183,6 +185,9 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& } // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() +// Retrieves the active finalizer policy generation applicatble for the block `num`, which +// can be the current block or one of its ancestors up to core.latest_qc_claim().block_num (incl). +// ----------------------------------------------------------------------------------------------- uint32_t block_header_state::get_active_finalizer_policy_generation(block_num_type num) const { if (num == block_num()) return active_finalizer_policy->generation; @@ -378,6 +383,7 @@ void finish_next(const block_header_state& prev, // now populate next_header_state.latest_qc_claim_block_active_finalizer_policy // this keeps track of the finalizer policy which was active @ latest_qc_claim().block_num, but which // can be overwritten by a previously pending police (member `active_finalizer_policy`) + // See full explanation in issue #694. // -------------------------------------------------------------------------------------------------- const auto& next_core = next_header_state.core; auto latest_qc_claim_block_num = next_core.latest_qc_claim().block_num; diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 547206cebd..2bb5371ee6 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -208,6 +208,9 @@ block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs) header_exts = header.validate_and_extract_header_extensions(); } +// Spring 1.01 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies +// generation numbers. Also new member `block_state::latest_qc_claim_block_active_finalizer_policy` +// ------------------------------------------------------------------------------------------------ block_state::block_state(snapshot_detail::snapshot_block_state_v8&& sbs) : block_header_state { .block_id = sbs.block_id, @@ -259,10 +262,10 @@ vote_status_t block_state::has_voted(const bls_public_key& key) const { // Called from net threads void block_state::verify_qc(const qc_t& qc) const { - // Do not use block_state::aggregating_qc which applies only for `this` block. - // verify_qc() can be called on a descendant `block_state` of `qc.block_num`, so we need + // Do not use `block_state::aggregating_qc` which applies only for `this` block. + // `verify_qc()` can be called on a descendant `block_state` of `qc.block_num`, so we need // to create a new `aggregating_qc_t` with the finalizer policies of the claimed block. - // ------------------------------------------------------------------------------------- + // --------------------------------------------------------------------------------------- finalizer_policies_t policies = get_finalizer_policies(qc.block_num); aggregating_qc_t aggregating_qc(policies.active_finalizer_policy, policies.pending_finalizer_policy); diff --git a/libraries/chain/include/eosio/chain/snapshot_detail.hpp b/libraries/chain/include/eosio/chain/snapshot_detail.hpp index db92c370a5..15d57348a2 100644 --- a/libraries/chain/include/eosio/chain/snapshot_detail.hpp +++ b/libraries/chain/include/eosio/chain/snapshot_detail.hpp @@ -155,6 +155,8 @@ namespace eosio::chain::snapshot_detail { /** * Snapshot V8 Data structures * --------------------------- + * Spring 1.01 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies + * generation numbers. Also new member `block_state::latest_qc_claim_block_active_finalizer_policy` */ struct snapshot_block_state_v8 { // from block_header_state From 98ff3a56383e36ffbbd762b3e08d8b1930f29ea4 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 12:04:07 -0400 Subject: [PATCH 27/50] Remove unused data structures related to unsupported snapshot v7 format. --- libraries/chain/block_state.cpp | 24 ------ .../chain/include/eosio/chain/block_state.hpp | 1 - .../include/eosio/chain/snapshot_detail.hpp | 84 ------------------- 3 files changed, 109 deletions(-) diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 2bb5371ee6..817efeff07 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -184,30 +184,6 @@ block_state_ptr block_state::create_transition_block( return result_ptr; } -block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs) - : block_header_state { - .block_id = sbs.block_id, - .header = std::move(sbs.header), - .activated_protocol_features = std::move(sbs.activated_protocol_features), - .core = std::move(sbs.core), - .active_finalizer_policy = std::move(sbs.active_finalizer_policy), - .active_proposer_policy = std::move(sbs.active_proposer_policy), - .latest_proposed_proposer_policy = std::move(sbs.latest_proposed_proposer_policy), - .latest_pending_proposer_policy = std::move(sbs.latest_pending_proposer_policy), - .proposed_finalizer_policies = std::move(sbs.proposed_finalizer_policies), - .pending_finalizer_policy = std::move(sbs.pending_finalizer_policy), - .finalizer_policy_generation = sbs.finalizer_policy_generation, - .last_pending_finalizer_policy_digest = sbs.last_pending_finalizer_policy_digest, - .last_pending_finalizer_policy_start_timestamp = sbs.last_pending_finalizer_policy_start_timestamp - } - , strong_digest(compute_finality_digest()) - , weak_digest(create_weak_digest(strong_digest)) - , aggregating_qc(active_finalizer_policy, pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{}) // just in case we receive votes - , valid(std::move(sbs.valid)) -{ - header_exts = header.validate_and_extract_header_extensions(); -} - // Spring 1.01 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies // generation numbers. Also new member `block_state::latest_qc_claim_block_active_finalizer_policy` // ------------------------------------------------------------------------------------------------ diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 15d86b9685..f40ed31f98 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -182,7 +182,6 @@ struct block_state : public block_header_state { // block_header_state provi bool skip_validate_signee, const std::optional& action_mroot_savanna); - explicit block_state(snapshot_detail::snapshot_block_state_v7&& sbs); explicit block_state(snapshot_detail::snapshot_block_state_v8&& sbs); void sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority); diff --git a/libraries/chain/include/eosio/chain/snapshot_detail.hpp b/libraries/chain/include/eosio/chain/snapshot_detail.hpp index 15d57348a2..69b83380f8 100644 --- a/libraries/chain/include/eosio/chain/snapshot_detail.hpp +++ b/libraries/chain/include/eosio/chain/snapshot_detail.hpp @@ -90,68 +90,6 @@ namespace eosio::chain::snapshot_detail { {} }; - /** - * Snapshot V7 Data structures - * --------------------------- - */ - struct snapshot_block_state_v7 { - // from block_header_state - block_id_type block_id; - block_header header; - protocol_feature_activation_set_ptr activated_protocol_features; - finality_core core; - finalizer_policy_ptr active_finalizer_policy; - proposer_policy_ptr active_proposer_policy; - proposer_policy_ptr latest_proposed_proposer_policy; - proposer_policy_ptr latest_pending_proposer_policy; - std::vector> proposed_finalizer_policies; - std::optional> pending_finalizer_policy; - uint32_t finalizer_policy_generation; - digest_type last_pending_finalizer_policy_digest; - block_timestamp_type last_pending_finalizer_policy_start_timestamp; - - // from block_state - std::optional valid; - - snapshot_block_state_v7() = default; - - // When adding a member initialization here also update block_state(snapshot_block_state_v7) constructor - explicit snapshot_block_state_v7(const block_state& bs) - : block_id(bs.block_id) - , header(bs.header) - , activated_protocol_features(bs.activated_protocol_features) - , core(bs.core) - , active_finalizer_policy(bs.active_finalizer_policy) - , active_proposer_policy(bs.active_proposer_policy) - , latest_proposed_proposer_policy(bs.latest_proposed_proposer_policy) - , latest_pending_proposer_policy(bs.latest_pending_proposer_policy) - , proposed_finalizer_policies(bs.proposed_finalizer_policies) - , pending_finalizer_policy(bs.pending_finalizer_policy) - , finalizer_policy_generation(bs.finalizer_policy_generation) - , last_pending_finalizer_policy_digest(bs.last_pending_finalizer_policy_digest) - , last_pending_finalizer_policy_start_timestamp(bs.last_pending_finalizer_policy_start_timestamp) - , valid(bs.valid) - {} - }; - - struct snapshot_block_state_data_v7 { - static constexpr uint32_t minimum_version = 7; - static constexpr uint32_t maximum_version = 7; - - std::optional bs_l; - std::optional bs; - - snapshot_block_state_data_v7() = default; - - explicit snapshot_block_state_data_v7(const block_state_pair& p) - { - if (p.first) - bs_l = snapshot_block_header_state_legacy_v3(*p.first); - if (p.second) - bs = snapshot_block_state_v7(*p.second); - } - }; - /** * Snapshot V8 Data structures * --------------------------- @@ -260,28 +198,6 @@ FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v3 ( additional_signatures ) ) -FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v7, - (block_id) - (header) - (activated_protocol_features) - (core) - (active_finalizer_policy) - (active_proposer_policy) - (latest_proposed_proposer_policy) - (latest_pending_proposer_policy) - (proposed_finalizer_policies) - (pending_finalizer_policy) - (finalizer_policy_generation) - (last_pending_finalizer_policy_digest) - (last_pending_finalizer_policy_start_timestamp) - (valid) - ) - -FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_data_v7, - (bs_l) - (bs) - ) - FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v8, (block_id) (header) From ef67a548a7cc648ff8b4fddcf4f6db8607f621aa Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 13:44:33 -0400 Subject: [PATCH 28/50] Update comment in test. --- unittests/savanna_misc_tests.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index 4b1b45793e..c3950cba14 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -535,9 +535,10 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste BOOST_REQUIRE_EQUAL(active->generation, p2); // b6 has active finalizer policy p2 BOOST_REQUIRE(!A.head_pending_finalizer_policy()); // and no pending finalizer policy. - auto b6_snapshot = A.snapshot(); + // At this point, in the Spring 1.0 implementation, policy P2 is lost from the block_header_state + // of B6, which is the source of the problem - // At this point, in the current implementation policy ... + auto b6_snapshot = A.snapshot(); push_block(0, b5); From 5a08f6e64cf045cbb99d915542f8b23b80fb7b45 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 13:44:51 -0400 Subject: [PATCH 29/50] Update fork_db version to `3`. Previous version `2` is not valid anymore. --- libraries/chain/fork_database.cpp | 18 +++++++++++++----- .../include/eosio/chain/fork_database.hpp | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 440226eabb..9fefcb3e98 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -646,10 +646,16 @@ namespace eosio::chain { std::ofstream out( fork_db_file.generic_string().c_str(), std::ios::out | std::ios::binary | std::ofstream::trunc ); fc::raw::pack( out, magic_number ); - fc::raw::pack( out, max_supported_version ); // write out current version which is always max_supported_version - // version == 1 -> legacy - // version == 2 -> savanna (two possible fork_db, one containing `block_state_legacy`, - // one containing `block_state`) + + // write out current version which is always max_supported_version + // version == 1 -> legacy + // version == 2 -> Spring 1.0 + // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) + // unsupported by Spring 1.01 and above + // version == 3 -> Spring 1.01 updated block_header_state (core with policy gen #) + // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) + // --------------------------------------------------------------------------------------------------------- + fc::raw::pack( out, max_supported_version ); fc::raw::pack(out, static_cast(in_use_value)); @@ -691,6 +697,8 @@ namespace eosio::chain { uint32_t version = 0; fc::raw::unpack( ds, version ); + EOS_ASSERT( version != 2, fork_database_exception, + "Version 2 of fork_database (created by Spring 1.0) is not supported" ); EOS_ASSERT( version >= fork_database::min_supported_version && version <= fork_database::max_supported_version, fork_database_exception, "Unsupported version of fork database file '${filename}'. " @@ -706,7 +714,7 @@ namespace eosio::chain { break; } - case 2: + case 3: { // ---------- Savanna format ---------------------------- uint32_t in_use_raw; diff --git a/libraries/chain/include/eosio/chain/fork_database.hpp b/libraries/chain/include/eosio/chain/fork_database.hpp index c0d12873ab..daac6f7bd3 100644 --- a/libraries/chain/include/eosio/chain/fork_database.hpp +++ b/libraries/chain/include/eosio/chain/fork_database.hpp @@ -302,6 +302,6 @@ namespace eosio::chain { // Update max_supported_version if the persistent file format changes. static constexpr uint32_t min_supported_version = 1; - static constexpr uint32_t max_supported_version = 2; + static constexpr uint32_t max_supported_version = 3; }; } /// eosio::chain From 1127abfe3ed6d90963ef2625d4fa570f41e94b00 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Mon, 9 Sep 2024 18:04:58 -0400 Subject: [PATCH 30/50] Add versioning to `chain_head.dat`, and load only version 1 files with updated `block_header_state`. --- libraries/chain/block_handle.cpp | 31 +++++++++++++++++++++++++------ unittests/finalizer_tests.cpp | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index 3855a6f4f6..05289bd079 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -4,6 +4,16 @@ namespace eosio::chain { +// ------------------------------------------------------------------------------------------- +// prior to writing magic and version numbers, we simply serialized the class (*this) to +// file. Let's call this the implicit version 0, which is not supported anymore in +// Spring 1.01 and above. +// However, we need to make sure that `chain_head_magic` can't match the tag of a std::variant +// ------------------------------------------------------------------------------------------- +constexpr uint64_t chain_head_magic = 0xf1f2f3f4f4f3f2f1; +constexpr uint64_t chain_head_version = 1; + + void block_handle::write(const std::filesystem::path& state_file) { if (!is_valid()) return; @@ -13,25 +23,34 @@ void block_handle::write(const std::filesystem::path& state_file) { fc::datastream f; f.set_file_path(state_file); f.open("wb"); - + fc::raw::pack(f, chain_head_magic); + fc::raw::pack(f, chain_head_version); fc::raw::pack(f, *this); } bool block_handle::read(const std::filesystem::path& state_file) { - if (!std::filesystem::exists(state_file)) - return false; + bool res = false; + + if (!std::filesystem::exists(state_file) || std::filesystem::file_size(state_file) <= 2 * sizeof(chain_head_magic)) + return res; fc::datastream f; f.set_file_path(state_file); f.open("rb"); - fc::raw::unpack(f, *this); + uint64_t magic, version; + fc::raw::unpack(f, magic); + fc::raw::unpack(f, version); - ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); + if (magic == chain_head_magic && version == chain_head_version) { + fc::raw::unpack(f, *this); + ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); + res = true; + } std::filesystem::remove(state_file); - return true; + return res; } } /// namespace eosio::chain diff --git a/unittests/finalizer_tests.cpp b/unittests/finalizer_tests.cpp index fe7f820d0d..e44f228138 100644 --- a/unittests/finalizer_tests.cpp +++ b/unittests/finalizer_tests.cpp @@ -39,7 +39,7 @@ template std::vector create_random_fsi(size_t count) { std::vector res; res.reserve(count); - // generation numbers in `block_ref` constructor have to be 0 as they are not saved in fsi the file, + // generation numbers in `block_ref` constructor have to be 0 as they are not saved in the fsi file, // but compared to loaded ones which get the default values of 0. for (size_t i = 0; i < count; ++i) { res.push_back(FSI{ From 0f4f6fb9e533704ca028502a869dd692b93f012f Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 10 Sep 2024 14:10:02 -0400 Subject: [PATCH 31/50] Add a couple asserts as suggested in PR review. --- libraries/chain/block_header_state.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index e259ffba57..587ca777d1 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -146,6 +146,8 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() // See full explanation in issue #694. // ------------------------------------------------------------------------------------------------ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& ref) const { + assert(core.links.empty() || // called from a bogus block_state constructed in a test + (core.latest_qc_claim().block_num <= ref.block_num() && ref.block_num() <= core.current_block_num())); finalizer_policies_t res; res.finality_digest = ref.finality_digest; @@ -189,6 +191,8 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& // can be the current block or one of its ancestors up to core.latest_qc_claim().block_num (incl). // ----------------------------------------------------------------------------------------------- uint32_t block_header_state::get_active_finalizer_policy_generation(block_num_type num) const { + assert(core.links.empty() || // called from a bogus block_state constructed in a test + (core.latest_qc_claim().block_num <= num && num <= core.current_block_num())); if (num == block_num()) return active_finalizer_policy->generation; const block_ref& ref = core.get_block_reference(num); From 98677ef5c99f84f5b7d1915e7bbe8df3d7f15802 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 10 Sep 2024 14:25:47 -0400 Subject: [PATCH 32/50] Address PR comments regarding loading the `chain_head.dat` file. --- libraries/chain/block_handle.cpp | 35 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index 05289bd079..0134c2186f 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -31,25 +31,32 @@ void block_handle::write(const std::filesystem::path& state_file) { bool block_handle::read(const std::filesystem::path& state_file) { bool res = false; - if (!std::filesystem::exists(state_file) || std::filesystem::file_size(state_file) <= 2 * sizeof(chain_head_magic)) + if (!std::filesystem::exists(state_file)) return res; - fc::datastream f; - f.set_file_path(state_file); - f.open("rb"); - - uint64_t magic, version; - fc::raw::unpack(f, magic); - fc::raw::unpack(f, version); - - if (magic == chain_head_magic && version == chain_head_version) { - fc::raw::unpack(f, *this); - ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); - res = true; + if (std::filesystem::file_size(state_file) <= 2 * sizeof(chain_head_magic)) { + try { + fc::datastream f; + f.set_file_path(state_file); + f.open("rb"); + + uint64_t magic, version; + fc::raw::unpack(f, magic); + fc::raw::unpack(f, version); + + EOS_ASSERT(magic == chain_head_magic && version == chain_head_version, chain_exception, + "Error reading chain_head.dat file. It is likely a Spring 1.0 version which is not supported by Spring 1.01 and above" + "The best course of action might be to restart from a v8 snapshot" ); + + fc::raw::unpack(f, *this); + ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); + res = true; + } catch (...) { + throw; + } } std::filesystem::remove(state_file); - return res; } From 50fbcb72544468b1e20dd28ca4c76df1fec3bdfc Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 10 Sep 2024 14:31:51 -0400 Subject: [PATCH 33/50] Fix typo in previous commit --- libraries/chain/block_handle.cpp | 41 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index 0134c2186f..f3eac7c8ea 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -34,26 +34,27 @@ bool block_handle::read(const std::filesystem::path& state_file) { if (!std::filesystem::exists(state_file)) return res; - if (std::filesystem::file_size(state_file) <= 2 * sizeof(chain_head_magic)) { - try { - fc::datastream f; - f.set_file_path(state_file); - f.open("rb"); - - uint64_t magic, version; - fc::raw::unpack(f, magic); - fc::raw::unpack(f, version); - - EOS_ASSERT(magic == chain_head_magic && version == chain_head_version, chain_exception, - "Error reading chain_head.dat file. It is likely a Spring 1.0 version which is not supported by Spring 1.01 and above" - "The best course of action might be to restart from a v8 snapshot" ); - - fc::raw::unpack(f, *this); - ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); - res = true; - } catch (...) { - throw; - } + EOS_ASSERT(std::filesystem::file_size(state_file) >= 2 * sizeof(chain_head_magic), chain_exception, + "File `chain_head.dat` seems to be corrupted. The best course of action might be to restart from a snapshot (v8 or above)" ); + + try { + fc::datastream f; + f.set_file_path(state_file); + f.open("rb"); + + uint64_t magic, version; + fc::raw::unpack(f, magic); + fc::raw::unpack(f, version); + + EOS_ASSERT(magic == chain_head_magic && version == chain_head_version, chain_exception, + "Error reading `chain_head.dat` file. It is likely a Spring 1.0 version which is not supported by Spring 1.01 and above" + "The best course of action might be to restart from a snapshot (v8 or above)" ); + + fc::raw::unpack(f, *this); + ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); + res = true; + } catch (...) { + throw; } std::filesystem::remove(state_file); From 43e7ab54332336a0188aa5a0871cf9263569365e Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 10 Sep 2024 14:43:49 -0400 Subject: [PATCH 34/50] Update comments. --- libraries/chain/controller.cpp | 2 +- libraries/chain/fork_database.cpp | 6 +++--- unittests/snapshot_tests.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 5f66fba081..32c37f1ad5 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2289,7 +2289,7 @@ struct controller_impl { // snapshot created with Spring 1.0, which was very soon superseded by Spring 1.0.1 // and a new snapshot format. // -------------------------------------------------------------------------------- - EOS_THROW(snapshot_exception, "v7 snapshots are not supported anymore in Spring 1.01 and above"); + EOS_THROW(snapshot_exception, "v7 snapshots are not supported anymore in Spring 1.0.1 and above"); } else { // loading a snapshot saved by Leap up to version 6. // ------------------------------------------------- diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 9fefcb3e98..7980fbaf4d 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -649,10 +649,10 @@ namespace eosio::chain { // write out current version which is always max_supported_version // version == 1 -> legacy - // version == 2 -> Spring 1.0 + // version == 2 -> Spring 1.0.0 // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) // unsupported by Spring 1.01 and above - // version == 3 -> Spring 1.01 updated block_header_state (core with policy gen #) + // version == 3 -> Spring 1.0.1 updated block_header_state (core with policy gen #) // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) // --------------------------------------------------------------------------------------------------------- fc::raw::pack( out, max_supported_version ); @@ -698,7 +698,7 @@ namespace eosio::chain { uint32_t version = 0; fc::raw::unpack( ds, version ); EOS_ASSERT( version != 2, fork_database_exception, - "Version 2 of fork_database (created by Spring 1.0) is not supported" ); + "Version 2 of fork_database (created by Spring 1.0.0) is not supported" ); EOS_ASSERT( version >= fork_database::min_supported_version && version <= fork_database::max_supported_version, fork_database_exception, "Unsupported version of fork database file '${filename}'. " diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 1601abf89a..5cd5a24032 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -385,7 +385,7 @@ void compatible_versions_test() std::string current_version = "v8"; int ordinal = 0; - for(std::string version : {"v2", "v3", "v4", "v5", "v6", "v8"}) // v7 version not supported in spring 1.01 and above + for(std::string version : {"v2", "v3", "v4", "v5", "v6", "v8"}) // v7 version not supported in spring 1.0.1 and above { if(save_snapshot && version == current_version) continue; static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); From 6b89dc1d082432fe1af0f25aeba8bdba567e339d Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 10 Sep 2024 14:57:44 -0400 Subject: [PATCH 35/50] Update comments as per PR review. --- libraries/chain/block_handle.cpp | 2 +- libraries/chain/block_header_state.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index f3eac7c8ea..1eefba35af 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -7,7 +7,7 @@ namespace eosio::chain { // ------------------------------------------------------------------------------------------- // prior to writing magic and version numbers, we simply serialized the class (*this) to // file. Let's call this the implicit version 0, which is not supported anymore in -// Spring 1.01 and above. +// Spring 1.0.1 and above. // However, we need to make sure that `chain_head_magic` can't match the tag of a std::variant // ------------------------------------------------------------------------------------------- constexpr uint64_t chain_head_magic = 0xf1f2f3f4f4f3f2f1; diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 587ca777d1..cce5d741f4 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -386,7 +386,7 @@ void finish_next(const block_header_state& prev, // now populate next_header_state.latest_qc_claim_block_active_finalizer_policy // this keeps track of the finalizer policy which was active @ latest_qc_claim().block_num, but which - // can be overwritten by a previously pending police (member `active_finalizer_policy`) + // can be overwritten by a previously pending policy (member `active_finalizer_policy`) // See full explanation in issue #694. // -------------------------------------------------------------------------------------------------- const auto& next_core = next_header_state.core; From e6c7331eb14f1781be3473b10c3b3b61bcc9cee4 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 10 Sep 2024 17:23:52 -0400 Subject: [PATCH 36/50] Slightly simplify `block_handle::read` --- libraries/chain/block_handle.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index 1eefba35af..a95fb018aa 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -29,10 +29,8 @@ void block_handle::write(const std::filesystem::path& state_file) { } bool block_handle::read(const std::filesystem::path& state_file) { - bool res = false; - if (!std::filesystem::exists(state_file)) - return res; + return false; EOS_ASSERT(std::filesystem::file_size(state_file) >= 2 * sizeof(chain_head_magic), chain_exception, "File `chain_head.dat` seems to be corrupted. The best course of action might be to restart from a snapshot (v8 or above)" ); @@ -52,13 +50,13 @@ bool block_handle::read(const std::filesystem::path& state_file) { fc::raw::unpack(f, *this); ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); - res = true; } catch (...) { throw; } + // remove the `chain_head.dat` file only if we were able to successfully load it. std::filesystem::remove(state_file); - return res; + return true; } } /// namespace eosio::chain From 008e1e5b6a5aab3debc4660d5d4f839b5f7f2eca Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:07:14 -0400 Subject: [PATCH 37/50] Update block_handle comments --- libraries/chain/block_handle.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index a95fb018aa..ade36c56ed 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -33,7 +33,7 @@ bool block_handle::read(const std::filesystem::path& state_file) { return false; EOS_ASSERT(std::filesystem::file_size(state_file) >= 2 * sizeof(chain_head_magic), chain_exception, - "File `chain_head.dat` seems to be corrupted. The best course of action might be to restart from a snapshot (v8 or above)" ); + "File `chain_head.dat` seems to be corrupted. The best course of action might be to restart from a snapshot" ); try { fc::datastream f; @@ -45,8 +45,8 @@ bool block_handle::read(const std::filesystem::path& state_file) { fc::raw::unpack(f, version); EOS_ASSERT(magic == chain_head_magic && version == chain_head_version, chain_exception, - "Error reading `chain_head.dat` file. It is likely a Spring 1.0 version which is not supported by Spring 1.01 and above" - "The best course of action might be to restart from a snapshot (v8 or above)" ); + "Error reading `chain_head.dat` file. It is likely a Spring 1.0.0 version which is not supported by Spring 1.0.1 and above" + "The best course of action might be to restart from a snapshot" ); fc::raw::unpack(f, *this); ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); From 8dfe2823940715f21f57ff44b1ec2600df157751 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:14:49 -0400 Subject: [PATCH 38/50] Use `FC_CAPTURE_AND_RETHROW` instead of just rethrowing the exception. --- libraries/chain/block_handle.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index ade36c56ed..3190e0c28d 100644 --- a/libraries/chain/block_handle.cpp +++ b/libraries/chain/block_handle.cpp @@ -50,9 +50,7 @@ bool block_handle::read(const std::filesystem::path& state_file) { fc::raw::unpack(f, *this); ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); - } catch (...) { - throw; - } + } FC_CAPTURE_AND_RETHROW( (state_file) ); // remove the `chain_head.dat` file only if we were able to successfully load it. std::filesystem::remove(state_file); From e3cdb7c8127968243dd824f66a392d0e3456aa2b Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:26:04 -0400 Subject: [PATCH 39/50] Update comment --- libraries/chain/fork_database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 7980fbaf4d..89d8db9ebe 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -651,7 +651,7 @@ namespace eosio::chain { // version == 1 -> legacy // version == 2 -> Spring 1.0.0 // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) - // unsupported by Spring 1.01 and above + // unsupported by Spring 1.0.1 and above // version == 3 -> Spring 1.0.1 updated block_header_state (core with policy gen #) // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) // --------------------------------------------------------------------------------------------------------- From 1068e0f341d7cd6074878893fcbe1581b89a6c09 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:28:14 -0400 Subject: [PATCH 40/50] Update comment --- libraries/chain/block_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index 817efeff07..3302a56363 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -184,7 +184,7 @@ block_state_ptr block_state::create_transition_block( return result_ptr; } -// Spring 1.01 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies +// Spring 1.0.1 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies // generation numbers. Also new member `block_state::latest_qc_claim_block_active_finalizer_policy` // ------------------------------------------------------------------------------------------------ block_state::block_state(snapshot_detail::snapshot_block_state_v8&& sbs) From fa686db91f87c71ea08255f6a194b3c8587ada42 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:30:44 -0400 Subject: [PATCH 41/50] Update comment --- libraries/chain/controller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index df01756228..ec1cd0c6ae 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2292,9 +2292,9 @@ struct controller_impl { EOS_THROW(snapshot_exception, "Unsupported block_state version"); } } else if (header.version == 7) { - // snapshot created with Spring 1.0, which was very soon superseded by Spring 1.0.1 + // snapshot created with Spring 1.0.0, which was very soon superseded by Spring 1.0.1 // and a new snapshot format. - // -------------------------------------------------------------------------------- + // ---------------------------------------------------------------------------------- EOS_THROW(snapshot_exception, "v7 snapshots are not supported anymore in Spring 1.0.1 and above"); } else { // loading a snapshot saved by Leap up to version 6. From 43622f20d47511d7e70b27c76e429ca2cf794326 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:34:24 -0400 Subject: [PATCH 42/50] Add precondition comments --- libraries/chain/include/eosio/chain/block_header_state.hpp | 2 ++ libraries/chain/include/eosio/chain/block_state.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index eb6cfd4df4..940dd60ab4 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -215,6 +215,8 @@ struct block_header_state : fc::reflect_init { // Only defined for core.latest_qc_claim().block_num <= num <= core.current_block_num() finalizer_policies_t get_finalizer_policies(const block_ref& ref) const; + + // Defined for core.last_final_block_num().block_num <= num <= core.current_block_num() uint32_t get_active_finalizer_policy_generation(block_num_type block_num) const; template const Ext* header_extension() const { diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index de8369de32..75b3282568 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -186,6 +186,7 @@ struct block_state : public block_header_state { // block_header_state provi void sign(const signer_callback_type& signer, const block_signing_authority& valid_block_signing_authority); + // Only defined for latest_qc_block_num() <= num <= block_num() finalizer_policies_t get_finalizer_policies(block_num_type num) const { if (num == block_num()) return block_header_state::get_finalizer_policies(make_block_ref()); From 4164a34b1f15871df1885844128842619d023dec Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:37:33 -0400 Subject: [PATCH 43/50] Be less restrictive with precondition --- libraries/chain/block_header_state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index cce5d741f4..608026bb4f 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -192,7 +192,7 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& // ----------------------------------------------------------------------------------------------- uint32_t block_header_state::get_active_finalizer_policy_generation(block_num_type num) const { assert(core.links.empty() || // called from a bogus block_state constructed in a test - (core.latest_qc_claim().block_num <= num && num <= core.current_block_num())); + (core.last_final_block_num() <= num && num <= core.current_block_num())); if (num == block_num()) return active_finalizer_policy->generation; const block_ref& ref = core.get_block_reference(num); From 0dd7a3639f78f350fae5bcf7ecbaa20972337226 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:43:42 -0400 Subject: [PATCH 44/50] Add clarification in comment --- libraries/chain/include/eosio/chain/block_header_state.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 940dd60ab4..035b97130c 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -143,6 +143,10 @@ struct block_header_state : fc::reflect_init { // can still be accessed, since they are needed for validating QCs for blocks as far back as core.latest_qc_claim(). // This pointer can (and will often) be nullptr, which means that a pending finalizer policy did not // become active between `core.latest_qc_claim().block_num` and `core.current_block_num()` (inclusive). + // + // note: It is also possible for the latest final block (which also is tracked in the finality_core) to have + // an active finalizer policy that is still not being tracked, but we don't care about that as it is not needed + // for QC verification. finalizer_policy_ptr latest_qc_claim_block_active_finalizer_policy; // generation increases by one each time a new finalizer_policy is proposed in a block From ced3e553b6a4b0500bc80efb05ae6c781e24c641 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:47:58 -0400 Subject: [PATCH 45/50] Fix typo in comment, and update `EOS_ASSERT` to avoid UB --- libraries/chain/block_header_state.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 608026bb4f..5fb703e52d 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -142,7 +142,7 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() } // Only defined for core.latest_qc_claim().block_num <= ref.block_num() <= core.current_block_num() -// Retrieves the finalizer policies applicatble for the block referenced by `ref`. +// Retrieves the finalizer policies applicable for the block referenced by `ref`. // See full explanation in issue #694. // ------------------------------------------------------------------------------------------------ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& ref) const { @@ -164,8 +164,10 @@ finalizer_policies_t block_header_state::get_finalizer_policies(const block_ref& // has to be the one in latest_qc_claim_block_active_finalizer_policy assert(latest_qc_claim_block_active_finalizer_policy != nullptr); assert(latest_qc_claim_block_active_finalizer_policy->generation == active_gen); - EOS_ASSERT(latest_qc_claim_block_active_finalizer_policy->generation == active_gen, chain_exception, - "Logic error in finalizer policy retrieval"); // just in case + EOS_ASSERT(latest_qc_claim_block_active_finalizer_policy != nullptr && + latest_qc_claim_block_active_finalizer_policy->generation == active_gen, + chain_exception, + "Logic error in finalizer policy retrieval"); // just in case res.active_finalizer_policy = latest_qc_claim_block_active_finalizer_policy; } From edf294dee52b5f314bab2a52928de9d2a06a1eb6 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:51:47 -0400 Subject: [PATCH 46/50] Update comments --- unittests/snapshot_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 5cd5a24032..4a856310cd 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -385,7 +385,7 @@ void compatible_versions_test() std::string current_version = "v8"; int ordinal = 0; - for(std::string version : {"v2", "v3", "v4", "v5", "v6", "v8"}) // v7 version not supported in spring 1.0.1 and above + for(std::string version : {"v2", "v3", "v4", "v5", "v6", "v8"}) // v7 version not supported in Spring 1.0.1 and above { if(save_snapshot && version == current_version) continue; static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); @@ -411,7 +411,7 @@ void compatible_versions_test() // 1. update `current_version` and the list of versions in `for` loop // 2. run: `unittests/unit_test -t "snapshot_tests/test_com*" -- --save-snapshot` to generate new snapshot files // 3. copy the newly generated files (see `ls -lrth ./unittests/snapshots/snap_*` to `spring/unittests/snapshots` - // for example `cp ./unittests/snapshots/snap_v7.* ../unittests/snapshots` + // for example `cp ./unittests/snapshots/snap_v8.* ../unittests/snapshots` // 4. edit `unittests/snapshots/CMakeLists.txt` and add the `configure_file` commands for the 3 new files. // now the test should pass. // 5. add the 3 new snapshot files in git. From 1bdd27645b5ab83eaa630970d9ccc55e7241d2f8 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:55:14 -0400 Subject: [PATCH 47/50] Remove unsupported snapshot v7 file references --- unittests/snapshots/CMakeLists.txt | 3 --- unittests/snapshots/snap_v7.bin.gz | Bin 9746 -> 0 bytes unittests/snapshots/snap_v7.bin.json.gz | Bin 29453 -> 0 bytes unittests/snapshots/snap_v7.json.gz | Bin 29136 -> 0 bytes 4 files changed, 3 deletions(-) delete mode 100644 unittests/snapshots/snap_v7.bin.gz delete mode 100644 unittests/snapshots/snap_v7.bin.json.gz delete mode 100644 unittests/snapshots/snap_v7.json.gz diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt index 3d8d35e1d2..bd35723a3f 100644 --- a/unittests/snapshots/CMakeLists.txt +++ b/unittests/snapshots/CMakeLists.txt @@ -20,9 +20,6 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v5.bin.json.gz ${CMAKE_CURRENT_ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v6.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v6.json.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v6.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v6.bin.json.gz COPYONLY ) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.gz COPYONLY ) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.json.gz COPYONLY ) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v7.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v7.bin.json.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v8.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v8.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.json.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v8.bin.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v8.bin.json.gz COPYONLY ) diff --git a/unittests/snapshots/snap_v7.bin.gz b/unittests/snapshots/snap_v7.bin.gz deleted file mode 100644 index 5159d929db1e0bb18f938ea8c5d7c360dfe6fc25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9746 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 UZFJ=|R|l*HboD33{`0^7FUDTrx&QzG From d272c2517ef206052e74fb9bec037c073e6d5be7 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 08:59:40 -0400 Subject: [PATCH 48/50] Fix comments. --- unittests/savanna_misc_tests.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index c3950cba14..c0ec641bdf 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -535,8 +535,8 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste BOOST_REQUIRE_EQUAL(active->generation, p2); // b6 has active finalizer policy p2 BOOST_REQUIRE(!A.head_pending_finalizer_policy()); // and no pending finalizer policy. - // At this point, in the Spring 1.0 implementation, policy P2 is lost from the block_header_state - // of B6, which is the source of the problem + // At this point, in the Spring 1.0.0 implementation (which has the bug described in issue #694), + // policy P2 is lost from the block_header_state of B6, which is the source of the problem auto b6_snapshot = A.snapshot(); @@ -593,11 +593,11 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste -// --------------------------------------------------------------------------------------------------- +// ---------------------------------------------------------------------------------------------------- // For issue #694, we need to change the finality core of the `block_header_state`, but we want to -// insure that this doesn't create a consensus incompatibility with spring 1.0, so the blocks created -// with newer versions remain compatible (and linkable) by spring 1.0. -// --------------------------------------------------------------------------------------------------- +// ensure that this doesn't create a consensus incompatibility with spring 1.0.0, so the blocks created +// with newer versions remain compatible (and linkable) by spring 1.0.0. +// ---------------------------------------------------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster::cluster_t) try { using namespace savanna_cluster; auto& A=_nodes[0]; From 5e591b16c1e81fc43c63fcfb6ded98a447ce4123 Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 09:00:51 -0400 Subject: [PATCH 49/50] Missed this instance of `Spring 1.0`. --- unittests/savanna_misc_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index c0ec641bdf..6951a7c54b 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -682,7 +682,7 @@ BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster:: BOOST_REQUIRE_EQUAL(qc_s(qc(b9)), strong_qc(b8)); // b9 claims a strong QC on b8 BOOST_REQUIRE_EQUAL(A.lib_number, b6->block_num()); // b9 makes B6 final - // check that the block id of b9 match what we got with spring 1.0 + // check that the block id of b9 match what we got with spring 1.0.0 auto b9_id = b9->calculate_id(); BOOST_REQUIRE_EQUAL(b9_id, block_id_type{"00000013725f3d79bd4dd4091d0853d010a320f95240981711a942673ad87918"}); From 5426c8598a0ba0b9b3d6cd1e23b763aa5ba1727c Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Wed, 11 Sep 2024 09:20:54 -0400 Subject: [PATCH 50/50] Cleanup some more references to Spring versions in comments. --- .../chain/include/eosio/chain/finality/finality_core.hpp | 4 ++-- libraries/chain/include/eosio/chain/snapshot_detail.hpp | 2 +- unittests/savanna_misc_tests.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/chain/include/eosio/chain/finality/finality_core.hpp b/libraries/chain/include/eosio/chain/finality/finality_core.hpp index b1a8f50431..78fa2dd49e 100644 --- a/libraries/chain/include/eosio/chain/finality/finality_core.hpp +++ b/libraries/chain/include/eosio/chain/finality/finality_core.hpp @@ -193,8 +193,8 @@ struct finality_core finality_core next(const block_ref& current_block, const qc_claim_t& most_recent_ancestor_with_qc) const; // should match the serialization provided by FC_REFLECT below, except that for compatibility with - // spring 1.0 consensus we do not pack the two new members of `block_ref` which were added in - // spring 1.0.1 (the finalizer policy generations) + // Spring 1.0.0 consensus we do not pack the two new members of `block_ref` which were added in + // Spring 1.0.1 (the finalizer policy generations) // ------------------------------------------------------------------------------------------------ template void pack_for_digest(Stream& s) const { diff --git a/libraries/chain/include/eosio/chain/snapshot_detail.hpp b/libraries/chain/include/eosio/chain/snapshot_detail.hpp index 69b83380f8..43253967eb 100644 --- a/libraries/chain/include/eosio/chain/snapshot_detail.hpp +++ b/libraries/chain/include/eosio/chain/snapshot_detail.hpp @@ -93,7 +93,7 @@ namespace eosio::chain::snapshot_detail { /** * Snapshot V8 Data structures * --------------------------- - * Spring 1.01 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies + * Spring 1.0.1 to ? snapshot v8 format. Updated `finality_core` to include finalizer policies * generation numbers. Also new member `block_state::latest_qc_claim_block_active_finalizer_policy` */ struct snapshot_block_state_v8 { diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index 6951a7c54b..4b65b76ff9 100644 --- a/unittests/savanna_misc_tests.cpp +++ b/unittests/savanna_misc_tests.cpp @@ -595,8 +595,8 @@ BOOST_FIXTURE_TEST_CASE(validate_qc_requiring_finalizer_policies, savanna_cluste // ---------------------------------------------------------------------------------------------------- // For issue #694, we need to change the finality core of the `block_header_state`, but we want to -// ensure that this doesn't create a consensus incompatibility with spring 1.0.0, so the blocks created -// with newer versions remain compatible (and linkable) by spring 1.0.0. +// ensure that this doesn't create a consensus incompatibility with Spring 1.0.0, so the blocks created +// with newer versions remain compatible (and linkable) by Spring 1.0.0. // ---------------------------------------------------------------------------------------------------- BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster::cluster_t) try { using namespace savanna_cluster; @@ -682,7 +682,7 @@ BOOST_FIXTURE_TEST_CASE(verify_spring_1_0_block_compatibitity, savanna_cluster:: BOOST_REQUIRE_EQUAL(qc_s(qc(b9)), strong_qc(b8)); // b9 claims a strong QC on b8 BOOST_REQUIRE_EQUAL(A.lib_number, b6->block_num()); // b9 makes B6 final - // check that the block id of b9 match what we got with spring 1.0.0 + // check that the block id of b9 match what we got with Spring 1.0.0 auto b9_id = b9->calculate_id(); BOOST_REQUIRE_EQUAL(b9_id, block_id_type{"00000013725f3d79bd4dd4091d0853d010a320f95240981711a942673ad87918"});