From 38b20e873ec2901d58fd4a484a996d3abe4d535a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:03:26 -0300 Subject: [PATCH 01/10] feat: compute message ID --- Cargo.lock | 1 + crates/net/p2p/Cargo.toml | 2 ++ crates/net/p2p/src/lib.rs | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ea611765..2558ed42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1752,6 +1752,7 @@ dependencies = [ "ethrex-rlp", "hex", "libp2p", + "sha2", "snap", "ssz_types", "tracing", diff --git a/crates/net/p2p/Cargo.toml b/crates/net/p2p/Cargo.toml index 369147e6..9a0e8ebd 100644 --- a/crates/net/p2p/Cargo.toml +++ b/crates/net/p2p/Cargo.toml @@ -32,5 +32,7 @@ ssz_types = "0.14.0" tree_hash = "0.12.0" tree_hash_derive = "0.12.0" +sha2 = "0.10" + [dev-dependencies] hex = "0.4" diff --git a/crates/net/p2p/src/lib.rs b/crates/net/p2p/src/lib.rs index bde9c565..360fc69b 100644 --- a/crates/net/p2p/src/lib.rs +++ b/crates/net/p2p/src/lib.rs @@ -12,6 +12,7 @@ use libp2p::{ request_response::{self, Event, Message}, swarm::{NetworkBehaviour, SwarmEvent}, }; +use sha2::Digest; use tracing::{info, trace}; use crate::messages::status::{STATUS_PROTOCOL_V1, Status}; @@ -35,12 +36,14 @@ pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { // seen_ttl_secs = seconds_per_slot * justification_lookback_slots * 2 .duplicate_cache_time(Duration::from_secs(4 * 3 * 2)) .validation_mode(ValidationMode::Anonymous) + .message_id_fn(compute_message_id) .build() .expect("invalid gossipsub config"); // TODO: setup custom message ID function let gossipsub = libp2p::gossipsub::Behaviour::new(MessageAuthenticity::Anonymous, config) .expect("failed to initiate behaviour"); + let req_resp = request_response::Behaviour::new( vec![( StreamProtocol::new(STATUS_PROTOCOL_V1), @@ -214,3 +217,24 @@ pub fn parse_validators_file(bootnodes_path: &str) -> Vec { } bootnodes } + +fn compute_message_id(message: &gossipsub::Message) -> gossipsub::MessageId { + const MESSAGE_DOMAIN_INVALID_SNAPPY: [u8; 4] = [0x00, 0x00, 0x00, 0x00]; + const MESSAGE_DOMAIN_VALID_SNAPPY: [u8; 4] = [0x01, 0x00, 0x00, 0x00]; + + let mut hasher = sha2::Sha256::new(); + let decompressed = snap::raw::Decoder::new().decompress_vec(&message.data); + + let (domain, data) = match decompressed.as_ref() { + Ok(decompressed_data) => (MESSAGE_DOMAIN_VALID_SNAPPY, decompressed_data), + Err(_) => (MESSAGE_DOMAIN_INVALID_SNAPPY, &message.data), + }; + let topic = message.topic.as_str().as_bytes(); + let topic_len = (topic.len() as u64).to_be_bytes(); + hasher.update(&domain); + hasher.update(&topic_len); + hasher.update(topic); + hasher.update(data); + let hash = hasher.finalize(); + gossipsub::MessageId(hash[..20].to_vec()) +} From 0b65f75b195e3bbfc53786e80e8e0fe33b123940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:23:11 -0300 Subject: [PATCH 02/10] feat: subscribe to gossipsub topics --- crates/net/p2p/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/net/p2p/src/lib.rs b/crates/net/p2p/src/lib.rs index 360fc69b..1f1e0118 100644 --- a/crates/net/p2p/src/lib.rs +++ b/crates/net/p2p/src/lib.rs @@ -96,6 +96,14 @@ pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { .listen_on(addr) .expect("failed to bind gossipsub listening address"); + let topic_kinds = ["block", "attestation"]; + let network = "devnet0"; + for topic_kind in topic_kinds { + let topic_str = format!("/leanconsensus/{network}/{topic_kind}/ssz_snappy"); + let topic = gossipsub::IdentTopic::new(topic_str); + swarm.behaviour_mut().gossipsub.subscribe(&topic).unwrap(); + } + println!("P2P node started on port {listening_port}"); event_loop(swarm).await; From ec37ebfee340cbdcc40677f234707d27d0d615db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:27:56 -0300 Subject: [PATCH 03/10] feat: receive block and attestation gossip --- crates/net/p2p/src/lib.rs | 66 ++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/crates/net/p2p/src/lib.rs b/crates/net/p2p/src/lib.rs index 1f1e0118..55b73085 100644 --- a/crates/net/p2p/src/lib.rs +++ b/crates/net/p2p/src/lib.rs @@ -9,7 +9,7 @@ use libp2p::{ gossipsub::{self, MessageAuthenticity, ValidationMode}, identity::{PublicKey, secp256k1}, multiaddr::Protocol, - request_response::{self, Event, Message}, + request_response, swarm::{NetworkBehaviour, SwarmEvent}, }; use sha2::Digest; @@ -17,6 +17,11 @@ use tracing::{info, trace}; use crate::messages::status::{STATUS_PROTOCOL_V1, Status}; +/// Topic kind for block gossip +const BLOCK_TOPIC_KIND: &str = "block"; +/// Topic kind for attestation gossip +const ATTESTATION_TOPIC_KIND: &str = "attestation"; + mod messages; pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { @@ -96,7 +101,7 @@ pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { .listen_on(addr) .expect("failed to bind gossipsub listening address"); - let topic_kinds = ["block", "attestation"]; + let topic_kinds = [BLOCK_TOPIC_KIND, ATTESTATION_TOPIC_KIND]; let network = "devnet0"; for topic_kind in topic_kinds { let topic_str = format!("/leanconsensus/{network}/{topic_kind}/ssz_snappy"); @@ -121,21 +126,16 @@ struct Behaviour { async fn event_loop(mut swarm: libp2p::Swarm) { while let Some(event) = swarm.next().await { match event { - SwarmEvent::Behaviour(BehaviourEvent::ReqResp(message @ Event::Message { .. })) => { + SwarmEvent::Behaviour(BehaviourEvent::ReqResp( + message @ request_response::Event::Message { .. }, + )) => { handle_req_resp_message(&mut swarm, message).await; } - // SwarmEvent::Behaviour(BehaviourEvent::ReqResp(Event::Message { - // peer, - // connection_id, - // message: - // Message::Request { - // request_id, - // request, - // channel, - // }, - // })) => { - // info!(finalized_slot=%request.finalized.slot, head_slot=%request.head.slot, "Received status request from peer {peer}"); - // } + SwarmEvent::Behaviour(BehaviourEvent::Gossipsub( + message @ gossipsub::Event::Message { .. }, + )) => { + handle_gossipsub_message(&mut swarm, message).await; + } _ => { trace!(?event, "Ignored swarm event"); } @@ -145,9 +145,9 @@ async fn event_loop(mut swarm: libp2p::Swarm) { async fn handle_req_resp_message( swarm: &mut libp2p::Swarm, - event: Event, + event: request_response::Event, ) { - let Event::Message { + let request_response::Event::Message { peer, connection_id: _, message, @@ -156,7 +156,7 @@ async fn handle_req_resp_message( unreachable!("we already matched on event_loop"); }; match message { - Message::Request { + request_response::Message::Request { request_id: _, request, channel, @@ -169,7 +169,7 @@ async fn handle_req_resp_message( .unwrap(); swarm.behaviour_mut().req_resp.send_request(&peer, request); } - Message::Response { + request_response::Message::Response { request_id: _, response, } => { @@ -178,6 +178,34 @@ async fn handle_req_resp_message( } } +async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, event: gossipsub::Event) { + let gossipsub::Event::Message { + propagation_source: _, + message_id: _, + message, + } = event + else { + unreachable!("we already matched on event_loop"); + }; + match message.topic.as_str().split("/").nth(2) { + Some(BLOCK_TOPIC_KIND) => { + info!( + "Received block gossip message of size {} bytes", + message.data.len() + ); + } + Some(ATTESTATION_TOPIC_KIND) => { + info!( + "Received attestation gossip message of size {} bytes", + message.data.len() + ); + } + _ => { + trace!("Received message on unknown topic: {}", message.topic); + } + } +} + pub struct Bootnode { ip: IpAddr, quic_port: u16, From 0481410199bd7ff273c0311d964b2a5da262951a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:43:39 -0300 Subject: [PATCH 04/10] chore: move gossipsub handler to own module --- crates/net/p2p/src/gossipsub.rs | 36 ++++++++++++++++++++++ crates/net/p2p/src/lib.rs | 53 ++++++++------------------------- 2 files changed, 48 insertions(+), 41 deletions(-) create mode 100644 crates/net/p2p/src/gossipsub.rs diff --git a/crates/net/p2p/src/gossipsub.rs b/crates/net/p2p/src/gossipsub.rs new file mode 100644 index 00000000..9426e6a2 --- /dev/null +++ b/crates/net/p2p/src/gossipsub.rs @@ -0,0 +1,36 @@ +use crate::Behaviour; +use libp2p::gossipsub::Event; +use tracing::{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(swarm: &mut libp2p::Swarm, 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(2) { + Some(BLOCK_TOPIC_KIND) => { + info!( + "Received block gossip message of size {} bytes", + message.data.len() + ); + } + Some(ATTESTATION_TOPIC_KIND) => { + info!( + "Received attestation gossip message of size {} bytes", + message.data.len() + ); + } + _ => { + trace!("Received message on unknown topic: {}", message.topic); + } + } +} diff --git a/crates/net/p2p/src/lib.rs b/crates/net/p2p/src/lib.rs index 55b73085..b4441ace 100644 --- a/crates/net/p2p/src/lib.rs +++ b/crates/net/p2p/src/lib.rs @@ -6,7 +6,7 @@ use ethrex_rlp::decode::RLPDecode; use libp2p::{ Multiaddr, PeerId, StreamProtocol, futures::StreamExt, - gossipsub::{self, MessageAuthenticity, ValidationMode}, + gossipsub::{MessageAuthenticity, ValidationMode}, identity::{PublicKey, secp256k1}, multiaddr::Protocol, request_response, @@ -15,13 +15,12 @@ use libp2p::{ use sha2::Digest; use tracing::{info, trace}; -use crate::messages::status::{STATUS_PROTOCOL_V1, Status}; - -/// Topic kind for block gossip -const BLOCK_TOPIC_KIND: &str = "block"; -/// Topic kind for attestation gossip -const ATTESTATION_TOPIC_KIND: &str = "attestation"; +use crate::{ + gossipsub::{ATTESTATION_TOPIC_KIND, BLOCK_TOPIC_KIND}, + messages::status::{STATUS_PROTOCOL_V1, Status}, +}; +mod gossipsub; mod messages; pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { @@ -105,7 +104,7 @@ pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { let network = "devnet0"; for topic_kind in topic_kinds { let topic_str = format!("/leanconsensus/{network}/{topic_kind}/ssz_snappy"); - let topic = gossipsub::IdentTopic::new(topic_str); + let topic = libp2p::gossipsub::IdentTopic::new(topic_str); swarm.behaviour_mut().gossipsub.subscribe(&topic).unwrap(); } @@ -117,7 +116,7 @@ pub async fn start_p2p(bootnodes: Vec, listening_port: u16) { /// [libp2p Behaviour](libp2p::swarm::NetworkBehaviour) combining Gossipsub and Request-Response Behaviours #[derive(NetworkBehaviour)] struct Behaviour { - gossipsub: gossipsub::Behaviour, + gossipsub: libp2p::gossipsub::Behaviour, req_resp: request_response::Behaviour, } @@ -132,9 +131,9 @@ async fn event_loop(mut swarm: libp2p::Swarm) { handle_req_resp_message(&mut swarm, message).await; } SwarmEvent::Behaviour(BehaviourEvent::Gossipsub( - message @ gossipsub::Event::Message { .. }, + message @ libp2p::gossipsub::Event::Message { .. }, )) => { - handle_gossipsub_message(&mut swarm, message).await; + gossipsub::handle_gossipsub_message(&mut swarm, message).await; } _ => { trace!(?event, "Ignored swarm event"); @@ -178,34 +177,6 @@ async fn handle_req_resp_message( } } -async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, event: gossipsub::Event) { - let gossipsub::Event::Message { - propagation_source: _, - message_id: _, - message, - } = event - else { - unreachable!("we already matched on event_loop"); - }; - match message.topic.as_str().split("/").nth(2) { - Some(BLOCK_TOPIC_KIND) => { - info!( - "Received block gossip message of size {} bytes", - message.data.len() - ); - } - Some(ATTESTATION_TOPIC_KIND) => { - info!( - "Received attestation gossip message of size {} bytes", - message.data.len() - ); - } - _ => { - trace!("Received message on unknown topic: {}", message.topic); - } - } -} - pub struct Bootnode { ip: IpAddr, quic_port: u16, @@ -254,7 +225,7 @@ pub fn parse_validators_file(bootnodes_path: &str) -> Vec { bootnodes } -fn compute_message_id(message: &gossipsub::Message) -> gossipsub::MessageId { +fn compute_message_id(message: &libp2p::gossipsub::Message) -> libp2p::gossipsub::MessageId { const MESSAGE_DOMAIN_INVALID_SNAPPY: [u8; 4] = [0x00, 0x00, 0x00, 0x00]; const MESSAGE_DOMAIN_VALID_SNAPPY: [u8; 4] = [0x01, 0x00, 0x00, 0x00]; @@ -272,5 +243,5 @@ fn compute_message_id(message: &gossipsub::Message) -> gossipsub::MessageId { hasher.update(topic); hasher.update(data); let hash = hasher.finalize(); - gossipsub::MessageId(hash[..20].to_vec()) + libp2p::gossipsub::MessageId(hash[..20].to_vec()) } From d1f618713aceb4dffc15d3ab7088ddf8d3210f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 7 Jan 2026 18:57:16 -0300 Subject: [PATCH 05/10] test: add test deserializing a block --- crates/net/p2p/src/gossipsub.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/net/p2p/src/gossipsub.rs b/crates/net/p2p/src/gossipsub.rs index 9426e6a2..07fedffb 100644 --- a/crates/net/p2p/src/gossipsub.rs +++ b/crates/net/p2p/src/gossipsub.rs @@ -16,7 +16,7 @@ pub async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, even else { unreachable!("we already matched on event_loop"); }; - match message.topic.as_str().split("/").nth(2) { + match message.topic.as_str().split("/").nth(3) { Some(BLOCK_TOPIC_KIND) => { info!( "Received block gossip message of size {} bytes", @@ -34,3 +34,18 @@ pub async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, even } } } + +#[cfg(test)] +mod tests { + #[test] + fn deserialize_gossipsub_block_message() { + let hex_data = "c4332408000000740100008c00190100120d0b7c12c1e49b1fe0e06caedc856f6ad6c112bff9ad8b72d6fbc72bec2c180fccad8011287c863ab6231eea99c326f9b38ae30b5e34d4fadf03140f34f337ffe802c45e17480d4fa2280011581101f0460d50c4cc9148a8909ae7e9bfef69b22279f050c946c3d105f3c18ec75c6e2a7a73d1756b780cdd7708fa036d3ef7d4a362cfe66d52fe8c367be30a69b91e280b54000000040000114f0400110d0a7e5800000c0d28fee0003ee000f4d40e24000000735bd71465bb7917096c3a4f2c44660e5f3bf316403d25432dfdbc3a2804000004000000a836fe5734b58e0128dd6c40afa2ed4149a98a10f488ec2fcec92f58ae69453151c86b5351b0e72c1aff205818491a77e4ba8e1f15c88f150fe25b5f4dd9417bc9eaa80818fbeb0a35d29e354bbf15438e698f23a3d5ba6dd94cbd721044b625c2cc2650107cd338d218eb4157b15441cd629f587defd63efdd1bc4c7ff3225cf7c90d14b5d9664fabb98f3a6493fb5ae65b2467b841b03021b6492eadd92448b08e30009b8eb70ddf35375c715d3138c8463b7940db312058000719e1836e5755e1c13f42a0596a4cfe975c337e9e0a148daa48ca434b03d6c3b97e2befab16f46add43e2ed966a5fa9e9686da7a77877ff6c3b21042439b7de78597243c321379fff4813e90a499895782fb1425d64948edc66901068494575982e4236ff1d53836d35a5c6c235efd45943aff19821d689b47d148a8b7926b28d080c86785f7084d90cb22a8e7c6d4e8c7a26ba5b08b521ac4a7078516854261d2eb0a4d32f13b15d57a48fcf181bf54649e2e70e6ba7124e0f6b51bc24e1edf51c5d65fa7523ae474f33078434d0778c0be47dac299917691788626249738d211b31f60f41b6025510ed770b308578605bc33b394882eac633b02d614a00ee9557af25251a39515e7b1c53b15f31db7a4ad9a3947114104a6c86c34457c0fc9b6b53f5cf3b7541c83ff81d2a1531337c51aabbb851f2c448559c220c3a5a09d209dcd3e2661640d43be7f3a727d6b31a3ef63f3401d64a743a1f6d4d4577e3d64b5187c07d5feb1d41db7c1b6916243b664b4fde6f12d5a70cc433c36b9346f05737247a29cfb22308f24b0532a781682a5527e07ad5eb044c99a741790c7add0570e26e0e59f40a534ab23a2ecfe6f44c3093ad4102596272d752ac67d982446f16fdc103b059f61b64d65750286ba81b1668e961a23f8e4d7f2cc06b06c57b0cec3de029d7d7810845052325e65f570f50384d1668c5130402ea7418c77e574e7755c07ee6b6f82ab1cd165eb086e23a997200072005ce5e1632a00b47dff55d0e3f2b6a72f7c03c99ef0824cdfa340f7aa2b8020bd97357d7281a4d39b1b059e81dfc2fe9850274381b415932623670b144ac447cec83043e764a6929deb2749a00dd13783a412b8868b66c362a8e5517679a3f97418634b22195611748f821d18b580e84c25a20f793057db5af8a4bef6b791777f625756816cd76bbb1a3051c7398511a0d53648786006941e03f44f3f96304bdcba242c6299256c5da0d77ba26231d65ff9d45a5002a2d7716514129d4fc472784d85adba8b77e55c02b2c2b5c333818901f20f9ea1356df39ae0bb410f7650416a374f3d47643f00253365d06935f5edb2d09c86d3513b108b75061ee0400969ebd76ad97f87bcbef1706294d5122c6ff903a948497015badc84e05a1706e2ac8b2217e1f645b1529ab740ec66764e26bd62f144583031b25ec64ea87ac55ad53ce1f5df416317ba4c619e35ee9488ca1d613fa90fe288b11c000ca01a50f2abeb438e9aebb71f1ebe76d411e56314eb54b3cfc09a04b43a7d51fe33042092367543209476217c27a9c415a8b0d498e2efa5816b5ee28f34c081805f628316256bc58d426522bfb63a02d1a9c443d8a177f59f711f5197d6a7d0099f1b85a104b4d61b684e6035b4889130129de03fdfd4e5fd0eafe45fc5bb96ad264067c95ada7602d99b30ffc89113c9c805b6ca8f025412aad175dc6094140745e9f29f2cca34f309e5239ca9c3f625dc8e1121f84375b0acc5d7cde0e1d508da1750bc36af01ce308081f49cae16321bb2460894bfc14b30c9b6b1444e0102bc2065ec7b644096cdb7f6a0930606f4aca3a429a7ff77165f1881339b10223a049431be0308a77dc27b346e0056c10b0cef67d669eb4157242e52ae7d66b06ffea3b7b38ff98797dd5d859433eea3c04e1741bc765f157bd271a14d875eb271289297987dd6267be1c5475fc9bf2084ef22f410caa172f3d368b525b037b6751365916eea3370fe53c6b30f772fd45e8923b42f4a1cb3e2033182a13d9514cdf0e2f2f0393066f2c061a4a8cfd0d473ce4952f2149b718cc3545498b04ba67d054ef2d32458a4e38039f5c452ae5565ddcb04bf0a3c75606433a1994672826370e942874f6aa79e9a3c7777223ce14f1323971ec58f10b75d68202a0d7884a2a0506318d97cb1ed634d1739a07de5ae42e3c54b31a4b60f049cb02fde5fc442c2ab1636c06da4c02c54b5c9964f630efee701cbfd22e21f7a0242b1a2fa82c55937c097c0c312cb4736d0648addb3692f53103cbe7902d08eaf46342fcfa54efd4b9544aac0c643ea1365324f5be4d081d000c33e40e7c7926a036565e4447f051764be725910a057c0673f8f83a639d5b2728302bba70fe8e411f1743444d88fa395aa7a1d8557f967f326cd0ca72acdee551a1cba50604308c20cedd390eecfbd03794cedf3345bc4215bc05d00c7710914aa81bc83ab79a1d0e52075b4161fc283b5cc7a73153c03f24a7d97916d350f768390a414b0e4ce03d37a79665dd3ab859922b92716b76e71d8f4f584410bac30a60d79c4a05ced442945b5253abddcf74aa6ea6094b471f3c5be25535b3f8131af6208d4d1382ff7e2ebb8c678ae49177f0251d7a07c5471088c0d9416f706028c566ca34899a5e6ac56f10267a9b9b73d873cd214ed0de7353e8ee14f3dee9031ab1251007ed68177dfbd07e4345350892bda57a3ea7630f8cc7882511928c17dc8e9650c2667c615d5e9047397e905d6b7d1940d52c884fd8ad81003608a851f682d95546c0f9571d056b4ffdaa257b16e92c1f4ac51554ccf7343e28b08f4ff20fc62bd6ddff3fbbe1d5329668f84e4e46004dd6a48e2eb0f3a47132c9785e267239764a26382755598935ec2c964aaf0cf9495fa4d718f8122a39caebf555ea79fa49d091c924409bea232421c80cf2eae507f7e5303b321f5c25fe19d24e09cbad722e1dd4006f8d7d11c18d352c5e70a929493f6f61e871f90cf4725819dbc35907e45dc8493c53836d1c9adc1ae108fe16c03d392da1d3c45e698363667851b9544092565aa57de237fc88426bf140b86280df1a60a0a966292205f1711bc11a537e9fc92453bbb962b136f1379818462aefb5583991613205b38f9364d57a3e73ec53c020f5b24d3349c39b2181b5692e5fe62c27923fda370c532b539a73bb38fb98661f8fe29d7c6d9bf611f68f3f1621ae6c5bced6c6651abf06787a04aa0fd01dca04f576ab59ca325e6f6ac2152f2362032607530c40c2cfb507ef814d79d9df7d55e3d5555cac901559721ee6379427f5437a92ef49c5f950357359114c55df9b084dd0360039cdfa3c98c5e613f97f017dcf0a3d293ded9378b3062407b5d27f1a24389709915653679db0a36e2cc79875b430bd789e8a8e142243523b75c0f379f8d9ea0a4c2bdc087255507650630c1f8086e337a20c8f54e83863501cd024686440e443714a9a117ea2e825a241a2738525d3374a6d715d9e4cb3400bc76e281e0030778ff5915dc9785e36069b7b57a066853e5a500666997a3e242e792801317e73219d17c861485d3443b9a5af5c2b840855b4b5f8350742e24eca7ee955810d203da21a5663a804bc3e3c57b3427ed7ba35b77e7d3dbe11e17184217f0d1507ee71179b11721dc9b53d270a36265b4b9d7905ea7276b5e64f57ef7887628b8a16224218d21f8e291f497ac7fa065fbb29633bdb143034d09821a4add547c490b07724207c77e5cd2202f4d1e316b582d26837983c078e3b1e11b956c0028ba7147b1f8492431654b31e7f36ba5baabceb44db76a2132b957c724376206115029308f57caf1dcb1d561f45041b5b1fb13a572e2b3824a914c40462323159e10cf02a690ef01e024acf76fd1e643718d27e034f98d53809fec31307a2ff1d74cdca619fd0087eb247953d1f66890c7fc9ea5695f42c4503442664a0223e6b14bec14504757c15dee23909c5cf8c6a1a0f6e20e527e57dfcf13a330fce8d428d96f85bafda11434a55207e62d14d0b8d8b2e3c09b0a403923397037e4b1e0ee41410582efac43c726d130165855b4318b2e31903f6bf059bceae172ad9411c0f8654098b44551eb0f58f29cd4e585c2042841a8b54803c943be2585eae20716fd4e4085f8f5434dcf5154218d57b1766942863d38ee1409e210d6bddb78e6e850c8d737ce3003a9b6d3779dd1ac02eb3a9da3bb833110bc48a9d5c750a864727a00579b328dc2995ddb107ca790f628fd463361af2ca65395a772eff90451d5e3d7317a28c8323653ad45e04f3c2522dcebb00b242750724000000fb8e0f13923b006eb3d20c1e8d9a9d1ef64dcb77ece9520949065f6e28040000040000004e881638b062bc1381f2b77670b4d808c9ee2675674aa37d30f30421b86dd46db025f254c44b902c76e2b9170d4dd63a00a33f6ce4c1333c935a3c1d66ed2118c9eaa80818fbeb0a35d29e354bbf15438e698f23a3d5ba6dd94cbd721044b625c2cc2650107cd338d218eb4157b15441cd629f587defd63efdd1bc4c7ff3225cf7c90d14b5d9664fabb98f3a6493fb5ae65b2467b841b03021b6492eadd92448b08e30009b8eb70ddf35375c715d3138c8463b7940db312058000719e1836e5755e1c13f42a0596a4cfe975c337e9e0a148daa48ca434b03d6c3b97e2befab16f46add43e2ed966a5fa9e9686da7a77877ff6c3b21042439b7de78597243c321379fff4813e90a499895782fb1425d64948edc66901068494575982e4236ff1d53836d35a5c6c235efd45943aff19821d689b47d148a8b7926b28d080c86785f7084d90cb22a8e7c6d4e8c7a26ba5b08b521ac4a7078516854261d2eb0a4d32f13b15d57a48fcf181bf54649e2e70e6ba7124e0f6b51bc24e1edf51c5d65fa7523ae474f33078434d0778c0be47dac299917691788626249738d211b31f60f41b6025510ed770b308578605bc33b394882eac633b02d614a00ee9557af25251a39515e7b1c53b15f31db7a4ad9a3947114104a6c86c34457c0fc9b6b53f5cf3b7541c83ff81d2a1531337c51aabbb851f2c448559c220c3a5a09d209dcd3e2661640d43be7f3a727d6b31a3ef63f3401d64a743a1f6d4d4577e3d64b5187c07d5feb1d41db7c1b6916243b664b4fde6f12d5a70cc433c36b9346f05737247a29cfb22308f24b0532a781682a5527e07ad5eb044c99a741790c7add0570e26e0e59f40a534ab23a2ecfe6f44c3093ad4102596272d752ac67d982446f16fdc103b059f61b64fe280cfe280cfe280cfe280cfe280cea280cf4ff07ecef9d3d69e897097b55d81843225543064f06236059774534715b693ad3d15e6c2fbc4393e8412a6b997b53840556497c008732e84fcb2721a69356b140fe6a376f0332a4a11005041a757b2518172227862c45c7b5066959cc28256883363aa4aa2a62ff6e2610c2bf43588ca64b5c3aeee66d67db08182d89de68dd93df55039a980cffb099096fb207549f35a534d1120f4ce82ab61a12563b3c2d7eae761be26d63f6492e14c366da44c5acd24082f86b1a5aedda74fca5b72e9ec8b14f41a71f08cf7343630c3ae37c3989062e4cfc923c83760a0d97d16d4bc148ac6d930740182f888233349e7d5edfb36d0a1beedd4b0bf05845e9a02c299a6d4d572ca98464ce29541266da937e9c92425b066dd50c3c3420096551321ed718db4e0c143c0e023b184559217919bad02012ddff671c0168372f928b197a4c85621099881d0215063c01450d766213c12d6111494962907ae022f56319567830181a63c61730667918301b02ad324487704914bca150b369ed72a731a00b7b81a60034f34e51b6c6bd1591586d6291e5bd025efe934d49de5f1b674163707422c80dc8269a234851b2537f4d4147ae78b765799cc15556e11d59bd692c3b31c8304646d15a2c22ff8172fb370078d62af022998a3367cffe1f61119b32092f214d0a1c1bf21f24353109950ff85f8b3944196bf43f1137b5e228790fb443abb0ac0b7fac9548e8d05863725c4055d93a2711717a711844d39c6c52a662027f97644181c23b4c2a524f41f582037bc43f8f5fbdd9cf5d935a241acd9c5c3e996b8e4bd294d8489d86430d706a546286d8e678ce92124a1c6780360f20807ca95a282f400e48678eb8145ddbf7c2243f143654a982f628e7daec284f25aa7c2e210468dbaa48165ce1d90ac896783bbbd71d1ca14cfa710eb0cc7081bda91aac85e93982cab95f8f8e46030c123a6f811ad04304e2da2da5a543220af55472605bb64c49b69e2a6df4b0241b07312a5bc7ca64bf7a1226dc54e538d42b046646962153b9ccfa679c991c65e19acd3a0b915125ce62ba421a95a92afae29d392b7a0d56ffd69a685fc02c14f363a56226c9ac2a07b10b1573ed387b490c2d546e80931c96cea43f4668964972fd040df0190d690f199f4cf7c8d03629031101c8c903203b23bf68883496241c9d35389c4b6d79f959660446eeae79c0d14f7b857b4e2eda6d5566ece155342a6bb615fbc91f686532b56bd31cc146c01482188942c44ae2a27b08bdab24029e2dcc23cb0907716931343ff177c22f54c1a94b86c23a16515d9f2682a98c0e22be48406586f372142eb85e1fcad54930a1620529461a4e0cd1f933b3c7f3502757853a0d6ee157f6f1b10076cdb91c632d0e141bad9428cc42f00aeb14c54f1f8e045f3dd4c51939cd3f666b6a4201c39a3b3b6cb7a71087920721840dd841704c5530036c845ba91c545840a09a03f25f5c778e43c921bde2c90e808d6e7c5a84b42f53c82e776f6d156295a2953ebe7b9c37185eaa2c430c01743f19cf713b61fc22ccb9161aeab98f689821464770c936461c35d634b510086b9e5c85168fe233622b282f6676126061e28d107916cd277accacfc0309f2f373f4af7d36eca2441771750360161e62244140bd780fcb9721b8245908102557523761ce659020ad123ffb114dee07c842a68b45083584a655df7ba934462a100d95f9f343ba998c3791116b3910cb296e4b6dce79d64f1017d752cb7ca032623d7bfe54715755a16ef2e5680cc5e7cc60d0c1c50e8007265d3b85063a3ad6c5571c5371729a33157220babb06d1f4dd5a7dc3b90a6d7152210afbd747f7f1a91914a3092df551911c3a38e573ce078967b0d71f55eb81633527f1162799b93e10a427c56555ccec18cecb98630ba83c501a8fcf6e637101657657a87b603a694da5cea70b8e650f1750317222510e2d6a5663c070866be362088c3e268e1c380b13b66167d7057e7ecc15641e892c89398282702ef74fb7752e515f782f8e75222980666a3abc113815c9dd7d54f8502ea2594436190e1610e5d657492d36445b5955141509692e3870ee2a3001725f4729fd581ba3ba3b7ca35ea41f339b6b657bfef609d869eb70f36c885b754cb45213b6944471fe4c636c1ff977fc35b26bcef80e0a7943d775a3741e6587481a5d65067a02f2e0926c549be56b38544630b624026212ca693119610f3c4efc7b416c47a26a81260142b125b47233d91e08b7b8b2447b03ef7a5d20273cd8e5a2536384eb519b8de3376b4f5b2b93f8416790e0ab2fd40da353c0cc4964584738283fb3175fc16d1155c16799214b687b21fc3d520c4cfbfa05ed42e500321c9c262ded192ebcf43064bf75b84c5e3ee6708de5391f6f4544222eb9940d5915892bfcd90e083767103041f78d6fcf4b5709453f1c58e471ab0c92ee0f73e5684d77541a596e02fdb25b2f27086ad65db831156d8e744980a7300b13c07c9ad6c6121750d6415640f447ac4aab2a67d66e0129ae1e48574f8137cb61ee37d2bf220f17304b001c94d62f3c5ea0614e21321285df83122a11050d46b6e60b4d011c117ad6625ee17da307bee9753a0ee33004b18d2218637c064e8fe085681f330234030b10466dac61351eeced619550925d197e526963c4da2cae6db9768504574080798a517a2fac3de4d9610b3d650a47f4b9ae15de48065dbb5a0b491198b04645924f14ec9cc922ac83da4f2d4530626a278053c28c8267ea76c238f9cd703741d4f046ad9750022a56486d2efbf639d001e665b8ecbb25b925032199962d0c744ff923de1a560e1b9d735efad5c70e1310ec7224d5e0718f5e55159ecc5630f9d43a0426567f680c4e034f"; + // Add tests for gossipsub message handling here + let data = hex::decode(hex_data).unwrap(); + let size = snap::raw::decompress_len(&data).unwrap(); + let mut buf = vec![0; size]; + let mut decoder = snap::raw::Decoder::new(); + decoder.decompress(&data, &mut buf).unwrap(); + assert!(!buf.is_empty()); + } +} From edd4f787f18452bbbf3d556f64b57192d37ae6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 7 Jan 2026 19:15:08 -0300 Subject: [PATCH 06/10] feat: add block gossip types --- crates/common/types/src/attestation.rs | 31 ++++++++++++ crates/common/types/src/block.rs | 69 ++++++++++++++++++++++++-- crates/common/types/src/lib.rs | 1 + crates/common/types/src/state.rs | 10 ++-- 4 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 crates/common/types/src/attestation.rs diff --git a/crates/common/types/src/attestation.rs b/crates/common/types/src/attestation.rs new file mode 100644 index 00000000..e6d377f8 --- /dev/null +++ b/crates/common/types/src/attestation.rs @@ -0,0 +1,31 @@ +use ssz_types::typenum::N4096; + +use crate::state::Checkpoint; + +/// Validator specific attestation wrapping shared attestation data. +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. +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; diff --git a/crates/common/types/src/block.rs b/crates/common/types/src/block.rs index bd29c673..30391870 100644 --- a/crates/common/types/src/block.rs +++ b/crates/common/types/src/block.rs @@ -1,6 +1,43 @@ -use crate::primitives::H256; +use ssz_types::typenum::N4096; -use crate::state::Slot; +use crate::{ + attestation::{Attestation, Attestations}, + primitives::H256, +}; + +/// Envelope carrying a block, an attestation from proposer, and aggregated signatures. +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, +} + +/// Aggregated signature list included alongside the block. +/// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`]. +pub type BlockSignatures = ssz_types::VariableList< + // TODO: change to Signature type when available + u64, + N4096, +>; + +/// Bundle containing a block and the proposer's attestation. +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. /// @@ -13,7 +50,7 @@ use crate::state::Slot; #[derive(Debug)] 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 @@ -23,3 +60,29 @@ pub struct BlockHeader { /// The root of the block body pub body_root: H256, } + +/// A complete block including header and body. +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. +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, +} diff --git a/crates/common/types/src/lib.rs b/crates/common/types/src/lib.rs index c7c712e5..f6637042 100644 --- a/crates/common/types/src/lib.rs +++ b/crates/common/types/src/lib.rs @@ -1,3 +1,4 @@ +pub mod attestation; pub mod block; pub mod genesis; pub mod primitives; diff --git a/crates/common/types/src/state.rs b/crates/common/types/src/state.rs index 1e8869f9..234cf0f3 100644 --- a/crates/common/types/src/state.rs +++ b/crates/common/types/src/state.rs @@ -4,8 +4,8 @@ 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)] @@ -13,7 +13,7 @@ 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 @@ -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, From d2d2a0b5ec87b8d89103458255fc1e79b78eec46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:41:10 -0300 Subject: [PATCH 07/10] feat: implement SSZ for types --- Cargo.lock | 266 ++++++++++++++++++++++--- Cargo.toml | 3 + crates/common/types/Cargo.toml | 2 + crates/common/types/src/attestation.rs | 8 +- crates/common/types/src/block.rs | 27 ++- crates/common/types/src/lib.rs | 1 + crates/common/types/src/signature.rs | 7 + 7 files changed, 279 insertions(+), 35 deletions(-) create mode 100644 crates/common/types/src/signature.rs diff --git a/Cargo.lock b/Cargo.lock index 2558ed42..39432a69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,6 +1336,20 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if 1.0.4", + "crossbeam-utils 0.8.21", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -1779,6 +1793,7 @@ dependencies = [ "ethereum-types", "ethereum_ssz", "ethereum_ssz_derive", + "leansig", "serde", "ssz_types", "thiserror 2.0.17", @@ -2996,7 +3011,7 @@ dependencies = [ "serde_arrays", "sha2", "sp1_bls12_381", - "spin", + "spin 0.9.8", ] [[package]] @@ -3020,6 +3035,26 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leansig" +version = "0.1.0" +source = "git+https://github.com/leanEthereum/leansig.git?rev=ae12a5feb25d917c42b6466444ebd56ec115a629#ae12a5feb25d917c42b6466444ebd56ec115a629" +dependencies = [ + "dashmap", + "ethereum_ssz", + "num-bigint 0.4.6", + "num-traits", + "p3-baby-bear 0.4.1", + "p3-field 0.4.1", + "p3-koala-bear", + "p3-symmetric 0.4.1", + "rand 0.9.2", + "rayon", + "serde", + "sha3", + "thiserror 2.0.17", +] + [[package]] name = "libc" version = "0.2.178" @@ -4228,24 +4263,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7521838ecab2ddf4f7bc4ceebad06ec02414729598485c1ada516c39900820e8" dependencies = [ "num-bigint 0.4.6", - "p3-field", - "p3-mds", - "p3-poseidon2", - "p3-symmetric", + "p3-field 0.2.3-succinct", + "p3-mds 0.2.3-succinct", + "p3-poseidon2 0.2.3-succinct", + "p3-symmetric 0.2.3-succinct", "rand 0.8.5", "serde", ] +[[package]] +name = "p3-baby-bear" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-challenger", + "p3-field 0.4.1", + "p3-mds 0.4.1", + "p3-monty-31", + "p3-poseidon2 0.4.1", + "p3-symmetric 0.4.1", + "rand 0.9.2", +] + +[[package]] +name = "p3-challenger" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-field 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-monty-31", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "tracing", +] + [[package]] name = "p3-dft" version = "0.2.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46414daedd796f1eefcdc1811c0484e4bced5729486b6eaba9521c572c76761a" dependencies = [ - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", + "p3-field 0.2.3-succinct", + "p3-matrix 0.2.3-succinct", + "p3-maybe-rayon 0.2.3-succinct", + "p3-util 0.2.3-succinct", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "p3-field 0.4.1", + "p3-matrix 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-util 0.4.1", + "spin 0.10.0", "tracing", ] @@ -4258,11 +4334,39 @@ dependencies = [ "itertools 0.12.1", "num-bigint 0.4.6", "num-traits", - "p3-util", + "p3-util 0.2.3-succinct", "rand 0.8.5", "serde", ] +[[package]] +name = "p3-field" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "num-bigint 0.4.6", + "p3-maybe-rayon 0.4.1", + "p3-util 0.4.1", + "paste", + "rand 0.9.2", + "serde", + "tracing", +] + +[[package]] +name = "p3-koala-bear" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-challenger", + "p3-field 0.4.1", + "p3-monty-31", + "p3-poseidon2 0.4.1", + "p3-symmetric 0.4.1", + "rand 0.9.2", +] + [[package]] name = "p3-matrix" version = "0.2.3-succinct" @@ -4270,20 +4374,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e4de3f373589477cb735ea58e125898ed20935e03664b4614c7fac258b3c42f" dependencies = [ "itertools 0.12.1", - "p3-field", - "p3-maybe-rayon", - "p3-util", + "p3-field 0.2.3-succinct", + "p3-maybe-rayon 0.2.3-succinct", + "p3-util 0.2.3-succinct", "rand 0.8.5", "serde", "tracing", ] +[[package]] +name = "p3-matrix" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "p3-field 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-util 0.4.1", + "rand 0.9.2", + "serde", + "tracing", + "transpose", +] + [[package]] name = "p3-maybe-rayon" version = "0.2.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3968ad1160310296eb04f91a5f4edfa38fe1d6b2b8cd6b5c64e6f9b7370979e" +[[package]] +name = "p3-maybe-rayon" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" + [[package]] name = "p3-mds" version = "0.2.3-succinct" @@ -4291,14 +4415,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2356b1ed0add6d5dfbf7a338ce534a6fde827374394a52cec16a0840af6e97c9" dependencies = [ "itertools 0.12.1", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-symmetric", - "p3-util", + "p3-dft 0.2.3-succinct", + "p3-field 0.2.3-succinct", + "p3-matrix 0.2.3-succinct", + "p3-symmetric 0.2.3-succinct", + "p3-util 0.2.3-succinct", "rand 0.8.5", ] +[[package]] +name = "p3-mds" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-dft 0.4.1", + "p3-field 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "rand 0.9.2", +] + +[[package]] +name = "p3-monty-31" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "num-bigint 0.4.6", + "p3-dft 0.4.1", + "p3-field 0.4.1", + "p3-matrix 0.4.1", + "p3-maybe-rayon 0.4.1", + "p3-mds 0.4.1", + "p3-poseidon2 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "paste", + "rand 0.9.2", + "serde", + "spin 0.10.0", + "tracing", + "transpose", +] + [[package]] name = "p3-poseidon2" version = "0.2.3-succinct" @@ -4306,13 +4465,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da1eec7e1b6900581bedd95e76e1ef4975608dd55be9872c9d257a8a9651c3a" dependencies = [ "gcd", - "p3-field", - "p3-mds", - "p3-symmetric", + "p3-field 0.2.3-succinct", + "p3-mds 0.2.3-succinct", + "p3-symmetric 0.2.3-succinct", "rand 0.8.5", "serde", ] +[[package]] +name = "p3-poseidon2" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "p3-field 0.4.1", + "p3-mds 0.4.1", + "p3-symmetric 0.4.1", + "p3-util 0.4.1", + "rand 0.9.2", +] + [[package]] name = "p3-symmetric" version = "0.2.3-succinct" @@ -4320,7 +4491,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb439bea1d822623b41ff4b51e3309e80d13cadf8b86d16ffd5e6efb9fdc360" dependencies = [ "itertools 0.12.1", - "p3-field", + "p3-field 0.2.3-succinct", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "itertools 0.14.0", + "p3-field 0.4.1", "serde", ] @@ -4333,6 +4514,14 @@ dependencies = [ "serde", ] +[[package]] +name = "p3-util" +version = "0.4.1" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=d421e32#d421e32d3821174ae1f7e528d4bb92b7b18ab295" +dependencies = [ + "serde", +] + [[package]] name = "pairing" version = "0.23.0" @@ -5617,10 +5806,10 @@ dependencies = [ "hex", "lazy_static", "num-bigint 0.4.6", - "p3-baby-bear", - "p3-field", - "p3-poseidon2", - "p3-symmetric", + "p3-baby-bear 0.2.3-succinct", + "p3-field 0.2.3-succinct", + "p3-poseidon2 0.2.3-succinct", + "p3-symmetric 0.2.3-succinct", "serde", "sha2", ] @@ -5673,6 +5862,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -5711,6 +5909,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "strsim" version = "0.11.1" @@ -6122,6 +6326,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "tree_hash" version = "0.12.0" diff --git a/Cargo.toml b/Cargo.toml index 60c86dc5..7e206963 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/common/types/Cargo.toml b/crates/common/types/Cargo.toml index 8391b0c5..556d4395 100644 --- a/crates/common/types/Cargo.toml +++ b/crates/common/types/Cargo.toml @@ -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" diff --git a/crates/common/types/src/attestation.rs b/crates/common/types/src/attestation.rs index e6d377f8..dbb50d52 100644 --- a/crates/common/types/src/attestation.rs +++ b/crates/common/types/src/attestation.rs @@ -1,8 +1,11 @@ -use ssz_types::typenum::N4096; +use ssz_derive::{Decode, Encode}; +use ssz_types::typenum::U4096; +use tree_hash_derive::TreeHash; use crate::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, @@ -12,6 +15,7 @@ pub struct Attestation { } /// 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, @@ -28,4 +32,4 @@ pub struct AttestationData { /// List of validator attestations included in a block. /// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`]. -pub type Attestations = ssz_types::VariableList; +pub type Attestations = ssz_types::VariableList; diff --git a/crates/common/types/src/block.rs b/crates/common/types/src/block.rs index 30391870..b204a1ae 100644 --- a/crates/common/types/src/block.rs +++ b/crates/common/types/src/block.rs @@ -1,11 +1,15 @@ -use ssz_types::typenum::N4096; +use ssz_derive::{Decode, Encode}; +use ssz_types::typenum::U4096; +use tree_hash_derive::TreeHash; use crate::{ attestation::{Attestation, Attestations}, primitives::H256, + signature::Signature, }; /// 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, @@ -22,15 +26,22 @@ pub struct SignedBlockWithAttestation { 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< - // TODO: change to Signature type when available - u64, - N4096, ->; +pub type BlockSignatures = ssz_types::VariableList; /// 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, @@ -47,7 +58,7 @@ pub struct BlockWithAttestation { /// /// 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: u64, @@ -62,6 +73,7 @@ pub struct BlockHeader { } /// 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, @@ -79,6 +91,7 @@ pub struct Block { /// /// 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. /// diff --git a/crates/common/types/src/lib.rs b/crates/common/types/src/lib.rs index f6637042..d344562c 100644 --- a/crates/common/types/src/lib.rs +++ b/crates/common/types/src/lib.rs @@ -2,4 +2,5 @@ pub mod attestation; pub mod block; pub mod genesis; pub mod primitives; +pub mod signature; pub mod state; diff --git a/crates/common/types/src/signature.rs b/crates/common/types/src/signature.rs new file mode 100644 index 00000000..d1c6715a --- /dev/null +++ b/crates/common/types/src/signature.rs @@ -0,0 +1,7 @@ +use leansig::signature::SignatureScheme; + +type LeanSignatureScheme = leansig::signature::generalized_xmss::instantiations_poseidon_top_level::lifetime_2_to_the_32::hashing_optimized::SIGTopLevelTargetSumLifetime32Dim64Base8; + +type LeanSigSignature = ::Signature; + +pub type Signature = LeanSigSignature; From 301cbed0d75f7debd6dccd6187ccdb1dc94536e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:15:58 -0300 Subject: [PATCH 08/10] feat: decode received blocks --- crates/common/types/src/block.rs | 7 ++++--- crates/common/types/src/signature.rs | 3 +++ crates/net/p2p/Cargo.toml | 2 ++ crates/net/p2p/src/gossipsub.rs | 30 ++++++++++------------------ 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/crates/common/types/src/block.rs b/crates/common/types/src/block.rs index b204a1ae..8d261b7c 100644 --- a/crates/common/types/src/block.rs +++ b/crates/common/types/src/block.rs @@ -1,11 +1,11 @@ use ssz_derive::{Decode, Encode}; -use ssz_types::typenum::U4096; +use ssz_types::typenum::{Diff, U488, U3600, U4096}; use tree_hash_derive::TreeHash; use crate::{ attestation::{Attestation, Attestations}, primitives::H256, - signature::Signature, + signature::{Signature, SignatureSize}, }; /// Envelope carrying a block, an attestation from proposer, and aggregated signatures. @@ -38,7 +38,8 @@ impl core::fmt::Debug for SignedBlockWithAttestation { /// Aggregated signature list included alongside the block. /// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`]. -pub type BlockSignatures = ssz_types::VariableList; +pub type BlockSignatures = + ssz_types::VariableList, U4096>; /// Bundle containing a block and the proposer's attestation. #[derive(Debug, Clone, Encode, Decode, TreeHash)] diff --git a/crates/common/types/src/signature.rs b/crates/common/types/src/signature.rs index d1c6715a..4b91bb62 100644 --- a/crates/common/types/src/signature.rs +++ b/crates/common/types/src/signature.rs @@ -1,7 +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 = ::Signature; pub type Signature = LeanSigSignature; + +pub type SignatureSize = Diff; diff --git a/crates/net/p2p/Cargo.toml b/crates/net/p2p/Cargo.toml index 9a0e8ebd..c42b8887 100644 --- a/crates/net/p2p/Cargo.toml +++ b/crates/net/p2p/Cargo.toml @@ -34,5 +34,7 @@ tree_hash_derive = "0.12.0" sha2 = "0.10" +hex = "0.4" + [dev-dependencies] hex = "0.4" diff --git a/crates/net/p2p/src/gossipsub.rs b/crates/net/p2p/src/gossipsub.rs index 07fedffb..d2f98355 100644 --- a/crates/net/p2p/src/gossipsub.rs +++ b/crates/net/p2p/src/gossipsub.rs @@ -1,5 +1,7 @@ use crate::Behaviour; +use ethlambda_types::block::SignedBlockWithAttestation; use libp2p::gossipsub::Event; +use ssz::Decode; use tracing::{info, trace}; /// Topic kind for block gossip @@ -18,10 +20,15 @@ pub async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, even }; match message.topic.as_str().split("/").nth(3) { Some(BLOCK_TOPIC_KIND) => { - info!( - "Received block gossip message of size {} bytes", - message.data.len() - ); + let uncompressed_size = snap::raw::decompress_len(&message.data).unwrap(); + let mut uncompressed_data = vec![0u8; uncompressed_size]; + snap::raw::Decoder::new() + .decompress(&message.data, &mut uncompressed_data) + .unwrap(); + + let signed_block = + SignedBlockWithAttestation::from_ssz_bytes(&uncompressed_data).unwrap(); + info!(slot=%signed_block.message.block.slot, "Received new block"); } Some(ATTESTATION_TOPIC_KIND) => { info!( @@ -34,18 +41,3 @@ pub async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, even } } } - -#[cfg(test)] -mod tests { - #[test] - fn deserialize_gossipsub_block_message() { - let hex_data = "c4332408000000740100008c00190100120d0b7c12c1e49b1fe0e06caedc856f6ad6c112bff9ad8b72d6fbc72bec2c180fccad8011287c863ab6231eea99c326f9b38ae30b5e34d4fadf03140f34f337ffe802c45e17480d4fa2280011581101f0460d50c4cc9148a8909ae7e9bfef69b22279f050c946c3d105f3c18ec75c6e2a7a73d1756b780cdd7708fa036d3ef7d4a362cfe66d52fe8c367be30a69b91e280b54000000040000114f0400110d0a7e5800000c0d28fee0003ee000f4d40e24000000735bd71465bb7917096c3a4f2c44660e5f3bf316403d25432dfdbc3a2804000004000000a836fe5734b58e0128dd6c40afa2ed4149a98a10f488ec2fcec92f58ae69453151c86b5351b0e72c1aff205818491a77e4ba8e1f15c88f150fe25b5f4dd9417bc9eaa80818fbeb0a35d29e354bbf15438e698f23a3d5ba6dd94cbd721044b625c2cc2650107cd338d218eb4157b15441cd629f587defd63efdd1bc4c7ff3225cf7c90d14b5d9664fabb98f3a6493fb5ae65b2467b841b03021b6492eadd92448b08e30009b8eb70ddf35375c715d3138c8463b7940db312058000719e1836e5755e1c13f42a0596a4cfe975c337e9e0a148daa48ca434b03d6c3b97e2befab16f46add43e2ed966a5fa9e9686da7a77877ff6c3b21042439b7de78597243c321379fff4813e90a499895782fb1425d64948edc66901068494575982e4236ff1d53836d35a5c6c235efd45943aff19821d689b47d148a8b7926b28d080c86785f7084d90cb22a8e7c6d4e8c7a26ba5b08b521ac4a7078516854261d2eb0a4d32f13b15d57a48fcf181bf54649e2e70e6ba7124e0f6b51bc24e1edf51c5d65fa7523ae474f33078434d0778c0be47dac299917691788626249738d211b31f60f41b6025510ed770b308578605bc33b394882eac633b02d614a00ee9557af25251a39515e7b1c53b15f31db7a4ad9a3947114104a6c86c34457c0fc9b6b53f5cf3b7541c83ff81d2a1531337c51aabbb851f2c448559c220c3a5a09d209dcd3e2661640d43be7f3a727d6b31a3ef63f3401d64a743a1f6d4d4577e3d64b5187c07d5feb1d41db7c1b6916243b664b4fde6f12d5a70cc433c36b9346f05737247a29cfb22308f24b0532a781682a5527e07ad5eb044c99a741790c7add0570e26e0e59f40a534ab23a2ecfe6f44c3093ad4102596272d752ac67d982446f16fdc103b059f61b64d65750286ba81b1668e961a23f8e4d7f2cc06b06c57b0cec3de029d7d7810845052325e65f570f50384d1668c5130402ea7418c77e574e7755c07ee6b6f82ab1cd165eb086e23a997200072005ce5e1632a00b47dff55d0e3f2b6a72f7c03c99ef0824cdfa340f7aa2b8020bd97357d7281a4d39b1b059e81dfc2fe9850274381b415932623670b144ac447cec83043e764a6929deb2749a00dd13783a412b8868b66c362a8e5517679a3f97418634b22195611748f821d18b580e84c25a20f793057db5af8a4bef6b791777f625756816cd76bbb1a3051c7398511a0d53648786006941e03f44f3f96304bdcba242c6299256c5da0d77ba26231d65ff9d45a5002a2d7716514129d4fc472784d85adba8b77e55c02b2c2b5c333818901f20f9ea1356df39ae0bb410f7650416a374f3d47643f00253365d06935f5edb2d09c86d3513b108b75061ee0400969ebd76ad97f87bcbef1706294d5122c6ff903a948497015badc84e05a1706e2ac8b2217e1f645b1529ab740ec66764e26bd62f144583031b25ec64ea87ac55ad53ce1f5df416317ba4c619e35ee9488ca1d613fa90fe288b11c000ca01a50f2abeb438e9aebb71f1ebe76d411e56314eb54b3cfc09a04b43a7d51fe33042092367543209476217c27a9c415a8b0d498e2efa5816b5ee28f34c081805f628316256bc58d426522bfb63a02d1a9c443d8a177f59f711f5197d6a7d0099f1b85a104b4d61b684e6035b4889130129de03fdfd4e5fd0eafe45fc5bb96ad264067c95ada7602d99b30ffc89113c9c805b6ca8f025412aad175dc6094140745e9f29f2cca34f309e5239ca9c3f625dc8e1121f84375b0acc5d7cde0e1d508da1750bc36af01ce308081f49cae16321bb2460894bfc14b30c9b6b1444e0102bc2065ec7b644096cdb7f6a0930606f4aca3a429a7ff77165f1881339b10223a049431be0308a77dc27b346e0056c10b0cef67d669eb4157242e52ae7d66b06ffea3b7b38ff98797dd5d859433eea3c04e1741bc765f157bd271a14d875eb271289297987dd6267be1c5475fc9bf2084ef22f410caa172f3d368b525b037b6751365916eea3370fe53c6b30f772fd45e8923b42f4a1cb3e2033182a13d9514cdf0e2f2f0393066f2c061a4a8cfd0d473ce4952f2149b718cc3545498b04ba67d054ef2d32458a4e38039f5c452ae5565ddcb04bf0a3c75606433a1994672826370e942874f6aa79e9a3c7777223ce14f1323971ec58f10b75d68202a0d7884a2a0506318d97cb1ed634d1739a07de5ae42e3c54b31a4b60f049cb02fde5fc442c2ab1636c06da4c02c54b5c9964f630efee701cbfd22e21f7a0242b1a2fa82c55937c097c0c312cb4736d0648addb3692f53103cbe7902d08eaf46342fcfa54efd4b9544aac0c643ea1365324f5be4d081d000c33e40e7c7926a036565e4447f051764be725910a057c0673f8f83a639d5b2728302bba70fe8e411f1743444d88fa395aa7a1d8557f967f326cd0ca72acdee551a1cba50604308c20cedd390eecfbd03794cedf3345bc4215bc05d00c7710914aa81bc83ab79a1d0e52075b4161fc283b5cc7a73153c03f24a7d97916d350f768390a414b0e4ce03d37a79665dd3ab859922b92716b76e71d8f4f584410bac30a60d79c4a05ced442945b5253abddcf74aa6ea6094b471f3c5be25535b3f8131af6208d4d1382ff7e2ebb8c678ae49177f0251d7a07c5471088c0d9416f706028c566ca34899a5e6ac56f10267a9b9b73d873cd214ed0de7353e8ee14f3dee9031ab1251007ed68177dfbd07e4345350892bda57a3ea7630f8cc7882511928c17dc8e9650c2667c615d5e9047397e905d6b7d1940d52c884fd8ad81003608a851f682d95546c0f9571d056b4ffdaa257b16e92c1f4ac51554ccf7343e28b08f4ff20fc62bd6ddff3fbbe1d5329668f84e4e46004dd6a48e2eb0f3a47132c9785e267239764a26382755598935ec2c964aaf0cf9495fa4d718f8122a39caebf555ea79fa49d091c924409bea232421c80cf2eae507f7e5303b321f5c25fe19d24e09cbad722e1dd4006f8d7d11c18d352c5e70a929493f6f61e871f90cf4725819dbc35907e45dc8493c53836d1c9adc1ae108fe16c03d392da1d3c45e698363667851b9544092565aa57de237fc88426bf140b86280df1a60a0a966292205f1711bc11a537e9fc92453bbb962b136f1379818462aefb5583991613205b38f9364d57a3e73ec53c020f5b24d3349c39b2181b5692e5fe62c27923fda370c532b539a73bb38fb98661f8fe29d7c6d9bf611f68f3f1621ae6c5bced6c6651abf06787a04aa0fd01dca04f576ab59ca325e6f6ac2152f2362032607530c40c2cfb507ef814d79d9df7d55e3d5555cac901559721ee6379427f5437a92ef49c5f950357359114c55df9b084dd0360039cdfa3c98c5e613f97f017dcf0a3d293ded9378b3062407b5d27f1a24389709915653679db0a36e2cc79875b430bd789e8a8e142243523b75c0f379f8d9ea0a4c2bdc087255507650630c1f8086e337a20c8f54e83863501cd024686440e443714a9a117ea2e825a241a2738525d3374a6d715d9e4cb3400bc76e281e0030778ff5915dc9785e36069b7b57a066853e5a500666997a3e242e792801317e73219d17c861485d3443b9a5af5c2b840855b4b5f8350742e24eca7ee955810d203da21a5663a804bc3e3c57b3427ed7ba35b77e7d3dbe11e17184217f0d1507ee71179b11721dc9b53d270a36265b4b9d7905ea7276b5e64f57ef7887628b8a16224218d21f8e291f497ac7fa065fbb29633bdb143034d09821a4add547c490b07724207c77e5cd2202f4d1e316b582d26837983c078e3b1e11b956c0028ba7147b1f8492431654b31e7f36ba5baabceb44db76a2132b957c724376206115029308f57caf1dcb1d561f45041b5b1fb13a572e2b3824a914c40462323159e10cf02a690ef01e024acf76fd1e643718d27e034f98d53809fec31307a2ff1d74cdca619fd0087eb247953d1f66890c7fc9ea5695f42c4503442664a0223e6b14bec14504757c15dee23909c5cf8c6a1a0f6e20e527e57dfcf13a330fce8d428d96f85bafda11434a55207e62d14d0b8d8b2e3c09b0a403923397037e4b1e0ee41410582efac43c726d130165855b4318b2e31903f6bf059bceae172ad9411c0f8654098b44551eb0f58f29cd4e585c2042841a8b54803c943be2585eae20716fd4e4085f8f5434dcf5154218d57b1766942863d38ee1409e210d6bddb78e6e850c8d737ce3003a9b6d3779dd1ac02eb3a9da3bb833110bc48a9d5c750a864727a00579b328dc2995ddb107ca790f628fd463361af2ca65395a772eff90451d5e3d7317a28c8323653ad45e04f3c2522dcebb00b242750724000000fb8e0f13923b006eb3d20c1e8d9a9d1ef64dcb77ece9520949065f6e28040000040000004e881638b062bc1381f2b77670b4d808c9ee2675674aa37d30f30421b86dd46db025f254c44b902c76e2b9170d4dd63a00a33f6ce4c1333c935a3c1d66ed2118c9eaa80818fbeb0a35d29e354bbf15438e698f23a3d5ba6dd94cbd721044b625c2cc2650107cd338d218eb4157b15441cd629f587defd63efdd1bc4c7ff3225cf7c90d14b5d9664fabb98f3a6493fb5ae65b2467b841b03021b6492eadd92448b08e30009b8eb70ddf35375c715d3138c8463b7940db312058000719e1836e5755e1c13f42a0596a4cfe975c337e9e0a148daa48ca434b03d6c3b97e2befab16f46add43e2ed966a5fa9e9686da7a77877ff6c3b21042439b7de78597243c321379fff4813e90a499895782fb1425d64948edc66901068494575982e4236ff1d53836d35a5c6c235efd45943aff19821d689b47d148a8b7926b28d080c86785f7084d90cb22a8e7c6d4e8c7a26ba5b08b521ac4a7078516854261d2eb0a4d32f13b15d57a48fcf181bf54649e2e70e6ba7124e0f6b51bc24e1edf51c5d65fa7523ae474f33078434d0778c0be47dac299917691788626249738d211b31f60f41b6025510ed770b308578605bc33b394882eac633b02d614a00ee9557af25251a39515e7b1c53b15f31db7a4ad9a3947114104a6c86c34457c0fc9b6b53f5cf3b7541c83ff81d2a1531337c51aabbb851f2c448559c220c3a5a09d209dcd3e2661640d43be7f3a727d6b31a3ef63f3401d64a743a1f6d4d4577e3d64b5187c07d5feb1d41db7c1b6916243b664b4fde6f12d5a70cc433c36b9346f05737247a29cfb22308f24b0532a781682a5527e07ad5eb044c99a741790c7add0570e26e0e59f40a534ab23a2ecfe6f44c3093ad4102596272d752ac67d982446f16fdc103b059f61b64fe280cfe280cfe280cfe280cfe280cea280cf4ff07ecef9d3d69e897097b55d81843225543064f06236059774534715b693ad3d15e6c2fbc4393e8412a6b997b53840556497c008732e84fcb2721a69356b140fe6a376f0332a4a11005041a757b2518172227862c45c7b5066959cc28256883363aa4aa2a62ff6e2610c2bf43588ca64b5c3aeee66d67db08182d89de68dd93df55039a980cffb099096fb207549f35a534d1120f4ce82ab61a12563b3c2d7eae761be26d63f6492e14c366da44c5acd24082f86b1a5aedda74fca5b72e9ec8b14f41a71f08cf7343630c3ae37c3989062e4cfc923c83760a0d97d16d4bc148ac6d930740182f888233349e7d5edfb36d0a1beedd4b0bf05845e9a02c299a6d4d572ca98464ce29541266da937e9c92425b066dd50c3c3420096551321ed718db4e0c143c0e023b184559217919bad02012ddff671c0168372f928b197a4c85621099881d0215063c01450d766213c12d6111494962907ae022f56319567830181a63c61730667918301b02ad324487704914bca150b369ed72a731a00b7b81a60034f34e51b6c6bd1591586d6291e5bd025efe934d49de5f1b674163707422c80dc8269a234851b2537f4d4147ae78b765799cc15556e11d59bd692c3b31c8304646d15a2c22ff8172fb370078d62af022998a3367cffe1f61119b32092f214d0a1c1bf21f24353109950ff85f8b3944196bf43f1137b5e228790fb443abb0ac0b7fac9548e8d05863725c4055d93a2711717a711844d39c6c52a662027f97644181c23b4c2a524f41f582037bc43f8f5fbdd9cf5d935a241acd9c5c3e996b8e4bd294d8489d86430d706a546286d8e678ce92124a1c6780360f20807ca95a282f400e48678eb8145ddbf7c2243f143654a982f628e7daec284f25aa7c2e210468dbaa48165ce1d90ac896783bbbd71d1ca14cfa710eb0cc7081bda91aac85e93982cab95f8f8e46030c123a6f811ad04304e2da2da5a543220af55472605bb64c49b69e2a6df4b0241b07312a5bc7ca64bf7a1226dc54e538d42b046646962153b9ccfa679c991c65e19acd3a0b915125ce62ba421a95a92afae29d392b7a0d56ffd69a685fc02c14f363a56226c9ac2a07b10b1573ed387b490c2d546e80931c96cea43f4668964972fd040df0190d690f199f4cf7c8d03629031101c8c903203b23bf68883496241c9d35389c4b6d79f959660446eeae79c0d14f7b857b4e2eda6d5566ece155342a6bb615fbc91f686532b56bd31cc146c01482188942c44ae2a27b08bdab24029e2dcc23cb0907716931343ff177c22f54c1a94b86c23a16515d9f2682a98c0e22be48406586f372142eb85e1fcad54930a1620529461a4e0cd1f933b3c7f3502757853a0d6ee157f6f1b10076cdb91c632d0e141bad9428cc42f00aeb14c54f1f8e045f3dd4c51939cd3f666b6a4201c39a3b3b6cb7a71087920721840dd841704c5530036c845ba91c545840a09a03f25f5c778e43c921bde2c90e808d6e7c5a84b42f53c82e776f6d156295a2953ebe7b9c37185eaa2c430c01743f19cf713b61fc22ccb9161aeab98f689821464770c936461c35d634b510086b9e5c85168fe233622b282f6676126061e28d107916cd277accacfc0309f2f373f4af7d36eca2441771750360161e62244140bd780fcb9721b8245908102557523761ce659020ad123ffb114dee07c842a68b45083584a655df7ba934462a100d95f9f343ba998c3791116b3910cb296e4b6dce79d64f1017d752cb7ca032623d7bfe54715755a16ef2e5680cc5e7cc60d0c1c50e8007265d3b85063a3ad6c5571c5371729a33157220babb06d1f4dd5a7dc3b90a6d7152210afbd747f7f1a91914a3092df551911c3a38e573ce078967b0d71f55eb81633527f1162799b93e10a427c56555ccec18cecb98630ba83c501a8fcf6e637101657657a87b603a694da5cea70b8e650f1750317222510e2d6a5663c070866be362088c3e268e1c380b13b66167d7057e7ecc15641e892c89398282702ef74fb7752e515f782f8e75222980666a3abc113815c9dd7d54f8502ea2594436190e1610e5d657492d36445b5955141509692e3870ee2a3001725f4729fd581ba3ba3b7ca35ea41f339b6b657bfef609d869eb70f36c885b754cb45213b6944471fe4c636c1ff977fc35b26bcef80e0a7943d775a3741e6587481a5d65067a02f2e0926c549be56b38544630b624026212ca693119610f3c4efc7b416c47a26a81260142b125b47233d91e08b7b8b2447b03ef7a5d20273cd8e5a2536384eb519b8de3376b4f5b2b93f8416790e0ab2fd40da353c0cc4964584738283fb3175fc16d1155c16799214b687b21fc3d520c4cfbfa05ed42e500321c9c262ded192ebcf43064bf75b84c5e3ee6708de5391f6f4544222eb9940d5915892bfcd90e083767103041f78d6fcf4b5709453f1c58e471ab0c92ee0f73e5684d77541a596e02fdb25b2f27086ad65db831156d8e744980a7300b13c07c9ad6c6121750d6415640f447ac4aab2a67d66e0129ae1e48574f8137cb61ee37d2bf220f17304b001c94d62f3c5ea0614e21321285df83122a11050d46b6e60b4d011c117ad6625ee17da307bee9753a0ee33004b18d2218637c064e8fe085681f330234030b10466dac61351eeced619550925d197e526963c4da2cae6db9768504574080798a517a2fac3de4d9610b3d650a47f4b9ae15de48065dbb5a0b491198b04645924f14ec9cc922ac83da4f2d4530626a278053c28c8267ea76c238f9cd703741d4f046ad9750022a56486d2efbf639d001e665b8ecbb25b925032199962d0c744ff923de1a560e1b9d735efad5c70e1310ec7224d5e0718f5e55159ecc5630f9d43a0426567f680c4e034f"; - // Add tests for gossipsub message handling here - let data = hex::decode(hex_data).unwrap(); - let size = snap::raw::decompress_len(&data).unwrap(); - let mut buf = vec![0; size]; - let mut decoder = snap::raw::Decoder::new(); - decoder.decompress(&data, &mut buf).unwrap(); - assert!(!buf.is_empty()); - } -} From 1e6bbe4fb1061dd243a45c0b30c9d4ad930e9e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:29:07 -0300 Subject: [PATCH 09/10] fix: error handling --- crates/net/p2p/src/gossipsub.rs | 29 +++++++++++++++++++---------- crates/net/p2p/src/lib.rs | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/crates/net/p2p/src/gossipsub.rs b/crates/net/p2p/src/gossipsub.rs index d2f98355..738a37e7 100644 --- a/crates/net/p2p/src/gossipsub.rs +++ b/crates/net/p2p/src/gossipsub.rs @@ -1,15 +1,14 @@ -use crate::Behaviour; use ethlambda_types::block::SignedBlockWithAttestation; use libp2p::gossipsub::Event; use ssz::Decode; -use tracing::{info, trace}; +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(swarm: &mut libp2p::Swarm, event: Event) { +pub async fn handle_gossipsub_message(event: Event) { let Event::Message { propagation_source: _, message_id: _, @@ -20,14 +19,17 @@ pub async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, even }; match message.topic.as_str().split("/").nth(3) { Some(BLOCK_TOPIC_KIND) => { - let uncompressed_size = snap::raw::decompress_len(&message.data).unwrap(); - let mut uncompressed_data = vec![0u8; uncompressed_size]; - snap::raw::Decoder::new() - .decompress(&message.data, &mut uncompressed_data) - .unwrap(); + let Ok(uncompressed_data) = decompress_message(&message.data) + .inspect_err(|err| error!(%err, "Failed to decompress gossipped block")) + else { + return; + }; - let signed_block = - SignedBlockWithAttestation::from_ssz_bytes(&uncompressed_data).unwrap(); + 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) => { @@ -41,3 +43,10 @@ pub async fn handle_gossipsub_message(swarm: &mut libp2p::Swarm, even } } } + +fn decompress_message(data: &[u8]) -> snap::Result> { + 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) +} diff --git a/crates/net/p2p/src/lib.rs b/crates/net/p2p/src/lib.rs index b4441ace..8590a613 100644 --- a/crates/net/p2p/src/lib.rs +++ b/crates/net/p2p/src/lib.rs @@ -133,7 +133,7 @@ async fn event_loop(mut swarm: libp2p::Swarm) { SwarmEvent::Behaviour(BehaviourEvent::Gossipsub( message @ libp2p::gossipsub::Event::Message { .. }, )) => { - gossipsub::handle_gossipsub_message(&mut swarm, message).await; + gossipsub::handle_gossipsub_message(message).await; } _ => { trace!(?event, "Ignored swarm event"); From db07565a94a78c726c69822d0fdc03f0f271a91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:41:27 -0300 Subject: [PATCH 10/10] feat: add Store type --- Cargo.lock | 3 + crates/common/types/src/attestation.rs | 11 +++- crates/common/types/src/state.rs | 2 +- crates/storage/Cargo.toml | 1 + crates/storage/src/lib.rs | 85 ++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39432a69..61b0e7ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1785,6 +1785,9 @@ version = "0.1.0" [[package]] name = "ethlambda-storage" version = "0.1.0" +dependencies = [ + "ethlambda-types", +] [[package]] name = "ethlambda-types" diff --git a/crates/common/types/src/attestation.rs b/crates/common/types/src/attestation.rs index dbb50d52..4b66c623 100644 --- a/crates/common/types/src/attestation.rs +++ b/crates/common/types/src/attestation.rs @@ -2,7 +2,7 @@ use ssz_derive::{Decode, Encode}; use ssz_types::typenum::U4096; use tree_hash_derive::TreeHash; -use crate::state::Checkpoint; +use crate::{signature::Signature, state::Checkpoint}; /// Validator specific attestation wrapping shared attestation data. #[derive(Debug, Clone, Encode, Decode, TreeHash)] @@ -33,3 +33,12 @@ pub struct AttestationData { /// List of validator attestations included in a block. /// Size limited to [`crate::state::VALIDATOR_REGISTRY_LIMIT`]. pub type Attestations = ssz_types::VariableList; + +/// 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, +} diff --git a/crates/common/types/src/state.rs b/crates/common/types/src/state.rs index 234cf0f3..6b125c5e 100644 --- a/crates/common/types/src/state.rs +++ b/crates/common/types/src/state.rs @@ -8,7 +8,7 @@ use crate::{block::BlockHeader, genesis::Genesis, primitives::H256}; 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, diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 6e52d165..06cfc91a 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -10,3 +10,4 @@ rust-version.workspace = true version.workspace = true [dependencies] +ethlambda-types.workspace = true diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index e69de29b..92b9c89d 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -0,0 +1,85 @@ +use std::collections::HashMap; + +use ethlambda_types::{ + attestation::SignedAttestation, + block::Block, + primitives::H256, + state::{Checkpoint, State}, +}; + +/// Forkchoice store tracking chain state and validator attestations. +/// +/// This is the "local view" that a node uses to run LMD GHOST. It contains: +/// +/// - which blocks and states are known, +/// - which checkpoints are justified and finalized, +/// - which block is currently considered the head, +/// - and, for each validator, their latest attestation that should influence fork choice. +/// +/// The `Store` is updated whenever: +/// - a new block is processed, +/// - an attestation is received (via a block or gossip), +/// - an interval tick occurs (activating new attestations), +/// - or when the head is recomputed. +#[derive(Clone)] +pub struct Store { + /// Current time in intervals since genesis. + time: u64, + + /// Chain configuration parameters. + // config: Config, + + /// Root of the current canonical chain head block. + /// + /// This is the result of running the fork choice algorithm on the current contents of the `Store`. + head: H256, + + /// Root of the current safe target for attestation. + /// + /// This can be used by higher-level logic to restrict which blocks are + /// considered safe to attest to, based on additional safety conditions. + /// + safe_target: H256, + + /// Highest slot justified checkpoint known to the store. + /// + /// LMD GHOST starts from this checkpoint when computing the head. + /// + /// Only descendants of this checkpoint are considered viable. + latest_justified: Checkpoint, + + /// Highest slot finalized checkpoint known to the store. + /// + /// Everything strictly before this checkpoint can be considered immutable. + /// + /// Fork choice will never revert finalized history. + latest_finalized: Checkpoint, + + /// Mapping from block root to Block objects. + /// + /// This is the set of blocks that the node currently knows about. + /// + /// Every block that might participate in fork choice must appear here. + blocks: HashMap, + + /// Mapping from state root to State objects. + /// + /// For each known block, we keep its post-state. + /// + /// These states carry justified and finalized checkpoints that we use to update the + /// `Store`'s latest justified and latest finalized checkpoints. + states: HashMap, + + /// Latest signed attestations by validator that have been processed. + /// + /// - These attestations are "known" and contribute to fork choice weights. + /// - Keyed by validator index to enforce one attestation per validator. + latest_known_attestations: HashMap, + + /// Latest signed attestations by validator that are pending processing. + /// + /// - These attestations are "new" and do not yet contribute to fork choice. + /// - They migrate to `latest_known_attestations` via interval ticks. + /// - Keyed by validator index to enforce one attestation per validator. + latest_new_attestations: HashMap, +}