From f0f59ad024d89a962d3794bdc19760e7ff6fef87 Mon Sep 17 00:00:00 2001 From: Omar Abdulla Date: Wed, 17 Sep 2025 20:23:31 +0300 Subject: [PATCH] Provide a common node implementation for substrate chains --- crates/core/src/lib.rs | 4 +- crates/node/src/lib.rs | 3 +- crates/node/src/pool.rs | 5 +- .../node/src/{kitchensink.rs => substrate.rs} | 221 ++++++++---------- 4 files changed, 107 insertions(+), 126 deletions(-) rename crates/node/src/{kitchensink.rs => substrate.rs} (89%) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 4e222fb..915a461 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -20,7 +20,7 @@ use revive_dt_format::traits::ResolverApi; use revive_dt_node::{ Node, geth::{self, GethNode}, - kitchensink::KitchensinkNode, + substrate::SubstrateNode, }; use revive_dt_node_interaction::EthereumNode; use tracing::info; @@ -54,7 +54,7 @@ impl Platform for Geth { pub struct Kitchensink; impl Platform for Kitchensink { - type Blockchain = KitchensinkNode; + type Blockchain = SubstrateNode; type Compiler = revive_resolc::Resolc; fn config_id() -> &'static TestingPlatform { diff --git a/crates/node/src/lib.rs b/crates/node/src/lib.rs index c729e40..9a69b5b 100644 --- a/crates/node/src/lib.rs +++ b/crates/node/src/lib.rs @@ -2,14 +2,13 @@ use alloy::genesis::Genesis; use revive_common::EVMVersion; -use revive_dt_config::*; use revive_dt_node_interaction::EthereumNode; pub mod common; pub mod constants; pub mod geth; -pub mod kitchensink; pub mod pool; +pub mod substrate; /// An abstract interface for testing nodes. pub trait Node: EthereumNode { diff --git a/crates/node/src/pool.rs b/crates/node/src/pool.rs index e3b93d4..f218e2a 100644 --- a/crates/node/src/pool.rs +++ b/crates/node/src/pool.rs @@ -12,7 +12,6 @@ use revive_dt_config::{ KitchensinkConfiguration, ReviveDevNodeConfiguration, WalletConfiguration, WorkingDirectoryConfiguration, }; -use tracing::info; use crate::Node; @@ -81,7 +80,7 @@ where } fn spawn_node( - context: impl AsRef + _: impl AsRef + AsRef + AsRef + AsRef @@ -91,7 +90,7 @@ fn spawn_node( + AsRef + Clone + 'static, - genesis: Genesis, + _: Genesis, ) -> anyhow::Result { todo!("Remove"); } diff --git a/crates/node/src/kitchensink.rs b/crates/node/src/substrate.rs similarity index 89% rename from crates/node/src/kitchensink.rs rename to crates/node/src/substrate.rs index f6fe27e..04b3fa1 100644 --- a/crates/node/src/kitchensink.rs +++ b/crates/node/src/substrate.rs @@ -51,12 +51,15 @@ use crate::{Node, common::FallbackGasFiller, constants::INITIAL_BALANCE}; static NODE_COUNT: AtomicU32 = AtomicU32::new(0); +/// A node implementation for Substrate based chains. Currently, this supports either kitchensink +/// or the revive-dev-node which is done by changing the path and some of the other arguments passed +/// to the command. #[derive(Debug)] -pub struct KitchensinkNode { +pub struct SubstrateNode { id: u32, - substrate_binary: PathBuf, - dev_node_binary: PathBuf, + node_binary: PathBuf, eth_proxy_binary: PathBuf, + export_chainspec_command: String, rpc_url: String, base_directory: PathBuf, logs_directory: PathBuf, @@ -65,16 +68,11 @@ pub struct KitchensinkNode { wallet: Arc, nonce_manager: CachedNonceManager, chain_id_filler: ChainIdFiller, - use_kitchensink_not_dev_node: bool, - /// This vector stores [`File`] objects that we use for logging which we want to flush when the - /// node object is dropped. We do not store them in a structured fashion at the moment (in - /// separate fields) as the logic that we need to apply to them is all the same regardless of - /// what it belongs to, we just want to flush them on [`Drop`] of the node. logs_file_to_flush: Vec, } -impl KitchensinkNode { - const BASE_DIRECTORY: &str = "kitchensink"; +impl SubstrateNode { + const BASE_DIRECTORY: &str = "Substrate"; const LOGS_DIRECTORY: &str = "logs"; const DATA_DIRECTORY: &str = "chains"; @@ -87,44 +85,39 @@ impl KitchensinkNode { const SUBSTRATE_LOG_ENV: &str = "error,evm=debug,sc_rpc_server=info,runtime::revive=debug"; const PROXY_LOG_ENV: &str = "info,eth-rpc=debug"; - const KITCHENSINK_STDOUT_LOG_FILE_NAME: &str = "node_stdout.log"; - const KITCHENSINK_STDERR_LOG_FILE_NAME: &str = "node_stderr.log"; + const SUBSTRATE_STDOUT_LOG_FILE_NAME: &str = "node_stdout.log"; + const SUBSTRATE_STDERR_LOG_FILE_NAME: &str = "node_stderr.log"; const PROXY_STDOUT_LOG_FILE_NAME: &str = "proxy_stdout.log"; const PROXY_STDERR_LOG_FILE_NAME: &str = "proxy_stderr.log"; + pub const KITCHENSINK_EXPORT_CHAINSPEC_COMMAND: &str = "export-chain-spec"; + pub const REVIVE_DEV_NODE_EXPORT_CHAINSPEC_COMMAND: &str = "build-spec"; + pub fn new( + node_path: PathBuf, + export_chainspec_command: &str, context: impl AsRef - + AsRef - + AsRef - + AsRef - + AsRef - + AsRef - + AsRef + AsRef - + Clone, + + AsRef, ) -> Self { - let kitchensink_configuration = AsRef::::as_ref(&context); - let dev_node_configuration = AsRef::::as_ref(&context); - let eth_rpc_configuration = AsRef::::as_ref(&context); - let working_directory_configuration = - AsRef::::as_ref(&context); - let wallet_configuration = AsRef::::as_ref(&context); + let working_directory_path = + AsRef::::as_ref(&context).as_path(); + let eth_rpc_path = AsRef::::as_ref(&context) + .path + .as_path(); + let wallet = AsRef::::as_ref(&context).wallet(); - let kitchensink_directory = working_directory_configuration - .as_path() - .join(Self::BASE_DIRECTORY); + let substrate_directory = working_directory_path.join(Self::BASE_DIRECTORY); let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst); - let base_directory = kitchensink_directory.join(id.to_string()); + let base_directory = substrate_directory.join(id.to_string()); let logs_directory = base_directory.join(Self::LOGS_DIRECTORY); - let wallet = wallet_configuration.wallet(); - Self { id, - substrate_binary: kitchensink_configuration.path.clone(), - dev_node_binary: dev_node_configuration.path.clone(), - eth_proxy_binary: eth_rpc_configuration.path.clone(), + node_binary: node_path, + eth_proxy_binary: eth_rpc_path.to_path_buf(), + export_chainspec_command: export_chainspec_command.to_string(), rpc_url: String::new(), base_directory, logs_directory, @@ -133,9 +126,6 @@ impl KitchensinkNode { wallet: wallet.clone(), chain_id_filler: Default::default(), nonce_manager: Default::default(), - use_kitchensink_not_dev_node: kitchensink_configuration.use_kitchensink, - // We know that we only need to be storing 4 files so we can specify that when creating - // the vector. It's the stdout and stderr of the substrate-node and the eth-rpc. logs_file_to_flush: Vec::with_capacity(4), } } @@ -153,33 +143,24 @@ impl KitchensinkNode { // Note: we do not pipe the logs of this process to a separate file since this is just a // once-off export of the default chain spec and not part of the long-running node process. - let output = if self.use_kitchensink_not_dev_node { - Command::new(&self.substrate_binary) - .arg("export-chain-spec") - .arg("--chain") - .arg("dev") - .output() - .context("Failed to export the chain-spec")? - } else { - Command::new(&self.dev_node_binary) - .arg("build-spec") - .arg("--chain") - .arg("dev") - .output() - .context("Failed to export the chain-spec")? - }; + let output = Command::new(&self.node_binary) + .arg(self.export_chainspec_command.as_str()) + .arg("--chain") + .arg("dev") + .output() + .context("Failed to export the chain-spec")?; if !output.status.success() { anyhow::bail!( - "substrate-node export-chain-spec failed: {}", + "Substrate-node export-chain-spec failed: {}", String::from_utf8_lossy(&output.stderr) ); } let content = String::from_utf8(output.stdout) - .context("Failed to decode substrate export-chain-spec output as UTF-8")?; + .context("Failed to decode Substrate export-chain-spec output as UTF-8")?; let mut chainspec_json: JsonValue = - serde_json::from_str(&content).context("Failed to parse substrate chain spec JSON")?; + serde_json::from_str(&content).context("Failed to parse Substrate chain spec JSON")?; let existing_chainspec_balances = chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] @@ -250,17 +231,13 @@ impl KitchensinkNode { // Start Substrate node let kitchensink_stdout_logs_file = open_options .clone() - .open(self.kitchensink_stdout_log_file_path()) + .open(self.substrate_stdout_log_file_path()) .context("Failed to open kitchensink stdout logs file")?; let kitchensink_stderr_logs_file = open_options .clone() - .open(self.kitchensink_stderr_log_file_path()) + .open(self.substrate_stderr_log_file_path()) .context("Failed to open kitchensink stderr logs file")?; - let node_binary_path = if self.use_kitchensink_not_dev_node { - self.substrate_binary.as_path() - } else { - self.dev_node_binary.as_path() - }; + let node_binary_path = self.node_binary.as_path(); self.process_substrate = Command::new(node_binary_path) .arg("--dev") .arg("--chain") @@ -290,17 +267,17 @@ impl KitchensinkNode { .context("Failed to clone kitchensink stderr log file handle")?, ) .spawn() - .context("Failed to spawn substrate node process")? + .context("Failed to spawn Substrate node process")? .into(); // Give the node a moment to boot if let Err(error) = Self::wait_ready( - self.kitchensink_stderr_log_file_path().as_path(), + self.substrate_stderr_log_file_path().as_path(), Self::SUBSTRATE_READY_MARKER, Duration::from_secs(60), ) { self.shutdown() - .context("Failed to gracefully shutdown after substrate start error")?; + .context("Failed to gracefully shutdown after Substrate start error")?; return Err(error); }; @@ -414,14 +391,14 @@ impl KitchensinkNode { Ok(String::from_utf8_lossy(&output).trim().to_string()) } - fn kitchensink_stdout_log_file_path(&self) -> PathBuf { + fn substrate_stdout_log_file_path(&self) -> PathBuf { self.logs_directory - .join(Self::KITCHENSINK_STDOUT_LOG_FILE_NAME) + .join(Self::SUBSTRATE_STDOUT_LOG_FILE_NAME) } - fn kitchensink_stderr_log_file_path(&self) -> PathBuf { + fn substrate_stderr_log_file_path(&self) -> PathBuf { self.logs_directory - .join(Self::KITCHENSINK_STDERR_LOG_FILE_NAME) + .join(Self::SUBSTRATE_STDERR_LOG_FILE_NAME) } fn proxy_stdout_log_file_path(&self) -> PathBuf { @@ -435,15 +412,11 @@ impl KitchensinkNode { async fn provider( &self, ) -> anyhow::Result< - FillProvider< - impl TxFiller, - impl Provider, - KitchensinkNetwork, - >, + FillProvider, impl Provider, ReviveNetwork>, > { ProviderBuilder::new() .disable_recommended_fillers() - .network::() + .network::() .filler(FallbackGasFiller::new( 25_000_000, 1_000_000_000, @@ -458,7 +431,7 @@ impl KitchensinkNode { } } -impl EthereumNode for KitchensinkNode { +impl EthereumNode for SubstrateNode { fn execute_transaction( &self, transaction: alloy::rpc::types::TransactionRequest, @@ -551,19 +524,18 @@ impl EthereumNode for KitchensinkNode { Box::pin(async move { let id = self.id; let provider = self.provider().await?; - Ok(Box::new(KitchensinkNodeResolver { id, provider }) as Box) + Ok(Box::new(SubstrateNodeResolver { id, provider }) as Box) }) } } -pub struct KitchensinkNodeResolver, P: Provider> -{ +pub struct SubstrateNodeResolver, P: Provider> { id: u32, - provider: FillProvider, + provider: FillProvider, } -impl, P: Provider> ResolverApi - for KitchensinkNodeResolver +impl, P: Provider> ResolverApi + for SubstrateNodeResolver { #[instrument(level = "info", skip_all, fields(kitchensink_node_id = self.id))] fn chain_id( @@ -688,7 +660,7 @@ impl, P: Provider> ResolverA } // TODO: Remove -impl ResolverApi for KitchensinkNode { +impl ResolverApi for SubstrateNode { fn chain_id( &self, ) -> Pin> + '_>> { @@ -830,7 +802,7 @@ impl ResolverApi for KitchensinkNode { } } -impl Node for KitchensinkNode { +impl Node for SubstrateNode { fn id(&self) -> usize { self.id as _ } @@ -848,7 +820,7 @@ impl Node for KitchensinkNode { } if let Some(mut child) = self.process_substrate.take() { child.kill().map_err(|error| { - anyhow::anyhow!("Failed to kill the substrate process: {error:?}") + anyhow::anyhow!("Failed to kill the Substrate process: {error:?}") })?; } @@ -870,7 +842,7 @@ impl Node for KitchensinkNode { } fn version(&self) -> anyhow::Result { - let output = Command::new(&self.substrate_binary) + let output = Command::new(&self.node_binary) .arg("--version") .stdin(Stdio::null()) .stdout(Stdio::piped()) @@ -895,16 +867,16 @@ impl Node for KitchensinkNode { } } -impl Drop for KitchensinkNode { +impl Drop for SubstrateNode { fn drop(&mut self) { self.shutdown().expect("Failed to shutdown") } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct KitchensinkNetwork; +pub struct ReviveNetwork; -impl Network for KitchensinkNetwork { +impl Network for ReviveNetwork { type TxType = ::TxType; type TxEnvelope = ::TxEnvelope; @@ -913,7 +885,7 @@ impl Network for KitchensinkNetwork { type ReceiptEnvelope = ::ReceiptEnvelope; - type Header = KitchensinkHeader; + type Header = ReviveHeader; type TransactionRequest = ::TransactionRequest; @@ -921,12 +893,12 @@ impl Network for KitchensinkNetwork { type ReceiptResponse = ::ReceiptResponse; - type HeaderResponse = Header; + type HeaderResponse = Header; - type BlockResponse = Block, Header>; + type BlockResponse = Block, Header>; } -impl TransactionBuilder for ::TransactionRequest { +impl TransactionBuilder for ::TransactionRequest { fn chain_id(&self) -> Option { <::TransactionRequest as TransactionBuilder>::chain_id(self) } @@ -1058,7 +1030,7 @@ impl TransactionBuilder for ::Transacti fn complete_type( &self, - ty: ::TxType, + ty: ::TxType, ) -> Result<(), Vec<&'static str>> { <::TransactionRequest as TransactionBuilder>::complete_type( self, ty, @@ -1075,13 +1047,13 @@ impl TransactionBuilder for ::Transacti <::TransactionRequest as TransactionBuilder>::can_build(self) } - fn output_tx_type(&self) -> ::TxType { + fn output_tx_type(&self) -> ::TxType { <::TransactionRequest as TransactionBuilder>::output_tx_type( self, ) } - fn output_tx_type_checked(&self) -> Option<::TxType> { + fn output_tx_type_checked(&self) -> Option<::TxType> { <::TransactionRequest as TransactionBuilder>::output_tx_type_checked( self, ) @@ -1095,15 +1067,14 @@ impl TransactionBuilder for ::Transacti fn build_unsigned( self, - ) -> alloy::network::BuildResult<::UnsignedTx, KitchensinkNetwork> - { + ) -> alloy::network::BuildResult<::UnsignedTx, ReviveNetwork> { let result = <::TransactionRequest as TransactionBuilder>::build_unsigned( self, ); match result { Ok(unsigned_tx) => Ok(unsigned_tx), Err(UnbuiltTransactionError { request, error }) => { - Err(UnbuiltTransactionError:: { + Err(UnbuiltTransactionError:: { request, error: match error { TransactionBuilderError::InvalidTransactionRequest(tx_type, items) => { @@ -1124,20 +1095,18 @@ impl TransactionBuilder for ::Transacti } } - async fn build>( + async fn build>( self, wallet: &W, - ) -> Result< - ::TxEnvelope, - TransactionBuilderError, - > { + ) -> Result<::TxEnvelope, TransactionBuilderError> + { Ok(wallet.sign_request(self).await?) } } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct KitchensinkHeader { +pub struct ReviveHeader { /// The Keccak 256-bit hash of the parent /// block’s header, in its entirety; formally Hp. pub parent_hash: B256, @@ -1240,7 +1209,7 @@ pub struct KitchensinkHeader { pub requests_hash: Option, } -impl BlockHeader for KitchensinkHeader { +impl BlockHeader for ReviveHeader { fn parent_hash(&self) -> B256 { self.parent_hash } @@ -1344,7 +1313,7 @@ mod tests { context } - fn new_node() -> (ExecutionContext, KitchensinkNode) { + fn new_node() -> (ExecutionContext, SubstrateNode) { // Note: When we run the tests in the CI we found that if they're all // run in parallel then the CI is unable to start all of the nodes in // time and their start up times-out. Therefore, we want all of the @@ -1364,7 +1333,11 @@ mod tests { let _guard = NODE_START_MUTEX.lock().unwrap(); let context = test_config(); - let mut node = KitchensinkNode::new(&context); + let mut node = SubstrateNode::new( + context.kitchensink_configuration.path.clone(), + SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, + &context, + ); node.init(context.genesis_configuration.genesis().unwrap().clone()) .expect("Failed to initialize the node") .spawn_process() @@ -1373,8 +1346,8 @@ mod tests { } /// A shared node that multiple tests can use. It starts up once. - fn shared_node() -> &'static KitchensinkNode { - static NODE: LazyLock<(ExecutionContext, KitchensinkNode)> = LazyLock::new(|| { + fn shared_node() -> &'static SubstrateNode { + static NODE: LazyLock<(ExecutionContext, SubstrateNode)> = LazyLock::new(|| { let (context, node) = new_node(); (context, node) }); @@ -1423,7 +1396,12 @@ mod tests { } "#; - let mut dummy_node = KitchensinkNode::new(&test_config()); + let context = test_config(); + let mut dummy_node = SubstrateNode::new( + context.kitchensink_configuration.path.clone(), + SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, + &context, + ); // Call `init()` dummy_node @@ -1433,16 +1411,16 @@ mod tests { // Check that the patched chainspec file was generated let final_chainspec_path = dummy_node .base_directory - .join(KitchensinkNode::CHAIN_SPEC_JSON_FILE); + .join(SubstrateNode::CHAIN_SPEC_JSON_FILE); assert!(final_chainspec_path.exists(), "Chainspec file should exist"); let contents = fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec"); // Validate that the Substrate addresses derived from the Ethereum addresses are in the file - let first_eth_addr = KitchensinkNode::eth_to_substrate_address( + let first_eth_addr = SubstrateNode::eth_to_substrate_address( &"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1".parse().unwrap(), ); - let second_eth_addr = KitchensinkNode::eth_to_substrate_address( + let second_eth_addr = SubstrateNode::eth_to_substrate_address( &"Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2".parse().unwrap(), ); @@ -1469,7 +1447,12 @@ mod tests { } "#; - let node = KitchensinkNode::new(&test_config()); + let context = test_config(); + let node = SubstrateNode::new( + context.kitchensink_configuration.path.clone(), + SubstrateNode::KITCHENSINK_EXPORT_CHAINSPEC_COMMAND, + &context, + ); let result = node .extract_balance_from_genesis_file(&serde_json::from_str(genesis_json).unwrap()) @@ -1502,7 +1485,7 @@ mod tests { ]; for eth_addr in eth_addresses { - let ss58 = KitchensinkNode::eth_to_substrate_address(ð_addr.parse().unwrap()); + let ss58 = SubstrateNode::eth_to_substrate_address(ð_addr.parse().unwrap()); println!("Ethereum: {eth_addr} -> Substrate SS58: {ss58}"); } @@ -1530,7 +1513,7 @@ mod tests { ]; for (eth_addr, expected_ss58) in cases { - let result = KitchensinkNode::eth_to_substrate_address(ð_addr.parse().unwrap()); + let result = SubstrateNode::eth_to_substrate_address(ð_addr.parse().unwrap()); assert_eq!( result, expected_ss58, "Mismatch for Ethereum address {eth_addr}" @@ -1545,8 +1528,8 @@ mod tests { let version = node.version().unwrap(); assert!( - version.starts_with("substrate-node"), - "Expected substrate-node version string, got: {version}" + version.starts_with("Substrate-node"), + "Expected Substrate-node version string, got: {version}" ); }