mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-11 00:08:13 -05:00
test: add deep reorg e2e test (#16531)
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
use crate::testsuite::{Environment, LatestBlockInfo};
|
||||
use alloy_primitives::{Bytes, B256, U256};
|
||||
use alloy_rpc_types_engine::{
|
||||
payload::ExecutionPayloadEnvelopeV3, ForkchoiceState, PayloadAttributes, PayloadStatusEnum,
|
||||
payload::ExecutionPayloadEnvelopeV3, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes,
|
||||
PayloadStatusEnum,
|
||||
};
|
||||
use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction};
|
||||
use eyre::Result;
|
||||
@@ -14,6 +15,27 @@ use std::{future::Future, marker::PhantomData, time::Duration};
|
||||
use tokio::time::sleep;
|
||||
use tracing::debug;
|
||||
|
||||
/// Validates a forkchoice update response and returns an error if invalid
|
||||
fn validate_fcu_response(response: &ForkchoiceUpdated, context: &str) -> Result<()> {
|
||||
match &response.payload_status.status {
|
||||
PayloadStatusEnum::Valid => {
|
||||
debug!("{}: FCU accepted as valid", context);
|
||||
Ok(())
|
||||
}
|
||||
PayloadStatusEnum::Invalid { validation_error } => {
|
||||
Err(eyre::eyre!("{}: FCU rejected as invalid: {:?}", context, validation_error))
|
||||
}
|
||||
PayloadStatusEnum::Syncing => {
|
||||
debug!("{}: FCU accepted, node is syncing", context);
|
||||
Ok(())
|
||||
}
|
||||
PayloadStatusEnum::Accepted => {
|
||||
debug!("{}: FCU accepted for processing", context);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An action that can be performed on an instance.
|
||||
///
|
||||
/// Actions execute operations and potentially make assertions in a single step.
|
||||
@@ -282,6 +304,9 @@ where
|
||||
|
||||
debug!("FCU result: {:?}", fcu_result);
|
||||
|
||||
// validate the FCU status before proceeding
|
||||
validate_fcu_response(&fcu_result, "GenerateNextPayload")?;
|
||||
|
||||
let payload_id = if let Some(payload_id) = fcu_result.payload_id {
|
||||
debug!("Received new payload ID: {:?}", payload_id);
|
||||
payload_id
|
||||
@@ -305,6 +330,9 @@ where
|
||||
|
||||
debug!("Fresh FCU result: {:?}", fresh_fcu_result);
|
||||
|
||||
// validate the fresh FCU status
|
||||
validate_fcu_response(&fresh_fcu_result, "GenerateNextPayload (fresh)")?;
|
||||
|
||||
if let Some(payload_id) = fresh_fcu_result.payload_id {
|
||||
payload_id
|
||||
} else {
|
||||
@@ -400,6 +428,8 @@ where
|
||||
"Client {}: Forkchoice update status: {:?}",
|
||||
idx, resp.payload_status.status
|
||||
);
|
||||
// validate that the forkchoice update was accepted
|
||||
validate_fcu_response(&resp, &format!("Client {idx}"))?;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(eyre::eyre!(
|
||||
@@ -594,12 +624,13 @@ where
|
||||
Box::pin(async move {
|
||||
for _ in 0..self.num_blocks {
|
||||
// create a fresh sequence for each block to avoid state pollution
|
||||
// Note: This produces blocks but does NOT make them canonical
|
||||
// Use MakeCanonical action explicitly if canonicalization is needed
|
||||
let mut sequence = Sequence::new(vec![
|
||||
Box::new(PickNextBlockProducer::default()),
|
||||
Box::new(GeneratePayloadAttributes::default()),
|
||||
Box::new(GenerateNextPayload::default()),
|
||||
Box::new(BroadcastNextNewPayload::default()),
|
||||
Box::new(BroadcastLatestForkchoice::default()),
|
||||
Box::new(UpdateBlockInfo::default()),
|
||||
]);
|
||||
sequence.execute(env).await?;
|
||||
@@ -1004,6 +1035,31 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Action that makes the current latest block canonical by broadcasting a forkchoice update
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MakeCanonical {}
|
||||
|
||||
impl MakeCanonical {
|
||||
/// Create a new `MakeCanonical` action
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Engine> Action<Engine> for MakeCanonical
|
||||
where
|
||||
Engine: EngineTypes + PayloadTypes,
|
||||
Engine::PayloadAttributes: From<PayloadAttributes> + Clone,
|
||||
Engine::ExecutionPayloadEnvelopeV3: Into<ExecutionPayloadEnvelopeV3>,
|
||||
{
|
||||
fn execute<'a>(&'a mut self, env: &'a mut Environment<Engine>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move {
|
||||
let mut broadcast_action = BroadcastLatestForkchoice::default();
|
||||
broadcast_action.execute(env).await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Action that captures the current block and tags it with a name for later reference
|
||||
#[derive(Debug)]
|
||||
pub struct CaptureBlock {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Example tests using the test suite framework.
|
||||
|
||||
use crate::testsuite::{
|
||||
actions::{AssertMineBlock, CaptureBlock, CreateFork, ProduceBlocks, ReorgTo},
|
||||
actions::{AssertMineBlock, CaptureBlock, CreateFork, MakeCanonical, ProduceBlocks, ReorgTo},
|
||||
setup::{NetworkSetup, Setup},
|
||||
TestBuilder,
|
||||
};
|
||||
@@ -63,8 +63,10 @@ async fn test_testsuite_produce_blocks() -> Result<()> {
|
||||
))
|
||||
.with_network(NetworkSetup::single_node());
|
||||
|
||||
let test =
|
||||
TestBuilder::new().with_setup(setup).with_action(ProduceBlocks::<EthEngineTypes>::new(5));
|
||||
let test = TestBuilder::new()
|
||||
.with_setup(setup)
|
||||
.with_action(ProduceBlocks::<EthEngineTypes>::new(5))
|
||||
.with_action(MakeCanonical::new());
|
||||
|
||||
test.run::<EthereumNode>().await?;
|
||||
|
||||
@@ -88,6 +90,7 @@ async fn test_testsuite_create_fork() -> Result<()> {
|
||||
let test = TestBuilder::new()
|
||||
.with_setup(setup)
|
||||
.with_action(ProduceBlocks::<EthEngineTypes>::new(2))
|
||||
.with_action(MakeCanonical::new())
|
||||
.with_action(CreateFork::<EthEngineTypes>::new(1, 3));
|
||||
|
||||
test.run::<EthereumNode>().await?;
|
||||
@@ -112,9 +115,46 @@ async fn test_testsuite_reorg_with_tagging() -> Result<()> {
|
||||
let test = TestBuilder::new()
|
||||
.with_setup(setup)
|
||||
.with_action(ProduceBlocks::<EthEngineTypes>::new(3)) // produce blocks 1, 2, 3
|
||||
.with_action(CaptureBlock::new("main_chain_tip")) // tag block 3 as "main_chain_tip"
|
||||
.with_action(MakeCanonical::new()) // make main chain tip canonical
|
||||
.with_action(CreateFork::<EthEngineTypes>::new(1, 2)) // fork from block 1, produce blocks 2', 3'
|
||||
.with_action(ReorgTo::<EthEngineTypes>::new_from_tag("main_chain_tip")); // reorg back to tagged block 3
|
||||
.with_action(CaptureBlock::new("fork_tip")) // tag fork tip
|
||||
.with_action(ReorgTo::<EthEngineTypes>::new_from_tag("fork_tip")); // reorg to fork tip
|
||||
|
||||
test.run::<EthereumNode>().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_testsuite_deep_reorg() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let setup = Setup::default()
|
||||
.with_chain_spec(Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
))
|
||||
.with_network(NetworkSetup::single_node());
|
||||
|
||||
let test = TestBuilder::new()
|
||||
.with_setup(setup)
|
||||
// receive newPayload and forkchoiceUpdated with block height 1
|
||||
.with_action(ProduceBlocks::<EthEngineTypes>::new(1))
|
||||
.with_action(MakeCanonical::new())
|
||||
.with_action(CaptureBlock::new("block1"))
|
||||
// receive forkchoiceUpdated with block hash A as head (block A at height 2)
|
||||
.with_action(CreateFork::<EthEngineTypes>::new(1, 1))
|
||||
.with_action(CaptureBlock::new("blockA_height2"))
|
||||
.with_action(MakeCanonical::new())
|
||||
// receive newPayload with block hash B and height 2
|
||||
.with_action(ReorgTo::<EthEngineTypes>::new_from_tag("block1"))
|
||||
.with_action(CreateFork::<EthEngineTypes>::new(1, 1))
|
||||
.with_action(CaptureBlock::new("blockB_height2"))
|
||||
// receive forkchoiceUpdated with block hash B as head
|
||||
.with_action(ReorgTo::<EthEngineTypes>::new_from_tag("blockB_height2"));
|
||||
|
||||
test.run::<EthereumNode>().await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user