feat(test): add rpc e2e tests (#17284)

This commit is contained in:
Federico Gimenez
2025-07-08 19:23:14 +02:00
committed by GitHub
parent 038ddd6614
commit 3ba16128af
21 changed files with 1067 additions and 9 deletions

View File

@@ -63,6 +63,7 @@ alloy-rpc-types-engine.workspace = true
alloy-network.workspace = true
alloy-consensus = { workspace = true, features = ["kzg"] }
alloy-provider = { workspace = true, features = ["reqwest"] }
alloy-genesis.workspace = true
futures-util.workspace = true
eyre.workspace = true

View File

@@ -53,6 +53,9 @@ impl std::fmt::Debug for ChainImportResult {
/// Note: This function is currently specific to `EthereumNode` because the import process
/// uses Ethereum-specific consensus and block format. It can be made generic in the future
/// by abstracting the import process.
/// It uses `NoopConsensus` during import to bypass validation checks like gas limit constraints,
/// which allows importing test chains that may not strictly conform to mainnet consensus rules. The
/// nodes themselves still run with proper consensus when started.
pub async fn setup_engine_with_chain_import(
num_nodes: usize,
chain_spec: Arc<ChainSpec>,
@@ -128,12 +131,14 @@ pub async fn setup_engine_with_chain_import(
reth_db_common::init::init_genesis(&provider_factory)?;
// Import the chain data
// Use no_state to skip state validation for test chains
let import_config = ImportConfig::default();
let config = Config::default();
// Create EVM and consensus for Ethereum
let evm_config = reth_node_ethereum::EthEvmConfig::new(chain_spec.clone());
let consensus = reth_ethereum_consensus::EthBeaconConsensus::new(chain_spec.clone());
// Use NoopConsensus to skip gas limit validation for test imports
let consensus = reth_consensus::noop::NoopConsensus::arc();
let result = import_blocks_from_file(
rlp_path,
@@ -141,7 +146,7 @@ pub async fn setup_engine_with_chain_import(
provider_factory.clone(),
&config,
evm_config,
Arc::new(consensus),
consensus,
)
.await?;
@@ -248,7 +253,8 @@ pub fn load_forkchoice_state(path: &Path) -> eyre::Result<alloy_rpc_types_engine
let json_str = std::fs::read_to_string(path)?;
let fcu_data: serde_json::Value = serde_json::from_str(&json_str)?;
let state = &fcu_data["forkchoiceState"];
// The headfcu.json file contains a JSON-RPC request with the forkchoice state in params[0]
let state = &fcu_data["params"][0];
Ok(alloy_rpc_types_engine::ForkchoiceState {
head_block_hash: state["headBlockHash"]
.as_str()
@@ -330,7 +336,8 @@ mod tests {
let import_config = ImportConfig::default();
let config = Config::default();
let evm_config = reth_node_ethereum::EthEvmConfig::new(chain_spec.clone());
let consensus = reth_ethereum_consensus::EthBeaconConsensus::new(chain_spec.clone());
// Use NoopConsensus to skip gas limit validation for test imports
let consensus = reth_consensus::noop::NoopConsensus::arc();
let result = import_blocks_from_file(
&rlp_path,
@@ -338,7 +345,7 @@ mod tests {
provider_factory.clone(),
&config,
evm_config,
Arc::new(consensus),
consensus,
)
.await
.unwrap();
@@ -480,7 +487,8 @@ mod tests {
let import_config = ImportConfig::default();
let config = Config::default();
let evm_config = reth_node_ethereum::EthEvmConfig::new(chain_spec.clone());
let consensus = reth_ethereum_consensus::EthBeaconConsensus::new(chain_spec.clone());
// Use NoopConsensus to skip gas limit validation for test imports
let consensus = reth_consensus::noop::NoopConsensus::arc();
let result = import_blocks_from_file(
&rlp_path,
@@ -488,7 +496,7 @@ mod tests {
provider_factory.clone(),
&config,
evm_config,
Arc::new(consensus),
consensus,
)
.await
.unwrap();

View File

@@ -176,10 +176,10 @@ pub fn write_blocks_to_rlp(blocks: &[SealedBlock], path: &Path) -> std::io::Resu
/// Create FCU JSON for the tip of the chain
pub fn create_fcu_json(tip: &SealedBlock) -> serde_json::Value {
serde_json::json!({
"forkchoiceState": {
"params": [{
"headBlockHash": format!("0x{:x}", tip.hash()),
"safeBlockHash": format!("0x{:x}", tip.hash()),
"finalizedBlockHash": format!("0x{:x}", tip.hash()),
}
}]
})
}

View File

@@ -293,6 +293,17 @@ where
self
}
/// Set the test setup with chain import from RLP file
pub fn with_setup_and_import(
mut self,
mut setup: Setup<I>,
rlp_path: impl Into<std::path::PathBuf>,
) -> Self {
setup.import_rlp_path = Some(rlp_path.into());
self.setup = Some(setup);
self
}
/// Add an action to the test
pub fn with_action<A>(mut self, action: A) -> Self
where

View File

@@ -46,6 +46,8 @@ pub struct Setup<I> {
/// Holds the import result to keep nodes alive when using imported chain
/// This is stored as an option to avoid lifetime issues with `tokio::spawn`
import_result_holder: Option<crate::setup_import::ChainImportResult>,
/// Path to RLP file to import during setup
pub import_rlp_path: Option<std::path::PathBuf>,
}
impl<I> Default for Setup<I> {
@@ -61,6 +63,7 @@ impl<I> Default for Setup<I> {
is_dev: true,
_phantom: Default::default(),
import_result_holder: None,
import_rlp_path: None,
}
}
}
@@ -174,6 +177,10 @@ where
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
// If import_rlp_path is set, use apply_with_import instead
if let Some(rlp_path) = self.import_rlp_path.take() {
return self.apply_with_import::<N>(env, &rlp_path).await;
}
let chain_spec =
self.chain_spec.clone().ok_or_else(|| eyre!("Chain specification is required"))?;