diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 8f2255e134..9359b8271c 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -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); } diff --git a/libraries/chain/block_state.cpp b/libraries/chain/block_state.cpp index a1cfd65670..a93f6ae136 100644 --- a/libraries/chain/block_state.cpp +++ b/libraries/chain/block_state.cpp @@ -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, {}}; } } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index edc929fa40..9401331a82 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -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 { diff --git a/libraries/chain/hotstuff/hotstuff.cpp b/libraries/chain/hotstuff/hotstuff.cpp index 200331842e..c22b046620 100644 --- a/libraries/chain/hotstuff/hotstuff.cpp +++ b/libraries/chain/hotstuff/hotstuff.cpp @@ -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 diff --git a/libraries/chain/hotstuff/qc_chain.cpp b/libraries/chain/hotstuff/qc_chain.cpp index 8315901810..e15af7729c 100644 --- a/libraries/chain/hotstuff/qc_chain.cpp +++ b/libraries/chain/hotstuff/qc_chain.cpp @@ -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()) { diff --git a/libraries/chain/hotstuff/test/hotstuff_tools.cpp b/libraries/chain/hotstuff/test/hotstuff_tools.cpp index 1ecb2bbec4..a5b62ffda0 100644 --- a/libraries/chain/hotstuff/test/hotstuff_tools.cpp +++ b/libraries/chain/hotstuff/test/hotstuff_tools.cpp @@ -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; }; { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 138a847f3a..48253149bc 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -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 { diff --git a/libraries/chain/include/eosio/chain/block_state.hpp b/libraries/chain/include/eosio/chain/block_state.hpp index a95fc61a7a..8c0b633107 100644 --- a/libraries/chain/include/eosio/chain/block_state.hpp +++ b/libraries/chain/include/eosio/chain/block_state.hpp @@ -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; diff --git a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp index d349597f7a..70efe9ffc1 100644 --- a/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp +++ b/libraries/chain/include/eosio/chain/hotstuff/hotstuff.hpp @@ -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; diff --git a/unittests/block_state_tests.cpp b/unittests/block_state_tests.cpp index 28ad30258c..e28df4ae41 100644 --- a/unittests/block_state_tests.cpp +++ b/unittests/block_state_tests.cpp @@ -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();