diff --git a/libraries/chain/block_handle.cpp b/libraries/chain/block_handle.cpp index 3855a6f4f6..3190e0c28d 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.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; +constexpr uint64_t chain_head_version = 1; + + void block_handle::write(const std::filesystem::path& state_file) { if (!is_valid()) return; @@ -13,7 +23,8 @@ 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); } @@ -21,16 +32,28 @@ bool block_handle::read(const std::filesystem::path& state_file) { if (!std::filesystem::exists(state_file)) return false; - fc::datastream f; - f.set_file_path(state_file); - f.open("rb"); + 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" ); - fc::raw::unpack(f, *this); + try { + fc::datastream f; + f.set_file_path(state_file); + f.open("rb"); - ilog("Loading chain_head block ${bn} ${id}", ("bn", block_num())("id", id())); + uint64_t magic, version; + fc::raw::unpack(f, magic); + fc::raw::unpack(f, version); - std::filesystem::remove(state_file); + 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.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())); + } 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); return true; } diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 82ac65153b..5fb703e52d 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 ); @@ -141,6 +141,67 @@ const finalizer_policy& block_header_state::get_last_pending_finalizer_policy() return *active_finalizer_policy; } +// Only defined for core.latest_qc_claim().block_num <= ref.block_num() <= core.current_block_num() +// 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 { + 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; + + 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; // 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); + + // 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 != 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; + } + + auto pending_gen = ref.pending_policy_generation; + 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. 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 + res.pending_finalizer_policy = pending_finalizer_policy->second; + } + + return res; +} + +// 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 { + assert(core.links.empty() || // called from a bogus block_state constructed in a test + (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); + 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) { @@ -325,6 +386,22 @@ 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 policy (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; + 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 = 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 { + 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/block_state.cpp b/libraries/chain/block_state.cpp index 28fbb1255b..3302a56363 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -184,25 +184,30 @@ block_state_ptr block_state::create_transition_block( return result_ptr; } -block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs) +// 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) : 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), - .finalizer_policy_generation = sbs.finalizer_policy_generation, + .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 + , 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(); @@ -233,7 +238,14 @@ 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); + // 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); + + 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/controller.cpp b/libraries/chain/controller.cpp index bf2dfbfad5..ec1cd0c6ae 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -2212,7 +2212,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 ){ @@ -2261,15 +2261,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) { - // loading a snapshot saved by Spring 1.0 and above. - // ----------------------------------------------- - if (std::clamp(header.version, v7::minimum_version, v7::maximum_version) == header.version ) { + if (header.version >= v8::minimum_version) { + // 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 ){ - 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) { @@ -2291,8 +2291,13 @@ struct controller_impl { } else { EOS_THROW(snapshot_exception, "Unsupported block_state version"); } + } else if (header.version == 7) { + // 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 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; @@ -4076,14 +4081,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 @@ -4096,7 +4095,7 @@ struct controller_impl { if constexpr (is_proper_savanna_block) { if (qc) { - verify_qc(b, prev, *qc); + verify_qc(prev, *qc); dlog("received block: #${bn} ${t} ${prod} ${id}, qc claim: ${qc_claim}, previous: ${p}", ("bn", b->block_num())("t", b->timestamp)("prod", b->producer)("id", id) @@ -4341,7 +4340,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/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/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 440226eabb..89d8db9ebe 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.0 + // (two possible fork_db, one containing `block_state_legacy`, one containing `block_state`) + // 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`) + // --------------------------------------------------------------------------------------------------------- + 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.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/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 7eb05c41f6..035b97130c 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; }; @@ -78,6 +79,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 : fc::reflect_init { // ------ data members ------------------------------------------------------------ block_id_type block_id; @@ -121,12 +129,26 @@ struct block_header_state : fc::reflect_init { // 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). + // + // 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 // It matches the finalizer policy generation most recently included in this block's `finality_extension` or its ancestors uint32_t finalizer_policy_generation{1}; @@ -164,7 +186,10 @@ struct block_header_state : fc::reflect_init { 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(), active_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. @@ -192,6 +217,12 @@ struct block_header_state : fc::reflect_init { 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(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 { if (auto itr = header_exts.find(Ext::extension_id()); itr != header_exts.end()) { return &std::get(itr->second); @@ -211,7 +242,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)) diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index 4f4729ba03..75b3282568 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, active_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 @@ -178,9 +182,16 @@ 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); + + // 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()); + return block_header_state::get_finalizer_policies(core.get_block_reference(num)); + } }; using block_state_ptr = std::shared_ptr; diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index 680264e20a..fea6764e7e 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/finality/finality_core.hpp b/libraries/chain/include/eosio/chain/finality/finality_core.hpp index 2ffa3ff54b..78fa2dd49e 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. @@ -186,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.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 @@ -194,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; } @@ -215,7 +239,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) ) 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 diff --git a/libraries/chain/include/eosio/chain/snapshot_detail.hpp b/libraries/chain/include/eosio/chain/snapshot_detail.hpp index 03942caad3..43253967eb 100644 --- a/libraries/chain/include/eosio/chain/snapshot_detail.hpp +++ b/libraries/chain/include/eosio/chain/snapshot_detail.hpp @@ -91,10 +91,12 @@ namespace eosio::chain::snapshot_detail { }; /** - * Snapshot V7 Data structures + * Snapshot V8 Data structures * --------------------------- + * 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_v7 { + struct snapshot_block_state_v8 { // from block_header_state block_id_type block_id; block_header header; @@ -106,6 +108,7 @@ namespace eosio::chain::snapshot_detail { 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; @@ -113,10 +116,10 @@ namespace eosio::chain::snapshot_detail { // from block_state std::optional valid; - snapshot_block_state_v7() = default; + snapshot_block_state_v8() = 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) + // 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) @@ -127,6 +130,7 @@ namespace eosio::chain::snapshot_detail { , 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) @@ -134,27 +138,26 @@ namespace eosio::chain::snapshot_detail { {} }; - struct snapshot_block_state_data_v7 { - static constexpr uint32_t minimum_version = 7; - static constexpr uint32_t maximum_version = 7; + 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; + std::optional bs; - snapshot_block_state_data_v7() = default; + snapshot_block_state_data_v8() = default; - explicit snapshot_block_state_data_v7(const block_state_pair& p) + 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_v7(*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 ) ( schedule_hash ) @@ -195,7 +198,7 @@ FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_header_state_legacy_v3 ( additional_signatures ) ) -FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v7, +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v8, (block_id) (header) (activated_protocol_features) @@ -206,13 +209,14 @@ FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_v7, (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_v7, +FC_REFLECT( eosio::chain::snapshot_detail::snapshot_block_state_data_v8, (bs_l) (bs) ) diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index 7a02bc5b10..4a65e69d38 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; @@ -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 }; @@ -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 { @@ -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; @@ -556,7 +555,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 { @@ -595,8 +594,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->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; + bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation+1, threshold, pending_finalizers ) }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; @@ -617,7 +615,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) ); } @@ -845,7 +843,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 { @@ -886,8 +884,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->aggregating_qc = aggregating_qc_t{ bsp->active_finalizer_policy, bsp->pending_finalizer_policy->second }; + bsp->pending_finalizer_policy = { bsp->block_num(), std::make_shared( generation+1, threshold, pending_finalizers ) }; bsp->strong_digest = strong_digest; bsp->weak_digest = weak_digest; 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..e44f228138 100644 --- a/unittests/finalizer_tests.cpp +++ b/unittests/finalizer_tests.cpp @@ -39,14 +39,16 @@ 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 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{ .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)), 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))}, + sha256::hash("lock_digest"s + std::to_string(i)), 0, 0}, .other_branch_latest_time = block_timestamp_type{} }); if (i) @@ -62,7 +64,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, 0, 0}); // generation numbers both 0 as not saved in fsi file } return res; } diff --git a/unittests/finalizer_vote_tests.cpp b/unittests/finalizer_vote_tests.cpp index 31817c745f..30ecc29741 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, {}}; } diff --git a/unittests/savanna_misc_tests.cpp b/unittests/savanna_misc_tests.cpp index cf92726c83..4b65b76ff9 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() @@ -537,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.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 - // At this point, in the current implementation policy ... + auto b6_snapshot = A.snapshot(); push_block(0, b5); @@ -585,12 +584,107 @@ 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. 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() + + + +// ---------------------------------------------------------------------------------------------------- +// 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. +// ---------------------------------------------------------------------------------------------------- +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_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 + 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 + + // 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"}); } FC_LOG_AND_RETHROW() diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 032c018acf..4a856310cd 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.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. diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt index 1f3735bc6c..bd35723a3f 100644 --- a/unittests/snapshots/CMakeLists.txt +++ b/unittests/snapshots/CMakeLists.txt @@ -20,6 +20,7 @@ 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_v8.bin.gz similarity index 98% rename from unittests/snapshots/snap_v7.bin.gz rename to unittests/snapshots/snap_v8.bin.gz index 5159d929db..41ed2848ca 100644 Binary files a/unittests/snapshots/snap_v7.bin.gz and b/unittests/snapshots/snap_v8.bin.gz differ diff --git a/unittests/snapshots/snap_v7.bin.json.gz b/unittests/snapshots/snap_v8.bin.json.gz similarity index 60% rename from unittests/snapshots/snap_v7.bin.json.gz rename to unittests/snapshots/snap_v8.bin.json.gz index 6702bc0cdc..5933518a99 100644 Binary files a/unittests/snapshots/snap_v7.bin.json.gz and b/unittests/snapshots/snap_v8.bin.json.gz differ diff --git a/unittests/snapshots/snap_v7.json.gz b/unittests/snapshots/snap_v8.json.gz similarity index 59% rename from unittests/snapshots/snap_v7.json.gz rename to unittests/snapshots/snap_v8.json.gz index 5a2e2f9a5d..614de0b72f 100644 Binary files a/unittests/snapshots/snap_v7.json.gz and b/unittests/snapshots/snap_v8.json.gz differ