mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-19 21:04:41 +00:00
Convert RpcBlock to an enum that indicates availability (#8424)
Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu> Co-Authored-By: Mark Mackey <mark@sigmaprime.io> Co-Authored-By: Eitan Seri-Levi <eserilev@gmail.com> Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#![cfg(not(debug_assertions))]
|
||||
|
||||
use beacon_chain::attestation_simulator::produce_unaggregated_attestation;
|
||||
use beacon_chain::block_verification_types::RpcBlock;
|
||||
use beacon_chain::custody_context::NodeCustodyType;
|
||||
use beacon_chain::test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy};
|
||||
use beacon_chain::validator_monitor::UNAGGREGATED_ATTESTATION_LAG_SLOTS;
|
||||
use beacon_chain::{StateSkipConfig, WhenSlotSkipped, metrics};
|
||||
@@ -114,6 +116,8 @@ async fn produces_attestations() {
|
||||
.keypairs(KEYPAIRS[..].to_vec())
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
// SemiSupernode ensures enough columns are stored for sampling + custody validation for RpcBlock
|
||||
.node_custody_type(NodeCustodyType::SemiSupernode)
|
||||
.build();
|
||||
|
||||
let chain = &harness.chain;
|
||||
@@ -221,14 +225,16 @@ async fn produces_attestations() {
|
||||
|
||||
let rpc_block =
|
||||
harness.build_rpc_block_from_store_blobs(Some(block_root), Arc::new(block.clone()));
|
||||
let beacon_chain::data_availability_checker::MaybeAvailableBlock::Available(
|
||||
available_block,
|
||||
) = chain
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_rpc_block(rpc_block)
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("block should be available")
|
||||
|
||||
let available_block = match rpc_block {
|
||||
RpcBlock::FullyAvailable(available_block) => {
|
||||
chain
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_available_block(&available_block)
|
||||
.unwrap();
|
||||
available_block
|
||||
}
|
||||
RpcBlock::BlockOnly { .. } => panic!("block should be available"),
|
||||
};
|
||||
|
||||
let early_attestation = {
|
||||
@@ -288,14 +294,17 @@ async fn early_attester_cache_old_request() {
|
||||
|
||||
let rpc_block = harness
|
||||
.build_rpc_block_from_store_blobs(Some(head.beacon_block_root), head.beacon_block.clone());
|
||||
let beacon_chain::data_availability_checker::MaybeAvailableBlock::Available(available_block) =
|
||||
harness
|
||||
.chain
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_rpc_block(rpc_block)
|
||||
.unwrap()
|
||||
else {
|
||||
panic!("block should be available")
|
||||
|
||||
let available_block = match rpc_block {
|
||||
RpcBlock::FullyAvailable(available_block) => {
|
||||
harness
|
||||
.chain
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_available_block(&available_block)
|
||||
.unwrap();
|
||||
available_block
|
||||
}
|
||||
RpcBlock::BlockOnly { .. } => panic!("block should be available"),
|
||||
};
|
||||
|
||||
harness
|
||||
|
||||
@@ -77,7 +77,7 @@ async fn rpc_blobs_with_invalid_header_signature() {
|
||||
// Process the block without blobs so that it doesn't become available.
|
||||
harness.advance_slot();
|
||||
let rpc_block = harness
|
||||
.build_rpc_block_from_blobs(block_root, signed_block.clone(), None)
|
||||
.build_rpc_block_from_blobs(signed_block.clone(), None, false)
|
||||
.unwrap();
|
||||
let availability = harness
|
||||
.chain
|
||||
@@ -85,11 +85,12 @@ async fn rpc_blobs_with_invalid_header_signature() {
|
||||
block_root,
|
||||
rpc_block,
|
||||
NotifyExecutionLayer::Yes,
|
||||
BlockImportSource::RangeSync,
|
||||
BlockImportSource::Lookup,
|
||||
|| Ok(()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
availability,
|
||||
AvailabilityProcessingStatus::MissingComponents(slot, block_root)
|
||||
@@ -114,6 +115,8 @@ async fn rpc_blobs_with_invalid_header_signature() {
|
||||
.process_rpc_blobs(slot, block_root, blob_sidecars)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
println!("{:?}", err);
|
||||
assert!(matches!(
|
||||
err,
|
||||
BlockError::InvalidSignature(InvalidSignature::ProposerSignature)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![cfg(not(debug_assertions))]
|
||||
|
||||
use beacon_chain::block_verification_types::{AsBlock, ExecutedBlock, RpcBlock};
|
||||
use beacon_chain::data_availability_checker::{AvailabilityCheckError, AvailableBlockData};
|
||||
use beacon_chain::data_column_verification::CustodyDataColumn;
|
||||
use beacon_chain::{
|
||||
AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, ExecutionPendingBlock,
|
||||
WhenSlotSkipped,
|
||||
custody_context::NodeCustodyType,
|
||||
test_utils::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, test_spec,
|
||||
@@ -11,7 +13,7 @@ use beacon_chain::{
|
||||
};
|
||||
use beacon_chain::{
|
||||
BeaconSnapshot, BlockError, ChainConfig, ChainSegmentResult, IntoExecutionPendingBlock,
|
||||
InvalidSignature, NotifyExecutionLayer,
|
||||
InvalidSignature, NotifyExecutionLayer, signature_verify_chain_segment,
|
||||
};
|
||||
use bls::{AggregateSignature, Keypair, Signature};
|
||||
use fixed_bytes::FixedBytesExtended;
|
||||
@@ -39,6 +41,7 @@ const BLOCK_INDICES: &[usize] = &[0, 1, 32, 64, 68 + 1, 129, CHAIN_SEGMENT_LENGT
|
||||
static KEYPAIRS: LazyLock<Vec<Keypair>> =
|
||||
LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT));
|
||||
|
||||
// TODO(#8633): Delete this unnecessary enum and refactor this file to use `AvailableBlockData` instead.
|
||||
enum DataSidecars<E: EthSpec> {
|
||||
Blobs(BlobSidecarList<E>),
|
||||
DataColumns(Vec<CustodyDataColumn<E>>),
|
||||
@@ -130,32 +133,65 @@ fn get_harness(
|
||||
harness
|
||||
}
|
||||
|
||||
fn chain_segment_blocks(
|
||||
fn chain_segment_blocks<T>(
|
||||
chain_segment: &[BeaconSnapshot<E>],
|
||||
chain_segment_sidecars: &[Option<DataSidecars<E>>],
|
||||
) -> Vec<RpcBlock<E>> {
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
) -> Vec<RpcBlock<E>>
|
||||
where
|
||||
T: BeaconChainTypes<EthSpec = E>,
|
||||
{
|
||||
chain_segment
|
||||
.iter()
|
||||
.zip(chain_segment_sidecars.iter())
|
||||
.map(|(snapshot, data_sidecars)| {
|
||||
let block = snapshot.beacon_block.clone();
|
||||
build_rpc_block(block, data_sidecars)
|
||||
build_rpc_block(block, data_sidecars, chain.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_rpc_block(
|
||||
fn build_rpc_block<T>(
|
||||
block: Arc<SignedBeaconBlock<E>>,
|
||||
data_sidecars: &Option<DataSidecars<E>>,
|
||||
) -> RpcBlock<E> {
|
||||
chain: Arc<BeaconChain<T>>,
|
||||
) -> RpcBlock<E>
|
||||
where
|
||||
T: BeaconChainTypes<EthSpec = E>,
|
||||
{
|
||||
match data_sidecars {
|
||||
Some(DataSidecars::Blobs(blobs)) => {
|
||||
RpcBlock::new(None, block, Some(blobs.clone())).unwrap()
|
||||
let block_data = AvailableBlockData::new_with_blobs(blobs.clone());
|
||||
RpcBlock::new(
|
||||
block,
|
||||
Some(block_data),
|
||||
&chain.data_availability_checker,
|
||||
chain.spec.clone(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
Some(DataSidecars::DataColumns(columns)) => {
|
||||
RpcBlock::new_with_custody_columns(None, block, columns.clone()).unwrap()
|
||||
let block_data = AvailableBlockData::new_with_data_columns(
|
||||
columns
|
||||
.iter()
|
||||
.map(|c| c.as_data_column().clone())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
RpcBlock::new(
|
||||
block,
|
||||
Some(block_data),
|
||||
&chain.data_availability_checker,
|
||||
chain.spec.clone(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
None => RpcBlock::new_without_blobs(None, block),
|
||||
None => RpcBlock::new(
|
||||
block,
|
||||
Some(AvailableBlockData::NoData),
|
||||
&chain.data_availability_checker,
|
||||
chain.spec.clone(),
|
||||
)
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,9 +302,10 @@ fn update_data_column_signed_header<E: EthSpec>(
|
||||
async fn chain_segment_full_segment() {
|
||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||
let blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let blocks: Vec<RpcBlock<E>> =
|
||||
chain_segment_blocks(&chain_segment, &chain_segment_blobs, harness.chain.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
harness
|
||||
.chain
|
||||
@@ -302,9 +339,11 @@ async fn chain_segment_full_segment() {
|
||||
#[tokio::test]
|
||||
async fn chain_segment_varying_chunk_size() {
|
||||
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
||||
let blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||
let blocks: Vec<RpcBlock<E>> =
|
||||
chain_segment_blocks(&chain_segment, &chain_segment_blobs, harness.chain.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
for chunk_size in &[1, 2, 31, 32, 33] {
|
||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||
@@ -346,9 +385,10 @@ async fn chain_segment_non_linear_parent_roots() {
|
||||
/*
|
||||
* Test with a block removed.
|
||||
*/
|
||||
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut blocks: Vec<RpcBlock<E>> =
|
||||
chain_segment_blocks(&chain_segment, &chain_segment_blobs, harness.chain.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
blocks.remove(2);
|
||||
|
||||
assert!(
|
||||
@@ -366,16 +406,21 @@ async fn chain_segment_non_linear_parent_roots() {
|
||||
/*
|
||||
* Test with a modified parent root.
|
||||
*/
|
||||
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut blocks: Vec<RpcBlock<E>> =
|
||||
chain_segment_blocks(&chain_segment, &chain_segment_blobs, harness.chain.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let (mut block, signature) = blocks[3].as_block().clone().deconstruct();
|
||||
*block.parent_root_mut() = Hash256::zero();
|
||||
blocks[3] = RpcBlock::new_without_blobs(
|
||||
None,
|
||||
|
||||
blocks[3] = RpcBlock::new(
|
||||
Arc::new(SignedBeaconBlock::from_block(block, signature)),
|
||||
);
|
||||
blocks[3].block_data().cloned(),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
@@ -403,15 +448,19 @@ async fn chain_segment_non_linear_slots() {
|
||||
* Test where a child is lower than the parent.
|
||||
*/
|
||||
|
||||
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut blocks: Vec<RpcBlock<E>> =
|
||||
chain_segment_blocks(&chain_segment, &chain_segment_blobs, harness.chain.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
let (mut block, signature) = blocks[3].as_block().clone().deconstruct();
|
||||
*block.slot_mut() = Slot::new(0);
|
||||
blocks[3] = RpcBlock::new_without_blobs(
|
||||
None,
|
||||
blocks[3] = RpcBlock::new(
|
||||
Arc::new(SignedBeaconBlock::from_block(block, signature)),
|
||||
);
|
||||
blocks[3].block_data().cloned(),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
@@ -429,15 +478,19 @@ async fn chain_segment_non_linear_slots() {
|
||||
* Test where a child is equal to the parent.
|
||||
*/
|
||||
|
||||
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let mut blocks: Vec<RpcBlock<E>> =
|
||||
chain_segment_blocks(&chain_segment, &chain_segment_blobs, harness.chain.clone())
|
||||
.into_iter()
|
||||
.collect();
|
||||
let (mut block, signature) = blocks[3].as_block().clone().deconstruct();
|
||||
*block.slot_mut() = blocks[2].slot();
|
||||
blocks[3] = RpcBlock::new_without_blobs(
|
||||
None,
|
||||
blocks[3] = RpcBlock::new(
|
||||
Arc::new(SignedBeaconBlock::from_block(block, signature)),
|
||||
);
|
||||
blocks[3].block_data().cloned(),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.chain.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
@@ -463,7 +516,9 @@ async fn assert_invalid_signature(
|
||||
let blocks: Vec<RpcBlock<E>> = snapshots
|
||||
.iter()
|
||||
.zip(chain_segment_blobs.iter())
|
||||
.map(|(snapshot, blobs)| build_rpc_block(snapshot.beacon_block.clone(), blobs))
|
||||
.map(|(snapshot, blobs)| {
|
||||
build_rpc_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Ensure the block will be rejected if imported in a chain segment.
|
||||
@@ -488,7 +543,9 @@ async fn assert_invalid_signature(
|
||||
.iter()
|
||||
.take(block_index)
|
||||
.zip(chain_segment_blobs.iter())
|
||||
.map(|(snapshot, blobs)| build_rpc_block(snapshot.beacon_block.clone(), blobs))
|
||||
.map(|(snapshot, blobs)| {
|
||||
build_rpc_block(snapshot.beacon_block.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
|
||||
// imported prior to this test.
|
||||
@@ -505,6 +562,7 @@ async fn assert_invalid_signature(
|
||||
build_rpc_block(
|
||||
snapshots[block_index].beacon_block.clone(),
|
||||
&chain_segment_blobs[block_index],
|
||||
harness.chain.clone(),
|
||||
),
|
||||
NotifyExecutionLayer::Yes,
|
||||
BlockImportSource::Lookup,
|
||||
@@ -562,7 +620,9 @@ async fn invalid_signature_gossip_block() {
|
||||
.iter()
|
||||
.take(block_index)
|
||||
.zip(chain_segment_blobs.iter())
|
||||
.map(|(snapshot, blobs)| build_rpc_block(snapshot.beacon_block.clone(), blobs))
|
||||
.map(|(snapshot, blobs)| {
|
||||
build_rpc_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
|
||||
})
|
||||
.collect();
|
||||
harness
|
||||
.chain
|
||||
@@ -571,7 +631,13 @@ async fn invalid_signature_gossip_block() {
|
||||
.into_block_error()
|
||||
.expect("should import all blocks prior to the one being tested");
|
||||
let signed_block = SignedBeaconBlock::from_block(block, junk_signature());
|
||||
let rpc_block = RpcBlock::new_without_blobs(None, Arc::new(signed_block));
|
||||
let rpc_block = RpcBlock::new(
|
||||
Arc::new(signed_block),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let process_res = harness
|
||||
.chain
|
||||
.process_block(
|
||||
@@ -613,7 +679,9 @@ async fn invalid_signature_block_proposal() {
|
||||
let blocks: Vec<RpcBlock<E>> = snapshots
|
||||
.iter()
|
||||
.zip(chain_segment_blobs.iter())
|
||||
.map(|(snapshot, blobs)| build_rpc_block(snapshot.beacon_block.clone(), blobs))
|
||||
.map(|(snapshot, blobs)| {
|
||||
build_rpc_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// Ensure the block will be rejected if imported in a chain segment.
|
||||
let process_res = harness
|
||||
@@ -930,7 +998,9 @@ async fn invalid_signature_deposit() {
|
||||
let blocks: Vec<RpcBlock<E>> = snapshots
|
||||
.iter()
|
||||
.zip(chain_segment_blobs.iter())
|
||||
.map(|(snapshot, blobs)| build_rpc_block(snapshot.beacon_block.clone(), blobs))
|
||||
.map(|(snapshot, blobs)| {
|
||||
build_rpc_block(snapshot.beacon_block.clone(), blobs, harness.chain.clone())
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
!matches!(
|
||||
@@ -1572,7 +1642,13 @@ async fn add_base_block_to_altair_chain() {
|
||||
));
|
||||
|
||||
// Ensure that it would be impossible to import via `BeaconChain::process_block`.
|
||||
let base_rpc_block = RpcBlock::new_without_blobs(None, Arc::new(base_block.clone()));
|
||||
let base_rpc_block = RpcBlock::new(
|
||||
Arc::new(base_block.clone()),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
harness
|
||||
.chain
|
||||
@@ -1596,7 +1672,15 @@ async fn add_base_block_to_altair_chain() {
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(
|
||||
vec![RpcBlock::new_without_blobs(None, Arc::new(base_block))],
|
||||
vec![
|
||||
RpcBlock::new(
|
||||
Arc::new(base_block),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone()
|
||||
)
|
||||
.unwrap()
|
||||
],
|
||||
NotifyExecutionLayer::Yes,
|
||||
)
|
||||
.await,
|
||||
@@ -1709,7 +1793,13 @@ async fn add_altair_block_to_base_chain() {
|
||||
));
|
||||
|
||||
// Ensure that it would be impossible to import via `BeaconChain::process_block`.
|
||||
let altair_rpc_block = RpcBlock::new_without_blobs(None, Arc::new(altair_block.clone()));
|
||||
let altair_rpc_block = RpcBlock::new(
|
||||
Arc::new(altair_block.clone()),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
harness
|
||||
.chain
|
||||
@@ -1733,7 +1823,15 @@ async fn add_altair_block_to_base_chain() {
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(
|
||||
vec![RpcBlock::new_without_blobs(None, Arc::new(altair_block))],
|
||||
vec![
|
||||
RpcBlock::new(
|
||||
Arc::new(altair_block),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone()
|
||||
)
|
||||
.unwrap()
|
||||
],
|
||||
NotifyExecutionLayer::Yes
|
||||
)
|
||||
.await,
|
||||
@@ -1796,7 +1894,13 @@ async fn import_duplicate_block_unrealized_justification() {
|
||||
// Create two verified variants of the block, representing the same block being processed in
|
||||
// parallel.
|
||||
let notify_execution_layer = NotifyExecutionLayer::Yes;
|
||||
let rpc_block = RpcBlock::new_without_blobs(Some(block_root), block.clone());
|
||||
let rpc_block = RpcBlock::new(
|
||||
block.clone(),
|
||||
Some(AvailableBlockData::NoData),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let verified_block1 = rpc_block
|
||||
.clone()
|
||||
.into_execution_pending_block(block_root, chain, notify_execution_layer)
|
||||
@@ -1870,3 +1974,277 @@ async fn import_execution_pending_block<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that `signature_verify_chain_segment` errors with a chain segment of mixed `FullyAvailable`
|
||||
// and `BlockOnly` RpcBlocks. This situation should never happen in production.
|
||||
#[tokio::test]
|
||||
async fn signature_verify_mixed_rpc_block_variants() {
|
||||
let (snapshots, data_sidecars) = get_chain_segment().await;
|
||||
let snapshots: Vec<_> = snapshots.into_iter().take(10).collect();
|
||||
let data_sidecars: Vec<_> = data_sidecars.into_iter().take(10).collect();
|
||||
|
||||
let harness = get_harness(VALIDATOR_COUNT, NodeCustodyType::Fullnode);
|
||||
|
||||
let mut chain_segment = Vec::new();
|
||||
|
||||
for (i, (snapshot, blobs)) in snapshots.iter().zip(data_sidecars.iter()).enumerate() {
|
||||
let block = snapshot.beacon_block.clone();
|
||||
let block_root = snapshot.beacon_block_root;
|
||||
|
||||
// Alternate between FullyAvailable and BlockOnly
|
||||
let rpc_block = if i % 2 == 0 {
|
||||
// FullyAvailable - with blobs/columns if needed
|
||||
build_rpc_block(block, blobs, harness.chain.clone())
|
||||
} else {
|
||||
// BlockOnly - no data
|
||||
RpcBlock::new(
|
||||
block,
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.chain.spec.clone(),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
chain_segment.push((block_root, rpc_block));
|
||||
}
|
||||
|
||||
// This should error because `signature_verify_chain_segment` expects a list
|
||||
// of `RpcBlock::FullyAvailable`.
|
||||
assert!(signature_verify_chain_segment(chain_segment.clone(), &harness.chain).is_err());
|
||||
}
|
||||
|
||||
// Test that RpcBlock::new() rejects blocks when blob count doesn't match expected.
|
||||
#[tokio::test]
|
||||
async fn rpc_block_construction_fails_with_wrong_blob_count() {
|
||||
let spec = test_spec::<E>();
|
||||
|
||||
if !spec.fork_name_at_slot::<E>(Slot::new(0)).deneb_enabled()
|
||||
|| spec.fork_name_at_slot::<E>(Slot::new(0)).fulu_enabled()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
||||
.spec(spec.into())
|
||||
.keypairs(KEYPAIRS[0..VALIDATOR_COUNT].to_vec())
|
||||
.node_custody_type(NodeCustodyType::Fullnode)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
harness
|
||||
.extend_chain(
|
||||
E::slots_per_epoch() as usize * 2,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Get a block with blobs
|
||||
for slot in 1..=5 {
|
||||
let root = harness
|
||||
.chain
|
||||
.block_root_at_slot(Slot::new(slot), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let block = harness.chain.get_block(&root).await.unwrap().unwrap();
|
||||
|
||||
if let Ok(commitments) = block.message().body().blob_kzg_commitments()
|
||||
&& !commitments.is_empty()
|
||||
{
|
||||
let blobs = harness.chain.get_blobs(&root).unwrap().blobs().unwrap();
|
||||
|
||||
// Create AvailableBlockData with wrong number of blobs (remove one)
|
||||
let mut wrong_blobs_vec: Vec<_> = blobs.iter().cloned().collect();
|
||||
wrong_blobs_vec.pop();
|
||||
|
||||
let max_blobs = harness.spec.max_blobs_per_block(block.epoch()) as usize;
|
||||
let wrong_blobs = ssz_types::RuntimeVariableList::new(wrong_blobs_vec, max_blobs)
|
||||
.expect("should create BlobSidecarList");
|
||||
let block_data = AvailableBlockData::new_with_blobs(wrong_blobs);
|
||||
|
||||
// Try to create RpcBlock with wrong blob count
|
||||
let result = RpcBlock::new(
|
||||
Arc::new(block),
|
||||
Some(block_data),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.chain.spec.clone(),
|
||||
);
|
||||
|
||||
// Should fail with MissingBlobs
|
||||
assert!(
|
||||
matches!(result, Err(AvailabilityCheckError::MissingBlobs)),
|
||||
"RpcBlock construction should fail with wrong blob count, got: {:?}",
|
||||
result
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("No block with blobs found");
|
||||
}
|
||||
|
||||
// Test that RpcBlock::new() rejects blocks when custody columns are incomplete.
|
||||
#[tokio::test]
|
||||
async fn rpc_block_rejects_missing_custody_columns() {
|
||||
let spec = test_spec::<E>();
|
||||
|
||||
if !spec.fork_name_at_slot::<E>(Slot::new(0)).fulu_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
||||
.spec(spec.into())
|
||||
.keypairs(KEYPAIRS[0..VALIDATOR_COUNT].to_vec())
|
||||
.node_custody_type(NodeCustodyType::Fullnode)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
// Extend chain to create some blocks with data columns
|
||||
harness
|
||||
.extend_chain(
|
||||
5,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Get a block with data columns
|
||||
for slot in 1..=5 {
|
||||
let root = harness
|
||||
.chain
|
||||
.block_root_at_slot(Slot::new(slot), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let block = harness.chain.get_block(&root).await.unwrap().unwrap();
|
||||
|
||||
if let Ok(commitments) = block.message().body().blob_kzg_commitments()
|
||||
&& !commitments.is_empty()
|
||||
{
|
||||
let fork_name = harness.chain.spec.fork_name_at_slot::<E>(block.slot());
|
||||
let columns = harness
|
||||
.chain
|
||||
.get_data_columns(&root, fork_name)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
if columns.len() > 1 {
|
||||
// Create AvailableBlockData with incomplete columns (remove one)
|
||||
let mut incomplete_columns: Vec<_> = columns.to_vec();
|
||||
incomplete_columns.pop();
|
||||
|
||||
let block_data = AvailableBlockData::new_with_data_columns(incomplete_columns);
|
||||
|
||||
// Try to create RpcBlock with incomplete custody columns
|
||||
let result = RpcBlock::new(
|
||||
Arc::new(block),
|
||||
Some(block_data),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.chain.spec.clone(),
|
||||
);
|
||||
|
||||
// Should fail with MissingCustodyColumns
|
||||
assert!(
|
||||
matches!(result, Err(AvailabilityCheckError::MissingCustodyColumns)),
|
||||
"RpcBlock construction should fail with missing custody columns, got: {:?}",
|
||||
result
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("No block with data columns found");
|
||||
}
|
||||
|
||||
// Test that RpcBlock::new() allows construction past the data availability boundary.
|
||||
// When a block is past the DA boundary, we should be able to construct an RpcBlock
|
||||
// with NoData even if the block has blob commitments, since columns are not expected.
|
||||
#[tokio::test]
|
||||
async fn rpc_block_allows_construction_past_da_boundary() {
|
||||
let spec = test_spec::<E>();
|
||||
|
||||
if !spec.fork_name_at_slot::<E>(Slot::new(0)).fulu_enabled() {
|
||||
return;
|
||||
}
|
||||
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
||||
.spec(spec.into())
|
||||
.keypairs(KEYPAIRS[0..VALIDATOR_COUNT].to_vec())
|
||||
.node_custody_type(NodeCustodyType::Fullnode)
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.build();
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
// Extend chain to create some blocks with blob commitments
|
||||
harness
|
||||
.extend_chain(
|
||||
5,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Find a block with blob commitments
|
||||
for slot in 1..=5 {
|
||||
let root = harness
|
||||
.chain
|
||||
.block_root_at_slot(Slot::new(slot), WhenSlotSkipped::None)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let block = harness.chain.get_block(&root).await.unwrap().unwrap();
|
||||
|
||||
if let Ok(commitments) = block.message().body().blob_kzg_commitments()
|
||||
&& !commitments.is_empty()
|
||||
{
|
||||
let block_epoch = block.epoch();
|
||||
|
||||
// Advance the slot clock far into the future, past the DA boundary
|
||||
// For a block to be past the DA boundary:
|
||||
// current_epoch - min_epochs_for_data_column_sidecars_requests > block_epoch
|
||||
let min_epochs_for_data = harness.spec.min_epochs_for_data_column_sidecars_requests;
|
||||
let future_epoch = block_epoch + min_epochs_for_data + 10;
|
||||
let future_slot = future_epoch.start_slot(E::slots_per_epoch());
|
||||
harness.chain.slot_clock.set_slot(future_slot.as_u64());
|
||||
|
||||
// Now verify the block is past the DA boundary
|
||||
let da_boundary = harness
|
||||
.chain
|
||||
.data_availability_checker
|
||||
.data_availability_boundary()
|
||||
.expect("DA boundary should be set");
|
||||
assert!(
|
||||
block_epoch < da_boundary,
|
||||
"Block should be past the DA boundary. Block epoch: {}, DA boundary: {}",
|
||||
block_epoch,
|
||||
da_boundary
|
||||
);
|
||||
|
||||
// Try to create RpcBlock with NoData for a block past DA boundary
|
||||
// This should succeed since columns are not expected for blocks past DA boundary
|
||||
let result = RpcBlock::new(
|
||||
Arc::new(block),
|
||||
Some(AvailableBlockData::NoData),
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.chain.spec.clone(),
|
||||
);
|
||||
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"RpcBlock construction should succeed for blocks past DA boundary, got: {:?}",
|
||||
result
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("No block with blob commitments found");
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ async fn rpc_columns_with_invalid_header_signature() {
|
||||
// Process the block without blobs so that it doesn't become available.
|
||||
harness.advance_slot();
|
||||
let rpc_block = harness
|
||||
.build_rpc_block_from_blobs(block_root, signed_block.clone(), None)
|
||||
.build_rpc_block_from_blobs(signed_block.clone(), None, false)
|
||||
.unwrap();
|
||||
let availability = harness
|
||||
.chain
|
||||
|
||||
@@ -685,7 +685,13 @@ async fn invalidates_all_descendants() {
|
||||
assert_eq!(fork_parent_state.slot(), fork_parent_slot);
|
||||
let ((fork_block, _), _fork_post_state) =
|
||||
rig.harness.make_block(fork_parent_state, fork_slot).await;
|
||||
let fork_rpc_block = RpcBlock::new_without_blobs(None, fork_block.clone());
|
||||
let fork_rpc_block = RpcBlock::new(
|
||||
fork_block.clone(),
|
||||
None,
|
||||
&rig.harness.chain.data_availability_checker,
|
||||
rig.harness.chain.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let fork_block_root = rig
|
||||
.harness
|
||||
.chain
|
||||
@@ -787,7 +793,13 @@ async fn switches_heads() {
|
||||
let ((fork_block, _), _fork_post_state) =
|
||||
rig.harness.make_block(fork_parent_state, fork_slot).await;
|
||||
let fork_parent_root = fork_block.parent_root();
|
||||
let fork_rpc_block = RpcBlock::new_without_blobs(None, fork_block.clone());
|
||||
let fork_rpc_block = RpcBlock::new(
|
||||
fork_block.clone(),
|
||||
None,
|
||||
&rig.harness.chain.data_availability_checker,
|
||||
rig.harness.chain.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let fork_block_root = rig
|
||||
.harness
|
||||
.chain
|
||||
@@ -1059,7 +1071,13 @@ async fn invalid_parent() {
|
||||
));
|
||||
|
||||
// Ensure the block built atop an invalid payload is invalid for import.
|
||||
let rpc_block = RpcBlock::new_without_blobs(None, block.clone());
|
||||
let rpc_block = RpcBlock::new(
|
||||
block.clone(),
|
||||
None,
|
||||
&rig.harness.chain.data_availability_checker,
|
||||
rig.harness.chain.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
rig.harness.chain.process_block(rpc_block.block_root(), rpc_block, NotifyExecutionLayer::Yes, BlockImportSource::Lookup,
|
||||
|| Ok(()),
|
||||
@@ -1384,7 +1402,13 @@ async fn recover_from_invalid_head_by_importing_blocks() {
|
||||
} = InvalidHeadSetup::new().await;
|
||||
|
||||
// Import the fork block, it should become the head.
|
||||
let fork_rpc_block = RpcBlock::new_without_blobs(None, fork_block.clone());
|
||||
let fork_rpc_block = RpcBlock::new(
|
||||
fork_block.clone(),
|
||||
None,
|
||||
&rig.harness.chain.data_availability_checker,
|
||||
rig.harness.chain.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
rig.harness
|
||||
.chain
|
||||
.process_block(
|
||||
|
||||
@@ -21,7 +21,6 @@ use beacon_chain::{
|
||||
compute_proposer_duties_from_head, ensure_state_can_determine_proposers_for_epoch,
|
||||
},
|
||||
custody_context::NodeCustodyType,
|
||||
data_availability_checker::MaybeAvailableBlock,
|
||||
historical_blocks::HistoricalBlockError,
|
||||
migrate::MigratorConfig,
|
||||
};
|
||||
@@ -3176,16 +3175,19 @@ async fn weak_subjectivity_sync_test(
|
||||
.expect("should get block")
|
||||
.expect("should get block");
|
||||
|
||||
if let MaybeAvailableBlock::Available(block) = harness
|
||||
.chain
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_rpc_block(
|
||||
let rpc_block =
|
||||
harness.build_rpc_block_from_store_blobs(Some(block_root), Arc::new(full_block));
|
||||
|
||||
match rpc_block {
|
||||
RpcBlock::FullyAvailable(available_block) => {
|
||||
harness
|
||||
.build_rpc_block_from_store_blobs(Some(block_root), Arc::new(full_block)),
|
||||
)
|
||||
.expect("should verify kzg")
|
||||
{
|
||||
available_blocks.push(block);
|
||||
.chain
|
||||
.data_availability_checker
|
||||
.verify_kzg_for_available_block(&available_block)
|
||||
.expect("should verify kzg");
|
||||
available_blocks.push(available_block);
|
||||
}
|
||||
RpcBlock::BlockOnly { .. } => panic!("Should be an available block"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3194,15 +3196,16 @@ async fn weak_subjectivity_sync_test(
|
||||
let mut batch_with_invalid_first_block =
|
||||
available_blocks.iter().map(clone_block).collect::<Vec<_>>();
|
||||
batch_with_invalid_first_block[0] = {
|
||||
let (block_root, block, data) = clone_block(&available_blocks[0]).deconstruct();
|
||||
let (_, block, data) = clone_block(&available_blocks[0]).deconstruct();
|
||||
let mut corrupt_block = (*block).clone();
|
||||
*corrupt_block.signature_mut() = Signature::empty();
|
||||
AvailableBlock::__new_for_testing(
|
||||
block_root,
|
||||
AvailableBlock::new(
|
||||
Arc::new(corrupt_block),
|
||||
data,
|
||||
&beacon_chain.data_availability_checker,
|
||||
Arc::new(spec),
|
||||
)
|
||||
.expect("available block")
|
||||
};
|
||||
|
||||
// Importing the invalid batch should error.
|
||||
@@ -3746,7 +3749,13 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
|
||||
assert_eq!(split.block_root, valid_fork_block.parent_root());
|
||||
assert_ne!(split.state_root, unadvanced_split_state_root);
|
||||
|
||||
let invalid_fork_rpc_block = RpcBlock::new_without_blobs(None, invalid_fork_block.clone());
|
||||
let invalid_fork_rpc_block = RpcBlock::new(
|
||||
invalid_fork_block.clone(),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
// Applying the invalid block should fail.
|
||||
let err = harness
|
||||
.chain
|
||||
@@ -3762,7 +3771,13 @@ async fn process_blocks_and_attestations_for_unaligned_checkpoint() {
|
||||
assert!(matches!(err, BlockError::WouldRevertFinalizedSlot { .. }));
|
||||
|
||||
// Applying the valid block should succeed, but it should not become head.
|
||||
let valid_fork_rpc_block = RpcBlock::new_without_blobs(None, valid_fork_block.clone());
|
||||
let valid_fork_rpc_block = RpcBlock::new(
|
||||
valid_fork_block.clone(),
|
||||
None,
|
||||
&harness.chain.data_availability_checker,
|
||||
harness.spec.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
harness
|
||||
.chain
|
||||
.process_block(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use beacon_chain::{
|
||||
BeaconChain, ChainConfig, NotifyExecutionLayer, StateSkipConfig, WhenSlotSkipped,
|
||||
attestation_verification::Error as AttnError,
|
||||
custody_context::NodeCustodyType,
|
||||
test_utils::{
|
||||
AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
||||
OP_POOL_DB_KEY,
|
||||
@@ -54,6 +55,28 @@ fn get_harness_with_config(
|
||||
harness
|
||||
}
|
||||
|
||||
/// Creates a harness with SemiSupernode custody type to ensure enough columns are stored
|
||||
/// for sampling validation in Fulu.
|
||||
fn get_harness_semi_supernode(
|
||||
validator_count: usize,
|
||||
) -> BeaconChainHarness<EphemeralHarnessType<MinimalEthSpec>> {
|
||||
let harness = BeaconChainHarness::builder(MinimalEthSpec)
|
||||
.default_spec()
|
||||
.chain_config(ChainConfig {
|
||||
reconstruct_historic_states: true,
|
||||
..Default::default()
|
||||
})
|
||||
.keypairs(KEYPAIRS[0..validator_count].to_vec())
|
||||
.fresh_ephemeral_store()
|
||||
.mock_execution_layer()
|
||||
.node_custody_type(NodeCustodyType::SemiSupernode)
|
||||
.build();
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
harness
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn massive_skips() {
|
||||
let harness = get_harness(8);
|
||||
@@ -679,8 +702,9 @@ async fn unaggregated_attestations_added_to_fork_choice_all_updated() {
|
||||
|
||||
async fn run_skip_slot_test(skip_slots: u64) {
|
||||
let num_validators = 8;
|
||||
let harness_a = get_harness(num_validators);
|
||||
let harness_b = get_harness(num_validators);
|
||||
// SemiSupernode ensures enough columns are stored for sampling + custody RpcBlock validation
|
||||
let harness_a = get_harness_semi_supernode(num_validators);
|
||||
let harness_b = get_harness_semi_supernode(num_validators);
|
||||
|
||||
for _ in 0..skip_slots {
|
||||
harness_a.advance_slot();
|
||||
|
||||
Reference in New Issue
Block a user