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

IF: Implement finalizer policy change at appropriate time #19

Merged
merged 21 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a3ce90c
Implement finalizer policy change at appropriate time.
greg7mdp Apr 10, 2024
64616c3
Add missing serialization of `proposer_policies`'s timestamp in `base…
greg7mdp Apr 10, 2024
7f30795
Add test for `set_finalizers`, wip
greg7mdp Apr 12, 2024
919e923
First test of correct finalizer set change passes.
greg7mdp Apr 15, 2024
5147914
Add new testcase validating that latest `set_finalizer()` call in a b…
greg7mdp Apr 15, 2024
3e0d4c3
Increment finalizer_policy generation correctly
greg7mdp Apr 17, 2024
eb74ba0
`finalizer_policies` should be a `multimap`.
greg7mdp Apr 17, 2024
22ef766
Fix debugging output for `bls_public_key`
greg7mdp Apr 17, 2024
00e59ef
Complete basic unittests.
greg7mdp Apr 17, 2024
60bda6d
Check that range not empty before copying remainder of pending finali…
greg7mdp Apr 17, 2024
f894cdd
Remove code duplication in `set_finalizers` tests.
greg7mdp Apr 17, 2024
bf72494
Use `block_num_type` instead of `uint32_t`
greg7mdp Apr 17, 2024
3d1cddc
Avoid modifying data from previous `block_header_state`.
greg7mdp Apr 17, 2024
7f6a622
Remove unnecessary setting of `finalizer_policy::generation`
greg7mdp Apr 17, 2024
8cc3518
Merge branch 'savanna' of github.com:AntelopeIO/spring into gh_1618
greg7mdp Apr 17, 2024
25f93f8
Fix asan issue.
greg7mdp Apr 17, 2024
addf4a1
Merge branch 'savanna' of github.com:AntelopeIO/spring into gh_1618
greg7mdp Apr 17, 2024
9f8da9e
Split up new finalizer update tests into new file.
greg7mdp Apr 17, 2024
5d226c4
Add comment for `finalizer_policies` multimap.
greg7mdp Apr 17, 2024
47cf8a2
Add `reserve`
greg7mdp Apr 18, 2024
c98b221
Add comments.
greg7mdp Apr 18, 2024
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
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) {
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
// 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;
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
} 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 };
linh2931 marked this conversation as resolved.
Show resolved Hide resolved
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;
heifner marked this conversation as resolved.
Show resolved Hide resolved


// ------ 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
Loading