diff --git a/Cargo.lock b/Cargo.lock index 5cc898714d31e..48ceb56b0a535 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8734,7 +8734,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -16455,7 +16455,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand", - "rand_core 0.6.4", + "rand_core 0.5.1", "serde", "unicode-normalization", ] @@ -20720,7 +20720,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.13.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -20766,7 +20766,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", @@ -29084,9 +29084,9 @@ dependencies = [ [[package]] name = "subxt" -version = "0.38.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53029d133e4e0cb7933f1fe06f2c68804b956de9bb8fa930ffca44e9e5e4230" +checksum = "1c17d7ec2359d33133b63c97e28c8b7cd3f0a5bc6ce567ae3aef9d9e85be3433" dependencies = [ "async-trait", "derive-where", diff --git a/prdoc/pr_7325.prdoc b/prdoc/pr_7325.prdoc new file mode 100644 index 0000000000000..788f01cb32470 --- /dev/null +++ b/prdoc/pr_7325.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] eth-rpc minor fixes' +doc: +- audience: Runtime Dev + description: |- + - Add option to specify database_url from an environment variable + - Add a test-deployment.rs rust script that can be used to test deployment and call of a contract before releasing eth-rpc + - Make evm_block non fallible so that it can return an Ok response for older blocks when the runtime API is not available + - Update subxt version to integrate changes from https://github.com/paritytech/subxt/pull/1904 +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 9d822f5ff8e27..014231f7f3e55 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -17,34 +17,33 @@ path = "src/main.rs" name = "eth-indexer" path = "src/eth-indexer.rs" +[[bin]] +name = "eth-rpc-tester" +path = "src/eth-rpc-tester.rs" + [[example]] name = "deploy" path = "examples/rust/deploy.rs" -required-features = ["example"] [[example]] name = "transfer" path = "examples/rust/transfer.rs" -required-features = ["example"] [[example]] name = "rpc-playground" path = "examples/rust/rpc-playground.rs" -required-features = ["example"] [[example]] name = "extrinsic" path = "examples/rust/extrinsic.rs" -required-features = ["example"] [[example]] name = "remark-extrinsic" path = "examples/rust/remark-extrinsic.rs" -required-features = ["example"] [dependencies] anyhow = { workspace = true } -clap = { workspace = true, features = ["derive"] } +clap = { workspace = true, features = ["derive", "env"] } codec = { workspace = true, features = ["derive"] } ethabi = { version = "18.0.0" } futures = { workspace = true, features = ["thread-pool"] } @@ -52,8 +51,9 @@ hex = { workspace = true } jsonrpsee = { workspace = true, features = ["full"] } log = { workspace = true } pallet-revive = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -rlp = { workspace = true, optional = true } +rlp = { workspace = true } sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } @@ -62,24 +62,18 @@ sp-arithmetic = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } -sqlx = { version = "0.8.2", features = [ - "macros", - "runtime-tokio", - "sqlite", +sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "sqlite"] } +subxt = { workspace = true, default-features = true, features = [ + "reconnecting-rpc-client", ] } -subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } -subxt-signer = { workspace = true, optional = true, features = [ +subxt-signer = { workspace = true, features = [ "unstable-eth", ] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } -[features] -example = ["rlp", "subxt-signer"] - [dev-dependencies] env_logger = { workspace = true } -pallet-revive-fixtures = { workspace = true, default-features = true } static_init = { workspace = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/examples/README.md b/substrate/frame/revive/rpc/examples/README.md index b9a2756b381d2..1079c254b9c20 100644 --- a/substrate/frame/revive/rpc/examples/README.md +++ b/substrate/frame/revive/rpc/examples/README.md @@ -42,7 +42,7 @@ RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc -- --dev Run one of the examples from the `examples` directory to send a transaction to the node: ```bash -RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --features example --example deploy +RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --example deploy ``` ## JS examples diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index d63d596ab7a8b..b6c57d2c3b0bf 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -19,7 +19,7 @@ use crate::{ client::{connect, Client}, BlockInfoProvider, BlockInfoProviderImpl, CacheReceiptProvider, DBReceiptProvider, EthRpcServer, EthRpcServerImpl, ReceiptProvider, SystemHealthRpcServer, - SystemHealthRpcServerImpl, + SystemHealthRpcServerImpl, LOG_TARGET, }; use clap::Parser; use futures::{pin_mut, FutureExt}; @@ -52,7 +52,7 @@ pub struct CliCommand { /// The database used to store Ethereum transaction hashes. /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC /// queries for transactions that are not in the in memory cache. - #[clap(long)] + #[clap(long, env = "DATABASE_URL")] pub database_url: Option, /// If true, we will only read from the database and not write to it. @@ -148,6 +148,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { Arc::new(BlockInfoProviderImpl::new(cache_size, api.clone(), rpc.clone())); let receipt_provider: Arc = if let Some(database_url) = database_url.as_ref() { + log::info!(target: LOG_TARGET, "🔗 Connecting to provided database"); Arc::new(( CacheReceiptProvider::default(), DBReceiptProvider::new( @@ -158,6 +159,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { .await?, )) } else { + log::info!(target: LOG_TARGET, "🔌 No database provided, using in-memory cache"); Arc::new(CacheReceiptProvider::default()) }; diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 440972c7a681b..47e439f068513 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -646,9 +646,9 @@ impl Client { &self, block: Arc, hydrated_transactions: bool, - ) -> Result { + ) -> Block { let runtime_api = self.api.runtime_api().at(block.hash()); - let gas_limit = Self::block_gas_limit(&runtime_api).await?; + let gas_limit = Self::block_gas_limit(&runtime_api).await.unwrap_or_default(); let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -658,7 +658,7 @@ impl Client { let state_root = header.state_root.0.into(); let extrinsics_root = header.extrinsics_root.0.into(); - let receipts = extract_receipts_from_block(&block).await?; + let receipts = extract_receipts_from_block(&block).await.unwrap_or_default(); let gas_used = receipts.iter().fold(U256::zero(), |acc, (_, receipt)| acc + receipt.gas_used); let transactions = if hydrated_transactions { @@ -675,7 +675,7 @@ impl Client { .into() }; - Ok(Block { + Block { hash: block.hash(), parent_hash, state_root, @@ -689,7 +689,7 @@ impl Client { receipts_root: extrinsics_root, transactions, ..Default::default() - }) + } } /// Convert a weight to a fee. @@ -697,7 +697,6 @@ impl Client { runtime_api: &subxt::runtime_api::RuntimeApi>, ) -> Result { let payload = subxt_client::apis().revive_api().block_gas_limit(); - let gas_limit = runtime_api.call(payload).await?; Ok(*gas_limit) } diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs index 3e7f6b6fa91b8..894143be0a525 100644 --- a/substrate/frame/revive/rpc/src/eth-indexer.rs +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -37,7 +37,7 @@ pub struct CliCommand { pub oldest_block: Option, /// The database used to store Ethereum transaction hashes. - #[clap(long)] + #[clap(long, env = "DATABASE_URL")] pub database_url: String, #[allow(missing_docs)] diff --git a/substrate/frame/revive/rpc/src/eth-rpc-tester.rs b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs new file mode 100644 index 0000000000000..0ddad6874dfd5 --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use clap::Parser; +use jsonrpsee::http_client::HttpClientBuilder; +use pallet_revive::evm::{Account, BlockTag, ReceiptInfo}; +use pallet_revive_eth_rpc::{ + example::{wait_for_receipt, TransactionBuilder}, + EthRpcClient, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::{Child, ChildStderr, Command}, + signal::unix::{signal, SignalKind}, +}; + +const DOCKER_CONTAINER_NAME: &str = "eth-rpc-test"; + +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The parity docker image e.g eth-rpc:master-fb2e414f + #[clap(long, default_value = "eth-rpc:master-fb2e414f")] + docker_image: String, + + /// The docker binary + /// Either docker or podman + #[clap(long, default_value = "docker")] + docker_bin: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let CliCommand { docker_bin, docker_image, .. } = CliCommand::parse(); + + let mut docker_process = start_docker(&docker_bin, &docker_image)?; + let stderr = docker_process.stderr.take().unwrap(); + + tokio::select! { + result = docker_process.wait() => { + println!("docker failed: {result:?}"); + } + _ = interrupt() => { + kill_docker().await?; + } + _ = test_eth_rpc(stderr) => { + kill_docker().await?; + } + } + + Ok(()) +} + +async fn interrupt() { + let mut sigint = signal(SignalKind::interrupt()).expect("failed to listen for SIGINT"); + let mut sigterm = signal(SignalKind::terminate()).expect("failed to listen for SIGTERM"); + + tokio::select! { + _ = sigint.recv() => {}, + _ = sigterm.recv() => {}, + } +} + +fn start_docker(docker_bin: &str, docker_image: &str) -> anyhow::Result { + let docker_process = Command::new(docker_bin) + .args([ + "run", + "--name", + DOCKER_CONTAINER_NAME, + "--rm", + "-p", + "8545:8545", + &format!("docker.io/paritypr/{docker_image}"), + "--node-rpc-url", + "wss://westend-asset-hub-rpc.polkadot.io", + "--rpc-cors", + "all", + "--unsafe-rpc-external", + "--log=sc_rpc_server:info", + ]) + .stderr(std::process::Stdio::piped()) + .kill_on_drop(true) + .spawn()?; + + Ok(docker_process) +} + +async fn kill_docker() -> anyhow::Result<()> { + Command::new("docker").args(["kill", DOCKER_CONTAINER_NAME]).output().await?; + Ok(()) +} + +async fn test_eth_rpc(stderr: ChildStderr) -> anyhow::Result<()> { + let mut reader = BufReader::new(stderr).lines(); + while let Some(line) = reader.next_line().await? { + println!("{line}"); + if line.contains("Running JSON-RPC server") { + break; + } + } + + let account = Account::default(); + let data = vec![]; + let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; + let input = bytes.into_iter().chain(data).collect::>(); + + println!("Account:"); + println!("- address: {:?}", account.address()); + let client = HttpClientBuilder::default().build("http://localhost:8545")?; + + let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; + let balance = client.get_balance(account.address(), BlockTag::Latest.into()).await?; + println!("- nonce: {nonce:?}"); + println!("- balance: {balance:?}"); + + println!("\n\n=== Deploying dummy contract ===\n\n"); + let hash = TransactionBuilder::default().input(input).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + let ReceiptInfo { block_number, gas_used, contract_address, .. } = + wait_for_receipt(&client, hash).await?; + + let contract_address = contract_address.unwrap(); + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- Address: {contract_address:?}"); + + println!("\n\n=== Calling dummy contract ===\n\n"); + let hash = TransactionBuilder::default().to(contract_address).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + + let ReceiptInfo { block_number, gas_used, to, .. } = wait_for_receipt(&client, hash).await?; + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- To: {to:?}"); + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 3b9a33296ef4d..aad5b4fbc344d 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -15,8 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Example utilities -#![cfg(any(feature = "example", test))] - use crate::{EthRpcClient, ReceiptInfo}; use anyhow::Context; use pallet_revive::evm::{ diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 5e1341e2a29ab..fcf93fa6c0d2e 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -214,7 +214,7 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } @@ -254,7 +254,7 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_number_or_tag(&block).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) }