Skip to content

Commit 2d22357

Browse files
authored
Avoid unnecessary database lookups in data column RPC requests (#7897)
This PR is an optimisation to avoid unnecessary database lookups when peer requests data columns that the node doesn't custody (advertised via `cgc`). e.g. an extreme but realistic example - a full node only store 4 custody columns by default, but it may receive a range request of 32 slots with all 128 columns, and this would result in 4096 database lookups but the node is only able to get 128 (4 * 32) of them. - Filter data column RPC requests (`DataColumnsByRoot`, `DataColumnsByRange`) to only lookup columns the node custodies - Prevents unnecessary database queries that would always fail for non-custody columns
1 parent f6859b1 commit 2d22357

File tree

3 files changed

+136
-2
lines changed

3 files changed

+136
-2
lines changed

beacon_node/beacon_chain/src/beacon_chain.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7153,6 +7153,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
71537153
.custody_context()
71547154
.sampling_columns_for_epoch(epoch, &self.spec)
71557155
}
7156+
7157+
/// Returns a list of column indices that the node is expected to custody for a given epoch.
7158+
/// i.e. the node must have validated and persisted the column samples and should be able to
7159+
/// serve them to peers.
7160+
///
7161+
/// If epoch is `None`, this function computes the custody columns at head.
7162+
pub fn custody_columns_for_epoch(&self, epoch_opt: Option<Epoch>) -> &[ColumnIndex] {
7163+
self.data_availability_checker
7164+
.custody_context()
7165+
.custody_columns_for_epoch(epoch_opt, &self.spec)
7166+
}
71567167
}
71577168

71587169
impl<T: BeaconChainTypes> Drop for BeaconChain<T> {

beacon_node/beacon_chain/src/validator_custody.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,37 @@ impl<E: EthSpec> CustodyContext<E> {
323323
.expect("all_custody_columns_ordered should be initialized");
324324
&all_columns_ordered[..num_of_columns_to_sample]
325325
}
326+
327+
/// Returns the ordered list of column indices that the node is assigned to custody
328+
/// (and advertised to peers) at the given epoch. If epoch is `None`, this function
329+
/// computes the custody columns at head.
330+
///
331+
/// This method differs from [`self::sampling_columns_for_epoch`] which returns all sampling columns.
332+
/// The columns returned by this method are either identical to or a subset of the sampling columns,
333+
/// representing only those columns that this node is responsible for maintaining custody of.
334+
///
335+
/// # Parameters
336+
/// * `epoch_opt` - Optional epoch to determine custody columns for.
337+
///
338+
/// # Returns
339+
/// A slice of ordered custody column indices for this epoch based on the node's custody configuration
340+
pub fn custody_columns_for_epoch(
341+
&self,
342+
epoch_opt: Option<Epoch>,
343+
spec: &ChainSpec,
344+
) -> &[ColumnIndex] {
345+
let custody_group_count = if let Some(epoch) = epoch_opt {
346+
self.custody_group_count_at_epoch(epoch, spec) as usize
347+
} else {
348+
self.custody_group_count_at_head(spec) as usize
349+
};
350+
351+
let all_columns_ordered = self
352+
.all_custody_columns_ordered
353+
.get()
354+
.expect("all_custody_columns_ordered should be initialized");
355+
&all_columns_ordered[..custody_group_count]
356+
}
326357
}
327358

328359
/// The custody count changed because of a change in the
@@ -670,4 +701,82 @@ mod tests {
670701
assert_eq!(updated_custody_count_opt, expected_cgc_change);
671702
}
672703
}
704+
705+
#[test]
706+
fn custody_columns_for_epoch_no_validators_fullnode() {
707+
let custody_context = CustodyContext::<E>::new(false);
708+
let spec = E::default_spec();
709+
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
710+
711+
custody_context
712+
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
713+
.expect("should initialise ordered data columns");
714+
715+
assert_eq!(
716+
custody_context.custody_columns_for_epoch(None, &spec).len(),
717+
spec.custody_requirement as usize
718+
);
719+
}
720+
721+
#[test]
722+
fn custody_columns_for_epoch_no_validators_supernode() {
723+
let custody_context = CustodyContext::<E>::new(true);
724+
let spec = E::default_spec();
725+
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
726+
727+
custody_context
728+
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
729+
.expect("should initialise ordered data columns");
730+
731+
assert_eq!(
732+
custody_context.custody_columns_for_epoch(None, &spec).len(),
733+
spec.number_of_custody_groups as usize
734+
);
735+
}
736+
737+
#[test]
738+
fn custody_columns_for_epoch_with_validators_should_match_cgc() {
739+
let custody_context = CustodyContext::<E>::new(false);
740+
let spec = E::default_spec();
741+
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
742+
let val_custody_units = 10;
743+
744+
custody_context
745+
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
746+
.expect("should initialise ordered data columns");
747+
748+
let _ = custody_context.register_validators(
749+
vec![(
750+
0,
751+
val_custody_units * spec.balance_per_additional_custody_group,
752+
)],
753+
Slot::new(10),
754+
&spec,
755+
);
756+
757+
assert_eq!(
758+
custody_context.custody_columns_for_epoch(None, &spec).len(),
759+
val_custody_units as usize
760+
);
761+
}
762+
763+
#[test]
764+
fn custody_columns_for_epoch_specific_epoch_uses_epoch_cgc() {
765+
let custody_context = CustodyContext::<E>::new(false);
766+
let spec = E::default_spec();
767+
let all_custody_groups_ordered = (0..spec.number_of_custody_groups).collect::<Vec<_>>();
768+
let test_epoch = Epoch::new(5);
769+
770+
custody_context
771+
.init_ordered_data_columns_from_custody_groups(all_custody_groups_ordered, &spec)
772+
.expect("should initialise ordered data columns");
773+
774+
let expected_cgc = custody_context.custody_group_count_at_epoch(test_epoch, &spec);
775+
assert_eq!(
776+
custody_context
777+
.custody_columns_for_epoch(Some(test_epoch), &spec)
778+
.len(),
779+
expected_cgc as usize
780+
);
781+
}
673782
}

beacon_node/network/src/network_beacon_processor/rpc_methods.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,19 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
364364
request: DataColumnsByRootRequest<T::EthSpec>,
365365
) -> Result<(), (RpcErrorResponse, &'static str)> {
366366
let mut send_data_column_count = 0;
367+
// Only attempt lookups for columns the node has advertised and is responsible for maintaining custody of.
368+
let available_columns = self.chain.custody_columns_for_epoch(None);
367369

368370
for data_column_ids_by_root in request.data_column_ids.as_slice() {
371+
let indices_to_retrieve = data_column_ids_by_root
372+
.columns
373+
.iter()
374+
.copied()
375+
.filter(|c| available_columns.contains(c))
376+
.collect::<Vec<_>>();
369377
match self.chain.get_data_columns_checking_all_caches(
370378
data_column_ids_by_root.block_root,
371-
data_column_ids_by_root.columns.iter().as_slice(),
379+
&indices_to_retrieve,
372380
) {
373381
Ok(data_columns) => {
374382
send_data_column_count += data_columns.len();
@@ -1070,8 +1078,14 @@ impl<T: BeaconChainTypes> NetworkBeaconProcessor<T> {
10701078
self.get_block_roots_for_slot_range(req.start_slot, req.count, "DataColumnsByRange")?;
10711079
let mut data_columns_sent = 0;
10721080

1081+
// Only attempt lookups for columns the node has advertised and is responsible for maintaining custody of.
1082+
let request_start_epoch = request_start_slot.epoch(T::EthSpec::slots_per_epoch());
1083+
let available_columns = self
1084+
.chain
1085+
.custody_columns_for_epoch(Some(request_start_epoch));
1086+
10731087
for root in block_roots {
1074-
for index in &req.columns {
1088+
for index in available_columns {
10751089
match self.chain.get_data_column(&root, index) {
10761090
Ok(Some(data_column_sidecar)) => {
10771091
// Due to skip slots, data columns could be out of the range, we ensure they

0 commit comments

Comments
 (0)