Merge remote-tracking branch 'origin/unstable' into alpha-spec-11

This commit is contained in:
Michael Sproul
2026-06-23 10:58:17 +10:00
7 changed files with 278 additions and 236 deletions

View File

@@ -9,6 +9,7 @@ beacon_node/beacon_chain/src/block_reward.rs
beacon_node/http_api/src/attestation_performance.rs
beacon_node/http_api/src/block_packing_efficiency.rs
beacon_node/http_api/src/block_rewards.rs
beacon_node/http_api/src/beacon/execution_payload_envelope.rs
common/eth2/src/lighthouse/attestation_performance.rs
common/eth2/src/lighthouse/block_packing_efficiency.rs
common/eth2/src/lighthouse/block_rewards.rs

2
Cargo.lock generated
View File

@@ -2475,7 +2475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de"
dependencies = [
"data-encoding",
"syn 1.0.109",
"syn 2.0.117",
]
[[package]]

View File

@@ -594,15 +594,113 @@ mod tests {
}
}
/// The custody-column coupling tests below build Fulu data-column sidecars directly, which is
/// incompatible with a Gloas genesis (Gloas columns have a different structure). Skip them when
/// `FORK_NAME` schedules Gloas at genesis. TODO(gloas): port the harness to build Gloas columns.
fn skip_under_gloas() -> bool {
/// Returns true when `FORK_NAME` schedules Gloas at genesis. Used to make the custody-column
/// coupling tests fork-aware: under Gloas the columns are coupled into the payload envelope, so
/// these tests build Gloas blocks/columns/envelopes and complete the payloads request.
fn is_gloas_env() -> bool {
test_spec::<E>()
.fork_name_at_epoch(Epoch::new(0))
.gloas_enabled()
}
/// The fork to build blocks/columns for in the custody-column coupling tests. Under a Gloas
/// genesis we must build Gloas columns (and matching envelopes); otherwise we use Fulu.
fn custody_test_fork() -> ForkName {
if is_gloas_env() {
ForkName::Gloas
} else {
ForkName::Fulu
}
}
/// A spec with custody-column (PeerDAS) coupling enabled at genesis, matching the env fork.
/// Under a Gloas env this enables Gloas at genesis (so envelopes are coupled); otherwise it
/// enables Fulu at genesis.
fn custody_test_spec() -> ChainSpec {
let mut spec = test_spec::<E>();
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = Some(Epoch::new(0));
if is_gloas_env() {
spec.gloas_fork_epoch = Some(Epoch::new(0));
}
spec
}
/// A block, its data columns, and (under Gloas) its matching payload envelope.
type BlockColumnsEnvelope = (
Arc<SignedBeaconBlock<E>>,
DataColumnSidecarList<E>,
Option<Arc<SignedExecutionPayloadEnvelope<E>>>,
);
/// Builds `count` blocks with their data columns, plus a matching payload envelope under Gloas.
/// Under Fulu the envelope is `None`.
fn make_blocks_and_columns(
count: usize,
num_blobs: NumBlobs,
spec: &ChainSpec,
) -> Vec<BlockColumnsEnvelope> {
let fork = custody_test_fork();
let mut u = types::test_utils::test_unstructured();
(0..count)
.map(|_| {
// `NumBlobs` isn't `Clone`, so rebuild a fresh value for each block.
let num_blobs = match &num_blobs {
NumBlobs::Random => NumBlobs::Random,
NumBlobs::Number(n) => NumBlobs::Number(*n),
NumBlobs::None => NumBlobs::None,
};
let (block, data_columns) =
generate_rand_block_and_data_columns::<E>(fork, num_blobs, &mut u, spec)
.unwrap();
let block = Arc::new(block);
let envelope = is_gloas_env().then(|| matching_envelope(&block));
(block, data_columns, envelope)
})
.collect()
}
/// Under Gloas, completes the payloads request with the envelopes from `blocks`. Under Fulu this
/// is a no-op (there is no payloads request). Pass the subset of blocks whose envelopes should
/// be supplied.
fn add_envelopes_if_gloas(
info: &mut RangeBlockComponentsRequest<E>,
payloads_req_id: Option<PayloadEnvelopesByRangeRequestId>,
blocks: &[BlockColumnsEnvelope],
) {
if let Some(payloads_req_id) = payloads_req_id {
info.add_payload_envelopes(
payloads_req_id,
blocks
.iter()
.filter_map(|(_, _, envelope)| envelope.clone())
.collect(),
)
.unwrap();
}
}
/// Asserts the coupled `responses` carry the expected data. Pre-Gloas only the count is checked;
/// under Gloas each block must additionally wrap an envelope holding `expected_columns` columns.
fn assert_custody_columns_coupled(
responses: &[RangeSyncBlock<E>],
expected_blocks: usize,
expected_columns: usize,
) {
assert_eq!(responses.len(), expected_blocks);
if is_gloas_env() {
for response in responses {
match response {
RangeSyncBlock::Gloas {
envelope: Some(envelope),
..
} => assert_eq!(envelope.columns.len(), expected_columns),
other => panic!("expected Gloas block with envelope, got {other:?}"),
}
}
}
}
fn blocks_id(parent_request_id: ComponentsByRangeRequestId) -> BlocksByRangeRequestId {
BlocksByRangeRequestId {
id: 1,
@@ -798,38 +896,33 @@ mod tests {
#[test]
fn no_blobs_into_responses() {
// This exercises the pre-Gloas blobs/no-data coupling path. Gloas coupling is covered
// by the dedicated `setup_gloas_coupling` tests below.
if skip_under_gloas() {
return;
}
let spec = Arc::new(test_spec::<E>());
let mut u = types::test_utils::test_unstructured();
let blocks = (0..4)
.map(|_| {
generate_rand_block_and_blobs::<E>(
spec.fork_name_at_epoch(Epoch::new(0)),
NumBlobs::None,
&mut u,
)
.unwrap()
.0
.into()
})
.collect::<Vec<Arc<SignedBeaconBlock<E>>>>();
let blocks_req_id = blocks_id(components_id());
let mut info =
RangeBlockComponentsRequest::<E>::new(blocks_req_id, None, None, None, Span::none());
// Send blocks and complete terminate response
info.add_blocks(blocks_req_id, blocks).unwrap();
// Coupling of blocks that carry no data. Pre-Gloas there is simply no data request; under
// Gloas each block still couples to its (empty-column) payload envelope, so the envelope
// request is driven too.
let spec = Arc::new(custody_test_spec());
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
let blocks = make_blocks_and_columns(4, NumBlobs::None, &spec);
// Assert response is finished and RpcBlocks can be constructed
info.responses(da_checker, spec).unwrap().unwrap();
let components_id = components_id();
let blocks_req_id = blocks_id(components_id);
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
let mut info = RangeBlockComponentsRequest::<E>::new(
blocks_req_id,
None,
is_gloas_env().then(|| (vec![], vec![])),
payloads_req_id,
Span::none(),
);
info.add_blocks(
blocks_req_id,
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
)
.unwrap();
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
let responses = info.responses(da_checker, spec).unwrap().unwrap();
assert_custody_columns_coupled(&responses, blocks.len(), 0);
}
#[test]
@@ -875,33 +968,17 @@ mod tests {
#[test]
fn rpc_block_with_custody_columns() {
if skip_under_gloas() {
return;
}
let mut spec = test_spec::<E>();
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = Some(Epoch::new(0));
let spec = Arc::new(spec);
let spec = Arc::new(custody_test_spec());
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
let expects_custody_columns = da_checker
.custody_context()
.sampling_columns_for_epoch(Epoch::new(0), &spec)
.to_vec();
let mut u = types::test_utils::test_unstructured();
let blocks = (0..4)
.map(|_| {
generate_rand_block_and_data_columns::<E>(
ForkName::Fulu,
NumBlobs::Number(1),
&mut u,
&spec,
)
.unwrap()
})
.collect::<Vec<_>>();
let blocks = make_blocks_and_columns(4, NumBlobs::Number(1), &spec);
let components_id = components_id();
let blocks_req_id = blocks_id(components_id);
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
let columns_req_id = expects_custody_columns
.iter()
.enumerate()
@@ -919,13 +996,13 @@ mod tests {
blocks_req_id,
None,
Some((columns_req_id.clone(), expects_custody_columns.clone())),
None,
payloads_req_id,
Span::none(),
);
// Send blocks and complete terminate response
info.add_blocks(
blocks_req_id,
blocks.iter().map(|b| b.0.clone().into()).collect(),
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
)
.unwrap();
// Assert response is not finished
@@ -938,7 +1015,12 @@ mod tests {
*req,
blocks
.iter()
.flat_map(|b| b.1.iter().filter(|d| *d.index() == column_index).cloned())
.flat_map(|(_, columns, _)| {
columns
.iter()
.filter(|d| *d.index() == column_index)
.cloned()
})
.collect(),
)
.unwrap();
@@ -951,19 +1033,18 @@ mod tests {
}
}
// Under Gloas the columns are coupled into the payload envelope; supply the envelopes so
// the request can complete.
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
// All completed construct response
info.responses(da_checker, spec).unwrap().unwrap();
let responses = info.responses(da_checker, spec).unwrap().unwrap();
assert_custody_columns_coupled(&responses, blocks.len(), expects_custody_columns.len());
}
#[test]
fn rpc_block_with_custody_columns_batched() {
if skip_under_gloas() {
return;
}
let mut spec = test_spec::<E>();
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = Some(Epoch::new(0));
let spec = Arc::new(spec);
let spec = Arc::new(custody_test_spec());
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
let expected_sampling_columns = da_checker
.custody_context()
@@ -981,6 +1062,7 @@ mod tests {
let components_id = components_id();
let blocks_req_id = blocks_id(components_id);
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
let columns_req_id = batched_column_requests
.iter()
.enumerate()
@@ -999,27 +1081,16 @@ mod tests {
blocks_req_id,
None,
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
None,
payloads_req_id,
Span::none(),
);
let mut u = types::test_utils::test_unstructured();
let blocks = (0..4)
.map(|_| {
generate_rand_block_and_data_columns::<E>(
ForkName::Fulu,
NumBlobs::Number(1),
&mut u,
&spec,
)
.unwrap()
})
.collect::<Vec<_>>();
let blocks = make_blocks_and_columns(4, NumBlobs::Number(1), &spec);
// Send blocks and complete terminate response
info.add_blocks(
blocks_req_id,
blocks.iter().map(|b| b.0.clone().into()).collect(),
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
)
.unwrap();
// Assert response is not finished
@@ -1032,8 +1103,9 @@ mod tests {
*req,
blocks
.iter()
.flat_map(|b| {
b.1.iter()
.flat_map(|(_, columns, _)| {
columns
.iter()
.filter(|d| column_indices.contains(d.index()))
.cloned()
})
@@ -1049,8 +1121,13 @@ mod tests {
}
}
// Under Gloas the columns are coupled into the payload envelope; supply the envelopes so
// the request can complete.
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
// All completed construct response
info.responses(da_checker, spec).unwrap().unwrap();
let responses = info.responses(da_checker, spec).unwrap().unwrap();
assert_custody_columns_coupled(&responses, blocks.len(), expected_sampling_columns.len());
}
#[test]
@@ -1164,31 +1241,18 @@ mod tests {
#[test]
fn missing_custody_columns_from_faulty_peers() {
if skip_under_gloas() {
return;
}
// GIVEN: A request expecting sampling columns from multiple peers
let spec = Arc::new(test_spec::<E>());
let spec = Arc::new(custody_test_spec());
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
let expected_sampling_columns = da_checker
.custody_context()
.sampling_columns_for_epoch(Epoch::new(0), &spec)
.to_vec();
let mut u = types::test_utils::test_unstructured();
let blocks = (0..2)
.map(|_| {
generate_rand_block_and_data_columns::<E>(
ForkName::Fulu,
NumBlobs::Number(1),
&mut u,
&spec,
)
.unwrap()
})
.collect::<Vec<_>>();
let blocks = make_blocks_and_columns(2, NumBlobs::Number(1), &spec);
let components_id = components_id();
let blocks_req_id = blocks_id(components_id);
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
let columns_req_id = expected_sampling_columns
.iter()
.enumerate()
@@ -1206,16 +1270,19 @@ mod tests {
blocks_req_id,
None,
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
None,
payloads_req_id,
Span::none(),
);
// AND: All blocks are received successfully
info.add_blocks(
blocks_req_id,
blocks.iter().map(|b| b.0.clone().into()).collect(),
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
)
.unwrap();
// Under Gloas the payloads request must be completed for `responses` to proceed; the
// faulty-peer detection happens before the envelope wrap and is fork-independent.
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
// AND: Only the first 2 sampling columns are received successfully
for (i, &column_index) in expected_sampling_columns.iter().take(2).enumerate() {
@@ -1224,7 +1291,12 @@ mod tests {
*req,
blocks
.iter()
.flat_map(|b| b.1.iter().filter(|d| *d.index() == column_index).cloned())
.flat_map(|(_, columns, _)| {
columns
.iter()
.filter(|d| *d.index() == column_index)
.cloned()
})
.collect(),
)
.unwrap();
@@ -1263,34 +1335,18 @@ mod tests {
#[test]
fn retry_logic_after_peer_failures() {
if skip_under_gloas() {
return;
}
// GIVEN: A request expecting sampling columns where some peers initially fail
let mut spec = test_spec::<E>();
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = Some(Epoch::new(0));
let spec = Arc::new(spec);
let spec = Arc::new(custody_test_spec());
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
let expected_sampling_columns = da_checker
.custody_context()
.sampling_columns_for_epoch(Epoch::new(0), &spec)
.to_vec();
let mut u = types::test_utils::test_unstructured();
let blocks = (0..2)
.map(|_| {
generate_rand_block_and_data_columns::<E>(
ForkName::Fulu,
NumBlobs::Number(1),
&mut u,
&spec,
)
.unwrap()
})
.collect::<Vec<_>>();
let blocks = make_blocks_and_columns(2, NumBlobs::Number(1), &spec);
let components_id = components_id();
let blocks_req_id = blocks_id(components_id);
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
let columns_req_id = expected_sampling_columns
.iter()
.enumerate()
@@ -1308,16 +1364,18 @@ mod tests {
blocks_req_id,
None,
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
None,
payloads_req_id,
Span::none(),
);
// AND: All blocks are received
info.add_blocks(
blocks_req_id,
blocks.iter().map(|b| b.0.clone().into()).collect(),
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
)
.unwrap();
// Under Gloas the payloads request must be completed for `responses` to proceed.
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
// AND: Only partial sampling columns are received (first column but not others)
let (req0, _) = columns_req_id.first().unwrap();
@@ -1325,8 +1383,9 @@ mod tests {
*req0,
blocks
.iter()
.flat_map(|b| {
b.1.iter()
.flat_map(|(_, columns, _)| {
columns
.iter()
.filter(|d| *d.index() == expected_sampling_columns[0])
.cloned()
})
@@ -1363,8 +1422,9 @@ mod tests {
new_columns_req_id,
blocks
.iter()
.flat_map(|b| {
b.1.iter()
.flat_map(|(_, columns, _)| {
columns
.iter()
.filter(|d| failed_column_indices.contains(d.index()))
.cloned()
})
@@ -1383,34 +1443,18 @@ mod tests {
#[test]
fn max_retries_exceeded_behavior() {
if skip_under_gloas() {
return;
}
// GIVEN: A request where peers consistently fail to provide required columns
let mut spec = test_spec::<E>();
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = Some(Epoch::new(0));
let spec = Arc::new(spec);
let spec = Arc::new(custody_test_spec());
let da_checker = Arc::new(test_da_checker(spec.clone(), NodeCustodyType::Fullnode));
let expected_sampling_columns = da_checker
.custody_context()
.sampling_columns_for_epoch(Epoch::new(0), &spec)
.to_vec();
let mut u = types::test_utils::test_unstructured();
let blocks = (0..1)
.map(|_| {
generate_rand_block_and_data_columns::<E>(
ForkName::Fulu,
NumBlobs::Number(1),
&mut u,
&spec,
)
.unwrap()
})
.collect::<Vec<_>>();
let blocks = make_blocks_and_columns(1, NumBlobs::Number(1), &spec);
let components_id = components_id();
let blocks_req_id = blocks_id(components_id);
let payloads_req_id = is_gloas_env().then(|| payloads_id(components_id));
let columns_req_id = expected_sampling_columns
.iter()
.enumerate()
@@ -1428,16 +1472,18 @@ mod tests {
blocks_req_id,
None,
Some((columns_req_id.clone(), expected_sampling_columns.clone())),
None,
payloads_req_id,
Span::none(),
);
// AND: All blocks are received
info.add_blocks(
blocks_req_id,
blocks.iter().map(|b| b.0.clone().into()).collect(),
blocks.iter().map(|(block, _, _)| block.clone()).collect(),
)
.unwrap();
// Under Gloas the payloads request must be completed for `responses` to proceed.
add_envelopes_if_gloas(&mut info, payloads_req_id, &blocks);
// AND: Only the first sampling column is provided successfully
let (req0, _) = columns_req_id.first().unwrap();
@@ -1445,8 +1491,9 @@ mod tests {
*req0,
blocks
.iter()
.flat_map(|b| {
b.1.iter()
.flat_map(|(_, columns, _)| {
columns
.iter()
.filter(|d| *d.index() == expected_sampling_columns[0])
.cloned()
})

View File

@@ -45,6 +45,17 @@ use types::{
const D: Duration = Duration::new(0, 0);
/// Extract the Gloas payload envelope (if any) carried by a stored `RangeSyncBlock`.
fn envelope_of(block: &RangeSyncBlock<E>) -> Option<Arc<SignedExecutionPayloadEnvelope<E>>> {
match block {
RangeSyncBlock::Gloas {
envelope: Some(envelope),
..
} => Some(envelope.envelope().clone()),
_ => None,
}
}
/// Gloas genesis needs enough validators to populate `proposer_lookahead`.
const TEST_RIG_VALIDATOR_COUNT: usize = 8;
@@ -331,7 +342,6 @@ impl TestRig {
fork_name,
network_blocks_by_root: <_>::default(),
network_blocks_by_slot: <_>::default(),
network_envelopes_by_root: <_>::default(),
penalties: <_>::default(),
seen_lookups: <_>::default(),
requests: <_>::default(),
@@ -669,7 +679,10 @@ impl TestRig {
if self.complete_strategy.hold_envelope_for_block == Some(block_root) {
return;
}
let envelope = self.network_envelopes_by_root.get(&block_root).cloned();
let envelope = self
.network_blocks_by_root
.get(&block_root)
.and_then(envelope_of);
self.send_rpc_envelope_response(req_id, peer_id, envelope);
}
@@ -843,7 +856,7 @@ impl TestRig {
if self.complete_strategy.hold_envelope_for_block == Some(block_root) {
return None;
}
self.network_envelopes_by_root.get(&block_root).cloned()
envelope_of(block)
})
.collect::<Vec<_>>();
self.send_rpc_envelopes_response(req_id, peer_id, &envelopes);
@@ -1057,13 +1070,7 @@ impl TestRig {
.await;
let block = external_harness.get_full_block(&block_root);
let block_slot = block.slot();
self.insert_external_block(
block,
external_harness
.chain
.get_payload_envelope(&block_root)
.unwrap(),
);
self.insert_external_block(block);
blocks.push((block_slot, block_root));
}
@@ -1171,8 +1178,7 @@ impl TestRig {
// Cache every block through the single `get_full_block` + `insert_external_block2` path.
for root in [g_root, a_root, c_root, b_root] {
let block = external_harness.get_full_block(&root);
let envelope = external_harness.chain.get_payload_envelope(&root).unwrap();
self.insert_external_block(block, envelope);
self.insert_external_block(block);
}
self.harness.set_current_slot(child_slot);
@@ -1200,21 +1206,12 @@ impl TestRig {
Some((r, fork))
}
fn insert_external_block(
&mut self,
block: RangeSyncBlock<E>,
envelope: Option<SignedExecutionPayloadEnvelope<E>>,
) {
fn insert_external_block(&mut self, block: RangeSyncBlock<E>) {
let block_root = block.canonical_root();
let block_slot = block.slot();
self.network_blocks_by_root
.insert(block_root, block.clone());
self.network_blocks_by_slot.insert(block_slot, block);
// Cache Gloas envelopes for lookup RPCs.
if let Some(envelope) = envelope {
self.network_envelopes_by_root
.insert(block_root, envelope.into());
}
self.log(&format!(
"Produced block {block_root:?} slot {block_slot} in external harness",
));
@@ -1300,9 +1297,9 @@ impl TestRig {
let range_sync_block = if block.fork_name_unchecked().gloas_enabled() {
// Gloas carries data columns in the payload envelope, not in `block_data`.
let envelope = self
.network_envelopes_by_root
.network_blocks_by_root
.get(&block_root)
.cloned()
.and_then(envelope_of)
.map(|envelope| AvailableEnvelope::new(envelope, columns.unwrap_or_default()));
RangeSyncBlock::new_gloas(block, envelope).unwrap()
} else {
@@ -1370,6 +1367,8 @@ impl TestRig {
.unwrap_or_else(|| panic!("No block at slot {slot}"))
.clone();
let block_root = rpc_block.canonical_root();
let block_state_root = rpc_block.as_block().state_root();
let envelope = envelope_of(&rpc_block);
self.harness
.chain
.process_block(
@@ -1381,6 +1380,18 @@ impl TestRig {
)
.await
.unwrap();
// Gloas: import the payload envelope so the block counts as full for its children.
if let Some(envelope) = envelope {
let state = self
.harness
.chain
.get_state(&block_state_root, Some(Slot::new(slot)), false)
.expect("should load state")
.expect("state should exist");
self.harness
.process_envelope(block_root, (*envelope).clone(), &state, block_state_root)
.await;
}
}
self.harness.chain.recompute_head_at_current_slot().await;
}
@@ -1495,6 +1506,17 @@ impl TestRig {
panic!("Some downscore events: {:?}", self.penalties);
}
}
fn assert_no_block_requests(&self) {
assert_eq!(
self.requests
.iter()
.filter(|(request, _)| matches!(request, RequestType::BlocksByRoot(_)))
.collect::<Vec<_>>(),
Vec::<&(RequestType<E>, AppRequestId)>::new(),
"There should be no block requests"
);
}
fn assert_failed_lookup_sync(&mut self) {
assert!(self.created_lookups() > 0, "no created lookups");
assert_eq!(self.completed_lookups(), 0, "some completed lookups");
@@ -1623,6 +1645,10 @@ impl TestRig {
genesis_fork().fulu_enabled().then(Self::default)
}
fn new_after_gloas() -> Option<Self> {
genesis_fork().gloas_enabled().then(Self::default)
}
pub fn new_fulu_peer_test(fulu_test_type: FuluTestType) -> Option<Self> {
genesis_fork().fulu_enabled().then(|| {
Self::new(TestRigConfig {
@@ -2129,7 +2155,8 @@ async fn happy_path_unknown_data_parent(depth: usize) {
let Some(mut r) = TestRig::new_after_fulu() else {
return;
};
// No unknown-parent data-column trigger post-Gloas.
// Fulu-only: the `UnknownDataColumnParent` trigger doesn't exist post-Gloas (columns ride in
// the payload envelope, not as standalone data columns).
if r.is_after_gloas() {
return;
}
@@ -2359,10 +2386,6 @@ async fn test_single_block_lookup_ignored_response() {
/// Assert that if the beacon processor returns DuplicateFullyImported, the lookup completes successfully
async fn test_single_block_lookup_duplicate_response() {
let mut r = TestRig::default();
// The mock only covers block processing; Gloas also needs real envelope/column results.
if r.is_after_gloas() {
return;
}
r.build_chain_and_trigger_last_block(1).await;
// Send a DuplicateFullyImported response, the lookup should complete successfully
r.simulate(
@@ -2427,10 +2450,6 @@ async fn lookups_form_chain() {
/// Assert that if a lookup chain (by appending ancestors) is too long we drop it
async fn test_parent_lookup_too_deep_grow_ancestor_one() {
let mut r = TestRig::default();
// TODO(gloas): range sync does not fetch payload envelopes yet.
if r.is_after_gloas() {
return;
}
r.build_chain(PARENT_DEPTH_TOLERANCE + 1).await;
r.trigger_with_last_block();
r.simulate(SimulateConfig::happy_path()).await;
@@ -2575,13 +2594,13 @@ 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 post-Fulu, as the block needs custody columns to remain in the da_checker
/// Assert that if the lookup's block is in the da_checker we don't download it again (pre-Gloas).
async fn block_in_da_checker_skips_download_fulu() {
// 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;
};
// TODO(gloas): the helper does not populate the envelope missing-component path yet.
// Pre-Gloas only; the Gloas equivalent is `block_in_da_checker_skips_download_gloas`.
if r.is_after_gloas() {
return;
}
@@ -2594,14 +2613,28 @@ async fn block_in_da_checker_skips_download() {
r.trigger_with_block_at_slot(1);
r.simulate(SimulateConfig::happy_path()).await;
r.assert_successful_lookup_sync();
assert_eq!(
r.requests
.iter()
.filter(|(request, _)| matches!(request, RequestType::BlocksByRoot(_)))
.collect::<Vec<_>>(),
Vec::<&(RequestType<E>, AppRequestId)>::new(),
"There should be no block requests"
);
r.assert_no_block_requests();
}
#[tokio::test]
/// Assert that if the lookup's block is in the da_checker we don't download it again (Gloas).
async fn block_in_da_checker_skips_download_gloas() {
let Some(mut r) = TestRig::new_after_gloas() else {
return;
};
// A Gloas block carries no inline DA, so a lone block never sits in the da_checker awaiting
// components: only a FULL *child* proves the block published a payload and supplies the peers
// that serve its columns/envelope. Build a parent + FULL child, insert the PARENT into the
// da_checker, then trigger via the child (which is provided by the trigger, not downloaded).
// The parent lookup must then skip the parent's block download.
r.build_chain(2).await;
let parent = r.block_at_slot(1);
let child = r.block_at_slot(2);
r.import_block_to_da_checker(parent).await;
r.trigger_unknown_parent_blocks_from_all_peers(&[child]);
r.simulate(SimulateConfig::happy_path()).await;
r.assert_successful_lookup_sync();
r.assert_no_block_requests();
}
macro_rules! fulu_peer_matrix_tests {

View File

@@ -21,7 +21,7 @@ use tokio::sync::mpsc;
use tracing_subscriber::fmt::MakeWriter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use types::{ForkName, Hash256, MinimalEthSpec as E, SignedExecutionPayloadEnvelope, Slot};
use types::{ForkName, Hash256, MinimalEthSpec as E, Slot};
mod lookups;
mod range;
@@ -77,10 +77,6 @@ struct TestRig {
/// Blocks that will be used in the test but may not be known to `harness` yet.
network_blocks_by_root: HashMap<Hash256, RangeSyncBlock<E>>,
network_blocks_by_slot: HashMap<Slot, RangeSyncBlock<E>>,
/// Gloas execution payload envelopes keyed by block root, populated during `build_chain`
/// from the external harness store. The rig serves these when a lookup issues a
/// `PayloadEnvelopesByRoot` request.
network_envelopes_by_root: HashMap<Hash256, Arc<SignedExecutionPayloadEnvelope<E>>>,
penalties: Vec<ReportedPenalty>,
/// All seen lookups through the test run
seen_lookups: HashMap<Id, SeenLookup>,

View File

@@ -34,13 +34,6 @@ use types::{Epoch, EthSpec, Hash256, MinimalEthSpec as E, Slot};
const SLOTS_PER_EPOCH: usize = 8;
impl TestRig {
/// Range sync doesn't yet ingest Gloas blocks in these tests: the range harness doesn't serve
/// payload envelopes, so a Gloas block never becomes fully available and sync can't complete.
/// Skip the affected completion tests under a Gloas genesis. TODO(gloas): support range sync.
fn skip_range_sync_under_gloas(&self) -> bool {
self.fork_name.gloas_enabled()
}
fn add_head_peer(&mut self) -> PeerId {
let local_info = self.local_info();
self.add_supernode_peer(SyncInfo {
@@ -267,9 +260,6 @@ impl TestRig {
#[tokio::test]
async fn head_sync_completes() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_head_sync().await;
r.simulate(SimulateConfig::happy_path()).await;
r.assert_head_sync_completed();
@@ -281,9 +271,6 @@ async fn head_sync_completes() {
#[tokio::test]
async fn finalized_to_head_transition() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_and_head_sync().await;
r.simulate(SimulateConfig::happy_path()).await;
r.assert_range_sync_completed();
@@ -295,9 +282,6 @@ async fn finalized_to_head_transition() {
#[tokio::test]
async fn finalized_sync_completes() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_sync().await;
r.simulate(SimulateConfig::happy_path()).await;
r.assert_range_sync_completed();
@@ -309,9 +293,6 @@ async fn finalized_sync_completes() {
#[tokio::test]
async fn batch_rpc_error_retries() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_sync().await;
r.simulate(SimulateConfig::happy_path().return_rpc_error(RPCError::UnsupportedProtocol))
.await;
@@ -380,9 +361,6 @@ async fn batch_peer_returns_partial_columns_then_succeeds() {
#[tokio::test]
async fn batch_non_faulty_failure_retries() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_sync().await;
r.simulate(SimulateConfig::happy_path().with_range_non_faulty_failures(1))
.await;
@@ -394,9 +372,6 @@ async fn batch_non_faulty_failure_retries() {
#[tokio::test]
async fn batch_faulty_failure_redownloads() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_sync().await;
r.simulate(SimulateConfig::happy_path().with_range_faulty_failures(1))
.await;
@@ -453,9 +428,6 @@ async fn late_response_for_removed_chain() {
#[tokio::test]
async fn ee_offline_then_online_resumes_sync() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_sync().await;
r.simulate(SimulateConfig::happy_path().with_ee_offline_for_n_range_responses(2))
.await;
@@ -468,9 +440,6 @@ async fn ee_offline_then_online_resumes_sync() {
#[tokio::test]
async fn finalized_sync_with_local_head_partial() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
r.setup_finalized_sync_with_local_head(3).await;
r.simulate(SimulateConfig::happy_path()).await;
r.assert_range_sync_completed();
@@ -481,9 +450,6 @@ async fn finalized_sync_with_local_head_partial() {
#[tokio::test]
async fn finalized_sync_with_local_head_near_target() {
let mut r = TestRig::default();
if r.skip_range_sync_under_gloas() {
return;
}
let target_epochs = 5;
let local_slots = (target_epochs * SLOTS_PER_EPOCH) - 1; // all blocks except last
r.build_chain(target_epochs * SLOTS_PER_EPOCH).await;
@@ -502,7 +468,7 @@ async fn finalized_sync_with_local_head_near_target() {
#[tokio::test]
async fn not_enough_custody_peers_then_peers_arrive() {
let mut r = TestRig::default();
if !r.fork_name.fulu_enabled() || r.skip_range_sync_under_gloas() {
if !r.fork_name.fulu_enabled() {
return;
}
let remote_info = r.setup_finalized_sync_insufficient_peers().await;
@@ -529,7 +495,7 @@ async fn not_enough_custody_peers_then_peers_arrive() {
#[tokio::test]
async fn finalized_sync_not_enough_custody_peers_resume_after_peer_cgc_update() {
let mut r = TestRig::default();
if !r.fork_name.fulu_enabled() || r.skip_range_sync_under_gloas() {
if !r.fork_name.fulu_enabled() {
return;
}

View File

@@ -108,7 +108,6 @@ impl GenericExecutionEngine for GethEngine {
.arg(http_auth_port.to_string())
.arg("--port")
.arg(network_port.to_string())
.arg("--allow-insecure-unlock")
.arg("--authrpc.jwtsecret")
.arg(jwt_secret_path.as_path().to_str().unwrap())
// This flag is required to help Geth perform reliably when feeding it blocks