Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pallet-revive] eth-rpc minor fixes #7325

Merged
merged 16 commits into from
Jan 24, 2025
7 changes: 6 additions & 1 deletion substrate/frame/revive/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ name = "deploy"
path = "examples/rust/deploy.rs"
required-features = ["example"]

[[example]]
name = "test-deployment"
path = "examples/rust/test-deployment.rs"
required-features = ["example"]

[[example]]
name = "transfer"
path = "examples/rust/transfer.rs"
Expand All @@ -44,7 +49,7 @@ 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"] }
Expand Down
156 changes: 156 additions & 0 deletions substrate/frame/revive/rpc/examples/rust/test-deployment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// 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<Child> {
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::<Vec<u8>>();

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!("\nWaiting for receipt...");
let ReceiptInfo { block_number, gas_used, contract_address, .. } =
wait_for_receipt(&client, hash).await?;

let contract_address = contract_address.unwrap();
println!("Receipt:");
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!("\nWaiting for receipt...");

let ReceiptInfo { block_number, gas_used, to, .. } = wait_for_receipt(&client, hash).await?;
println!("Receipt:");
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(())
}
6 changes: 4 additions & 2 deletions substrate/frame/revive/rpc/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<String>,

/// If true, we will only read from the database and not write to it.
Expand Down Expand Up @@ -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<dyn ReceiptProvider> =
if let Some(database_url) = database_url.as_ref() {
log::info!(target: LOG_TARGET, "🔗 Connecting to provided database");
Arc::new((
CacheReceiptProvider::default(),
DBReceiptProvider::new(
Expand All @@ -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())
};

Expand Down
11 changes: 5 additions & 6 deletions substrate/frame/revive/rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,9 +646,9 @@ impl Client {
&self,
block: Arc<SubstrateBlock>,
hydrated_transactions: bool,
) -> Result<Block, ClientError> {
) -> 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();
Expand All @@ -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 {
Expand All @@ -675,7 +675,7 @@ impl Client {
.into()
};

Ok(Block {
Block {
hash: block.hash(),
parent_hash,
state_root,
Expand All @@ -689,15 +689,14 @@ impl Client {
receipts_root: extrinsics_root,
transactions,
..Default::default()
})
}
}

/// Convert a weight to a fee.
async fn block_gas_limit(
runtime_api: &subxt::runtime_api::RuntimeApi<SrcChainConfig, OnlineClient<SrcChainConfig>>,
) -> Result<U256, ClientError> {
let payload = subxt_client::apis().revive_api().block_gas_limit();

let gas_limit = runtime_api.call(payload).await?;
Ok(*gas_limit)
}
Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/revive/rpc/src/eth-indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub struct CliCommand {
pub oldest_block: Option<SubstrateBlockNumber>,

/// The database used to store Ethereum transaction hashes.
#[clap(long)]
#[clap(long, env = "DATABASE_URL")]
pub database_url: String,

#[allow(missing_docs)]
Expand Down
7 changes: 5 additions & 2 deletions substrate/frame/revive/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down Expand Up @@ -251,10 +251,13 @@ impl EthRpcServer for EthRpcServerImpl {
block: BlockNumberOrTag,
hydrated_transactions: bool,
) -> RpcResult<Option<Block>> {
log::debug!(target: LOG_TARGET, "get_block_by_number({block:?})");
let Some(block) = self.client.block_by_number_or_tag(&block).await? else {
log::debug!(target: LOG_TARGET, "block {block:?} not found");
return Ok(None);
};
let block = self.client.evm_block(block, hydrated_transactions).await?;
log::debug!(target: LOG_TARGET, "Building evm block");
let block = self.client.evm_block(block, hydrated_transactions).await;
Ok(Some(block))
}

Expand Down
Loading