diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 037dc6de50..b0feb1c9f3 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1990,6 +1990,10 @@ struct controller_impl { emit( self.new_hs_new_block_message, msg ); } + void set_finalizers_impl(uint64_t fthreshold, vector finalizers) { + emit( self.notify_set_finalizers, std::tie(fthreshold, finalizers) ); + } + /** * 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 @@ -3313,6 +3317,10 @@ int64_t controller::set_proposed_producers( vector producers return version; } +void controller::set_finalizers( uint64_t fthreshold, vector finalizers ) { + my->set_finalizers_impl(fthreshold, finalizers); +} + const producer_authority_schedule& controller::active_producers()const { if( !(my->pending) ) return my->head->active_schedule; 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 934ed25dac..c75a3aa419 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace chainbase { class database; } @@ -306,6 +308,8 @@ namespace eosio { namespace chain { int64_t set_proposed_producers( vector producers ); + void set_finalizers( uint64_t fthreshold, vector finalizers ); + bool light_validation_allowed() const; bool skip_auth_check()const; bool skip_trx_checks()const; @@ -356,6 +360,7 @@ namespace eosio { namespace chain { signal new_hs_vote_message; signal new_hs_new_view_message; signal new_hs_new_block_message; + signal&>)> notify_set_finalizers; /* signal pre_apply_block; 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..5a6123617e --- /dev/null +++ b/libraries/chain/include/eosio/chain/finalizer_set.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace eosio::chain { + + struct shared_finalizer_authority { + shared_finalizer_authority() = delete; + shared_finalizer_authority( const shared_finalizer_authority& ) = default; + shared_finalizer_authority( shared_finalizer_authority&& ) = default; + shared_finalizer_authority& operator= ( shared_finalizer_authority && ) = default; + shared_finalizer_authority& operator= ( const shared_finalizer_authority & ) = default; + + shared_finalizer_authority( const name& finalizer_name, const uint64_t fweight, const fc::crypto::blslib::bls_public_key& public_key ) + :finalizer_name(finalizer_name) + ,fweight(fweight) + ,public_key(public_key) + {} + + name finalizer_name; + uint64_t fweight; + fc::crypto::blslib::bls_public_key public_key; + }; + + struct shared_finalizer_set { + shared_finalizer_set() = delete; + + explicit shared_finalizer_set( chainbase::allocator alloc ) + :finalizers(alloc){} + + shared_finalizer_set( const shared_finalizer_set& ) = default; + shared_finalizer_set( shared_finalizer_set&& ) = default; + shared_finalizer_set& operator= ( shared_finalizer_set && ) = default; + shared_finalizer_set& operator= ( const shared_finalizer_set & ) = default; + + uint32_t version = 0; ///< sequentially incrementing version number + uint64_t fthreshold = 0; // minimum finalizer fweight sum for block finalization + shared_vector finalizers; + }; + + struct finalizer_authority { + + name finalizer_name; + uint64_t fweight; // weight that this finalizer's vote has for meeting fthreshold + fc::crypto::blslib::bls_public_key public_key; + + auto to_shared(chainbase::allocator alloc) const { + return shared_finalizer_authority(finalizer_name, fweight, public_key); + } + + static auto from_shared( const shared_finalizer_authority& src ) { + finalizer_authority result; + result.finalizer_name = src.finalizer_name; + result.fweight = src.fweight; + result.public_key = src.public_key; + return result; + } + + /** + * ABI's for contracts expect variants to be serialized as a 2 entry array of + * [type-name, value]. + * + * This is incompatible with standard FC rules for + * static_variants which produce + * + * [ordinal, value] + * + * this method produces an appropriate variant for contracts where the authority field + * is correctly formatted + */ + fc::variant get_abi_variant() const; + + friend bool operator == ( const finalizer_authority& lhs, const finalizer_authority& rhs ) { + return tie( lhs.finalizer_name, lhs.fweight, lhs.public_key ) == tie( rhs.finalizer_name, rhs.fweight, rhs.public_key ); + } + friend bool operator != ( const finalizer_authority& lhs, const finalizer_authority& rhs ) { + return tie( lhs.finalizer_name, lhs.fweight, lhs.public_key ) != tie( rhs.finalizer_name, rhs.fweight, rhs.public_key ); + } + }; + + struct finalizer_set { + finalizer_set() = default; + + finalizer_set( uint32_t version, uint64_t fthreshold, std::initializer_list finalizers ) + :version(version) + ,fthreshold(fthreshold) + ,finalizers(finalizers) + {} + + auto to_shared(chainbase::allocator alloc) const { + auto result = shared_finalizer_set(alloc); + result.version = version; + result.fthreshold = fthreshold; + result.finalizers.clear(); + result.finalizers.reserve( finalizers.size() ); + for( const auto& f : finalizers ) { + result.finalizers.emplace_back(f.to_shared(alloc)); + } + return result; + } + + static auto from_shared( const shared_finalizer_set& src ) { + finalizer_set result; + result.version = src.version; + result.fthreshold = src.fthreshold; + result.finalizers.reserve( src.finalizers.size() ); + for( const auto& f : src.finalizers ) { + result.finalizers.emplace_back(finalizer_authority::from_shared(f)); + } + return result; + } + + uint32_t version = 0; ///< sequentially incrementing version number + uint64_t fthreshold; // 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, (finalizer_name)(fweight)(public_key) ) +FC_REFLECT( eosio::chain::finalizer_set, (version)(fthreshold)(finalizers) ) +FC_REFLECT( eosio::chain::shared_finalizer_authority, (finalizer_name)(fweight)(public_key) ) +FC_REFLECT( eosio::chain::shared_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..28528e33f6 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(legacy_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..c985fed132 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(legacy_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; + + 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; + std::set unique_finalizers; + uint64_t f_weight_sum = 0; + + for (const auto& f: finalizers) { + EOS_ASSERT( context.is_account(f.finalizer_name), wasm_execution_error, "Finalizer set includes a nonexisting account" ); + EOS_ASSERT( f.public_key.valid(), wasm_execution_error, "Finalizer set includes an invalid key" ); + f_weight_sum += f.fweight; + unique_finalizer_keys.insert(f.public_key); + unique_finalizers.insert(f.finalizer_name); + } + + EOS_ASSERT( finalizers.size() == unique_finalizers.size(), wasm_execution_error, "Duplicate finalizer name 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 treshold cannot be met by finalizer weights" ); + + context.control.set_finalizers( finset.fthreshold, std::move(finalizers) ); + } + uint32_t interface::get_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) const { auto& gpo = context.control.get_global_properties(); diff --git a/libraries/libfc/include/fc/crypto/bls_public_key.hpp b/libraries/libfc/include/fc/crypto/bls_public_key.hpp index b805252de3..79b83d51a7 100644 --- a/libraries/libfc/include/fc/crypto/bls_public_key.hpp +++ b/libraries/libfc/include/fc/crypto/bls_public_key.hpp @@ -60,9 +60,10 @@ namespace fc { namespace crypto { namespace blslib { friend std::ostream& operator<< (std::ostream& s, const bls_public_key& k); - //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); - //friend bool operator < ( const bls_public_key& p1, const bls_public_key& p2); +#warning FIXME/TODO: Must implement these operators. + friend bool operator == ( const bls_public_key& p1, const bls_public_key& p2) { return false; /*p1._pkey == p2._pkey;*/ } + friend bool operator != ( const bls_public_key& p1, const bls_public_key& p2) { return false; /*p1._pkey != p2._pkey;*/ } + friend bool operator < ( const bls_public_key& p1, const bls_public_key& p2) { return false; /*p1._pkey < p2._pkey;*/ } friend struct reflector; friend class bls_private_key; }; // bls_public_key