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
270 changes: 244 additions & 26 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ clap = { version = "4.3", features = ["derive", "env"] }
# TODO: switch to ethrex implementation when available
ethereum-types = { version = "0.15.1", features = ["serialize"] }

# XMSS signatures
leansig = { git = "https://github.com/leanEthereum/leansig.git", rev = "ae12a5feb25d917c42b6466444ebd56ec115a629" }

# SSZ deps
# TODO: roll up our own implementation
ethereum_ssz_derive = "0.8.3"
Expand Down
2 changes: 2 additions & 0 deletions crates/common/types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ thiserror.workspace = true
serde.workspace = true
ethereum-types.workspace = true

leansig.workspace = true

ethereum_ssz_derive = "0.10.0"
ethereum_ssz = "0.10.0"
ssz_types = "0.14.0"
Expand Down
44 changes: 44 additions & 0 deletions crates/common/types/src/attestation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::U4096;
use tree_hash_derive::TreeHash;

use crate::{signature::Signature, state::Checkpoint};

/// Validator specific attestation wrapping shared attestation data.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct Attestation {
/// The index of the validator making the attestation.
pub validator_id: u64,

/// The attestation data produced by the validator.
pub data: AttestationData,
}

/// Attestation content describing the validator's observed chain view.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct AttestationData {
/// The slot for which the attestation is made.
pub slot: u64,

/// 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,
}

/// List of validator attestations included in a block.
/// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`].
pub type Attestations = ssz_types::VariableList<Attestation, U4096>;

/// Validator attestation bundled with its signature.
#[derive(Clone, Encode, Decode)]
pub struct SignedAttestation {
/// The attestation message signed by the validator.
message: Attestation,
/// Signature aggregation produced by the leanVM (SNARKs in the future).
signature: Signature,
}
85 changes: 81 additions & 4 deletions crates/common/types/src/block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
use crate::primitives::H256;
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::{Diff, U488, U3600, U4096};
use tree_hash_derive::TreeHash;

use crate::state::Slot;
use crate::{
attestation::{Attestation, Attestations},
primitives::H256,
signature::{Signature, SignatureSize},
};

/// Envelope carrying a block, an attestation from proposer, and aggregated signatures.
#[derive(Clone, Encode, Decode)]
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
/// over entire message. For devnet 1, however the proposer signature is just
/// over message.proposer_attestation since leanVM is not yet performant enough
/// to aggregate signatures with sufficient throughput.
///
/// Eventually this field will be replaced by a SNARK (which represents the
/// aggregation of all signatures).
pub signature: BlockSignatures,
}

// Manual Debug impl because leanSig signatures don't implement Debug.
impl core::fmt::Debug for SignedBlockWithAttestation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SignedBlockWithAttestation")
.field("message", &self.message)
.field("signature", &"...")
.finish()
}
}

/// Aggregated signature list included alongside the block.
/// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`].
pub type BlockSignatures =
ssz_types::VariableList<ssz_types::FixedVector<u8, SignatureSize>, U4096>;

/// Bundle containing a block and the proposer's attestation.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct BlockWithAttestation {
/// The proposed block message.
pub block: Block,

/// The proposer's attestation corresponding to this block.
pub proposer_attestation: Attestation,
}

/// The header of a block, containing metadata.
///
Expand All @@ -10,10 +59,10 @@ use crate::state::Slot;
///
/// Headers are smaller than full blocks. They're useful for tracking the chain
/// without storing everything.
#[derive(Debug)]
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct BlockHeader {
/// The slot in which the block was proposed
pub slot: Slot,
pub slot: u64,
/// The index of the validator that proposed the block
pub proposer_index: u64,
/// The root of the parent block
Expand All @@ -23,3 +72,31 @@ pub struct BlockHeader {
/// The root of the block body
pub body_root: H256,
}

/// A complete block including header and body.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct Block {
/// The slot in which the block was proposed.
pub slot: u64,
/// The index of the validator that proposed the block.
pub proposer_index: u64,
/// The root of the parent block.
pub parent_root: H256,
/// The root of the state after applying transactions in this block.
pub state_root: H256,
/// The block's payload.
pub body: BlockBody,
}

/// The body of a block, containing payload data.
///
/// Currently, the main operation is voting. Validators submit attestations which are
/// packaged into blocks.
#[derive(Debug, Clone, Encode, Decode, TreeHash)]
pub struct BlockBody {
/// Plain validator attestations carried in the block body.
///
/// Individual signatures live in the aggregated block signature list, so
/// these entries contain only attestation data without per-attestation signatures.
pub attestations: Attestations,
}
2 changes: 2 additions & 0 deletions crates/common/types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod attestation;
pub mod block;
pub mod genesis;
pub mod primitives;
pub mod signature;
pub mod state;
10 changes: 10 additions & 0 deletions crates/common/types/src/signature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use leansig::signature::SignatureScheme;
use ssz_types::typenum::{Diff, U488, U3600};

type LeanSignatureScheme = leansig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8;

type LeanSigSignature = <LeanSignatureScheme as SignatureScheme>::Signature;

pub type Signature = LeanSigSignature;

pub type SignatureSize = Diff<U3600, U488>;
12 changes: 6 additions & 6 deletions crates/common/types/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ use tree_hash_derive::TreeHash;

use crate::{block::BlockHeader, genesis::Genesis, primitives::H256};

#[derive(Debug)]
pub struct Slot(u64);
/// The maximum number of validators that can be in the registry.
pub const VALIDATOR_REGISTRY_LIMIT: u64 = 4096; // 2 ** 12

/// The main consensus state object
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct State {
/// The chain's configuration parameters
config: NetworkConfig,
/// The current slot number
slot: Slot,
slot: u64,
/// The header of the most recent block
latest_block_header: BlockHeader,
/// The latest justified checkpoint
Expand All @@ -36,9 +36,9 @@ impl State {
pub fn from_genesis(genesis: &Genesis) -> Self {
State {
config: genesis.config.clone(),
slot: Slot(0),
slot: 0,
latest_block_header: BlockHeader {
slot: Slot(0),
slot: 0,
proposer_index: 0,
parent_root: H256::ZERO,
state_root: H256::ZERO,
Expand Down
4 changes: 4 additions & 0 deletions crates/net/p2p/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,9 @@ ssz_types = "0.14.0"
tree_hash = "0.12.0"
tree_hash_derive = "0.12.0"

sha2 = "0.10"

hex = "0.4"

[dev-dependencies]
hex = "0.4"
52 changes: 52 additions & 0 deletions crates/net/p2p/src/gossipsub.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use ethlambda_types::block::SignedBlockWithAttestation;
use libp2p::gossipsub::Event;
use ssz::Decode;
use tracing::{error, info, trace};

/// Topic kind for block gossip
pub const BLOCK_TOPIC_KIND: &str = "block";
/// Topic kind for attestation gossip
pub const ATTESTATION_TOPIC_KIND: &str = "attestation";

pub async fn handle_gossipsub_message(event: Event) {
let Event::Message {
propagation_source: _,
message_id: _,
message,
} = event
else {
unreachable!("we already matched on event_loop");
};
match message.topic.as_str().split("/").nth(3) {
Some(BLOCK_TOPIC_KIND) => {
let Ok(uncompressed_data) = decompress_message(&message.data)
.inspect_err(|err| error!(%err, "Failed to decompress gossipped block"))
else {
return;
};

let Ok(signed_block) = SignedBlockWithAttestation::from_ssz_bytes(&uncompressed_data)
.inspect_err(|err| error!(?err, "Failed to decode gossipped block"))
else {
return;
};
info!(slot=%signed_block.message.block.slot, "Received new block");
}
Some(ATTESTATION_TOPIC_KIND) => {
info!(
"Received attestation gossip message of size {} bytes",
message.data.len()
);
}
_ => {
trace!("Received message on unknown topic: {}", message.topic);
}
}
}

fn decompress_message(data: &[u8]) -> snap::Result<Vec<u8>> {
let uncompressed_size = snap::raw::decompress_len(&data)?;
let mut uncompressed_data = vec![0u8; uncompressed_size];
snap::raw::Decoder::new().decompress(&data, &mut uncompressed_data)?;
Ok(uncompressed_data)
}
Loading