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] Verify finality and QC extensions in Legacy and Transition blocks #697

Merged
merged 22 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
de89f2e
Verify finality and QC extensions in Legacy and Transition blocks
linh2931 Sep 4, 2024
f8157c5
Improve EOS_ASSERT messages
linh2931 Sep 4, 2024
aa3aaa5
Rename header_ext with finality_ext
linh2931 Sep 4, 2024
ba4d31b
Use invalid_qc_claim instead of block_validate_exception
linh2931 Sep 4, 2024
3d737ca
Refactor verify_legacy_block_exts into 3 functions
linh2931 Sep 4, 2024
91163d4
Add detailed comments and add an EOS_ASSERT for b->is_proper_svnn_blo…
linh2931 Sep 4, 2024
ba8d755
Simplify prev_finality_ext checks
linh2931 Sep 4, 2024
88629d8
Remove verify_legacy_and_transition_block_exts_common()
linh2931 Sep 4, 2024
4adb976
Rename savanna_mode with is_proper_savanna_block
linh2931 Sep 4, 2024
effc869
add more f_ext.new_finalizer_policy_diff check
linh2931 Sep 4, 2024
463d649
Rename verify_qc_claim to verify_proper_block_exts
linh2931 Sep 4, 2024
2974c04
Add assert(is_proper_savanna_block == b->is_proper_svnn_block())
linh2931 Sep 4, 2024
e44c48d
Make error message more specific about Non Genesis Transition block
linh2931 Sep 4, 2024
b59612e
more comments
linh2931 Sep 4, 2024
35d734d
Various changes to comments and exception type
linh2931 Sep 5, 2024
5f503a1
Use EOS_RETHROW_EXCEPTIONS instead of EOS_THROW
linh2931 Sep 5, 2024
68eea97
Fix a typo in comment
linh2931 Sep 5, 2024
9a5e133
One more invalid_qc_claim which should be block_validate_exception
linh2931 Sep 5, 2024
d4e9960
Rename is_proper_savanna_block to prev_is_of_type_block_state
linh2931 Sep 5, 2024
36e1988
Revert block_validate_exception to invalid_qc_claim for QC extension …
linh2931 Sep 5, 2024
43bab67
Remove duplicate b->is_proper_svnn_block false check
linh2931 Sep 5, 2024
31f1d5a
Revert renaming is_proper_savanna_block
linh2931 Sep 5, 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
178 changes: 141 additions & 37 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3894,44 +3894,28 @@ struct controller_impl {
bool qc_extension_present = block_exts.count(qc_ext_id) != 0;
uint32_t block_num = b->block_num();

if( !header_ext ) {
// If there is no header extension, ensure the block does not have a QC and also the previous
// block doesn't have a header extension either. Then return early.
// ------------------------------------------------------------------------------------------
EOS_ASSERT( !qc_extension_present,
invalid_qc_claim,
"Block #${b} includes a QC block extension, but doesn't have a finality header extension",
("b", block_num) );

EOS_ASSERT( !prev_finality_ext,
invalid_qc_claim,
"Block #${b} doesn't have a finality header extension even though its predecessor does.",
("b", block_num) );

dlog("received block: #${bn} ${t} ${prod} ${id}, no qc claim, previous: ${p}",
("bn", block_num)("t", b->timestamp)("prod", b->producer)("id", id)("p", b->previous));
return;
}
// This function is called only in Savanna. Finality block header
// extension must exist
EOS_ASSERT( header_ext,
invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Block #${b} doesn't have a finality header extension",
arhag marked this conversation as resolved.
Show resolved Hide resolved
("b", block_num) );

arhag marked this conversation as resolved.
Show resolved Hide resolved
assert(header_ext);
const auto& f_ext = std::get<finality_extension>(*header_ext);
const auto new_qc_claim = f_ext.qc_claim;

dlog("received block: #${bn} ${t} ${prod} ${id}, qc claim: ${qc}, previous: ${p}",
("bn", block_num)("t", b->timestamp)("prod", b->producer)("id", id)
("qc", new_qc_claim)("p", b->previous));

// If there is a header extension, but the previous block does not have a header extension,
// ensure the block does not have a QC and the QC claim of the current block has a block_num
// of the current block’s number and that it is a claim of a weak QC. Then return early.
// The only time a block should have a finality block header extension but
// its parent block does not, is if it is a Savanna Genesis block (which is
// necessarily a Transition block). Since verify_qc_claim will not be called
// on Transition blocks, prev_finality_ext should always be present
// -------------------------------------------------------------------------------------------------
if (!prev_finality_ext) {
EOS_ASSERT( !qc_extension_present && new_qc_claim.block_num == block_num && new_qc_claim.is_strong_qc == false,
invalid_qc_claim,
"Block #${b}, which is the finality transition block, doesn't have the expected extensions",
("b", block_num) );
return;
}
EOS_ASSERT( prev_finality_ext, invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Previous block of Block #${b} doesn't have finality block header extension",
arhag marked this conversation as resolved.
Show resolved Hide resolved
("b", block_num) );

// at this point both current block and its parent have IF extensions, and we are past the
// IF transition block
Expand Down Expand Up @@ -3999,18 +3983,138 @@ struct controller_impl {
bsp->verify_qc(qc_proof);
}

void verify_legacy_block_exts( const signed_block_ptr& b, const block_header_state_legacy& prev ) {
assert(b->is_legacy_block());

uint32_t block_num = b->block_num();
auto block_exts = b->validate_and_extract_extensions();
auto qc_ext_id = quorum_certificate_extension::extension_id();
bool qc_extension_present = block_exts.count(qc_ext_id) != 0;

EOS_ASSERT( !qc_extension_present, invalid_qc_claim,
"Legacy block #${b} includes a QC block extension",
("b", block_num) );

EOS_ASSERT( !b->is_proper_svnn_block(), invalid_qc_claim,
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
"Legacy block #${b} has invalid schedule_version",
("b", block_num) );

EOS_ASSERT( !prev.header.is_proper_svnn_block(), invalid_qc_claim,
"Legacy block #${b} may not have previous block that is a Proper Savanna block",
("b", block_num) );

auto it = prev.header_exts.find(finality_extension::extension_id());
EOS_ASSERT( it == prev.header_exts.end(), invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Legacy block #${b} has previous block which contains finality block header extension",
arhag marked this conversation as resolved.
Show resolved Hide resolved
("b", block_num) );
}

void verify_transition_block_exts( const signed_block_ptr& b, const block_header_state_legacy& prev ) {
assert(!b->is_legacy_block());
arhag marked this conversation as resolved.
Show resolved Hide resolved

uint32_t block_num = b->block_num();
auto block_exts = b->validate_and_extract_extensions();
auto qc_ext_id = quorum_certificate_extension::extension_id();
bool qc_extension_present = block_exts.count(qc_ext_id) != 0;

EOS_ASSERT( !qc_extension_present, invalid_qc_claim,
"Transition block #${b} includes a QC block extension",
("b", block_num) );

EOS_ASSERT( !b->is_proper_svnn_block(), invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Transition block #${b} has invalid schedule_version",
("b", block_num) );

EOS_ASSERT( !prev.header.is_proper_svnn_block(), invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Transition block #${b} may not have previous block that is a Proper Savanna block",
("b", block_num) );

auto f_ext_id = finality_extension::extension_id();
std::optional<block_header_extension> finality_ext = b->extract_header_extension(f_ext_id);
const auto& f_ext = std::get<finality_extension>(*finality_ext);
arhag marked this conversation as resolved.
Show resolved Hide resolved

EOS_ASSERT( !f_ext.qc_claim.is_strong_qc, invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Transition block #${b} has a strong QC claim",
("b", block_num) );
EOS_ASSERT( !f_ext.new_proposer_policy_diff, invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Transition block #${b} has new_proposer_policy_diff",
("b", block_num) );

if (auto it = prev.header_exts.find(finality_extension::extension_id()); it != prev.header_exts.end()) {
arhag marked this conversation as resolved.
Show resolved Hide resolved
const auto& prev_finality_ext = std::get<finality_extension>(it->second);
EOS_ASSERT( f_ext.qc_claim.block_num == prev_finality_ext.qc_claim.block_num,
arhag marked this conversation as resolved.
Show resolved Hide resolved
invalid_qc_claim,
"Transition block #${b} QC claim block_num not equal to previous QC claim block_num",
("b", block_num) );
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
EOS_ASSERT( !f_ext.new_finalizer_policy_diff, invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Non Genesis Transition block #${b} finality block header extension may not have new_finalizer_policy_diff",
("b", block_num) );
} else {
// Savanna Genesis Block
EOS_ASSERT( f_ext.qc_claim.block_num == block_num, invalid_qc_claim,
"Savanna Genesis block #${b} QC claim block_num not equal to current block_num",
arhag marked this conversation as resolved.
Show resolved Hide resolved
("b", block_num) );
EOS_ASSERT( f_ext.new_finalizer_policy_diff, invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Savanna Genesis block #${b} finality block header extension misses new_finalizer_policy_diff",
("b", block_num) );

finalizer_policy no_policy;
// apply_diff will FC_ASSERT if new_finalizer_policy_diff is malformated
heifner marked this conversation as resolved.
Show resolved Hide resolved
finalizer_policy genesis_policy = no_policy.apply_diff(*f_ext.new_finalizer_policy_diff);
arhag marked this conversation as resolved.
Show resolved Hide resolved
EOS_ASSERT( genesis_policy.generation == 1, invalid_qc_claim,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Savanna Genesis block #${b} finalizer policy generation (${g}) not 1",
("b", block_num)("g", genesis_policy.generation) );
}
}

// thread safe, expected to be called from thread other than the main thread
template<typename ForkDB, typename BS>
block_handle create_block_state_i( ForkDB& forkdb, const block_id_type& id, const signed_block_ptr& b, const BS& prev ) {
constexpr bool savanna_mode = std::is_same_v<typename std::decay_t<BS>, block_state>;
if constexpr (savanna_mode) {
// Verify claim made by finality_extension in block header extension and
// quorum_certificate_extension in block extension are valid.
// This is the only place the evaluation is done.
constexpr bool is_proper_savanna_block = std::is_same_v<typename std::decay_t<BS>, block_state>;
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
// If is_proper_savanna_block is true, then it means that the parent block of block b has a block_state.
//
// For a block to have a block_state it means the block must be a Savanna block,
// which means either Transition block or a Proper Savanna block.
//
// A block that has a Savanna block as a parent must also be a Savanna block.
// A block that has a Proper Savanna block as a parent must also be a Proper Savanna block.
//
// So given that the parent block has a block_state, we know that block b must either be
// a Transition block or a Proper Savanna block.

// If is_proper_savanna_block is false, then it means that the parent block of block b
// has a block_state_legacy.
//
// For a block to have a block_state_legacy it means the block must either be
// a Legacy block or a Transition block.
//
// A block that has a Legacy block as a parent must either be a Legacy block
// or a Transition block.
//
// A block that has a Transition block as a parent must either be a Transition
// block or a Proper Savanna block.
arhag marked this conversation as resolved.
Show resolved Hide resolved

// Verify claim made by finality_extension in block header extension and
// quorum_certificate_extension in block extension are valid.
// This is the only place the evaluation is done.
if constexpr (is_proper_savanna_block) {
EOS_ASSERT( b->is_proper_svnn_block(), block_validate_exception,
"create_block_state_i cannot be called on block #${b} which is not a Savanna block while its parent is a Savanna block",
arhag marked this conversation as resolved.
Show resolved Hide resolved
("b", b->block_num()) );
verify_qc_claim(id, b, prev);
arhag marked this conversation as resolved.
Show resolved Hide resolved
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
} else {
EOS_ASSERT( !b->is_proper_svnn_block(), block_validate_exception,
"create_block_state_i cannot be called on block #${b} which is a Savanna block while its parent is not a Savanna block",
arhag marked this conversation as resolved.
Show resolved Hide resolved
("b", b->block_num()) );

if (b->is_legacy_block()) {
verify_legacy_block_exts(b, prev);
} else {
verify_transition_block_exts(b, prev);
}
}

auto trx_mroot = calculate_trx_merkle( b->transactions, savanna_mode );
auto trx_mroot = calculate_trx_merkle( b->transactions, is_proper_savanna_block );
EOS_ASSERT( b->transaction_mroot == trx_mroot,
block_validate_exception,
"invalid block transaction merkle root ${b} != ${c}", ("b", b->transaction_mroot)("c", trx_mroot) );
Expand All @@ -4030,14 +4134,14 @@ struct controller_impl {
EOS_ASSERT( id == bsp->id(), block_validate_exception,
"provided id ${id} does not match block id ${bid}", ("id", id)("bid", bsp->id()) );

if constexpr (savanna_mode) {
if constexpr (is_proper_savanna_block) {
integrate_received_qc_to_block(bsp); // Save the received QC as soon as possible, no matter whether the block itself is valid or not
consider_voting(bsp, use_thread_pool_t::no);
}

if (!should_terminate(bsp->block_num())) {
forkdb.add(bsp, ignore_duplicate_t::yes);
if constexpr (savanna_mode)
if constexpr (is_proper_savanna_block)
vote_processor.notify_new_block(async_aggregation);
}

Expand Down
3 changes: 3 additions & 0 deletions libraries/chain/include/eosio/chain/block_header.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ namespace eosio::chain {
// finality extension must exist.
bool is_proper_svnn_block() const { return ( schedule_version == proper_svnn_schedule_version ); }

// Returns true if the block is a pure Legacy block
bool is_legacy_block() const { return !contains_header_extension(finality_extension::extension_id()); }

header_extension_multimap validate_and_extract_header_extensions()const;
std::optional<block_header_extension> extract_header_extension(uint16_t extension_id)const;
template<typename Ext> Ext extract_header_extension()const {
Expand Down