diff --git a/Makefile b/Makefile index 1a7a8fa..1dbd445 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 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/handlers.rs b/crates/net/p2p/src/req_resp/handlers.rs index 026ae9d..336ffc2 100644 --- a/crates/net/p2p/src/req_resp/handlers.rs +++ b/crates/net/p2p/src/req_resp/handlers.rs @@ -13,7 +13,7 @@ use super::{ }; use crate::{ BACKOFF_MULTIPLIER, INITIAL_BACKOFF_MS, MAX_FETCH_RETRIES, P2PServer, PendingRequest, - RetryMessage, + RetryMessage, req_resp::RequestedBlockRoots, }; pub async fn handle_req_resp_message( @@ -99,7 +99,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 @@ -166,11 +166,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 = 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..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; @@ -46,4 +47,61 @@ 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, +} + +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); + } +} 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, };