mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 03:31:45 +00:00
3064 lines
111 KiB
Rust
3064 lines
111 KiB
Rust
use crate::network_beacon_processor::NetworkBeaconProcessor;
|
|
use crate::sync::block_lookups::{
|
|
BlockLookupSummary, PARENT_DEPTH_TOLERANCE, SINGLE_BLOCK_LOOKUP_MAX_ATTEMPTS,
|
|
};
|
|
use crate::sync::{
|
|
manager::{BlockProcessType, BlockProcessingResult, SyncManager},
|
|
peer_sampling::SamplingConfig,
|
|
SamplingId, SyncMessage,
|
|
};
|
|
use crate::NetworkMessage;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use super::*;
|
|
|
|
use crate::sync::block_lookups::common::ResponseType;
|
|
use beacon_chain::{
|
|
blob_verification::GossipVerifiedBlob,
|
|
block_verification_types::{AsBlock, BlockImportData},
|
|
data_availability_checker::Availability,
|
|
test_utils::{
|
|
generate_rand_block_and_blobs, generate_rand_block_and_data_columns, test_spec,
|
|
BeaconChainHarness, EphemeralHarnessType, NumBlobs,
|
|
},
|
|
validator_monitor::timestamp_now,
|
|
AvailabilityPendingExecutedBlock, AvailabilityProcessingStatus, BlockError,
|
|
PayloadVerificationOutcome, PayloadVerificationStatus,
|
|
};
|
|
use beacon_processor::WorkEvent;
|
|
use lighthouse_network::discovery::CombinedKey;
|
|
use lighthouse_network::{
|
|
rpc::{RPCError, RequestType, RpcErrorResponse},
|
|
service::api_types::{
|
|
AppRequestId, DataColumnsByRootRequestId, DataColumnsByRootRequester, Id,
|
|
SamplingRequester, SingleLookupReqId, SyncRequestId,
|
|
},
|
|
types::SyncState,
|
|
NetworkConfig, NetworkGlobals, PeerId, SyncInfo,
|
|
};
|
|
use slot_clock::{SlotClock, TestingSlotClock};
|
|
use tokio::sync::mpsc;
|
|
use tracing::info;
|
|
use types::{
|
|
data_column_sidecar::ColumnIndex,
|
|
test_utils::{SeedableRng, TestRandom, XorShiftRng},
|
|
BeaconState, BeaconStateBase, BlobSidecar, DataColumnSidecar, DataColumnSidecarList, EthSpec,
|
|
ForkContext, ForkName, Hash256, MinimalEthSpec as E, SignedBeaconBlock, Slot,
|
|
};
|
|
|
|
const D: Duration = Duration::new(0, 0);
|
|
const PARENT_FAIL_TOLERANCE: u8 = SINGLE_BLOCK_LOOKUP_MAX_ATTEMPTS;
|
|
const SAMPLING_REQUIRED_SUCCESSES: usize = 2;
|
|
type DCByRootIds = Vec<DCByRootId>;
|
|
type DCByRootId = (SyncRequestId, Vec<ColumnIndex>);
|
|
|
|
pub enum PeersConfig {
|
|
SupernodeAndRandom,
|
|
SupernodeOnly,
|
|
}
|
|
|
|
impl TestRig {
|
|
pub fn test_setup() -> Self {
|
|
Self::test_setup_with_options(false)
|
|
}
|
|
|
|
pub fn test_setup_as_supernode() -> Self {
|
|
Self::test_setup_with_options(true)
|
|
}
|
|
|
|
fn test_setup_with_options(is_supernode: bool) -> Self {
|
|
// Use `fork_from_env` logic to set correct fork epochs
|
|
let spec = test_spec::<E>();
|
|
|
|
// Initialise a new beacon chain
|
|
let harness = BeaconChainHarness::<EphemeralHarnessType<E>>::builder(E)
|
|
.spec(Arc::new(spec))
|
|
.deterministic_keypairs(1)
|
|
.fresh_ephemeral_store()
|
|
.mock_execution_layer()
|
|
.testing_slot_clock(TestingSlotClock::new(
|
|
Slot::new(0),
|
|
Duration::from_secs(0),
|
|
Duration::from_secs(12),
|
|
))
|
|
.build();
|
|
|
|
let chain = harness.chain.clone();
|
|
let fork_context = Arc::new(ForkContext::new::<E>(
|
|
Slot::new(0),
|
|
chain.genesis_validators_root,
|
|
&chain.spec,
|
|
));
|
|
|
|
let (network_tx, network_rx) = mpsc::unbounded_channel();
|
|
let (sync_tx, sync_rx) = mpsc::unbounded_channel::<SyncMessage<E>>();
|
|
// TODO(das): make the generation of the ENR use the deterministic rng to have consistent
|
|
// column assignments
|
|
let network_config = Arc::new(NetworkConfig::default());
|
|
let globals = Arc::new(NetworkGlobals::new_test_globals_as_supernode(
|
|
Vec::new(),
|
|
network_config,
|
|
chain.spec.clone(),
|
|
is_supernode,
|
|
));
|
|
let (beacon_processor, beacon_processor_rx) = NetworkBeaconProcessor::null_for_testing(
|
|
globals,
|
|
sync_tx,
|
|
chain.clone(),
|
|
harness.runtime.task_executor.clone(),
|
|
);
|
|
|
|
let fork_name = chain.spec.fork_name_at_slot::<E>(chain.slot().unwrap());
|
|
|
|
// All current tests expect synced and EL online state
|
|
beacon_processor
|
|
.network_globals
|
|
.set_sync_state(SyncState::Synced);
|
|
|
|
let spec = chain.spec.clone();
|
|
|
|
// deterministic seed
|
|
let rng = ChaCha20Rng::from_seed([0u8; 32]);
|
|
|
|
TestRig {
|
|
beacon_processor_rx,
|
|
beacon_processor_rx_queue: vec![],
|
|
network_rx,
|
|
network_rx_queue: vec![],
|
|
sync_rx,
|
|
sent_blocks_by_range: <_>::default(),
|
|
rng,
|
|
network_globals: beacon_processor.network_globals.clone(),
|
|
sync_manager: SyncManager::new(
|
|
chain,
|
|
network_tx,
|
|
beacon_processor.into(),
|
|
// Pass empty recv not tied to any tx
|
|
mpsc::unbounded_channel().1,
|
|
SamplingConfig::Custom {
|
|
required_successes: vec![SAMPLING_REQUIRED_SUCCESSES],
|
|
},
|
|
fork_context,
|
|
),
|
|
harness,
|
|
fork_name,
|
|
spec,
|
|
}
|
|
}
|
|
|
|
fn test_setup_after_deneb_before_fulu() -> Option<Self> {
|
|
let r = Self::test_setup();
|
|
if r.after_deneb() && !r.fork_name.fulu_enabled() {
|
|
Some(r)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn test_setup_after_fulu() -> Option<Self> {
|
|
let r = Self::test_setup();
|
|
if r.fork_name.fulu_enabled() {
|
|
Some(r)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn log(&self, msg: &str) {
|
|
info!(msg, "TEST_RIG");
|
|
}
|
|
|
|
pub fn after_deneb(&self) -> bool {
|
|
self.fork_name.deneb_enabled()
|
|
}
|
|
|
|
pub fn after_fulu(&self) -> bool {
|
|
self.fork_name.fulu_enabled()
|
|
}
|
|
|
|
fn trigger_unknown_parent_block(&mut self, peer_id: PeerId, block: Arc<SignedBeaconBlock<E>>) {
|
|
let block_root = block.canonical_root();
|
|
self.send_sync_message(SyncMessage::UnknownParentBlock(peer_id, block, block_root))
|
|
}
|
|
|
|
fn trigger_unknown_parent_blob(&mut self, peer_id: PeerId, blob: BlobSidecar<E>) {
|
|
self.send_sync_message(SyncMessage::UnknownParentBlob(peer_id, blob.into()));
|
|
}
|
|
|
|
fn trigger_unknown_block_from_attestation(&mut self, block_root: Hash256, peer_id: PeerId) {
|
|
self.send_sync_message(SyncMessage::UnknownBlockHashFromAttestation(
|
|
peer_id, block_root,
|
|
));
|
|
}
|
|
|
|
fn trigger_sample_block(&mut self, block_root: Hash256, block_slot: Slot) {
|
|
self.send_sync_message(SyncMessage::SampleBlock(block_root, block_slot))
|
|
}
|
|
|
|
/// Drain all sync messages in the sync_rx attached to the beacon processor
|
|
fn drain_sync_rx(&mut self) {
|
|
while let Ok(sync_message) = self.sync_rx.try_recv() {
|
|
self.send_sync_message(sync_message);
|
|
}
|
|
}
|
|
|
|
fn rand_block(&mut self) -> SignedBeaconBlock<E> {
|
|
self.rand_block_and_blobs(NumBlobs::None).0
|
|
}
|
|
|
|
fn rand_block_and_blobs(
|
|
&mut self,
|
|
num_blobs: NumBlobs,
|
|
) -> (SignedBeaconBlock<E>, Vec<BlobSidecar<E>>) {
|
|
let fork_name = self.fork_name;
|
|
let rng = &mut self.rng;
|
|
generate_rand_block_and_blobs::<E>(fork_name, num_blobs, rng, &self.spec)
|
|
}
|
|
|
|
fn rand_block_and_data_columns(&mut self) -> (SignedBeaconBlock<E>, DataColumnSidecarList<E>) {
|
|
let num_blobs = NumBlobs::Number(1);
|
|
generate_rand_block_and_data_columns::<E>(
|
|
self.fork_name,
|
|
num_blobs,
|
|
&mut self.rng,
|
|
&self.harness.spec,
|
|
)
|
|
}
|
|
|
|
pub fn rand_block_and_parent(
|
|
&mut self,
|
|
) -> (SignedBeaconBlock<E>, SignedBeaconBlock<E>, Hash256, Hash256) {
|
|
let parent = self.rand_block();
|
|
let parent_root = parent.canonical_root();
|
|
let mut block = self.rand_block();
|
|
*block.message_mut().parent_root_mut() = parent_root;
|
|
let block_root = block.canonical_root();
|
|
(parent, block, parent_root, block_root)
|
|
}
|
|
|
|
pub fn send_sync_message(&mut self, sync_message: SyncMessage<E>) {
|
|
self.sync_manager.handle_message(sync_message);
|
|
}
|
|
|
|
fn active_single_lookups(&self) -> Vec<BlockLookupSummary> {
|
|
self.sync_manager.active_single_lookups()
|
|
}
|
|
|
|
fn active_single_lookups_count(&self) -> usize {
|
|
self.sync_manager.active_single_lookups().len()
|
|
}
|
|
|
|
fn active_parent_lookups(&self) -> Vec<Vec<Hash256>> {
|
|
self.sync_manager.active_parent_lookups()
|
|
}
|
|
|
|
fn active_parent_lookups_count(&self) -> usize {
|
|
self.sync_manager.active_parent_lookups().len()
|
|
}
|
|
|
|
fn active_range_sync_chain(&mut self) -> (RangeSyncType, Slot, Slot) {
|
|
self.sync_manager.range_sync().state().unwrap().unwrap()
|
|
}
|
|
|
|
fn assert_single_lookups_count(&self, count: usize) {
|
|
assert_eq!(
|
|
self.active_single_lookups_count(),
|
|
count,
|
|
"Unexpected count of single lookups. Current lookups: {:?}",
|
|
self.active_single_lookups()
|
|
);
|
|
}
|
|
|
|
fn expect_no_active_sampling(&mut self) {
|
|
assert_eq!(
|
|
self.sync_manager.active_sampling_requests(),
|
|
Vec::<Hash256>::new(),
|
|
"expected no active sampling"
|
|
);
|
|
}
|
|
|
|
fn expect_active_sampling(&mut self, block_root: &Hash256) {
|
|
assert!(self
|
|
.sync_manager
|
|
.active_sampling_requests()
|
|
.contains(block_root));
|
|
}
|
|
|
|
fn expect_clean_finished_sampling(&mut self) {
|
|
self.expect_empty_network();
|
|
self.expect_sampling_result_work();
|
|
self.expect_no_active_sampling();
|
|
}
|
|
|
|
fn assert_parent_lookups_count(&self, count: usize) {
|
|
assert_eq!(
|
|
self.active_parent_lookups_count(),
|
|
count,
|
|
"Unexpected count of parent lookups. Parent lookups: {:?}. Current lookups: {:?}",
|
|
self.active_parent_lookups(),
|
|
self.active_single_lookups()
|
|
);
|
|
}
|
|
|
|
fn assert_lookup_is_active(&self, block_root: Hash256) {
|
|
let lookups = self.sync_manager.active_single_lookups();
|
|
if !lookups.iter().any(|l| l.1 == block_root) {
|
|
panic!("Expected lookup {block_root} to be the only active: {lookups:?}");
|
|
}
|
|
}
|
|
|
|
fn assert_lookup_peers(&self, block_root: Hash256, mut expected_peers: Vec<PeerId>) {
|
|
let mut lookup = self
|
|
.sync_manager
|
|
.active_single_lookups()
|
|
.into_iter()
|
|
.find(|l| l.1 == block_root)
|
|
.unwrap_or_else(|| panic!("no lookup for {block_root}"));
|
|
lookup.3.sort();
|
|
expected_peers.sort();
|
|
assert_eq!(
|
|
lookup.3, expected_peers,
|
|
"unexpected peers on lookup {block_root}"
|
|
);
|
|
}
|
|
|
|
fn insert_failed_chain(&mut self, block_root: Hash256) {
|
|
self.sync_manager.insert_failed_chain(block_root);
|
|
}
|
|
|
|
fn assert_not_failed_chain(&mut self, chain_hash: Hash256) {
|
|
let failed_chains = self.sync_manager.get_failed_chains();
|
|
if failed_chains.contains(&chain_hash) {
|
|
panic!("failed chains contain {chain_hash:?}: {failed_chains:?}");
|
|
}
|
|
}
|
|
|
|
fn assert_failed_chain(&mut self, chain_hash: Hash256) {
|
|
let failed_chains = self.sync_manager.get_failed_chains();
|
|
if !failed_chains.contains(&chain_hash) {
|
|
panic!("expected failed chains to contain {chain_hash:?}: {failed_chains:?}");
|
|
}
|
|
}
|
|
|
|
fn find_single_lookup_for(&self, block_root: Hash256) -> Id {
|
|
self.active_single_lookups()
|
|
.iter()
|
|
.find(|l| l.1 == block_root)
|
|
.unwrap_or_else(|| panic!("no single block lookup found for {block_root}"))
|
|
.0
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_no_active_single_lookups(&self) {
|
|
assert!(
|
|
self.active_single_lookups().is_empty(),
|
|
"expect no single block lookups: {:?}",
|
|
self.active_single_lookups()
|
|
);
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_no_active_lookups(&self) {
|
|
self.expect_no_active_single_lookups();
|
|
}
|
|
|
|
fn expect_no_active_lookups_empty_network(&mut self) {
|
|
self.expect_no_active_lookups();
|
|
self.expect_empty_network();
|
|
}
|
|
|
|
// Note: prefer to use `add_connected_peer_testing_only`. This is currently extensively used in
|
|
// lookup tests. We should consolidate this "add peer" methods in a future refactor
|
|
fn new_connected_peer(&mut self) -> PeerId {
|
|
self.add_connected_peer_testing_only(false)
|
|
}
|
|
|
|
// Note: prefer to use `add_connected_peer_testing_only`. This is currently extensively used in
|
|
// lookup tests. We should consolidate this "add peer" methods in a future refactor
|
|
fn new_connected_supernode_peer(&mut self) -> PeerId {
|
|
self.add_connected_peer_testing_only(true)
|
|
}
|
|
|
|
/// Add a random connected peer that is not known by the sync module
|
|
pub fn add_connected_peer_testing_only(&mut self, supernode: bool) -> PeerId {
|
|
let key = self.determinstic_key();
|
|
let peer_id = self
|
|
.network_globals
|
|
.peers
|
|
.write()
|
|
.__add_connected_peer_testing_only(supernode, &self.harness.spec, key);
|
|
let mut peer_custody_subnets = self
|
|
.network_globals
|
|
.peers
|
|
.read()
|
|
.peer_info(&peer_id)
|
|
.expect("peer was just added")
|
|
.custody_subnets_iter()
|
|
.map(|subnet| **subnet)
|
|
.collect::<Vec<_>>();
|
|
peer_custody_subnets.sort_unstable();
|
|
self.log(&format!(
|
|
"Added new peer for testing {peer_id:?} custody subnets {peer_custody_subnets:?}"
|
|
));
|
|
peer_id
|
|
}
|
|
|
|
/// Add a random connected peer + add it to sync with a specific remote Status
|
|
pub fn add_sync_peer(&mut self, supernode: bool, remote_info: SyncInfo) -> PeerId {
|
|
let peer_id = self.add_connected_peer_testing_only(supernode);
|
|
self.send_sync_message(SyncMessage::AddPeer(peer_id, remote_info));
|
|
peer_id
|
|
}
|
|
|
|
fn determinstic_key(&mut self) -> CombinedKey {
|
|
k256::ecdsa::SigningKey::random(&mut self.rng).into()
|
|
}
|
|
|
|
pub fn add_sync_peers(&mut self, config: PeersConfig, remote_info: SyncInfo) {
|
|
match config {
|
|
PeersConfig::SupernodeAndRandom => {
|
|
for _ in 0..100 {
|
|
self.add_sync_peer(false, remote_info.clone());
|
|
}
|
|
self.add_sync_peer(true, remote_info);
|
|
}
|
|
PeersConfig::SupernodeOnly => {
|
|
self.add_sync_peer(true, remote_info);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn new_connected_peers_for_peerdas(&mut self) {
|
|
// Enough sampling peers with few columns
|
|
for _ in 0..100 {
|
|
self.new_connected_peer();
|
|
}
|
|
// One supernode peer to ensure all columns have at least one peer
|
|
self.new_connected_supernode_peer();
|
|
}
|
|
|
|
fn parent_chain_processed_success(
|
|
&mut self,
|
|
chain_hash: Hash256,
|
|
blocks: &[Arc<SignedBeaconBlock<E>>],
|
|
) {
|
|
// Send import events for all pending parent blocks
|
|
for _ in blocks {
|
|
self.parent_block_processed_imported(chain_hash);
|
|
}
|
|
// Send final import event for the block that triggered the lookup
|
|
self.single_block_component_processed_imported(chain_hash);
|
|
}
|
|
|
|
/// Locate a parent lookup chain with tip hash `chain_hash`
|
|
fn find_oldest_parent_lookup(&self, chain_hash: Hash256) -> Hash256 {
|
|
let parent_chain = self
|
|
.active_parent_lookups()
|
|
.into_iter()
|
|
.find(|chain| chain.first() == Some(&chain_hash))
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"No parent chain with chain_hash {chain_hash:?}: Parent lookups {:?} Single lookups {:?}",
|
|
self.active_parent_lookups(),
|
|
self.active_single_lookups(),
|
|
)
|
|
});
|
|
*parent_chain.last().unwrap()
|
|
}
|
|
|
|
fn parent_block_processed(&mut self, chain_hash: Hash256, result: BlockProcessingResult) {
|
|
let id = self.find_single_lookup_for(self.find_oldest_parent_lookup(chain_hash));
|
|
self.single_block_component_processed(id, result);
|
|
}
|
|
|
|
fn parent_blob_processed(&mut self, chain_hash: Hash256, result: BlockProcessingResult) {
|
|
let id = self.find_single_lookup_for(self.find_oldest_parent_lookup(chain_hash));
|
|
self.single_blob_component_processed(id, result);
|
|
}
|
|
|
|
fn parent_block_processed_imported(&mut self, chain_hash: Hash256) {
|
|
self.parent_block_processed(
|
|
chain_hash,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(chain_hash)),
|
|
);
|
|
}
|
|
|
|
fn single_block_component_processed(&mut self, id: Id, result: BlockProcessingResult) {
|
|
self.send_sync_message(SyncMessage::BlockComponentProcessed {
|
|
process_type: BlockProcessType::SingleBlock { id },
|
|
result,
|
|
})
|
|
}
|
|
|
|
fn single_block_component_processed_imported(&mut self, block_root: Hash256) {
|
|
let id = self.find_single_lookup_for(block_root);
|
|
self.single_block_component_processed(
|
|
id,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(block_root)),
|
|
)
|
|
}
|
|
|
|
fn single_blob_component_processed(&mut self, id: Id, result: BlockProcessingResult) {
|
|
self.send_sync_message(SyncMessage::BlockComponentProcessed {
|
|
process_type: BlockProcessType::SingleBlob { id },
|
|
result,
|
|
})
|
|
}
|
|
|
|
fn parent_lookup_block_response(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
peer_id: PeerId,
|
|
beacon_block: Option<Arc<SignedBeaconBlock<E>>>,
|
|
) {
|
|
self.log("parent_lookup_block_response");
|
|
self.send_sync_message(SyncMessage::RpcBlock {
|
|
sync_request_id: SyncRequestId::SingleBlock { id },
|
|
peer_id,
|
|
beacon_block,
|
|
seen_timestamp: D,
|
|
});
|
|
}
|
|
|
|
fn single_lookup_block_response(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
peer_id: PeerId,
|
|
beacon_block: Option<Arc<SignedBeaconBlock<E>>>,
|
|
) {
|
|
self.log("single_lookup_block_response");
|
|
self.send_sync_message(SyncMessage::RpcBlock {
|
|
sync_request_id: SyncRequestId::SingleBlock { id },
|
|
peer_id,
|
|
beacon_block,
|
|
seen_timestamp: D,
|
|
});
|
|
}
|
|
|
|
fn parent_lookup_blob_response(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
peer_id: PeerId,
|
|
blob_sidecar: Option<Arc<BlobSidecar<E>>>,
|
|
) {
|
|
self.log(&format!(
|
|
"parent_lookup_blob_response {:?}",
|
|
blob_sidecar.as_ref().map(|b| b.index)
|
|
));
|
|
self.send_sync_message(SyncMessage::RpcBlob {
|
|
sync_request_id: SyncRequestId::SingleBlob { id },
|
|
peer_id,
|
|
blob_sidecar,
|
|
seen_timestamp: D,
|
|
});
|
|
}
|
|
|
|
fn single_lookup_blob_response(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
peer_id: PeerId,
|
|
blob_sidecar: Option<Arc<BlobSidecar<E>>>,
|
|
) {
|
|
self.send_sync_message(SyncMessage::RpcBlob {
|
|
sync_request_id: SyncRequestId::SingleBlob { id },
|
|
peer_id,
|
|
blob_sidecar,
|
|
seen_timestamp: D,
|
|
});
|
|
}
|
|
|
|
fn complete_single_lookup_blob_download(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
peer_id: PeerId,
|
|
blobs: Vec<BlobSidecar<E>>,
|
|
) {
|
|
for blob in blobs {
|
|
self.single_lookup_blob_response(id, peer_id, Some(blob.into()));
|
|
}
|
|
self.single_lookup_blob_response(id, peer_id, None);
|
|
}
|
|
|
|
fn complete_single_lookup_blob_lookup_valid(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
peer_id: PeerId,
|
|
blobs: Vec<BlobSidecar<E>>,
|
|
import: bool,
|
|
) {
|
|
let block_root = blobs.first().unwrap().block_root();
|
|
let block_slot = blobs.first().unwrap().slot();
|
|
self.complete_single_lookup_blob_download(id, peer_id, blobs);
|
|
self.expect_block_process(ResponseType::Blob);
|
|
self.single_blob_component_processed(
|
|
id.lookup_id,
|
|
if import {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(block_root))
|
|
} else {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
block_slot, block_root,
|
|
))
|
|
},
|
|
);
|
|
}
|
|
|
|
fn complete_lookup_block_download(&mut self, block: SignedBeaconBlock<E>) {
|
|
let block_root = block.canonical_root();
|
|
let id = self.expect_block_lookup_request(block_root);
|
|
self.expect_empty_network();
|
|
let peer_id = self.new_connected_peer();
|
|
self.single_lookup_block_response(id, peer_id, Some(block.into()));
|
|
self.single_lookup_block_response(id, peer_id, None);
|
|
}
|
|
|
|
fn complete_lookup_block_import_valid(&mut self, block_root: Hash256, import: bool) {
|
|
self.expect_block_process(ResponseType::Block);
|
|
let id = self.find_single_lookup_for(block_root);
|
|
self.single_block_component_processed(
|
|
id,
|
|
if import {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(block_root))
|
|
} else {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
Slot::new(0),
|
|
block_root,
|
|
))
|
|
},
|
|
)
|
|
}
|
|
|
|
fn complete_single_lookup_block_valid(&mut self, block: SignedBeaconBlock<E>, import: bool) {
|
|
let block_root = block.canonical_root();
|
|
self.complete_lookup_block_download(block);
|
|
self.complete_lookup_block_import_valid(block_root, import)
|
|
}
|
|
|
|
fn parent_lookup_failed(&mut self, id: SingleLookupReqId, peer_id: PeerId, error: RPCError) {
|
|
self.send_sync_message(SyncMessage::RpcError {
|
|
peer_id,
|
|
sync_request_id: SyncRequestId::SingleBlock { id },
|
|
error,
|
|
})
|
|
}
|
|
|
|
fn parent_lookup_failed_unavailable(&mut self, id: SingleLookupReqId, peer_id: PeerId) {
|
|
self.parent_lookup_failed(
|
|
id,
|
|
peer_id,
|
|
RPCError::ErrorResponse(
|
|
RpcErrorResponse::ResourceUnavailable,
|
|
"older than deneb".into(),
|
|
),
|
|
);
|
|
}
|
|
|
|
fn single_lookup_failed(&mut self, id: SingleLookupReqId, peer_id: PeerId, error: RPCError) {
|
|
self.send_sync_message(SyncMessage::RpcError {
|
|
peer_id,
|
|
sync_request_id: SyncRequestId::SingleBlock { id },
|
|
error,
|
|
})
|
|
}
|
|
|
|
fn return_empty_sampling_requests(&mut self, ids: DCByRootIds) {
|
|
for id in ids {
|
|
self.log(&format!("return empty data column for {id:?}"));
|
|
self.return_empty_sampling_request(id)
|
|
}
|
|
}
|
|
|
|
fn return_empty_sampling_request(&mut self, (sync_request_id, _): DCByRootId) {
|
|
let peer_id = PeerId::random();
|
|
// Send stream termination
|
|
self.send_sync_message(SyncMessage::RpcDataColumn {
|
|
sync_request_id,
|
|
peer_id,
|
|
data_column: None,
|
|
seen_timestamp: timestamp_now(),
|
|
});
|
|
}
|
|
|
|
fn sampling_requests_failed(
|
|
&mut self,
|
|
sampling_ids: DCByRootIds,
|
|
peer_id: PeerId,
|
|
error: RPCError,
|
|
) {
|
|
for (sync_request_id, _) in sampling_ids {
|
|
self.send_sync_message(SyncMessage::RpcError {
|
|
peer_id,
|
|
sync_request_id,
|
|
error: error.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
fn complete_valid_block_request(
|
|
&mut self,
|
|
id: SingleLookupReqId,
|
|
block: Arc<SignedBeaconBlock<E>>,
|
|
missing_components: bool,
|
|
) {
|
|
// Complete download
|
|
let peer_id = PeerId::random();
|
|
let slot = block.slot();
|
|
let block_root = block.canonical_root();
|
|
self.single_lookup_block_response(id, peer_id, Some(block));
|
|
self.single_lookup_block_response(id, peer_id, None);
|
|
// Expect processing and resolve with import
|
|
self.expect_block_process(ResponseType::Block);
|
|
self.single_block_component_processed(
|
|
id.lookup_id,
|
|
if missing_components {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
slot, block_root,
|
|
))
|
|
} else {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(block_root))
|
|
},
|
|
)
|
|
}
|
|
|
|
fn complete_valid_sampling_column_requests(
|
|
&mut self,
|
|
ids: DCByRootIds,
|
|
data_columns: DataColumnSidecarList<E>,
|
|
) {
|
|
for id in ids {
|
|
self.log(&format!("return valid data column for {id:?}"));
|
|
let indices = &id.1;
|
|
let columns_to_send = indices
|
|
.iter()
|
|
.map(|&i| data_columns[i as usize].clone())
|
|
.collect::<Vec<_>>();
|
|
self.complete_valid_sampling_column_request(id, &columns_to_send);
|
|
}
|
|
}
|
|
|
|
fn complete_valid_sampling_column_request(
|
|
&mut self,
|
|
id: DCByRootId,
|
|
data_columns: &[Arc<DataColumnSidecar<E>>],
|
|
) {
|
|
let first_dc = data_columns.first().unwrap();
|
|
let block_root = first_dc.block_root();
|
|
let sampling_request_id = match id.0 {
|
|
SyncRequestId::DataColumnsByRoot(DataColumnsByRootRequestId {
|
|
requester: DataColumnsByRootRequester::Sampling(sampling_id),
|
|
..
|
|
}) => sampling_id.sampling_request_id,
|
|
_ => unreachable!(),
|
|
};
|
|
self.complete_data_columns_by_root_request(id, data_columns);
|
|
|
|
// Expect work event
|
|
self.expect_rpc_sample_verify_work_event();
|
|
|
|
// Respond with valid result
|
|
self.send_sync_message(SyncMessage::SampleVerified {
|
|
id: SamplingId {
|
|
id: SamplingRequester::ImportedBlock(block_root),
|
|
sampling_request_id,
|
|
},
|
|
result: Ok(()),
|
|
})
|
|
}
|
|
|
|
fn complete_valid_custody_request(
|
|
&mut self,
|
|
ids: DCByRootIds,
|
|
data_columns: DataColumnSidecarList<E>,
|
|
missing_components: bool,
|
|
) {
|
|
let lookup_id = if let SyncRequestId::DataColumnsByRoot(DataColumnsByRootRequestId {
|
|
requester: DataColumnsByRootRequester::Custody(id),
|
|
..
|
|
}) = ids.first().unwrap().0
|
|
{
|
|
id.requester.0.lookup_id
|
|
} else {
|
|
panic!("not a custody requester")
|
|
};
|
|
|
|
let first_column = data_columns.first().cloned().unwrap();
|
|
|
|
for id in ids {
|
|
self.log(&format!("return valid data column for {id:?}"));
|
|
let indices = &id.1;
|
|
let columns_to_send = indices
|
|
.iter()
|
|
.map(|&i| data_columns[i as usize].clone())
|
|
.collect::<Vec<_>>();
|
|
self.complete_data_columns_by_root_request(id, &columns_to_send);
|
|
}
|
|
|
|
// Expect work event
|
|
self.expect_rpc_custody_column_work_event();
|
|
|
|
// Respond with valid result
|
|
self.send_sync_message(SyncMessage::BlockComponentProcessed {
|
|
process_type: BlockProcessType::SingleCustodyColumn(lookup_id),
|
|
result: if missing_components {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
first_column.slot(),
|
|
first_column.block_root(),
|
|
))
|
|
} else {
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(
|
|
first_column.block_root(),
|
|
))
|
|
},
|
|
});
|
|
}
|
|
|
|
fn complete_data_columns_by_root_request(
|
|
&mut self,
|
|
(sync_request_id, _): DCByRootId,
|
|
data_columns: &[Arc<DataColumnSidecar<E>>],
|
|
) {
|
|
let peer_id = PeerId::random();
|
|
for data_column in data_columns {
|
|
// Send chunks
|
|
self.send_sync_message(SyncMessage::RpcDataColumn {
|
|
sync_request_id,
|
|
peer_id,
|
|
data_column: Some(data_column.clone()),
|
|
seen_timestamp: timestamp_now(),
|
|
});
|
|
}
|
|
// Send stream termination
|
|
self.send_sync_message(SyncMessage::RpcDataColumn {
|
|
sync_request_id,
|
|
peer_id,
|
|
data_column: None,
|
|
seen_timestamp: timestamp_now(),
|
|
});
|
|
}
|
|
|
|
/// Return RPCErrors for all active requests of peer
|
|
fn rpc_error_all_active_requests(&mut self, disconnected_peer_id: PeerId) {
|
|
self.drain_network_rx();
|
|
while let Ok(sync_request_id) = self.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::SendRequest {
|
|
peer_id,
|
|
app_request_id: AppRequestId::Sync(id),
|
|
..
|
|
} if *peer_id == disconnected_peer_id => Some(*id),
|
|
_ => None,
|
|
}) {
|
|
self.send_sync_message(SyncMessage::RpcError {
|
|
peer_id: disconnected_peer_id,
|
|
sync_request_id,
|
|
error: RPCError::Disconnected,
|
|
});
|
|
}
|
|
}
|
|
|
|
pub fn peer_disconnected(&mut self, peer_id: PeerId) {
|
|
self.send_sync_message(SyncMessage::Disconnect(peer_id));
|
|
}
|
|
|
|
fn drain_network_rx(&mut self) {
|
|
while let Ok(event) = self.network_rx.try_recv() {
|
|
self.network_rx_queue.push(event);
|
|
}
|
|
}
|
|
|
|
fn drain_processor_rx(&mut self) {
|
|
while let Ok(event) = self.beacon_processor_rx.try_recv() {
|
|
self.beacon_processor_rx_queue.push(event);
|
|
}
|
|
}
|
|
|
|
pub fn pop_received_network_event<T, F: Fn(&NetworkMessage<E>) -> Option<T>>(
|
|
&mut self,
|
|
predicate_transform: F,
|
|
) -> Result<T, String> {
|
|
self.drain_network_rx();
|
|
|
|
if let Some(index) = self
|
|
.network_rx_queue
|
|
.iter()
|
|
.position(|x| predicate_transform(x).is_some())
|
|
{
|
|
// Transform the item, knowing that it won't be None because we checked it in the position predicate.
|
|
let transformed = predicate_transform(&self.network_rx_queue[index]).unwrap();
|
|
self.network_rx_queue.remove(index);
|
|
Ok(transformed)
|
|
} else {
|
|
Err(format!("current network messages {:?}", self.network_rx_queue).to_string())
|
|
}
|
|
}
|
|
|
|
/// Similar to `pop_received_network_events` but finds matching events without removing them.
|
|
pub fn filter_received_network_events<T, F: Fn(&NetworkMessage<E>) -> Option<T>>(
|
|
&mut self,
|
|
predicate_transform: F,
|
|
) -> Vec<T> {
|
|
self.drain_network_rx();
|
|
|
|
self.network_rx_queue
|
|
.iter()
|
|
.filter_map(predicate_transform)
|
|
.collect()
|
|
}
|
|
|
|
pub fn pop_received_processor_event<T, F: Fn(&WorkEvent<E>) -> Option<T>>(
|
|
&mut self,
|
|
predicate_transform: F,
|
|
) -> Result<T, String> {
|
|
self.drain_processor_rx();
|
|
|
|
if let Some(index) = self
|
|
.beacon_processor_rx_queue
|
|
.iter()
|
|
.position(|x| predicate_transform(x).is_some())
|
|
{
|
|
// Transform the item, knowing that it won't be None because we checked it in the position predicate.
|
|
let transformed = predicate_transform(&self.beacon_processor_rx_queue[index]).unwrap();
|
|
self.beacon_processor_rx_queue.remove(index);
|
|
Ok(transformed)
|
|
} else {
|
|
Err(format!(
|
|
"current processor messages {:?}",
|
|
self.beacon_processor_rx_queue
|
|
)
|
|
.to_string())
|
|
}
|
|
}
|
|
|
|
pub fn expect_empty_processor(&mut self) {
|
|
self.drain_processor_rx();
|
|
if !self.beacon_processor_rx_queue.is_empty() {
|
|
panic!(
|
|
"Expected processor to be empty, but has events: {:?}",
|
|
self.beacon_processor_rx_queue
|
|
);
|
|
}
|
|
}
|
|
|
|
fn find_block_lookup_request(
|
|
&mut self,
|
|
for_block: Hash256,
|
|
) -> Result<SingleLookupReqId, String> {
|
|
self.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::SendRequest {
|
|
peer_id: _,
|
|
request: RequestType::BlocksByRoot(request),
|
|
app_request_id: AppRequestId::Sync(SyncRequestId::SingleBlock { id }),
|
|
} if request.block_roots().to_vec().contains(&for_block) => Some(*id),
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_block_lookup_request(&mut self, for_block: Hash256) -> SingleLookupReqId {
|
|
self.find_block_lookup_request(for_block)
|
|
.unwrap_or_else(|e| panic!("Expected block request for {for_block:?}: {e}"))
|
|
}
|
|
|
|
fn find_blob_lookup_request(
|
|
&mut self,
|
|
for_block: Hash256,
|
|
) -> Result<SingleLookupReqId, String> {
|
|
self.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::SendRequest {
|
|
peer_id: _,
|
|
request: RequestType::BlobsByRoot(request),
|
|
app_request_id: AppRequestId::Sync(SyncRequestId::SingleBlob { id }),
|
|
} if request
|
|
.blob_ids
|
|
.to_vec()
|
|
.iter()
|
|
.any(|r| r.block_root == for_block) =>
|
|
{
|
|
Some(*id)
|
|
}
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_blob_lookup_request(&mut self, for_block: Hash256) -> SingleLookupReqId {
|
|
self.find_blob_lookup_request(for_block)
|
|
.unwrap_or_else(|e| panic!("Expected blob request for {for_block:?}: {e}"))
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_block_parent_request(&mut self, for_block: Hash256) -> SingleLookupReqId {
|
|
self.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::SendRequest {
|
|
peer_id: _,
|
|
request: RequestType::BlocksByRoot(request),
|
|
app_request_id: AppRequestId::Sync(SyncRequestId::SingleBlock { id }),
|
|
} if request.block_roots().to_vec().contains(&for_block) => Some(*id),
|
|
_ => None,
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected block parent request for {for_block:?}: {e}"))
|
|
}
|
|
|
|
fn expect_no_requests_for(&mut self, block_root: Hash256) {
|
|
if let Ok(request) = self.find_block_lookup_request(block_root) {
|
|
panic!("Expected no block request for {block_root:?} found {request:?}");
|
|
}
|
|
if let Ok(request) = self.find_blob_lookup_request(block_root) {
|
|
panic!("Expected no blob request for {block_root:?} found {request:?}");
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_blob_parent_request(&mut self, for_block: Hash256) -> SingleLookupReqId {
|
|
self.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::SendRequest {
|
|
peer_id: _,
|
|
request: RequestType::BlobsByRoot(request),
|
|
app_request_id: AppRequestId::Sync(SyncRequestId::SingleBlob { id }),
|
|
} if request
|
|
.blob_ids
|
|
.to_vec()
|
|
.iter()
|
|
.all(|r| r.block_root == for_block) =>
|
|
{
|
|
Some(*id)
|
|
}
|
|
_ => None,
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected blob parent request for {for_block:?}: {e}"))
|
|
}
|
|
|
|
/// Retrieves an unknown number of requests for data columns of `block_root`. Because peer ENRs
|
|
/// are random, and peer selection is random, the total number of batched requests is unknown.
|
|
fn expect_data_columns_by_root_requests(
|
|
&mut self,
|
|
block_root: Hash256,
|
|
count: usize,
|
|
) -> DCByRootIds {
|
|
let mut requests: DCByRootIds = vec![];
|
|
loop {
|
|
let req = self
|
|
.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::SendRequest {
|
|
peer_id: _,
|
|
request: RequestType::DataColumnsByRoot(request),
|
|
app_request_id:
|
|
AppRequestId::Sync(id @ SyncRequestId::DataColumnsByRoot { .. }),
|
|
} => {
|
|
let matching = request
|
|
.data_column_ids
|
|
.iter()
|
|
.find(|id| id.block_root == block_root)?;
|
|
|
|
let indices = matching.columns.iter().copied().collect();
|
|
Some((*id, indices))
|
|
}
|
|
_ => None,
|
|
})
|
|
.unwrap_or_else(|e| {
|
|
panic!("Expected more DataColumnsByRoot requests for {block_root:?}: {e}")
|
|
});
|
|
requests.push(req);
|
|
|
|
// Should never infinite loop because sync does not send requests for 0 columns
|
|
if requests.iter().map(|r| r.1.len()).sum::<usize>() >= count {
|
|
return requests;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn expect_only_data_columns_by_root_requests(
|
|
&mut self,
|
|
for_block: Hash256,
|
|
count: usize,
|
|
) -> DCByRootIds {
|
|
let ids = self.expect_data_columns_by_root_requests(for_block, count);
|
|
self.expect_empty_network();
|
|
ids
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_block_process(&mut self, response_type: ResponseType) {
|
|
match response_type {
|
|
ResponseType::Block => self
|
|
.pop_received_processor_event(|ev| {
|
|
(ev.work_type() == beacon_processor::WorkType::RpcBlock).then_some(())
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected block work event: {e}")),
|
|
ResponseType::Blob => self
|
|
.pop_received_processor_event(|ev| {
|
|
(ev.work_type() == beacon_processor::WorkType::RpcBlobs).then_some(())
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected blobs work event: {e}")),
|
|
ResponseType::CustodyColumn => self
|
|
.pop_received_processor_event(|ev| {
|
|
(ev.work_type() == beacon_processor::WorkType::RpcCustodyColumn).then_some(())
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected column work event: {e}")),
|
|
}
|
|
}
|
|
|
|
fn expect_rpc_custody_column_work_event(&mut self) {
|
|
self.pop_received_processor_event(|ev| {
|
|
if ev.work_type() == beacon_processor::WorkType::RpcCustodyColumn {
|
|
Some(())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected RPC custody column work: {e}"))
|
|
}
|
|
|
|
fn expect_rpc_sample_verify_work_event(&mut self) {
|
|
self.pop_received_processor_event(|ev| {
|
|
if ev.work_type() == beacon_processor::WorkType::RpcVerifyDataColumn {
|
|
Some(())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected sample verify work: {e}"))
|
|
}
|
|
|
|
fn expect_sampling_result_work(&mut self) {
|
|
self.pop_received_processor_event(|ev| {
|
|
if ev.work_type() == beacon_processor::WorkType::SamplingResult {
|
|
Some(())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.unwrap_or_else(|e| panic!("Expected sampling result work: {e}"))
|
|
}
|
|
|
|
fn expect_no_work_event(&mut self) {
|
|
self.drain_processor_rx();
|
|
assert!(self.network_rx_queue.is_empty());
|
|
}
|
|
|
|
fn expect_no_penalty_for(&mut self, peer_id: PeerId) {
|
|
self.drain_network_rx();
|
|
let downscore_events = self
|
|
.network_rx_queue
|
|
.iter()
|
|
.filter_map(|ev| match ev {
|
|
NetworkMessage::ReportPeer {
|
|
peer_id: p_id, msg, ..
|
|
} if p_id == &peer_id => Some(msg),
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
if !downscore_events.is_empty() {
|
|
panic!("Some downscore events for {peer_id}: {downscore_events:?}");
|
|
}
|
|
}
|
|
|
|
pub fn expect_no_penalty_for_anyone(&mut self) {
|
|
let downscore_events = self.filter_received_network_events(|ev| match ev {
|
|
NetworkMessage::ReportPeer { peer_id, msg, .. } => Some((peer_id, msg)),
|
|
_ => None,
|
|
});
|
|
if !downscore_events.is_empty() {
|
|
panic!("Expected no downscoring events but found: {downscore_events:?}");
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_parent_chain_process(&mut self) {
|
|
match self.beacon_processor_rx.try_recv() {
|
|
Ok(work) => {
|
|
// Parent chain sends blocks one by one
|
|
assert_eq!(work.work_type(), beacon_processor::WorkType::RpcBlock);
|
|
}
|
|
other => panic!(
|
|
"Expected rpc_block from chain segment process, found {:?}",
|
|
other
|
|
),
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn expect_empty_network(&mut self) {
|
|
self.drain_network_rx();
|
|
if !self.network_rx_queue.is_empty() {
|
|
let n = self.network_rx_queue.len();
|
|
panic!(
|
|
"expected no network events but got {n} events, displaying first 2: {:#?}",
|
|
self.network_rx_queue[..n.min(2)].iter().collect::<Vec<_>>()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
fn expect_empty_beacon_processor(&mut self) {
|
|
match self.beacon_processor_rx.try_recv() {
|
|
Err(mpsc::error::TryRecvError::Empty) => {} // ok
|
|
Ok(event) => panic!("expected empty beacon processor: {:?}", event),
|
|
other => panic!("unexpected err {:?}", other),
|
|
}
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn expect_penalties(&mut self, expected_penalty_msg: &'static str) {
|
|
let all_penalties = self.filter_received_network_events(|ev| match ev {
|
|
NetworkMessage::ReportPeer { peer_id, msg, .. } => Some((*peer_id, *msg)),
|
|
_ => None,
|
|
});
|
|
if all_penalties
|
|
.iter()
|
|
.any(|(_, msg)| *msg != expected_penalty_msg)
|
|
{
|
|
panic!(
|
|
"Expected penalties only of {expected_penalty_msg}, but found {all_penalties:?}"
|
|
);
|
|
}
|
|
self.log(&format!(
|
|
"Found expected penalties {expected_penalty_msg}: {all_penalties:?}"
|
|
));
|
|
}
|
|
|
|
#[track_caller]
|
|
pub fn expect_penalty(&mut self, peer_id: PeerId, expect_penalty_msg: &'static str) {
|
|
let penalty_msg = self
|
|
.pop_received_network_event(|ev| match ev {
|
|
NetworkMessage::ReportPeer {
|
|
peer_id: p_id, msg, ..
|
|
} if p_id == &peer_id => Some(msg.to_owned()),
|
|
_ => None,
|
|
})
|
|
.unwrap_or_else(|_| {
|
|
panic!(
|
|
"Expected '{expect_penalty_msg}' penalty for peer {peer_id}: {:#?}",
|
|
self.network_rx_queue
|
|
)
|
|
});
|
|
assert_eq!(
|
|
penalty_msg, expect_penalty_msg,
|
|
"Unexpected penalty msg for {peer_id}"
|
|
);
|
|
self.log(&format!("Found expected penalty {penalty_msg}"));
|
|
}
|
|
|
|
pub fn expect_single_penalty(&mut self, peer_id: PeerId, expect_penalty_msg: &'static str) {
|
|
self.expect_penalty(peer_id, expect_penalty_msg);
|
|
self.expect_no_penalty_for(peer_id);
|
|
}
|
|
|
|
pub fn block_with_parent_and_blobs(
|
|
&mut self,
|
|
parent_root: Hash256,
|
|
num_blobs: NumBlobs,
|
|
) -> (SignedBeaconBlock<E>, Vec<BlobSidecar<E>>) {
|
|
let (mut block, mut blobs) = self.rand_block_and_blobs(num_blobs);
|
|
*block.message_mut().parent_root_mut() = parent_root;
|
|
blobs.iter_mut().for_each(|blob| {
|
|
blob.signed_block_header = block.signed_block_header();
|
|
});
|
|
(block, blobs)
|
|
}
|
|
|
|
pub fn rand_blockchain(&mut self, depth: usize) -> Vec<Arc<SignedBeaconBlock<E>>> {
|
|
let mut blocks = Vec::<Arc<SignedBeaconBlock<E>>>::with_capacity(depth);
|
|
for slot in 0..depth {
|
|
let parent = blocks
|
|
.last()
|
|
.map(|b| b.canonical_root())
|
|
.unwrap_or_else(Hash256::random);
|
|
let mut block = self.rand_block();
|
|
*block.message_mut().parent_root_mut() = parent;
|
|
*block.message_mut().slot_mut() = slot.into();
|
|
blocks.push(block.into());
|
|
}
|
|
self.log(&format!(
|
|
"Blockchain dump {:#?}",
|
|
blocks
|
|
.iter()
|
|
.map(|b| format!(
|
|
"block {} {} parent {}",
|
|
b.slot(),
|
|
b.canonical_root(),
|
|
b.parent_root()
|
|
))
|
|
.collect::<Vec<_>>()
|
|
));
|
|
blocks
|
|
}
|
|
|
|
fn insert_block_to_da_checker(&mut self, block: Arc<SignedBeaconBlock<E>>) {
|
|
let state = BeaconState::Base(BeaconStateBase::random_for_test(&mut self.rng));
|
|
let parent_block = self.rand_block();
|
|
let import_data = BlockImportData::<E>::__new_for_test(
|
|
block.canonical_root(),
|
|
state,
|
|
parent_block.into(),
|
|
);
|
|
let payload_verification_outcome = PayloadVerificationOutcome {
|
|
payload_verification_status: PayloadVerificationStatus::Verified,
|
|
is_valid_merge_transition_block: false,
|
|
};
|
|
let executed_block = AvailabilityPendingExecutedBlock::new(
|
|
block,
|
|
import_data,
|
|
payload_verification_outcome,
|
|
self.network_globals.custody_columns_count() as usize,
|
|
);
|
|
match self
|
|
.harness
|
|
.chain
|
|
.data_availability_checker
|
|
.put_pending_executed_block(executed_block)
|
|
.unwrap()
|
|
{
|
|
Availability::Available(_) => panic!("block removed from da_checker, available"),
|
|
Availability::MissingComponents(block_root) => {
|
|
self.log(&format!("inserted block to da_checker {block_root:?}"))
|
|
}
|
|
};
|
|
}
|
|
|
|
fn insert_blob_to_da_checker(&mut self, blob: BlobSidecar<E>) {
|
|
match self
|
|
.harness
|
|
.chain
|
|
.data_availability_checker
|
|
.put_gossip_blob(GossipVerifiedBlob::__assumed_valid(blob.into()))
|
|
.unwrap()
|
|
{
|
|
Availability::Available(_) => panic!("blob removed from da_checker, available"),
|
|
Availability::MissingComponents(block_root) => {
|
|
self.log(&format!("inserted blob to da_checker {block_root:?}"))
|
|
}
|
|
};
|
|
}
|
|
|
|
fn insert_block_to_processing_cache(&mut self, block: Arc<SignedBeaconBlock<E>>) {
|
|
self.harness
|
|
.chain
|
|
.reqresp_pre_import_cache
|
|
.write()
|
|
.insert(block.canonical_root(), block);
|
|
}
|
|
|
|
fn simulate_block_gossip_processing_becomes_invalid(&mut self, block_root: Hash256) {
|
|
self.harness
|
|
.chain
|
|
.reqresp_pre_import_cache
|
|
.write()
|
|
.remove(&block_root);
|
|
|
|
self.send_sync_message(SyncMessage::GossipBlockProcessResult {
|
|
block_root,
|
|
imported: false,
|
|
});
|
|
}
|
|
|
|
fn simulate_block_gossip_processing_becomes_valid_missing_components(
|
|
&mut self,
|
|
block: Arc<SignedBeaconBlock<E>>,
|
|
) {
|
|
let block_root = block.canonical_root();
|
|
self.harness
|
|
.chain
|
|
.reqresp_pre_import_cache
|
|
.write()
|
|
.remove(&block_root);
|
|
|
|
self.insert_block_to_da_checker(block);
|
|
|
|
self.send_sync_message(SyncMessage::GossipBlockProcessResult {
|
|
block_root,
|
|
imported: false,
|
|
});
|
|
}
|
|
|
|
fn assert_sampling_request_ongoing(&self, block_root: Hash256, indices: &[ColumnIndex]) {
|
|
for index in indices {
|
|
let status = self
|
|
.sync_manager
|
|
.get_sampling_request_status(block_root, index)
|
|
.unwrap_or_else(|| panic!("No request state for {index}"));
|
|
if !matches!(status, crate::sync::peer_sampling::Status::Sampling { .. }) {
|
|
panic!("expected {block_root} {index} request to be on going: {status:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn assert_sampling_request_nopeers(&self, block_root: Hash256, indices: &[ColumnIndex]) {
|
|
for index in indices {
|
|
let status = self
|
|
.sync_manager
|
|
.get_sampling_request_status(block_root, index)
|
|
.unwrap_or_else(|| panic!("No request state for {index}"));
|
|
if !matches!(status, crate::sync::peer_sampling::Status::NoPeers) {
|
|
panic!("expected {block_root} {index} request to be no peers: {status:?}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn log_sampling_requests(&self, block_root: Hash256, indices: &[ColumnIndex]) {
|
|
let statuses = indices
|
|
.iter()
|
|
.map(|index| {
|
|
let status = self
|
|
.sync_manager
|
|
.get_sampling_request_status(block_root, index)
|
|
.unwrap_or_else(|| panic!("No request state for {index}"));
|
|
(index, status)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
self.log(&format!(
|
|
"Sampling request status for {block_root}: {statuses:?}"
|
|
));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stable_rng() {
|
|
let spec = types::MainnetEthSpec::default_spec();
|
|
let mut rng = XorShiftRng::from_seed([42; 16]);
|
|
let (block, _) =
|
|
generate_rand_block_and_blobs::<E>(ForkName::Base, NumBlobs::None, &mut rng, &spec);
|
|
assert_eq!(
|
|
block.canonical_root(),
|
|
Hash256::from_slice(
|
|
&hex::decode("adfd2e9e7a7976e8ccaed6eaf0257ed36a5b476732fee63ff44966602fd099ec")
|
|
.unwrap()
|
|
),
|
|
"rng produces a consistent value"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_block_lookup_happy_path() {
|
|
let mut rig = TestRig::test_setup();
|
|
let block = rig.rand_block();
|
|
let peer_id = rig.new_connected_peer();
|
|
let block_root = block.canonical_root();
|
|
// Trigger the request
|
|
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
let id = rig.expect_block_lookup_request(block_root);
|
|
|
|
// The peer provides the correct block, should not be penalized. Now the block should be sent
|
|
// for processing.
|
|
rig.single_lookup_block_response(id, peer_id, Some(block.into()));
|
|
rig.expect_empty_network();
|
|
rig.expect_block_process(ResponseType::Block);
|
|
|
|
// The request should still be active.
|
|
assert_eq!(rig.active_single_lookups_count(), 1);
|
|
|
|
// Send the stream termination. Peer should have not been penalized, and the request removed
|
|
// after processing.
|
|
rig.single_lookup_block_response(id, peer_id, None);
|
|
rig.single_block_component_processed_imported(block_root);
|
|
rig.expect_empty_network();
|
|
rig.expect_no_active_lookups();
|
|
}
|
|
|
|
// Tests that if a peer does not respond with a block, we downscore and retry the block only
|
|
#[test]
|
|
fn test_single_block_lookup_empty_response() {
|
|
let mut r = TestRig::test_setup();
|
|
|
|
let block = r.rand_block();
|
|
let block_root = block.canonical_root();
|
|
let peer_id = r.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
r.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
let id = r.expect_block_lookup_request(block_root);
|
|
|
|
// The peer does not have the block. It should be penalized.
|
|
r.single_lookup_block_response(id, peer_id, None);
|
|
r.expect_penalty(peer_id, "NotEnoughResponsesReturned");
|
|
// it should be retried
|
|
let id = r.expect_block_lookup_request(block_root);
|
|
// Send the right block this time.
|
|
r.single_lookup_block_response(id, peer_id, Some(block.into()));
|
|
r.expect_block_process(ResponseType::Block);
|
|
r.single_block_component_processed_imported(block_root);
|
|
r.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_block_lookup_wrong_response() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let block_hash = Hash256::random();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_block_from_attestation(block_hash, peer_id);
|
|
let id = rig.expect_block_lookup_request(block_hash);
|
|
|
|
// Peer sends something else. It should be penalized.
|
|
let bad_block = rig.rand_block();
|
|
rig.single_lookup_block_response(id, peer_id, Some(bad_block.into()));
|
|
rig.expect_penalty(peer_id, "UnrequestedBlockRoot");
|
|
rig.expect_block_lookup_request(block_hash); // should be retried
|
|
|
|
// Send the stream termination. This should not produce an additional penalty.
|
|
rig.single_lookup_block_response(id, peer_id, None);
|
|
rig.expect_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_block_lookup_failure() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let block_hash = Hash256::random();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_block_from_attestation(block_hash, peer_id);
|
|
let id = rig.expect_block_lookup_request(block_hash);
|
|
|
|
// The request fails. RPC failures are handled elsewhere so we should not penalize the peer.
|
|
rig.single_lookup_failed(id, peer_id, RPCError::UnsupportedProtocol);
|
|
rig.expect_block_lookup_request(block_hash);
|
|
rig.expect_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_block_lookup_peer_disconnected_then_rpc_error() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let block_hash = Hash256::random();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request.
|
|
rig.trigger_unknown_block_from_attestation(block_hash, peer_id);
|
|
let id = rig.expect_block_lookup_request(block_hash);
|
|
|
|
// The peer disconnect event reaches sync before the rpc error.
|
|
rig.peer_disconnected(peer_id);
|
|
// The lookup is not removed as it can still potentially make progress.
|
|
rig.assert_single_lookups_count(1);
|
|
// The request fails.
|
|
rig.single_lookup_failed(id, peer_id, RPCError::Disconnected);
|
|
rig.expect_block_lookup_request(block_hash);
|
|
// The request should be removed from the network context on disconnection.
|
|
rig.expect_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_block_lookup_becomes_parent_request() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let block = Arc::new(rig.rand_block());
|
|
let block_root = block.canonical_root();
|
|
let parent_root = block.parent_root();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_block_from_attestation(block.canonical_root(), peer_id);
|
|
let id = rig.expect_block_parent_request(block_root);
|
|
|
|
// The peer provides the correct block, should not be penalized. Now the block should be sent
|
|
// for processing.
|
|
rig.single_lookup_block_response(id, peer_id, Some(block.clone()));
|
|
rig.expect_empty_network();
|
|
rig.expect_block_process(ResponseType::Block);
|
|
|
|
// The request should still be active.
|
|
assert_eq!(rig.active_single_lookups_count(), 1);
|
|
|
|
// Send the stream termination. Peer should have not been penalized, and the request moved to a
|
|
// parent request after processing.
|
|
rig.single_block_component_processed(
|
|
id.lookup_id,
|
|
BlockProcessingResult::Err(BlockError::ParentUnknown {
|
|
parent_root: block.parent_root(),
|
|
}),
|
|
);
|
|
assert_eq!(rig.active_single_lookups_count(), 2); // 2 = current + parent
|
|
rig.expect_block_parent_request(parent_root);
|
|
rig.expect_empty_network();
|
|
assert_eq!(rig.active_parent_lookups_count(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_happy_path() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let (parent, block, parent_root, block_root) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
|
|
// Peer sends the right block, it should be sent for processing. Peer should not be penalized.
|
|
rig.parent_lookup_block_response(id, peer_id, Some(parent.into()));
|
|
// No request of blobs because the block has not data
|
|
rig.expect_empty_network();
|
|
rig.expect_block_process(ResponseType::Block);
|
|
rig.expect_empty_network();
|
|
|
|
// Add peer to child lookup to prevent it being dropped
|
|
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Processing succeeds, now the rest of the chain should be sent for processing.
|
|
rig.parent_block_processed(
|
|
block_root,
|
|
BlockError::DuplicateFullyImported(block_root).into(),
|
|
);
|
|
rig.expect_parent_chain_process();
|
|
rig.parent_chain_processed_success(block_root, &[]);
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_wrong_response() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let (parent, block, parent_root, block_root) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
let id1 = rig.expect_block_parent_request(parent_root);
|
|
|
|
// Peer sends the wrong block, peer should be penalized and the block re-requested.
|
|
let bad_block = rig.rand_block();
|
|
rig.parent_lookup_block_response(id1, peer_id, Some(bad_block.into()));
|
|
rig.expect_penalty(peer_id, "UnrequestedBlockRoot");
|
|
let id2 = rig.expect_block_parent_request(parent_root);
|
|
|
|
// Send the stream termination for the first request. This should not produce extra penalties.
|
|
rig.parent_lookup_block_response(id1, peer_id, None);
|
|
rig.expect_empty_network();
|
|
|
|
// Send the right block this time.
|
|
rig.parent_lookup_block_response(id2, peer_id, Some(parent.into()));
|
|
rig.expect_block_process(ResponseType::Block);
|
|
|
|
// Add peer to child lookup to prevent it being dropped
|
|
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Processing succeeds, now the rest of the chain should be sent for processing.
|
|
rig.parent_block_processed_imported(block_root);
|
|
rig.expect_parent_chain_process();
|
|
rig.parent_chain_processed_success(block_root, &[]);
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_rpc_failure() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let (parent, block, parent_root, block_root) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
|
|
// The request fails. It should be tried again.
|
|
rig.parent_lookup_failed_unavailable(id, peer_id);
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
|
|
// Send the right block this time.
|
|
rig.parent_lookup_block_response(id, peer_id, Some(parent.into()));
|
|
rig.expect_block_process(ResponseType::Block);
|
|
|
|
// Add peer to child lookup to prevent it being dropped
|
|
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Processing succeeds, now the rest of the chain should be sent for processing.
|
|
rig.parent_block_processed_imported(block_root);
|
|
rig.expect_parent_chain_process();
|
|
rig.parent_chain_processed_success(block_root, &[]);
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_too_many_attempts() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let block = rig.rand_block();
|
|
let parent_root = block.parent_root();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
for i in 1..=PARENT_FAIL_TOLERANCE {
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
// Blobs are only requested in the first iteration as this test only retries blocks
|
|
|
|
if i % 2 == 0 {
|
|
// make sure every error is accounted for
|
|
// The request fails. It should be tried again.
|
|
rig.parent_lookup_failed_unavailable(id, peer_id);
|
|
} else {
|
|
// Send a bad block this time. It should be tried again.
|
|
let bad_block = rig.rand_block();
|
|
rig.parent_lookup_block_response(id, peer_id, Some(bad_block.into()));
|
|
// Send the stream termination
|
|
|
|
// Note, previously we would send the same lookup id with a stream terminator,
|
|
// we'd ignore it because we'd intrepret it as an unrequested response, since
|
|
// we already got one response for the block. I'm not sure what the intent is
|
|
// for having this stream terminator line in this test at all. Receiving an invalid
|
|
// block and a stream terminator with the same Id now results in two failed attempts,
|
|
// I'm unsure if this is how it should behave?
|
|
//
|
|
rig.parent_lookup_block_response(id, peer_id, None);
|
|
rig.expect_penalty(peer_id, "UnrequestedBlockRoot");
|
|
}
|
|
}
|
|
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_too_many_download_attempts_no_blacklist() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let (parent, block, parent_root, block_root) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
for i in 1..=PARENT_FAIL_TOLERANCE {
|
|
rig.assert_not_failed_chain(block_root);
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
if i % 2 != 0 {
|
|
// The request fails. It should be tried again.
|
|
rig.parent_lookup_failed_unavailable(id, peer_id);
|
|
} else {
|
|
// Send a bad block this time. It should be tried again.
|
|
let bad_block = rig.rand_block();
|
|
rig.parent_lookup_block_response(id, peer_id, Some(bad_block.into()));
|
|
rig.expect_penalty(peer_id, "UnrequestedBlockRoot");
|
|
}
|
|
}
|
|
|
|
rig.assert_not_failed_chain(block_root);
|
|
rig.assert_not_failed_chain(parent.canonical_root());
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_too_many_processing_attempts_must_blacklist() {
|
|
const PROCESSING_FAILURES: u8 = PARENT_FAIL_TOLERANCE / 2 + 1;
|
|
let mut rig = TestRig::test_setup();
|
|
let (parent, block, parent_root, block_root) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
|
|
rig.log("Fail downloading the block");
|
|
for _ in 0..(PARENT_FAIL_TOLERANCE - PROCESSING_FAILURES) {
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
// The request fails. It should be tried again.
|
|
rig.parent_lookup_failed_unavailable(id, peer_id);
|
|
}
|
|
|
|
rig.log("Now fail processing a block in the parent request");
|
|
for _ in 0..PROCESSING_FAILURES {
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
// Blobs are only requested in the previous first iteration as this test only retries blocks
|
|
rig.assert_not_failed_chain(block_root);
|
|
// send the right parent but fail processing
|
|
rig.parent_lookup_block_response(id, peer_id, Some(parent.clone().into()));
|
|
rig.parent_block_processed(block_root, BlockError::BlockSlotLimitReached.into());
|
|
rig.parent_lookup_block_response(id, peer_id, None);
|
|
rig.expect_penalty(peer_id, "lookup_block_processing_failure");
|
|
}
|
|
|
|
rig.assert_not_failed_chain(block_root);
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_too_deep_grow_ancestor() {
|
|
let mut rig = TestRig::test_setup();
|
|
let mut blocks = rig.rand_blockchain(PARENT_DEPTH_TOLERANCE);
|
|
|
|
let peer_id = rig.new_connected_peer();
|
|
let trigger_block = blocks.pop().unwrap();
|
|
let chain_hash = trigger_block.canonical_root();
|
|
rig.trigger_unknown_parent_block(peer_id, trigger_block);
|
|
|
|
for block in blocks.into_iter().rev() {
|
|
let id = rig.expect_block_parent_request(block.canonical_root());
|
|
// the block
|
|
rig.parent_lookup_block_response(id, peer_id, Some(block.clone()));
|
|
// the stream termination
|
|
rig.parent_lookup_block_response(id, peer_id, None);
|
|
// the processing request
|
|
rig.expect_block_process(ResponseType::Block);
|
|
// the processing result
|
|
rig.parent_block_processed(
|
|
chain_hash,
|
|
BlockProcessingResult::Err(BlockError::ParentUnknown {
|
|
parent_root: block.parent_root(),
|
|
}),
|
|
)
|
|
}
|
|
|
|
// Should create a new syncing chain
|
|
rig.drain_sync_rx();
|
|
assert_eq!(
|
|
rig.active_range_sync_chain(),
|
|
(
|
|
RangeSyncType::Head,
|
|
Slot::new(0),
|
|
Slot::new(PARENT_DEPTH_TOLERANCE as u64 - 1)
|
|
)
|
|
);
|
|
// Should not penalize peer, but network is not clear because of the blocks_by_range requests
|
|
rig.expect_no_penalty_for(peer_id);
|
|
rig.assert_failed_chain(chain_hash);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_too_deep_grow_tip() {
|
|
let mut rig = TestRig::test_setup();
|
|
let blocks = rig.rand_blockchain(PARENT_DEPTH_TOLERANCE - 1);
|
|
let peer_id = rig.new_connected_peer();
|
|
let tip = blocks.last().unwrap().clone();
|
|
|
|
for block in blocks.into_iter() {
|
|
let block_root = block.canonical_root();
|
|
rig.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
let id = rig.expect_block_parent_request(block_root);
|
|
rig.single_lookup_block_response(id, peer_id, Some(block.clone()));
|
|
rig.single_lookup_block_response(id, peer_id, None);
|
|
rig.expect_block_process(ResponseType::Block);
|
|
rig.single_block_component_processed(
|
|
id.lookup_id,
|
|
BlockError::ParentUnknown {
|
|
parent_root: block.parent_root(),
|
|
}
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
// Should create a new syncing chain
|
|
rig.drain_sync_rx();
|
|
assert_eq!(
|
|
rig.active_range_sync_chain(),
|
|
(
|
|
RangeSyncType::Head,
|
|
Slot::new(0),
|
|
Slot::new(PARENT_DEPTH_TOLERANCE as u64 - 2)
|
|
)
|
|
);
|
|
// Should not penalize peer, but network is not clear because of the blocks_by_range requests
|
|
rig.expect_no_penalty_for(peer_id);
|
|
rig.assert_failed_chain(tip.canonical_root());
|
|
}
|
|
|
|
#[test]
|
|
fn test_lookup_peer_disconnected_no_peers_left_while_request() {
|
|
let mut rig = TestRig::test_setup();
|
|
let peer_id = rig.new_connected_peer();
|
|
let trigger_block = rig.rand_block();
|
|
rig.trigger_unknown_parent_block(peer_id, trigger_block.into());
|
|
rig.peer_disconnected(peer_id);
|
|
rig.rpc_error_all_active_requests(peer_id);
|
|
// Erroring all rpc requests and disconnecting the peer shouldn't remove the requests
|
|
// from the lookups map as they can still progress.
|
|
rig.assert_single_lookups_count(2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lookup_disconnection_peer_left() {
|
|
let mut rig = TestRig::test_setup();
|
|
let peer_ids = (0..2).map(|_| rig.new_connected_peer()).collect::<Vec<_>>();
|
|
let disconnecting_peer = *peer_ids.first().unwrap();
|
|
let block_root = Hash256::random();
|
|
// lookup should have two peers associated with the same block
|
|
for peer_id in peer_ids.iter() {
|
|
rig.trigger_unknown_block_from_attestation(block_root, *peer_id);
|
|
}
|
|
// Disconnect the first peer only, which is the one handling the request
|
|
rig.peer_disconnected(disconnecting_peer);
|
|
rig.rpc_error_all_active_requests(disconnecting_peer);
|
|
rig.assert_single_lookups_count(1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lookup_add_peers_to_parent() {
|
|
let mut r = TestRig::test_setup();
|
|
let peer_id_1 = r.new_connected_peer();
|
|
let peer_id_2 = r.new_connected_peer();
|
|
let blocks = r.rand_blockchain(5);
|
|
let last_block_root = blocks.last().unwrap().canonical_root();
|
|
// Create a chain of lookups
|
|
for block in &blocks {
|
|
r.trigger_unknown_parent_block(peer_id_1, block.clone());
|
|
}
|
|
r.trigger_unknown_block_from_attestation(last_block_root, peer_id_2);
|
|
for block in blocks.iter().take(blocks.len() - 1) {
|
|
// Parent has the original unknown parent event peer + new peer
|
|
r.assert_lookup_peers(block.canonical_root(), vec![peer_id_1, peer_id_2]);
|
|
}
|
|
// Child lookup only has the unknown attestation peer
|
|
r.assert_lookup_peers(last_block_root, vec![peer_id_2]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_skip_creating_failed_parent_lookup() {
|
|
let mut rig = TestRig::test_setup();
|
|
let (_, block, parent_root, _) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
rig.insert_failed_chain(parent_root);
|
|
rig.trigger_unknown_parent_block(peer_id, block.into());
|
|
// Expect single penalty for peer, despite dropping two lookups
|
|
rig.expect_single_penalty(peer_id, "failed_chain");
|
|
// Both current and parent lookup should be rejected
|
|
rig.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn test_single_block_lookup_ignored_response() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let block = rig.rand_block();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_block_from_attestation(block.canonical_root(), peer_id);
|
|
let id = rig.expect_block_lookup_request(block.canonical_root());
|
|
|
|
// The peer provides the correct block, should not be penalized. Now the block should be sent
|
|
// for processing.
|
|
rig.single_lookup_block_response(id, peer_id, Some(block.into()));
|
|
rig.expect_empty_network();
|
|
rig.expect_block_process(ResponseType::Block);
|
|
|
|
// The request should still be active.
|
|
assert_eq!(rig.active_single_lookups_count(), 1);
|
|
|
|
// Send the stream termination. Peer should have not been penalized, and the request removed
|
|
// after processing.
|
|
rig.single_lookup_block_response(id, peer_id, None);
|
|
// Send an Ignored response, the request should be dropped
|
|
rig.single_block_component_processed(id.lookup_id, BlockProcessingResult::Ignored);
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn test_parent_lookup_ignored_response() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
let (parent, block, parent_root, block_root) = rig.rand_block_and_parent();
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
rig.trigger_unknown_parent_block(peer_id, block.clone().into());
|
|
let id = rig.expect_block_parent_request(parent_root);
|
|
// Note: single block lookup for current `block` does not trigger any request because it does
|
|
// not have blobs, and the block is already cached
|
|
|
|
// Peer sends the right block, it should be sent for processing. Peer should not be penalized.
|
|
rig.parent_lookup_block_response(id, peer_id, Some(parent.into()));
|
|
rig.expect_block_process(ResponseType::Block);
|
|
rig.expect_empty_network();
|
|
|
|
// Return an Ignored result. The request should be dropped
|
|
rig.parent_block_processed(block_root, BlockProcessingResult::Ignored);
|
|
rig.expect_empty_network();
|
|
rig.expect_no_active_lookups();
|
|
}
|
|
|
|
/// This is a regression test.
|
|
#[test]
|
|
fn test_same_chain_race_condition() {
|
|
let mut rig = TestRig::test_setup();
|
|
|
|
// if we use one or two blocks it will match on the hash or the parent hash, so make a longer
|
|
// chain.
|
|
let depth = 4;
|
|
let mut blocks = rig.rand_blockchain(depth);
|
|
let peer_id = rig.new_connected_peer();
|
|
let trigger_block = blocks.pop().unwrap();
|
|
let chain_hash = trigger_block.canonical_root();
|
|
rig.trigger_unknown_parent_block(peer_id, trigger_block.clone());
|
|
|
|
for (i, block) in blocks.clone().into_iter().rev().enumerate() {
|
|
let id = rig.expect_block_parent_request(block.canonical_root());
|
|
// the block
|
|
rig.parent_lookup_block_response(id, peer_id, Some(block.clone()));
|
|
// the stream termination
|
|
rig.parent_lookup_block_response(id, peer_id, None);
|
|
// the processing request
|
|
rig.expect_block_process(ResponseType::Block);
|
|
// the processing result
|
|
if i + 2 == depth {
|
|
rig.log(&format!("Block {i} was removed and is already known"));
|
|
rig.parent_block_processed(
|
|
chain_hash,
|
|
BlockError::DuplicateFullyImported(block.canonical_root()).into(),
|
|
)
|
|
} else {
|
|
rig.log(&format!("Block {i} ParentUnknown"));
|
|
rig.parent_block_processed(
|
|
chain_hash,
|
|
BlockProcessingResult::Err(BlockError::ParentUnknown {
|
|
parent_root: block.parent_root(),
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Try to get this block again while the chain is being processed. We should not request it again.
|
|
let peer_id = rig.new_connected_peer();
|
|
rig.trigger_unknown_parent_block(peer_id, trigger_block.clone());
|
|
rig.expect_empty_network();
|
|
|
|
// Add a peer to the tip child lookup which has zero peers
|
|
rig.trigger_unknown_block_from_attestation(trigger_block.canonical_root(), peer_id);
|
|
|
|
rig.log("Processing succeeds, now the rest of the chain should be sent for processing.");
|
|
for block in blocks.iter().skip(1).chain(&[trigger_block]) {
|
|
rig.expect_parent_chain_process();
|
|
rig.single_block_component_processed_imported(block.canonical_root());
|
|
}
|
|
rig.expect_no_active_lookups_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn block_in_da_checker_skips_download() {
|
|
let Some(mut r) = TestRig::test_setup_after_deneb_before_fulu() else {
|
|
return;
|
|
};
|
|
let (block, blobs) = r.rand_block_and_blobs(NumBlobs::Number(1));
|
|
let block_root = block.canonical_root();
|
|
let peer_id = r.new_connected_peer();
|
|
r.insert_block_to_da_checker(block.into());
|
|
r.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Should not trigger block request
|
|
let id = r.expect_blob_lookup_request(block_root);
|
|
r.expect_empty_network();
|
|
// Resolve blob and expect lookup completed
|
|
r.complete_single_lookup_blob_lookup_valid(id, peer_id, blobs, true);
|
|
r.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn block_in_processing_cache_becomes_invalid() {
|
|
let Some(mut r) = TestRig::test_setup_after_deneb_before_fulu() else {
|
|
return;
|
|
};
|
|
let (block, blobs) = r.rand_block_and_blobs(NumBlobs::Number(1));
|
|
let block_root = block.canonical_root();
|
|
let peer_id = r.new_connected_peer();
|
|
r.insert_block_to_processing_cache(block.clone().into());
|
|
r.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Should trigger blob request
|
|
let id = r.expect_blob_lookup_request(block_root);
|
|
// Should not trigger block request
|
|
r.expect_empty_network();
|
|
// Simulate invalid block, removing it from processing cache
|
|
r.simulate_block_gossip_processing_becomes_invalid(block_root);
|
|
// Should download block, then issue blobs request
|
|
r.complete_lookup_block_download(block);
|
|
// Should not trigger block or blob request
|
|
r.expect_empty_network();
|
|
r.complete_lookup_block_import_valid(block_root, false);
|
|
// Resolve blob and expect lookup completed
|
|
r.complete_single_lookup_blob_lookup_valid(id, peer_id, blobs, true);
|
|
r.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn block_in_processing_cache_becomes_valid_imported() {
|
|
let Some(mut r) = TestRig::test_setup_after_deneb_before_fulu() else {
|
|
return;
|
|
};
|
|
let (block, blobs) = r.rand_block_and_blobs(NumBlobs::Number(1));
|
|
let block_root = block.canonical_root();
|
|
let peer_id = r.new_connected_peer();
|
|
r.insert_block_to_processing_cache(block.clone().into());
|
|
r.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Should trigger blob request
|
|
let id = r.expect_blob_lookup_request(block_root);
|
|
// Should not trigger block request
|
|
r.expect_empty_network();
|
|
// Resolve the block from processing step
|
|
r.simulate_block_gossip_processing_becomes_valid_missing_components(block.into());
|
|
// Should not trigger block or blob request
|
|
r.expect_empty_network();
|
|
// Resolve blob and expect lookup completed
|
|
r.complete_single_lookup_blob_lookup_valid(id, peer_id, blobs, true);
|
|
r.expect_no_active_lookups();
|
|
}
|
|
|
|
// IGNORE: wait for change that delays blob fetching to knowing the block
|
|
#[ignore]
|
|
#[test]
|
|
fn blobs_in_da_checker_skip_download() {
|
|
let Some(mut r) = TestRig::test_setup_after_deneb_before_fulu() else {
|
|
return;
|
|
};
|
|
let (block, blobs) = r.rand_block_and_blobs(NumBlobs::Number(1));
|
|
let block_root = block.canonical_root();
|
|
let peer_id = r.new_connected_peer();
|
|
for blob in blobs {
|
|
r.insert_blob_to_da_checker(blob);
|
|
}
|
|
r.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Should download and process the block
|
|
r.complete_single_lookup_block_valid(block, true);
|
|
// Should not trigger blob request
|
|
r.expect_empty_network();
|
|
r.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn sampling_happy_path() {
|
|
let Some(mut r) = TestRig::test_setup_after_fulu() else {
|
|
return;
|
|
};
|
|
r.new_connected_peers_for_peerdas();
|
|
let (block, data_columns) = r.rand_block_and_data_columns();
|
|
let block_root = block.canonical_root();
|
|
r.trigger_sample_block(block_root, block.slot());
|
|
// Retrieve all outgoing sample requests for random column indexes
|
|
let sampling_ids =
|
|
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
|
|
// Resolve all of them one by one
|
|
r.complete_valid_sampling_column_requests(sampling_ids, data_columns);
|
|
r.expect_clean_finished_sampling();
|
|
}
|
|
|
|
#[test]
|
|
fn sampling_with_retries() {
|
|
let Some(mut r) = TestRig::test_setup_after_fulu() else {
|
|
return;
|
|
};
|
|
r.new_connected_peers_for_peerdas();
|
|
// Add another supernode to ensure that the node can retry.
|
|
r.new_connected_supernode_peer();
|
|
let (block, data_columns) = r.rand_block_and_data_columns();
|
|
let block_root = block.canonical_root();
|
|
r.trigger_sample_block(block_root, block.slot());
|
|
// Retrieve all outgoing sample requests for random column indexes, and return empty responses
|
|
let sampling_ids =
|
|
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
|
|
r.return_empty_sampling_requests(sampling_ids);
|
|
// Expect retries for all of them, and resolve them
|
|
let sampling_ids =
|
|
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
|
|
r.complete_valid_sampling_column_requests(sampling_ids, data_columns);
|
|
r.expect_clean_finished_sampling();
|
|
}
|
|
|
|
#[test]
|
|
fn sampling_avoid_retrying_same_peer() {
|
|
let Some(mut r) = TestRig::test_setup_after_fulu() else {
|
|
return;
|
|
};
|
|
let peer_id_1 = r.new_connected_supernode_peer();
|
|
let peer_id_2 = r.new_connected_supernode_peer();
|
|
let block_root = Hash256::random();
|
|
r.trigger_sample_block(block_root, Slot::new(0));
|
|
// Retrieve all outgoing sample requests for random column indexes, and return empty responses
|
|
let sampling_ids =
|
|
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
|
|
r.sampling_requests_failed(sampling_ids, peer_id_1, RPCError::Disconnected);
|
|
// Should retry the other peer
|
|
let sampling_ids =
|
|
r.expect_only_data_columns_by_root_requests(block_root, SAMPLING_REQUIRED_SUCCESSES);
|
|
r.sampling_requests_failed(sampling_ids, peer_id_2, RPCError::Disconnected);
|
|
// Expect no more retries
|
|
r.expect_empty_network();
|
|
}
|
|
|
|
#[test]
|
|
fn sampling_batch_requests() {
|
|
let Some(mut r) = TestRig::test_setup_after_fulu() else {
|
|
return;
|
|
};
|
|
let _supernode = r.new_connected_supernode_peer();
|
|
let (block, data_columns) = r.rand_block_and_data_columns();
|
|
let block_root = block.canonical_root();
|
|
r.trigger_sample_block(block_root, block.slot());
|
|
|
|
// Retrieve the sample request, which should be batched.
|
|
let (sync_request_id, column_indexes) = r
|
|
.expect_only_data_columns_by_root_requests(block_root, 1)
|
|
.pop()
|
|
.unwrap();
|
|
assert_eq!(column_indexes.len(), SAMPLING_REQUIRED_SUCCESSES);
|
|
r.assert_sampling_request_ongoing(block_root, &column_indexes);
|
|
|
|
// Resolve the request.
|
|
r.complete_valid_sampling_column_requests(
|
|
vec![(sync_request_id, column_indexes.clone())],
|
|
data_columns,
|
|
);
|
|
r.expect_clean_finished_sampling();
|
|
}
|
|
|
|
#[test]
|
|
fn sampling_batch_requests_not_enough_responses_returned() {
|
|
let Some(mut r) = TestRig::test_setup_after_fulu() else {
|
|
return;
|
|
};
|
|
let _supernode = r.new_connected_supernode_peer();
|
|
let (block, data_columns) = r.rand_block_and_data_columns();
|
|
let block_root = block.canonical_root();
|
|
r.trigger_sample_block(block_root, block.slot());
|
|
|
|
// Retrieve the sample request, which should be batched.
|
|
let (sync_request_id, column_indexes) = r
|
|
.expect_only_data_columns_by_root_requests(block_root, 1)
|
|
.pop()
|
|
.unwrap();
|
|
assert_eq!(column_indexes.len(), SAMPLING_REQUIRED_SUCCESSES);
|
|
|
|
// The request status should be set to Sampling.
|
|
r.assert_sampling_request_ongoing(block_root, &column_indexes);
|
|
|
|
// Split the indexes to simulate the case where the supernode doesn't have the requested column.
|
|
let (column_indexes_supernode_does_not_have, column_indexes_to_complete) =
|
|
column_indexes.split_at(1);
|
|
|
|
// Complete the requests but only partially, so a NotEnoughResponsesReturned error occurs.
|
|
let data_columns_to_complete = data_columns
|
|
.iter()
|
|
.filter(|d| column_indexes_to_complete.contains(&d.index))
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
r.complete_data_columns_by_root_request(
|
|
(sync_request_id, column_indexes.clone()),
|
|
&data_columns_to_complete,
|
|
);
|
|
|
|
// The request status should be set to NoPeers since the supernode, the only peer, returned not enough responses.
|
|
r.log_sampling_requests(block_root, &column_indexes);
|
|
r.assert_sampling_request_nopeers(block_root, column_indexes_supernode_does_not_have);
|
|
|
|
// The sampling request stalls.
|
|
r.expect_empty_network();
|
|
r.expect_no_work_event();
|
|
r.expect_active_sampling(&block_root);
|
|
}
|
|
|
|
#[test]
|
|
fn custody_lookup_happy_path() {
|
|
let Some(mut r) = TestRig::test_setup_after_fulu() else {
|
|
return;
|
|
};
|
|
let spec = E::default_spec();
|
|
r.new_connected_peers_for_peerdas();
|
|
let (block, data_columns) = r.rand_block_and_data_columns();
|
|
let block_root = block.canonical_root();
|
|
let peer_id = r.new_connected_peer();
|
|
r.trigger_unknown_block_from_attestation(block_root, peer_id);
|
|
// Should not request blobs
|
|
let id = r.expect_block_lookup_request(block.canonical_root());
|
|
r.complete_valid_block_request(id, block.into(), true);
|
|
// for each slot we download `samples_per_slot` columns
|
|
let sample_column_count = spec.samples_per_slot * spec.data_columns_per_group();
|
|
let custody_ids =
|
|
r.expect_only_data_columns_by_root_requests(block_root, sample_column_count as usize);
|
|
r.complete_valid_custody_request(custody_ids, data_columns, false);
|
|
r.expect_no_active_lookups();
|
|
}
|
|
|
|
// TODO(das): Test retries of DataColumnByRoot:
|
|
// - Expect request for column_index
|
|
// - Respond with bad data
|
|
// - Respond with stream terminator
|
|
// ^ The stream terminator should be ignored and not close the next retry
|
|
|
|
// TODO(das): Test error early a sampling request and it getting drop + then receiving responses
|
|
// from pending requests.
|
|
|
|
mod deneb_only {
|
|
use super::*;
|
|
use beacon_chain::{
|
|
block_verification_types::{AsBlock, RpcBlock},
|
|
data_availability_checker::AvailabilityCheckError,
|
|
};
|
|
use std::collections::VecDeque;
|
|
use types::RuntimeVariableList;
|
|
|
|
struct DenebTester {
|
|
rig: TestRig,
|
|
block: Arc<SignedBeaconBlock<E>>,
|
|
blobs: Vec<Arc<BlobSidecar<E>>>,
|
|
parent_block_roots: Vec<Hash256>,
|
|
parent_block: VecDeque<Arc<SignedBeaconBlock<E>>>,
|
|
parent_blobs: VecDeque<Vec<Arc<BlobSidecar<E>>>>,
|
|
unknown_parent_block: Option<Arc<SignedBeaconBlock<E>>>,
|
|
unknown_parent_blobs: Option<Vec<Arc<BlobSidecar<E>>>>,
|
|
peer_id: PeerId,
|
|
block_req_id: Option<SingleLookupReqId>,
|
|
parent_block_req_id: Option<SingleLookupReqId>,
|
|
blob_req_id: Option<SingleLookupReqId>,
|
|
parent_blob_req_id: Option<SingleLookupReqId>,
|
|
slot: Slot,
|
|
block_root: Hash256,
|
|
}
|
|
|
|
enum RequestTrigger {
|
|
AttestationUnknownBlock,
|
|
GossipUnknownParentBlock(usize),
|
|
GossipUnknownParentBlob(usize),
|
|
}
|
|
|
|
impl RequestTrigger {
|
|
fn num_parents(&self) -> usize {
|
|
match self {
|
|
RequestTrigger::AttestationUnknownBlock => 0,
|
|
RequestTrigger::GossipUnknownParentBlock(num_parents) => *num_parents,
|
|
RequestTrigger::GossipUnknownParentBlob(num_parents) => *num_parents,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DenebTester {
|
|
fn new(request_trigger: RequestTrigger) -> Option<Self> {
|
|
let Some(mut rig) = TestRig::test_setup_after_deneb_before_fulu() else {
|
|
return None;
|
|
};
|
|
let (block, blobs) = rig.rand_block_and_blobs(NumBlobs::Random);
|
|
let mut block = Arc::new(block);
|
|
let mut blobs = blobs.into_iter().map(Arc::new).collect::<Vec<_>>();
|
|
let slot = block.slot();
|
|
|
|
let num_parents = request_trigger.num_parents();
|
|
let mut parent_block_chain = VecDeque::with_capacity(num_parents);
|
|
let mut parent_blobs_chain = VecDeque::with_capacity(num_parents);
|
|
let mut parent_block_roots = vec![];
|
|
for _ in 0..num_parents {
|
|
// Set the current block as the parent.
|
|
let parent_root = block.canonical_root();
|
|
let parent_block = block.clone();
|
|
let parent_blobs = blobs.clone();
|
|
parent_block_chain.push_front(parent_block);
|
|
parent_blobs_chain.push_front(parent_blobs);
|
|
parent_block_roots.push(parent_root);
|
|
|
|
// Create the next block.
|
|
let (child_block, child_blobs) =
|
|
rig.block_with_parent_and_blobs(parent_root, NumBlobs::Random);
|
|
let mut child_block = Arc::new(child_block);
|
|
let mut child_blobs = child_blobs.into_iter().map(Arc::new).collect::<Vec<_>>();
|
|
|
|
// Update the new block to the current block.
|
|
std::mem::swap(&mut child_block, &mut block);
|
|
std::mem::swap(&mut child_blobs, &mut blobs);
|
|
}
|
|
let block_root = block.canonical_root();
|
|
|
|
let peer_id = rig.new_connected_peer();
|
|
|
|
// Trigger the request
|
|
let (block_req_id, blob_req_id, parent_block_req_id, parent_blob_req_id) =
|
|
match request_trigger {
|
|
RequestTrigger::AttestationUnknownBlock => {
|
|
rig.send_sync_message(SyncMessage::UnknownBlockHashFromAttestation(
|
|
peer_id, block_root,
|
|
));
|
|
let block_req_id = rig.expect_block_lookup_request(block_root);
|
|
(Some(block_req_id), None, None, None)
|
|
}
|
|
RequestTrigger::GossipUnknownParentBlock { .. } => {
|
|
rig.send_sync_message(SyncMessage::UnknownParentBlock(
|
|
peer_id,
|
|
block.clone(),
|
|
block_root,
|
|
));
|
|
|
|
let parent_root = block.parent_root();
|
|
let parent_block_req_id = rig.expect_block_parent_request(parent_root);
|
|
rig.expect_empty_network(); // expect no more requests
|
|
(None, None, Some(parent_block_req_id), None)
|
|
}
|
|
RequestTrigger::GossipUnknownParentBlob { .. } => {
|
|
let single_blob = blobs.first().cloned().unwrap();
|
|
let parent_root = single_blob.block_parent_root();
|
|
rig.send_sync_message(SyncMessage::UnknownParentBlob(peer_id, single_blob));
|
|
|
|
let parent_block_req_id = rig.expect_block_parent_request(parent_root);
|
|
rig.expect_empty_network(); // expect no more requests
|
|
(None, None, Some(parent_block_req_id), None)
|
|
}
|
|
};
|
|
|
|
Some(Self {
|
|
rig,
|
|
block,
|
|
blobs,
|
|
parent_block: parent_block_chain,
|
|
parent_blobs: parent_blobs_chain,
|
|
parent_block_roots,
|
|
unknown_parent_block: None,
|
|
unknown_parent_blobs: None,
|
|
peer_id,
|
|
block_req_id,
|
|
parent_block_req_id,
|
|
blob_req_id,
|
|
parent_blob_req_id,
|
|
slot,
|
|
block_root,
|
|
})
|
|
}
|
|
|
|
fn trigger_unknown_block_from_attestation(mut self) -> Self {
|
|
let block_root = self.block.canonical_root();
|
|
self.rig
|
|
.trigger_unknown_block_from_attestation(block_root, self.peer_id);
|
|
self
|
|
}
|
|
|
|
fn parent_block_response(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
let block = self.parent_block.pop_front().unwrap().clone();
|
|
let _ = self.unknown_parent_block.insert(block.clone());
|
|
self.rig.parent_lookup_block_response(
|
|
self.parent_block_req_id.expect("parent request id"),
|
|
self.peer_id,
|
|
Some(block),
|
|
);
|
|
|
|
self.rig.assert_parent_lookups_count(1);
|
|
self
|
|
}
|
|
|
|
fn parent_block_response_expect_blobs(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
let block = self.parent_block.pop_front().unwrap().clone();
|
|
let _ = self.unknown_parent_block.insert(block.clone());
|
|
self.rig.parent_lookup_block_response(
|
|
self.parent_block_req_id.expect("parent request id"),
|
|
self.peer_id,
|
|
Some(block),
|
|
);
|
|
|
|
// Expect blobs request after sending block
|
|
let s = self.expect_parent_blobs_request();
|
|
|
|
s.rig.assert_parent_lookups_count(1);
|
|
s
|
|
}
|
|
|
|
fn parent_blob_response(mut self) -> Self {
|
|
let blobs = self.parent_blobs.pop_front().unwrap();
|
|
let _ = self.unknown_parent_blobs.insert(blobs.clone());
|
|
for blob in &blobs {
|
|
self.rig.parent_lookup_blob_response(
|
|
self.parent_blob_req_id.expect("parent blob request id"),
|
|
self.peer_id,
|
|
Some(blob.clone()),
|
|
);
|
|
assert_eq!(self.rig.active_parent_lookups_count(), 1);
|
|
}
|
|
self.rig.parent_lookup_blob_response(
|
|
self.parent_blob_req_id.expect("parent blob request id"),
|
|
self.peer_id,
|
|
None,
|
|
);
|
|
|
|
self
|
|
}
|
|
|
|
fn block_response_triggering_process(self) -> Self {
|
|
let mut me = self.block_response_and_expect_blob_request();
|
|
me.rig.expect_block_process(ResponseType::Block);
|
|
|
|
// The request should still be active.
|
|
assert_eq!(me.rig.active_single_lookups_count(), 1);
|
|
me
|
|
}
|
|
|
|
fn block_response_and_expect_blob_request(mut self) -> Self {
|
|
// The peer provides the correct block, should not be penalized. Now the block should be sent
|
|
// for processing.
|
|
self.rig.single_lookup_block_response(
|
|
self.block_req_id.expect("block request id"),
|
|
self.peer_id,
|
|
Some(self.block.clone()),
|
|
);
|
|
// After responding with block the node will issue a blob request
|
|
let mut s = self.expect_blobs_request();
|
|
|
|
s.rig.expect_empty_network();
|
|
|
|
// The request should still be active.
|
|
s.rig.assert_lookup_is_active(s.block.canonical_root());
|
|
s
|
|
}
|
|
|
|
fn blobs_response(mut self) -> Self {
|
|
self.rig
|
|
.log(&format!("blobs response {}", self.blobs.len()));
|
|
for blob in &self.blobs {
|
|
self.rig.single_lookup_blob_response(
|
|
self.blob_req_id.expect("blob request id"),
|
|
self.peer_id,
|
|
Some(blob.clone()),
|
|
);
|
|
self.rig
|
|
.assert_lookup_is_active(self.block.canonical_root());
|
|
}
|
|
self.rig.single_lookup_blob_response(
|
|
self.blob_req_id.expect("blob request id"),
|
|
self.peer_id,
|
|
None,
|
|
);
|
|
self
|
|
}
|
|
|
|
fn blobs_response_was_valid(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
if !self.blobs.is_empty() {
|
|
self.rig.expect_block_process(ResponseType::Blob);
|
|
}
|
|
self
|
|
}
|
|
|
|
fn expect_empty_beacon_processor(mut self) -> Self {
|
|
self.rig.expect_empty_beacon_processor();
|
|
self
|
|
}
|
|
|
|
fn empty_block_response(mut self) -> Self {
|
|
self.rig.single_lookup_block_response(
|
|
self.block_req_id.expect("block request id"),
|
|
self.peer_id,
|
|
None,
|
|
);
|
|
self
|
|
}
|
|
|
|
fn empty_blobs_response(mut self) -> Self {
|
|
self.rig.single_lookup_blob_response(
|
|
self.blob_req_id.expect("blob request id"),
|
|
self.peer_id,
|
|
None,
|
|
);
|
|
self
|
|
}
|
|
|
|
fn empty_parent_blobs_response(mut self) -> Self {
|
|
self.rig.parent_lookup_blob_response(
|
|
self.parent_blob_req_id.expect("blob request id"),
|
|
self.peer_id,
|
|
None,
|
|
);
|
|
self
|
|
}
|
|
|
|
fn block_missing_components(mut self) -> Self {
|
|
self.rig.single_block_component_processed(
|
|
self.block_req_id.expect("block request id").lookup_id,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
self.block.slot(),
|
|
self.block_root,
|
|
)),
|
|
);
|
|
self.rig.expect_empty_network();
|
|
self.rig.assert_single_lookups_count(1);
|
|
self
|
|
}
|
|
|
|
fn blob_imported(mut self) -> Self {
|
|
self.rig.single_blob_component_processed(
|
|
self.blob_req_id.expect("blob request id").lookup_id,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(self.block_root)),
|
|
);
|
|
self.rig.expect_empty_network();
|
|
self.rig.assert_single_lookups_count(0);
|
|
self
|
|
}
|
|
|
|
fn block_imported(mut self) -> Self {
|
|
// Missing blobs should be the request is not removed, the outstanding blobs request should
|
|
// mean we do not send a new request.
|
|
self.rig.single_block_component_processed(
|
|
self.block_req_id
|
|
.or(self.blob_req_id)
|
|
.expect("block request id")
|
|
.lookup_id,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(self.block_root)),
|
|
);
|
|
self.rig.expect_empty_network();
|
|
self.rig.assert_single_lookups_count(0);
|
|
self
|
|
}
|
|
|
|
fn parent_block_imported(mut self) -> Self {
|
|
let parent_root = *self.parent_block_roots.first().unwrap();
|
|
self.rig
|
|
.log(&format!("parent_block_imported {parent_root:?}"));
|
|
self.rig.parent_block_processed(
|
|
self.block_root,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(parent_root)),
|
|
);
|
|
self.rig.expect_no_requests_for(parent_root);
|
|
self.rig.assert_parent_lookups_count(0);
|
|
self
|
|
}
|
|
|
|
fn parent_block_missing_components(mut self) -> Self {
|
|
let parent_root = *self.parent_block_roots.first().unwrap();
|
|
self.rig
|
|
.log(&format!("parent_block_missing_components {parent_root:?}"));
|
|
self.rig.parent_block_processed(
|
|
self.block_root,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
Slot::new(0),
|
|
parent_root,
|
|
)),
|
|
);
|
|
self.rig.expect_no_requests_for(parent_root);
|
|
self
|
|
}
|
|
|
|
fn parent_blob_imported(mut self) -> Self {
|
|
let parent_root = *self.parent_block_roots.first().unwrap();
|
|
self.rig
|
|
.log(&format!("parent_blob_imported {parent_root:?}"));
|
|
self.rig.parent_blob_processed(
|
|
self.block_root,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::Imported(parent_root)),
|
|
);
|
|
|
|
self.rig.expect_no_requests_for(parent_root);
|
|
self.rig.assert_parent_lookups_count(0);
|
|
self
|
|
}
|
|
|
|
fn parent_block_unknown_parent(mut self) -> Self {
|
|
self.rig.log("parent_block_unknown_parent");
|
|
let block = self.unknown_parent_block.take().unwrap();
|
|
let max_len = self.rig.spec.max_blobs_per_block(block.epoch()) as usize;
|
|
// Now this block is the one we expect requests from
|
|
self.block = block.clone();
|
|
let block = RpcBlock::new(
|
|
Some(block.canonical_root()),
|
|
block,
|
|
self.unknown_parent_blobs
|
|
.take()
|
|
.map(|vec| RuntimeVariableList::from_vec(vec, max_len)),
|
|
)
|
|
.unwrap();
|
|
self.rig.parent_block_processed(
|
|
self.block_root,
|
|
BlockProcessingResult::Err(BlockError::ParentUnknown {
|
|
parent_root: block.parent_root(),
|
|
}),
|
|
);
|
|
assert_eq!(self.rig.active_parent_lookups_count(), 1);
|
|
self
|
|
}
|
|
|
|
fn invalid_parent_processed(mut self) -> Self {
|
|
self.rig.parent_block_processed(
|
|
self.block_root,
|
|
BlockProcessingResult::Err(BlockError::BlockSlotLimitReached),
|
|
);
|
|
assert_eq!(self.rig.active_parent_lookups_count(), 1);
|
|
self
|
|
}
|
|
|
|
fn invalid_block_processed(mut self) -> Self {
|
|
self.rig.single_block_component_processed(
|
|
self.block_req_id.expect("block request id").lookup_id,
|
|
BlockProcessingResult::Err(BlockError::BlockSlotLimitReached),
|
|
);
|
|
self.rig.assert_single_lookups_count(1);
|
|
self
|
|
}
|
|
|
|
fn invalid_blob_processed(mut self) -> Self {
|
|
self.rig.log("invalid_blob_processed");
|
|
self.rig.single_blob_component_processed(
|
|
self.blob_req_id.expect("blob request id").lookup_id,
|
|
BlockProcessingResult::Err(BlockError::AvailabilityCheck(
|
|
AvailabilityCheckError::InvalidBlobs(kzg::Error::KzgVerificationFailed),
|
|
)),
|
|
);
|
|
self.rig.assert_single_lookups_count(1);
|
|
self
|
|
}
|
|
|
|
fn missing_components_from_block_request(mut self) -> Self {
|
|
self.rig.single_block_component_processed(
|
|
self.block_req_id.expect("block request id").lookup_id,
|
|
BlockProcessingResult::Ok(AvailabilityProcessingStatus::MissingComponents(
|
|
self.slot,
|
|
self.block_root,
|
|
)),
|
|
);
|
|
// Add block to da_checker so blobs request can continue
|
|
self.rig.insert_block_to_da_checker(self.block.clone());
|
|
|
|
self.rig.assert_single_lookups_count(1);
|
|
self
|
|
}
|
|
|
|
fn complete_current_block_and_blobs_lookup(self) -> Self {
|
|
self.expect_block_request()
|
|
.block_response_and_expect_blob_request()
|
|
.blobs_response()
|
|
// TODO: Should send blobs for processing
|
|
.expect_block_process()
|
|
.block_imported()
|
|
}
|
|
|
|
fn log(self, msg: &str) -> Self {
|
|
self.rig.log(msg);
|
|
self
|
|
}
|
|
|
|
fn parent_block_then_empty_parent_blobs(self) -> Self {
|
|
self.log(
|
|
" Return empty blobs for parent, block errors with missing components, downscore",
|
|
)
|
|
.parent_block_response()
|
|
.expect_parent_blobs_request()
|
|
.empty_parent_blobs_response()
|
|
.expect_penalty("NotEnoughResponsesReturned")
|
|
.log("Re-request parent blobs, succeed and import parent")
|
|
.expect_parent_blobs_request()
|
|
.parent_blob_response()
|
|
.expect_block_process()
|
|
.parent_block_missing_components()
|
|
// Insert new peer into child request before completing parent
|
|
.trigger_unknown_block_from_attestation()
|
|
.parent_blob_imported()
|
|
}
|
|
|
|
fn expect_penalty(mut self, expect_penalty_msg: &'static str) -> Self {
|
|
self.rig.expect_penalty(self.peer_id, expect_penalty_msg);
|
|
self
|
|
}
|
|
fn expect_no_penalty(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
self
|
|
}
|
|
fn expect_no_penalty_and_no_requests(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
self
|
|
}
|
|
fn expect_block_request(mut self) -> Self {
|
|
let id = self
|
|
.rig
|
|
.expect_block_lookup_request(self.block.canonical_root());
|
|
self.block_req_id = Some(id);
|
|
self
|
|
}
|
|
fn expect_blobs_request(mut self) -> Self {
|
|
let id = self
|
|
.rig
|
|
.expect_blob_lookup_request(self.block.canonical_root());
|
|
self.blob_req_id = Some(id);
|
|
self
|
|
}
|
|
fn expect_parent_block_request(mut self) -> Self {
|
|
let id = self
|
|
.rig
|
|
.expect_block_parent_request(self.block.parent_root());
|
|
self.parent_block_req_id = Some(id);
|
|
self
|
|
}
|
|
fn expect_parent_blobs_request(mut self) -> Self {
|
|
let id = self
|
|
.rig
|
|
.expect_blob_parent_request(self.block.parent_root());
|
|
self.parent_blob_req_id = Some(id);
|
|
self
|
|
}
|
|
fn expect_no_blobs_request(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
self
|
|
}
|
|
fn expect_no_block_request(mut self) -> Self {
|
|
self.rig.expect_empty_network();
|
|
self
|
|
}
|
|
fn invalidate_blobs_too_few(mut self) -> Self {
|
|
self.blobs.pop().expect("blobs");
|
|
self
|
|
}
|
|
fn expect_block_process(mut self) -> Self {
|
|
self.rig.expect_block_process(ResponseType::Block);
|
|
self
|
|
}
|
|
fn expect_no_active_lookups(self) -> Self {
|
|
self.rig.expect_no_active_lookups();
|
|
self
|
|
}
|
|
fn search_parent_dup(mut self) -> Self {
|
|
self.rig
|
|
.trigger_unknown_parent_block(self.peer_id, self.block.clone());
|
|
self
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_and_blob_lookup_block_returned_first_attestation() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::AttestationUnknownBlock) else {
|
|
return;
|
|
};
|
|
tester
|
|
.block_response_and_expect_blob_request()
|
|
.blobs_response()
|
|
.block_missing_components() // blobs not yet imported
|
|
.blobs_response_was_valid()
|
|
.blob_imported(); // now blobs resolve as imported
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_response_then_empty_blob_response_attestation() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::AttestationUnknownBlock) else {
|
|
return;
|
|
};
|
|
tester
|
|
.block_response_and_expect_blob_request()
|
|
.missing_components_from_block_request()
|
|
.empty_blobs_response()
|
|
.expect_penalty("NotEnoughResponsesReturned")
|
|
.expect_blobs_request()
|
|
.expect_no_block_request();
|
|
}
|
|
|
|
#[test]
|
|
fn single_invalid_block_response_then_blob_response_attestation() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::AttestationUnknownBlock) else {
|
|
return;
|
|
};
|
|
tester
|
|
.block_response_triggering_process()
|
|
.invalid_block_processed()
|
|
.expect_penalty("lookup_block_processing_failure")
|
|
.expect_block_request()
|
|
.expect_no_blobs_request()
|
|
.blobs_response()
|
|
// blobs not sent for processing until the block is processed
|
|
.expect_no_penalty_and_no_requests();
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_response_then_invalid_blob_response_attestation() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::AttestationUnknownBlock) else {
|
|
return;
|
|
};
|
|
tester
|
|
.block_response_triggering_process()
|
|
.missing_components_from_block_request()
|
|
.blobs_response()
|
|
.invalid_blob_processed()
|
|
.expect_penalty("lookup_blobs_processing_failure")
|
|
.expect_blobs_request()
|
|
.expect_no_block_request();
|
|
}
|
|
|
|
#[test]
|
|
fn single_block_response_then_too_few_blobs_response_attestation() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::AttestationUnknownBlock) else {
|
|
return;
|
|
};
|
|
tester
|
|
.block_response_triggering_process()
|
|
.missing_components_from_block_request()
|
|
.invalidate_blobs_too_few()
|
|
.blobs_response()
|
|
.expect_penalty("NotEnoughResponsesReturned")
|
|
.expect_blobs_request()
|
|
.expect_no_block_request();
|
|
}
|
|
|
|
// Test peer returning block that has unknown parent, and a new lookup is created
|
|
#[test]
|
|
fn parent_block_unknown_parent() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlock(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.expect_empty_beacon_processor()
|
|
.parent_block_response_expect_blobs()
|
|
.parent_blob_response()
|
|
.expect_block_process()
|
|
.parent_block_unknown_parent()
|
|
.expect_parent_block_request()
|
|
.expect_empty_beacon_processor();
|
|
}
|
|
|
|
// Test peer returning invalid (processing) block, expect retry
|
|
#[test]
|
|
fn parent_block_invalid_parent() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlock(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.parent_block_response_expect_blobs()
|
|
.parent_blob_response()
|
|
.expect_block_process()
|
|
.invalid_parent_processed()
|
|
.expect_penalty("lookup_block_processing_failure")
|
|
.expect_parent_block_request()
|
|
.expect_empty_beacon_processor();
|
|
}
|
|
|
|
// Tests that if a peer does not respond with a block, we downscore and retry the block only
|
|
#[test]
|
|
fn empty_block_is_retried() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::AttestationUnknownBlock) else {
|
|
return;
|
|
};
|
|
tester
|
|
.empty_block_response()
|
|
.expect_penalty("NotEnoughResponsesReturned")
|
|
.expect_block_request()
|
|
.expect_no_blobs_request()
|
|
.block_response_and_expect_blob_request()
|
|
.blobs_response()
|
|
.block_imported()
|
|
.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn parent_block_then_empty_parent_blobs() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlock(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.parent_block_then_empty_parent_blobs()
|
|
.log("resolve original block trigger blobs request and import")
|
|
// Should not have block request, it is cached
|
|
.expect_blobs_request()
|
|
// TODO: Should send blobs for processing
|
|
.block_imported()
|
|
.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn parent_blob_unknown_parent() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlob(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.expect_empty_beacon_processor()
|
|
.parent_block_response_expect_blobs()
|
|
.parent_blob_response()
|
|
.expect_block_process()
|
|
.parent_block_unknown_parent()
|
|
.expect_parent_block_request()
|
|
.expect_empty_beacon_processor();
|
|
}
|
|
|
|
#[test]
|
|
fn parent_blob_invalid_parent() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlob(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.expect_empty_beacon_processor()
|
|
.parent_block_response_expect_blobs()
|
|
.parent_blob_response()
|
|
.expect_block_process()
|
|
.invalid_parent_processed()
|
|
.expect_penalty("lookup_block_processing_failure")
|
|
.expect_parent_block_request()
|
|
// blobs are not sent until block is processed
|
|
.expect_empty_beacon_processor();
|
|
}
|
|
|
|
#[test]
|
|
fn parent_block_and_blob_lookup_parent_returned_first_blob_trigger() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlob(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.parent_block_response()
|
|
.expect_parent_blobs_request()
|
|
.parent_blob_response()
|
|
.expect_block_process()
|
|
.trigger_unknown_block_from_attestation()
|
|
.parent_block_imported()
|
|
.complete_current_block_and_blobs_lookup()
|
|
.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn parent_block_then_empty_parent_blobs_blob_trigger() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlob(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.parent_block_then_empty_parent_blobs()
|
|
.log("resolve original block trigger blobs request and import")
|
|
.complete_current_block_and_blobs_lookup()
|
|
.expect_no_active_lookups();
|
|
}
|
|
|
|
#[test]
|
|
fn parent_blob_unknown_parent_chain() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlob(2)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.expect_empty_beacon_processor()
|
|
.parent_block_response_expect_blobs()
|
|
.parent_blob_response()
|
|
.expect_no_penalty()
|
|
.expect_block_process()
|
|
.parent_block_unknown_parent()
|
|
.expect_parent_block_request()
|
|
.expect_empty_beacon_processor()
|
|
.parent_block_response()
|
|
.expect_parent_blobs_request()
|
|
.parent_blob_response()
|
|
.expect_no_penalty()
|
|
.expect_block_process();
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_parent_block_dup() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlock(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.search_parent_dup()
|
|
.expect_no_blobs_request()
|
|
.expect_no_block_request();
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_parent_blob_dup() {
|
|
let Some(tester) = DenebTester::new(RequestTrigger::GossipUnknownParentBlob(1)) else {
|
|
return;
|
|
};
|
|
tester
|
|
.search_parent_dup()
|
|
.expect_no_blobs_request()
|
|
.expect_no_block_request();
|
|
}
|
|
|
|
// This test no longer applies, we don't issue requests for child lookups
|
|
// Keep for after updating rules on fetching blocks only first
|
|
#[ignore]
|
|
#[test]
|
|
fn no_peer_penalty_when_rpc_response_already_known_from_gossip() {
|
|
let Some(mut r) = TestRig::test_setup_after_deneb_before_fulu() else {
|
|
return;
|
|
};
|
|
let (block, blobs) = r.rand_block_and_blobs(NumBlobs::Number(2));
|
|
let block_root = block.canonical_root();
|
|
let blob_0 = blobs[0].clone();
|
|
let blob_1 = blobs[1].clone();
|
|
let peer_a = r.new_connected_peer();
|
|
let peer_b = r.new_connected_peer();
|
|
// Send unknown parent block lookup
|
|
r.trigger_unknown_parent_block(peer_a, block.into());
|
|
// Expect network request for blobs
|
|
let id = r.expect_blob_lookup_request(block_root);
|
|
// Peer responses with blob 0
|
|
r.single_lookup_blob_response(id, peer_a, Some(blob_0.into()));
|
|
// Blob 1 is received via gossip unknown parent blob from a different peer
|
|
r.trigger_unknown_parent_blob(peer_b, blob_1.clone());
|
|
// Original peer sends blob 1 via RPC
|
|
r.single_lookup_blob_response(id, peer_a, Some(blob_1.into()));
|
|
// Assert no downscore event for original peer
|
|
r.expect_no_penalty_for(peer_a);
|
|
}
|
|
}
|