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
5 changes: 5 additions & 0 deletions bin/ream/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ version.workspace = true
name = "ream"
path = "src/main.rs"

[features]
default = ["devnet1"]
devnet1 = []
devnet2 = []

[dependencies]
alloy-primitives.workspace = true
anyhow.workspace = true
Expand Down
27 changes: 26 additions & 1 deletion bin/ream/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@ use ream_chain_lean::{
messages::LeanChainServiceMessage, p2p_request::LeanP2PRequest, service::LeanChainService,
};
use ream_checkpoint_sync::initialize_db_from_checkpoint;
#[cfg(feature = "devnet2")]
use ream_consensus_lean::attestation::AggregatedAttestations;
#[cfg(feature = "devnet1")]
use ream_consensus_lean::attestation::Attestation;
#[cfg(feature = "devnet2")]
use ream_consensus_lean::block::BlockSignatures;
use ream_consensus_lean::{
attestation::{Attestation, AttestationData},
attestation::AttestationData,
block::{BlockWithAttestation, SignedBlockWithAttestation},
checkpoint::Checkpoint,
validator::Validator,
Expand All @@ -61,6 +67,8 @@ use ream_p2p::{
},
network::lean::{LeanNetworkConfig, LeanNetworkService},
};
#[cfg(feature = "devnet2")]
use ream_post_quantum_crypto::leansig::signature::Signature;
use ream_post_quantum_crypto::leansig::{
private_key::PrivateKey as LeanSigPrivateKey, public_key::PublicKey,
};
Expand Down Expand Up @@ -217,6 +225,7 @@ pub async fn run_lean_node(config: LeanNodeConfig, executor: ReamExecutor, ream_
SignedBlockWithAttestation {
message: BlockWithAttestation {
block: genesis_block,
#[cfg(feature = "devnet1")]
proposer_attestation: Attestation {
validator_id: 0,
data: AttestationData {
Expand All @@ -226,8 +235,24 @@ pub async fn run_lean_node(config: LeanNodeConfig, executor: ReamExecutor, ream_
source: Checkpoint::default(),
},
},
#[cfg(feature = "devnet2")]
proposer_attestation: AggregatedAttestations {
validator_id: 0,
data: AttestationData {
slot: 0,
head: Checkpoint::default(),
target: Checkpoint::default(),
source: Checkpoint::default(),
},
},
},
#[cfg(feature = "devnet1")]
signature: VariableList::default(),
#[cfg(feature = "devnet2")]
signature: BlockSignatures {
attestation_signatures: VariableList::default(),
proposer_signature: Signature::blank(),
},
},
genesis_state,
lean_db,
Expand Down
5 changes: 5 additions & 0 deletions crates/common/chain/lean/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ repository.workspace = true
rust-version.workspace = true
version.workspace = true

[features]
default = ["devnet1"]
devnet1 = []
devnet2 = []

[dependencies]
alloy-primitives.workspace = true
anyhow.workspace = true
Expand Down
20 changes: 20 additions & 0 deletions crates/common/chain/lean/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ impl LeanChainService {
}
LeanChainServiceMessage::ProcessAttestation { signed_attestation, need_gossip } => {
if enabled!(Level::DEBUG) {
#[cfg(feature = "devnet1")]
debug!(
slot = signed_attestation.message.slot(),
head = ?signed_attestation.message.head(),
Expand All @@ -171,14 +172,33 @@ impl LeanChainService {
"Processing attestation by Validator {}",
signed_attestation.message.validator_id,
);
#[cfg(feature = "devnet2")]
debug!(
slot = signed_attestation.message.slot,
head = ?signed_attestation.message.head,
source = ?signed_attestation.message.source,
target = ?signed_attestation.message.target,
"Processing attestation by Validator {}",
signed_attestation.validator_id,
);
} else {
#[cfg(feature = "devnet1")]
info!(
slot = signed_attestation.message.slot(),
source_slot = signed_attestation.message.source().slot,
target_slot = signed_attestation.message.target().slot,
"Processing attestation by Validator {}",
signed_attestation.message.validator_id,
);
#[cfg(feature = "devnet2")]
debug!(
slot = signed_attestation.message.slot,
head = ?signed_attestation.message.head,
source = ?signed_attestation.message.source,
target = ?signed_attestation.message.target,
"Processing attestation by Validator {}",
signed_attestation.validator_id,
);
}

if let Err(err) = self.handle_process_attestation(*signed_attestation.clone()).await {
Expand Down
45 changes: 43 additions & 2 deletions crates/common/consensus/lean/src/attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use tree_hash_derive::TreeHash;
use crate::checkpoint::Checkpoint;

/// Attestation content describing the validator's observed chain view.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, Hash)]
pub struct AttestationData {
pub slot: u64,
pub head: Checkpoint,
Expand All @@ -17,12 +17,14 @@ pub struct AttestationData {
}

/// Validator specific attestation wrapping shared attestation data.
#[cfg(feature = "devnet1")]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct Attestation {
pub validator_id: u64,
pub data: AttestationData,
}

#[cfg(feature = "devnet1")]
impl Attestation {
/// Return the attested slot.
pub fn slot(&self) -> u64 {
Expand All @@ -45,6 +47,36 @@ impl Attestation {
}
}

#[cfg(feature = "devnet2")]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct AggregatedAttestations {
pub validator_id: u64,
pub data: AttestationData,
}

#[cfg(feature = "devnet2")]
impl AggregatedAttestation {
/// Return the attested slot.
pub fn slot(&self) -> u64 {
self.message.slot
}

/// Return the attested head checkpoint.
pub fn head(&self) -> Checkpoint {
self.message.head
}

/// Return the attested target checkpoint.
pub fn target(&self) -> Checkpoint {
self.message.target
}

/// Return the attested source checkpoint.
pub fn source(&self) -> Checkpoint {
self.message.source
}
}

/// Validator attestation bundled with its signature.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct SignedAttestation {
Expand Down Expand Up @@ -76,7 +108,6 @@ pub struct SignedAggregatedAttestation {

#[cfg(test)]
mod tests {

use alloy_primitives::hex;
use ssz::{Decode, Encode};

Expand All @@ -86,6 +117,7 @@ mod tests {
#[test]
fn test_encode_decode_signed_attestation_roundtrip() -> anyhow::Result<()> {
let signed_attestation = SignedAttestation {
#[cfg(feature = "devnet1")]
message: Attestation {
validator_id: 0,
data: AttestationData {
Expand All @@ -95,6 +127,15 @@ mod tests {
source: Checkpoint::default(),
},
},
#[cfg(feature = "devnet2")]
message: AttestationData {
slot: 1,
head: Checkpoint::default(),
target: Checkpoint::default(),
source: Checkpoint::default(),
},
#[cfg(feature = "devnet2")]
validator_id: 0,
signature: Signature {
inner: FixedBytes::default(),
},
Expand Down
115 changes: 112 additions & 3 deletions crates/common/consensus/lean/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ use ssz_types::{VariableList, typenum::U4096};
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;

use crate::{attestation::Attestation, state::LeanState};
#[cfg(feature = "devnet2")]
use crate::attestation::AggregatedAttestation;
#[cfg(feature = "devnet2")]
use crate::attestation::AggregatedAttestations;
#[cfg(feature = "devnet1")]
use crate::attestation::Attestation;
use crate::state::LeanState;

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct BlockSignatures {
Expand All @@ -34,18 +40,34 @@ impl SignedBlockWithAttestation {
) -> anyhow::Result<bool> {
let block = &self.message.block;
let signatures = &self.signature;
#[cfg(feature = "devnet1")]
let mut all_attestations = block.body.attestations.to_vec();
#[cfg(feature = "devnet2")]
let aggregated_attestations = &block.body.attestations;
#[cfg(feature = "devnet2")]
let attestation_signatures = &signatures.attestation_signatures;

#[cfg(feature = "devnet1")]
all_attestations.push(self.message.proposer_attestation.clone());

#[cfg(feature = "devnet1")]
ensure!(
signatures.len() == all_attestations.len(),
"Number of signatures {} does not match number of attestations {}",
signatures.len(),
all_attestations.len(),
);
#[cfg(feature = "devnet2")]
ensure!(
attestation_signatures.len() == aggregated_attestations.len(),
"Number of signatures {} does not match number of attestations {}",
attestation_signatures.len(),
aggregated_attestations.len(),
);

let validators = &parent_state.validators;

#[cfg(feature = "devnet1")]
for (attestation, signature) in all_attestations.iter().zip(signatures.iter()) {
ensure!(
attestation.validator_id < validators.len() as u64,
Expand All @@ -69,6 +91,73 @@ impl SignedBlockWithAttestation {
}
}

#[cfg(feature = "devnet2")]
{
let mut signature_iter = attestation_signatures.iter();
for aggregated_attestation in aggregated_attestations.iter() {
let validator_ids: Vec<usize> = aggregated_attestation
.aggregation_bits
.iter()
.enumerate()
.filter(|(_, bit)| *bit)
.map(|(index, _)| index)
.collect();

let attestation_root = aggregated_attestation.message.tree_hash_root();

for validator_id in validator_ids {
let signature = signature_iter.next().ok_or_else(|| {
anyhow!("Missing signature for validator index {validator_id}")
})?;

ensure!(
validator_id < validators.len(),
"Validator index out of range"
);

let validator = validators
.get(validator_id)
.ok_or_else(|| anyhow!("Failed to get validator"))?;

if verify_signatures {
let timer = start_timer(&PQ_SIGNATURE_ATTESTATION_VERIFICATION_TIME, &[]);
ensure!(
signature.verify(
&validator.public_key,
aggregated_attestation.message.slot as u32,
&attestation_root,
)?,
"Attestation signature verification failed"
);
stop_timer(timer);
}
}
}

let proposer_attestation = &self.message.proposer_attestation;
let proposer_signature = &signatures.proposer_signature;

ensure!(
proposer_attestation.validator_id < validators.len() as u64,
"Proposer index out of range"
);

let proposer = validators
.get(proposer_attestation.validator_id as usize)
.ok_or_else(|| anyhow!("Failed to get proposer validator"))?;

if verify_signatures {
ensure!(
proposer_signature.verify(
&proposer.public_key,
proposer_attestation.data.slot as u32,
&proposer_attestation.data.tree_hash_root(),
)?,
"Failed to verify"
);
}
}

Ok(true)
}
}
Expand All @@ -77,7 +166,10 @@ impl SignedBlockWithAttestation {
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct BlockWithAttestation {
pub block: Block,
#[cfg(feature = "devnet1")]
pub proposer_attestation: Attestation,
#[cfg(feature = "devnet2")]
pub proposer_attestation: AggregatedAttestations,
}

/// Represents a block in the Lean chain.
Expand Down Expand Up @@ -117,10 +209,10 @@ impl From<Block> for BlockHeader {
/// Represents the body of a block in the Lean chain.
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
pub struct BlockBody {
#[cfg(feature = "devnet2")]
pub attestations: VariableList<AggregatedAttestations, U4096>,
#[cfg(feature = "devnet1")]
pub attestations: VariableList<Attestation, U4096>,
#[cfg(feature = "devnet2")]
pub attestations: VariableList<AggregatedAttestation, U4096>,
}

#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)]
Expand Down Expand Up @@ -151,6 +243,7 @@ mod tests {
attestations: Default::default(),
},
},
#[cfg(feature = "devnet1")]
proposer_attestation: Attestation {
validator_id: 0,
data: AttestationData {
Expand All @@ -160,8 +253,24 @@ mod tests {
source: Checkpoint::default(),
},
},
#[cfg(feature = "devnet2")]
proposer_attestation: AggregatedAttestations {
validator_id: 0,
data: AttestationData {
slot: 0,
head: Checkpoint::default(),
target: Checkpoint::default(),
source: Checkpoint::default(),
},
},
},
#[cfg(feature = "devnet1")]
signature: VariableList::default(),
#[cfg(feature = "devnet2")]
signature: BlockSignatures {
attestation_signatures: VariableList::default(),
proposer_signature: Signature::blank(),
},
};

let encode = signed_block_with_attestation.as_ssz_bytes();
Expand Down
Loading