Skip to content
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
lean_client/target/*
lean_client/tests/mainnet
lean_client/bin
lean_client/spec/
target/*
*.local*

Expand Down
2 changes: 2 additions & 0 deletions lean_client/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 18 additions & 5 deletions lean_client/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
### Variables

# Commit of lean specification. Allows to specify which spec version to use,
# when generating test vectors. Pinned to leanSpec PR #682 merge commit.
LEAN_SPEC_COMMIT ?= 62eff6e7e6041a283877a546a07cb3b83f4f7d5b
# when generating test vectors.
LEAN_SPEC_COMMIT ?= f12000bd68a9640cffdfbd9a07503c9112d32bee
# Docker image name. Used for both release (with publish) and local builds.
DOCKER_REPO ?= sifrai/lean
# Image tag.
Expand Down Expand Up @@ -56,9 +56,22 @@ generate-test-vectors:
git remote add origin https://github.com/leanEthereum/leanSpec.git && \
git fetch --depth 1 origin $(LEAN_SPEC_COMMIT) && \
git switch --detach FETCH_HEAD
cd spec && uv run fill --clean --fork=devnet --scheme prod || echo "run failed"
rm -rf ./test_vectors && mkdir -p ./test_vectors
cp -r ./spec/fixtures/consensus/* ./test_vectors/
# Pre-download prod keys. The pinned leanSpec commit predates leanSpec PR #745,
# whose `download_keys` reads the still-open release URL. Remove once the pin
# moves past PR #745.
cd spec && \
KEYS_URL=$$(uv run python -c "from consensus_testing.keys import KEY_DOWNLOAD_URLS; print(KEY_DOWNLOAD_URLS['prod'])") && \
KEYS_DIR=packages/testing/src/consensus_testing/test_keys && \
mkdir -p $$KEYS_DIR && \
curl -sSL "$$KEYS_URL" -o /tmp/prod_scheme.tar.gz && \
tar -xzf /tmp/prod_scheme.tar.gz -C $$KEYS_DIR
cd spec && uv run fill --clean --fork=Lstar --scheme prod || echo "run failed"
@for d in ./spec/fixtures/consensus/*/; do \
f=$$(basename $$d); \
rm -rf ./test_vectors/$$f/devnet ./test_vectors/$$f/lstar; \
mkdir -p ./test_vectors/$$f/lstar; \
cp -R $$d/lstar/. ./test_vectors/$$f/lstar/; \
done
Comment on lines +68 to +74

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why this directory re-mapping is necessary? I was thinking that we may keep the same directory structure, as lean spec has?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only consensus/ emits the JSON fixtures we can actually consume interop/ and node/ are Python pytest suites that test the spec's own reference node, so there's nothing there for us to import.


.PHONY: build
build:
Expand Down
1 change: 1 addition & 0 deletions lean_client/containers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ xmss = { workspace = true }
pretty_assertions = { workspace = true }
rstest = { workspace = true }
serde_json = { workspace = true }
spec_test_fixtures = { workspace = true }
test-generator = { workspace = true }
18 changes: 6 additions & 12 deletions lean_client/containers/src/attestation.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use ssz::{BitList, H256, PersistentList, Ssz, SszHash};
use std::collections::HashSet;
use typenum::{U4096, Unsigned as _};
Expand All @@ -19,8 +18,7 @@ pub type AttestationSignatures = PersistentList<AggregatedSignatureProof, Valida
///
/// Combines the participant bitfield with the proof bytes.
/// Used in `aggregated_payloads` to track which validators are covered by each proof.
#[derive(Clone, Debug, Ssz, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Ssz)]
pub struct AggregatedSignatureProof {
/// Bitfield indicating which validators' signatures are included.
pub participants: AggregationBits,
Expand Down Expand Up @@ -136,11 +134,9 @@ impl AggregatedSignatureProof {

/// Bitlist representing validator participation in an attestation.
/// Limit is VALIDATOR_REGISTRY_LIMIT (4096).
#[derive(Clone, Debug, Ssz, Serialize, Deserialize)]
#[derive(Clone, Debug, Ssz)]
#[ssz(transparent)]
pub struct AggregationBits(
#[serde(with = "crate::serde_helpers::bitlist")] pub BitList<ValidatorRegistryLimit>,
);
pub struct AggregationBits(pub BitList<ValidatorRegistryLimit>);

impl AggregationBits {
pub const LIMIT: u64 = ValidatorRegistryLimit::U64;
Expand Down Expand Up @@ -190,7 +186,7 @@ pub type AggregatedSignatures = ssz::PersistentList<Signature, U4096>;
/// Attestation content describing the validator's observed chain view.
///
/// todo(containers): default implementation doesn't make sense here
#[derive(Clone, Debug, Ssz, PartialEq, Eq, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, Ssz, PartialEq, Eq, Default)]
pub struct AttestationData {
/// The slot for which the attestation is made.
pub slot: Slot,
Expand Down Expand Up @@ -225,8 +221,7 @@ impl SignatureKey {
/// Validator specific attestation wrapping shared attestation data.
///
/// todo(containers): default implementation doesn't make sense here
#[derive(Clone, Debug, Ssz, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Ssz, Default, PartialEq, Eq)]
pub struct Attestation {
/// The index of the validator making the attestation.
pub validator_id: u64,
Expand All @@ -243,8 +238,7 @@ pub struct SignedAttestation {
}

/// Aggregated attestation consisting of participation bits and message.
#[derive(Clone, Debug, Ssz, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Ssz)]
pub struct AggregatedAttestation {
/// Bitfield indicating which validators participated in the aggregation.
pub aggregation_bits: AggregationBits,
Expand Down
18 changes: 5 additions & 13 deletions lean_client/containers/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::{Slot, State};
use anyhow::{Context, Result, ensure};
use metrics::METRICS;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use ssz::{H256, Ssz, SszHash};
use xmss::Signature;

Expand All @@ -13,14 +12,12 @@ use crate::attestation::{AggregatedAttestations, AttestationSignatures};
/// Attestations are stored WITHOUT signatures. Signatures are aggregated
/// separately in BlockSignatures to match the spec architecture.
// todo(containers): default implementation doesn't make sense here.
#[derive(Clone, Debug, Ssz, Serialize, Deserialize, Default)]
#[derive(Clone, Debug, Ssz, Default)]
pub struct BlockBody {
#[serde(with = "crate::serde_helpers::aggregated_attestations")]
pub attestations: AggregatedAttestations,
}

#[derive(Clone, Debug, Ssz, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Ssz)]
pub struct BlockHeader {
pub slot: Slot,
pub proposer_index: u64,
Expand All @@ -29,8 +26,7 @@ pub struct BlockHeader {
pub body_root: H256,
}

#[derive(Clone, Debug, Ssz, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Ssz)]
pub struct Block {
pub slot: Slot,
pub proposer_index: u64,
Expand All @@ -40,23 +36,19 @@ pub struct Block {
}

// todo(containers): default implementation doesn't make sense here
#[derive(Debug, Clone, Ssz, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[derive(Debug, Clone, Ssz, Default)]
pub struct BlockSignatures {
#[serde(with = "crate::serde_helpers::attestation_signatures")]
pub attestation_signatures: AttestationSignatures,
pub proposer_signature: Signature,
}

/// Signed block for devnet4: block body + aggregated signatures.
/// Proposer attestation is no longer embedded in the block message.
#[derive(Clone, Debug, Ssz, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, Ssz)]
pub struct SignedBlock {
/// The proposed block.
pub block: Block,
/// Aggregated signature payload (attestation proofs + proposer signature).
#[serde(alias = "signatures")]
pub signature: BlockSignatures,
}

Expand Down
3 changes: 1 addition & 2 deletions lean_client/containers/src/checkpoint.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::Slot;
use serde::{Deserialize, Serialize};
use ssz::{H256, Ssz};

/// Represents a checkpoint in the chain's history.
///
/// A checkpoint marks a specific moment in the chain. It combines a block
/// identifier with a slot number. Checkpoints are used for justification and
/// finalization.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default)]
pub struct Checkpoint {
/// The root hash of the checkpoint's block.
pub root: H256,
Expand Down
5 changes: 3 additions & 2 deletions lean_client/containers/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ fn default_attestation_committee_count() -> u64 {
1
}

#[derive(Clone, Debug, PartialEq, Eq, Ssz, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default)]
pub struct Config {
pub genesis_time: u64,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GenesisValidatorEntry {
#[serde(alias = "attestationPubkey")]
pub attestation_pubkey: String,
#[serde(alias = "proposalPubkey")]
pub proposal_pubkey: String,
}

Expand Down
1 change: 0 additions & 1 deletion lean_client/containers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ mod attestation;
mod block;
mod checkpoint;
mod config;
mod serde_helpers;
mod slot;
mod state;
mod status;
Expand Down
Loading
Loading