Skip to content

Commit a1bec62

Browse files
pgherveouactions-user
authored andcommitted
[pallet-revive] eth-rpc fixes (paritytech#6453)
- Breaking down the integration-test into multiple tests - Fix tx hash to use expected keccak-256 - Add option to ethers.js example to connect to westend and use a private key --------- Co-authored-by: GitHub Action <action@github.com>
1 parent 36484a5 commit a1bec62

8 files changed

Lines changed: 150 additions & 47 deletions

File tree

.config/nextest.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,10 @@ serial-integration = { max-threads = 1 }
124124
[[profile.default.overrides]]
125125
filter = 'test(/(^ui$|_ui|ui_)/)'
126126
test-group = 'serial-integration'
127+
128+
# Running eth-rpc tests sequentially
129+
# These tests rely on a shared resource (the RPC and Node)
130+
# and would cause race conditions due to transaction nonces if run in parallel.
131+
[[profile.default.overrides]]
132+
filter = 'package(pallet-revive-eth-rpc) and test(/^tests::/)'
133+
test-group = 'serial-integration'

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

prdoc/pr_6453.prdoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
title: '[pallet-revive] breakdown integration tests'
2+
doc:
3+
- audience: Runtime Dev
4+
description: Break down the single integration tests into multiple tests, use keccak-256 for tx.hash
5+
crates:
6+
- name: pallet-revive-eth-rpc
7+
bump: minor

substrate/frame/revive/rpc/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ ethabi = { version = "18.0.0" }
7474
example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"]
7575

7676
[dev-dependencies]
77+
static_init = { workspace = true }
7778
hex-literal = { workspace = true }
7879
pallet-revive-fixtures = { workspace = true }
7980
substrate-cli-test-utils = { workspace = true }

substrate/frame/revive/rpc/examples/js/src/lib.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,34 @@ import {
44
JsonRpcProvider,
55
TransactionReceipt,
66
TransactionResponse,
7+
Wallet,
78
} from 'ethers'
89
import { readFileSync } from 'node:fs'
910
import type { compile } from '@parity/revive'
1011
import { spawn } from 'node:child_process'
12+
import { parseArgs } from 'node:util'
1113

1214
type CompileOutput = Awaited<ReturnType<typeof compile>>
1315
type Abi = CompileOutput['contracts'][string][string]['abi']
1416

15-
const geth = process.argv.includes('--geth')
17+
const {
18+
values: { geth, westend, ['private-key']: privateKey },
19+
} = parseArgs({
20+
args: process.argv.slice(2),
21+
options: {
22+
['private-key']: {
23+
type: 'string',
24+
short: 'k',
25+
},
26+
geth: {
27+
type: 'boolean',
28+
},
29+
westend: {
30+
type: 'boolean',
31+
},
32+
},
33+
})
34+
1635
if (geth) {
1736
console.log('Testing with Geth')
1837
const child = spawn(
@@ -31,13 +50,15 @@ if (geth) {
3150
)
3251

3352
process.on('exit', () => child.kill())
34-
3553
child.unref()
3654
await new Promise((resolve) => setTimeout(resolve, 500))
3755
}
3856

39-
const provider = new JsonRpcProvider('http://localhost:8545')
40-
const signer = await provider.getSigner()
57+
const provider = new JsonRpcProvider(
58+
westend ? 'https://westend-asset-hub-eth-rpc.polkadot.io' : 'http://localhost:8545'
59+
)
60+
61+
const signer = privateKey ? new Wallet(privateKey, provider) : await provider.getSigner()
4162
console.log(`Signer address: ${await signer.getAddress()}, Nonce: ${await signer.getNonce()}`)
4263

4364
/**

substrate/frame/revive/rpc/src/client.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use pallet_revive::{
3535
},
3636
EthContractResult,
3737
};
38+
use sp_core::keccak_256;
3839
use sp_weights::Weight;
3940
use std::{
4041
collections::{HashMap, VecDeque},
@@ -278,6 +279,7 @@ impl ClientInner {
278279
// Filter extrinsics from pallet_revive
279280
let extrinsics = extrinsics.iter().flat_map(|ext| {
280281
let call = ext.as_extrinsic::<EthTransact>().ok()??;
282+
let transaction_hash = H256(keccak_256(&call.payload));
281283
let tx = rlp::decode::<TransactionLegacySigned>(&call.payload).ok()?;
282284
let from = tx.recover_eth_address().ok()?;
283285
let contract_address = if tx.transaction_legacy_unsigned.to.is_none() {
@@ -286,12 +288,12 @@ impl ClientInner {
286288
None
287289
};
288290

289-
Some((from, tx, contract_address, ext))
291+
Some((from, tx, transaction_hash, contract_address, ext))
290292
});
291293

292294
// Map each extrinsic to a receipt
293295
stream::iter(extrinsics)
294-
.map(|(from, tx, contract_address, ext)| async move {
296+
.map(|(from, tx, transaction_hash, contract_address, ext)| async move {
295297
let events = ext.events().await?;
296298
let tx_fees =
297299
events.find_first::<TransactionFeePaid>()?.ok_or(ClientError::TxFeeNotFound)?;
@@ -305,7 +307,6 @@ impl ClientInner {
305307
let transaction_index = ext.index();
306308
let block_hash = block.hash();
307309
let block_number = block.number().into();
308-
let transaction_hash= ext.hash();
309310

310311
// get logs from ContractEmitted event
311312
let logs = events.iter()

substrate/frame/revive/rpc/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use jsonrpsee::{
2424
types::{ErrorCode, ErrorObjectOwned},
2525
};
2626
use pallet_revive::{evm::*, EthContractResult};
27-
use sp_core::{H160, H256, U256};
27+
use sp_core::{keccak_256, H160, H256, U256};
2828
use thiserror::Error;
2929

3030
pub mod cli;
@@ -135,6 +135,8 @@ impl EthRpcServer for EthRpcServerImpl {
135135
}
136136

137137
async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
138+
let hash = H256(keccak_256(&transaction.0));
139+
138140
let tx = rlp::decode::<TransactionLegacySigned>(&transaction.0).map_err(|err| {
139141
log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}");
140142
EthRpcError::from(err)
@@ -167,7 +169,7 @@ impl EthRpcServer for EthRpcServerImpl {
167169
gas_required.into(),
168170
storage_deposit,
169171
);
170-
let hash = self.client.submit(call).await?;
172+
self.client.submit(call).await?;
171173
log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
172174
Ok(hash)
173175
}

substrate/frame/revive/rpc/src/tests.rs

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use pallet_revive::{
2727
create1,
2828
evm::{Account, BlockTag, U256},
2929
};
30+
use static_init::dynamic;
3031
use std::thread;
3132
use substrate_cli_test_utils::*;
3233

@@ -60,6 +61,52 @@ fn get_contract(name: &str) -> anyhow::Result<(Vec<u8>, ethabi::Contract)> {
6061
Ok((bytecode, contract))
6162
}
6263

64+
struct SharedResources {
65+
_node_handle: std::thread::JoinHandle<()>,
66+
_rpc_handle: std::thread::JoinHandle<()>,
67+
}
68+
69+
impl SharedResources {
70+
fn start() -> Self {
71+
// Start the node.
72+
let _node_handle = thread::spawn(move || {
73+
if let Err(e) = start_node_inline(vec![
74+
"--dev",
75+
"--rpc-port=45789",
76+
"--no-telemetry",
77+
"--no-prometheus",
78+
"-lerror,evm=debug,sc_rpc_server=info,runtime::revive=trace",
79+
]) {
80+
panic!("Node exited with error: {e:?}");
81+
}
82+
});
83+
84+
// Start the rpc server.
85+
let args = CliCommand::parse_from([
86+
"--dev",
87+
"--rpc-port=45788",
88+
"--node-rpc-url=ws://localhost:45789",
89+
"--no-prometheus",
90+
"-linfo,eth-rpc=debug",
91+
]);
92+
93+
let _rpc_handle = thread::spawn(move || {
94+
if let Err(e) = cli::run(args) {
95+
panic!("eth-rpc exited with error: {e:?}");
96+
}
97+
});
98+
99+
Self { _node_handle, _rpc_handle }
100+
}
101+
102+
async fn client() -> WsClient {
103+
ws_client_with_retry("ws://localhost:45788").await
104+
}
105+
}
106+
107+
#[dynamic(lazy)]
108+
static mut SHARED_RESOURCES: SharedResources = SharedResources::start();
109+
63110
macro_rules! unwrap_call_err(
64111
($err:expr) => {
65112
match $err.downcast_ref::<jsonrpsee::core::client::Error>().unwrap() {
@@ -70,41 +117,42 @@ macro_rules! unwrap_call_err(
70117
);
71118

72119
#[tokio::test]
73-
async fn test_jsonrpsee_server() -> anyhow::Result<()> {
74-
// Start the node.
75-
let _ = thread::spawn(move || {
76-
if let Err(e) = start_node_inline(vec![
77-
"--dev",
78-
"--rpc-port=45789",
79-
"--no-telemetry",
80-
"--no-prometheus",
81-
"-lerror,evm=debug,sc_rpc_server=info,runtime::revive=trace",
82-
]) {
83-
panic!("Node exited with error: {e:?}");
84-
}
85-
});
86-
87-
// Start the rpc server.
88-
let args = CliCommand::parse_from([
89-
"--dev",
90-
"--rpc-port=45788",
91-
"--node-rpc-url=ws://localhost:45789",
92-
"--no-prometheus",
93-
"-linfo,eth-rpc=debug",
94-
]);
95-
let _ = thread::spawn(move || {
96-
if let Err(e) = cli::run(args) {
97-
panic!("eth-rpc exited with error: {e:?}");
98-
}
99-
});
120+
async fn transfer() -> anyhow::Result<()> {
121+
let _lock = SHARED_RESOURCES.write();
122+
let client = SharedResources::client().await;
100123

101-
let client = ws_client_with_retry("ws://localhost:45788").await;
124+
let ethan = Account::from(subxt_signer::eth::dev::ethan());
125+
let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
126+
127+
let value = 1_000_000_000_000_000_000_000u128.into();
128+
let hash = TransactionBuilder::default()
129+
.value(value)
130+
.to(ethan.address())
131+
.send(&client)
132+
.await?;
133+
134+
let receipt = wait_for_successful_receipt(&client, hash).await?;
135+
assert_eq!(
136+
Some(ethan.address()),
137+
receipt.to,
138+
"Receipt should have the correct contract address."
139+
);
140+
141+
let increase =
142+
client.get_balance(ethan.address(), BlockTag::Latest.into()).await? - initial_balance;
143+
assert_eq!(value, increase);
144+
Ok(())
145+
}
146+
147+
#[tokio::test]
148+
async fn deploy_and_call() -> anyhow::Result<()> {
149+
let _lock = SHARED_RESOURCES.write();
150+
let client = SharedResources::client().await;
102151
let account = Account::default();
103152

104153
// Balance transfer
105154
let ethan = Account::from(subxt_signer::eth::dev::ethan());
106-
let ethan_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
107-
assert_eq!(U256::zero(), ethan_balance);
155+
let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
108156

109157
let value = 1_000_000_000_000_000_000_000u128.into();
110158
let hash = TransactionBuilder::default()
@@ -120,8 +168,8 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> {
120168
"Receipt should have the correct contract address."
121169
);
122170

123-
let ethan_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
124-
assert_eq!(value, ethan_balance, "ethan's balance should be the same as the value sent.");
171+
let updated_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
172+
assert_eq!(value, updated_balance - initial_balance);
125173

126174
// Deploy contract
127175
let data = b"hello world".to_vec();
@@ -169,15 +217,19 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> {
169217
wait_for_successful_receipt(&client, hash).await?;
170218
let increase = client.get_balance(contract_address, BlockTag::Latest.into()).await? - balance;
171219
assert_eq!(value, increase, "contract's balance should have increased by the value sent.");
220+
Ok(())
221+
}
172222

173-
// Deploy revert
223+
#[tokio::test]
224+
async fn revert_call() -> anyhow::Result<()> {
225+
let _lock = SHARED_RESOURCES.write();
226+
let client = SharedResources::client().await;
174227
let (bytecode, contract) = get_contract("revert")?;
175228
let receipt = TransactionBuilder::default()
176229
.input(contract.constructor.clone().unwrap().encode_input(bytecode, &[]).unwrap())
177230
.send_and_wait_for_receipt(&client)
178231
.await?;
179232

180-
// Call doRevert
181233
let err = TransactionBuilder::default()
182234
.to(receipt.contract_address.unwrap())
183235
.input(contract.function("doRevert")?.encode_input(&[])?.to_vec())
@@ -187,25 +239,36 @@ async fn test_jsonrpsee_server() -> anyhow::Result<()> {
187239

188240
let call_err = unwrap_call_err!(err.source().unwrap());
189241
assert_eq!(call_err.message(), "Execution reverted: revert message");
242+
Ok(())
243+
}
190244

191-
// Deploy event
245+
#[tokio::test]
246+
async fn event_logs() -> anyhow::Result<()> {
247+
let _lock = SHARED_RESOURCES.write();
248+
let client = SharedResources::client().await;
192249
let (bytecode, contract) = get_contract("event")?;
193250
let receipt = TransactionBuilder::default()
194251
.input(bytecode)
195252
.send_and_wait_for_receipt(&client)
196253
.await?;
197254

198-
// Call triggerEvent
199255
let receipt = TransactionBuilder::default()
200256
.to(receipt.contract_address.unwrap())
201257
.input(contract.function("triggerEvent")?.encode_input(&[])?.to_vec())
202258
.send_and_wait_for_receipt(&client)
203259
.await?;
204260
assert_eq!(receipt.logs.len(), 1, "There should be one log.");
261+
Ok(())
262+
}
263+
264+
#[tokio::test]
265+
async fn invalid_transaction() -> anyhow::Result<()> {
266+
let _lock = SHARED_RESOURCES.write();
267+
let client = SharedResources::client().await;
268+
let ethan = Account::from(subxt_signer::eth::dev::ethan());
205269

206-
// Invalid transaction
207270
let err = TransactionBuilder::default()
208-
.value(value)
271+
.value(U256::from(1_000_000_000_000u128))
209272
.to(ethan.address())
210273
.mutate(|tx| tx.chain_id = Some(42u32.into()))
211274
.send(&client)

0 commit comments

Comments
 (0)