Add individual by_range sync requests (#6497)

Part of
- https://github.com/sigp/lighthouse/issues/6258

To address PeerDAS sync issues we need to make individual by_range requests within a batch retriable. We should adopt the same pattern for lookup sync where each request (block/blobs/columns) is tracked individually within a "meta" request that group them all and handles retry logic.


  - Building on https://github.com/sigp/lighthouse/pull/6398

second step is to add individual request accumulators for `blocks_by_range`, `blobs_by_range`, and `data_columns_by_range`. This will allow each request to progress independently and be retried separately.

Most of the logic is just piping, excuse the large diff. This PR does not change the logic of how requests are handled or retried. This will be done in a future PR changing the logic of `RangeBlockComponentsRequest`.

### Before

- Sync manager receives block with `SyncRequestId::RangeBlockAndBlobs`
- Insert block into `SyncNetworkContext::range_block_components_requests`
- (If received stream terminators of all requests)
- Return `Vec<RpcBlock>`, and insert into `range_sync`

### Now

- Sync manager receives block with `SyncRequestId::RangeBlockAndBlobs`
- Insert block into `SyncNetworkContext:: blocks_by_range_requests`
- (If received stream terminator of this request)
- Return `Vec<SignedBlock>`, and insert into `SyncNetworkContext::components_by_range_requests `
- (If received a result for all requests)
- Return `Vec<RpcBlock>`, and insert into `range_sync`
This commit is contained in:
Lion - dapplion
2025-02-05 04:08:28 -03:00
committed by GitHub
parent 7bfdb33729
commit 2193f6a4d4
15 changed files with 776 additions and 502 deletions

View File

@@ -1,7 +1,6 @@
use beacon_chain::{
block_verification_types::RpcBlock, data_column_verification::CustodyDataColumn, get_block_root,
};
use lighthouse_network::PeerId;
use std::{
collections::{HashMap, VecDeque},
sync::Arc,
@@ -29,9 +28,6 @@ pub struct RangeBlockComponentsRequest<E: EthSpec> {
/// Used to determine if the number of data columns stream termination this accumulator should
/// wait for. This may be less than the number of `expects_custody_columns` due to request batching.
num_custody_column_requests: Option<usize>,
/// The peers the request was made to.
pub(crate) peer_ids: Vec<PeerId>,
max_blobs_per_block: usize,
}
impl<E: EthSpec> RangeBlockComponentsRequest<E> {
@@ -39,8 +35,6 @@ impl<E: EthSpec> RangeBlockComponentsRequest<E> {
expects_blobs: bool,
expects_custody_columns: Option<Vec<ColumnIndex>>,
num_custody_column_requests: Option<usize>,
peer_ids: Vec<PeerId>,
max_blobs_per_block: usize,
) -> Self {
Self {
blocks: <_>::default(),
@@ -52,50 +46,42 @@ impl<E: EthSpec> RangeBlockComponentsRequest<E> {
expects_blobs,
expects_custody_columns,
num_custody_column_requests,
peer_ids,
max_blobs_per_block,
}
}
// TODO: This function should be deprecated when simplying the retry mechanism of this range
// requests.
pub fn get_requirements(&self) -> (bool, Option<Vec<ColumnIndex>>) {
(self.expects_blobs, self.expects_custody_columns.clone())
pub fn add_blocks(&mut self, blocks: Vec<Arc<SignedBeaconBlock<E>>>) {
for block in blocks {
self.blocks.push_back(block);
}
self.is_blocks_stream_terminated = true;
}
pub fn add_block_response(&mut self, block_opt: Option<Arc<SignedBeaconBlock<E>>>) {
match block_opt {
Some(block) => self.blocks.push_back(block),
None => self.is_blocks_stream_terminated = true,
pub fn add_blobs(&mut self, blobs: Vec<Arc<BlobSidecar<E>>>) {
for blob in blobs {
self.blobs.push_back(blob);
}
self.is_sidecars_stream_terminated = true;
}
pub fn add_sidecar_response(&mut self, sidecar_opt: Option<Arc<BlobSidecar<E>>>) {
match sidecar_opt {
Some(sidecar) => self.blobs.push_back(sidecar),
None => self.is_sidecars_stream_terminated = true,
}
}
pub fn add_data_column(&mut self, column_opt: Option<Arc<DataColumnSidecar<E>>>) {
match column_opt {
Some(column) => self.data_columns.push_back(column),
// TODO(das): this mechanism is dangerous, if somehow there are two requests for the
// same column index it can terminate early. This struct should track that all requests
// for all custody columns terminate.
None => self.custody_columns_streams_terminated += 1,
pub fn add_custody_columns(&mut self, columns: Vec<Arc<DataColumnSidecar<E>>>) {
for column in columns {
self.data_columns.push_back(column);
}
// TODO(das): this mechanism is dangerous, if somehow there are two requests for the
// same column index it can terminate early. This struct should track that all requests
// for all custody columns terminate.
self.custody_columns_streams_terminated += 1;
}
pub fn into_responses(self, spec: &ChainSpec) -> Result<Vec<RpcBlock<E>>, String> {
if let Some(expects_custody_columns) = self.expects_custody_columns.clone() {
self.into_responses_with_custody_columns(expects_custody_columns, spec)
} else {
self.into_responses_with_blobs()
self.into_responses_with_blobs(spec)
}
}
fn into_responses_with_blobs(self) -> Result<Vec<RpcBlock<E>>, String> {
fn into_responses_with_blobs(self, spec: &ChainSpec) -> Result<Vec<RpcBlock<E>>, String> {
let RangeBlockComponentsRequest { blocks, blobs, .. } = self;
// There can't be more more blobs than blocks. i.e. sending any blob (empty
@@ -103,7 +89,8 @@ impl<E: EthSpec> RangeBlockComponentsRequest<E> {
let mut responses = Vec::with_capacity(blocks.len());
let mut blob_iter = blobs.into_iter().peekable();
for block in blocks.into_iter() {
let mut blob_list = Vec::with_capacity(self.max_blobs_per_block);
let max_blobs_per_block = spec.max_blobs_per_block(block.epoch()) as usize;
let mut blob_list = Vec::with_capacity(max_blobs_per_block);
while {
let pair_next_blob = blob_iter
.peek()
@@ -114,7 +101,7 @@ impl<E: EthSpec> RangeBlockComponentsRequest<E> {
blob_list.push(blob_iter.next().ok_or("Missing next blob".to_string())?);
}
let mut blobs_buffer = vec![None; self.max_blobs_per_block];
let mut blobs_buffer = vec![None; max_blobs_per_block];
for blob in blob_list {
let blob_index = blob.index as usize;
let Some(blob_opt) = blobs_buffer.get_mut(blob_index) else {
@@ -128,7 +115,7 @@ impl<E: EthSpec> RangeBlockComponentsRequest<E> {
}
let blobs = RuntimeVariableList::new(
blobs_buffer.into_iter().flatten().collect::<Vec<_>>(),
self.max_blobs_per_block,
max_blobs_per_block,
)
.map_err(|_| "Blobs returned exceeds max length".to_string())?;
responses.push(RpcBlock::new(None, block, Some(blobs)).map_err(|e| format!("{e:?}"))?)
@@ -246,30 +233,25 @@ mod tests {
use beacon_chain::test_utils::{
generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec, NumBlobs,
};
use lighthouse_network::PeerId;
use rand::SeedableRng;
use types::{test_utils::XorShiftRng, ForkName, MinimalEthSpec as E};
use std::sync::Arc;
use types::{test_utils::XorShiftRng, ForkName, MinimalEthSpec as E, SignedBeaconBlock};
#[test]
fn no_blobs_into_responses() {
let spec = test_spec::<E>();
let peer_id = PeerId::random();
let mut rng = XorShiftRng::from_seed([42; 16]);
let blocks = (0..4)
.map(|_| {
generate_rand_block_and_blobs::<E>(ForkName::Base, NumBlobs::None, &mut rng, &spec)
.0
.into()
})
.collect::<Vec<_>>();
let max_len = spec.max_blobs_per_block(blocks.first().unwrap().epoch()) as usize;
let mut info =
RangeBlockComponentsRequest::<E>::new(false, None, None, vec![peer_id], max_len);
.collect::<Vec<Arc<SignedBeaconBlock<E>>>>();
let mut info = RangeBlockComponentsRequest::<E>::new(false, None, None);
// Send blocks and complete terminate response
for block in blocks {
info.add_block_response(Some(block.into()));
}
info.add_block_response(None);
info.add_blocks(blocks);
// Assert response is finished and RpcBlocks can be constructed
assert!(info.is_finished());
@@ -279,7 +261,6 @@ mod tests {
#[test]
fn empty_blobs_into_responses() {
let spec = test_spec::<E>();
let peer_id = PeerId::random();
let mut rng = XorShiftRng::from_seed([42; 16]);
let blocks = (0..4)
.map(|_| {
@@ -291,19 +272,15 @@ mod tests {
&spec,
)
.0
.into()
})
.collect::<Vec<_>>();
let max_len = spec.max_blobs_per_block(blocks.first().unwrap().epoch()) as usize;
let mut info =
RangeBlockComponentsRequest::<E>::new(true, None, None, vec![peer_id], max_len);
.collect::<Vec<Arc<SignedBeaconBlock<E>>>>();
let mut info = RangeBlockComponentsRequest::<E>::new(true, None, None);
// Send blocks and complete terminate response
for block in blocks {
info.add_block_response(Some(block.into()));
}
info.add_block_response(None);
info.add_blocks(blocks);
// Expect no blobs returned
info.add_sidecar_response(None);
info.add_blobs(vec![]);
// Assert response is finished and RpcBlocks can be constructed, even if blobs weren't returned.
// This makes sure we don't expect blobs here when they have expired. Checking this logic should
@@ -316,7 +293,6 @@ mod tests {
fn rpc_block_with_custody_columns() {
let spec = test_spec::<E>();
let expects_custody_columns = vec![1, 2, 3, 4];
let mut rng = XorShiftRng::from_seed([42; 16]);
let blocks = (0..4)
.map(|_| {
@@ -328,34 +304,24 @@ mod tests {
)
})
.collect::<Vec<_>>();
let max_len = spec.max_blobs_per_block(blocks.first().unwrap().0.epoch()) as usize;
let mut info = RangeBlockComponentsRequest::<E>::new(
false,
Some(expects_custody_columns.clone()),
Some(expects_custody_columns.len()),
vec![PeerId::random()],
max_len,
);
// Send blocks and complete terminate response
for block in &blocks {
info.add_block_response(Some(block.0.clone().into()));
}
info.add_block_response(None);
info.add_blocks(blocks.iter().map(|b| b.0.clone().into()).collect());
// Assert response is not finished
assert!(!info.is_finished());
// Send data columns interleaved
for block in &blocks {
for column in &block.1 {
if expects_custody_columns.contains(&column.index) {
info.add_data_column(Some(column.clone()));
}
}
}
// Terminate the requests
for (i, _column_index) in expects_custody_columns.iter().enumerate() {
info.add_data_column(None);
// Send data columns
for (i, &column_index) in expects_custody_columns.iter().enumerate() {
info.add_custody_columns(
blocks
.iter()
.flat_map(|b| b.1.iter().filter(|d| d.index == column_index).cloned())
.collect(),
);
if i < expects_custody_columns.len() - 1 {
assert!(
@@ -377,8 +343,21 @@ mod tests {
#[test]
fn rpc_block_with_custody_columns_batched() {
let spec = test_spec::<E>();
let expects_custody_columns = vec![1, 2, 3, 4];
let num_of_data_column_requests = 2;
let batched_column_requests = [vec![1_u64, 2], vec![3, 4]];
let expects_custody_columns = batched_column_requests
.iter()
.flatten()
.cloned()
.collect::<Vec<_>>();
let custody_column_request_ids =
(0..batched_column_requests.len() as u32).collect::<Vec<_>>();
let num_of_data_column_requests = custody_column_request_ids.len();
let mut info = RangeBlockComponentsRequest::<E>::new(
false,
Some(expects_custody_columns.clone()),
Some(num_of_data_column_requests),
);
let mut rng = XorShiftRng::from_seed([42; 16]);
let blocks = (0..4)
@@ -391,34 +370,25 @@ mod tests {
)
})
.collect::<Vec<_>>();
let max_len = spec.max_blobs_per_block(blocks.first().unwrap().0.epoch()) as usize;
let mut info = RangeBlockComponentsRequest::<E>::new(
false,
Some(expects_custody_columns.clone()),
Some(num_of_data_column_requests),
vec![PeerId::random()],
max_len,
);
// Send blocks and complete terminate response
for block in &blocks {
info.add_block_response(Some(block.0.clone().into()));
}
info.add_block_response(None);
info.add_blocks(blocks.iter().map(|b| b.0.clone().into()).collect());
// Assert response is not finished
assert!(!info.is_finished());
// Send data columns interleaved
for block in &blocks {
for column in &block.1 {
if expects_custody_columns.contains(&column.index) {
info.add_data_column(Some(column.clone()));
}
}
}
for (i, column_indices) in batched_column_requests.iter().enumerate() {
// Send the set of columns in the same batch request
info.add_custody_columns(
blocks
.iter()
.flat_map(|b| {
b.1.iter()
.filter(|d| column_indices.contains(&d.index))
.cloned()
})
.collect::<Vec<_>>(),
);
// Terminate the requests
for i in 0..num_of_data_column_requests {
info.add_data_column(None);
if i < num_of_data_column_requests - 1 {
assert!(
!info.is_finished(),