Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set_finalizers host function (#1511) #1561

Merged
merged 19 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arhag ?

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