mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
test: add a test for devnet failure (#14288)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@ -266,8 +266,6 @@ where
|
|||||||
|
|
||||||
/// Sends FCU and waits for the node to sync to the given block.
|
/// Sends FCU and waits for the node to sync to the given block.
|
||||||
pub async fn sync_to(&self, block: BlockHash) -> eyre::Result<()> {
|
pub async fn sync_to(&self, block: BlockHash) -> eyre::Result<()> {
|
||||||
self.engine_api.update_forkchoice(block, block).await?;
|
|
||||||
|
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
while self
|
while self
|
||||||
@ -277,6 +275,7 @@ where
|
|||||||
.is_none_or(|h| h.hash() != block)
|
.is_none_or(|h| h.hash() != block)
|
||||||
{
|
{
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
self.engine_api.update_forkchoice(block, block).await?;
|
||||||
|
|
||||||
assert!(start.elapsed() <= std::time::Duration::from_secs(10), "timed out");
|
assert!(start.elapsed() <= std::time::Duration::from_secs(10), "timed out");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -135,3 +135,46 @@ async fn test_long_reorg() -> eyre::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_reorg_through_backfill() -> eyre::Result<()> {
|
||||||
|
reth_tracing::init_test_tracing();
|
||||||
|
|
||||||
|
let seed: [u8; 32] = rand::thread_rng().gen();
|
||||||
|
let mut rng = StdRng::from_seed(seed);
|
||||||
|
println!("Seed: {:?}", seed);
|
||||||
|
|
||||||
|
let chain_spec = Arc::new(
|
||||||
|
ChainSpecBuilder::default()
|
||||||
|
.chain(MAINNET.chain)
|
||||||
|
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||||
|
.cancun_activated()
|
||||||
|
.prague_activated()
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (mut nodes, _tasks, _) =
|
||||||
|
setup_engine::<EthereumNode>(2, chain_spec.clone(), false, eth_payload_attributes).await?;
|
||||||
|
|
||||||
|
let mut first_node = nodes.pop().unwrap();
|
||||||
|
let mut second_node = nodes.pop().unwrap();
|
||||||
|
|
||||||
|
let first_provider = ProviderBuilder::new().on_http(first_node.rpc_url());
|
||||||
|
|
||||||
|
// Advance first node 100 blocks and finalize the chain.
|
||||||
|
advance_with_random_transactions(&mut first_node, 100, &mut rng, true).await?;
|
||||||
|
|
||||||
|
// Sync second node to 20th block.
|
||||||
|
let head = first_provider.get_block_by_number(20.into(), false.into()).await?.unwrap();
|
||||||
|
second_node.sync_to(head.header.hash).await?;
|
||||||
|
|
||||||
|
// Produce an unfinalized fork chain with 5 blocks
|
||||||
|
second_node.payload.timestamp = head.header.timestamp;
|
||||||
|
advance_with_random_transactions(&mut second_node, 5, &mut rng, false).await?;
|
||||||
|
|
||||||
|
// Now reorg second node to the finalized canonical head
|
||||||
|
let head = first_provider.get_block_by_number(100.into(), false.into()).await?.unwrap();
|
||||||
|
second_node.sync_to(head.header.hash).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ use std::{
|
|||||||
task::{ready, Context, Poll},
|
task::{ready, Context, Poll},
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
/// A heuristic that is used to determine the number of requests that should be prepared for a peer.
|
/// A heuristic that is used to determine the number of requests that should be prepared for a peer.
|
||||||
/// This should ensure that there are always requests lined up for peers to handle while the
|
/// This should ensure that there are always requests lined up for peers to handle while the
|
||||||
@ -203,6 +203,16 @@ where
|
|||||||
self.queued_validated_headers.last().or(self.lowest_validated_header.as_ref())
|
self.queued_validated_headers.last().or(self.lowest_validated_header.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resets the request trackers and clears the sync target.
|
||||||
|
///
|
||||||
|
/// This ensures the downloader will restart after a new sync target has been set.
|
||||||
|
fn reset(&mut self) {
|
||||||
|
debug!(target: "downloaders::headers", "Resetting headers downloader");
|
||||||
|
self.next_request_block_number = 0;
|
||||||
|
self.next_chain_tip_block_number = 0;
|
||||||
|
self.sync_target.take();
|
||||||
|
}
|
||||||
|
|
||||||
/// Validate that the received header matches the expected sync target.
|
/// Validate that the received header matches the expected sync target.
|
||||||
fn validate_sync_target(
|
fn validate_sync_target(
|
||||||
&self,
|
&self,
|
||||||
@ -294,11 +304,23 @@ where
|
|||||||
|
|
||||||
// If the header is valid on its own, but not against its parent, we return it as
|
// If the header is valid on its own, but not against its parent, we return it as
|
||||||
// detached head error.
|
// detached head error.
|
||||||
|
// In stage sync this will trigger an unwind because this means that the the local head
|
||||||
|
// is not part of the chain the sync target is on. In other words, the downloader was
|
||||||
|
// unable to connect the the sync target with the local head because the sync target and
|
||||||
|
// the local head or on different chains.
|
||||||
if let Err(error) = self.consensus.validate_header_against_parent(&*last_header, head) {
|
if let Err(error) = self.consensus.validate_header_against_parent(&*last_header, head) {
|
||||||
|
let local_head = head.clone();
|
||||||
// Replace the last header with a detached variant
|
// Replace the last header with a detached variant
|
||||||
error!(target: "downloaders::headers", %error, number = last_header.number(), hash = ?last_header.hash(), "Header cannot be attached to known canonical chain");
|
error!(target: "downloaders::headers", %error, number = last_header.number(), hash = ?last_header.hash(), "Header cannot be attached to known canonical chain");
|
||||||
|
|
||||||
|
// Reset trackers so that we can start over the next time the sync target is
|
||||||
|
// updated.
|
||||||
|
// The expected event flow when that happens is that the node will unwind the local
|
||||||
|
// chain and restart the downloader.
|
||||||
|
self.reset();
|
||||||
|
|
||||||
return Err(HeadersDownloaderError::DetachedHead {
|
return Err(HeadersDownloaderError::DetachedHead {
|
||||||
local_head: Box::new(head.clone()),
|
local_head: Box::new(local_head),
|
||||||
header: Box::new(last_header.clone()),
|
header: Box::new(last_header.clone()),
|
||||||
error: Box::new(error),
|
error: Box::new(error),
|
||||||
}
|
}
|
||||||
@ -674,6 +696,11 @@ where
|
|||||||
// headers are sorted high to low
|
// headers are sorted high to low
|
||||||
self.queued_validated_headers.pop();
|
self.queued_validated_headers.pop();
|
||||||
}
|
}
|
||||||
|
trace!(
|
||||||
|
target: "downloaders::headers",
|
||||||
|
head=?head.num_hash(),
|
||||||
|
"Updating local head"
|
||||||
|
);
|
||||||
// update the local head
|
// update the local head
|
||||||
self.local_head = Some(head);
|
self.local_head = Some(head);
|
||||||
}
|
}
|
||||||
@ -681,6 +708,12 @@ where
|
|||||||
/// If the given target is different from the current target, we need to update the sync target
|
/// If the given target is different from the current target, we need to update the sync target
|
||||||
fn update_sync_target(&mut self, target: SyncTarget) {
|
fn update_sync_target(&mut self, target: SyncTarget) {
|
||||||
let current_tip = self.sync_target.as_ref().and_then(|t| t.hash());
|
let current_tip = self.sync_target.as_ref().and_then(|t| t.hash());
|
||||||
|
trace!(
|
||||||
|
target: "downloaders::headers",
|
||||||
|
sync_target=?target,
|
||||||
|
current_tip=?current_tip,
|
||||||
|
"Updating sync target"
|
||||||
|
);
|
||||||
match target {
|
match target {
|
||||||
SyncTarget::Tip(tip) => {
|
SyncTarget::Tip(tip) => {
|
||||||
if Some(tip) != current_tip {
|
if Some(tip) != current_tip {
|
||||||
|
|||||||
@ -176,6 +176,7 @@ impl<T: HeaderDownloader> Future for SpawnedDownloader<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Commands delegated to the spawned [`HeaderDownloader`]
|
/// Commands delegated to the spawned [`HeaderDownloader`]
|
||||||
|
#[derive(Debug)]
|
||||||
enum DownloaderUpdates<H> {
|
enum DownloaderUpdates<H> {
|
||||||
UpdateSyncGap(SealedHeader<H>, SyncTarget),
|
UpdateSyncGap(SealedHeader<H>, SyncTarget),
|
||||||
UpdateLocalHead(SealedHeader<H>),
|
UpdateLocalHead(SealedHeader<H>),
|
||||||
|
|||||||
@ -23,7 +23,7 @@ pub trait HeaderDownloader:
|
|||||||
/// The header type being downloaded.
|
/// The header type being downloaded.
|
||||||
type Header: Sealable + Debug + Send + Sync + Unpin + 'static;
|
type Header: Sealable + Debug + Send + Sync + Unpin + 'static;
|
||||||
|
|
||||||
/// Updates the gap to sync which ranges from local head to the sync target
|
/// Updates the gap to sync which ranges from local head to the sync target.
|
||||||
///
|
///
|
||||||
/// See also [`HeaderDownloader::update_sync_target`] and
|
/// See also [`HeaderDownloader::update_sync_target`] and
|
||||||
/// [`HeaderDownloader::update_local_head`]
|
/// [`HeaderDownloader::update_local_head`]
|
||||||
@ -35,7 +35,7 @@ pub trait HeaderDownloader:
|
|||||||
/// Updates the block number of the local database
|
/// Updates the block number of the local database
|
||||||
fn update_local_head(&mut self, head: SealedHeader<Self::Header>);
|
fn update_local_head(&mut self, head: SealedHeader<Self::Header>);
|
||||||
|
|
||||||
/// Updates the target we want to sync to
|
/// Updates the target we want to sync to.
|
||||||
fn update_sync_target(&mut self, target: SyncTarget);
|
fn update_sync_target(&mut self, target: SyncTarget);
|
||||||
|
|
||||||
/// Sets the headers batch size that the Stream should return.
|
/// Sets the headers batch size that the Stream should return.
|
||||||
|
|||||||
Reference in New Issue
Block a user