Skip to content

Commit

Permalink
Merge pull request #1561 from AntelopeIO/hotstuff_1511_hostfn
Browse files Browse the repository at this point in the history
set_finalizers host function (#1511)
  • Loading branch information
heifner authored Aug 30, 2023
2 parents f8e3dea + 36a60e1 commit b605ce9
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 66 deletions.
19 changes: 17 additions & 2 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <eosio/chain/platform_timer.hpp>
#include <eosio/chain/deep_mind.hpp>
#include <eosio/chain/wasm_interface_collection.hpp>
#include <eosio/chain/finalizer_set.hpp>

#include <chainbase/chainbase.hpp>
#include <eosio/vm/allocator.hpp>
Expand Down Expand Up @@ -263,6 +264,8 @@ struct controller_impl {
map< account_name, map<handler_key, apply_handler> > apply_handlers;
unordered_map< builtin_protocol_feature_t, std::function<void(controller_impl&)>, enum_hash<builtin_protocol_feature_t> > 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 );
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3281,6 +3289,14 @@ int64_t controller::set_proposed_producers( vector<producer_authority> 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;
Expand Down Expand Up @@ -3844,8 +3860,7 @@ void controller_impl::on_activation<builtin_protocol_feature_t::bls_primitives>(
template<>
void controller_impl::on_activation<builtin_protocol_feature_t::instant_finality>() {
db.modify( db.get<protocol_state_object>(), [&]( 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" );
} );
}

Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/include/eosio/chain/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ namespace eosio { namespace vm { class wasm_allocator; }}

namespace eosio { namespace chain {

struct finalizer_set;

class authorization_manager;

namespace resource_limits {
Expand Down Expand Up @@ -291,6 +293,9 @@ namespace eosio { namespace chain {

int64_t set_proposed_producers( vector<producer_authority> 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;
Expand Down Expand Up @@ -366,7 +371,6 @@ namespace eosio { namespace chain {
chainbase::database& mutable_db()const;

std::unique_ptr<controller_impl> my;

};

} } /// eosio::chain
59 changes: 59 additions & 0 deletions libraries/chain/include/eosio/chain/finalizer_set.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

#include <eosio/chain/config.hpp>
#include <eosio/chain/types.hpp>
#include <chainbase/chainbase.hpp>
#include <eosio/chain/authority.hpp>
#include <eosio/chain/snapshot.hpp>

#include <fc/crypto/bls_public_key.hpp>

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<finalizer_authority> 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<finalizer_authority> 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) )
9 changes: 9 additions & 0 deletions libraries/chain/include/eosio/chain/webassembly/interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ namespace webassembly {
*/
int64_t set_proposed_producers_ex(uint64_t packed_producer_format, legacy_span<const char> 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<const char> packed_finalizer_set);

/**
* Retrieve the blockchain config parameters.
*
Expand Down
30 changes: 30 additions & 0 deletions libraries/chain/webassembly/privileged.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <eosio/chain/transaction_context.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/apply_context.hpp>
#include <eosio/chain/finalizer_set.hpp>

#include <fc/io/datastream.hpp>

Expand Down Expand Up @@ -150,6 +151,35 @@ namespace eosio { namespace chain { namespace webassembly {
}
}

void interface::set_finalizers(span<const char> 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<const char*> ds( packed_finalizer_set.data(), packed_finalizer_set.size() );
finalizer_set finset;
fc::raw::unpack(ds, finset);
vector<finalizer_authority> & 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<fc::crypto::blslib::bls_public_key> unique_finalizer_keys;
#warning REVIEW: Is checking for unique finalizer descriptions at all relevant?
std::set<std::string> 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<char> packed_blockchain_parameters ) const {
auto& gpo = context.control.get_global_properties();

Expand Down
85 changes: 48 additions & 37 deletions libraries/hotstuff/chain_pacemaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(const chain::hs_message&)> broadcast_hs_message) {
Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -241,7 +245,26 @@ namespace eosio { namespace hotstuff {
}

std::vector<name> 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<producer_authority>& pa_list = hbs->active_schedule.producers;
std::vector<name> pn_list;
pn_list.reserve(pa_list.size());
Expand All @@ -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();
Expand All @@ -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); },
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down
2 changes: 0 additions & 2 deletions libraries/hotstuff/include/eosio/hotstuff/base_pacemaker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit b605ce9

Please sign in to comment.