From a0a2d76f4c655f8309607be0825785cba014d4f8 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 12:40:29 -0600 Subject: [PATCH 01/13] GH-1916 Move hotstuff.hpp into hotstuff library. Change ownership of chain_pacemaker from chain_plugin to controller. --- libraries/chain/CMakeLists.txt | 2 +- libraries/chain/controller.cpp | 33 +++++++-- .../chain/include/eosio/chain/controller.hpp | 15 +++- libraries/hotstuff/chain_pacemaker.cpp | 10 +-- .../include/eosio/hotstuff/base_pacemaker.hpp | 17 ++--- .../eosio/hotstuff/chain_pacemaker.hpp | 30 ++++---- .../include/eosio/hotstuff}/hotstuff.hpp | 56 +++++++-------- .../include/eosio/hotstuff/qc_chain.hpp | 13 ++-- .../hotstuff/include/eosio/hotstuff/state.hpp | 16 ++--- .../include/eosio/hotstuff/test_pacemaker.hpp | 20 +++--- libraries/hotstuff/qc_chain.cpp | 2 +- libraries/hotstuff/test/test_pacemaker.cpp | 2 +- libraries/testing/tester.cpp | 5 ++ plugins/chain_plugin/chain_plugin.cpp | 70 +++++-------------- .../eosio/chain_plugin/chain_plugin.hpp | 34 ++++----- .../include/eosio/net_plugin/protocol.hpp | 5 +- plugins/net_plugin/net_plugin.cpp | 26 +++---- plugins/producer_plugin/producer_plugin.cpp | 9 ++- tests/chain_plugin_tests.cpp | 6 +- tests/get_producers_tests.cpp | 4 +- tests/get_table_seckey_tests.cpp | 2 +- tests/get_table_tests.cpp | 8 +-- tests/test_chain_plugin.cpp | 2 +- 23 files changed, 188 insertions(+), 199 deletions(-) rename libraries/{chain/include/eosio/chain => hotstuff/include/eosio/hotstuff}/hotstuff.hpp (58%) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 8ebef9283c..9948f04656 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -144,7 +144,7 @@ add_library( eosio_chain add_library(boost_numeric_ublas INTERFACE) add_library(Boost::numeric_ublas ALIAS boost_numeric_ublas) -target_link_libraries( eosio_chain PUBLIC bn256 fc chainbase eosio_rapidjson Logging IR WAST WASM +target_link_libraries( eosio_chain PUBLIC hotstuff bn256 fc chainbase eosio_rapidjson Logging IR WAST WASM softfloat builtins ${CHAIN_EOSVM_LIBRARIES} ${LLVM_LIBS} ${CHAIN_RT_LINKAGE} Boost::signals2 Boost::hana Boost::property_tree Boost::multi_index Boost::asio Boost::lockfree Boost::assign Boost::accumulators diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 27b9940dd0..f2d2af5315 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -238,6 +239,7 @@ struct controller_impl { std::optional pending; block_state_ptr head; fork_database fork_db; + std::optional pacemaker; std::atomic hs_irreversible_block_num{0}; resource_limits_manager resource_limits; subjective_billing subjective_bill; @@ -363,11 +365,6 @@ struct controller_impl { SET_APP_HANDLER( eosio, eosio, deleteauth ); SET_APP_HANDLER( eosio, eosio, linkauth ); SET_APP_HANDLER( eosio, eosio, unlinkauth ); -/* - SET_APP_HANDLER( eosio, eosio, postrecovery ); - SET_APP_HANDLER( eosio, eosio, passrecovery ); - SET_APP_HANDLER( eosio, eosio, vetorecovery ); -*/ SET_APP_HANDLER( eosio, eosio, canceldelay ); } @@ -1990,6 +1987,7 @@ struct controller_impl { if( s == controller::block_status::incomplete ) { log_irreversible(); + pacemaker->beat(); } } catch (...) { // dont bother resetting pending, instead abort the block @@ -3329,6 +3327,31 @@ void controller::set_proposed_finalizers( const finalizer_set& fin_set ) { my->set_proposed_finalizers(fin_set); } +void controller::get_finalizer_state( hotstuff::finalizer_state& fs ) const { + EOS_ASSERT( my->pacemaker, misc_exception, "chain_pacemaker not created" ); + my->pacemaker->get_state(fs); +} + +void controller::create_pacemaker(std::set my_producers, hotstuff::bls_key_map_t finalizer_keys, fc::logger& hotstuff_logger) { + EOS_ASSERT( !my->pacemaker, misc_exception, "duplicate chain_pacemaker initialization" ); + my->pacemaker.emplace(this, std::move(my_producers), std::move(finalizer_keys), hotstuff_logger); +} + +void controller::register_pacemaker_bcast_function(std::function&, const hotstuff::hs_message&)> bcast_hs_message) { + EOS_ASSERT( my->pacemaker, misc_exception, "chain_pacemaker not created" ); + my->pacemaker->register_bcast_function(std::move(bcast_hs_message)); +} + +void controller::register_pacemaker_warn_function(std::function warn_hs_message) { + EOS_ASSERT( my->pacemaker, misc_exception, "chain_pacemaker not created" ); + my->pacemaker->register_warn_function(std::move(warn_hs_message)); +} + +// called from net threads +void controller::notify_hs_message( const uint32_t connection_id, const hotstuff::hs_message& msg ) { + my->pacemaker->on_hs_msg(connection_id, msg); +}; + const producer_authority_schedule& controller::active_producers()const { if( !(my->pending) ) return my->head->active_schedule; diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 9a0d2af9b2..a8d4bd13b5 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -3,12 +3,15 @@ #include #include #include -#include -#include - #include #include #include +#include + +#include + +#include + namespace chainbase { class database; @@ -296,6 +299,12 @@ namespace eosio { namespace chain { // called by host function set_finalizers void set_proposed_finalizers( const finalizer_set& fin_set ); + void get_finalizer_state( hotstuff::finalizer_state& fs ) const; + // called from net threads + void notify_hs_message( const uint32_t connection_id, const hotstuff::hs_message& msg ); + void create_pacemaker(std::set my_producers, hotstuff::bls_key_map_t finalizer_keys, fc::logger& hotstuff_logger); + void register_pacemaker_bcast_function(std::function&, const hotstuff::hs_message&)> bcast_hs_message); + void register_pacemaker_warn_function(std::function warn_hs_message); bool light_validation_allowed() const; bool skip_auth_check()const; diff --git a/libraries/hotstuff/chain_pacemaker.cpp b/libraries/hotstuff/chain_pacemaker.cpp index db98a65c61..210a581247 100644 --- a/libraries/hotstuff/chain_pacemaker.cpp +++ b/libraries/hotstuff/chain_pacemaker.cpp @@ -119,13 +119,13 @@ namespace eosio { namespace hotstuff { _head_block_state = chain->head_block_state(); } - void chain_pacemaker::register_bcast_function(std::function&, const chain::hs_message&)> broadcast_hs_message) { + void chain_pacemaker::register_bcast_function(std::function&, const hs_message&)> broadcast_hs_message) { FC_ASSERT(broadcast_hs_message, "on_hs_message must be provided"); // no need to std::lock_guard g( _hotstuff_global_mutex ); here since pre-comm init bcast_hs_message = std::move(broadcast_hs_message); } - void chain_pacemaker::register_warn_function(std::function warning_hs_message) { + void chain_pacemaker::register_warn_function(std::function warning_hs_message) { FC_ASSERT(warning_hs_message, "must provide callback"); // no need to std::lock_guard g( _hotstuff_global_mutex ); here since pre-comm init warn_hs_message = std::move(warning_hs_message); @@ -198,7 +198,7 @@ namespace eosio { namespace hotstuff { return p_auth.producer_name; } - const finalizer_set& chain_pacemaker::get_finalizer_set(){ + const finalizer_set& chain_pacemaker::get_finalizer_set() { return _active_finalizer_set; } @@ -232,13 +232,13 @@ namespace eosio { namespace hotstuff { bcast_hs_message(exclude_peer, msg); } - void chain_pacemaker::send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code) { + void chain_pacemaker::send_hs_message_warning(uint32_t sender_peer, const hs_message_warning code) { warn_hs_message(sender_peer, code); } // called from net threads - void chain_pacemaker::on_hs_msg(const uint32_t connection_id, const eosio::chain::hs_message &msg) { + void chain_pacemaker::on_hs_msg(const uint32_t connection_id, const hs_message &msg) { std::visit(overloaded{ [this, connection_id](const hs_vote_message& m) { on_hs_vote_msg(connection_id, m); }, [this, connection_id](const hs_proposal_message& m) { on_hs_proposal_msg(connection_id, m); }, diff --git a/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp index f5cda74bd0..c9b0edb3f9 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp @@ -1,15 +1,10 @@ #pragma once +#include #include #include -#include - -#include - #include -#include - namespace eosio::hotstuff { // Abstract pacemaker; a reference of this type will only be used by qc_chain, as qc_chain @@ -31,14 +26,14 @@ namespace eosio::hotstuff { virtual chain::name get_proposer() = 0; virtual chain::name get_leader() = 0; virtual chain::name get_next_leader() = 0; - virtual const eosio::chain::finalizer_set& get_finalizer_set() = 0; + virtual const chain::finalizer_set& get_finalizer_set() = 0; //outbound communications; 'id' is the producer name (can be ignored if/when irrelevant to the implementer) - virtual void send_hs_proposal_msg(const chain::hs_proposal_message& msg, const std::string& id, const std::optional& exclude_peer = std::nullopt) = 0; - virtual void send_hs_vote_msg(const chain::hs_vote_message& msg, const std::string& id, const std::optional& exclude_peer = std::nullopt) = 0; - virtual void send_hs_new_view_msg(const chain::hs_new_view_message& msg, const std::string& id, const std::optional& exclude_peer = std::nullopt) = 0; + virtual void send_hs_proposal_msg(const hs_proposal_message& msg, const std::string& id, const std::optional& exclude_peer = std::nullopt) = 0; + virtual void send_hs_vote_msg(const hs_vote_message& msg, const std::string& id, const std::optional& exclude_peer = std::nullopt) = 0; + virtual void send_hs_new_view_msg(const hs_new_view_message& msg, const std::string& id, const std::optional& exclude_peer = std::nullopt) = 0; - virtual void send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code) = 0; + virtual void send_hs_message_warning(uint32_t sender_peer, hs_message_warning code) = 0; }; diff --git a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp index c9d1bcd272..5d9561fd33 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp @@ -20,10 +20,10 @@ namespace eosio::hotstuff { chain_pacemaker(controller* chain, std::set my_producers, - chain::bls_key_map_t finalizer_keys, + bls_key_map_t finalizer_keys, fc::logger& logger); - void register_bcast_function(std::function&, const chain::hs_message&)> broadcast_hs_message); - void register_warn_function(std::function warning_hs_message); + void register_bcast_function(std::function&, const hs_message&)> broadcast_hs_message); + void register_warn_function(std::function warning_hs_message); void beat(); @@ -33,20 +33,20 @@ namespace eosio::hotstuff { //base_pacemaker interface functions - name get_proposer(); - name get_leader() ; - name get_next_leader() ; - const finalizer_set& get_finalizer_set(); + name get_proposer() final; + name get_leader() final; + name get_next_leader() final; + const finalizer_set& get_finalizer_set() final; - block_id_type get_current_block_id(); + block_id_type get_current_block_id() final; - uint32_t get_quorum_threshold(); + uint32_t get_quorum_threshold() final; - void send_hs_proposal_msg(const hs_proposal_message& msg, const std::string& id, const std::optional& exclude_peer); - void send_hs_vote_msg(const hs_vote_message& msg, const std::string& id, const std::optional& exclude_peer); - void send_hs_new_view_msg(const hs_new_view_message& msg, const std::string& id, const std::optional& exclude_peer); + void send_hs_proposal_msg(const hs_proposal_message& msg, const std::string& id, const std::optional& exclude_peer) final; + void send_hs_vote_msg(const hs_vote_message& msg, const std::string& id, const std::optional& exclude_peer) final; + void send_hs_new_view_msg(const hs_new_view_message& msg, const std::string& id, const std::optional& exclude_peer) final; - void send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code); + void send_hs_message_warning(uint32_t sender_peer, hs_message_warning code) final; private: void on_accepted_block( const block_state_ptr& blk ); @@ -80,8 +80,8 @@ namespace eosio::hotstuff { boost::signals2::scoped_connection _irreversible_block_connection; qc_chain _qc_chain; - std::function&, const chain::hs_message&)> bcast_hs_message; - std::function warn_hs_message; + std::function&, const hs_message&)> bcast_hs_message; + std::function warn_hs_message; uint32_t _quorum_threshold = 15; //FIXME/TODO: calculate from schedule fc::logger& _logger; diff --git a/libraries/chain/include/eosio/chain/hotstuff.hpp b/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp similarity index 58% rename from libraries/chain/include/eosio/chain/hotstuff.hpp rename to libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp index 82bd92e228..a23ee82006 100644 --- a/libraries/chain/include/eosio/chain/hotstuff.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp @@ -4,18 +4,17 @@ #include #include #include -#include #include -namespace eosio::chain { +namespace eosio::hotstuff { using hs_bitset = boost::dynamic_bitset; using bls_key_map_t = std::map; - inline digest_type get_digest_to_sign(const block_id_type& block_id, uint8_t phase_counter, const fc::sha256& final_on_qc) { - digest_type h1 = digest_type::hash( std::make_pair( std::cref(block_id), phase_counter ) ); - digest_type h2 = digest_type::hash( std::make_pair( std::cref(h1), std::cref(final_on_qc) ) ); + inline chain::digest_type get_digest_to_sign(const chain::block_id_type& block_id, uint8_t phase_counter, const fc::sha256& final_on_qc) { + chain::digest_type h1 = chain::digest_type::hash( std::make_pair( std::cref(block_id), phase_counter ) ); + chain::digest_type h2 = chain::digest_type::hash( std::make_pair( std::cref(h1), std::cref(final_on_qc) ) ); return h2; } @@ -29,6 +28,7 @@ namespace eosio::chain { auto operator<=>(const view_number&) const = default; friend std::ostream& operator<<(std::ostream& os, const view_number& vn) { os << "view_number(" << vn.bheight << ", " << vn.pcounter << ")\n"; + return os; } uint32_t block_height() const { return bheight; } @@ -41,13 +41,13 @@ namespace eosio::chain { }; struct extended_schedule { - producer_authority_schedule producer_schedule; - std::map bls_pub_keys; + chain::producer_authority_schedule producer_schedule; + std::map bls_pub_keys; }; struct quorum_certificate_message { fc::sha256 proposal_id; - std::vector active_finalizers; //bitset encoding, following canonical order + std::vector active_finalizers; //bitset encoding, following canonical order fc::crypto::blslib::bls_signature active_agg_sig; }; @@ -59,18 +59,18 @@ namespace eosio::chain { struct hs_proposal_message { fc::sha256 proposal_id; //vote on proposal - block_id_type block_id; + chain::block_id_type block_id; fc::sha256 parent_id; //new proposal fc::sha256 final_on_qc; quorum_certificate_message justify; //justification uint8_t phase_counter = 0; - digest_type get_proposal_id() const { return get_digest_to_sign(block_id, phase_counter, final_on_qc); }; + chain::digest_type get_proposal_id() const { return get_digest_to_sign(block_id, phase_counter, final_on_qc); }; - uint32_t block_num() const { return block_header::num_from_id(block_id); } - uint64_t get_key() const { return compute_height(block_header::num_from_id(block_id), phase_counter); }; + uint32_t block_num() const { return chain::block_header::num_from_id(block_id); } + uint64_t get_key() const { return compute_height(chain::block_header::num_from_id(block_id), phase_counter); }; - view_number get_view_number() const { return view_number(block_header::num_from_id(block_id), phase_counter); }; + view_number get_view_number() const { return view_number(chain::block_header::num_from_id(block_id), phase_counter); }; }; struct hs_new_view_message { @@ -92,13 +92,13 @@ namespace eosio::chain { fc::sha256 b_lock; fc::sha256 b_exec; fc::sha256 b_finality_violation; - block_id_type block_exec; - block_id_type pending_proposal_block; - eosio::chain::view_number v_height; - eosio::chain::quorum_certificate_message high_qc; - eosio::chain::quorum_certificate_message current_qc; - eosio::chain::extended_schedule schedule; - map proposals; + chain::block_id_type block_exec; + chain::block_id_type pending_proposal_block; + view_number v_height; + quorum_certificate_message high_qc; + quorum_certificate_message current_qc; + extended_schedule schedule; + std::map proposals; const hs_proposal_message* get_proposal(const fc::sha256& id) const { auto it = proposals.find(id); @@ -108,13 +108,13 @@ namespace eosio::chain { } }; -} //eosio::chain +} //eosio::hotstuff -FC_REFLECT(eosio::chain::view_number, (bheight)(pcounter)); -FC_REFLECT(eosio::chain::quorum_certificate_message, (proposal_id)(active_finalizers)(active_agg_sig)); -FC_REFLECT(eosio::chain::extended_schedule, (producer_schedule)(bls_pub_keys)); -FC_REFLECT(eosio::chain::hs_vote_message, (proposal_id)(finalizer_key)(sig)); -FC_REFLECT(eosio::chain::hs_proposal_message, (proposal_id)(block_id)(parent_id)(final_on_qc)(justify)(phase_counter)); -FC_REFLECT(eosio::chain::hs_new_view_message, (high_qc)); -FC_REFLECT(eosio::chain::finalizer_state, (chained_mode)(b_leaf)(b_lock)(b_exec)(b_finality_violation)(block_exec)(pending_proposal_block)(v_height)(high_qc)(current_qc)(schedule)(proposals)); +FC_REFLECT(eosio::hotstuff::view_number, (bheight)(pcounter)); +FC_REFLECT(eosio::hotstuff::quorum_certificate_message, (proposal_id)(active_finalizers)(active_agg_sig)); +FC_REFLECT(eosio::hotstuff::extended_schedule, (producer_schedule)(bls_pub_keys)); +FC_REFLECT(eosio::hotstuff::hs_vote_message, (proposal_id)(finalizer_key)(sig)); +FC_REFLECT(eosio::hotstuff::hs_proposal_message, (proposal_id)(block_id)(parent_id)(final_on_qc)(justify)(phase_counter)); +FC_REFLECT(eosio::hotstuff::hs_new_view_message, (high_qc)); +FC_REFLECT(eosio::hotstuff::finalizer_state, (chained_mode)(b_leaf)(b_lock)(b_exec)(b_finality_violation)(block_exec)(pending_proposal_block)(v_height)(high_qc)(current_qc)(schedule)(proposals)); diff --git a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp index 2b980d96a1..de3be8f4d9 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp @@ -1,11 +1,10 @@ #pragma once -#include -#include -#include -#include +#include #include #include +#include +#include #include #include @@ -154,7 +153,7 @@ namespace eosio::hotstuff { qc_chain(std::string id, base_pacemaker* pacemaker, std::set my_producers, - chain::bls_key_map_t finalizer_keys, + bls_key_map_t finalizer_keys, fc::logger& logger, std::string safety_state_file); @@ -227,7 +226,7 @@ namespace eosio::hotstuff { void send_hs_vote_msg(const std::optional& connection_id, const hs_vote_message& msg); void send_hs_new_view_msg(const std::optional& connection_id, const hs_new_view_message& msg); - void send_hs_message_warning(const std::optional& connection_id, const chain::hs_message_warning code); + void send_hs_message_warning(const std::optional& connection_id, const hs_message_warning code); void update(const hs_proposal_message& proposal); void commit(const hs_proposal_message& proposal); @@ -246,7 +245,7 @@ namespace eosio::hotstuff { quorum_certificate _current_qc; base_pacemaker* _pacemaker = nullptr; std::set _my_producers; - chain::bls_key_map_t _my_finalizer_keys; + bls_key_map_t _my_finalizer_keys; std::string _id; std::string _safety_state_file; // if empty, safety state persistence is turned off diff --git a/libraries/hotstuff/include/eosio/hotstuff/state.hpp b/libraries/hotstuff/include/eosio/hotstuff/state.hpp index 589f3a2188..b016efb7e3 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/state.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/state.hpp @@ -1,6 +1,4 @@ -#include - -#include +#include namespace eosio::hotstuff { @@ -8,7 +6,7 @@ namespace eosio::hotstuff { struct safety_state { - void set_v_height(const fc::crypto::blslib::bls_public_key& finalizer_key, const eosio::chain::view_number v_height) { + void set_v_height(const fc::crypto::blslib::bls_public_key& finalizer_key, const view_number v_height) { _states[finalizer_key].first = v_height; } @@ -16,13 +14,13 @@ namespace eosio::hotstuff { _states[finalizer_key].second = b_lock; } - std::pair get_safety_state(const fc::crypto::blslib::bls_public_key& finalizer_key) const { + std::pair get_safety_state(const fc::crypto::blslib::bls_public_key& finalizer_key) const { auto s = _states.find(finalizer_key); if (s != _states.end()) return s->second; else return {}; } - eosio::chain::view_number get_v_height(const fc::crypto::blslib::bls_public_key& finalizer_key) const { + view_number get_v_height(const fc::crypto::blslib::bls_public_key& finalizer_key) const { auto s = _states.find(finalizer_key); if (s != _states.end()) return s->second.first; else return {}; @@ -36,13 +34,13 @@ namespace eosio::hotstuff { //todo : implement safety state default / sorting - std::pair get_safety_state() const { + std::pair get_safety_state() const { auto s = _states.begin(); if (s != _states.end()) return s->second; else return {}; } - eosio::chain::view_number get_v_height() const { + view_number get_v_height() const { auto s = _states.begin(); if (s != _states.end()) return s->second.first; else return {}; @@ -54,7 +52,7 @@ namespace eosio::hotstuff { else return {}; }; - std::map> _states; + std::map> _states; }; } diff --git a/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp index 2b616b8a26..9feaa15b15 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp @@ -64,20 +64,20 @@ namespace eosio { namespace hotstuff { //base_pacemaker interface functions - name get_proposer(); - name get_leader(); - name get_next_leader(); - const finalizer_set& get_finalizer_set(); + name get_proposer() override; + name get_leader() override; + name get_next_leader() override; + const finalizer_set& get_finalizer_set() override; - block_id_type get_current_block_id(); + block_id_type get_current_block_id() override; - uint32_t get_quorum_threshold(); + uint32_t get_quorum_threshold() override; - void send_hs_proposal_msg(const hs_proposal_message & msg, const std::string& id, const std::optional& exclude_peer); - void send_hs_vote_msg(const hs_vote_message & msg, const std::string& id, const std::optional& exclude_peer); - void send_hs_new_view_msg(const hs_new_view_message & msg, const std::string& id, const std::optional& exclude_peer); + void send_hs_proposal_msg(const hs_proposal_message & msg, const std::string& id, const std::optional& exclude_peer) override; + void send_hs_vote_msg(const hs_vote_message & msg, const std::string& id, const std::optional& exclude_peer) override; + void send_hs_new_view_msg(const hs_new_view_message & msg, const std::string& id, const std::optional& exclude_peer) override; - void send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code); + void send_hs_message_warning(uint32_t sender_peer, const hs_message_warning code) override; private: diff --git a/libraries/hotstuff/qc_chain.cpp b/libraries/hotstuff/qc_chain.cpp index e9baed30b9..3746be6133 100644 --- a/libraries/hotstuff/qc_chain.cpp +++ b/libraries/hotstuff/qc_chain.cpp @@ -529,7 +529,7 @@ namespace eosio::hotstuff { _pacemaker->send_hs_new_view_msg(msg, _id, connection_id); } - void qc_chain::send_hs_message_warning(const std::optional& connection_id, const chain::hs_message_warning code) { + void qc_chain::send_hs_message_warning(const std::optional& connection_id, const hs_message_warning code) { if (connection_id.has_value()) _pacemaker->send_hs_message_warning(connection_id.value(), code); } diff --git a/libraries/hotstuff/test/test_pacemaker.cpp b/libraries/hotstuff/test/test_pacemaker.cpp index be883ca57e..3ffb8587b5 100644 --- a/libraries/hotstuff/test/test_pacemaker.cpp +++ b/libraries/hotstuff/test/test_pacemaker.cpp @@ -206,7 +206,7 @@ namespace eosio::hotstuff { _pending_message_queue.push_back(std::make_pair(id, msg)); }; - void test_pacemaker::send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code) { } + void test_pacemaker::send_hs_message_warning(uint32_t sender_peer, const hs_message_warning code) { } void test_pacemaker::on_hs_proposal_msg(const hs_proposal_message& msg, const std::string& id) { for (const auto& [qcc_name, qcc_ptr] : _qcc_store) { diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 20ee363b22..9341876d70 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -23,6 +23,8 @@ eosio::chain::asset core_from_string(const std::string& s) { namespace eosio { namespace testing { + fc::logger test_logger = fc::logger::get(); + inline auto get_bls_private_key( name keyname ) { auto secret = fc::sha256::hash(keyname.to_string()); std::vector seed(secret.data_size()); @@ -347,6 +349,9 @@ namespace eosio { namespace testing { } } }); + control->create_pacemaker({}, {}, test_logger); + control->register_pacemaker_bcast_function([](const std::optional&, const hotstuff::hs_message&){}); + control->register_pacemaker_warn_function([](uint32_t, const hotstuff::hs_message_warning&){}); } void base_tester::open( protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot ) { diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 52666f789c..564410e056 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include @@ -40,9 +39,6 @@ FC_REFLECT(chainbase::environment, (debug)(os)(arch)(boost_version)(compiler) ) const std::string deep_mind_logger_name("deep-mind"); eosio::chain::deep_mind_handler _deep_mind_log; -const std::string hotstuff_logger_name("hotstuff"); -fc::logger hotstuff_logger; - namespace eosio { //declare operator<< and validate function for read_mode in the same namespace as read_mode itself @@ -211,7 +207,6 @@ class chain_plugin_impl { std::optional applied_transaction_connection; std::optional block_start_connection; - std::optional _chain_pacemaker; std::optional _account_query_db; std::optional _trx_retry_db; chain_apis::trx_finality_status_processing_ptr _trx_finality_status_processing; @@ -1106,21 +1101,6 @@ void chain_plugin_impl::plugin_initialize(const variables_map& options) { } FC_LOG_AND_RETHROW() } -void chain_plugin::create_pacemaker(std::set my_producers, chain::bls_key_map_t finalizer_keys) { - EOS_ASSERT( !my->_chain_pacemaker, plugin_config_exception, "duplicate chain_pacemaker initialization" ); - my->_chain_pacemaker.emplace(&chain(), std::move(my_producers), std::move(finalizer_keys), hotstuff_logger); -} - -void chain_plugin::register_pacemaker_bcast_function(std::function&, const chain::hs_message&)> bcast_hs_message) { - EOS_ASSERT( my->_chain_pacemaker, plugin_config_exception, "chain_pacemaker not created" ); - my->_chain_pacemaker->register_bcast_function(std::move(bcast_hs_message)); -} - -void chain_plugin::register_pacemaker_warn_function(std::function warn_hs_message) { - EOS_ASSERT( my->_chain_pacemaker, plugin_config_exception, "chain_pacemaker not created" ); - my->_chain_pacemaker->register_warn_function(std::move(warn_hs_message)); -} - void chain_plugin::plugin_initialize(const variables_map& options) { handle_sighup(); // Sets loggers my->plugin_initialize(options); @@ -1196,7 +1176,6 @@ void chain_plugin::plugin_shutdown() { void chain_plugin::handle_sighup() { _deep_mind_log.update_logger( deep_mind_logger_name ); - fc::logger::update( hotstuff_logger_name, hotstuff_logger ); } chain_apis::read_write::read_write(controller& db, @@ -1222,7 +1201,7 @@ chain_apis::read_write chain_plugin::get_read_write_api(const fc::microseconds& } chain_apis::read_only chain_plugin::get_read_only_api(const fc::microseconds& http_max_response_time) const { - return chain_apis::read_only(chain(), my->_account_query_db, my->_chain_pacemaker, get_abi_serializer_max_time(), http_max_response_time, my->_trx_finality_status_processing.get()); + return chain_apis::read_only(chain(), my->_account_query_db, get_abi_serializer_max_time(), http_max_response_time, my->_trx_finality_status_processing.get()); } @@ -2662,42 +2641,29 @@ read_only::get_finalizer_state_results read_only::get_finalizer_state(const get_finalizer_state_params&, const fc::time_point& deadline ) const { get_finalizer_state_results results; - if ( chain_pacemaker ) { // is null when called from chain_plugin_tests.cpp and get_table_tests.cpp - finalizer_state fs; - chain_pacemaker->get_state( fs ); - results.chained_mode = fs.chained_mode; - results.b_leaf = fs.b_leaf; - results.b_lock = fs.b_lock; - results.b_exec = fs.b_exec; - results.b_finality_violation = fs.b_finality_violation; - results.block_exec = fs.block_exec; - results.pending_proposal_block = fs.pending_proposal_block; - results.v_height = fs.v_height; - results.high_qc = fs.high_qc; - results.current_qc = fs.current_qc; - results.schedule = fs.schedule; - results.proposals.reserve( fs.proposals.size() ); - for (const auto& proposal : fs.proposals) { - const chain::hs_proposal_message& p = proposal.second; - results.proposals.push_back( hs_complete_proposal_message( p ) ); - } + hotstuff::finalizer_state fs; + db.get_finalizer_state( fs ); + results.chained_mode = fs.chained_mode; + results.b_leaf = fs.b_leaf; + results.b_lock = fs.b_lock; + results.b_exec = fs.b_exec; + results.b_finality_violation = fs.b_finality_violation; + results.block_exec = fs.block_exec; + results.pending_proposal_block = fs.pending_proposal_block; + results.v_height = fs.v_height; + results.high_qc = fs.high_qc; + results.current_qc = fs.current_qc; + results.schedule = fs.schedule; + results.proposals.reserve( fs.proposals.size() ); + for (const auto& proposal : fs.proposals) { + const hotstuff::hs_proposal_message& p = proposal.second; + results.proposals.push_back( hs_complete_proposal_message( p ) ); } return results; } } // namespace chain_apis -// called from net threads -void chain_plugin::notify_hs_message( const uint32_t connection_id, const hs_message& msg ) { - my->_chain_pacemaker->on_hs_msg(connection_id, msg); -}; - -void chain_plugin::notify_hs_block_produced() { - if (chain().is_builtin_activated( builtin_protocol_feature_t::instant_finality )) { - my->_chain_pacemaker->beat(); - } -} - fc::variant chain_plugin::get_log_trx_trace(const transaction_trace_ptr& trx_trace ) const { fc::variant pretty_output; try { diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index 155bf00c65..b075e583c8 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -1,4 +1,9 @@ #pragma once + +#include +#include +#include + #include #include #include @@ -12,23 +17,17 @@ #include #include #include -#include +#include #include #include -#include -#include -#include - -#include #include namespace fc { class variant; } namespace eosio { namespace chain { class abi_resolver; } - namespace hotstuff { class chain_pacemaker; } using chain::controller; using std::unique_ptr; @@ -141,7 +140,6 @@ class api_base { class read_only : public api_base { const controller& db; const std::optional& aqdb; - const std::optional& chain_pacemaker; const fc::microseconds abi_serializer_max_time; const fc::microseconds http_max_response_time; bool shorten_abi_errors = true; @@ -152,12 +150,10 @@ class read_only : public api_base { static const string KEYi64; read_only(const controller& db, const std::optional& aqdb, - const std::optional& chain_pacemaker, const fc::microseconds& abi_serializer_max_time, const fc::microseconds& http_max_response_time, const trx_finality_status_processing* trx_finality_status_proc) : db(db) , aqdb(aqdb) - , chain_pacemaker(chain_pacemaker) , abi_serializer_max_time(abi_serializer_max_time) , http_max_response_time(http_max_response_time) , trx_finality_status_proc(trx_finality_status_proc) { @@ -839,11 +835,11 @@ class read_only : public api_base { chain::block_id_type block_id; fc::sha256 parent_id; fc::sha256 final_on_qc; - chain::quorum_certificate_message justify; + hotstuff::quorum_certificate_message justify; uint8_t phase_counter = 0; uint32_t block_height = 0; uint64_t view_number = 0; - explicit hs_complete_proposal_message( const chain::hs_proposal_message& p ) { + explicit hs_complete_proposal_message( const hotstuff::hs_proposal_message& p ) { proposal_id = p.proposal_id; block_id = p.block_id; parent_id = p.parent_id; @@ -865,10 +861,10 @@ class read_only : public api_base { fc::sha256 b_finality_violation; chain::block_id_type block_exec; chain::block_id_type pending_proposal_block; - chain::view_number v_height; - chain::quorum_certificate_message high_qc; - chain::quorum_certificate_message current_qc; - chain::extended_schedule schedule; + hotstuff::view_number v_height; + hotstuff::quorum_certificate_message high_qc; + hotstuff::quorum_certificate_message current_qc; + hotstuff::extended_schedule schedule; vector proposals; }; @@ -1031,12 +1027,6 @@ class chain_plugin : public plugin { // Only call this after plugin_initialize()! const controller& chain() const; - void create_pacemaker(std::set my_producers, chain::bls_key_map_t finalizer_keys); - void register_pacemaker_bcast_function(std::function&, const chain::hs_message&)> bcast_hs_message); - void register_pacemaker_warn_function(std::function warn_hs_message); - void notify_hs_message( const uint32_t connection_id, const chain::hs_message& msg ); - void notify_hs_block_produced(); - chain::chain_id_type get_chain_id() const; fc::microseconds get_abi_serializer_max_time() const; bool api_accept_transactions() const; diff --git a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp index aa8d31be82..ef7a2d488a 100644 --- a/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp +++ b/plugins/net_plugin/include/eosio/net_plugin/protocol.hpp @@ -1,8 +1,7 @@ #pragma once #include -#include +#include #include -#include namespace eosio { using namespace chain; @@ -144,7 +143,7 @@ namespace eosio { sync_request_message, signed_block, packed_transaction, - hs_message>; + hotstuff::hs_message>; } // namespace eosio diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index ce069fa935..37edcd90c7 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -538,8 +538,8 @@ namespace eosio { void transaction_ack(const std::pair&); void on_irreversible_block( const block_state_ptr& block ); - void bcast_hs_message( const std::optional& exclude_peer, const hs_message& msg ); - void warn_hs_message( const uint32_t sender_peer, const hs_message_warning& code ); + void bcast_hs_message( const std::optional& exclude_peer, const hotstuff::hs_message& msg ); + void warn_hs_message( uint32_t sender_peer, const hotstuff::hs_message_warning& code ); void start_conn_timer(boost::asio::steady_timer::duration du, std::weak_ptr from_connection); void start_expire_timer(); @@ -1095,7 +1095,7 @@ namespace eosio { void handle_message( const block_id_type& id, signed_block_ptr ptr ); void handle_message( const packed_transaction& msg ) = delete; // packed_transaction_ptr overload used instead void handle_message( packed_transaction_ptr trx ); - void handle_message( const hs_message& msg ); + void handle_message( const hotstuff::hs_message& msg ); // returns calculated number of blocks combined latency uint32_t calc_block_latency(); @@ -1177,7 +1177,7 @@ namespace eosio { c->handle_message( msg ); } - void operator()( const hs_message& msg ) const { + void operator()( const hotstuff::hs_message& msg ) const { // continue call to handle_message on connection strand peer_dlog( c, "handle hs_message" ); c->handle_message( msg ); @@ -3666,9 +3666,10 @@ namespace eosio { } } - void connection::handle_message( const hs_message& msg ) { + void connection::handle_message( const hotstuff::hs_message& msg ) { peer_dlog(this, "received hs: ${msg}", ("msg", msg)); - my_impl->chain_plug->notify_hs_message(connection_id, msg); + controller& cc = my_impl->chain_plug->chain(); + cc.notify_hs_message(connection_id, msg); } size_t calc_trx_size( const packed_transaction_ptr& trx ) { @@ -3925,7 +3926,7 @@ namespace eosio { on_active_schedule(chain_plug->chain().active_producers()); } - void net_plugin_impl::bcast_hs_message( const std::optional& exclude_peer, const hs_message& msg ) { + void net_plugin_impl::bcast_hs_message( const std::optional& exclude_peer, const hotstuff::hs_message& msg ) { fc_dlog(logger, "sending hs msg: ${msg}", ("msg", msg)); buffer_factory buff_factory; @@ -3936,7 +3937,7 @@ namespace eosio { }); } - void net_plugin_impl::warn_hs_message( const uint32_t sender_peer, const hs_message_warning& code ) { + void net_plugin_impl::warn_hs_message( uint32_t sender_peer, const hotstuff::hs_message_warning& code ) { // potentially react to (repeated) receipt of invalid, irrelevant, duplicate, etc. hotstuff messages from sender_peer (connection ID) here } @@ -4286,12 +4287,13 @@ namespace eosio { void net_plugin_impl::plugin_startup() { fc_ilog( logger, "my node_id is ${id}", ("id", node_id )); - chain_plug->register_pacemaker_bcast_function( - [my = shared_from_this()](const std::optional& c, const hs_message& s) { + controller& cc = chain_plug->chain(); + cc.register_pacemaker_bcast_function( + [my = shared_from_this()](const std::optional& c, const hotstuff::hs_message& s) { my->bcast_hs_message(c, s); } ); - chain_plug->register_pacemaker_warn_function( - [my = shared_from_this()](const uint32_t c, const hs_message_warning& s) { + cc.register_pacemaker_warn_function( + [my = shared_from_this()](uint32_t c, const hotstuff::hs_message_warning& s) { my->warn_hs_message(c, s); } ); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 950941956f..a12fa8d980 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -78,6 +78,9 @@ fc::logger _transient_trx_successful_trace_log; const std::string transient_trx_failed_trace_logger_name("transient_trx_failure_tracing"); fc::logger _transient_trx_failed_trace_log; +const std::string hotstuff_logger_name("hotstuff"); +fc::logger hotstuff_logger; + namespace eosio { static auto _producer_plugin = application::register_plugin(); @@ -489,7 +492,7 @@ class producer_plugin_impl : public std::enable_shared_from_this _signature_providers; - bls_key_map_t _finalizer_keys; + hotstuff::bls_key_map_t _finalizer_keys; std::set _producers; boost::asio::deadline_timer _timer; block_timing_util::producer_watermarks _producer_watermarks; @@ -1337,7 +1340,7 @@ void producer_plugin_impl::plugin_startup() { EOS_ASSERT(_producers.empty() || chain_plug->accept_transactions(), plugin_config_exception, "node cannot have any producer-name configured because no block production is possible with no [api|p2p]-accepted-transactions"); - chain_plug->create_pacemaker(_producers, std::move(_finalizer_keys)); + chain.create_pacemaker(_producers, std::move(_finalizer_keys), hotstuff_logger); _finalizer_keys.clear(); _accepted_block_connection.emplace(chain.accepted_block.connect([this](const auto& bsp) { on_block(bsp); })); @@ -1429,6 +1432,7 @@ void producer_plugin::handle_sighup() { fc::logger::update(trx_logger_name, _trx_log); fc::logger::update(transient_trx_successful_trace_logger_name, _transient_trx_successful_trace_log); fc::logger::update(transient_trx_failed_trace_logger_name, _transient_trx_failed_trace_log); + fc::logger::update( hotstuff_logger_name, hotstuff_logger ); } void producer_plugin::pause() { @@ -2650,7 +2654,6 @@ void producer_plugin_impl::produce_block() { block_state_ptr new_bs = chain.head_block_state(); producer_plugin::produced_block_metrics metrics; - chain_plug->notify_hs_block_produced(); br.total_time += fc::time_point::now() - start; diff --git a/tests/chain_plugin_tests.cpp b/tests/chain_plugin_tests.cpp index ee27bf89b8..b40d38e5c9 100644 --- a/tests/chain_plugin_tests.cpp +++ b/tests/chain_plugin_tests.cpp @@ -90,7 +90,7 @@ BOOST_FIXTURE_TEST_CASE( get_block_with_invalid_abi, validating_tester ) try { char headnumstr[20]; sprintf(headnumstr, "%d", headnum); chain_apis::read_only::get_raw_block_params param{headnumstr}; - chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); // block should be decoded successfully auto block = plugin.get_raw_block(param, fc::time_point::maximum()); @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE( get_consensus_parameters ) try { tester t{setup_policy::old_wasm_parser}; t.produce_blocks(1); - chain_apis::read_only plugin(*(t.control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), nullptr); + chain_apis::read_only plugin(*(t.control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), nullptr); auto parms = plugin.get_consensus_parameters({}, fc::time_point::maximum()); @@ -191,7 +191,7 @@ BOOST_FIXTURE_TEST_CASE( get_account, validating_tester ) try { produce_block(); - chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), nullptr); + chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), nullptr); chain_apis::read_only::get_account_params p{"alice"_n}; diff --git a/tests/get_producers_tests.cpp b/tests/get_producers_tests.cpp index 1fd574a2ee..3655defaaf 100644 --- a/tests/get_producers_tests.cpp +++ b/tests/get_producers_tests.cpp @@ -17,7 +17,7 @@ using namespace eosio::testing; BOOST_AUTO_TEST_CASE( get_producers) { try { tester chain; - eosio::chain_apis::read_only plugin(*(chain.control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(chain.control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); eosio::chain_apis::read_only::get_producers_params params = { .json = true, .lower_bound = "", .limit = 21 }; auto results = plugin.get_producers(params, fc::time_point::maximum()); @@ -53,7 +53,7 @@ BOOST_AUTO_TEST_CASE( get_producers_from_table) { try { // ensure that enough voting is occurring so that producer1111 is elected as the producer chain.cross_15_percent_threshold(); - eosio::chain_apis::read_only plugin(*(chain.control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(chain.control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); eosio::chain_apis::read_only::get_producers_params params = { .json = true, .lower_bound = "", .limit = 21 }; auto results = plugin.get_producers(params, fc::time_point::maximum()); diff --git a/tests/get_table_seckey_tests.cpp b/tests/get_table_seckey_tests.cpp index b3ad3b06a5..0ee0990577 100644 --- a/tests/get_table_seckey_tests.cpp +++ b/tests/get_table_seckey_tests.cpp @@ -44,7 +44,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_next_key_test, validating_tester ) try { set_abi( "test"_n, test_contracts::get_table_seckey_test_abi() ); produce_block(); - chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); chain_apis::read_only::get_table_rows_params params = []{ chain_apis::read_only::get_table_rows_params params{}; params.json=true; diff --git a/tests/get_table_tests.cpp b/tests/get_table_tests.cpp index b4b68bd7fc..eb6640d538 100644 --- a/tests/get_table_tests.cpp +++ b/tests/get_table_tests.cpp @@ -91,7 +91,7 @@ BOOST_FIXTURE_TEST_CASE( get_scope_test, validating_tester ) try { produce_blocks(1); // iterate over scope - eosio::chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); eosio::chain_apis::read_only::get_table_by_scope_params param{"eosio.token"_n, "accounts"_n, "inita", "", 10}; eosio::chain_apis::read_only::get_table_by_scope_result result = plugin.read_only::get_table_by_scope(param, fc::time_point::maximum()); @@ -196,7 +196,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_test, validating_tester ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); eosio::chain_apis::read_only::get_table_rows_params p; p.code = "eosio.token"_n; p.scope = "inita"; @@ -366,7 +366,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_by_seckey_test, validating_tester ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); eosio::chain_apis::read_only::get_table_rows_params p; p.code = "eosio"_n; p.scope = "eosio"; @@ -518,7 +518,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_next_key_test, validating_tester ) try { // } - chain_apis::read_only plugin(*(this->control), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); chain_apis::read_only::get_table_rows_params params = []{ chain_apis::read_only::get_table_rows_params params{}; params.json=true; diff --git a/tests/test_chain_plugin.cpp b/tests/test_chain_plugin.cpp index 42925450b7..9332bbd8c6 100644 --- a/tests/test_chain_plugin.cpp +++ b/tests/test_chain_plugin.cpp @@ -227,7 +227,7 @@ class chain_plugin_tester : public validating_tester { read_only::get_account_results get_account_info(const account_name acct){ auto account_object = control->get_account(acct); read_only::get_account_params params = { account_object.name }; - chain_apis::read_only plugin(*(control.get()), {}, {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); + chain_apis::read_only plugin(*(control.get()), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}); auto res = plugin.get_account(params, fc::time_point::maximum())(); BOOST_REQUIRE(!std::holds_alternative(res)); return std::get(std::move(res)); From bd037a74036f3b07ace61b881bc34fd7e40bfe35 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 13:18:54 -0600 Subject: [PATCH 02/13] GH-1916 Provide default no-op functions for testing --- libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp | 4 ++-- libraries/testing/tester.cpp | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp index 5d9561fd33..c6b73ef99b 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp @@ -80,8 +80,8 @@ namespace eosio::hotstuff { boost::signals2::scoped_connection _irreversible_block_connection; qc_chain _qc_chain; - std::function&, const hs_message&)> bcast_hs_message; - std::function warn_hs_message; + std::function&, const hs_message&)> bcast_hs_message = [](const std::optional&, const hs_message&){}; + std::function warn_hs_message = [](uint32_t, const hs_message_warning&){}; uint32_t _quorum_threshold = 15; //FIXME/TODO: calculate from schedule fc::logger& _logger; diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 9341876d70..8c6066d3a8 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -350,8 +350,6 @@ namespace eosio { namespace testing { } }); control->create_pacemaker({}, {}, test_logger); - control->register_pacemaker_bcast_function([](const std::optional&, const hotstuff::hs_message&){}); - control->register_pacemaker_warn_function([](uint32_t, const hotstuff::hs_message_warning&){}); } void base_tester::open( protocol_feature_set&& pfs, const snapshot_reader_ptr& snapshot ) { From ade1a285a79faa58b753b47a45182e577de86c40 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 13:19:09 -0600 Subject: [PATCH 03/13] GH-1916 Add hotstuff --- libraries/testing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/testing/CMakeLists.txt b/libraries/testing/CMakeLists.txt index 491a868536..cf6476f9d7 100644 --- a/libraries/testing/CMakeLists.txt +++ b/libraries/testing/CMakeLists.txt @@ -50,7 +50,7 @@ add_library( eosio_testing ${HEADERS} ) -target_link_libraries( eosio_testing eosio_testing_contracts eosio_chain fc chainbase Logging IR WAST WASM) +target_link_libraries( eosio_testing eosio_testing_contracts eosio_chain hotstuff fc chainbase Logging IR WAST WASM) target_include_directories( eosio_testing PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../wasm-jit/Include" From d7a82846f8d59a6cdf88dc3977c2631f1f921bc0 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 14:00:52 -0600 Subject: [PATCH 04/13] GH-1916 Add hotstuff activation test --- unittests/api_tests.cpp | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 3b44500395..9b218978bf 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -3862,26 +3862,43 @@ BOOST_AUTO_TEST_CASE(set_finalizer_test) { try { t.produce_block(); - // Create producer accounts - vector producers = { + // Create finalizer accounts + vector finalizers = { "inita"_n, "initb"_n, "initc"_n, "initd"_n, "inite"_n, "initf"_n, "initg"_n, "inith"_n, "initi"_n, "initj"_n, "initk"_n, "initl"_n, "initm"_n, "initn"_n, "inito"_n, "initp"_n, "initq"_n, "initr"_n, "inits"_n, "initt"_n, "initu"_n }; - t.create_accounts(producers); + t.create_accounts(finalizers); t.produce_block(); // activate hotstuff - t.set_finalizers(producers); - auto block = t.produce_block(); + t.set_finalizers(finalizers); + auto block = t.produce_block(); // this block contains the header extension of the finalizer set std::optional ext = block->extract_header_extension(hs_finalizer_set_extension::extension_id()); - BOOST_TEST(!!ext); - BOOST_TEST(std::get(*ext).finalizers.size() == producers.size()); + BOOST_TEST(std::get(*ext).finalizers.size() == finalizers.size()); BOOST_TEST(std::get(*ext).generation == 1); - BOOST_TEST(std::get(*ext).fthreshold == producers.size() / 3 * 2); + BOOST_TEST(std::get(*ext).fthreshold == finalizers.size() / 3 * 2); + + // old dpos still in affect until block is irreversible + BOOST_TEST(block->confirmed == 0); + block_state_ptr block_state = t.control->fetch_block_state_by_id(block->calculate_id()); + BOOST_REQUIRE(!!block_state); + BOOST_TEST(block_state->dpos_irreversible_blocknum > 2); + + block = t.produce_block(); // only one producer so now this block is irreversible, next block will be hotstuff + BOOST_TEST(block->confirmed == 0); + block_state = t.control->fetch_block_state_by_id(block->calculate_id()); + BOOST_REQUIRE(!!block_state); + BOOST_TEST(block_state->dpos_irreversible_blocknum > 2); + + block = t.produce_block(); // hotstuff now active + BOOST_TEST(block->confirmed == std::numeric_limits::max()); + block_state = t.control->fetch_block_state_by_id(block->calculate_id()); + BOOST_REQUIRE(!!block_state); + BOOST_TEST(block_state->dpos_irreversible_blocknum == hs_dpos_irreversible_blocknum); } FC_LOG_AND_RETHROW() } From 476e3b8c190a2c1b2586d94139bb6a25b31e8182 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 14:01:16 -0600 Subject: [PATCH 05/13] GH-1916 Add hotstuff to dev package --- libraries/hotstuff/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/hotstuff/CMakeLists.txt b/libraries/hotstuff/CMakeLists.txt index 0a2a7c6b06..6fd8501988 100644 --- a/libraries/hotstuff/CMakeLists.txt +++ b/libraries/hotstuff/CMakeLists.txt @@ -15,3 +15,10 @@ target_include_directories( hotstuff ) add_subdirectory( test ) + +install(TARGETS hotstuff + LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} COMPONENT dev EXCLUDE_FROM_ALL + ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} COMPONENT dev EXCLUDE_FROM_ALL) +install(DIRECTORY include/eosio/hotstuff/ + DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/eosio/hotstuff + COMPONENT dev EXCLUDE_FROM_ALL) From 0dc9c10d6ae1cffdb9f98ed9cc382a0f4ee9e14c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 14:57:49 -0600 Subject: [PATCH 06/13] GH-1916 Remove include of hotstuff.hpp from controller.hpp since libtester does not like std::span --- libraries/chain/controller.cpp | 5 +++-- libraries/chain/include/eosio/chain/controller.hpp | 11 ++++++++--- libraries/hotstuff/chain_pacemaker.cpp | 10 +++++----- .../include/eosio/hotstuff/chain_pacemaker.hpp | 2 +- .../hotstuff/include/eosio/hotstuff/hotstuff.hpp | 5 ++++- .../hotstuff/include/eosio/hotstuff/qc_chain.hpp | 2 +- libraries/hotstuff/qc_chain.cpp | 7 +++++-- libraries/hotstuff/test/test_hotstuff.cpp | 2 +- plugins/net_plugin/net_plugin.cpp | 2 +- plugins/producer_plugin/producer_plugin.cpp | 4 ++-- 10 files changed, 31 insertions(+), 19 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index f2d2af5315..030c738794 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -3332,7 +3333,7 @@ void controller::get_finalizer_state( hotstuff::finalizer_state& fs ) const { my->pacemaker->get_state(fs); } -void controller::create_pacemaker(std::set my_producers, hotstuff::bls_key_map_t finalizer_keys, fc::logger& hotstuff_logger) { +void controller::create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger) { EOS_ASSERT( !my->pacemaker, misc_exception, "duplicate chain_pacemaker initialization" ); my->pacemaker.emplace(this, std::move(my_producers), std::move(finalizer_keys), hotstuff_logger); } @@ -3342,7 +3343,7 @@ void controller::register_pacemaker_bcast_function(std::functionpacemaker->register_bcast_function(std::move(bcast_hs_message)); } -void controller::register_pacemaker_warn_function(std::function warn_hs_message) { +void controller::register_pacemaker_warn_function(std::function warn_hs_message) { EOS_ASSERT( my->pacemaker, misc_exception, "chain_pacemaker not created" ); my->pacemaker->register_warn_function(std::move(warn_hs_message)); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index a8d4bd13b5..9951537425 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include @@ -22,6 +21,12 @@ namespace boost { namespace asio { namespace eosio { namespace vm { class wasm_allocator; }} +namespace eosio::hotstuff { + struct hs_message; + struct finalizer_state; + enum class hs_message_warning; +} + namespace eosio { namespace chain { struct finalizer_set; @@ -302,9 +307,9 @@ namespace eosio { namespace chain { void get_finalizer_state( hotstuff::finalizer_state& fs ) const; // called from net threads void notify_hs_message( const uint32_t connection_id, const hotstuff::hs_message& msg ); - void create_pacemaker(std::set my_producers, hotstuff::bls_key_map_t finalizer_keys, fc::logger& hotstuff_logger); + void create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger); void register_pacemaker_bcast_function(std::function&, const hotstuff::hs_message&)> bcast_hs_message); - void register_pacemaker_warn_function(std::function warn_hs_message); + void register_pacemaker_warn_function(std::function warn_hs_message); bool light_validation_allowed() const; bool skip_auth_check()const; diff --git a/libraries/hotstuff/chain_pacemaker.cpp b/libraries/hotstuff/chain_pacemaker.cpp index 210a581247..ad638808aa 100644 --- a/libraries/hotstuff/chain_pacemaker.cpp +++ b/libraries/hotstuff/chain_pacemaker.cpp @@ -104,7 +104,7 @@ namespace eosio { namespace hotstuff { #warning TODO get a data directory str passed into the chain_pacemaker ctor and use it to compose the absolute filepathname that is passed to qc_chain ctor chain_pacemaker::chain_pacemaker(controller* chain, std::set my_producers, - bls_key_map_t finalizer_keys, + std::map finalizer_keys, fc::logger& logger) : _chain(chain), _qc_chain("default", this, std::move(my_producers), std::move(finalizer_keys), logger, eosio::chain::config::safetydb_filename), @@ -221,15 +221,15 @@ namespace eosio { namespace hotstuff { } void chain_pacemaker::send_hs_proposal_msg(const hs_proposal_message& msg, const std::string& id, const std::optional& exclude_peer) { - bcast_hs_message(exclude_peer, msg); + bcast_hs_message(exclude_peer, {msg}); } void chain_pacemaker::send_hs_vote_msg(const hs_vote_message& msg, const std::string& id, const std::optional& exclude_peer) { - bcast_hs_message(exclude_peer, msg); + bcast_hs_message(exclude_peer, {msg}); } void chain_pacemaker::send_hs_new_view_msg(const hs_new_view_message& msg, const std::string& id, const std::optional& exclude_peer) { - bcast_hs_message(exclude_peer, msg); + bcast_hs_message(exclude_peer, {msg}); } void chain_pacemaker::send_hs_message_warning(uint32_t sender_peer, const hs_message_warning code) { @@ -243,7 +243,7 @@ namespace eosio { namespace hotstuff { [this, connection_id](const hs_vote_message& m) { on_hs_vote_msg(connection_id, m); }, [this, connection_id](const hs_proposal_message& m) { on_hs_proposal_msg(connection_id, m); }, [this, connection_id](const hs_new_view_message& m) { on_hs_new_view_msg(connection_id, m); }, - }, msg); + }, msg.msg); } // called from net threads diff --git a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp index c6b73ef99b..18fbf003be 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp @@ -20,7 +20,7 @@ namespace eosio::hotstuff { chain_pacemaker(controller* chain, std::set my_producers, - bls_key_map_t finalizer_keys, + std::map finalizer_keys, fc::logger& logger); void register_bcast_function(std::function&, const hs_message&)> broadcast_hs_message); void register_warn_function(std::function warning_hs_message); diff --git a/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp b/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp index a23ee82006..62c5cacae4 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp @@ -77,7 +77,9 @@ namespace eosio::hotstuff { quorum_certificate_message high_qc; //justification }; - using hs_message = std::variant; + struct hs_message { + std::variant msg; + }; enum class hs_message_warning { discarded, // default code for dropped messages (irrelevant, redundant, ...) @@ -118,3 +120,4 @@ FC_REFLECT(eosio::hotstuff::hs_vote_message, (proposal_id)(finalizer_key)(sig)); FC_REFLECT(eosio::hotstuff::hs_proposal_message, (proposal_id)(block_id)(parent_id)(final_on_qc)(justify)(phase_counter)); FC_REFLECT(eosio::hotstuff::hs_new_view_message, (high_qc)); FC_REFLECT(eosio::hotstuff::finalizer_state, (chained_mode)(b_leaf)(b_lock)(b_exec)(b_finality_violation)(block_exec)(pending_proposal_block)(v_height)(high_qc)(current_qc)(schedule)(proposals)); +FC_REFLECT(eosio::hotstuff::hs_message, (msg)); diff --git a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp index de3be8f4d9..04afcd2ea8 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp @@ -153,7 +153,7 @@ namespace eosio::hotstuff { qc_chain(std::string id, base_pacemaker* pacemaker, std::set my_producers, - bls_key_map_t finalizer_keys, + std::map finalizer_keys, fc::logger& logger, std::string safety_state_file); diff --git a/libraries/hotstuff/qc_chain.cpp b/libraries/hotstuff/qc_chain.cpp index 3746be6133..937c28310c 100644 --- a/libraries/hotstuff/qc_chain.cpp +++ b/libraries/hotstuff/qc_chain.cpp @@ -165,18 +165,21 @@ namespace eosio::hotstuff { qc_chain::qc_chain(std::string id, base_pacemaker* pacemaker, std::set my_producers, - bls_key_map_t finalizer_keys, + std::map finalizer_keys, fc::logger& logger, std::string safety_state_file) : _pacemaker(pacemaker), _my_producers(std::move(my_producers)), - _my_finalizer_keys(std::move(finalizer_keys)), _id(std::move(id)), _safety_state_file(safety_state_file), _logger(logger) { //todo : read liveness state / select initialization heuristics ? + for (const auto& kp : finalizer_keys) { + _my_finalizer_keys[fc::crypto::blslib::bls_public_key{kp.first}] = fc::crypto::blslib::bls_private_key{kp.second}; + } + if (!_safety_state_file.empty()) { _safety_state_file_handle.set_file_path(safety_state_file); state_db_manager::read(_safety_state_file, _safety_state); diff --git a/libraries/hotstuff/test/test_hotstuff.cpp b/libraries/hotstuff/test/test_hotstuff.cpp index ff9dbe452d..a6c2964cae 100644 --- a/libraries/hotstuff/test/test_hotstuff.cpp +++ b/libraries/hotstuff/test/test_hotstuff.cpp @@ -75,7 +75,7 @@ class hotstuff_test_handler { for (size_t i = 0 ; i < replicas.size() ; i++){ fc::crypto::blslib::bls_private_key sk = fc::crypto::blslib::bls_private_key(replica_keys[i]); - bls_key_map_t keys{{sk.get_public_key(), sk}}; + std::map keys{{sk.get_public_key().to_string(), sk.to_string()}}; qc_chain *qcc_ptr = new qc_chain(replica_keys[i].to_string(), &tpm, {replicas[i]}, keys, hotstuff_logger, std::string()); std::shared_ptr qcc_shared_ptr(qcc_ptr); _qc_chains.push_back( std::make_pair(replicas[i], qcc_shared_ptr) ); diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 37edcd90c7..fdf0beeb53 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4293,7 +4293,7 @@ namespace eosio { my->bcast_hs_message(c, s); } ); cc.register_pacemaker_warn_function( - [my = shared_from_this()](uint32_t c, const hotstuff::hs_message_warning& s) { + [my = shared_from_this()](uint32_t c, hotstuff::hs_message_warning s) { my->warn_hs_message(c, s); } ); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index a12fa8d980..d52634c925 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -492,7 +492,7 @@ class producer_plugin_impl : public std::enable_shared_from_this _signature_providers; - hotstuff::bls_key_map_t _finalizer_keys; + std::map _finalizer_keys; // public, private std::set _producers; boost::asio::deadline_timer _timer; block_timing_util::producer_watermarks _producer_watermarks; @@ -1138,7 +1138,7 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia const auto bls = app().get_plugin().bls_public_key_for_specification(key_spec_pair); if (bls) { const auto& [pubkey, privkey] = *bls; - _finalizer_keys[pubkey] = privkey; + _finalizer_keys[pubkey.to_string()] = privkey.to_string(); } } catch(secure_enclave_exception& e) { elog("Error with Secure Enclave signature provider: ${e}; ignoring ${val}", ("e", e.top_message())("val", key_spec_pair)); From 4d6318ef00051c6afe3a2ae1db0174dc5aa13b0a Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 16:11:23 -0600 Subject: [PATCH 07/13] GH-1916 Add hotstuff lib to libtester --- CMakeModules/EosioTester.cmake.in | 2 ++ CMakeModules/EosioTesterBuild.cmake.in | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CMakeModules/EosioTester.cmake.in b/CMakeModules/EosioTester.cmake.in index 155819b03f..f7816310ac 100644 --- a/CMakeModules/EosioTester.cmake.in +++ b/CMakeModules/EosioTester.cmake.in @@ -47,6 +47,7 @@ find_library(libfc fc @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libsecp256k1 secp256k1 @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libbn256 bn256 @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libbls12-381 bls12-381 @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) +find_library(libhotstuff hotstuff @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libwasm WASM @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libwast WAST @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) @@ -72,6 +73,7 @@ add_library(EosioChain INTERFACE) target_link_libraries(EosioChain INTERFACE ${libchain} + ${libhotstuff} ${libfc} ${libwast} ${libwasm} diff --git a/CMakeModules/EosioTesterBuild.cmake.in b/CMakeModules/EosioTesterBuild.cmake.in index 91828dc700..61a89a45e6 100644 --- a/CMakeModules/EosioTesterBuild.cmake.in +++ b/CMakeModules/EosioTesterBuild.cmake.in @@ -44,6 +44,7 @@ find_library(libfc fc @CMAKE_BINARY_DIR@/libraries/libfc NO_DEFAULT_PATH) find_library(libsecp256k1 secp256k1 @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) find_library(libbn256 bn256 @CMAKE_BINARY_DIR@/libraries/libfc/libraries/bn256/src NO_DEFAULT_PATH) find_library(libbls12-381 bls12-381 @CMAKE_BINARY_DIR@/libraries/libfc/libraries/bls12-381 NO_DEFAULT_PATH) +find_library(libhotstuff hotstuff @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libwasm WASM @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WASM NO_DEFAULT_PATH) find_library(libwast WAST @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WAST NO_DEFAULT_PATH) @@ -69,6 +70,7 @@ add_library(EosioChain INTERFACE) target_link_libraries(EosioChain INTERFACE ${libchain} + ${libhotstuff} ${libfc} ${libwast} ${libwasm} From df9ce4a694ffd6a02fce5d94b2cbd9b038796caa Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 21 Nov 2023 17:06:55 -0600 Subject: [PATCH 08/13] GH-1916 Add hotstuff lib to libtester --- CMakeModules/EosioTesterBuild.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeModules/EosioTesterBuild.cmake.in b/CMakeModules/EosioTesterBuild.cmake.in index 61a89a45e6..06002a3d71 100644 --- a/CMakeModules/EosioTesterBuild.cmake.in +++ b/CMakeModules/EosioTesterBuild.cmake.in @@ -44,7 +44,7 @@ find_library(libfc fc @CMAKE_BINARY_DIR@/libraries/libfc NO_DEFAULT_PATH) find_library(libsecp256k1 secp256k1 @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) find_library(libbn256 bn256 @CMAKE_BINARY_DIR@/libraries/libfc/libraries/bn256/src NO_DEFAULT_PATH) find_library(libbls12-381 bls12-381 @CMAKE_BINARY_DIR@/libraries/libfc/libraries/bls12-381 NO_DEFAULT_PATH) -find_library(libhotstuff hotstuff @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) +find_library(libhotstuff hotstuff @CMAKE_BINARY_DIR@/libraries/hotstuff NO_DEFAULT_PATH) find_library(libwasm WASM @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WASM NO_DEFAULT_PATH) find_library(libwast WAST @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WAST NO_DEFAULT_PATH) From 2520c4cb523616dde7a19291bb9b77ebf0a59b2b Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 22 Nov 2023 08:00:43 -0600 Subject: [PATCH 09/13] GH-1916 Improve test --- unittests/api_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 9b218978bf..b4b903d55c 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -3886,13 +3886,13 @@ BOOST_AUTO_TEST_CASE(set_finalizer_test) { try { BOOST_TEST(block->confirmed == 0); block_state_ptr block_state = t.control->fetch_block_state_by_id(block->calculate_id()); BOOST_REQUIRE(!!block_state); - BOOST_TEST(block_state->dpos_irreversible_blocknum > 2); + BOOST_TEST(block_state->dpos_irreversible_blocknum != hs_dpos_irreversible_blocknum); block = t.produce_block(); // only one producer so now this block is irreversible, next block will be hotstuff BOOST_TEST(block->confirmed == 0); block_state = t.control->fetch_block_state_by_id(block->calculate_id()); BOOST_REQUIRE(!!block_state); - BOOST_TEST(block_state->dpos_irreversible_blocknum > 2); + BOOST_TEST(block_state->dpos_irreversible_blocknum != hs_dpos_irreversible_blocknum); block = t.produce_block(); // hotstuff now active BOOST_TEST(block->confirmed == std::numeric_limits::max()); From c1075823eca4345b01db730a543715cbbc124ea2 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 22 Nov 2023 10:53:42 -0600 Subject: [PATCH 10/13] GH-1916 Fix validating tester to have a chain_pacemaker. --- .../testing/include/eosio/testing/tester.hpp | 16 +--------------- libraries/testing/tester.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index ea503c84c0..7a69c5fe0e 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -572,21 +572,7 @@ namespace eosio { namespace testing { vcfg.contracts_console = false; } - static unique_ptr create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis, deep_mind_handler* dmlog = nullptr) { - unique_ptr validating_node = std::make_unique(vcfg, make_protocol_feature_set(), genesis.compute_chain_id()); - validating_node->add_indices(); - if(dmlog) - { - validating_node->enable_deep_mind(dmlog); - } - if (use_genesis) { - validating_node->startup( [](){}, []() { return false; }, genesis ); - } - else { - validating_node->startup( [](){}, []() { return false; } ); - } - return validating_node; - } + static unique_ptr create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis, deep_mind_handler* dmlog = nullptr); validating_tester(const fc::temp_directory& tempdir, bool use_genesis) { auto def_conf = default_config(tempdir); diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 8c6066d3a8..152d53ccb8 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -1317,6 +1317,24 @@ namespace eosio { namespace testing { execute_setup_policy(policy); } + unique_ptr validating_tester::create_validating_node(controller::config vcfg, const genesis_state& genesis, bool use_genesis, deep_mind_handler* dmlog) { + unique_ptr validating_node = std::make_unique(vcfg, make_protocol_feature_set(), genesis.compute_chain_id()); + validating_node->add_indices(); + validating_node->create_pacemaker({}, {}, test_logger); + + if(dmlog) + { + validating_node->enable_deep_mind(dmlog); + } + if (use_genesis) { + validating_node->startup( [](){}, []() { return false; }, genesis ); + } + else { + validating_node->startup( [](){}, []() { return false; } ); + } + return validating_node; + } + bool fc_exception_message_is::operator()( const fc::exception& ex ) { auto message = ex.get_log().at( 0 ).get_message(); bool match = (message == expected); From a512505b1e2ffc23e7106a3d76abee0d7f668538 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 22 Nov 2023 10:55:51 -0600 Subject: [PATCH 11/13] GH-1916 Minor whitespace changes --- libraries/chain/controller.cpp | 18 +++++++++--------- .../chain/include/eosio/chain/controller.hpp | 6 +++--- .../include/eosio/hotstuff/hotstuff.hpp | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 030c738794..95fc5241d4 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3324,15 +3324,6 @@ int64_t controller::set_proposed_producers( vector producers return version; } -void controller::set_proposed_finalizers( const finalizer_set& fin_set ) { - my->set_proposed_finalizers(fin_set); -} - -void controller::get_finalizer_state( hotstuff::finalizer_state& fs ) const { - EOS_ASSERT( my->pacemaker, misc_exception, "chain_pacemaker not created" ); - my->pacemaker->get_state(fs); -} - void controller::create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger) { EOS_ASSERT( !my->pacemaker, misc_exception, "duplicate chain_pacemaker initialization" ); my->pacemaker.emplace(this, std::move(my_producers), std::move(finalizer_keys), hotstuff_logger); @@ -3348,6 +3339,15 @@ void controller::register_pacemaker_warn_function(std::functionpacemaker->register_warn_function(std::move(warn_hs_message)); } +void controller::set_proposed_finalizers( const finalizer_set& fin_set ) { + my->set_proposed_finalizers(fin_set); +} + +void controller::get_finalizer_state( hotstuff::finalizer_state& fs ) const { + EOS_ASSERT( my->pacemaker, misc_exception, "chain_pacemaker not created" ); + my->pacemaker->get_state(fs); +} + // called from net threads void controller::notify_hs_message( const uint32_t connection_id, const hotstuff::hs_message& msg ) { my->pacemaker->on_hs_msg(connection_id, msg); diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 9951537425..e17c82b6d4 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -302,14 +302,14 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); + void create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger); + void register_pacemaker_bcast_function(std::function&, const hotstuff::hs_message&)> bcast_hs_message); + void register_pacemaker_warn_function(std::function warn_hs_message); // called by host function set_finalizers void set_proposed_finalizers( const finalizer_set& fin_set ); void get_finalizer_state( hotstuff::finalizer_state& fs ) const; // called from net threads void notify_hs_message( const uint32_t connection_id, const hotstuff::hs_message& msg ); - void create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger); - void register_pacemaker_bcast_function(std::function&, const hotstuff::hs_message&)> bcast_hs_message); - void register_pacemaker_warn_function(std::function warn_hs_message); bool light_validation_allowed() const; bool skip_auth_check()const; diff --git a/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp b/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp index 62c5cacae4..9556ec1c3b 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/hotstuff.hpp @@ -59,7 +59,7 @@ namespace eosio::hotstuff { struct hs_proposal_message { fc::sha256 proposal_id; //vote on proposal - chain::block_id_type block_id; + chain::block_id_type block_id; fc::sha256 parent_id; //new proposal fc::sha256 final_on_qc; quorum_certificate_message justify; //justification From 05b110bce7f415c146f4e14946cae527f1df66ed Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 22 Nov 2023 11:08:28 -0600 Subject: [PATCH 12/13] GH-1916 Add bls_pub_priv_key_map_t typedef --- libraries/chain/controller.cpp | 2 +- libraries/chain/include/eosio/chain/controller.hpp | 3 ++- libraries/hotstuff/chain_pacemaker.cpp | 2 +- .../hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp | 2 +- libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp | 4 +++- libraries/hotstuff/qc_chain.cpp | 2 +- libraries/hotstuff/test/test_hotstuff.cpp | 2 +- plugins/producer_plugin/producer_plugin.cpp | 6 +++--- 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 95fc5241d4..9093d19931 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -3324,7 +3324,7 @@ int64_t controller::set_proposed_producers( vector producers return version; } -void controller::create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger) { +void controller::create_pacemaker(std::set my_producers, hotstuff::bls_pub_priv_key_map_t finalizer_keys, fc::logger& hotstuff_logger) { EOS_ASSERT( !my->pacemaker, misc_exception, "duplicate chain_pacemaker initialization" ); my->pacemaker.emplace(this, std::move(my_producers), std::move(finalizer_keys), hotstuff_logger); } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index e17c82b6d4..451b5ec3af 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -25,6 +25,7 @@ namespace eosio::hotstuff { struct hs_message; struct finalizer_state; enum class hs_message_warning; + using bls_pub_priv_key_map_t = std::map; } namespace eosio { namespace chain { @@ -302,7 +303,7 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); - void create_pacemaker(std::set my_producers, std::map finalizer_keys, fc::logger& hotstuff_logger); + void create_pacemaker(std::set my_producers, hotstuff::bls_pub_priv_key_map_t finalizer_keys, fc::logger& hotstuff_logger); void register_pacemaker_bcast_function(std::function&, const hotstuff::hs_message&)> bcast_hs_message); void register_pacemaker_warn_function(std::function warn_hs_message); // called by host function set_finalizers diff --git a/libraries/hotstuff/chain_pacemaker.cpp b/libraries/hotstuff/chain_pacemaker.cpp index ad638808aa..1002a22718 100644 --- a/libraries/hotstuff/chain_pacemaker.cpp +++ b/libraries/hotstuff/chain_pacemaker.cpp @@ -104,7 +104,7 @@ namespace eosio { namespace hotstuff { #warning TODO get a data directory str passed into the chain_pacemaker ctor and use it to compose the absolute filepathname that is passed to qc_chain ctor chain_pacemaker::chain_pacemaker(controller* chain, std::set my_producers, - std::map finalizer_keys, + bls_pub_priv_key_map_t finalizer_keys, fc::logger& logger) : _chain(chain), _qc_chain("default", this, std::move(my_producers), std::move(finalizer_keys), logger, eosio::chain::config::safetydb_filename), diff --git a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp index 18fbf003be..d1b82a27ac 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp @@ -20,7 +20,7 @@ namespace eosio::hotstuff { chain_pacemaker(controller* chain, std::set my_producers, - std::map finalizer_keys, + bls_pub_priv_key_map_t finalizer_keys, fc::logger& logger); void register_bcast_function(std::function&, const hs_message&)> broadcast_hs_message); void register_warn_function(std::function warning_hs_message); diff --git a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp index 04afcd2ea8..473ba86575 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp @@ -144,6 +144,8 @@ namespace eosio::hotstuff { std::set finalizers; // finalizers that have voted on the proposal }; + using bls_pub_priv_key_map_t = std::map; + // Concurrency note: qc_chain is a single-threaded and lock-free decision engine. // All thread synchronization, if any, is external. class qc_chain { @@ -153,7 +155,7 @@ namespace eosio::hotstuff { qc_chain(std::string id, base_pacemaker* pacemaker, std::set my_producers, - std::map finalizer_keys, + bls_pub_priv_key_map_t finalizer_keys, fc::logger& logger, std::string safety_state_file); diff --git a/libraries/hotstuff/qc_chain.cpp b/libraries/hotstuff/qc_chain.cpp index 937c28310c..00bac1f853 100644 --- a/libraries/hotstuff/qc_chain.cpp +++ b/libraries/hotstuff/qc_chain.cpp @@ -165,7 +165,7 @@ namespace eosio::hotstuff { qc_chain::qc_chain(std::string id, base_pacemaker* pacemaker, std::set my_producers, - std::map finalizer_keys, + bls_pub_priv_key_map_t finalizer_keys, fc::logger& logger, std::string safety_state_file) : _pacemaker(pacemaker), diff --git a/libraries/hotstuff/test/test_hotstuff.cpp b/libraries/hotstuff/test/test_hotstuff.cpp index a6c2964cae..c66da253af 100644 --- a/libraries/hotstuff/test/test_hotstuff.cpp +++ b/libraries/hotstuff/test/test_hotstuff.cpp @@ -75,7 +75,7 @@ class hotstuff_test_handler { for (size_t i = 0 ; i < replicas.size() ; i++){ fc::crypto::blslib::bls_private_key sk = fc::crypto::blslib::bls_private_key(replica_keys[i]); - std::map keys{{sk.get_public_key().to_string(), sk.to_string()}}; + eosio::hotstuff::bls_pub_priv_key_map_t keys{{sk.get_public_key().to_string(), sk.to_string()}}; qc_chain *qcc_ptr = new qc_chain(replica_keys[i].to_string(), &tpm, {replicas[i]}, keys, hotstuff_logger, std::string()); std::shared_ptr qcc_shared_ptr(qcc_ptr); _qc_chains.push_back( std::make_pair(replicas[i], qcc_shared_ptr) ); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index d52634c925..2927db7357 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -492,9 +492,9 @@ class producer_plugin_impl : public std::enable_shared_from_this _signature_providers; - std::map _finalizer_keys; // public, private - std::set _producers; - boost::asio::deadline_timer _timer; + hotstuff::bls_pub_priv_key_map_t _finalizer_keys; // public, private + std::set _producers; + boost::asio::deadline_timer _timer; block_timing_util::producer_watermarks _producer_watermarks; pending_block_mode _pending_block_mode = pending_block_mode::speculating; unapplied_transaction_queue _unapplied_transactions; From 884d978dc0cdf482795480736c12d28fd9019ca6 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 22 Nov 2023 14:09:24 -0600 Subject: [PATCH 13/13] GH-1916 Add producer schedule change tests. --- libraries/chain/block_header_state.cpp | 1 + libraries/chain/webassembly/privileged.cpp | 2 +- unittests/producer_schedule_hs_tests.cpp | 350 +++++++++++++++++++++ 3 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 unittests/producer_schedule_hs_tests.cpp diff --git a/libraries/chain/block_header_state.cpp b/libraries/chain/block_header_state.cpp index 07a27d8330..ded73b5f8b 100644 --- a/libraries/chain/block_header_state.cpp +++ b/libraries/chain/block_header_state.cpp @@ -128,6 +128,7 @@ namespace eosio { namespace chain { // fork_database will prefer hotstuff blocks over dpos blocks result.dpos_irreversible_blocknum = hs_dpos_irreversible_blocknum; // Change to active on the next().next() producer block_num + // TODO: use calculated hotstuff lib instead of block_num if( pending_schedule.schedule.producers.size() && block_num >= detail::get_next_next_round_block_num(when, pending_schedule.schedule_lib_num)) { result.active_schedule = pending_schedule.schedule; diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 431a3c7945..164fc58a15 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -196,7 +196,7 @@ namespace eosio { namespace chain { namespace webassembly { // system contract should perform a duplicate check and fthreshold check before calling EOS_ASSERT( finalizers.size() == unique_finalizer_keys.size(), wasm_execution_error, "Duplicate finalizer bls key in finalizer set" ); - EOS_ASSERT( finset.fthreshold > f_weight_sum / 2, wasm_execution_error, "Finalizer set threshold cannot be met by finalizer weights" ); + EOS_ASSERT( finset.fthreshold >= f_weight_sum / 2, wasm_execution_error, "Finalizer set threshold cannot be met by finalizer weights" ); context.control.set_proposed_finalizers( finset ); } diff --git a/unittests/producer_schedule_hs_tests.cpp b/unittests/producer_schedule_hs_tests.cpp new file mode 100644 index 0000000000..69e83bc42b --- /dev/null +++ b/unittests/producer_schedule_hs_tests.cpp @@ -0,0 +1,350 @@ +#include +#include + +#include + +#include "fork_test_utilities.hpp" + +using namespace eosio::testing; +using namespace eosio::chain; +using mvo = fc::mutable_variant_object; + +BOOST_AUTO_TEST_SUITE(producer_schedule_hs_tests) + +namespace { + +// Calculate expected producer given the schedule and slot number +inline account_name get_expected_producer(const vector& schedule, block_timestamp_type t) { + const auto& index = (t.slot % (schedule.size() * config::producer_repetitions)) / config::producer_repetitions; + return schedule.at(index).producer_name; +}; + +} // anonymous namespace + +BOOST_FIXTURE_TEST_CASE( verify_producer_schedule_after_hotstuff_activation, validating_tester ) try { + + // Utility function to ensure that producer schedule work as expected + const auto& confirm_schedule_correctness = [&](const vector& new_prod_schd, uint32_t expected_schd_ver, uint32_t expected_block_num = 0) { + const uint32_t check_duration = 100; // number of blocks + bool scheduled_changed_to_new = false; + for (uint32_t i = 0; i < check_duration; ++i) { + const auto current_schedule = control->head_block_state()->active_schedule.producers; + if (new_prod_schd == current_schedule) { + scheduled_changed_to_new = true; + if (expected_block_num != 0) + BOOST_TEST(control->head_block_num() == expected_block_num); + } + + produce_block(); + + // Check if the producer is the same as what we expect + const auto block_time = control->head_block_time(); + const auto& expected_producer = get_expected_producer(current_schedule, block_time); + BOOST_TEST(control->head_block_producer() == expected_producer); + + if (scheduled_changed_to_new) + break; + } + + BOOST_TEST(scheduled_changed_to_new); + + const auto current_schd_ver = control->head_block_header().schedule_version; + BOOST_TEST(current_schd_ver == expected_schd_ver); + }; + + uint32_t lib = 0; + control->irreversible_block.connect([&](const block_state_ptr& bs) { + lib = bs->block_num; + }); + + // Create producer accounts + vector producers = { + "inita"_n, "initb"_n, "initc"_n, "initd"_n, "inite"_n, "initf"_n, "initg"_n, + "inith"_n, "initi"_n, "initj"_n, "initk"_n, "initl"_n, "initm"_n, "initn"_n, + "inito"_n, "initp"_n, "initq"_n, "initr"_n, "inits"_n, "initt"_n, "initu"_n + }; + create_accounts(producers); + + // activate hotstuff + set_finalizers(producers); + auto block = produce_block(); // this block contains the header extension of the finalizer set + BOOST_TEST(lib == 3); + + // ---- Test first set of producers ---- + // Send set prods action and confirm schedule correctness + set_producers(producers); + const auto first_prod_schd = get_producer_authorities(producers); + // TODO: update expected when lib for hotstuff is working, will change from 22 at that time + confirm_schedule_correctness(first_prod_schd, 1, 22); + + // ---- Test second set of producers ---- + vector second_set_of_producer = { + producers[3], producers[6], producers[9], producers[12], producers[15], producers[18], producers[20] + }; + // Send set prods action and confirm schedule correctness + set_producers(second_set_of_producer); + const auto second_prod_schd = get_producer_authorities(second_set_of_producer); + // TODO: update expected when lib for hotstuff is working, will change from 44 at that time + confirm_schedule_correctness(second_prod_schd, 2, 44); + + // ---- Test deliberately miss some blocks ---- + const int64_t num_of_missed_blocks = 5000; + produce_block(fc::microseconds(500 * 1000 * num_of_missed_blocks)); + // Ensure schedule is still correct + confirm_schedule_correctness(second_prod_schd, 2); + produce_block(); + + // ---- Test third set of producers ---- + vector third_set_of_producer = { + producers[2], producers[5], producers[8], producers[11], producers[14], producers[17], producers[20], + producers[0], producers[3], producers[6], producers[9], producers[12], producers[15], producers[18], + producers[1], producers[4], producers[7], producers[10], producers[13], producers[16], producers[19] + }; + // Send set prods action and confirm schedule correctness + set_producers(third_set_of_producer); + const auto third_prod_schd = get_producer_authorities(third_set_of_producer); + confirm_schedule_correctness(third_prod_schd, 3); + +} FC_LOG_AND_RETHROW() + +/** TODO: Enable tests after hotstuff LIB is working + +BOOST_FIXTURE_TEST_CASE( producer_schedule_promotion_test, validating_tester ) try { + create_accounts( {"alice"_n,"bob"_n,"carol"_n} ); + while (control->head_block_num() < 3) { + produce_block(); + } + + // activate hotstuff + set_finalizers({"alice"_n,"bob"_n,"carol"_n}); + auto block = produce_block(); // this block contains the header extension of the finalizer set + + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { + return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); + }; + + auto res = set_producers( {"alice"_n,"bob"_n} ); + vector sch1 = { + producer_authority{"alice"_n, block_signing_authority_v0{1, {{get_public_key("alice"_n, "active"), 1}}}}, + producer_authority{"bob"_n, block_signing_authority_v0{1, {{get_public_key("bob"_n, "active"), 1}}}} + }; + //wdump((fc::json::to_pretty_string(res))); + wlog("set producer schedule to [alice,bob]"); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *control->proposed_producers() ) ); + BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); + produce_block(); // Starts new block which promotes the proposed schedule to pending + BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->pending_producers() ) ); + BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); + produce_block(); + produce_block(); // Starts new block which promotes the pending schedule to active + BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, control->active_producers() ) ); + produce_blocks(6); + + res = set_producers( {"alice"_n,"bob"_n,"carol"_n} ); + vector sch2 = { + producer_authority{"alice"_n, block_signing_authority_v0{1, {{get_public_key("alice"_n, "active"),1}}}}, + producer_authority{"bob"_n, block_signing_authority_v0{1, {{get_public_key("bob"_n, "active"),1}}}}, + producer_authority{"carol"_n, block_signing_authority_v0{1, {{get_public_key("carol"_n, "active"),1}}}} + }; + wlog("set producer schedule to [alice,bob,carol]"); + BOOST_REQUIRE_EQUAL( true, control->proposed_producers().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *control->proposed_producers() ) ); + + produce_block(); + produce_blocks(23); // Alice produces the last block of her first round. + // Bob's first block (which advances LIB to Alice's last block) is started but not finalized. + BOOST_REQUIRE_EQUAL( control->head_block_producer(), "alice"_n ); + BOOST_REQUIRE_EQUAL( control->pending_block_producer(), "bob"_n ); + BOOST_CHECK_EQUAL( control->pending_producers().version, 2u ); + + produce_blocks(12); // Bob produces his first 11 blocks + BOOST_CHECK_EQUAL( control->active_producers().version, 1u ); + produce_blocks(12); // Bob produces his 12th block. + // Alice's first block of the second round is started but not finalized (which advances LIB to Bob's last block). + BOOST_REQUIRE_EQUAL( control->head_block_producer(), "alice"_n ); + BOOST_REQUIRE_EQUAL( control->pending_block_producer(), "bob"_n ); + BOOST_CHECK_EQUAL( control->active_producers().version, 2u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, control->active_producers() ) ); + + produce_block(); // Alice produces the first block of her second round which has changed the active schedule. + + // The next block will be produced according to the new schedule + produce_block(); + BOOST_CHECK_EQUAL( control->head_block_producer(), "carol"_n ); // And that next block happens to be produced by Carol. + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( producer_watermark_test ) try { + tester c; + + c.create_accounts( {"alice"_n,"bob"_n,"carol"_n} ); + c.produce_block(); + + // activate hotstuff + c.set_finalizers({"alice"_n,"bob"_n,"carol"_n}); + auto block = c.produce_block(); // this block contains the header extension of the finalizer set + + auto compare_schedules = [&]( const vector& a, const producer_authority_schedule& b ) { + return std::equal( a.begin(), a.end(), b.producers.begin(), b.producers.end() ); + }; + + auto res = c.set_producers( {"alice"_n,"bob"_n,"carol"_n} ); + vector sch1 = { + producer_authority{"alice"_n, block_signing_authority_v0{ 1, {{c.get_public_key("alice"_n, "active"),1}}}}, + producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{c.get_public_key("bob"_n, "active"),1}}}}, + producer_authority{"carol"_n, block_signing_authority_v0{ 1, {{c.get_public_key("carol"_n, "active"),1}}}} + }; + wlog("set producer schedule to [alice,bob,carol]"); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 0u ); + c.produce_block(); // Starts new block which promotes the proposed schedule to pending + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->pending_producers() ) ); + BOOST_CHECK_EQUAL( c.control->active_producers().version, 0u ); + c.produce_block(); + c.produce_block(); // Starts new block which promotes the pending schedule to active + BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 1u ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, c.control->active_producers() ) ); + + produce_until_transition( c, "carol"_n, "alice"_n ); + c.produce_block(); + produce_until_transition( c, "carol"_n, "alice"_n ); + + res = c.set_producers( {"alice"_n,"bob"_n} ); + vector sch2 = { + producer_authority{"alice"_n, block_signing_authority_v0{ 1, {{c.get_public_key("alice"_n, "active"),1}}}}, + producer_authority{"bob"_n, block_signing_authority_v0{ 1, {{c.get_public_key("bob"_n, "active"),1}}}} + }; + wlog("set producer schedule to [alice,bob]"); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch2, *c.control->proposed_producers() ) ); + + produce_until_transition( c, "bob"_n, "carol"_n ); + produce_until_transition( c, "alice"_n, "bob"_n ); + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); + BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); + + produce_until_transition( c, "carol"_n, "alice"_n ); + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 2u ); + BOOST_CHECK_EQUAL( c.control->active_producers().version, 1u ); + + produce_until_transition( c, "bob"_n, "carol"_n ); + BOOST_CHECK_EQUAL( c.control->pending_block_producer(), "carol"_n ); + BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 2u ); + + auto carol_last_produced_block_num = c.control->head_block_num() + 1; + wdump((carol_last_produced_block_num)); + + c.produce_block(); + BOOST_CHECK( c.control->pending_block_producer() == "alice"_n ); + + res = c.set_producers( {"alice"_n,"bob"_n,"carol"_n} ); + wlog("set producer schedule to [alice,bob,carol]"); + BOOST_REQUIRE_EQUAL( true, c.control->proposed_producers().has_value() ); + BOOST_CHECK_EQUAL( true, compare_schedules( sch1, *c.control->proposed_producers() ) ); + + produce_until_transition( c, "bob"_n, "alice"_n ); + + auto bob_last_produced_block_num = c.control->head_block_num(); + wdump((bob_last_produced_block_num)); + + produce_until_transition( c, "alice"_n, "bob"_n ); + + auto alice_last_produced_block_num = c.control->head_block_num(); + wdump((alice_last_produced_block_num)); + + { + wdump((c.control->head_block_state()->producer_to_last_produced)); + const auto& last_produced = c.control->head_block_state()->producer_to_last_produced; + auto alice_itr = last_produced.find( "alice"_n ); + BOOST_REQUIRE( alice_itr != last_produced.end() ); + BOOST_CHECK_EQUAL( alice_itr->second, alice_last_produced_block_num ); + auto bob_itr = last_produced.find( "bob"_n ); + BOOST_REQUIRE( bob_itr != last_produced.end() ); + BOOST_CHECK_EQUAL( bob_itr->second, bob_last_produced_block_num ); + auto carol_itr = last_produced.find( "carol"_n ); + BOOST_REQUIRE( carol_itr != last_produced.end() ); + BOOST_CHECK_EQUAL( carol_itr->second, carol_last_produced_block_num ); + } + + BOOST_CHECK_EQUAL( c.control->pending_producers().version, 3u ); + BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 2u ); + + produce_until_transition( c, "bob"_n, "alice"_n ); + BOOST_REQUIRE_EQUAL( c.control->active_producers().version, 3u ); + + produce_until_transition( c, "alice"_n, "bob"_n ); + c.produce_blocks(11); + BOOST_CHECK_EQUAL( c.control->pending_block_producer(), "bob"_n ); + c.finish_block(); + + auto carol_block_num = c.control->head_block_num() + 1; + auto carol_block_time = c.control->head_block_time() + fc::milliseconds(config::block_interval_ms); + auto confirmed = carol_block_num - carol_last_produced_block_num - 1; + + c.control->start_block( carol_block_time, confirmed, {}, controller::block_status::incomplete ); + BOOST_CHECK_EQUAL( c.control->pending_block_producer(), "carol"_n ); + c.produce_block(); + auto h = c.control->head_block_header(); + + BOOST_CHECK_EQUAL( h.producer, "carol"_n ); + BOOST_CHECK_EQUAL( h.confirmed, confirmed ); + + produce_until_transition( c, "carol"_n, "alice"_n ); + +} FC_LOG_AND_RETHROW() + +**/ + +BOOST_FIXTURE_TEST_CASE( producer_one_of_n_test, validating_tester ) try { + create_accounts( {"alice"_n,"bob"_n} ); + produce_block(); + + // activate hotstuff + set_finalizers({"alice"_n,"bob"_n}); + auto block = produce_block(); // this block contains the header extension of the finalizer set + + vector sch1 = { + producer_authority{"alice"_n, block_signing_authority_v0{1, {{get_public_key("alice"_n, "bs1"), 1}, {get_public_key("alice"_n, "bs2"), 1}}}}, + producer_authority{"bob"_n, block_signing_authority_v0{1, {{get_public_key("bob"_n, "bs1"), 1}, {get_public_key("bob"_n, "bs2"), 1}}}} + }; + + auto res = set_producer_schedule( sch1 ); + block_signing_private_keys.emplace(get_public_key("alice"_n, "bs1"), get_private_key("alice"_n, "bs1")); + block_signing_private_keys.emplace(get_public_key("bob"_n, "bs1"), get_private_key("bob"_n, "bs1")); + + BOOST_REQUIRE(produce_until_blocks_from(*this, {"alice"_n, "bob"_n}, 100)); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( producer_m_of_n_test, validating_tester ) try { + create_accounts( {"alice"_n,"bob"_n} ); + produce_block(); + + // activate hotstuff + set_finalizers({"alice"_n,"bob"_n}); + auto block = produce_block(); // this block contains the header extension of the finalizer set + + vector sch1 = { + producer_authority{"alice"_n, block_signing_authority_v0{2, {{get_public_key("alice"_n, "bs1"), 1}, {get_public_key("alice"_n, "bs2"), 1}}}}, + producer_authority{"bob"_n, block_signing_authority_v0{2, {{get_public_key("bob"_n, "bs1"), 1}, {get_public_key("bob"_n, "bs2"), 1}}}} + }; + + auto res = set_producer_schedule( sch1 ); + block_signing_private_keys.emplace(get_public_key("alice"_n, "bs1"), get_private_key("alice"_n, "bs1")); + block_signing_private_keys.emplace(get_public_key("alice"_n, "bs2"), get_private_key("alice"_n, "bs2")); + block_signing_private_keys.emplace(get_public_key("bob"_n, "bs1"), get_private_key("bob"_n, "bs1")); + block_signing_private_keys.emplace(get_public_key("bob"_n, "bs2"), get_private_key("bob"_n, "bs2")); + + BOOST_REQUIRE(produce_until_blocks_from(*this, {"alice"_n, "bob"_n}, 100)); + + BOOST_REQUIRE_EQUAL( validate(), true ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END()