From ec74795ed594f7f3d052c4e0d1d95558cf955916 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 26 Apr 2026 12:16:32 +0800 Subject: [PATCH 1/5] test(network): cover BAL soft limit requests --- crates/net/network/tests/it/requests.rs | 110 +++++++++++++++++------- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 8877c4864d6..e44ff72c676 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -2,12 +2,13 @@ //! Tests for eth related requests use alloy_consensus::Header; -use alloy_primitives::Bytes; +use alloy_primitives::{Bytes, B256}; use rand::Rng; use reth_eth_wire::{BlockAccessLists, EthVersion, GetBlockAccessLists, HeadersDirection}; use reth_ethereum_primitives::Block; use reth_network::{ - test_utils::{NetworkEventStream, PeerConfig, Testnet}, + eth_requests::SOFT_RESPONSE_LIMIT, + test_utils::{NetworkEventStream, PeerConfig, Testnet, TestnetHandle}, BlockDownloaderProvider, NetworkEventListenerProvider, }; use reth_network_api::{NetworkInfo, Peers}; @@ -20,6 +21,8 @@ use reth_transaction_pool::test_utils::{TestPool, TransactionGenerator}; use std::sync::Arc; use tokio::sync::oneshot; +type Eth71BalTestnetHandle = TestnetHandle, TestPool>; + #[tokio::test(flavor = "multi_thread")] async fn test_get_body() { reth_tracing::init_test_tracing(); @@ -531,7 +534,47 @@ async fn test_eth69_get_receipts() { #[tokio::test(flavor = "multi_thread")] async fn test_eth71_get_block_access_lists() { reth_tracing::init_test_tracing(); - let mut rng = rand::rng(); + let (net, bal_store) = spawn_eth71_bal_testnet().await; + + let hash0 = B256::random(); + let hash1 = B256::random(); + let hash2 = B256::random(); + let bal0 = Bytes::from_static(&[0xc1, 0x01]); + let bal2 = Bytes::from_static(&[0xc1, 0x02]); + + bal_store.insert(hash0, 1, bal0.clone()).unwrap(); + bal_store.insert(hash2, 3, bal2.clone()).unwrap(); + + let response = request_block_access_lists(&net, vec![hash0, hash1, hash2]).await; + assert_eq!( + response, + BlockAccessLists(vec![bal0, Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]), bal2,]) + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth71_get_block_access_lists_respects_response_soft_limit() { + reth_tracing::init_test_tracing(); + let (net, bal_store) = spawn_eth71_bal_testnet().await; + + let hash0 = B256::random(); + let hash1 = B256::random(); + let hash2 = B256::random(); + let bal0 = raw_bal_with_len(2); + let bal1 = raw_bal_with_len(SOFT_RESPONSE_LIMIT); + let bal2 = raw_bal_with_len(2); + assert!(bal0.len() + bal1.len() > SOFT_RESPONSE_LIMIT); + + bal_store.insert(hash0, 1, bal0.clone()).unwrap(); + bal_store.insert(hash1, 2, bal1.clone()).unwrap(); + bal_store.insert(hash2, 3, bal2).unwrap(); + + let response = request_block_access_lists(&net, vec![hash0, hash1, hash2]).await; + + assert_eq!(response, BlockAccessLists(vec![bal0, bal1])); +} + +async fn spawn_eth71_bal_testnet() -> (Eth71BalTestnetHandle, BalStoreHandle) { let mut mock_provider = MockEthProvider::default(); let bal_store = BalStoreHandle::new(InMemoryBalStore::default()); mock_provider.bal_store = bal_store.clone(); @@ -542,42 +585,51 @@ async fn test_eth71_get_block_access_lists() { let p0 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth71.into())); net.add_peer_with_config(p0).await.unwrap(); - let p1 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth71.into())); + let p1 = PeerConfig::with_protocols(mock_provider, Some(EthVersion::Eth71.into())); net.add_peer_with_config(p1).await.unwrap(); net.for_each_mut(|peer| peer.install_request_handler()); - let handle0 = net.peers()[0].handle(); - let mut events0 = NetworkEventStream::new(handle0.event_listener()); - let handle1 = net.peers()[1].handle(); - - let _handle = net.spawn(); + let net = net.spawn(); + net.connect_peers().await; - handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); - let connected = events0.next_session_established().await.unwrap(); - assert_eq!(connected, *handle1.peer_id()); - - let hash0 = rng.random(); - let hash1 = rng.random(); - let hash2 = rng.random(); - let bal0 = Bytes::from_static(&[0xc1, 0x01]); - let bal2 = Bytes::from_static(&[0xc1, 0x02]); - - bal_store.insert(hash0, 1, bal0.clone()).unwrap(); - bal_store.insert(hash2, 3, bal2.clone()).unwrap(); + (net, bal_store) +} +async fn request_block_access_lists( + net: &Eth71BalTestnetHandle, + hashes: Vec, +) -> BlockAccessLists { + let requester = &net.peers()[0]; + let responder = &net.peers()[1]; let (tx, rx) = oneshot::channel(); - handle0.send_request( - *handle1.peer_id(), + + requester.network().send_request( + *responder.peer_id(), reth_network::PeerRequest::GetBlockAccessLists { - request: GetBlockAccessLists(vec![hash0, hash1, hash2]), + request: GetBlockAccessLists(hashes), response: tx, }, ); - let response = rx.await.unwrap().unwrap(); - assert_eq!( - response, - BlockAccessLists(vec![bal0, Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE]), bal2,]) - ); + rx.await.unwrap().unwrap() +} + +fn raw_bal_with_len(len: usize) -> Bytes { + assert!(len > 0); + + let mut payload_length = len - 1; + loop { + let header_length = alloy_rlp::Header { list: true, payload_length }.length(); + let next_payload_length = len.checked_sub(header_length).unwrap(); + if next_payload_length == payload_length { + break + } + payload_length = next_payload_length; + } + + let mut out = Vec::with_capacity(len); + alloy_rlp::Header { list: true, payload_length }.encode(&mut out); + out.resize(len, alloy_rlp::EMPTY_LIST_CODE); + Bytes::from(out) } From 9d7062967080850984729764c2b2c852aa75d841 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 26 Apr 2026 12:52:21 +0800 Subject: [PATCH 2/5] test(network): expand BAL request coverage --- crates/net/network/tests/it/requests.rs | 88 +++++++++++++++++++++---- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index e44ff72c676..04b9aa4c498 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -14,14 +14,16 @@ use reth_network::{ use reth_network_api::{NetworkInfo, Peers}; use reth_network_p2p::{ bodies::client::BodiesClient, + error::RequestError, headers::client::{HeadersClient, HeadersRequest}, + BalRequirement, BlockAccessListsClient, }; use reth_provider::{test_utils::MockEthProvider, BalStoreHandle, InMemoryBalStore}; use reth_transaction_pool::test_utils::{TestPool, TransactionGenerator}; use std::sync::Arc; use tokio::sync::oneshot; -type Eth71BalTestnetHandle = TestnetHandle, TestPool>; +type BalTestnetHandle = TestnetHandle, TestPool>; #[tokio::test(flavor = "multi_thread")] async fn test_get_body() { @@ -574,7 +576,75 @@ async fn test_eth71_get_block_access_lists_respects_response_soft_limit() { assert_eq!(response, BlockAccessLists(vec![bal0, bal1])); } -async fn spawn_eth71_bal_testnet() -> (Eth71BalTestnetHandle, BalStoreHandle) { +#[tokio::test(flavor = "multi_thread")] +async fn test_eth71_get_block_access_lists_returns_single_oversized_bal() { + reth_tracing::init_test_tracing(); + let (net, bal_store) = spawn_eth71_bal_testnet().await; + + let hash0 = B256::random(); + let hash1 = B256::random(); + let bal0 = raw_bal_with_len(SOFT_RESPONSE_LIMIT + 1); + let bal1 = raw_bal_with_len(2); + + bal_store.insert(hash0, 1, bal0.clone()).unwrap(); + bal_store.insert(hash1, 2, bal1).unwrap(); + + let response = request_block_access_lists(&net, vec![hash0, hash1]).await; + + assert_eq!(response, BlockAccessLists(vec![bal0])); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth71_get_block_access_lists_empty_request() { + reth_tracing::init_test_tracing(); + let (net, _) = spawn_eth71_bal_testnet().await; + + let response = request_block_access_lists(&net, Vec::new()).await; + + assert_eq!(response, BlockAccessLists(Vec::new())); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth71_fetch_client_get_block_access_lists() { + reth_tracing::init_test_tracing(); + let (net, bal_store) = spawn_eth71_bal_testnet().await; + + let hash0 = B256::random(); + let hash1 = B256::random(); + let bal0 = Bytes::from_static(&[0xc1, 0x01]); + + bal_store.insert(hash0, 1, bal0.clone()).unwrap(); + + let fetch = net.peers()[0].network().fetch_client().await.unwrap(); + let response = fetch.get_block_access_lists(vec![hash0, hash1]).await.unwrap().into_data(); + + assert_eq!( + response, + BlockAccessLists(vec![bal0, Bytes::from_static(&[alloy_rlp::EMPTY_LIST_CODE])]) + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth70_fetch_client_rejects_optional_block_access_lists_request() { + reth_tracing::init_test_tracing(); + let (net, _) = spawn_bal_testnet([EthVersion::Eth70, EthVersion::Eth70]).await; + + let fetch = net.peers()[0].network().fetch_client().await.unwrap(); + let err = fetch + .get_block_access_lists_with_requirement(vec![B256::random()], BalRequirement::Optional) + .await + .unwrap_err(); + + assert_eq!(err, RequestError::UnsupportedCapability); +} + +async fn spawn_eth71_bal_testnet() -> (BalTestnetHandle, BalStoreHandle) { + spawn_bal_testnet([EthVersion::Eth71, EthVersion::Eth71]).await +} + +async fn spawn_bal_testnet( + versions: impl IntoIterator, +) -> (BalTestnetHandle, BalStoreHandle) { let mut mock_provider = MockEthProvider::default(); let bal_store = BalStoreHandle::new(InMemoryBalStore::default()); mock_provider.bal_store = bal_store.clone(); @@ -582,11 +652,10 @@ async fn spawn_eth71_bal_testnet() -> (Eth71BalTestnetHandle, BalStoreHandle) { let mut net: Testnet, TestPool> = Testnet::default(); - let p0 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth71.into())); - net.add_peer_with_config(p0).await.unwrap(); - - let p1 = PeerConfig::with_protocols(mock_provider, Some(EthVersion::Eth71.into())); - net.add_peer_with_config(p1).await.unwrap(); + for version in versions { + let peer = PeerConfig::with_protocols(mock_provider.clone(), Some(version.into())); + net.add_peer_with_config(peer).await.unwrap(); + } net.for_each_mut(|peer| peer.install_request_handler()); @@ -596,10 +665,7 @@ async fn spawn_eth71_bal_testnet() -> (Eth71BalTestnetHandle, BalStoreHandle) { (net, bal_store) } -async fn request_block_access_lists( - net: &Eth71BalTestnetHandle, - hashes: Vec, -) -> BlockAccessLists { +async fn request_block_access_lists(net: &BalTestnetHandle, hashes: Vec) -> BlockAccessLists { let requester = &net.peers()[0]; let responder = &net.peers()[1]; let (tx, rx) = oneshot::channel(); From 345feb8939a07c64073a59291321e45078018152 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 26 Apr 2026 14:09:38 +0800 Subject: [PATCH 3/5] test(network): document BAL request cases --- crates/net/network/tests/it/requests.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 04b9aa4c498..3621e063580 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -554,6 +554,7 @@ async fn test_eth71_get_block_access_lists() { ); } +// Ensures BAL responses stop at the soft response limit while keeping the item that crosses it. #[tokio::test(flavor = "multi_thread")] async fn test_eth71_get_block_access_lists_respects_response_soft_limit() { reth_tracing::init_test_tracing(); @@ -576,6 +577,7 @@ async fn test_eth71_get_block_access_lists_respects_response_soft_limit() { assert_eq!(response, BlockAccessLists(vec![bal0, bal1])); } +// Ensures a single BAL larger than the soft limit is still returned. #[tokio::test(flavor = "multi_thread")] async fn test_eth71_get_block_access_lists_returns_single_oversized_bal() { reth_tracing::init_test_tracing(); @@ -594,6 +596,7 @@ async fn test_eth71_get_block_access_lists_returns_single_oversized_bal() { assert_eq!(response, BlockAccessLists(vec![bal0])); } +// Ensures an empty BAL request roundtrips to an empty response. #[tokio::test(flavor = "multi_thread")] async fn test_eth71_get_block_access_lists_empty_request() { reth_tracing::init_test_tracing(); @@ -604,6 +607,7 @@ async fn test_eth71_get_block_access_lists_empty_request() { assert_eq!(response, BlockAccessLists(Vec::new())); } +// Ensures the fetch client can request BALs through an eth/71 peer. #[tokio::test(flavor = "multi_thread")] async fn test_eth71_fetch_client_get_block_access_lists() { reth_tracing::init_test_tracing(); @@ -624,6 +628,7 @@ async fn test_eth71_fetch_client_get_block_access_lists() { ); } +// Ensures fetch client BAL requests are rejected when no eth/71 peer is available. #[tokio::test(flavor = "multi_thread")] async fn test_eth70_fetch_client_rejects_optional_block_access_lists_request() { reth_tracing::init_test_tracing(); From b88f1ef1cc7e47688128472751087c7c14b07651 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 26 Apr 2026 14:27:28 +0800 Subject: [PATCH 4/5] test(network): document BAL helpers --- crates/net/network/tests/it/requests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 3621e063580..34eeccc69ab 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -647,6 +647,7 @@ async fn spawn_eth71_bal_testnet() -> (BalTestnetHandle, BalStoreHandle) { spawn_bal_testnet([EthVersion::Eth71, EthVersion::Eth71]).await } +// Spawns a two-peer BAL testnet with the requested eth protocol versions. async fn spawn_bal_testnet( versions: impl IntoIterator, ) -> (BalTestnetHandle, BalStoreHandle) { @@ -670,6 +671,7 @@ async fn spawn_bal_testnet( (net, bal_store) } +// Sends a GetBlockAccessLists request from peer 0 to peer 1. async fn request_block_access_lists(net: &BalTestnetHandle, hashes: Vec) -> BlockAccessLists { let requester = &net.peers()[0]; let responder = &net.peers()[1]; @@ -686,6 +688,7 @@ async fn request_block_access_lists(net: &BalTestnetHandle, hashes: Vec) - rx.await.unwrap().unwrap() } +// Builds a complete raw RLP list item with the requested encoded byte length. fn raw_bal_with_len(len: usize) -> Bytes { assert!(len > 0); From ceac3e01b9e5b4b865ba26e0a2cdc75aa5d016c6 Mon Sep 17 00:00:00 2001 From: Karl Date: Sun, 26 Apr 2026 14:47:44 +0800 Subject: [PATCH 5/5] test(network): clarify BAL testnet helper --- crates/net/network/tests/it/requests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 34eeccc69ab..6cca06bb1cb 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -647,7 +647,7 @@ async fn spawn_eth71_bal_testnet() -> (BalTestnetHandle, BalStoreHandle) { spawn_bal_testnet([EthVersion::Eth71, EthVersion::Eth71]).await } -// Spawns a two-peer BAL testnet with the requested eth protocol versions. +// Spawns a BAL testnet with one peer per requested eth protocol version. async fn spawn_bal_testnet( versions: impl IntoIterator, ) -> (BalTestnetHandle, BalStoreHandle) {