Skip to content

Commit

Permalink
Merge pull request #19 from AntelopeIO/gh_1618
Browse files Browse the repository at this point in the history
IF: Implement finalizer policy change at appropriate time
  • Loading branch information
greg7mdp authored Apr 18, 2024
2 parents 608464d + c98b221 commit 3a80e26
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 35 deletions.
83 changes: 67 additions & 16 deletions libraries/chain/block_header_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ digest_type block_header_state::compute_base_digest() const {

for (const auto& fp_pair : finalizer_policies) {
fc::raw::pack( enc, fp_pair.first );
assert(fp_pair.second);
fc::raw::pack( enc, *fp_pair.second );
const finalizer_policy_tracker& tracker = fp_pair.second;
fc::raw::pack( enc, tracker.state );
assert(tracker.policy);
fc::raw::pack( enc, *tracker.policy );
}

assert(active_proposer_policy);
fc::raw::pack( enc, *active_proposer_policy );

for (const auto& pp_pair : proposer_policies) {
fc::raw::pack( enc, pp_pair.first );
assert(pp_pair.second);
fc::raw::pack( enc, *pp_pair.second );
}
Expand Down Expand Up @@ -79,6 +82,7 @@ void finish_next(const block_header_state& prev,
block_header_state& next_header_state,
vector<digest_type> new_protocol_feature_activations,
std::shared_ptr<proposer_policy> new_proposer_policy,
std::optional<finalizer_policy> new_finalizer_policy,
qc_claim_t qc_claim) {

// activated protocol features
Expand Down Expand Up @@ -110,10 +114,6 @@ void finish_next(const block_header_state& prev,
next_header_state.proposer_policies[new_proposer_policy->active_time] = std::move(new_proposer_policy);
}

// finalizer policy
// ----------------
next_header_state.active_finalizer_policy = prev.active_finalizer_policy;

// finality_core
// -------------
block_ref parent_block {
Expand All @@ -122,6 +122,49 @@ void finish_next(const block_header_state& prev,
};
next_header_state.core = prev.core.next(parent_block, qc_claim);

// finalizer policy
// ----------------
next_header_state.active_finalizer_policy = prev.active_finalizer_policy;

if(!prev.finalizer_policies.empty()) {
auto lib = next_header_state.core.last_final_block_num();
auto it = prev.finalizer_policies.begin();
if (it->first > lib) {
// we have at least one `finalizer_policy` in our map, but none of these is
// due to become active of this block because lib has not advanced enough, so
// we just copy the multimap and keep using the same `active_finalizer_policy`
next_header_state.finalizer_policies = prev.finalizer_policies;
} else {
while (it != prev.finalizer_policies.end() && it->first <= lib) {
const finalizer_policy_tracker& tracker = it->second;
if (tracker.state == finalizer_policy_tracker::state_t::pending) {
// new finalizer_policy becones active
next_header_state.active_finalizer_policy.reset(new finalizer_policy(*tracker.policy));
next_header_state.active_finalizer_policy->generation = prev.active_finalizer_policy->generation + 1;
} else {
assert(tracker.state == finalizer_policy_tracker::state_t::proposed);
// block where finalizer_policy was proposed became final. The finalizer policy will
// become active when next block becomes final.
finalizer_policy_tracker t { finalizer_policy_tracker::state_t::pending, tracker.policy };
next_header_state.finalizer_policies.emplace(next_header_state.block_num(), std::move(t));
}
++it;
}
if (it != prev.finalizer_policies.end()) {
// copy remainder of pending finalizer_policy changes
next_header_state.finalizer_policies.insert(boost::container::ordered_unique_range_t(),
it, prev.finalizer_policies.end());
}
}
}

if (new_finalizer_policy) {
next_header_state.finalizer_policies.emplace(
next_header_state.block_num(),
finalizer_policy_tracker{finalizer_policy_tracker::state_t::proposed,
std::make_shared<finalizer_policy>(std::move(*new_finalizer_policy))});
}

// Finally update block id from header
// -----------------------------------
next_header_state.block_id = next_header_state.header.calculate_id();
Expand All @@ -145,7 +188,7 @@ block_header_state block_header_state::next(block_header_state_input& input) con
// finality extension
// ------------------
instant_finality_extension new_if_ext {input.most_recent_ancestor_with_qc,
std::move(input.new_finalizer_policy),
input.new_finalizer_policy,
input.new_proposer_policy};

uint16_t if_ext_id = instant_finality_extension::extension_id();
Expand All @@ -162,7 +205,9 @@ block_header_state block_header_state::next(block_header_state_input& input) con
next_header_state.header_exts.emplace(ext_id, std::move(pfa_ext));
}

finish_next(*this, next_header_state, std::move(input.new_protocol_feature_activations), std::move(input.new_proposer_policy), input.most_recent_ancestor_with_qc);
finish_next(*this, next_header_state, std::move(input.new_protocol_feature_activations),
std::move(input.new_proposer_policy), std::move(input.new_finalizer_policy),
input.most_recent_ancestor_with_qc);

return next_header_state;
}
Expand All @@ -176,14 +221,16 @@ block_header_state block_header_state::next(block_header_state_input& input) con
block_header_state block_header_state::next(const signed_block_header& h, validator_t& validator) const {
auto producer = detail::get_scheduled_producer(active_proposer_policy->proposer_schedule.producers, h.timestamp).producer_name;

EOS_ASSERT( h.previous == block_id, unlinkable_block_exception, "previous mismatch ${p} != ${id}", ("p", h.previous)("id", block_id) );
EOS_ASSERT( h.previous == block_id, unlinkable_block_exception,
"previous mismatch ${p} != ${id}", ("p", h.previous)("id", block_id) );
EOS_ASSERT( h.producer == producer, wrong_producer, "wrong producer specified" );
EOS_ASSERT( !h.new_producers, producer_schedule_exception, "Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" );
EOS_ASSERT( !h.new_producers, producer_schedule_exception,
"Block header contains legacy producer schedule outdated by activation of WTMsig Block Signatures" );

block_header_state next_header_state;
next_header_state.header = static_cast<const block_header&>(h);
next_header_state.header_exts = h.validate_and_extract_header_extensions();
auto& exts = next_header_state.header_exts;
const auto& exts = next_header_state.header_exts;

// retrieve protocol_feature_activation from incoming block header extension
// -------------------------------------------------------------------------
Expand All @@ -199,8 +246,8 @@ block_header_state block_header_state::next(const signed_block_header& h, valida
// --------------------------------------------------------------------
EOS_ASSERT(exts.count(instant_finality_extension::extension_id()) > 0, invalid_block_header_extension,
"Instant Finality Extension is expected to be present in all block headers after switch to IF");
auto if_entry = exts.lower_bound(instant_finality_extension::extension_id());
auto& if_ext = std::get<instant_finality_extension>(if_entry->second);
auto if_entry = exts.lower_bound(instant_finality_extension::extension_id());
const auto& if_ext = std::get<instant_finality_extension>(if_entry->second);

if (h.is_proper_svnn_block()) {
// if there is no Finality Tree Root associated with the block,
Expand All @@ -211,14 +258,18 @@ block_header_state block_header_state::next(const signed_block_header& h, valida
EOS_ASSERT(no_finality_tree_associated == h.action_mroot.empty(), block_validate_exception,
"No Finality Tree Root associated with the block, does not match with empty action_mroot: "
"(${n}), action_mroot empty (${e}), final_on_strong_qc_block_num (${f})",
("n", no_finality_tree_associated)("e", h.action_mroot.empty())("f", next_core_metadata.final_on_strong_qc_block_num));
("n", no_finality_tree_associated)("e", h.action_mroot.empty())
("f", next_core_metadata.final_on_strong_qc_block_num));
};

finish_next(*this, next_header_state, std::move(new_protocol_feature_activations), if_ext.new_proposer_policy, if_ext.qc_claim);
finish_next(*this, next_header_state, std::move(new_protocol_feature_activations), if_ext.new_proposer_policy,
if_ext.new_finalizer_policy, if_ext.qc_claim);

return next_header_state;
}

} // namespace eosio::chain

FC_REFLECT( eosio::chain::finality_digest_data_v1, (major_version)(minor_version)(active_finalizer_policy_generation)(finality_tree_digest)(active_finalizer_policy_and_base_digest) )
FC_REFLECT( eosio::chain::finality_digest_data_v1,
(major_version)(minor_version)(active_finalizer_policy_generation)
(finality_tree_digest)(active_finalizer_policy_and_base_digest) )
12 changes: 9 additions & 3 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ block_state::block_state(const block_header_state& prev, signed_block_ptr b, con
, block(std::move(b))
, strong_digest(compute_finality_digest())
, weak_digest(create_weak_digest(strong_digest))
, pending_qc(prev.active_finalizer_policy->finalizers.size(), prev.active_finalizer_policy->threshold, prev.active_finalizer_policy->max_weak_sum_before_weak_final())
, pending_qc(active_finalizer_policy->finalizers.size(),
active_finalizer_policy->threshold,
active_finalizer_policy->max_weak_sum_before_weak_final())
{
// ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here
if( !skip_validate_signee ) {
Expand All @@ -37,7 +39,9 @@ block_state::block_state(const block_header_state& bhs,
, block(std::make_shared<signed_block>(signed_block_header{bhs.header}))
, strong_digest(compute_finality_digest())
, weak_digest(create_weak_digest(strong_digest))
, pending_qc(bhs.active_finalizer_policy->finalizers.size(), bhs.active_finalizer_policy->threshold, bhs.active_finalizer_policy->max_weak_sum_before_weak_final())
, pending_qc(active_finalizer_policy->finalizers.size(),
active_finalizer_policy->threshold,
active_finalizer_policy->max_weak_sum_before_weak_final())
, valid(valid)
, pub_keys_recovered(true) // called by produce_block so signature recovery of trxs must have been done
, cached_trxs(std::move(trx_metas))
Expand Down Expand Up @@ -84,7 +88,9 @@ block_state_ptr block_state::create_if_genesis_block(const block_state_legacy& b

// TODO: https://github.com/AntelopeIO/leap/issues/2057
// TODO: Do not aggregate votes on blocks created from block_state_legacy. This can be removed when #2057 complete.
result.pending_qc = pending_quorum_certificate{result.active_finalizer_policy->finalizers.size(), result.active_finalizer_policy->threshold, result.active_finalizer_policy->max_weak_sum_before_weak_final()};
result.pending_qc = pending_quorum_certificate{result.active_finalizer_policy->finalizers.size(),
result.active_finalizer_policy->threshold,
result.active_finalizer_policy->max_weak_sum_before_weak_final()};

// build leaf_node and validation_tree
valid_t::finality_leaf_node_t leaf_node {
Expand Down
21 changes: 19 additions & 2 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,8 @@ struct building_block {
bb.new_finalizer_policy = std::move(fin_pol);
},
[&](building_block_if& bb) {
fin_pol.generation = bb.parent.active_finalizer_policy->generation + 1;
bb.new_finalizer_policy = std::move(fin_pol);
bb.new_finalizer_policy = std::move(fin_pol);
// generation will be updated when activated
} },
v);
}
Expand Down Expand Up @@ -3580,6 +3580,10 @@ struct controller_impl {
auto bsp = forkdb.get_block(id);
if (bsp) {
return my_finalizers.all_of_public_keys([&bsp](const auto& k) {
const finalizer_policy_ptr& fp { bsp->active_finalizer_policy };
assert(fp);
if (!std::ranges::any_of(fp->finalizers, [&](const auto& auth) { return auth.public_key == k; }))
return true; // we only care about keys from the active finalizer_policy
return bsp->has_voted(k);
});
}
Expand All @@ -3589,6 +3593,15 @@ struct controller_impl {
return !voted || *voted;
}

std::optional<finalizer_policy> active_finalizer_policy(const block_id_type& id) const {
return fork_db.apply_s<std::optional<finalizer_policy>>([&](auto& forkdb) -> std::optional<finalizer_policy> {
auto bsp = forkdb.get_block(id);
if (bsp)
return *bsp->active_finalizer_policy;
return {};
});
}

// thread safe
void create_and_send_vote_msg(const block_state_ptr& bsp) {
if (!bsp->block->is_proper_svnn_block())
Expand Down Expand Up @@ -5263,6 +5276,10 @@ bool controller::node_has_voted_if_finalizer(const block_id_type& id) const {
return my->node_has_voted_if_finalizer(id);
}

std::optional<finalizer_policy> controller::active_finalizer_policy(const block_id_type& id) const {
return my->active_finalizer_policy(id);
}

const producer_authority_schedule& controller::active_producers()const {
return my->active_producers();
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/hotstuff/finalizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,10 @@ my_finalizers_t::fsi_map my_finalizers_t::load_finalizer_safety_info() {

// ----------------------------------------------------------------------------------------
void my_finalizers_t::set_keys(const std::map<std::string, std::string>& finalizer_keys) {
assert(finalizers.empty()); // set_keys should be called only once at startup
if (finalizer_keys.empty())
return;

assert(finalizers.empty()); // set_keys should be called only once at startup
fsi_map safety_info = load_finalizer_safety_info();
for (const auto& [pub_key_str, priv_key_str] : finalizer_keys) {
auto public_key {bls_public_key{pub_key_str}};
Expand Down
32 changes: 30 additions & 2 deletions libraries/chain/include/eosio/chain/block_header_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@ namespace detail { struct schedule_info; };
constexpr uint32_t light_header_protocol_version_major = 1;
constexpr uint32_t light_header_protocol_version_minor = 0;

// ------------------------------------------------------------------------------------------
// this is used for tracking in-flight `finalizer_policy` changes, which have been requested,
// but are not activated yet. This struct is associated to a block_number in the
// `finalizer_policies` flat_multimap: `block_num => state, finalizer_policy`
//
// When state == proposed, the block_num identifies the block in which the new policy was
// proposed via set_finalizers.
//
// When that block becomes final, according to the block_header_state's finality_core,
// 1. the policy becomes pending
// 2. its key `block_num,` in the proposer_policies multimap, is the current block
//
// When this current block itself becomes final, the policy becomes active.
// ------------------------------------------------------------------------------------------
struct finalizer_policy_tracker {
enum class state_t { proposed = 0, pending };
state_t state;
finalizer_policy_ptr policy;
};

struct building_block_input {
block_id_type parent_id;
block_timestamp_type parent_timestamp;
Expand Down Expand Up @@ -49,8 +69,12 @@ struct block_header_state {
proposer_policy_ptr active_proposer_policy; // producer authority schedule, supports `digest()`

// block time when proposer_policy will become active
flat_map<block_timestamp_type, proposer_policy_ptr> proposer_policies;
flat_map<uint32_t, finalizer_policy_ptr> finalizer_policies;
flat_map<block_timestamp_type, proposer_policy_ptr> proposer_policies;

// track in-flight finalizer policies. This is a `multimap` because the same block number
// can hold a `proposed` and a `pending` finalizer_policy. When that block becomes final, the
// `pending` becomes active, and the `proposed` becomes `pending` (for a different block number).
flat_multimap<block_num_type, finalizer_policy_tracker> finalizer_policies;


// ------ data members caching information available elsewhere ----------------------
Expand Down Expand Up @@ -93,6 +117,10 @@ using block_header_state_ptr = std::shared_ptr<block_header_state>;

}

FC_REFLECT_ENUM( eosio::chain::finalizer_policy_tracker::state_t, (proposed)(pending))

FC_REFLECT( eosio::chain::finalizer_policy_tracker, (state)(policy))

FC_REFLECT( eosio::chain::block_header_state, (block_id)(header)
(activated_protocol_features)(core)(active_finalizer_policy)
(active_proposer_policy)(proposer_policies)(finalizer_policies)(header_exts))
3 changes: 3 additions & 0 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ namespace eosio::chain {
// thread safe, for testing
bool node_has_voted_if_finalizer(const block_id_type& id) const;

// thread safe, for testing
std::optional<finalizer_policy> active_finalizer_policy(const block_id_type& id) const;

bool light_validation_allowed() const;
bool skip_auth_check()const;
bool skip_trx_checks()const;
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eosio/chain/snapshot_detail.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ namespace eosio::chain::snapshot_detail {
finalizer_policy_ptr active_finalizer_policy;
proposer_policy_ptr active_proposer_policy;
flat_map<block_timestamp_type, proposer_policy_ptr> proposer_policies;
flat_map<uint32_t, finalizer_policy_ptr> finalizer_policies;
flat_multimap<block_num_type, finalizer_policy_tracker> finalizer_policies;

// from block_state
std::optional<valid_t> valid;
Expand Down
8 changes: 8 additions & 0 deletions libraries/libfc/include/fc/crypto/bls_public_key.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ namespace fc::crypto::blslib {
return ds;
}

friend std::ostream& operator<<(std::ostream& os, const bls_public_key& k) {
os << "bls_public_key(0x" << std::hex;
for (auto c : k.affine_non_montgomery_le())
os << std::setfill('0') << std::setw(2) << (int)c;
os << std::dec << ")";
return os;
}

template<typename T>
friend T& operator>>(T& ds, bls_public_key& sig) {
// Serialization as variable length array when it is stored as a fixed length array. This makes for easier deserialization by external tools
Expand Down
Loading

0 comments on commit 3a80e26

Please sign in to comment.