Skip to content

Commit

Permalink
GH-376 Rename many qc types and added support for getting qc sigs on …
Browse files Browse the repository at this point in the history
…active and pending finalizer policies.

quorum_certificate_sig => qc_sig_t
quorum_certificate => qc_t
pending_quorum_certificate => open_qc_sig_t
Added open_qc_t which has the active finalizer policy signature and the optional pending finalizer policy signature.
  • Loading branch information
heifner committed Jul 20, 2024
1 parent e80b4b4 commit 71a58a3
Show file tree
Hide file tree
Showing 17 changed files with 660 additions and 499 deletions.
2 changes: 1 addition & 1 deletion libraries/chain/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ set(CHAIN_WEBASSEMBLY_SOURCES

set(CHAIN_FINALITY_SOURCES
finality/finalizer.cpp
finality/quorum_certificate.cpp
finality/qc.cpp
finality/finality_core.cpp
)

Expand Down
209 changes: 99 additions & 110 deletions libraries/chain/block_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ 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(active_finalizer_policy->finalizers.size(),
active_finalizer_policy->threshold,
active_finalizer_policy->max_weak_sum_before_weak_final())
, open_qc(active_finalizer_policy, pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{})
{
// ASSUMPTION FROM controller_impl::apply_block = all untrusted blocks will have their signatures pre-validated here
if( !skip_validate_signee ) {
Expand All @@ -31,17 +29,15 @@ block_state::block_state(const block_header_state& bhs,
deque<transaction_metadata_ptr>&& trx_metas,
deque<transaction_receipt>&& trx_receipts,
const std::optional<valid_t>& valid,
const std::optional<quorum_certificate>& qc,
const std::optional<qc_t>& qc,
const signer_callback_type& signer,
const block_signing_authority& valid_block_signing_authority,
const digest_type& action_mroot)
: 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(active_finalizer_policy->finalizers.size(),
active_finalizer_policy->threshold,
active_finalizer_policy->max_weak_sum_before_weak_final())
, open_qc(active_finalizer_policy, pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{})
, 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 @@ -96,10 +92,8 @@ block_state_ptr block_state::create_if_genesis_block(const block_state_legacy& b
result.strong_digest = result.compute_finality_digest(); // all block_header_state data populated in result at this point
result.weak_digest = create_weak_digest(result.strong_digest);

// pending_qc will not be used in the genesis block as finalizers will not vote on it, but still create it for consistency.
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()};
// open_qc will not be used in the genesis block as finalizers will not vote on it, but still create it for consistency.
result.open_qc = open_qc_t{result.active_finalizer_policy, finalizer_policy_ptr{}};

// build leaf_node and validation_tree
valid_t::finality_leaf_node_t leaf_node {
Expand Down Expand Up @@ -160,8 +154,7 @@ block_state::block_state(snapshot_detail::snapshot_block_state_v7&& sbs)
}
, strong_digest(compute_finality_digest())
, weak_digest(create_weak_digest(strong_digest))
, pending_qc(active_finalizer_policy->finalizers.size(), active_finalizer_policy->threshold,
active_finalizer_policy->max_weak_sum_before_weak_final()) // just in case we receive votes
, open_qc(active_finalizer_policy, pending_finalizer_policy ? pending_finalizer_policy->second : finalizer_policy_ptr{}) // just in case we receive votes
, valid(std::move(sbs.valid))
{
header_exts = header.validate_and_extract_header_extensions();
Expand All @@ -181,122 +174,118 @@ void block_state::set_trxs_metas( deque<transaction_metadata_ptr>&& trxs_metas,

// Called from vote threads
vote_status block_state::aggregate_vote(uint32_t connection_id, const vote_message& vote) {
const auto& finalizers = active_finalizer_policy->finalizers;
auto it = std::find_if(finalizers.begin(),
finalizers.end(),
[&](const auto& finalizer) { return finalizer.public_key == vote.finalizer_key; });

if (it != finalizers.end()) {
auto index = std::distance(finalizers.begin(), it);
auto digest = vote.strong ? strong_digest.to_uint8_span() : std::span<const uint8_t>(weak_digest);
return pending_qc.add_vote(connection_id,
block_num(),
vote.strong,
digest,
index,
vote.finalizer_key,
vote.sig,
finalizers[index].weight);
} else {
fc_wlog(vote_logger, "connection - ${c} finalizer_key ${k} in vote is not in finalizer policy",
("c", connection_id)("k", vote.finalizer_key.to_string().substr(8,16)));
return vote_status::unknown_public_key;
std::string key = vote.finalizer_key.to_string();
bool verified_sig = false;
auto verify_sig = [&]() -> vote_status {
auto finalizer_digest = vote.strong ? strong_digest.to_uint8_span() : std::span<const uint8_t>(weak_digest);
if (!verified_sig && !fc::crypto::blslib::verify(vote.finalizer_key, finalizer_digest, vote.sig)) {
fc_wlog(vote_logger, "connection - ${c} signature from finalizer ${k}.. cannot be verified",
("c", connection_id)("k", vote.finalizer_key.to_string().substr(8,16)));
return vote_status::invalid_signature;
}
verified_sig = true;
return vote_status::success;
};

auto add_vote = [&](const finalizer_policy_ptr& finalizer_policy, open_qc_sig_t& open_qc_sig) -> vote_status {
const auto& finalizers = finalizer_policy->finalizers;
auto itr = std::ranges::find_if(finalizers, [&](const auto& finalizer) { return finalizer.public_key == vote.finalizer_key; });
vote_status s = vote_status::unknown_public_key;
if (itr != finalizers.end()) {
auto index = std::distance(finalizers.begin(), itr);
if (open_qc_sig.has_voted(vote.strong, index)) {
fc_dlog(vote_logger, "connection - ${c} block_num: ${bn}, duplicate", ("c", connection_id)("bn", block_num()));
return vote_status::duplicate;
}
if (vote_status vs = verify_sig(); vs != vote_status::success)
return vs;
s = open_qc_sig.add_vote(connection_id, block_num(),
vote.strong,
index,
vote.sig,
finalizers[index].weight);

}
return s;
};

vote_status s = add_vote(active_finalizer_policy, open_qc.active_policy_sig);
if (s != vote_status::success && s != vote_status::unknown_public_key) {
ilog("aggregate active vote ${v} for key ${k}", ("v", s)("k", key));
return s;
}

if (pending_finalizer_policy) {
assert(open_qc.pending_policy_sig);
vote_status ps = add_vote(pending_finalizer_policy->second, *open_qc.pending_policy_sig);
if (ps != vote_status::unknown_public_key)
s = ps;
}

if (s != vote_status::unknown_public_key) {
ilog("aggregate pending vote ${v} for key ${k}", ("v", s)("k", key));
return s;
}

fc_wlog(vote_logger, "connection - ${c} finalizer_key ${k} in vote is not in finalizer policies",
("c", connection_id)("k", vote.finalizer_key.to_string().substr(8,16)));
return s;
}

vote_status_t block_state::has_voted(const bls_public_key& key) const {
const auto& finalizers = active_finalizer_policy->finalizers;
auto it = std::find_if(finalizers.begin(),
finalizers.end(),
[&](const auto& finalizer) { return finalizer.public_key == key; });
std::string key_str = key.to_string();
auto finalizer_has_voted = [](const finalizer_policy_ptr& policy,
const open_qc_sig_t& open_qc_sig,
const bls_public_key& key) -> vote_status_t {
const auto& finalizers = policy->finalizers;
auto it = std::ranges::find_if(finalizers, [&](const auto& finalizer) { return finalizer.public_key == key; });
if (it != finalizers.end()) {
auto index = std::distance(finalizers.begin(), it);
return open_qc_sig.has_voted(index) ? vote_status_t::voted : vote_status_t::not_voted;
}
return vote_status_t::irrelevant_finalizer;
};

if (it != finalizers.end()) {
auto index = std::distance(finalizers.begin(), it);
return pending_qc.has_voted(index) ? vote_status_t::voted : vote_status_t::not_voted;
vote_status_t active_status = finalizer_has_voted(active_finalizer_policy, open_qc.active_policy_sig, key);
if (!pending_finalizer_policy || active_status == vote_status_t::not_voted) {
ilog("has voted active vote ${v} for key ${k}", ("v", (int)active_status)("k", key_str));
return active_status;
}
return vote_status_t::irrelevant_finalizer;

EOS_ASSERT(open_qc.pending_policy_sig, invalid_qc_claim,
"qc does not contain pending policy signature for pending finalizer policy");
vote_status_t pending_status = finalizer_has_voted(pending_finalizer_policy->second, *open_qc.pending_policy_sig, key);

if (pending_status == vote_status_t::irrelevant_finalizer) {
ilog("has voted active, pending irrevelevant vote ${v} for key ${k}", ("v", (int)active_status)("k", key_str));
return active_status;
}
ilog("has voted pending/active vote ${v} for key ${k}", ("v", (int)pending_status)("k", key_str));
return pending_status;
}

vote_info_vec block_state::get_votes() const {
const auto& finalizers = active_finalizer_policy->finalizers;
vote_info_vec res;
res.reserve(finalizers.size());
pending_qc.visit_votes([&](size_t idx, bool strong) { res.emplace_back(finalizers[idx].public_key, strong); });
//todo: open_qc.visit_votes([&](size_t idx, bool strong) { res.emplace_back(finalizers[idx].public_key, strong); });
return res;
}

// Called from net threads
void block_state::verify_qc(const quorum_certificate_sig& qc) const {
const auto& finalizers = active_finalizer_policy->finalizers;
auto num_finalizers = finalizers.size();

// utility to accumulate voted weights
auto weights = [&] ( const vote_bitset& votes_bitset ) -> uint64_t {
EOS_ASSERT( num_finalizers == votes_bitset.size(),
invalid_qc_claim,
"vote bitset size is not the same as the number of finalizers for the policy it refers to, vote bitset size: ${s}, num of finalizers for the policy: ${n}",
("s", votes_bitset.size())("n", num_finalizers) );

uint64_t sum = 0;
for (auto i = 0u; i < num_finalizers; ++i) {
if( votes_bitset[i] ) { // ith finalizer voted
sum += finalizers[i].weight;
}
}
return sum;
};

// compute strong and weak accumulated weights
auto strong_weights = qc.strong_votes ? weights( *qc.strong_votes ) : 0;
auto weak_weights = qc.weak_votes ? weights( *qc.weak_votes ) : 0;

// verfify quorum is met
if( qc.is_strong() ) {
EOS_ASSERT( strong_weights >= active_finalizer_policy->threshold,
invalid_qc_claim,
"strong quorum is not met, strong_weights: ${s}, threshold: ${t}",
("s", strong_weights)("t", active_finalizer_policy->threshold) );
} else {
EOS_ASSERT( strong_weights + weak_weights >= active_finalizer_policy->threshold,
invalid_qc_claim,
"weak quorum is not met, strong_weights: ${s}, weak_weights: ${w}, threshold: ${t}",
("s", strong_weights)("w", weak_weights)("t", active_finalizer_policy->threshold) );
void block_state::verify_qc(const qc_t& qc) const {
if (qc.pending_policy_sig) {
EOS_ASSERT(pending_finalizer_policy, invalid_qc_claim,
"qc contains pending policy signature for nonexistent pending finalizer policy");
} else if (pending_finalizer_policy) {
EOS_ASSERT(false, invalid_qc_claim,
"qc does not contain pending policy signature for pending finalizer policy");
}

// no reason to use bls_public_key wrapper
std::vector<bls12_381::g1> pubkeys;
pubkeys.reserve(2);
std::vector<std::vector<uint8_t>> digests;
digests.reserve(2);

// utility to aggregate public keys for verification
auto aggregate_pubkeys = [&](const auto& votes_bitset) -> bls12_381::g1 {
const auto n = std::min(num_finalizers, votes_bitset.size());
std::vector<bls12_381::g1> pubkeys_to_aggregate;
pubkeys_to_aggregate.reserve(n);
for(auto i = 0u; i < n; ++i) {
if (votes_bitset[i]) { // ith finalizer voted
pubkeys_to_aggregate.emplace_back(finalizers[i].public_key.jacobian_montgomery_le());
}
}

return bls12_381::aggregate_public_keys(pubkeys_to_aggregate);
};

// aggregate public keys and digests for strong and weak votes
if( qc.strong_votes ) {
pubkeys.emplace_back(aggregate_pubkeys(*qc.strong_votes));
digests.emplace_back(std::vector<uint8_t>{strong_digest.data(), strong_digest.data() + strong_digest.data_size()});
}

if( qc.weak_votes ) {
pubkeys.emplace_back(aggregate_pubkeys(*qc.weak_votes));
digests.emplace_back(std::vector<uint8_t>{weak_digest.begin(), weak_digest.end()});
qc.active_policy_sig.verify(active_finalizer_policy, strong_digest, weak_digest);
if (pending_finalizer_policy) {
qc.pending_policy_sig->verify(pending_finalizer_policy->second, strong_digest, weak_digest);
}

// validate aggregated signature
EOS_ASSERT( bls12_381::aggregate_verify(pubkeys, digests, qc.sig.jacobian_montgomery_le()),
invalid_qc_claim, "signature validation failed" );
}

qc_claim_t block_state::extract_qc_claim() const {
Expand Down
Loading

0 comments on commit 71a58a3

Please sign in to comment.