Remove peer sampling code (#7768)

Peer sampling has been completely removed from the spec. This PR removes our partial implementation from the codebase.
https://github.com/ethereum/consensus-specs/pull/4393
This commit is contained in:
Jimmy Chen
2025-07-23 13:24:45 +10:00
committed by GitHub
parent c4b973f5ba
commit 4daa015971
17 changed files with 11 additions and 1509 deletions

View File

@@ -4,8 +4,7 @@ use crate::sync::block_lookups::{
};
use crate::sync::{
manager::{BlockProcessType, BlockProcessingResult, SyncManager},
peer_sampling::SamplingConfig,
SamplingId, SyncMessage,
SyncMessage,
};
use crate::NetworkMessage;
use std::sync::Arc;
@@ -33,7 +32,7 @@ use lighthouse_network::{
rpc::{RPCError, RequestType, RpcErrorResponse},
service::api_types::{
AppRequestId, DataColumnsByRootRequestId, DataColumnsByRootRequester, Id,
SamplingRequester, SingleLookupReqId, SyncRequestId,
SingleLookupReqId, SyncRequestId,
},
types::SyncState,
NetworkConfig, NetworkGlobals, PeerId,
@@ -50,7 +49,6 @@ use types::{
const D: Duration = Duration::new(0, 0);
const PARENT_FAIL_TOLERANCE: u8 = SINGLE_BLOCK_LOOKUP_MAX_ATTEMPTS;
const SAMPLING_REQUIRED_SUCCESSES: usize = 2;
type DCByRootIds = Vec<DCByRootId>;
type DCByRootId = (SyncRequestId, Vec<ColumnIndex>);
@@ -124,9 +122,6 @@ impl TestRig {
beacon_processor.into(),
// Pass empty recv not tied to any tx
mpsc::unbounded_channel().1,
SamplingConfig::Custom {
required_successes: vec![SAMPLING_REQUIRED_SUCCESSES],
},
fork_context,
),
harness,
@@ -180,10 +175,6 @@ impl TestRig {
));
}
fn trigger_sample_block(&mut self, block_root: Hash256, block_slot: Slot) {
self.send_sync_message(SyncMessage::SampleBlock(block_root, block_slot))
}
/// Drain all sync messages in the sync_rx attached to the beacon processor
fn drain_sync_rx(&mut self) {
while let Ok(sync_message) = self.sync_rx.try_recv() {
@@ -260,27 +251,6 @@ impl TestRig {
);
}
fn expect_no_active_sampling(&mut self) {
assert_eq!(
self.sync_manager.active_sampling_requests(),
Vec::<Hash256>::new(),
"expected no active sampling"
);
}
fn expect_active_sampling(&mut self, block_root: &Hash256) {
assert!(self
.sync_manager
.active_sampling_requests()
.contains(block_root));
}
fn expect_clean_finished_sampling(&mut self) {
self.expect_empty_network();
self.expect_sampling_result_work();
self.expect_no_active_sampling();
}
fn assert_parent_lookups_count(&self, count: usize) {
assert_eq!(
self.active_parent_lookups_count(),
@@ -613,39 +583,6 @@ impl TestRig {
})
}
fn return_empty_sampling_requests(&mut self, ids: DCByRootIds) {
for id in ids {
self.log(&format!("return empty data column for {id:?}"));
self.return_empty_sampling_request(id)
}
}
fn return_empty_sampling_request(&mut self, (sync_request_id, _): DCByRootId) {
let peer_id = PeerId::random();
// Send stream termination
self.send_sync_message(SyncMessage::RpcDataColumn {
sync_request_id,
peer_id,
data_column: None,
seen_timestamp: timestamp_now(),
});
}
fn sampling_requests_failed(
&mut self,
sampling_ids: DCByRootIds,
peer_id: PeerId,
error: RPCError,
) {
for (sync_request_id, _) in sampling_ids {
self.send_sync_message(SyncMessage::RpcError {
peer_id,
sync_request_id,
error: error.clone(),
})
}
}
fn complete_valid_block_request(
&mut self,
id: SingleLookupReqId,
@@ -672,51 +609,6 @@ impl TestRig {
)
}
fn complete_valid_sampling_column_requests(
&mut self,
ids: DCByRootIds,
data_columns: Vec<Arc<DataColumnSidecar<E>>>,
) {
for id in ids {
self.log(&format!("return valid data column for {id:?}"));
let indices = &id.1;
let columns_to_send = indices
.iter()
.map(|&i| data_columns[i as usize].clone())
.collect::<Vec<_>>();
self.complete_valid_sampling_column_request(id, &columns_to_send);
}
}
fn complete_valid_sampling_column_request(
&mut self,
id: DCByRootId,
data_columns: &[Arc<DataColumnSidecar<E>>],
) {
let first_dc = data_columns.first().unwrap();
let block_root = first_dc.block_root();
let sampling_request_id = match id.0 {
SyncRequestId::DataColumnsByRoot(DataColumnsByRootRequestId {
requester: DataColumnsByRootRequester::Sampling(sampling_id),
..
}) => sampling_id.sampling_request_id,
_ => unreachable!(),
};
self.complete_data_columns_by_root_request(id, data_columns);
// Expect work event
self.expect_rpc_sample_verify_work_event();
// Respond with valid result
self.send_sync_message(SyncMessage::SampleVerified {
id: SamplingId {
id: SamplingRequester::ImportedBlock(block_root),
sampling_request_id,
},
result: Ok(()),
})
}
fn complete_valid_custody_request(
&mut self,
ids: DCByRootIds,
@@ -1047,28 +939,7 @@ impl TestRig {
.unwrap_or_else(|e| panic!("Expected RPC custody column work: {e}"))
}
fn expect_rpc_sample_verify_work_event(&mut self) {
self.pop_received_processor_event(|ev| {
if ev.work_type() == beacon_processor::WorkType::RpcVerifyDataColumn {
Some(())
} else {
None
}
})
.unwrap_or_else(|e| panic!("Expected sample verify work: {e}"))
}
fn expect_sampling_result_work(&mut self) {
self.pop_received_processor_event(|ev| {
if ev.work_type() == beacon_processor::WorkType::SamplingResult {
Some(())
} else {
None
}
})
.unwrap_or_else(|e| panic!("Expected sampling result work: {e}"))
}
#[allow(dead_code)]
fn expect_no_work_event(&mut self) {
self.drain_processor_rx();
assert!(self.network_rx_queue.is_empty());
@@ -1280,46 +1151,6 @@ impl TestRig {
imported: false,
});
}
fn assert_sampling_request_ongoing(&self, block_root: Hash256, indices: &[ColumnIndex]) {
for index in indices {
let status = self
.sync_manager
.get_sampling_request_status(block_root, index)
.unwrap_or_else(|| panic!("No request state for {index}"));
if !matches!(status, crate::sync::peer_sampling::Status::Sampling { .. }) {
panic!("expected {block_root} {index} request to be on going: {status:?}");
}
}
}
fn assert_sampling_request_nopeers(&self, block_root: Hash256, indices: &[ColumnIndex]) {
for index in indices {
let status = self
.sync_manager
.get_sampling_request_status(block_root, index)
.unwrap_or_else(|| panic!("No request state for {index}"));
if !matches!(status, crate::sync::peer_sampling::Status::NoPeers) {
panic!("expected {block_root} {index} request to be no peers: {status:?}");
}
}
}
fn log_sampling_requests(&self, block_root: Hash256, indices: &[ColumnIndex]) {
let statuses = indices
.iter()
.map(|index| {
let status = self
.sync_manager
.get_sampling_request_status(block_root, index)
.unwrap_or_else(|| panic!("No request state for {index}"));
(index, status)
})
.collect::<Vec<_>>();
self.log(&format!(
"Sampling request status for {block_root}: {statuses:?}"
));
}
}
#[test]
@@ -2074,137 +1905,6 @@ fn blobs_in_da_checker_skip_download() {
r.expect_no_active_lookups();
}
#[test]
fn sampling_happy_path() {
let Some(mut r) = TestRig::test_setup_after_fulu() else {
return;
};
r.new_connected_peers_for_peerdas();
let (block, data_columns) = r.rand_block_and_data_columns();
let block_root = block.canonical_root();
r.trigger_sample_block(block_root, block.slot());
// Retrieve all outgoing sample requests for random column indexes
let sampling_ids =
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
// Resolve all of them one by one
r.complete_valid_sampling_column_requests(sampling_ids, data_columns);
r.expect_clean_finished_sampling();
}
#[test]
fn sampling_with_retries() {
let Some(mut r) = TestRig::test_setup_after_fulu() else {
return;
};
r.new_connected_peers_for_peerdas();
// Add another supernode to ensure that the node can retry.
r.new_connected_supernode_peer();
let (block, data_columns) = r.rand_block_and_data_columns();
let block_root = block.canonical_root();
r.trigger_sample_block(block_root, block.slot());
// Retrieve all outgoing sample requests for random column indexes, and return empty responses
let sampling_ids =
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
r.return_empty_sampling_requests(sampling_ids);
// Expect retries for all of them, and resolve them
let sampling_ids =
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
r.complete_valid_sampling_column_requests(sampling_ids, data_columns);
r.expect_clean_finished_sampling();
}
#[test]
fn sampling_avoid_retrying_same_peer() {
let Some(mut r) = TestRig::test_setup_after_fulu() else {
return;
};
let peer_id_1 = r.new_connected_supernode_peer();
let peer_id_2 = r.new_connected_supernode_peer();
let block_root = Hash256::random();
r.trigger_sample_block(block_root, Slot::new(0));
// Retrieve all outgoing sample requests for random column indexes, and return empty responses
let sampling_ids =
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
r.sampling_requests_failed(sampling_ids, peer_id_1, RPCError::Disconnected);
// Should retry the other peer
let sampling_ids =
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
r.sampling_requests_failed(sampling_ids, peer_id_2, RPCError::Disconnected);
// Expect no more retries
r.expect_empty_network();
}
#[test]
fn sampling_batch_requests() {
let Some(mut r) = TestRig::test_setup_after_fulu() else {
return;
};
let _supernode = r.new_connected_supernode_peer();
let (block, data_columns) = r.rand_block_and_data_columns();
let block_root = block.canonical_root();
r.trigger_sample_block(block_root, block.slot());
// Retrieve the sample request, which should be batched.
let (sync_request_id, column_indexes) = r
.expect_only_data_columns_by_root_requests(block_root, 1)
.pop()
.unwrap();
assert_eq!(column_indexes.len(), SAMPLING_REQUIRED_SUCCESSES);
r.assert_sampling_request_ongoing(block_root, &column_indexes);
// Resolve the request.
r.complete_valid_sampling_column_requests(
vec![(sync_request_id, column_indexes.clone())],
data_columns,
);
r.expect_clean_finished_sampling();
}
#[test]
fn sampling_batch_requests_not_enough_responses_returned() {
let Some(mut r) = TestRig::test_setup_after_fulu() else {
return;
};
let _supernode = r.new_connected_supernode_peer();
let (block, data_columns) = r.rand_block_and_data_columns();
let block_root = block.canonical_root();
r.trigger_sample_block(block_root, block.slot());
// Retrieve the sample request, which should be batched.
let (sync_request_id, column_indexes) = r
.expect_only_data_columns_by_root_requests(block_root, 1)
.pop()
.unwrap();
assert_eq!(column_indexes.len(), SAMPLING_REQUIRED_SUCCESSES);
// The request status should be set to Sampling.
r.assert_sampling_request_ongoing(block_root, &column_indexes);
// Split the indexes to simulate the case where the supernode doesn't have the requested column.
let (column_indexes_supernode_does_not_have, column_indexes_to_complete) =
column_indexes.split_at(1);
// Complete the requests but only partially, so a NotEnoughResponsesReturned error occurs.
let data_columns_to_complete = data_columns
.iter()
.filter(|d| column_indexes_to_complete.contains(&d.index))
.cloned()
.collect::<Vec<_>>();
r.complete_data_columns_by_root_request(
(sync_request_id, column_indexes.clone()),
&data_columns_to_complete,
);
// The request status should be set to NoPeers since the supernode, the only peer, returned not enough responses.
r.log_sampling_requests(block_root, &column_indexes);
r.assert_sampling_request_nopeers(block_root, column_indexes_supernode_does_not_have);
// The sampling request stalls.
r.expect_empty_network();
r.expect_no_work_event();
r.expect_active_sampling(&block_root);
}
#[test]
fn custody_lookup_happy_path() {
let Some(mut r) = TestRig::test_setup_after_fulu() else {
@@ -2233,9 +1933,6 @@ fn custody_lookup_happy_path() {
// - Respond with stream terminator
// ^ The stream terminator should be ignored and not close the next retry
// TODO(das): Test error early a sampling request and it getting drop + then receiving responses
// from pending requests.
mod deneb_only {
use super::*;
use beacon_chain::{