Skip to content

Commit

Permalink
GH-2100 Set lib according to voting. Fix validation of qc_info. Add i…
Browse files Browse the repository at this point in the history
…n needed digests in block_state. Do not send out vote until after block is processed.
heifner committed Jan 18, 2024

Verified

This commit was signed with the committer’s verified signature.
heifner Kevin Heifner
1 parent d7c6458 commit a4e45ff
Showing 10 changed files with 122 additions and 53 deletions.
14 changes: 8 additions & 6 deletions libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
@@ -24,7 +24,8 @@ block_header_state_core block_header_state_core::next(qc_info_t incoming) const
}

EOS_ASSERT(incoming.last_qc_block_num > this->last_qc_block_num, block_validate_exception,
"new last_qc_block_num must be greater than old last_qc_block_num");
"new last_qc_block_num ${new} must be greater than old last_qc_block_num ${old}",
("new", incoming.last_qc_block_num)("old", this->last_qc_block_num));

auto old_last_qc_block_num = this->last_qc_block_num;
auto old_final_on_strong_qc_block_num = this->final_on_strong_qc_block_num;
@@ -117,9 +118,8 @@ block_header_state block_header_state::next(block_header_state_input& input) con
result.active_finalizer_policy = active_finalizer_policy;

// [greg todo] correct support for new finalizer_policy activation using finalizer_policies map

if (input.new_finalizer_policy)
++input.new_finalizer_policy->generation;
// if (input.new_finalizer_policy)
// ++input.new_finalizer_policy->generation;


// add IF block header extension
@@ -131,7 +131,9 @@ block_header_state block_header_state::next(block_header_state_input& input) con
instant_finality_extension new_if_ext {if_ext.qc_info,
std::move(input.new_finalizer_policy),
std::move(input.new_proposer_policy)};
if (input.qc_info)
if (input.validating)
new_if_ext.qc_info = input.qc_info;
else if (input.qc_info)
new_if_ext.qc_info = *input.qc_info;

emplace_extension(result.header.header_extensions, if_ext_id, fc::raw::pack(new_if_ext));
@@ -196,7 +198,7 @@ block_header_state block_header_state::next(const signed_block_header& h, const

block_header_state_input bhs_input{
bb_input, h.transaction_mroot, h.action_mroot, if_ext.new_proposer_policy, if_ext.new_finalizer_policy,
if_ext.qc_info};
if_ext.qc_info, true};

return next(bhs_input);
}
14 changes: 11 additions & 3 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
@@ -9,12 +9,18 @@ block_state::block_state(const block_header_state& prev, signed_block_ptr b, con
const validator_t& validator, bool skip_validate_signee)
: block_header_state(prev.next(*b, pfs, validator))
, block(std::move(b))
, strong_digest(compute_finalizer_digest())
, weak_digest(compute_finalizer_digest())
, pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold)
{}

block_state::block_state(const block_header_state& bhs, deque<transaction_metadata_ptr>&& trx_metas,
deque<transaction_receipt>&& trx_receipts, const std::optional<quorum_certificate>& qc)
: block_header_state(bhs)
, block(std::make_shared<signed_block>(signed_block_header{bhs.header})) // [greg todo] do we need signatures?
, strong_digest(compute_finalizer_digest())
, weak_digest(compute_finalizer_digest())
, pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold)
, pub_keys_recovered(true) // probably not needed
, cached_trxs(std::move(trx_metas))
{
@@ -29,6 +35,7 @@ block_state::block_state(const block_header_state& bhs, deque<transaction_metada
block_state::block_state(const block_state_legacy& bsp) {
block_header_state::id = bsp.id();
header = bsp.header;
core.last_final_block_num = bsp.block_num();
activated_protocol_features = bsp.activated_protocol_features;
std::optional<block_header_extension> ext = bsp.block->extract_header_extension(instant_finality_extension::extension_id());
assert(ext); // required by current transition mechanism
@@ -58,7 +65,7 @@ void block_state::set_trxs_metas( deque<transaction_metadata_ptr>&& trxs_metas,
}

// Called from net threads
bool block_state::aggregate_vote(const hs_vote_message& vote) {
std::pair<bool, std::optional<uint32_t>> block_state::aggregate_vote(const hs_vote_message& vote) {
const auto& finalizers = active_finalizer_policy->finalizers;
auto it = std::find_if(finalizers.begin(),
finalizers.end(),
@@ -67,15 +74,16 @@ bool block_state::aggregate_vote(const hs_vote_message& vote) {
if (it != finalizers.end()) {
auto index = std::distance(finalizers.begin(), it);
const digest_type& digest = vote.strong ? strong_digest : weak_digest;
return pending_qc.add_vote(vote.strong,
auto [valid, strong] = pending_qc.add_vote(vote.strong,
#warning TODO change to use std::span if possible
std::vector<uint8_t>{digest.data(), digest.data() + digest.data_size()},
index,
vote.finalizer_key,
vote.sig);
return {valid, strong ? core.final_on_strong_qc_block_num : std::optional<uint32_t>{}};
} else {
wlog( "finalizer_key (${k}) in vote is not in finalizer policy", ("k", vote.finalizer_key) );
return false;
return {false, {}};
}
}

104 changes: 79 additions & 25 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
@@ -302,16 +302,19 @@ struct block_data_t {
}

// called from net thread
bool aggregate_vote(const hs_vote_message& vote) {
std::pair<bool, std::optional<uint32_t>> aggregate_vote(const hs_vote_message& vote) {
return std::visit(
overloaded{[](const block_data_legacy_t&) {
overloaded{[](const block_data_legacy_t&) -> std::pair<bool, std::optional<uint32_t>> {
// We can be late in switching to Instant Finality
// and receive votes from those already having switched.
return false; },
[&](const block_data_new_t& bd) {
return {false, {}}; },
[&](const block_data_new_t& bd) -> std::pair<bool, std::optional<uint32_t>> {
auto bsp = bd.fork_db.get_block(vote.proposal_id);
return bsp && bsp->aggregate_vote(vote); }
},
if (bsp) {
return bsp->aggregate_vote(vote);
}
return {false, {}};
}},
v);
}

@@ -857,7 +860,9 @@ struct building_block {

assembled_block assemble_block(boost::asio::io_context& ioc,
const protocol_feature_set& pfs,
const block_data_t& block_data) {
const block_data_t& block_data,
bool validating,
std::optional<qc_info_t> validating_qc_info) {
digests_t& action_receipts = action_receipt_digests();
return std::visit(
overloaded{
@@ -913,13 +918,17 @@ struct building_block {
// find most recent ancestor block that has a QC by traversing fork db
// branch from parent
std::optional<qc_data_t> qc_data;
auto branch = fork_db.fetch_branch(parent_id());
for( auto it = branch.begin(); it != branch.end(); ++it ) {
auto qc = (*it)->get_best_qc();
if( qc ) {
EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception, "most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})", ("a", qc->block_height)("p", block_header::num_from_id(parent_id())) );
qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }};
break;
if (!validating) {
auto branch = fork_db.fetch_branch(parent_id());
for( auto it = branch.begin(); it != branch.end(); ++it ) {
auto qc = (*it)->get_best_qc();
if( qc ) {
EOS_ASSERT( qc->block_height <= block_header::num_from_id(parent_id()), block_validate_exception,
"most recent ancestor QC block number (${a}) cannot be greater than parent's block number (${p})",
("a", qc->block_height)("p", block_header::num_from_id(parent_id())) );
qc_data = qc_data_t{ *qc, qc_info_t{ qc->block_height, qc->qc.is_strong() }};
break;
}
}
}

@@ -930,9 +939,17 @@ struct building_block {
.new_protocol_feature_activations = new_protocol_feature_activations()
};

std::optional<qc_info_t> qc_info;
if (validating) {
qc_info = validating_qc_info;
} else if (qc_data) {
qc_info = qc_data->qc_info;
}
block_header_state_input bhs_input{
bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy), std::move(bb.new_finalizer_policy),
qc_data ? qc_data->qc_info : std::optional<qc_info_t>{} };
bb_input, transaction_mroot, action_mroot, std::move(bb.new_proposer_policy),
std::move(bb.new_finalizer_policy),
qc_info, validating
};

assembled_block::assembled_block_if ab{std::move(bb.active_producer_authority), bb.parent.next(bhs_input),
std::move(bb.pending_trx_metas), std::move(bb.pending_trx_receipts),
@@ -2682,7 +2699,7 @@ struct controller_impl {
guard_pending.cancel();
} /// start_block

void finish_block()
void finish_block(bool validating = false, std::optional<qc_info_t> validating_qc_info = {})
{
EOS_ASSERT( pending, block_validate_exception, "it is not valid to finalize when there is no pending block");
EOS_ASSERT( std::holds_alternative<building_block>(pending->_block_stage), block_validate_exception, "already called finish_block");
@@ -2705,8 +2722,8 @@ struct controller_impl {
);
resource_limits.process_block_usage(bb.block_num());

auto assembled_block =
bb.assemble_block(thread_pool.get_executor(), protocol_features.get_protocol_feature_set(), block_data);
auto assembled_block = bb.assemble_block(thread_pool.get_executor(),
protocol_features.get_protocol_feature_set(), block_data, validating, validating_qc_info);

// Update TaPoS table:
create_block_summary( assembled_block.id() );
@@ -2875,6 +2892,23 @@ struct controller_impl {
EOS_REPORT( "new_producers", b.new_producers, ab.new_producers )
EOS_REPORT( "header_extensions", b.header_extensions, ab.header_extensions )

if (b.header_extensions != ab.header_extensions) {
{
flat_multimap<uint16_t, block_header_extension> bheader_exts = b.validate_and_extract_header_extensions();
if (bheader_exts.count(instant_finality_extension::extension_id())) {
const auto& if_extension =
std::get<instant_finality_extension>(bheader_exts.lower_bound(instant_finality_extension::extension_id())->second);
elog("b if: ${i}", ("i", if_extension));
}
}
flat_multimap<uint16_t, block_header_extension> abheader_exts = ab.validate_and_extract_header_extensions();
if (abheader_exts.count(instant_finality_extension::extension_id())) {
const auto& if_extension =
std::get<instant_finality_extension>(abheader_exts.lower_bound(instant_finality_extension::extension_id())->second);
elog("ab if: ${i}", ("i", if_extension));
}
}

#undef EOS_REPORT
}

@@ -2968,7 +3002,13 @@ struct controller_impl {
("lhs", r)("rhs", static_cast<const transaction_receipt_header&>(receipt)));
}

finish_block();
std::optional<qc_info_t> qc_info;
auto exts = b->validate_and_extract_header_extensions();
if (auto if_entry = exts.lower_bound(instant_finality_extension::extension_id()); if_entry != exts.end()) {
auto& if_ext = std::get<instant_finality_extension>(if_entry->second);
qc_info = if_ext.qc_info;
}
finish_block(true, qc_info);

auto& ab = std::get<assembled_block>(pending->_block_stage);

@@ -3016,22 +3056,31 @@ struct controller_impl {

// A vote is created and signed by each finalizer configured on the node that
// in active finalizer policy
bool found = false;
// TODO: remove dlog statements
dlog( "active finalizers ${n}, threshold ${t}",
("n", bsp->active_finalizer_policy->finalizers.size())("t", bsp->active_finalizer_policy->threshold));
for (const auto& f: bsp->active_finalizer_policy->finalizers) {
auto it = node_finalizer_keys.find( f.public_key );
if( it != node_finalizer_keys.end() ) {
found = true;
dlog("finalizer used: ${f}", ("f", f.public_key.to_string()));
const auto& private_key = it->second;
const auto& digest = bsp->compute_finalizer_digest();

auto sig = private_key.sign(std::vector<uint8_t>(digest.data(), digest.data() + digest.data_size()));

// construct the vote message
hs_vote_message vote{ bsp->id(), strong, private_key.get_public_key(), sig };
hs_vote_message vote{ bsp->id(), strong, f.public_key, sig };

// net plugin subscribed this signal. it will broadcast the vote message
// on receiving the signal
emit( self.voted_block, vote );
}
}
if (!found) {
dlog("No finalizer found on node for key, we have ${n} finalizers configured", ("n", node_finalizer_keys.size()));
}
}

// thread safe, expected to be called from thread other than the main thread
@@ -3088,9 +3137,7 @@ 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()) );

create_and_send_vote_msg(bsp);
"provided id ${id} does not match calculated block id ${bid}", ("id", id)("bid", bsp->id()) );

// integrate the received QC into the claimed block
integrate_received_qc_to_block(id, b);
@@ -3179,6 +3226,9 @@ struct controller_impl {

block_data.apply<void>(do_push);

if constexpr (std::is_same_v<block_state_ptr, typename std::decay_t<decltype(bsp)>>)
create_and_send_vote_msg(bsp);

} FC_LOG_AND_RETHROW( )
}

@@ -4266,7 +4316,11 @@ void controller::get_finalizer_state( finalizer_state& fs ) const {

// called from net threads
bool controller::process_vote_message( const hs_vote_message& vote ) {
return my->block_data.aggregate_vote(vote);
auto [valid, new_lib] = my->block_data.aggregate_vote(vote);
if (new_lib) {
my->if_irreversible_block_num = *new_lib;
}
return valid;
};

const producer_authority_schedule& controller::active_producers()const {
14 changes: 9 additions & 5 deletions libraries/chain/hotstuff/hotstuff.cpp
Original file line number Diff line number Diff line change
@@ -87,6 +87,9 @@ bool pending_quorum_certificate::add_strong_vote(const std::vector<uint8_t>& pro
size_t weak = num_weak();
size_t strong = num_strong();

// TODO: remove dlog statement
dlog( "strong ${n}, q ${q}", ("n", strong)("q", _quorum));

switch (_state) {
case state_t::unrestricted:
case state_t::restricted:
@@ -146,12 +149,13 @@ bool pending_quorum_certificate::add_weak_vote(const std::vector<uint8_t>& propo
return true;
}

// thread safe
bool pending_quorum_certificate::add_vote(bool strong, const std::vector<uint8_t>& proposal_digest, size_t index,
const bls_public_key& pubkey, const bls_signature& sig) {
// thread safe, <valid, strong>
std::pair<bool, bool> pending_quorum_certificate::add_vote(bool strong, const std::vector<uint8_t>& proposal_digest, size_t index,
const bls_public_key& pubkey, const bls_signature& sig) {
std::lock_guard g(*_mtx);
return strong ? add_strong_vote(proposal_digest, index, pubkey, sig)
: add_weak_vote(proposal_digest, index, pubkey, sig);
bool valid = strong ? add_strong_vote(proposal_digest, index, pubkey, sig)
: add_weak_vote(proposal_digest, index, pubkey, sig);
return {valid, _state == state_t::strong};
}

// thread safe
2 changes: 1 addition & 1 deletion libraries/chain/hotstuff/qc_chain.cpp
Original file line number Diff line number Diff line change
@@ -360,7 +360,7 @@ namespace eosio::chain {
for (size_t i=0; i<finalizers.size(); ++i) {
if (finalizers[i].public_key == vote.finalizer_key) {
if (_current_qc.add_vote(vote.strong, std::vector<uint8_t>(digest.data(), digest.data() + 32),
i, vote.finalizer_key, vote.sig)) {
i, vote.finalizer_key, vote.sig).first) {
// fc_tlog(_logger, " === update bitset ${value} ${finalizer_key}",
// ("value", _current_qc.get_active_finalizers_string())("finalizer_key", vote.finalizer_key));
if (_current_qc.is_quorum_met()) {
4 changes: 2 additions & 2 deletions libraries/chain/hotstuff/test/hotstuff_tools.cpp
Original file line number Diff line number Diff line change
@@ -100,11 +100,11 @@ BOOST_AUTO_TEST_CASE(qc_state_transitions) try {
pubkey.push_back(k.get_public_key());

auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector<uint8_t>& digest, size_t index) {
return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest));
return qc.add_vote(false, digest, index, pubkey[index], sk[index].sign(digest)).first;
};

auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector<uint8_t>& digest, size_t index) {
return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest));
return qc.add_vote(true, digest, index, pubkey[index], sk[index].sign(digest)).first;
};

{
1 change: 1 addition & 0 deletions libraries/chain/include/eosio/chain/block_header_state.hpp
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ struct block_header_state_input : public building_block_input {
std::optional<finalizer_policy> new_finalizer_policy; // Comes from building_block::new_finalizer_policy
std::optional<qc_info_t> qc_info; // Comes from traversing branch from parent and calling get_best_qc()
// assert(qc->block_num <= num_from_id(previous));
bool validating = false;
};

struct block_header_state_core {
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/block_state.hpp
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ struct block_state : public block_header_state { // block_header_state provi
deque<transaction_metadata_ptr> extract_trxs_metas();
void set_trxs_metas(deque<transaction_metadata_ptr>&& trxs_metas, bool keys_recovered);
const deque<transaction_metadata_ptr>& trxs_metas() const { return cached_trxs; }
bool aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc
std::pair<bool, std::optional<uint32_t>> aggregate_vote(const hs_vote_message& vote); // aggregate vote into pending_qc

using bhs_t = block_header_state;
using bhsp_t = block_header_state_ptr;
10 changes: 5 additions & 5 deletions libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp
Original file line number Diff line number Diff line change
@@ -199,11 +199,11 @@ namespace eosio::chain {
void reset(const fc::sha256& proposal_id, const digest_type& proposal_digest, size_t num_finalizers, size_t quorum);

// thread safe
bool add_vote(bool strong,
const std::vector<uint8_t>& proposal_digest,
size_t index,
const bls_public_key& pubkey,
const bls_signature& sig);
std::pair<bool, bool> add_vote(bool strong,
const std::vector<uint8_t>&proposal_digest,
size_t index,
const bls_public_key&pubkey,
const bls_signature&sig);

state_t state() const { std::lock_guard g(*_mtx); return _state; };
valid_quorum_certificate to_valid_quorum_certificate() const;
10 changes: 5 additions & 5 deletions unittests/block_state_tests.cpp
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
bool strong = (i % 2 == 0); // alternate strong and weak
auto sig = strong ? private_key[i].sign(strong_digest_data) : private_key[i].sign(weak_digest_data);
hs_vote_message vote{ block_id, strong, public_key[i], sig };
BOOST_REQUIRE(bsp->aggregate_vote(vote));
BOOST_REQUIRE(bsp->aggregate_vote(vote).first);
}
}

@@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };

hs_vote_message vote {block_id, true, public_key[0], private_key[1].sign(strong_digest_data) };
BOOST_REQUIRE(!bsp->aggregate_vote(vote));
BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
}

{ // duplicate votes
@@ -69,8 +69,8 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
bsp->pending_qc = pending_quorum_certificate{ num_finalizers, 1 };

hs_vote_message vote {block_id, true, public_key[0], private_key[0].sign(strong_digest_data) };
BOOST_REQUIRE(bsp->aggregate_vote(vote));
BOOST_REQUIRE(!bsp->aggregate_vote(vote));
BOOST_REQUIRE(bsp->aggregate_vote(vote).first);
BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
}

{ // public key does not exit in finalizer set
@@ -83,7 +83,7 @@ BOOST_AUTO_TEST_CASE(aggregate_vote_test) try {
bls_public_key new_public_key{ new_private_key.get_public_key() };

hs_vote_message vote {block_id, true, new_public_key, private_key[0].sign(strong_digest_data) };
BOOST_REQUIRE(!bsp->aggregate_vote(vote));
BOOST_REQUIRE(!bsp->aggregate_vote(vote).first);
}
} FC_LOG_AND_RETHROW();

0 comments on commit a4e45ff

Please sign in to comment.