diff --git a/Cargo.lock b/Cargo.lock index 3c875ecbf12c..fccef4a55126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12552,6 +12552,7 @@ name = "pallet-revive-eth-rpc" version = "0.1.0" dependencies = [ "anyhow", + "assert_cmd", "clap 4.5.13", "env_logger 0.11.3", "futures", @@ -12572,6 +12573,7 @@ dependencies = [ "sp-crypto-hashing 0.1.0", "sp-runtime 31.0.1", "sp-weights 27.0.0", + "substrate-cli-test-utils", "subxt", "subxt-signer", "thiserror", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 4c9ec4c963f1..aa20ff8613aa 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -77,5 +77,8 @@ dev = [] example = ["hex", "hex-literal", "rlp", "secp256k1", "subxt-signer"] [dev-dependencies] +pallet-revive = { workspace = true, default-features = true, features = ["riscv"] } hex-literal = { workspace = true } pallet-revive-fixtures = { workspace = true } +substrate-cli-test-utils = { workspace = true } +assert_cmd = { workspace = true } diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index cbd022fa3b73..b01a918d23ad 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -15,18 +15,20 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Example utilities -#![cfg(feature = "example")] +#![cfg(any(feature = "example", test))] use crate::{EthRpcClient, ReceiptInfo}; use anyhow::Context; -use jsonrpsee::http_client::HttpClient; use pallet_revive::evm::{ rlp::*, Account, BlockTag, Bytes, GenericTransaction, TransactionLegacyUnsigned, H160, H256, U256, }; /// Wait for a transaction receipt. -pub async fn wait_for_receipt(client: &HttpClient, hash: H256) -> anyhow::Result { +pub async fn wait_for_receipt( + client: &(impl EthRpcClient + Send + Sync), + hash: H256, +) -> anyhow::Result { for _ in 0..6 { tokio::time::sleep(std::time::Duration::from_secs(2)).await; let receipt = client.get_transaction_receipt(hash).await?; @@ -41,7 +43,7 @@ pub async fn wait_for_receipt(client: &HttpClient, hash: H256) -> anyhow::Result /// Send a transaction. pub async fn send_transaction( signer: &Account, - client: &HttpClient, + client: &(impl EthRpcClient + Send + Sync), value: U256, input: Bytes, to: Option, diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 2b14df43eb0f..7605bedf9926 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -31,6 +31,9 @@ pub mod client; pub mod example; pub mod subxt_client; +#[cfg(test)] +mod tests; + mod rpc_methods_gen; pub use rpc_methods_gen::*; diff --git a/substrate/frame/revive/rpc/src/main.rs b/substrate/frame/revive/rpc/src/main.rs index 5dbaeba899b2..e966e199ad70 100644 --- a/substrate/frame/revive/rpc/src/main.rs +++ b/substrate/frame/revive/rpc/src/main.rs @@ -34,12 +34,12 @@ use tracing_subscriber::{util::SubscriberInitExt, EnvFilter, FmtSubscriber}; #[clap(author, about, version)] struct CliCommand { /// The server address to bind to - #[clap(long, default_value = "127.0.0.1:9090")] - url: String, + #[clap(long, default_value = "9090")] + rpc_port: String, /// The node url to connect to #[clap(long, default_value = "ws://127.0.0.1:9944")] - node_url: String, + node_rpc_url: String, } /// Initialize tracing @@ -56,20 +56,20 @@ fn init_tracing() { #[tokio::main] async fn main() -> anyhow::Result<()> { - let CliCommand { url, node_url } = CliCommand::parse(); + let CliCommand { rpc_port, node_rpc_url } = CliCommand::parse(); init_tracing(); - let client = Client::from_url(&node_url).await?; + let client = Client::from_url(&node_rpc_url).await?; let mut updates = client.updates.clone(); - let server_addr = run_server(client, &url).await?; - log::info!(target: LOG_TARGET, "Server started on: {}", server_addr); + let server_addr = run_server(client, &format!("127.0.0.1:{rpc_port}")).await?; + log::info!("Running JSON-RPC server: addr={server_addr},"); let url = format!("http://{}", server_addr); let client = HttpClientBuilder::default().build(url)?; let response = client.block_number().await?; - log::info!(target: LOG_TARGET, "client initialized with block number {:?}", response); + log::info!(target: LOG_TARGET, "Client initialized with block number {:?}", response); // keep running server until ctrl-c or client subscription fails let _ = updates.wait_for(|_| false).await; @@ -99,7 +99,7 @@ mod dev { let params = req.params.clone().unwrap_or_default(); async move { - log::info!(target: LOG_TARGET, "method: {method} params: {params}"); + log::info!(target: LOG_TARGET, "Method: {method} params: {params}"); let resp = service.call(req).await; if resp.is_success() { log::info!(target: LOG_TARGET, "✅ rpc: {method}"); diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs new file mode 100644 index 000000000000..1757d477a9e5 --- /dev/null +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -0,0 +1,79 @@ +use crate::{ + example::{send_transaction, wait_for_receipt}, + EthRpcClient, +}; +use assert_cmd::cargo::cargo_bin; +use jsonrpsee::ws_client::WsClientBuilder; +use pallet_revive::{ + create1, + evm::{Account, BlockTag, Bytes, U256}, +}; +use std::{ + io::{BufRead, BufReader}, + process::{self, Child, Command}, +}; +use substrate_cli_test_utils::*; + +/// Start eth-rpc server, and return the child process and the WebSocket URL. +fn start_eth_rpc_server(node_ws_url: &str) -> (Child, String) { + let mut child = Command::new(cargo_bin("eth-rpc")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .env("RUST_LOG", "info,eth-rpc=debug") + .args(&["--rpc-port=45788", &format!("--node-rpc-url={node_ws_url}")]) + .spawn() + .unwrap(); + + let mut data = String::new(); + let ws_url = BufReader::new(child.stdout.take().unwrap()) + .lines() + .find_map(|line| { + let line = line.expect("failed to obtain next line while extracting node info"); + data.push_str(&line); + data.push_str("\n"); + + // does the line contain our port (we expect this specific output from eth-rpc). + let sock_addr = match line.split_once("Running JSON-RPC server: addr=") { + None => return None, + Some((_, after)) => after.split_once(",").unwrap().0, + }; + + Some(format!("ws://{}", sock_addr)) + }) + .unwrap_or_else(|| { + eprintln!("Observed eth-rpc output:\n{}", data); + panic!("We should get a WebSocket address") + }); + + (child, ws_url) +} + +#[tokio::test] +async fn test_jsonrpsee_server() -> anyhow::Result<()> { + let mut node_child = substrate_cli_test_utils::start_node(); + let (info, _) = extract_info_from_output(node_child.stderr.take().unwrap()); + let (_rpc_child, ws_url) = start_eth_rpc_server(&info.ws_url); + + let data = b"hello world".to_vec(); + let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; + let input = bytes.into_iter().chain(data.clone()).collect::>(); + + let account = Account::default(); + let client = WsClientBuilder::default().build(ws_url).await?; + + // Deploy contract + let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; + let hash = send_transaction(&account, &client, U256::zero(), input.into(), None).await?; + let receipt = wait_for_receipt(&client, hash).await?; + let contract_address = create1(&account.address(), nonce.try_into().unwrap()); + assert_eq!(contract_address, receipt.contract_address.unwrap()); + + // Call contract + let hash = + send_transaction(&account, &client, U256::zero(), Bytes::default(), Some(contract_address)) + .await?; + let receipt = wait_for_receipt(&client, hash).await?; + assert_eq!(contract_address, receipt.to.unwrap()); + + Ok(()) +}