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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions lean_client/containers/src/attestation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::{Checkpoint, Slot, Uint64};
use ssz::ByteVector;
use ssz_derive::Ssz;
use serde::{Deserialize, Serialize};
use typenum::{Prod, U100, U31};

// Type-level number for 3100 bytes (signature size) = 31 * 100
pub type U3100 = Prod<U31, U100>;

// Type alias for Signature
pub type Signature = ByteVector<U3100>;

// Type-level number for 4096 (validator registry limit)
use typenum::U4096;

/// List of validator attestations included in a block (without signatures).
/// Limit is VALIDATOR_REGISTRY_LIMIT (4096).
pub type Attestations = ssz::PersistentList<Attestation, U4096>;

/// List of signatures corresponding to attestations in a block.
/// Limit is VALIDATOR_REGISTRY_LIMIT (4096).
pub type BlockSignatures = ssz::PersistentList<Signature, U4096>;

/// Bitlist representing validator participation in an attestation.
/// Limit is VALIDATOR_REGISTRY_LIMIT (4096).
pub type AggregationBits = ssz::BitList<U4096>;

/// Naive list of validator signatures used for aggregation placeholders.
/// Limit is VALIDATOR_REGISTRY_LIMIT (4096).
pub type AggregatedSignatures = ssz::PersistentList<Signature, U4096>;

/// Attestation content describing the validator's observed chain view.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct AttestationData {
/// The slot for which the attestation is made.
pub slot: Slot,
/// The checkpoint representing the head block as observed by the validator.
pub head: Checkpoint,
/// The checkpoint representing the target block as observed by the validator.
pub target: Checkpoint,
/// The checkpoint representing the source block as observed by the validator.
pub source: Checkpoint,
}

/// Validator specific attestation wrapping shared attestation data.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct Attestation {
/// The index of the validator making the attestation.
pub validator_id: Uint64,
/// The attestation data produced by the validator.
pub data: AttestationData,
}

/// Validator attestation bundled with its signature.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct SignedAttestation {
/// The attestation message signed by the validator.
pub message: Attestation,
/// Signature aggregation produced by the leanVM (SNARKs in the future).
pub signature: Signature,
}

/// Aggregated attestation consisting of participation bits and message.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct AggregatedAttestations {
/// Bitfield indicating which validators participated in the aggregation.
pub aggregation_bits: AggregationBits,
/// Combined attestation data similar to the beacon chain format.
///
/// Multiple validator attestations are aggregated here without the complexity of
/// committee assignments.
pub data: AttestationData,
}

/// Aggregated attestation bundled with aggregated signatures.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct SignedAggregatedAttestations {
/// Aggregated attestation data.
pub message: AggregatedAttestations,
/// Aggregated attestation plus its combined signature.
///
/// Stores a naive list of validator signatures that mirrors the attestation
/// order.
///
/// TODO: this will be replaced by a SNARK in future devnets.
pub signature: AggregatedSignatures,
}
33 changes: 28 additions & 5 deletions lean_client/containers/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use crate::{Bytes32, Slot, SignedVote, ValidatorIndex, U4000};
use ssz::{ByteVector, PersistentList as List};
use crate::{Attestation, Attestations, BlockSignatures, Bytes32, Slot, ValidatorIndex, Signature};
use ssz_derive::Ssz;
use serde::{Deserialize, Serialize};
use typenum::U4096;

/// The body of a block, containing payload data.
///
/// Attestations are stored WITHOUT signatures. Signatures are aggregated
/// separately in BlockSignatures to match the spec architecture.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct BlockBody {
#[serde(with = "crate::serde_helpers")]
pub attestations: List<SignedVote, U4096>,
pub attestations: Attestations,
}

#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
Expand All @@ -29,10 +31,31 @@ pub struct Block {
pub body: BlockBody,
}

/// Bundle containing a block and the proposer's attestation.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct BlockWithAttestation {
/// The proposed block message.
pub block: Block,
/// The proposer's attestation corresponding to this block.
pub proposer_attestation: Attestation,
}

/// Envelope carrying a block, an attestation from proposer, and aggregated signatures.
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct SignedBlockWithAttestation {
/// The block plus an attestation from proposer being signed.
pub message: BlockWithAttestation,
/// Aggregated signature payload for the block.
///
/// Signatures remain in attestation order followed by the proposer signature.
pub signature: BlockSignatures,
}

/// Legacy signed block structure (kept for backwards compatibility).
#[derive(Clone, Debug, PartialEq, Eq, Ssz, Default, Serialize, Deserialize)]
pub struct SignedBlock {
pub message: Block,
pub signature: ByteVector<U4000>,
pub signature: Signature,
}

/// Compute the SSZ hash tree root for any type implementing `SszHash`.
Expand Down
13 changes: 9 additions & 4 deletions lean_client/containers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ pub mod types;
pub mod config;
pub mod slot;
pub mod checkpoint;
pub mod vote;
pub mod attestation;
pub mod block;
pub mod state;
pub mod validator;
pub mod serde_helpers;

pub use block::{Block, BlockBody, BlockHeader, SignedBlock};
pub use attestation::{
Attestation, AttestationData, SignedAttestation,
AggregatedAttestations, SignedAggregatedAttestations,
AggregationBits, AggregatedSignatures, Signature,
Attestations, BlockSignatures,
};
pub use block::{Block, BlockBody, BlockHeader, SignedBlock, BlockWithAttestation, SignedBlockWithAttestation};
pub use checkpoint::Checkpoint;
pub use config::Config as ContainerConfig;
pub use slot::Slot;
pub use state::State;
pub use types::{Bytes32, Uint64, ValidatorIndex, HistoricalBlockHashes, JustificationRoots, JustifiedSlots, JustificationsValidators, U4000};
pub use vote::{SignedVote, Vote};
pub use types::{Bytes32, Uint64, ValidatorIndex, HistoricalBlockHashes, JustificationRoots, JustifiedSlots, JustificationsValidators};
pub use ssz;
38 changes: 19 additions & 19 deletions lean_client/containers/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Bytes32, Checkpoint, ContainerConfig, Slot, Uint64, ValidatorIndex, block::{Block, BlockBody, BlockHeader, SignedBlock, hash_tree_root}, SignedVote};
use crate::{Attestation, Attestations, Bytes32, Checkpoint, ContainerConfig, Slot, Uint64, ValidatorIndex, block::{Block, BlockBody, BlockHeader, SignedBlock, hash_tree_root}};
use crate::{HistoricalBlockHashes, JustificationRoots, JustifiedSlots, JustificationsValidators};
use crate::validator::Validator;
use ssz::PersistentList as List;
Expand Down Expand Up @@ -281,19 +281,19 @@ impl State {
self.process_attestations(&body.attestations)
}

pub fn process_attestations(&self, attestations: &List<SignedVote, typenum::U4096>) -> Self {
pub fn process_attestations(&self, attestations: &Attestations) -> Self {
let mut justifications = self.get_justifications();
let mut latest_justified = self.latest_justified.clone();
let mut latest_finalized = self.latest_finalized.clone();
let justified_slots = self.justified_slots.clone();

// PersistentList doesn't expose iter; convert to Vec for simple iteration for now
// Build a temporary Vec by probing sequentially until index error
let mut votes_vec: Vec<SignedVote> = Vec::new();
let mut attestations_vec: Vec<Attestation> = Vec::new();
let mut i: u64 = 0;
loop {
match attestations.get(i) {
Ok(v) => votes_vec.push(v.clone()),
Ok(a) => attestations_vec.push(a.clone()),
Err(_) => break,
}
i += 1;
Expand All @@ -305,12 +305,12 @@ impl State {
justified_slots_working.push(justified_slots.get(i).map(|b| *b).unwrap_or(false));
}

for signed_vote in votes_vec.iter() {
let vote = signed_vote.message.clone();
let target_slot = vote.target.slot;
let source_slot = vote.source.slot;
let target_root = vote.target.root;
let source_root = vote.source.root;
for attestation in attestations_vec.iter() {
let attestation_data = attestation.data.clone();
let target_slot = attestation_data.target.slot;
let source_slot = attestation_data.source.slot;
let target_root = attestation_data.target.root;
let source_root = attestation_data.source.root;

let target_slot_int = target_slot.0 as usize;
let source_slot_int = source_slot.0 as usize;
Expand All @@ -334,24 +334,24 @@ impl State {
let target_is_after_source = target_slot > source_slot;
let target_is_justifiable = target_slot.is_justifiable_after(latest_finalized.slot);

let is_valid_vote = source_is_justified &&
let is_valid_attestation = source_is_justified &&
!target_already_justified &&
source_root_matches_history &&
target_root_is_valid &&
target_is_after_source &&
target_is_justifiable;

if !is_valid_vote { continue; }
if !is_valid_attestation { continue; }

if !justifications.contains_key(&target_root) {
let limit = VALIDATOR_REGISTRY_LIMIT;
justifications.insert(target_root, vec![false; limit]);
}

let validator_id = signed_vote.validator_id.0 as usize;
if let Some(votes) = justifications.get_mut(&target_root) {
if validator_id < votes.len() && !votes[validator_id] {
votes[validator_id] = true;
let validator_id = attestation.validator_id.0 as usize;
if let Some(attestation_votes) = justifications.get_mut(&target_root) {
if validator_id < attestation_votes.len() && !attestation_votes[validator_id] {
attestation_votes[validator_id] = true;

// Count validators
let mut num_validators: u64 = 0;
Expand All @@ -366,9 +366,9 @@ impl State {
}
}

let count = votes.iter().filter(|&&v| v).count();
let count = attestation_votes.iter().filter(|&&v| v).count();
if 3 * count >= 2 * num_validators as usize {
latest_justified = vote.target;
latest_justified = attestation_data.target;

// Extend justified_slots_working if needed
while justified_slots_working.len() <= target_slot_int {
Expand All @@ -387,7 +387,7 @@ impl State {
}

if is_finalizable {
latest_finalized = vote.source;
latest_finalized = attestation_data.source;
}
}
}
Expand Down
20 changes: 0 additions & 20 deletions lean_client/containers/src/vote.rs

This file was deleted.

18 changes: 9 additions & 9 deletions lean_client/containers/tests/unit_tests/common.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use containers::{
Attestations,
block::{Block, BlockBody, BlockHeader, SignedBlock, hash_tree_root},
checkpoint::Checkpoint,
ContainerConfig,
slot::Slot,
state::State,
types::{Bytes32, ValidatorIndex},
vote::SignedVote,
};
use ssz::PersistentList as List;
use typenum::U4096;

pub const DEVNET_CONFIG_VALIDATOR_REGISTRY_LIMIT: usize = 1 << 12; // 4096

pub fn create_block(slot: u64, parent_header: &mut BlockHeader, votes: Option<List<SignedVote, U4096>>) -> SignedBlock {
pub fn create_block(slot: u64, parent_header: &mut BlockHeader, attestations: Option<Attestations>) -> SignedBlock {
let body = BlockBody {
attestations: votes.unwrap_or_else(List::default),
attestations: attestations.unwrap_or_else(List::default),
};

let block_message = Block {
Expand All @@ -27,18 +27,18 @@ pub fn create_block(slot: u64, parent_header: &mut BlockHeader, votes: Option<Li

SignedBlock {
message: block_message,
signature: Bytes32(ssz::H256::zero()),
signature: ssz::ByteVector::default(),
}
}

pub fn create_votes(indices: &[usize]) -> Vec<bool> {
let mut votes = vec![false; DEVNET_CONFIG_VALIDATOR_REGISTRY_LIMIT];
pub fn create_attestations(indices: &[usize]) -> Vec<bool> {
let mut attestations = vec![false; DEVNET_CONFIG_VALIDATOR_REGISTRY_LIMIT];
for &index in indices {
if index < votes.len() {
votes[index] = true;
if index < attestations.len() {
attestations[index] = true;
}
}
votes
attestations
}

pub fn sample_block_header() -> BlockHeader {
Expand Down
16 changes: 8 additions & 8 deletions lean_client/containers/tests/unit_tests/state_justifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use ssz::PersistentList as List;
#[path = "common.rs"]
mod common;
use common::{
base_state, create_votes, sample_config, DEVNET_CONFIG_VALIDATOR_REGISTRY_LIMIT,
base_state, create_attestations, sample_config, DEVNET_CONFIG_VALIDATOR_REGISTRY_LIMIT,
};

#[fixture]
Expand Down Expand Up @@ -173,26 +173,26 @@ fn test_with_justifications_invalid_length() {
#[case::empty_justifications(std::collections::BTreeMap::new())]
#[case::single_root({
let mut map = std::collections::BTreeMap::new();
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_votes(&[0]));
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_attestations(&[0]));
map
})]
#[case::multiple_roots_sorted({
let mut map = std::collections::BTreeMap::new();
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_votes(&[0]));
map.insert(Bytes32(ssz::H256::from_slice(&[2u8; 32])), create_votes(&[1, 2]));
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_attestations(&[0]));
map.insert(Bytes32(ssz::H256::from_slice(&[2u8; 32])), create_attestations(&[1, 2]));
map
})]
#[case::multiple_roots_unsorted({
let mut map = std::collections::BTreeMap::new();
map.insert(Bytes32(ssz::H256::from_slice(&[2u8; 32])), create_votes(&[1, 2]));
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_votes(&[0]));
map.insert(Bytes32(ssz::H256::from_slice(&[2u8; 32])), create_attestations(&[1, 2]));
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_attestations(&[0]));
map
})]
#[case::complex_unsorted({
let mut map = std::collections::BTreeMap::new();
map.insert(Bytes32(ssz::H256::from_slice(&[3u8; 32])), vec![true; DEVNET_CONFIG_VALIDATOR_REGISTRY_LIMIT]);
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_votes(&[0]));
map.insert(Bytes32(ssz::H256::from_slice(&[2u8; 32])), create_votes(&[1, 2]));
map.insert(Bytes32(ssz::H256::from_slice(&[1u8; 32])), create_attestations(&[0]));
map.insert(Bytes32(ssz::H256::from_slice(&[2u8; 32])), create_attestations(&[1, 2]));
map
})]
fn test_justifications_roundtrip(
Expand Down
Loading