Fix and add new tests

This commit is contained in:
Pawan Dhananjay
2026-06-02 15:38:12 -07:00
parent f2c812564a
commit 1993438ac8
2 changed files with 234 additions and 28 deletions

View File

@@ -4,6 +4,7 @@ use crate::data_availability_checker::DataAvailabilityChecker;
use crate::graffiti_calculator::GraffitiSettings;
use crate::kzg_utils::{build_data_column_sidecars_fulu, build_data_column_sidecars_gloas};
use crate::observed_operations::ObservationOutcome;
use crate::payload_envelope_verification::AvailableEnvelope;
pub use crate::persisted_beacon_chain::PersistedBeaconChain;
use crate::{BeaconBlockResponseWrapper, CustodyContext, get_block_root};
use crate::{
@@ -2929,18 +2930,29 @@ where
block: Arc<SignedBeaconBlock<E>>,
) -> RangeSyncBlock<E> {
let block_root = block_root.unwrap_or_else(|| get_block_root(&block));
let is_gloas = block.fork_name_unchecked().gloas_enabled();
// For Gloas, kzg commitments live in the bid (`signed_execution_payload_bid`), so the
// body's `blob_kzg_commitments()` accessor returns Err. `num_expected_blobs` already
// handles both shapes.
let has_blobs = block.num_expected_blobs() > 0;
if !has_blobs {
return RangeSyncBlock::new(
block,
AvailableBlockData::NoData,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap();
return if is_gloas {
let envelope = self
.chain
.get_payload_envelope(&block_root)
.unwrap()
.map(Arc::new)
.map(|envelope| Box::new(AvailableEnvelope::new(envelope, vec![])));
RangeSyncBlock::new_gloas(block, envelope).unwrap()
} else {
RangeSyncBlock::new(
block,
AvailableBlockData::NoData,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap()
};
}
// Blobs are stored as data columns from Fulu (PeerDAS)
@@ -2952,14 +2964,24 @@ where
.unwrap()
.unwrap();
let custody_columns = columns.into_iter().collect::<Vec<_>>();
let block_data = AvailableBlockData::new_with_data_columns(custody_columns);
RangeSyncBlock::new(
block,
block_data,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap()
if is_gloas {
let envelope = self
.chain
.get_payload_envelope(&block_root)
.unwrap()
.map(Arc::new)
.map(|envelope| Box::new(AvailableEnvelope::new(envelope, custody_columns)));
RangeSyncBlock::new_gloas(block, envelope).unwrap()
} else {
let block_data = AvailableBlockData::new_with_data_columns(custody_columns);
RangeSyncBlock::new(
block,
block_data,
&self.chain.data_availability_checker,
self.chain.spec.clone(),
)
.unwrap()
}
} else {
let blobs = self.chain.get_blobs(&block_root).unwrap().blobs();
let block_data = if let Some(blobs) = blobs {
@@ -2984,6 +3006,19 @@ where
block: Arc<SignedBeaconBlock<E, FullPayload<E>>>,
blob_items: Option<(KzgProofs<E>, BlobsList<E>)>,
) -> Result<RangeSyncBlock<E>, BlockError> {
if block.fork_name_unchecked().gloas_enabled() {
let columns = blob_items
.map(|_| generate_data_column_sidecars_from_block(&block, &self.spec))
.unwrap_or_default();
let envelope = self
.chain
.get_payload_envelope(&block.canonical_root())
.map_err(|e| BlockError::BeaconChainError(Box::new(e)))?
.map(Arc::new)
.map(|envelope| Box::new(AvailableEnvelope::new(envelope, columns)));
return RangeSyncBlock::new_gloas(block, envelope).map_err(BlockError::InternalError);
}
Ok(if self.spec.is_peer_das_enabled_for_epoch(block.epoch()) {
let epoch = block.slot().epoch(E::slots_per_epoch());
let sampling_columns = self.chain.sampling_columns_for_epoch(epoch);

View File

@@ -3,6 +3,7 @@
use beacon_chain::block_verification_types::{AsBlock, ExecutedBlock, LookupBlock, RangeSyncBlock};
use beacon_chain::data_availability_checker::{AvailabilityCheckError, AvailableBlockData};
use beacon_chain::data_column_verification::CustodyDataColumn;
use beacon_chain::payload_envelope_verification::AvailableEnvelope;
use beacon_chain::{
AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, ExecutionPendingBlock,
WhenSlotSkipped,
@@ -148,19 +149,38 @@ where
.zip(chain_segment_sidecars.iter())
.map(|(snapshot, data_sidecars)| {
let block = snapshot.beacon_block.clone();
build_range_sync_block(block, data_sidecars, chain.clone())
build_range_sync_block(
block,
snapshot.execution_envelope.clone(),
data_sidecars,
chain.clone(),
)
})
.collect()
}
fn build_range_sync_block<T>(
block: Arc<SignedBeaconBlock<E>>,
execution_envelope: Option<Arc<SignedExecutionPayloadEnvelope<E>>>,
data_sidecars: &Option<DataSidecars<E>>,
chain: Arc<BeaconChain<T>>,
) -> RangeSyncBlock<E>
where
T: BeaconChainTypes<EthSpec = E>,
{
if block.fork_name_unchecked().gloas_enabled() {
let columns = match data_sidecars {
Some(DataSidecars::DataColumns(columns)) => columns
.iter()
.map(|c| c.as_data_column().clone())
.collect::<Vec<_>>(),
Some(DataSidecars::Blobs(_)) | None => vec![],
};
let envelope = execution_envelope
.map(|envelope| Box::new(AvailableEnvelope::new(envelope, columns)));
return RangeSyncBlock::new_gloas(block, envelope).unwrap();
}
match data_sidecars {
Some(DataSidecars::Blobs(blobs)) => {
let block_data = AvailableBlockData::new_with_blobs(blobs.clone());
@@ -581,7 +601,12 @@ async fn assert_invalid_signature(
.iter()
.zip(chain_segment_blobs.iter())
.map(|(snapshot, blobs)| {
build_range_sync_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
build_range_sync_block(
snapshot.beacon_block.clone(),
snapshot.execution_envelope.clone(),
blobs,
harness.chain.clone(),
)
})
.collect();
@@ -620,7 +645,12 @@ async fn assert_invalid_signature(
.zip(chain_segment_blobs.iter())
.filter(|(snapshot, _)| snapshot.beacon_block.slot() > finalized_slot)
.map(|(snapshot, blobs)| {
build_range_sync_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
build_range_sync_block(
snapshot.beacon_block.clone(),
snapshot.execution_envelope.clone(),
blobs,
harness.chain.clone(),
)
})
.collect();
// We don't care if this fails, we just call this to ensure that all prior blocks have been
@@ -634,13 +664,14 @@ async fn assert_invalid_signature(
let process_res = harness
.chain
.process_block(
snapshots[block_index].beacon_block.canonical_root(),
build_range_sync_block(
snapshots[block_index].beacon_block.clone(),
&chain_segment_blobs[block_index],
harness.chain.clone(),
),
.process_block(
snapshots[block_index].beacon_block.canonical_root(),
build_range_sync_block(
snapshots[block_index].beacon_block.clone(),
snapshots[block_index].execution_envelope.clone(),
&chain_segment_blobs[block_index],
harness.chain.clone(),
),
NotifyExecutionLayer::Yes,
BlockImportSource::Lookup,
|| Ok(()),
@@ -699,7 +730,12 @@ async fn invalid_signature_gossip_block() {
.take(block_index)
.zip(chain_segment_blobs.iter())
.map(|(snapshot, blobs)| {
build_range_sync_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
build_range_sync_block(
snapshot.beacon_block.clone(),
snapshot.execution_envelope.clone(),
blobs,
harness.chain.clone(),
)
})
.collect();
harness
@@ -752,7 +788,12 @@ async fn invalid_signature_block_proposal() {
.iter()
.zip(chain_segment_blobs.iter())
.map(|(snapshot, blobs)| {
build_range_sync_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
build_range_sync_block(
snapshot.beacon_block.clone(),
snapshot.execution_envelope.clone(),
blobs,
harness.chain.clone(),
)
})
.collect::<Vec<_>>();
// Ensure the block will be rejected if imported in a chain segment.
@@ -1071,7 +1112,12 @@ async fn invalid_signature_deposit() {
.iter()
.zip(chain_segment_blobs.iter())
.map(|(snapshot, blobs)| {
build_range_sync_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
build_range_sync_block(
snapshot.beacon_block.clone(),
snapshot.execution_envelope.clone(),
blobs,
harness.chain.clone(),
)
})
.collect();
assert!(
@@ -2186,6 +2232,131 @@ async fn import_execution_pending_block<T: BeaconChainTypes>(
}
}
async fn make_gloas_range_sync_block_inputs(
) -> Option<(
Arc<SignedBeaconBlock<E>>,
SignedExecutionPayloadEnvelope<E>,
)> {
let spec = test_spec::<E>();
if !spec.fork_name_at_slot::<E>(Slot::new(1)).gloas_enabled() {
return None;
}
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec.into())
.keypairs(KEYPAIRS[0..VALIDATOR_COUNT].to_vec())
.node_custody_type(NodeCustodyType::Supernode)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
harness.advance_slot();
let state = harness.get_current_state();
let slot = harness.get_current_slot();
let ((block, _), envelope, _) = harness.make_block_with_envelope(state, slot).await;
Some((block, envelope.expect("gloas block should have envelope")))
}
#[tokio::test]
async fn range_sync_block_new_gloas_accepts_matching_envelope() {
let Some((block, envelope)) = make_gloas_range_sync_block_inputs().await else {
return;
};
let available_envelope = Box::new(AvailableEnvelope::new(Arc::new(envelope), vec![]));
let result = RangeSyncBlock::new_gloas(block, Some(available_envelope));
assert!(
result.is_ok(),
"new_gloas should accept matching block/envelope, got: {:?}",
result
);
}
#[tokio::test]
async fn range_sync_block_new_gloas_allows_missing_envelope() {
let Some((block, _)) = make_gloas_range_sync_block_inputs().await else {
return;
};
let result = RangeSyncBlock::new_gloas(block, None);
assert!(
result.is_ok(),
"new_gloas should allow None envelope, got: {:?}",
result
);
}
#[tokio::test]
async fn range_sync_block_new_gloas_rejects_mismatched_block_root() {
let Some((block, mut envelope)) = make_gloas_range_sync_block_inputs().await else {
return;
};
envelope.message.beacon_block_root = Hash256::repeat_byte(0x11);
let available_envelope = Box::new(AvailableEnvelope::new(Arc::new(envelope), vec![]));
let result = RangeSyncBlock::new_gloas(block, Some(available_envelope));
assert!(
matches!(result, Err(ref err) if err.contains("envelope block root mismatch")),
"new_gloas should reject mismatched block root, got: {:?}",
result
);
}
#[tokio::test]
async fn range_sync_block_new_gloas_rejects_slot_mismatch() {
let Some((block, mut envelope)) = make_gloas_range_sync_block_inputs().await else {
return;
};
envelope.message.payload.slot_number += 1;
let available_envelope = Box::new(AvailableEnvelope::new(Arc::new(envelope), vec![]));
let result = RangeSyncBlock::new_gloas(block, Some(available_envelope));
assert!(
matches!(result, Err(ref err) if err.contains("envelope slot mismatch")),
"new_gloas should reject mismatched slot, got: {:?}",
result
);
}
#[tokio::test]
async fn range_sync_block_new_gloas_rejects_builder_index_mismatch() {
let Some((block, mut envelope)) = make_gloas_range_sync_block_inputs().await else {
return;
};
envelope.message.builder_index += 1;
let available_envelope = Box::new(AvailableEnvelope::new(Arc::new(envelope), vec![]));
let result = RangeSyncBlock::new_gloas(block, Some(available_envelope));
assert!(
matches!(result, Err(ref err) if err.contains("envelope builder index mismatch")),
"new_gloas should reject mismatched builder index, got: {:?}",
result
);
}
#[tokio::test]
async fn range_sync_block_new_gloas_rejects_block_hash_mismatch() {
let Some((block, mut envelope)) = make_gloas_range_sync_block_inputs().await else {
return;
};
envelope.message.payload.block_hash = ExecutionBlockHash::repeat_byte(0x22);
let available_envelope = Box::new(AvailableEnvelope::new(Arc::new(envelope), vec![]));
let result = RangeSyncBlock::new_gloas(block, Some(available_envelope));
assert!(
matches!(result, Err(ref err) if err.contains("envelope block hash mismatch")),
"new_gloas should reject mismatched block hash, got: {:?}",
result
);
}
// Test that RpcBlock::new() rejects blocks when blob count doesn't match expected.
#[tokio::test]
async fn range_sync_block_construction_fails_with_wrong_blob_count() {