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
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions prdoc/pr_7325.prdoc
Original file line number Diff line number Diff line change
@@ -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
28 changes: 11 additions & 17 deletions substrate/frame/revive/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,43 @@ 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"] }
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 }
Expand All @@ -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"] }
2 changes: 1 addition & 1 deletion substrate/frame/revive/rpc/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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
157 changes: 157 additions & 0 deletions substrate/frame/revive/rpc/src/eth-rpc-tester.rs
Original file line number Diff line number Diff line change
@@ -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<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!("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(())
}
Loading
Loading