Skip to content

Commit

Permalink
[pallet-revive] eth-rpc minor fixes (#7325)
Browse files Browse the repository at this point in the history
- Add option to specify database_url using DATABASE_URL environment
variable
- Add a eth-rpc-tester rust bin that can be used to test deployment
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 cargo.lock to integrate changes from
paritytech/subxt#1904

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
pgherveou and github-actions[bot] authored Jan 24, 2025
1 parent ccd6337 commit 223bd28
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 37 deletions.
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

0 comments on commit 223bd28

Please sign in to comment.