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 17 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
166 changes: 122 additions & 44 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3882,62 +3882,47 @@ 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_qc_claim( 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_header_state& prev ) {
assert(b->is_proper_svnn_block());

auto qc_ext_id = quorum_certificate_extension::extension_id();
auto f_ext_id = finality_extension::extension_id();

// extract current block extension and previous header extension
auto block_exts = b->validate_and_extract_extensions();
const finality_extension* prev_finality_ext = prev.header_extension<finality_extension>();
std::optional<block_header_extension> header_ext = b->extract_header_extension(f_ext_id);
std::optional<block_header_extension> finality_ext = b->extract_header_extension(f_ext_id);

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) );
// This function is called only in Savanna. Finality block header
// extension must exist
EOS_ASSERT( finality_ext, block_validate_exception,
"Proper Savanna block #${b} does not have a finality header extension",
("b", block_num) );

arhag marked this conversation as resolved.
Show resolved Hide resolved
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;
}

assert(header_ext);
const auto& f_ext = std::get<finality_extension>(*header_ext);
assert(finality_ext);
const auto& f_ext = std::get<finality_extension>(*finality_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_proper_block_exts will not be called
// on Transition blocks, previous block may not be a Legacy block
// -------------------------------------------------------------------------------------------------
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.header.is_legacy_block(), block_validate_exception,
"Proper Savanna block #${b} may not have previous block that is a Legacy block",
("b", block_num) );

// at this point both current block and its parent have IF extensions, and we are past the
// IF transition block
// ----------------------------------------------------------------------------------------
arhag marked this conversation as resolved.
Show resolved Hide resolved
assert(header_ext && prev_finality_ext);

assert(prev_finality_ext);
const auto& prev_qc_claim = prev_finality_ext->qc_claim;

// validate QC claim against previous block QC info
Expand Down Expand Up @@ -3999,18 +3984,111 @@ 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, block_validate_exception,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Legacy block #${b} includes a QC block extension",
("b", block_num) );

EOS_ASSERT( !b->is_proper_svnn_block(), block_validate_exception,
"Legacy block #${b} has invalid schedule_version",
("b", block_num) );

// Verify we don't go back from Savanna (Transition or Proper) block to Legacy block
EOS_ASSERT( prev.header.is_legacy_block(), block_validate_exception,
"Legacy block #${b} must have previous block that is also a Legacy block",
("b", block_num) );
}

void verify_transition_block_exts( const signed_block_ptr& b, const block_header_state_legacy& prev ) {
assert(!b->is_legacy_block() && !b->is_proper_svnn_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, block_validate_exception,
arhag marked this conversation as resolved.
Show resolved Hide resolved
"Transition block #${b} includes a QC block extension",
("b", block_num) );

EOS_ASSERT( !b->is_proper_svnn_block(), block_validate_exception,
"Transition block #${b} has invalid schedule_version",
("b", block_num) );

EOS_ASSERT( !prev.header.is_proper_svnn_block(), block_validate_exception,
"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);
assert(finality_ext);
const auto& f_ext = std::get<finality_extension>(*finality_ext);
arhag marked this conversation as resolved.
Show resolved Hide resolved

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
// Transition block other than Genesis Block
const auto& prev_finality_ext = std::get<finality_extension>(it->second);
EOS_ASSERT( f_ext.qc_claim == prev_finality_ext.qc_claim, invalid_qc_claim,
"Non Genesis Transition block #${b} QC claim ${this_qc_claim} not equal to previous QC claim ${prev_qc_claim}",
("b", block_num)("this_qc_claim", f_ext.qc_claim)("prev_qc_claim", prev_finality_ext.qc_claim) );
EOS_ASSERT( !f_ext.new_finalizer_policy_diff, block_validate_exception,
"Non Genesis Transition block #${b} finality block header extension may not have new_finalizer_policy_diff",
("b", block_num) );
greg7mdp marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Savanna Genesis Block
qc_claim_t genesis_qc_claim {.block_num = block_num, .is_strong_qc = false};
EOS_ASSERT( f_ext.qc_claim == genesis_qc_claim, invalid_qc_claim,
"Savanna Genesis block #${b} has invalid QC claim ${qc_claim}",
("b", block_num)("qc_claim", f_ext.qc_claim) );
EOS_ASSERT( f_ext.new_finalizer_policy_diff, block_validate_exception,
"Savanna Genesis block #${b} finality block header extension misses new_finalizer_policy_diff",
("b", block_num) );

// apply_diff will FC_ASSERT if new_finalizer_policy_diff is malformed
try {
finalizer_policy no_policy;
finalizer_policy genesis_policy = no_policy.apply_diff(*f_ext.new_finalizer_policy_diff);
EOS_ASSERT( genesis_policy.generation == 1, block_validate_exception,
greg7mdp 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) );
} EOS_RETHROW_EXCEPTIONS(block_validate_exception, "applying diff of Savanna Genesis Block")
}
}

// 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.
verify_qc_claim(id, b, prev);
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
assert(is_proper_savanna_block == b->is_proper_svnn_block());

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 Proper Savanna block unless the prev block state provided is of type block_state",
("b", b->block_num()) );
verify_proper_block_exts(id, b, prev);
} else {
EOS_ASSERT( !b->is_proper_svnn_block(), block_validate_exception,
"create_block_state_i cannot be called on block #${b} which is a Proper Savanna block unless the prev block state provided is of type block_state_legacy",
("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 +4108,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 Expand Up @@ -4095,7 +4173,7 @@ struct controller_impl {
return fork_db.apply<std::optional<block_handle>>(unlinkable, f);
}

// thread safe, QC already verified by verify_qc_claim
// thread safe, QC already verified by verify_proper_block_exts
void integrate_received_qc_to_block(const block_state_ptr& bsp_in) {
// extract QC from block extension
assert(bsp_in->block);
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