Deprecate blob lookup sync (#9383)

- Extends https://github.com/sigp/lighthouse/pull/9126 to cover blob lookup sync

Lookup sync is only for unfinalized blocks, which will never contains blobs in any network we support.


  


Co-Authored-By: dapplion <35266934+dapplion@users.noreply.github.com>

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
Lion - dapplion
2026-06-01 14:10:47 +02:00
committed by GitHub
parent 578b6a62c7
commit b781227f1d
17 changed files with 49 additions and 855 deletions

View File

@@ -8,13 +8,11 @@ use crate::sync::{
SyncMessage,
manager::{BatchProcessResult, BlockProcessType, BlockProcessingResult, SyncManager},
};
use beacon_chain::blob_verification::KzgVerifiedBlob;
use beacon_chain::block_verification_types::LookupBlock;
use beacon_chain::custody_context::NodeCustodyType;
use beacon_chain::{
AvailabilityProcessingStatus, BlockError, EngineState, NotifyExecutionLayer,
block_verification_types::{AsBlock, AvailableBlockData},
data_availability_checker::Availability,
test_utils::{
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, NumBlobs,
generate_rand_block_and_blobs, test_spec,
@@ -36,8 +34,8 @@ use std::time::Duration;
use tokio::sync::mpsc;
use tracing::info;
use types::{
BlobSidecar, BlockImportSource, ColumnIndex, DataColumnSidecar, EthSpec, ForkContext, ForkName,
Hash256, MinimalEthSpec as E, SignedBeaconBlock, Slot,
BlobSidecar, BlockImportSource, ColumnIndex, DataColumnSidecar, ForkContext, ForkName, Hash256,
MinimalEthSpec as E, SignedBeaconBlock, Slot,
};
const D: Duration = Duration::new(0, 0);
@@ -549,52 +547,6 @@ impl TestRig {
self.send_rpc_blocks_response(req_id, peer_id, &blocks);
}
(RequestType::BlobsByRoot(req), AppRequestId::Sync(req_id)) => {
if self.complete_strategy.return_no_data_n_times > 0 {
self.complete_strategy.return_no_data_n_times -= 1;
return self.send_rpc_blobs_response(req_id, peer_id, &[]);
}
let mut blobs = req
.blob_ids
.iter()
.map(|id| {
self.network_blocks_by_root
.get(&id.block_root)
.unwrap_or_else(|| {
panic!("Test consumer requested unknown block: {id:?}")
})
.block_data()
.blobs()
.unwrap_or_else(|| panic!("Block {id:?} has no blobs"))
.iter()
.find(|blob| blob.index == id.index)
.unwrap_or_else(|| panic!("Blob id {id:?} not avail"))
.clone()
})
.collect::<Vec<_>>();
if self.complete_strategy.return_too_few_data_n_times > 0 {
self.complete_strategy.return_too_few_data_n_times -= 1;
blobs.pop();
}
if self
.complete_strategy
.return_wrong_sidecar_for_block_n_times
> 0
{
self.complete_strategy
.return_wrong_sidecar_for_block_n_times -= 1;
let first = blobs.first_mut().expect("empty blobs");
let mut blob = Arc::make_mut(first).clone();
blob.signed_block_header.message.body_root = Hash256::ZERO;
*first = Arc::new(blob);
}
self.send_rpc_blobs_response(req_id, peer_id, &blobs);
}
(RequestType::DataColumnsByRoot(req), AppRequestId::Sync(req_id)) => {
if self.complete_strategy.return_no_data_n_times > 0 {
self.complete_strategy.return_no_data_n_times -= 1;
@@ -1006,48 +958,6 @@ impl TestRig {
keypair.sk.sign(msg)
}
fn corrupt_last_blob_proposer_signature(&mut self) {
let range_sync_block = self.get_last_block().clone();
let block = range_sync_block.block_cloned();
let mut blobs = range_sync_block
.block_data()
.blobs()
.expect("no blobs")
.into_iter()
.collect::<Vec<_>>();
let columns = range_sync_block.block_data().data_columns();
let first = blobs.first_mut().expect("empty blobs");
Arc::make_mut(first).signed_block_header.signature = self.valid_signature();
let max_blobs =
self.harness
.spec
.max_blobs_per_block(block.slot().epoch(E::slots_per_epoch())) as usize;
let blobs =
types::BlobSidecarList::new(blobs, max_blobs).expect("invalid blob sidecar list");
self.re_insert_block(block, Some(blobs), columns);
}
fn corrupt_last_blob_kzg_proof(&mut self) {
let range_sync_block = self.get_last_block().clone();
let block = range_sync_block.block_cloned();
let mut blobs = range_sync_block
.block_data()
.blobs()
.expect("no blobs")
.into_iter()
.collect::<Vec<_>>();
let columns = range_sync_block.block_data().data_columns();
let first = blobs.first_mut().expect("empty blobs");
Arc::make_mut(first).kzg_proof = kzg::KzgProof::empty();
let max_blobs =
self.harness
.spec
.max_blobs_per_block(block.slot().epoch(E::slots_per_epoch())) as usize;
let blobs =
types::BlobSidecarList::new(blobs, max_blobs).expect("invalid blob sidecar list");
self.re_insert_block(block, Some(blobs), columns);
}
fn corrupt_last_column_proposer_signature(&mut self) {
let range_sync_block = self.get_last_block().clone();
let block = range_sync_block.block_cloned();
@@ -1413,10 +1323,6 @@ impl TestRig {
// Test setup
fn new_after_deneb() -> Option<Self> {
genesis_fork().deneb_enabled().then(Self::default)
}
fn new_after_fulu() -> Option<Self> {
genesis_fork().fulu_enabled().then(Self::default)
}
@@ -1443,10 +1349,6 @@ impl TestRig {
info!(msg, "TEST_RIG");
}
pub fn is_after_deneb(&self) -> bool {
self.fork_name.deneb_enabled()
}
pub fn is_after_fulu(&self) -> bool {
self.fork_name.fulu_enabled()
}
@@ -1732,27 +1634,6 @@ impl TestRig {
}
}
fn insert_blob_to_da_checker(&mut self, blob: Arc<BlobSidecar<E>>) {
match self
.harness
.chain
.data_availability_checker
.put_kzg_verified_blobs(
blob.block_root(),
std::iter::once(
KzgVerifiedBlob::new(blob, &self.harness.chain.kzg, Duration::new(0, 0))
.expect("Invalid blob"),
),
)
.unwrap()
{
Availability::Available(_) => panic!("column removed from da_checker, available"),
Availability::MissingComponents(block_root) => {
self.log(&format!("inserted column to da_checker {block_root:?}"))
}
};
}
fn insert_block_to_da_checker_as_pre_execution(&mut self, block: Arc<SignedBeaconBlock<E>>) {
self.log(&format!(
"Inserting block to availability_cache as pre_execution_block {:?}",
@@ -1919,18 +1800,14 @@ async fn happy_path_unknown_block_parent(depth: usize) {
r.build_chain(depth).await;
r.trigger_with_last_unknown_block_parent();
r.simulate(SimulateConfig::happy_path()).await;
// All lookups should NOT complete on this test, however note the following for the tip lookup,
// it's the lookup for the tip block which has 0 peers and a block cached:
// Note the following for the tip lookup, it's the lookup for the tip block which has 0 peers
// and a block cached:
// - before deneb the block is cached, so it's sent for processing, and success
// - before fulu the block is cached, but we can't fetch blobs so it's stuck
// - deneb/electra the block is cached, so it's sent for processing, and success
// - after fulu the block is cached, we start a custody request and since we use the global pool
// of peers we DO have 1 connected synced supernode peer, which gives us the columns and the
// lookup succeeds
if r.is_after_deneb() && !r.is_after_fulu() {
r.assert_successful_lookup_sync_parent_trigger()
} else {
r.assert_successful_lookup_sync();
}
r.assert_successful_lookup_sync();
}
/// Assert that sync completes from an UnknownDataColumnParent
@@ -1978,9 +1855,9 @@ async fn bad_peer_empty_block_response(depth: usize) {
// TODO(tree-sync) Assert that a single lookup is created (no drops)
}
/// Assert that if peer responds with no blobs / columns, we downscore, and retry the same lookup
/// Assert that if peer responds with no columns, we downscore, and retry the same lookup.
async fn bad_peer_empty_data_response(depth: usize) {
let Some(mut r) = TestRig::new_after_deneb() else {
let Some(mut r) = TestRig::new_after_fulu() else {
return;
};
r.build_chain_and_trigger_last_block(depth).await;
@@ -1992,10 +1869,10 @@ async fn bad_peer_empty_data_response(depth: usize) {
// TODO(tree-sync) Assert that a single lookup is created (no drops)
}
/// Assert that if peer responds with not enough blobs / columns, we downscore, and retry the same
/// lookup
/// Assert that if peer responds with not enough columns, we downscore, and retry the same
/// lookup.
async fn bad_peer_too_few_data_response(depth: usize) {
let Some(mut r) = TestRig::new_after_deneb() else {
let Some(mut r) = TestRig::new_after_fulu() else {
return;
};
r.build_chain_and_trigger_last_block(depth).await;
@@ -2019,9 +1896,9 @@ async fn bad_peer_wrong_block_response(depth: usize) {
// TODO(tree-sync) Assert that a single lookup is created (no drops)
}
/// Assert that if peer responds with bad blobs / columns, we downscore, and retry the same lookup
/// Assert that if peer responds with bad columns, we downscore, and retry the same lookup.
async fn bad_peer_wrong_data_response(depth: usize) {
let Some(mut r) = TestRig::new_after_deneb() else {
let Some(mut r) = TestRig::new_after_fulu() else {
return;
};
r.build_chain_and_trigger_last_block(depth).await;
@@ -2342,8 +2219,8 @@ async fn test_same_chain_race_condition() {
#[tokio::test]
/// Assert that if the lookup's block is in the da_checker we don't download it again
async fn block_in_da_checker_skips_download() {
// Only in Deneb, as the block needs blobs to remain in the da_checker
let Some(mut r) = TestRig::new_after_deneb_before_fulu() else {
// Only post-Fulu, as the block needs custody columns to remain in the da_checker
let Some(mut r) = TestRig::new_after_fulu() else {
return;
};
// Add block to da_checker
@@ -2407,32 +2284,6 @@ async fn block_in_processing_cache_becomes_valid_imported() {
r.assert_no_active_lookups();
}
// IGNORE: wait for change that delays blob fetching to knowing the block
#[tokio::test]
async fn blobs_in_da_checker_skip_download() {
let Some(mut r) = TestRig::new_after_deneb_before_fulu() else {
return;
};
r.build_chain(1).await;
let block = r.get_last_block().clone();
let blobs = block.block_data().blobs().expect("block with no blobs");
for blob in &blobs {
r.insert_blob_to_da_checker(blob.clone());
}
r.trigger_with_last_block();
r.simulate(SimulateConfig::happy_path()).await;
r.assert_successful_lookup_sync();
assert_eq!(
r.requests
.iter()
.filter(|(request, _)| matches!(request, RequestType::BlobsByRoot(_)))
.collect::<Vec<_>>(),
Vec::<&(RequestType<E>, AppRequestId)>::new(),
"There should be no blob requests"
);
}
macro_rules! fulu_peer_matrix_tests {
(
[$($name:ident => $variant:expr),+ $(,)?]
@@ -2545,42 +2396,6 @@ async fn crypto_on_fail_with_invalid_block_signature() {
}
}
#[tokio::test]
async fn crypto_on_fail_with_bad_blob_proposer_signature() {
let Some(mut r) = TestRig::new_after_deneb_before_fulu() else {
return;
};
r.build_chain(1).await;
r.corrupt_last_blob_proposer_signature();
r.trigger_with_last_block();
r.simulate(SimulateConfig::happy_path()).await;
if cfg!(feature = "fake_crypto") {
r.assert_successful_lookup_sync();
r.assert_no_penalties();
} else {
r.assert_failed_lookup_sync();
r.assert_penalties_of_type("lookup_blobs_processing_failure");
}
}
#[tokio::test]
async fn crypto_on_fail_with_bad_blob_kzg_proof() {
let Some(mut r) = TestRig::new_after_deneb_before_fulu() else {
return;
};
r.build_chain(1).await;
r.corrupt_last_blob_kzg_proof();
r.trigger_with_last_block();
r.simulate(SimulateConfig::happy_path()).await;
if cfg!(feature = "fake_crypto") {
r.assert_successful_lookup_sync();
r.assert_no_penalties();
} else {
r.assert_failed_lookup_sync();
r.assert_penalties_of_type("lookup_blobs_processing_failure");
}
}
#[tokio::test]
async fn crypto_on_fail_with_bad_column_proposer_signature() {
let Some(mut r) = TestRig::new_fulu_peer_test(FuluTestType::WeSupernodeThemSupernode) else {