Skip to content
Open
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
301 changes: 216 additions & 85 deletions src/blockchain/fork_choice.cpp

Large diffs are not rendered by default.

69 changes: 54 additions & 15 deletions src/blockchain/fork_choice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "types/state.hpp"
#include "types/validator_index.hpp"
#include "utils/ceil_div.hpp"
#include "utils/tuple_hash.hpp"

namespace lean {
struct GenesisConfig;
Expand Down Expand Up @@ -56,12 +57,13 @@ namespace lean {
class ForkChoiceStore {
public:
using Blocks = std::unordered_map<BlockHash, SignedBlockWithAttestation>;
using SignedAttestations =
std::unordered_map<ValidatorIndex, SignedAttestation>;
using AttestationDataByValidator =
std::unordered_map<ValidatorIndex, AttestationData>;

enum class Error {
INVALID_ATTESTATION,
INVALID_PROPOSER,
SIGNATURE_COUNT_MISMATCH,
};
Q_ENUM_ERROR_CODE_FRIEND(Error) {
using E = decltype(e);
Expand All @@ -70,6 +72,8 @@ namespace lean {
return "Invalid attestation";
case E::INVALID_PROPOSER:
return "Invalid proposer";
case E::SIGNATURE_COUNT_MISMATCH:
return "Signature count must match attestation count";
}
abort();
}
Expand Down Expand Up @@ -111,8 +115,8 @@ namespace lean {
Checkpoint latest_finalized,
Blocks blocks,
std::unordered_map<BlockHash, State> states,
SignedAttestations latest_known_attestations,
SignedAttestations latest_new_votes,
AttestationDataByValidator latest_known_attestations,
AttestationDataByValidator latest_new_votes,
ValidatorIndex validator_index,
qtils::SharedRef<ValidatorRegistry> validator_registry,
qtils::SharedRef<app::ValidatorKeysManifest> validator_keys_manifest,
Expand Down Expand Up @@ -151,13 +155,13 @@ namespace lean {
const Blocks &getBlocks() const {
return blocks_;
}
const SignedAttestations &getLatestNewAttestations() const {
const AttestationDataByValidator &getLatestNewAttestations() const {
return latest_new_attestations_;
}
const SignedAttestations &getLatestKnownAttestations() const {
const AttestationDataByValidator &getLatestKnownAttestations() const {
return latest_known_attestations_;
}
SignedAttestations &getLatestNewVotesRef() {
AttestationDataByValidator &getLatestNewVotesRef() {
return latest_new_attestations_;
}

Expand Down Expand Up @@ -193,9 +197,10 @@ namespace lean {
* Returns:
* Hash of the chosen head block.
*/
BlockHash computeLmdGhostHead(const BlockHash &start_root,
const SignedAttestations &attestations,
uint64_t min_score = 0) const;
BlockHash computeLmdGhostHead(
const BlockHash &start_root,
const AttestationDataByValidator &attestations,
uint64_t min_score = 0) const;

/**
* Calculate target checkpoint for validator attestations.
Expand Down Expand Up @@ -307,7 +312,18 @@ namespace lean {
* Returns:
* Success if validation passes, error otherwise.
*/
outcome::result<void> validateAttestation(
outcome::result<void> validateAttestation(const Attestation &attestation);

/**
* Process a signed attestation received via gossip network.
* This method:
* 1. Verifies the XMSS signature
* 2. Stores the signature in the gossip signature map
* 3. Processes the attestation data via on_attestation
* Args:
* signed_attestation: The signed attestation from gossip.
*/
outcome::result<void> onGossipAttestation(
const SignedAttestation &signed_attestation);

/**
Expand Down Expand Up @@ -356,8 +372,8 @@ namespace lean {
* Returns:
* Success if the attestation was processed, error otherwise.
*/
outcome::result<void> onAttestation(
const SignedAttestation &signed_attestation, bool is_from_block);
outcome::result<void> onAttestation(const Attestation &attestation,
bool is_from_block);


// Processes a new block, updates the store, and triggers a head update.
Expand All @@ -377,6 +393,12 @@ namespace lean {
}

private:
using ValidatorAttestationKey = std::tuple<ValidatorIndex, BlockHash>;

static ValidatorAttestationKey validatorAttestationKey(
ValidatorIndex validator_index,
const AttestationData &attestation_data);

// Verify all XMSS signatures in a signed block.
//
// This method ensures that every attestation included in the block
Expand Down Expand Up @@ -464,7 +486,7 @@ namespace lean {
* For each validator, stores their most recent attestation that is
* currently influencing the fork choice head computation.
*/
SignedAttestations latest_known_attestations_;
AttestationDataByValidator latest_known_attestations_;

/**
* Pending attestations awaiting activation.
Expand All @@ -477,7 +499,24 @@ namespace lean {
* Attestations move from this map to latest_known_attestations_ during
* interval ticks.
*/
SignedAttestations latest_new_attestations_;
AttestationDataByValidator latest_new_attestations_;
/**
* Map of validator id and attestation root to the XMSS signature.
*/
std::unordered_map<ValidatorAttestationKey, Signature>
gossip_attestation_signatures_;
/**
* Aggregated signature payloads for attestations from blocks.
* - Keyed by (validator_id, attestation_data_root).
* - Values are lists because same (validator_id, data) can appear in
* multiple aggregations.
* - Used for recursive signature aggregation when building blocks.
* - Populated by on_block.
*/
std::unordered_map<ValidatorAttestationKey,
std::vector<std::shared_ptr<LeanAggregatedSignature>>>
block_attestation_signatures_;

qtils::SharedRef<ValidatorRegistry> validator_registry_;
qtils::SharedRef<app::ValidatorKeysManifest> validator_keys_manifest_;
log::Logger logger_;
Expand Down
16 changes: 9 additions & 7 deletions src/blockchain/state_transition_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ namespace lean {
}

outcome::result<void> STF::processAttestations(
State &state, const Attestations &attestations) const {
State &state, const AggregatedAttestations &attestations) const {
auto timer = metrics_->stf_attestations_processing_time_seconds()->timer();

// NOTE:
Expand Down Expand Up @@ -346,12 +346,14 @@ namespace lean {
justifications_it->second.resize(state.validatorCount());
}

auto validator_id = attestation.validator_id;
if (validator_id >= justifications_it->second.size()) {
return Error::INVALID_VOTER;
}
if (not justifications_it->second.at(validator_id)) {
justifications_it->second.at(validator_id) = true;
for (auto &&validator_id :
getAggregatedValidators(attestation.aggregation_bits)) {
if (validator_id >= justifications_it->second.size()) {
return Error::INVALID_VOTER;
}
if (not justifications_it->second.at(validator_id)) {
justifications_it->second.at(validator_id) = true;
}
}

size_t count = std::ranges::count(justifications_it->second, true);
Expand Down
2 changes: 1 addition & 1 deletion src/blockchain/state_transition_function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ namespace lean {
outcome::result<void> processOperations(State &state,
const BlockBody &body) const;
outcome::result<void> processAttestations(
State &state, const Attestations &attestations) const;
State &state, const AggregatedAttestations &attestations) const;
bool validateProposerIndex(const State &state, const Block &block) const;

private:
Expand Down
4 changes: 4 additions & 0 deletions src/crypto/crypto_metrics.def
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@ METRIC_HISTOGRAM(crypto_pq_signature_attestation_verification_time_seconds,
"lean_pq_signature_attestation_verification_time_seconds",
"Time taken to verify an attestation signature",
(0.005, 0.01, 0.025, 0.05, 0.1, 1))
METRIC_HISTOGRAM(crypto_pq_signature_aggregated_attestation_verification_time_seconds,
"TODO_lean_pq_signature_aggregated_attestation_verification_time_seconds",
"TODO Time taken to verify an aggregated attestation signature",
(0.005, 0.01, 0.025, 0.05, 0.1, 1))
2 changes: 2 additions & 0 deletions src/crypto/xmss/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ namespace lean::crypto::xmss {
XmssPrivateKey private_key;
XmssPublicKey public_key;
};
using XmssMessage = qtils::ByteArr<PQ_MESSAGE_SIZE>;
using XmssSignature = qtils::ByteArr<PQ_SIGNATURE_SIZE>;
using XmssAggregatedSignature = qtils::ByteVec;
} // namespace lean::crypto::xmss
20 changes: 16 additions & 4 deletions src/crypto/xmss/xmss_provider.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,23 @@ namespace lean::crypto::xmss {

virtual XmssSignature sign(XmssPrivateKey xmss_private_key,
uint32_t epoch,
qtils::BytesIn message) = 0;
const XmssMessage &message) = 0;

virtual bool verify(XmssPublicKey xmss_public_key,
qtils::BytesIn message,
virtual bool verify(const XmssPublicKey &xmss_public_key,
const XmssMessage &message,
uint32_t epoch,
XmssSignature xmss_signature) = 0;
const XmssSignature &xmss_signature) = 0;

virtual XmssAggregatedSignature aggregateSignatures(
std::span<const XmssPublicKey> public_keys,
std::span<const XmssSignature> signatures,
uint32_t epoch,
const XmssMessage &message) const = 0;

virtual bool verifyAggregatedSignatures(
std::span<const XmssPublicKey> public_keys,
uint32_t epoch,
const XmssMessage &message,
const XmssAggregatedSignature &aggregated_signature) const = 0;
};
} // namespace lean::crypto::xmss
55 changes: 51 additions & 4 deletions src/crypto/xmss/xmss_provider_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ namespace lean::crypto::xmss {

XmssSignature XmssProviderImpl::sign(XmssPrivateKey xmss_private_key,
uint32_t epoch,
qtils::BytesIn message) {
const XmssMessage &message) {
// Sign the message
PQSignature *signature_raw = nullptr;
ffi::asOutcome(
Expand All @@ -71,10 +71,10 @@ namespace lean::crypto::xmss {
return signature_bytes;
}

bool XmssProviderImpl::verify(XmssPublicKey xmss_public_key,
qtils::BytesIn message,
bool XmssProviderImpl::verify(const XmssPublicKey &xmss_public_key,
const XmssMessage &message,
uint32_t epoch,
XmssSignature xmss_signature) {
const XmssSignature &xmss_signature) {
// Deserialize public key
PQPublicKey *public_key_raw = nullptr;
ffi::asOutcome(
Expand All @@ -100,4 +100,51 @@ namespace lean::crypto::xmss {
return verify_result == 1;
}

auto manyToRaw(const auto &items) {
std::vector<const uint8_t *> items_raw;
items_raw.reserve(items.size());
for (auto &item : items) {
items_raw.emplace_back(item.data());
}
return items_raw;
}

XmssAggregatedSignature XmssProviderImpl::aggregateSignatures(
std::span<const XmssPublicKey> public_keys,
std::span<const XmssSignature> signatures,
uint32_t epoch,
const XmssMessage &message) const {
if (public_keys.size() != signatures.size()) {
throw std::logic_error{
"XmssProviderImpl::aggregateSignatures public key and signature "
"count mismatch"};
}
auto public_keys_raw = manyToRaw(public_keys);
auto signatures_raw = manyToRaw(signatures);
auto ffi_bytevec = pq_aggregate_signatures(public_keys.size(),
public_keys_raw.data(),
signatures_raw.data(),
epoch,
message.data());
XmssAggregatedSignature aggregated_signature{std::span{
ffi_bytevec.ptr,
ffi_bytevec.size,
}};
PQByteVec_drop(ffi_bytevec);
return aggregated_signature;
}

bool XmssProviderImpl::verifyAggregatedSignatures(
std::span<const XmssPublicKey> public_keys,
uint32_t epoch,
const XmssMessage &message,
const XmssAggregatedSignature &aggregated_signature) const {
auto public_keys_raw = manyToRaw(public_keys);
return pq_verify_aggregated_signatures(public_keys.size(),
public_keys_raw.data(),
epoch,
message.data(),
aggregated_signature.data(),
aggregated_signature.size());
}
} // namespace lean::crypto::xmss
19 changes: 15 additions & 4 deletions src/crypto/xmss/xmss_provider_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,23 @@ namespace lean::crypto::xmss {

XmssSignature sign(XmssPrivateKey xmss_private_key,
uint32_t epoch,
qtils::BytesIn message) override;
const XmssMessage &message) override;

bool verify(XmssPublicKey xmss_public_key,
qtils::BytesIn message,
bool verify(const XmssPublicKey &xmss_public_key,
const XmssMessage &message,
uint32_t epoch,
XmssSignature xmss_signature) override;
const XmssSignature &xmss_signature) override;

XmssAggregatedSignature aggregateSignatures(
std::span<const XmssPublicKey> public_keys,
std::span<const XmssSignature> signatures,
uint32_t epoch,
const XmssMessage &message) const override;
bool verifyAggregatedSignatures(
std::span<const XmssPublicKey> public_keys,
uint32_t epoch,
const XmssMessage &message,
const XmssAggregatedSignature &aggregated_signature) const override;
};

} // namespace lean::crypto::xmss
8 changes: 4 additions & 4 deletions src/modules/networking/networking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -392,18 +392,18 @@ namespace lean::modules {
if (not self) {
return;
}
auto res = self->fork_choice_store_->onAttestation(signed_attestation,
false);
auto res =
self->fork_choice_store_->onGossipAttestation(signed_attestation);
if (not res.has_value()) {
SL_WARN(self->logger_,
"Error processing vote for target {}: {}",
signed_attestation.message.data.target,
signed_attestation.message.target,
res.error());
return;
}
SL_DEBUG(self->logger_,
"Received vote for target {}",
signed_attestation.message.data.target);
signed_attestation.message.target);
});

io_thread_.emplace([io_context{io_context_}] {
Expand Down
22 changes: 22 additions & 0 deletions src/types/aggregated_attestation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <sszpp/container.hpp>

#include "types/aggregation_bits.hpp"
#include "types/attestation_data.hpp"

namespace lean {
struct AggregatedAttestation : ssz::ssz_variable_size_container {
AggregationBits aggregation_bits;
AttestationData data;

SSZ_CONT(aggregation_bits, data);
bool operator==(const AggregatedAttestation &) const = default;
};
} // namespace lean
Loading
Loading