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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions crates/net/p2p/src/req_resp/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Comments

Code Correctness and Potential Bugs

  1. Function Call: The change from from_ssz_bytes to from_ssz_bytes_compat (lines 43-44) implies a modified function for deserialization. Ensure that this new function is validated against the SSZ standards. Verify that from_ssz_bytes_compat is fully compatible with the expected data structure, especially if this data is involved in consensus-critical operations.

Security Vulnerabilities

  • Deserialization Handling: While there's handling of the SSZ payload with error mapping to io::Error, it's crucial to ensure that from_ssz_bytes_compat itself does not introduce any vulnerabilities or unchecked assumptions about the input. Consider running fuzz tests to ensure robustness against malformed inputs.

Rust Best Practices and Idiomatic Patterns

  • No obvious violations of Rust best practices seen in the provided snippet. The use of map_err for error handling is commendable.

Memory Safety and Proper Error Handling

  • Error Handling: The current error mapping is appropriate, but consider using a more descriptive error context where possible. Details can help when debugging.

Code Readability and Maintainability

  • Code Comments: Adding a brief comment explaining the switch to from_ssz_bytes_compat would be beneficial for future maintainability.

Consensus-Layer Considerations

  • SSZ Decoding: Ensure extensive testing of from_ssz_bytes_compat if it affects block processing. Differences in serialization interpretation can potentially lead to consensus failures.

Overall, while the change seems minimal, the implications on consensus and security require careful validation. Double-check the compatibility and testing of the from_ssz_bytes_compat function before merging.

Expand Down
9 changes: 5 additions & 4 deletions crates/net/p2p/src/req_resp/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -99,7 +99,7 @@ async fn handle_blocks_by_root_request(
_channel: request_response::ResponseChannel<Response>,
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
Expand Down Expand Up @@ -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
Expand Down
60 changes: 59 additions & 1 deletion crates/net/p2p/src/req_resp/messages.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -46,4 +47,61 @@ pub struct Status {

type MaxRequestBlocks = typenum::U1024;

pub type BlocksByRootRequest = ssz_types::VariableList<H256, MaxRequestBlocks>;
pub type RequestedBlockRoots = ssz_types::VariableList<H256, MaxRequestBlocks>;

#[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<Self, ssz::DecodeError> {
// 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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Correctness and Potential Bugs

  • The implementation correctly attempts to decode using a new format first, then falls back to an older format if decoding fails. However, there is a lack of detailed logging for failure causes, which can help in debugging if the decoding process fails.

Security Vulnerabilities

  • The code doesn’t present any immediate security concerns, but handling untrusted input during the decoding process is critical. Make sure that ssz::Decode handles cases like oversized input or malformed data securely without panicking or exposing vulnerabilities.

Performance Implications

  • The use of or_else for fallback decoding doesn't incur significant overhead beyond the required decoding operations. However, please ensure that the ssz decoding performance is sufficient for production loads.

Rust Best Practices and Idiomatic Patterns

  • Consider adding an expect_err or similar debug logging when the fallback decoding succeeds after a primary decoding failure. This will make your tests more robust and easier to debug.

Memory Safety and Proper Error Handling

  • Error handling seems adequate with the structured Result return type. However, ensure that all errors are adequately documented, especially since this function interfaces with serialized data.

Code Readability and Maintainability

  • The code is generally readable. Consider adding comments to the from_ssz_bytes_compat function to clarify the fallback behavior for maintenance purposes.

Consensus Layer Considerations

  • Ensure that the SSZ encoding and decoding is aligned with Ethereum's consensus layer specification to prevent deserialization issues.
  • There are no indications of consensus-layer functionality (like fork choice or state transitions) being impacted directly by this change.

Additional Recommendations

  • Consider using feature flags or versioning comments to facilitate transitions between decoding structures as Ethereum evolves.
  • Expand test coverage to include malformed and edge case inputs to ensure robustness against unexpected input.

Overall, the code introduces a necessary compatibility feature but could be improved with better logging and more comprehensive test cases for malformed input.

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);
}
}
4 changes: 2 additions & 2 deletions crates/net/p2p/src/req_resp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Loading