From 08444281c20d323cc16704ad66b3c6031fd9dc4a 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, 28 Jan 2026 12:54:40 -0300 Subject: [PATCH 1/8] chore: bump leanSpec to use latest fixtures --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1a7a8fa..92a850f 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ help: ## ๐Ÿ“š Show help for each of the Makefile recipes lint: ## ๐Ÿ” Run clippy on all workspace crates cargo clippy --workspace --all-targets -- -D warnings -test: ## ๐Ÿงช Run all tests, then forkchoice tests with skip-signature-verification +test: leanSpec/fixtures ## ๐Ÿงช Run all tests, then forkchoice tests with skip-signature-verification # Tests need to be run on release to avoid stack overflows during signature verification/aggregation cargo test --workspace --release cargo test -p ethlambda-blockchain --features skip-signature-verification --test forkchoice_spectests @@ -20,7 +20,7 @@ docker-build: ## ๐Ÿณ Build the Docker image --build-arg GIT_BRANCH=$(GIT_BRANCH) \ -t ghcr.io/lambdaclass/ethlambda:local . -LEAN_SPEC_COMMIT_HASH:=4edcf7bc9271e6a70ded8aff17710d68beac4266 +LEAN_SPEC_COMMIT_HASH:=531c2c23c556354aca33c49fbf48558b8f1ec878 leanSpec: git clone https://github.com/leanEthereum/leanSpec.git --single-branch From 9766f79c56e05e5fa10b0c0ff87a9949d1f9dce2 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, 28 Jan 2026 15:47:45 -0300 Subject: [PATCH 2/8] fix: change BlocksByRoot request de/serialization The request type was changed in https://github.com/leanEthereum/leanSpec/commit/054b2e95ace24655a8fa85397ca8813f539e5b2e --- crates/net/p2p/src/req_resp/handlers.rs | 7 ++++--- crates/net/p2p/src/req_resp/messages.rs | 7 ++++++- crates/net/p2p/src/req_resp/mod.rs | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/net/p2p/src/req_resp/handlers.rs b/crates/net/p2p/src/req_resp/handlers.rs index d8e670c..16c6716 100644 --- a/crates/net/p2p/src/req_resp/handlers.rs +++ b/crates/net/p2p/src/req_resp/handlers.rs @@ -96,7 +96,7 @@ async fn handle_blocks_by_root_request( _channel: request_response::ResponseChannel, peer: PeerId, ) { - let num_roots = request.len(); + let num_roots = request.roots.len(); info!(%peer, num_roots, "Received BlocksByRoot request"); // TODO: Implement signed block storage and send response chunks @@ -163,11 +163,12 @@ pub async fn fetch_block_from_peer( }; // Create BlocksByRoot request with single root - let mut request = BlocksByRootRequest::empty(); - if let Err(err) = request.push(root) { + let mut roots = super::RequestedBlockRoots::empty(); + if let Err(err) = roots.push(root) { error!(%root, ?err, "Failed to create BlocksByRoot request"); return false; } + let request = BlocksByRootRequest { roots }; info!(%peer, %root, "Sending BlocksByRoot request for missing block"); let request_id = server diff --git a/crates/net/p2p/src/req_resp/messages.rs b/crates/net/p2p/src/req_resp/messages.rs index 91cc70b..c9b3e14 100644 --- a/crates/net/p2p/src/req_resp/messages.rs +++ b/crates/net/p2p/src/req_resp/messages.rs @@ -46,4 +46,9 @@ pub struct Status { type MaxRequestBlocks = typenum::U1024; -pub type BlocksByRootRequest = ssz_types::VariableList; +pub type RequestedBlockRoots = ssz_types::VariableList; + +#[derive(Debug, Clone, Encode, Decode)] +pub struct BlocksByRootRequest { + pub roots: RequestedBlockRoots, +} diff --git a/crates/net/p2p/src/req_resp/mod.rs b/crates/net/p2p/src/req_resp/mod.rs index 654cba2..f78493f 100644 --- a/crates/net/p2p/src/req_resp/mod.rs +++ b/crates/net/p2p/src/req_resp/mod.rs @@ -7,6 +7,6 @@ pub use codec::Codec; pub use encoding::MAX_COMPRESSED_PAYLOAD_SIZE; pub use handlers::{build_status, fetch_block_from_peer, handle_req_resp_message}; pub use messages::{ - BLOCKS_BY_ROOT_PROTOCOL_V1, BlocksByRootRequest, Request, Response, ResponsePayload, - ResponseResult, STATUS_PROTOCOL_V1, Status, + BLOCKS_BY_ROOT_PROTOCOL_V1, BlocksByRootRequest, Request, RequestedBlockRoots, Response, + ResponsePayload, ResponseResult, STATUS_PROTOCOL_V1, Status, }; From 448b1a21890cd3e76449d8144f8b14a7ef16ecff 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, 28 Jan 2026 15:52:02 -0300 Subject: [PATCH 3/8] refactor: clean up --- crates/net/p2p/src/req_resp/handlers.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/net/p2p/src/req_resp/handlers.rs b/crates/net/p2p/src/req_resp/handlers.rs index 16c6716..8df9105 100644 --- a/crates/net/p2p/src/req_resp/handlers.rs +++ b/crates/net/p2p/src/req_resp/handlers.rs @@ -11,7 +11,10 @@ use super::{ BLOCKS_BY_ROOT_PROTOCOL_V1, BlocksByRootRequest, Request, Response, ResponsePayload, ResponseResult, Status, }; -use crate::{BACKOFF_MULTIPLIER, INITIAL_BACKOFF_MS, MAX_FETCH_RETRIES, P2PServer, PendingRequest}; +use crate::{ + BACKOFF_MULTIPLIER, INITIAL_BACKOFF_MS, MAX_FETCH_RETRIES, P2PServer, PendingRequest, + req_resp::RequestedBlockRoots, +}; pub async fn handle_req_resp_message( server: &mut P2PServer, @@ -163,7 +166,7 @@ pub async fn fetch_block_from_peer( }; // Create BlocksByRoot request with single root - let mut roots = super::RequestedBlockRoots::empty(); + let mut roots = RequestedBlockRoots::empty(); if let Err(err) = roots.push(root) { error!(%root, ?err, "Failed to create BlocksByRoot request"); return false; From 68097152dc0232ba7b7eb3e1157f575741e1a05e 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, 28 Jan 2026 15:52:36 -0300 Subject: [PATCH 4/8] Revert "chore: bump leanSpec to use latest fixtures" This reverts commit 08444281c20d323cc16704ad66b3c6031fd9dc4a. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 92a850f..1dbd445 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ docker-build: ## ๐Ÿณ Build the Docker image --build-arg GIT_BRANCH=$(GIT_BRANCH) \ -t ghcr.io/lambdaclass/ethlambda:local . -LEAN_SPEC_COMMIT_HASH:=531c2c23c556354aca33c49fbf48558b8f1ec878 +LEAN_SPEC_COMMIT_HASH:=4edcf7bc9271e6a70ded8aff17710d68beac4266 leanSpec: git clone https://github.com/leanEthereum/leanSpec.git --single-branch From 3975a1b8c24da6c30ff7ebb24f5f19a8175c1d98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:38:53 -0300 Subject: [PATCH 5/8] chore: fmt --- crates/net/p2p/src/req_resp/handlers.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/net/p2p/src/req_resp/handlers.rs b/crates/net/p2p/src/req_resp/handlers.rs index 136cbaa..336ffc2 100644 --- a/crates/net/p2p/src/req_resp/handlers.rs +++ b/crates/net/p2p/src/req_resp/handlers.rs @@ -13,8 +13,7 @@ use super::{ }; use crate::{ BACKOFF_MULTIPLIER, INITIAL_BACKOFF_MS, MAX_FETCH_RETRIES, P2PServer, PendingRequest, - req_resp::RequestedBlockRoots, - RetryMessage, + RetryMessage, req_resp::RequestedBlockRoots, }; pub async fn handle_req_resp_message( From 163f315cae1b04a3658292714f210c9e2e539010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:53:03 -0300 Subject: [PATCH 6/8] chore: revert to old encoding for now --- crates/net/p2p/src/req_resp/messages.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/net/p2p/src/req_resp/messages.rs b/crates/net/p2p/src/req_resp/messages.rs index c9b3e14..95256cc 100644 --- a/crates/net/p2p/src/req_resp/messages.rs +++ b/crates/net/p2p/src/req_resp/messages.rs @@ -49,6 +49,8 @@ type MaxRequestBlocks = typenum::U1024; pub type RequestedBlockRoots = ssz_types::VariableList; #[derive(Debug, Clone, Encode, Decode)] +// TODO: remove this for devnet 3 +#[ssz(struct_behaviour = "transparent")] pub struct BlocksByRootRequest { pub roots: RequestedBlockRoots, } From a0bcbb7a07b6d10041cdd914a155a1527ecea416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:56:25 -0300 Subject: [PATCH 7/8] Revert "chore: revert to old encoding for now" This reverts commit 163f315cae1b04a3658292714f210c9e2e539010. --- crates/net/p2p/src/req_resp/messages.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/net/p2p/src/req_resp/messages.rs b/crates/net/p2p/src/req_resp/messages.rs index 95256cc..c9b3e14 100644 --- a/crates/net/p2p/src/req_resp/messages.rs +++ b/crates/net/p2p/src/req_resp/messages.rs @@ -49,8 +49,6 @@ type MaxRequestBlocks = typenum::U1024; pub type RequestedBlockRoots = ssz_types::VariableList; #[derive(Debug, Clone, Encode, Decode)] -// TODO: remove this for devnet 3 -#[ssz(struct_behaviour = "transparent")] pub struct BlocksByRootRequest { pub roots: RequestedBlockRoots, } From f79a17eae1774644e9356c39fcd4c6b1c13b5bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:53:16 -0300 Subject: [PATCH 8/8] fix: support both SSZ formats --- crates/net/p2p/src/req_resp/codec.rs | 7 ++-- crates/net/p2p/src/req_resp/messages.rs | 53 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/net/p2p/src/req_resp/codec.rs b/crates/net/p2p/src/req_resp/codec.rs index e50775f..e67e2e1 100644 --- a/crates/net/p2p/src/req_resp/codec.rs +++ b/crates/net/p2p/src/req_resp/codec.rs @@ -41,9 +41,10 @@ impl libp2p::request_response::Codec for Codec { Ok(Request::Status(status)) } BLOCKS_BY_ROOT_PROTOCOL_V1 => { - let request = BlocksByRootRequest::from_ssz_bytes(&payload).map_err(|err| { - io::Error::new(io::ErrorKind::InvalidData, format!("{err:?}")) - })?; + let request = + BlocksByRootRequest::from_ssz_bytes_compat(&payload).map_err(|err| { + io::Error::new(io::ErrorKind::InvalidData, format!("{err:?}")) + })?; Ok(Request::BlocksByRoot(request)) } _ => Err(io::Error::new( diff --git a/crates/net/p2p/src/req_resp/messages.rs b/crates/net/p2p/src/req_resp/messages.rs index c9b3e14..f5fc199 100644 --- a/crates/net/p2p/src/req_resp/messages.rs +++ b/crates/net/p2p/src/req_resp/messages.rs @@ -1,4 +1,5 @@ use ethlambda_types::{block::SignedBlockWithAttestation, primitives::H256, state::Checkpoint}; +use ssz::Decode as SszDecode; use ssz_derive::{Decode, Encode}; use ssz_types::typenum; @@ -52,3 +53,55 @@ pub type RequestedBlockRoots = ssz_types::VariableList; pub struct BlocksByRootRequest { pub roots: RequestedBlockRoots, } + +impl BlocksByRootRequest { + /// Decode from SSZ bytes with backward compatibility. + /// + /// Tries to decode as new format (container with `roots` field) first. + /// Falls back to old format (transparent - direct list of roots) if that fails. + pub fn from_ssz_bytes_compat(bytes: &[u8]) -> Result { + // Try new format (container) first + SszDecode::from_ssz_bytes(bytes).or_else(|_| { + // Fall back to old format (transparent/direct list) + SszDecode::from_ssz_bytes(bytes).map(|roots| Self { roots }) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use ssz::Encode as SszEncode; + + #[test] + fn test_blocks_by_root_backward_compatibility() { + // Create some test roots + let root1 = H256::from_slice(&[1u8; 32]); + let root2 = H256::from_slice(&[2u8; 32]); + let roots_list = + RequestedBlockRoots::new(vec![root1, root2]).expect("Failed to create roots list"); + + // Encode as old format (direct list, similar to transparent) + let old_format_bytes = roots_list.as_ssz_bytes(); + + // Encode as new format (container) + let new_request = BlocksByRootRequest { + roots: roots_list.clone(), + }; + let new_format_bytes = new_request.as_ssz_bytes(); + + // Both formats should decode successfully + let decoded_from_old = BlocksByRootRequest::from_ssz_bytes_compat(&old_format_bytes) + .expect("Failed to decode old format"); + let decoded_from_new = BlocksByRootRequest::from_ssz_bytes_compat(&new_format_bytes) + .expect("Failed to decode new format"); + + // Both should have the same roots + assert_eq!(decoded_from_old.roots.len(), 2); + assert_eq!(decoded_from_new.roots.len(), 2); + assert_eq!(decoded_from_old.roots[0], root1); + assert_eq!(decoded_from_old.roots[1], root2); + assert_eq!(decoded_from_new.roots[0], root1); + assert_eq!(decoded_from_new.roots[1], root2); + } +}