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

IF: Reject blocks with unsupported claims of last QC in block header extension #2120

Merged
merged 10 commits into from
Jan 24, 2024
58 changes: 57 additions & 1 deletion libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <eosio/chain/block_state_legacy.hpp>
#include <eosio/chain/exceptions.hpp>

#include <fc/crypto/bls_utils.hpp>

namespace eosio::chain {

block_state::block_state(const block_header_state& prev, signed_block_ptr b, const protocol_feature_set& pfs,
Expand Down Expand Up @@ -86,7 +88,61 @@ std::pair<bool, std::optional<uint32_t>> block_state::aggregate_vote(const hs_vo
return {};
}
}


// Called from net threads
void block_state::verify_qc(const valid_quorum_certificate& qc) const {
const auto& finalizers = active_finalizer_policy->finalizers;
auto num_finalizers = finalizers.size();

// utility to accumulate voted weights
auto weights = [&] ( const hs_bitset& votes_bitset ) -> uint64_t {
uint64_t sum = 0;
auto n = std::min(num_finalizers, votes_bitset.size());
for (auto i = 0u; i < n; ++i) {
if( votes_bitset[i] ) { // ith finalizer voted
sum += finalizers[i].weight;
}
}
return sum;
};

// compute strong and weak accumulated weights
auto strong_weights = qc._strong_votes ? weights( *qc._strong_votes ) : 0;
auto weak_weights = qc._weak_votes ? weights( *qc._weak_votes ) : 0;

// verfify quorum is met
if( qc.is_strong() ) {
EOS_ASSERT( strong_weights >= active_finalizer_policy->threshold, block_validate_exception, "strong quorum is not met, strong_weights: ${s}, threshold: ${t}", ("s", strong_weights)("t", active_finalizer_policy->threshold) );
} else {
EOS_ASSERT( strong_weights + weak_weights >= active_finalizer_policy->threshold, block_validate_exception, "weak quorum is not met, strong_weights: ${s}, weak_weights: ${w}, threshold: ${t}", ("s", strong_weights)("w", weak_weights)("t", active_finalizer_policy->threshold) );
heifner marked this conversation as resolved.
Show resolved Hide resolved
}

std::vector<bls_public_key> pubkeys;
std::vector<std::vector<uint8_t>> digests;

// utility to aggregate public keys and digests for verification
auto prepare_pubkeys_digests = [&] ( const auto& votes_bitset, const auto& digest ) {
auto n = std::min(num_finalizers, votes_bitset.size());
heifner marked this conversation as resolved.
Show resolved Hide resolved
for (auto i = 0u; i < n; ++i) {
if( votes_bitset[i] ) { // ith finalizer voted the digest
pubkeys.emplace_back(finalizers[i].public_key);
digests.emplace_back(std::vector<uint8_t>{digest.data(), digest.data() + digest.data_size()});
}
}
};
arhag marked this conversation as resolved.
Show resolved Hide resolved

// aggregate public keys and digests for strong and weak votes
if( qc._strong_votes ) {
prepare_pubkeys_digests(*qc._strong_votes, strong_digest);
}
if( qc._weak_votes ) {
prepare_pubkeys_digests(*qc._weak_votes, weak_digest);
}
arhag marked this conversation as resolved.
Show resolved Hide resolved

// validate aggregayed signature
Copy link
Member

Choose a reason for hiding this comment

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

Spelling aggregated

EOS_ASSERT( fc::crypto::blslib::aggregate_verify( pubkeys, digests, qc._sig ), block_validate_exception, "signature validation failed" );
}

std::optional<quorum_certificate> block_state::get_best_qc() const {
auto block_number = block_num();

Expand Down
75 changes: 69 additions & 6 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3052,19 +3052,76 @@ struct controller_impl {
}

void integrate_received_qc_to_block(const block_id_type& id, const signed_block_ptr& b) {
heifner marked this conversation as resolved.
Show resolved Hide resolved
#warning validate received QC before integrate it: https://github.com/AntelopeIO/leap/issues/2071
// extract QC from block extensions
auto block_exts = b->validate_and_extract_extensions();
if( block_exts.count(quorum_certificate_extension::extension_id()) > 0 ) {
const auto& qc_ext = std::get<quorum_certificate_extension>(block_exts. lower_bound(quorum_certificate_extension::extension_id())->second);
auto last_qc_block_bsp = fork_db_fetch_bsp_by_num( qc_ext.qc.block_height );
#warning a mutex might be needed for updating valid_pc
last_qc_block_bsp->valid_qc = qc_ext.qc.qc;
auto bsp = fork_db_fetch_bsp_by_num( qc_ext.qc.block_height );
heifner marked this conversation as resolved.
Show resolved Hide resolved
// Only save the QC from block extension if the claimed block does not
// has valid_qc or the new QC is better than valid_qc
heifner marked this conversation as resolved.
Show resolved Hide resolved
if( !bsp->valid_qc || (qc_ext.qc.qc.is_strong() && bsp->valid_qc->is_weak()) ) {
bsp->valid_qc = qc_ext.qc.qc;

if( bsp->valid_qc->is_strong() && bsp->core.final_on_strong_qc_block_num ) {
set_if_irreversible_block_num(*bsp->core.final_on_strong_qc_block_num);
}
}
}
}

// verify claim made by instant_finality_extension in block header extension and
// quorum_certificate_extension in block extension are valid
void verify_qc_claim( const signed_block_ptr& b, const block_header_state& prev ) {
heifner marked this conversation as resolved.
Show resolved Hide resolved
// If block header extension does not have instant_finality_extension,
// just return
std::optional<block_header_extension> header_ext = b->extract_header_extension(instant_finality_extension::extension_id());
if( !header_ext ) {
return;
}

// If qc_info is not included, just return
const auto& if_ext = std::get<instant_finality_extension>(*header_ext);
if( !if_ext.qc_info ) {
return;
}

// extract received QC information
qc_info_t received_qc_info{ *if_ext.qc_info };

// if previous block's header extension has the same claim, return true
heifner marked this conversation as resolved.
Show resolved Hide resolved
// (previous block had already validated the claim)
std::optional<block_header_extension> prev_header_ext = prev.header.extract_header_extension(instant_finality_extension::extension_id());
if( prev_header_ext ) {
auto prev_if_ext = std::get<instant_finality_extension>(*prev_header_ext);
if( prev_if_ext.qc_info && prev_if_ext.qc_info->last_qc_block_num == received_qc_info.last_qc_block_num && prev_if_ext.qc_info->is_last_qc_strong == received_qc_info.is_last_qc_strong ) {
return;
arhag marked this conversation as resolved.
Show resolved Hide resolved
}
}
arhag marked this conversation as resolved.
Show resolved Hide resolved

// extract QC from block extensions
auto block_exts = b->validate_and_extract_extensions();
EOS_ASSERT( block_exts.count(quorum_certificate_extension::extension_id()) > 0, block_validate_exception, "received block has finality header extension but does not have QC block extension" );
const auto& qc_ext = std::get<quorum_certificate_extension>(block_exts. lower_bound(quorum_certificate_extension::extension_id())->second);
const auto& received_qc = qc_ext.qc;
const auto& valid_qc = qc_ext.qc.qc;

// Check QC information in header extension and block extension match
EOS_ASSERT( received_qc.block_height == received_qc_info.last_qc_block_num, block_validate_exception, "QC block number (${n1}) in block extension does not match last_qc_block_num (${n2}) in header extension", ("n1", received_qc.block_height)("n2", received_qc_info.last_qc_block_num));
EOS_ASSERT( valid_qc.is_strong() == received_qc_info.is_last_qc_strong, block_validate_exception, "QC is_strong (${s1}) in block extension does not match is_last_qc_strong (${22}) in header extension", ("s1", valid_qc.is_strong())("s2", received_qc_info.is_last_qc_strong));

// find the claimed block's block state
auto claimed_block_bsp = fork_db_fetch_bsp_by_num( received_qc_info.last_qc_block_num );
heifner marked this conversation as resolved.
Show resolved Hide resolved

// verify the claims
claimed_block_bsp->verify_qc(valid_qc);
}

// thread safe, expected to be called from thread other than the main thread
block_token create_block_state_i( const block_id_type& id, const signed_block_ptr& b, const block_header_state& prev ) {
// Verify claim made by instant_finality_extension in block header extension and
// quorum_certificate_extension in block extension are valid
heifner marked this conversation as resolved.
Show resolved Hide resolved
verify_qc_claim(b, prev);

auto trx_mroot = calculate_trx_merkle( b->transactions, true );
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 Down Expand Up @@ -3611,6 +3668,12 @@ struct controller_impl {
return is_trx_transient ? nullptr : deep_mind_logger;
}

void set_if_irreversible_block_num(uint32_t block_num) {
if( block_num > if_irreversible_block_num ) {
if_irreversible_block_num = block_num;
}
}

uint32_t earliest_available_block_num() const {
return (blog.first_block_num() != 0) ? blog.first_block_num() : fork_db_root_block_num();
}
Expand Down Expand Up @@ -4062,7 +4125,7 @@ std::optional<block_id_type> controller::pending_producer_block_id()const {
void controller::set_if_irreversible_block_num(uint32_t block_num) {
// needs to be set by qc_chain at startup and as irreversible changes
assert(block_num > 0);
my->if_irreversible_block_num = block_num;
my->set_if_irreversible_block_num(block_num);
}

uint32_t controller::last_irreversible_block_num() const {
Expand Down Expand Up @@ -4257,7 +4320,7 @@ bool controller::process_vote_message( const hs_vote_message& vote ) {
};
auto [valid, new_lib] = my->fork_db.apply_if<std::pair<bool, std::optional<uint32_t>>>(do_vote);
if (new_lib) {
my->if_irreversible_block_num = *new_lib;
my->set_if_irreversible_block_num(*new_lib);
}
return valid;
};
Expand Down
1 change: 1 addition & 0 deletions libraries/chain/include/eosio/chain/block_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct block_state : public block_header_state { // block_header_state provi
void set_trxs_metas(deque<transaction_metadata_ptr>&& trxs_metas, bool keys_recovered);
const deque<transaction_metadata_ptr>& trxs_metas() const { return cached_trxs; }
std::pair<bool, std::optional<uint32_t>> aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc
void verify_qc(const valid_quorum_certificate& qc) const; // verify given qc is valid with respect block_state

using bhs_t = block_header_state;
using bhsp_t = block_header_state_ptr;
Expand Down
Loading
Loading