Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.0.1] Add missing data to block_header_state and change verify_qc_claim to use block_header_state data rather than only looking up a block_state in the fork database #719

Merged
merged 52 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
e8b2aa2
Add test verifying spring 1.0 compatibility.
greg7mdp Sep 6, 2024
2ec1e10
Minor test update
greg7mdp Sep 6, 2024
c582674
Add `active` and `pending` finalize policy generations to `block_ref`
greg7mdp Sep 7, 2024
d72477b
Add `pack_for_digest` finction to preserve compatibility with spring …
greg7mdp Sep 7, 2024
926f805
Add new member `block_header_state::latest_qc_claim_block_active_fina…
greg7mdp Sep 7, 2024
3177015
Update `std::ostream` output function to display new members.
greg7mdp Sep 7, 2024
1f6447e
Implement `get_finalizer_policies`
greg7mdp Sep 7, 2024
ac6552a
Check for zero `pending` generation and minor edits.
greg7mdp Sep 7, 2024
af6aced
Update `verify_qc` to use `get_finalizer_policies`
greg7mdp Sep 7, 2024
dc4e11e
Support the case where `block_num` is current clock (use `make_block_…
greg7mdp Sep 7, 2024
0d1c2a4
Merge branch 'release/1.0' of github.com:AntelopeIO/spring into gh_694
greg7mdp Sep 7, 2024
27725de
Update `latest_qc_claim_block_active_finalizer_policy` and implement …
greg7mdp Sep 7, 2024
8b6a095
Uncomment `validate_qc_after_restart_from_snapshot` which now passes
greg7mdp Sep 7, 2024
1cf1c56
re-enable `validate_qc_requiring_finalizer_policies` testcase.
greg7mdp Sep 7, 2024
2419d7d
Update snapshot format to v8
greg7mdp Sep 8, 2024
3349fd6
Fix issue with accessing current block's `block_ref` in `finality_core`.
greg7mdp Sep 8, 2024
dfff2bf
Minor changes (whitespace and comment).
greg7mdp Sep 8, 2024
f3d2a71
Better error message when loading v7 snapshot.
greg7mdp Sep 8, 2024
33c011f
Update `snapshot_unit_test` for v8 snapshot, and add reference files.
greg7mdp Sep 8, 2024
e6fc1f5
Create correct weak digests in tests (they need to be derived from th…
greg7mdp Sep 8, 2024
549dd7f
Fix incorrect initialization of pending policy generation in `block_s…
greg7mdp Sep 8, 2024
dafa750
No need to populate `bsp->aggregating_qc` for `verify_qc`
greg7mdp Sep 8, 2024
a1979b5
Maintain fsi serialization file format unchanged (not saving generati…
greg7mdp Sep 9, 2024
33cd6ab
Fix typo in `unittests/snapshots/CMakeLists.txt`
greg7mdp Sep 9, 2024
fd087fe
Update comments.
greg7mdp Sep 9, 2024
6dad5bb
Add comment for `block_state::verify_qc()`.
greg7mdp Sep 9, 2024
c1a760b
Update some comments.
greg7mdp Sep 9, 2024
98ff3a5
Remove unused data structures related to unsupported snapshot v7 format.
greg7mdp Sep 9, 2024
ef67a54
Update comment in test.
greg7mdp Sep 9, 2024
5a08f6e
Update fork_db version to `3`. Previous version `2` is not valid anym…
greg7mdp Sep 9, 2024
1127abf
Add versioning to `chain_head.dat`, and load only version 1 files wit…
greg7mdp Sep 9, 2024
0f4f6fb
Add a couple asserts as suggested in PR review.
greg7mdp Sep 10, 2024
98677ef
Address PR comments regarding loading the `chain_head.dat` file.
greg7mdp Sep 10, 2024
50fbcb7
Fix typo in previous commit
greg7mdp Sep 10, 2024
43e7ab5
Update comments.
greg7mdp Sep 10, 2024
6b89dc1
Update comments as per PR review.
greg7mdp Sep 10, 2024
c3c6729
Merge branch 'release/1.0' of github.com:AntelopeIO/spring into gh_694
greg7mdp Sep 10, 2024
e6c7331
Slightly simplify `block_handle::read`
greg7mdp Sep 10, 2024
008e1e5
Update block_handle comments
greg7mdp Sep 11, 2024
8dfe282
Use `FC_CAPTURE_AND_RETHROW` instead of just rethrowing the exception.
greg7mdp Sep 11, 2024
e3cdb7c
Update comment
greg7mdp Sep 11, 2024
1068e0f
Update comment
greg7mdp Sep 11, 2024
fa686db
Update comment
greg7mdp Sep 11, 2024
43622f2
Add precondition comments
greg7mdp Sep 11, 2024
4164a34
Be less restrictive with precondition
greg7mdp Sep 11, 2024
0dd7a36
Add clarification in comment
greg7mdp Sep 11, 2024
ced3e55
Fix typo in comment, and update `EOS_ASSERT` to avoid UB
greg7mdp Sep 11, 2024
edf294d
Update comments
greg7mdp Sep 11, 2024
1bdd276
Remove unsupported snapshot v7 file references
greg7mdp Sep 11, 2024
d272c25
Fix comments.
greg7mdp Sep 11, 2024
5e591b1
Missed this instance of `Spring 1.0`.
greg7mdp Sep 11, 2024
5426c85
Cleanup some more references to Spring versions in comments.
greg7mdp Sep 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions libraries/chain/block_handle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,24 +23,37 @@ void block_handle::write(const std::filesystem::path& state_file) {
fc::datastream<fc::cfile> 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;

fc::datastream<fc::cfile> 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<fc::cfile> 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;
}

Expand Down
79 changes: 78 additions & 1 deletion libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since EOS_ASSERT on line 164 is always on, is this small assert redundant?

Copy link
Contributor Author

@greg7mdp greg7mdp Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I have both on purpose.

The assert to clearly state this property always hold. And the fact that this small assert is there indicates that the property holds in all our tests.

The EOS_ASSERT in case I am wrong and that is not true. Which is why I added the comment // 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;
}

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this small assert redundant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I have both on purpose.
The assert to clearly state this property always hold. And the fact that this small assert is there indicates that the property holds in all our tests.

The EOS_ASSERT in case I am wrong and that is not true. Which is why I added the comment // just in case.

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) {
Expand Down Expand Up @@ -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();
Expand Down
36 changes: 24 additions & 12 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down
35 changes: 17 additions & 18 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ){
Expand Down Expand Up @@ -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 &section ){
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) {
Expand All @@ -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<block_state_legacy>();
using v2 = snapshot_block_header_state_legacy_v2;
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -4341,7 +4340,7 @@ struct controller_impl {

if constexpr (std::is_same_v<typename std::decay_t<BSP>, block_state_ptr>) {
if (conf.force_all_checks && qc) {
verify_qc(b, *head, *qc);
verify_qc(*head, *qc);
}
}

Expand Down
Loading