diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index bad536b07a..b9e4042a62 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -263,6 +264,8 @@ struct controller_impl { map< account_name, map > apply_handlers; unordered_map< builtin_protocol_feature_t, std::function, enum_hash > protocol_feature_activation_handlers; + // TODO: This probably wants to be something better; store in chainbase and/or block_state + finalizer_set current_finalizer_set; void pop_block() { auto prev = fork_db.get_block( head->header.previous ); @@ -1974,6 +1977,11 @@ struct controller_impl { pending->push(); } + void set_finalizers_impl(const finalizer_set& fin_set) { + // TODO store in chainbase + current_finalizer_set = fin_set; + } + /** * This method is called from other threads. The controller_impl should outlive those threads. * However, to avoid race conditions, it means that the behavior of this function should not change @@ -3281,6 +3289,14 @@ int64_t controller::set_proposed_producers( vector producers return version; } +void controller::set_finalizers( const finalizer_set& fin_set ) { + my->set_finalizers_impl(fin_set); +} + +const finalizer_set& controller::get_finalizers() const { + return my->current_finalizer_set; +} + const producer_authority_schedule& controller::active_producers()const { if( !(my->pending) ) return my->head->active_schedule; @@ -3844,8 +3860,7 @@ void controller_impl::on_activation( template<> void controller_impl::on_activation() { db.modify( db.get(), [&]( auto& ps ) { -#warning host functions to set proposers, leaders, finalizers/validators - // FIXME/TODO: host functions to set proposers, leaders, finalizers/validators + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "set_finalizers" ); } ); } diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 6448c6ae59..c3f91d9cbe 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -130,6 +130,10 @@ const static int max_producers = 125; const static size_t maximum_tracked_dpos_confirmations = 1024; ///< static_assert(maximum_tracked_dpos_confirmations >= ((max_producers * 2 / 3) + 1) * producer_repetitions, "Settings never allow for DPOS irreversibility" ); +/** + * Maximum number of finalizers in the finalizer set + */ +const static int max_finalizers = max_producers; /** * The number of blocks produced per round is based upon all producers having a chance diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 5f0da59768..7f7dede8f0 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -21,6 +21,8 @@ namespace eosio { namespace vm { class wasm_allocator; }} namespace eosio { namespace chain { + struct finalizer_set; + class authorization_manager; namespace resource_limits { @@ -291,6 +293,9 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); + void set_finalizers( const finalizer_set& fin_set ); + const finalizer_set& get_finalizers() const; + bool light_validation_allowed() const; bool skip_auth_check()const; bool skip_trx_checks()const; @@ -366,7 +371,6 @@ namespace eosio { namespace chain { chainbase::database& mutable_db()const; std::unique_ptr my; - }; } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/finalizer_set.hpp b/libraries/chain/include/eosio/chain/finalizer_set.hpp new file mode 100644 index 0000000000..ca5630ade1 --- /dev/null +++ b/libraries/chain/include/eosio/chain/finalizer_set.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace eosio::chain { + + struct finalizer_authority { + + std::string description; + uint64_t fweight = 0; // weight that this finalizer's vote has for meeting fthreshold + fc::crypto::blslib::bls_public_key public_key; + + friend bool operator == ( const finalizer_authority& lhs, const finalizer_authority& rhs ) { + return tie( lhs.description, lhs.fweight, lhs.public_key ) == tie( rhs.description, rhs.fweight, rhs.public_key ); + } + friend bool operator != ( const finalizer_authority& lhs, const finalizer_authority& rhs ) { + return !(lhs == rhs); + } + }; + + struct finalizer_set { + finalizer_set() = default; + + finalizer_set( uint32_t version, uint64_t fthreshold, std::initializer_list finalizers ) + :version(version) + ,fthreshold(fthreshold) + ,finalizers(finalizers) + {} + + uint32_t version = 0; ///< sequentially incrementing version number + uint64_t fthreshold = 0; // vote fweight threshold to finalize blocks + vector finalizers; // Instant Finality voter set + + friend bool operator == ( const finalizer_set& a, const finalizer_set& b ) + { + if( a.version != b.version ) return false; + if( a.fthreshold != b.fthreshold ) return false; + if ( a.finalizers.size() != b.finalizers.size() ) return false; + for( uint32_t i = 0; i < a.finalizers.size(); ++i ) + if( ! (a.finalizers[i] == b.finalizers[i]) ) return false; + return true; + } + + friend bool operator != ( const finalizer_set& a, const finalizer_set& b ) + { + return !(a==b); + } + }; + +} /// eosio::chain + +FC_REFLECT( eosio::chain::finalizer_authority, (description)(fweight)(public_key) ) +FC_REFLECT( eosio::chain::finalizer_set, (version)(fthreshold)(finalizers) ) diff --git a/libraries/chain/include/eosio/chain/webassembly/interface.hpp b/libraries/chain/include/eosio/chain/webassembly/interface.hpp index 59fac5078a..24dc555250 100644 --- a/libraries/chain/include/eosio/chain/webassembly/interface.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/interface.hpp @@ -173,6 +173,15 @@ namespace webassembly { */ int64_t set_proposed_producers_ex(uint64_t packed_producer_format, legacy_span packed_producer_schedule); + /** + * Submits a finalizer set change to Hotstuff. + * + * @ingroup privileged + * + * @param packed_finalizer_set - a serialized finalizer_set object. + */ + void set_finalizers(span packed_finalizer_set); + /** * Retrieve the blockchain config parameters. * diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index f9a8456745..dc3992709f 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -150,6 +151,35 @@ namespace eosio { namespace chain { namespace webassembly { } } + void interface::set_finalizers(span packed_finalizer_set) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_proposed_finalizers not allowed in a readonly transaction"); + fc::datastream ds( packed_finalizer_set.data(), packed_finalizer_set.size() ); + finalizer_set finset; + fc::raw::unpack(ds, finset); + vector & finalizers = finset.finalizers; + + // TODO: check version and increment it or verify correct + EOS_ASSERT( finalizers.size() <= config::max_finalizers, wasm_execution_error, "Finalizer set exceeds the maximum finalizer count for this chain" ); + EOS_ASSERT( finalizers.size() > 0, wasm_execution_error, "Finalizer set cannot be empty" ); + + std::set unique_finalizer_keys; +#warning REVIEW: Is checking for unique finalizer descriptions at all relevant? + std::set unique_finalizers; + uint64_t f_weight_sum = 0; + + for (const auto& f: finalizers) { + f_weight_sum += f.fweight; + unique_finalizer_keys.insert(f.public_key); + unique_finalizers.insert(f.description); + } + + EOS_ASSERT( finalizers.size() == unique_finalizers.size(), wasm_execution_error, "Duplicate finalizer description in finalizer set" ); + 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" ); + + context.control.set_finalizers( finset ); + } + uint32_t interface::get_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) const { auto& gpo = context.control.get_global_properties(); diff --git a/libraries/hotstuff/chain_pacemaker.cpp b/libraries/hotstuff/chain_pacemaker.cpp index 1b3f62801c..1e55bda11a 100644 --- a/libraries/hotstuff/chain_pacemaker.cpp +++ b/libraries/hotstuff/chain_pacemaker.cpp @@ -105,6 +105,10 @@ namespace eosio { namespace hotstuff { _qc_chain("default"_n, this, std::move(my_producers), logger), _logger(logger) { + _accepted_block_connection = chain->accepted_block.connect( [this]( const block_state_ptr& blk ) { + on_accepted_block( blk ); + } ); + _head_block_state = chain->head_block_state(); } void chain_pacemaker::register_bcast_function(std::function broadcast_hs_message) { @@ -113,16 +117,7 @@ namespace eosio { namespace hotstuff { bcast_hs_message = std::move(broadcast_hs_message); } - // Called internally by the chain_pacemaker to decide whether it should do something or not, based on feature activation. - // Only methods called by the outside need to call this; methods called by qc_chain only don't need to check for enable(). - bool chain_pacemaker::enabled() const { - return _chain->is_builtin_activated( builtin_protocol_feature_t::instant_finality ); - } - void chain_pacemaker::get_state(finalizer_state& fs) const { - if (! enabled()) - return; - // lock-free state version check uint64_t current_state_version = _qc_chain.get_state_version(); if (_state_cache_version != current_state_version) { @@ -147,12 +142,6 @@ namespace eosio { namespace hotstuff { fs = _state_cache; } - name chain_pacemaker::get_proposer() { - const block_state_ptr& hbs = _chain->head_block_state(); - name n = hbs->header.producer; - return n; - } - name chain_pacemaker::debug_leader_remap(name n) { /* // FIXME/REMOVE: simple device to test proposer/leader @@ -218,9 +207,23 @@ namespace eosio { namespace hotstuff { return n; } + // called from main thread + void chain_pacemaker::on_accepted_block( const block_state_ptr& blk ) { + std::scoped_lock g( _chain_state_mutex ); + _head_block_state = blk; + // TODO only update local cache if changed, check version or use != + _finalizer_set = _chain->get_finalizers(); // TODO get from chainbase or from block_state + } + + name chain_pacemaker::get_proposer() { + std::scoped_lock g( _chain_state_mutex ); + return _head_block_state->header.producer; + } + name chain_pacemaker::get_leader() { - const block_state_ptr& hbs = _chain->head_block_state(); - name n = hbs->header.producer; + std::unique_lock g( _chain_state_mutex ); + name n = _head_block_state->header.producer; + g.unlock(); // FIXME/REMOVE: testing leader/proposer separation n = debug_leader_remap(n); @@ -229,9 +232,10 @@ namespace eosio { namespace hotstuff { } name chain_pacemaker::get_next_leader() { - const block_state_ptr& hbs = _chain->head_block_state(); - block_timestamp_type next_block_time = hbs->header.timestamp.next(); - producer_authority p_auth = hbs->get_scheduled_producer(next_block_time); + std::unique_lock g( _chain_state_mutex ); + block_timestamp_type next_block_time = _head_block_state->header.timestamp.next(); + producer_authority p_auth = _head_block_state->get_scheduled_producer(next_block_time); + g.unlock(); name n = p_auth.producer_name; // FIXME/REMOVE: testing leader/proposer separation @@ -241,7 +245,26 @@ namespace eosio { namespace hotstuff { } std::vector chain_pacemaker::get_finalizers() { - const block_state_ptr& hbs = _chain->head_block_state(); + +#warning FIXME: Use new finalizer list in pacemaker/qc_chain. + // Every time qc_chain wants to know what the finalizers are, we get it from the controller, which + // is where it's currently stored. + // + // TODO: + // - solve threading. for this particular case, I don't think using _chain-> is a big deal really; + // set_finalizers is called once in a blue moon, and this could be solved by a simple mutex even + // if it is the main thread that is waiting for a lock. But maybe there's a better way to do this + // overall. + // - use this information in qc_chain and delete the old code below + // - list of string finalizer descriptions instead of eosio name now + // - also return the keys for each finalizer, not just name/description so qc_chain can use them + // + std::unique_lock g( _chain_state_mutex ); + const auto& fin_set = _chain->get_finalizers(); // TODO use + block_state_ptr hbs = _head_block_state; + g.unlock(); + + // Old code: get eosio::name from the producer schedule const std::vector& pa_list = hbs->active_schedule.producers; std::vector pn_list; pn_list.reserve(pa_list.size()); @@ -252,17 +275,16 @@ namespace eosio { namespace hotstuff { } block_id_type chain_pacemaker::get_current_block_id() { - return _chain->head_block_id(); + std::scoped_lock g( _chain_state_mutex ); + return _head_block_state->id; } uint32_t chain_pacemaker::get_quorum_threshold() { return _quorum_threshold; } + // called from the main application thread void chain_pacemaker::beat() { - if (! enabled()) - return; - csc prof("beat"); std::lock_guard g( _hotstuff_global_mutex ); prof.core_in(); @@ -286,6 +308,7 @@ namespace eosio { namespace hotstuff { bcast_hs_message(msg); } + // called from net threads void chain_pacemaker::on_hs_msg(const eosio::chain::hs_message &msg) { std::visit(overloaded{ [this](const hs_vote_message& m) { on_hs_vote_msg(m); }, @@ -297,9 +320,6 @@ namespace eosio { namespace hotstuff { // called from net threads void chain_pacemaker::on_hs_proposal_msg(const hs_proposal_message& msg) { - if (! enabled()) - return; - csc prof("prop"); std::lock_guard g( _hotstuff_global_mutex ); prof.core_in(); @@ -309,9 +329,6 @@ namespace eosio { namespace hotstuff { // called from net threads void chain_pacemaker::on_hs_vote_msg(const hs_vote_message& msg) { - if (! enabled()) - return; - csc prof("vote"); std::lock_guard g( _hotstuff_global_mutex ); prof.core_in(); @@ -321,9 +338,6 @@ namespace eosio { namespace hotstuff { // called from net threads void chain_pacemaker::on_hs_new_block_msg(const hs_new_block_message& msg) { - if (! enabled()) - return; - csc prof("nblk"); std::lock_guard g( _hotstuff_global_mutex ); prof.core_in(); @@ -333,9 +347,6 @@ namespace eosio { namespace hotstuff { // called from net threads void chain_pacemaker::on_hs_new_view_msg(const hs_new_view_message& msg) { - if (! enabled()) - return; - csc prof("view"); std::lock_guard g( _hotstuff_global_mutex ); prof.core_in(); diff --git a/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp index 7b264b28dc..7fce189948 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp @@ -30,8 +30,6 @@ namespace eosio::hotstuff { virtual chain::block_id_type get_current_block_id() = 0; - //hotstuff getters. todo : implement relevant setters as host functions -#warning hotstuff getters. todo : implement relevant setters as host functions virtual chain::name get_proposer() = 0; virtual chain::name get_leader() = 0; virtual chain::name get_next_leader() = 0; diff --git a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp index aa8e1f0c4c..a16a583a7f 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/chain_pacemaker.hpp @@ -3,6 +3,10 @@ #include #include +#include + +#include + #include namespace eosio::chain { @@ -42,17 +46,16 @@ namespace eosio::hotstuff { void send_hs_new_block_msg(const hs_new_block_message& msg, name id); private: - - //FIXME/REMOVE: for testing/debugging only - name debug_leader_remap(name n); - - // Check if consensus upgrade feature is activated - bool enabled() const; + void on_accepted_block( const block_state_ptr& blk ); void on_hs_proposal_msg(const hs_proposal_message& msg); //consensus msg event handler void on_hs_vote_msg(const hs_vote_message& msg); //confirmation msg event handler void on_hs_new_view_msg(const hs_new_view_message& msg); //new view msg event handler void on_hs_new_block_msg(const hs_new_block_message& msg); //new block msg event handler + private: + + //FIXME/REMOVE: for testing/debugging only + name debug_leader_remap(name n); // This serializes all messages (high-level requests) to the qc_chain core. // For maximum safety, the qc_chain core will only process one request at a time. @@ -67,14 +70,19 @@ namespace eosio::hotstuff { mutable finalizer_state _state_cache; mutable std::atomic _state_cache_version = 0; - chain::controller* _chain = nullptr; + chain::controller* _chain = nullptr; // TODO will not be needed once this is merged with PR#1559 + + mutable std::mutex _chain_state_mutex; + block_state_ptr _head_block_state; + finalizer_set _finalizer_set; + + boost::signals2::scoped_connection _accepted_block_connection; qc_chain _qc_chain; std::function bcast_hs_message; uint32_t _quorum_threshold = 15; //FIXME/TODO: calculate from schedule fc::logger& _logger; - }; } // namespace eosio::hotstuff diff --git a/libraries/hotstuff/qc_chain.cpp b/libraries/hotstuff/qc_chain.cpp index c4bf6407ee..3463bbdacf 100644 --- a/libraries/hotstuff/qc_chain.cpp +++ b/libraries/hotstuff/qc_chain.cpp @@ -622,6 +622,7 @@ namespace eosio { namespace hotstuff { } // Invoked when we could perhaps make a proposal to the network (or to ourselves, if we are the leader). + // Called from the main application thread void qc_chain::on_beat(){ // Non-proposing leaders do not care about on_beat(), because leaders react to a block proposal diff --git a/libraries/libfc/include/fc/crypto/bls_private_key.hpp b/libraries/libfc/include/fc/crypto/bls_private_key.hpp index 1893a37ebc..deff417e85 100644 --- a/libraries/libfc/include/fc/crypto/bls_private_key.hpp +++ b/libraries/libfc/include/fc/crypto/bls_private_key.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include diff --git a/libraries/libfc/include/fc/crypto/bls_public_key.hpp b/libraries/libfc/include/fc/crypto/bls_public_key.hpp index 2a2e17c77d..b2d552888d 100644 --- a/libraries/libfc/include/fc/crypto/bls_public_key.hpp +++ b/libraries/libfc/include/fc/crypto/bls_public_key.hpp @@ -1,11 +1,7 @@ #pragma once -#include -#include -#include #include #include #include -#include #include namespace fc::crypto::blslib { @@ -21,12 +17,14 @@ namespace fc::crypto::blslib { bls_public_key() = default; bls_public_key( bls_public_key&& ) = default; bls_public_key( const bls_public_key& ) = default; - explicit bls_public_key( const bls12_381::g1& pkey ){_pkey = pkey;} + explicit bls_public_key( const bls12_381::g1& pkey ) {_pkey = pkey;} explicit bls_public_key(const std::string& base58str); - bls_public_key& operator= (const bls_public_key& ) = default; + bls_public_key& operator=(const bls_public_key&) = default; std::string to_string(const yield_function_t& yield = yield_function_t()) const; - friend bool operator == ( const bls_public_key& p1, const bls_public_key& p2); + friend bool operator==(const bls_public_key& p1, const bls_public_key& p2); + + auto operator<=>(const bls_public_key&) const = default; bls12_381::g1 _pkey; diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index ae2512371e..509d357f1c 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1134,7 +1134,6 @@ void chain_plugin_impl::plugin_startup() { try { EOS_ASSERT( chain_config->read_mode != db_read_mode::IRREVERSIBLE || !accept_transactions, plugin_config_exception, "read-mode = irreversible. transactions should not be enabled by enable_accept_transactions" ); - EOS_ASSERT( _chain_pacemaker, plugin_config_exception, "chain_pacemaker not initialization" ); try { auto shutdown = [](){ return app().quit(); }; auto check_shutdown = [](){ return app().is_quiting(); }; @@ -2688,7 +2687,9 @@ void chain_plugin::notify_hs_message( const hs_message& msg ) { }; void chain_plugin::notify_hs_block_produced() { - my->_chain_pacemaker->beat(); + 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 { diff --git a/plugins/net_plugin/net_plugin.cpp b/plugins/net_plugin/net_plugin.cpp index 07cd52f3a3..a8bb78dc00 100644 --- a/plugins/net_plugin/net_plugin.cpp +++ b/plugins/net_plugin/net_plugin.cpp @@ -4152,17 +4152,17 @@ namespace eosio { chain_plug->enable_accept_transactions(); } - chain_plug->register_pacemaker_bcast_function( - [my = shared_from_this()](const hs_message& s) { - my->bcast_hs_message(s); - } ); - } FC_LOG_AND_RETHROW() } 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 hs_message& s) { + my->bcast_hs_message(s); + } ); + producer_plug = app().find_plugin(); set_producer_accounts(producer_plug->producer_accounts()); diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 4384c52c56..054068b25a 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1326,8 +1326,6 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia _snapshot_scheduler.set_db_path(_snapshots_dir); _snapshot_scheduler.set_snapshots_path(_snapshots_dir); - - chain_plug->create_pacemaker(_producers); } void producer_plugin::plugin_initialize(const boost::program_options::variables_map& options) { @@ -1360,6 +1358,8 @@ 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); + _accepted_block_connection.emplace(chain.accepted_block.connect([this](const auto& bsp) { on_block(bsp); })); _accepted_block_header_connection.emplace(chain.accepted_block_header.connect([this](const auto& bsp) { on_block_header(bsp); })); _irreversible_block_connection.emplace(