Skip to content

Commit 9c73758

Browse files
lrubaszelexnvgithub-actions[bot]pgherveou
authored
Add comprehensive test data for Ethereum trie root validation (#9452)
### Summary This PR adds comprehensive test data for validating Ethereum transaction and receipt trie root calculations in the `revive` crate. It includes real-world Ethereum blocks covering all supported transaction types. --- ### Details #### 🧪 Test Data - **Expanded Test Fixtures**: - Added 3 Ethereum blocks with their receipts (2 from mainnet, 1 from Sepolia testnet) - Blocks include all supported transaction types (Legacy, EIP-2930, EIP-1559, EIP-4844) - Test data validates `transactions_root` and `receipts_root` calculations against real Ethereum data - Organized naming: `block_{block_number}_{network}.json` and `receipts_{block_number}_{network}.json` #### 🛠️ Tooling - **Test Data Collection Script**: - Added `get_test_data.sh` for fetching test data from live Ethereum networks - Simple curl-based script that can be extended with additional blocks Builds on top of: #9418 Part of: paritytech/contract-issues#139 --------- Signed-off-by: Alexandru Vasile <[email protected]> Co-authored-by: Alexandru Vasile <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Alexandru Vasile <[email protected]> Co-authored-by: PG Herveou <[email protected]>
1 parent c5c4734 commit 9c73758

13 files changed

+37023
-539
lines changed

prdoc/pr_9452.prdoc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
title: Add comprehensive test data for Ethereum trie root validation
2+
doc:
3+
- audience: Runtime Dev
4+
description: "### Summary\n\nThis PR adds comprehensive test data for validating\
5+
\ Ethereum transaction and receipt trie root calculations in the `revive` crate.\
6+
\ It includes real-world Ethereum blocks covering all supported transaction types.\n\
7+
\n---\n\n### Details\n\n#### \U0001F9EA Test Data\n\n- **Expanded Test Fixtures**:\n\
8+
\ - Added 3 Ethereum blocks with their receipts (2 from mainnet, 1 from Sepolia\
9+
\ testnet)\n - Blocks include all supported transaction types (Legacy, EIP-2930,\
10+
\ EIP-1559, EIP-4844)\n - Test data validates `transactions_root` and `receipts_root`\
11+
\ calculations against real Ethereum data\n - Organized naming: `block_{block_number}_{network}.json`\
12+
\ and `receipts_{block_number}_{network}.json`\n\n#### \U0001F6E0\uFE0F Tooling\n\
13+
\n- **Test Data Collection Script**:\n - Added `get_test_data.sh` for fetching\
14+
\ test data from live Ethereum networks\n - Simple curl-based script that can\
15+
\ be extended with additional blocks\n\nBuilds on top of: https://github.com/paritytech/polkadot-sdk/pull/9418\n\
16+
\nPart of: https://github.com/paritytech/contract-issues/issues/139"
17+
crates:
18+
- name: pallet-revive
19+
bump: minor
20+
- name: sp-core
21+
bump: minor
22+
- name: asset-hub-westend-runtime
23+
bump: minor
24+
- name: penpal-runtime
25+
bump: minor
26+
- name: pallet-revive-eth-rpc
27+
bump: minor
28+
- name: pallet-xcm
29+
bump: minor
30+
- name: pallet-assets
31+
bump: minor
32+
- name: revive-dev-runtime
33+
bump: minor
34+
- name: pallet-assets-precompiles
35+
bump: minor
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
use jsonrpsee::http_client::HttpClientBuilder;
18+
use pallet_revive::evm::Account;
19+
use pallet_revive_eth_rpc::example::{TransactionBuilder, TransactionType};
20+
use std::sync::Arc;
21+
22+
#[tokio::main]
23+
async fn main() -> anyhow::Result<()> {
24+
let client = Arc::new(HttpClientBuilder::default().build("http://localhost:8545")?);
25+
26+
let alith = Account::default();
27+
let ethan = Account::from(subxt_signer::eth::dev::ethan());
28+
let value = 1_000_000_000_000_000_000_000u128.into();
29+
30+
for tx_type in [
31+
TransactionType::Legacy,
32+
TransactionType::Eip2930,
33+
TransactionType::Eip1559,
34+
TransactionType::Eip4844,
35+
] {
36+
println!("\n\n=== TransactionType {tx_type:?} ===\n\n",);
37+
38+
let tx = TransactionBuilder::new(&client)
39+
.signer(alith.clone())
40+
.value(value)
41+
.to(ethan.address())
42+
.send_with_type(tx_type)
43+
.await?;
44+
println!("Transaction hash: {:?}", tx.hash());
45+
46+
let receipt = tx.wait_for_receipt().await?;
47+
println!("Receipt: {receipt:#?}");
48+
}
49+
Ok(())
50+
}

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

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ use anyhow::Context;
2020
use pallet_revive::evm::*;
2121
use std::sync::Arc;
2222

23+
/// Transaction type enum for specifying which type of transaction to send
24+
#[derive(Debug, Clone, Copy)]
25+
pub enum TransactionType {
26+
Legacy,
27+
Eip2930,
28+
Eip1559,
29+
Eip4844,
30+
}
31+
2332
/// Transaction builder.
2433
pub struct TransactionBuilder<Client: EthRpcClient + Sync + Send> {
2534
client: Arc<Client>,
@@ -28,7 +37,7 @@ pub struct TransactionBuilder<Client: EthRpcClient + Sync + Send> {
2837
input: Bytes,
2938
to: Option<H160>,
3039
nonce: Option<U256>,
31-
mutate: Box<dyn FnOnce(&mut TransactionLegacyUnsigned)>,
40+
mutate: Box<dyn FnOnce(&mut TransactionUnsigned)>,
3241
}
3342

3443
#[derive(Debug)]
@@ -63,7 +72,9 @@ impl<Client: EthRpcClient + Sync + Send> SubmittedTransaction<Client> {
6372
if receipt.is_success() {
6473
assert!(
6574
self.gas() > receipt.gas_used,
66-
"Gas used should be less than gas estimated."
75+
"Gas used {:?} should be less than gas estimated {:?}",
76+
receipt.gas_used,
77+
self.gas()
6778
);
6879
return Ok(receipt);
6980
} else {
@@ -119,7 +130,7 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
119130
}
120131

121132
/// Set a mutation function, that mutates the transaction before sending.
122-
pub fn mutate(mut self, mutate: impl FnOnce(&mut TransactionLegacyUnsigned) + 'static) -> Self {
133+
pub fn mutate(mut self, mutate: impl FnOnce(&mut TransactionUnsigned) + 'static) -> Self {
123134
self.mutate = Box::new(mutate);
124135
self
125136
}
@@ -147,10 +158,18 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
147158

148159
/// Send the transaction.
149160
pub async fn send(self) -> anyhow::Result<SubmittedTransaction<Client>> {
161+
self.send_with_type(TransactionType::Legacy).await
162+
}
163+
164+
/// Send the transaction with a specific transaction type.
165+
pub async fn send_with_type(
166+
self,
167+
tx_type: TransactionType,
168+
) -> anyhow::Result<SubmittedTransaction<Client>> {
150169
let TransactionBuilder { client, signer, value, input, to, nonce, mutate } = self;
151170

152171
let from = signer.address();
153-
let chain_id = Some(client.chain_id().await?);
172+
let chain_id = client.chain_id().await?;
154173
let gas_price = client.gas_price().await?;
155174
let nonce = if let Some(nonce) = nonce {
156175
nonce
@@ -176,20 +195,73 @@ impl<Client: EthRpcClient + Send + Sync> TransactionBuilder<Client> {
176195
.await
177196
.with_context(|| "Failed to fetch gas estimate")?;
178197

179-
let mut unsigned_tx = TransactionLegacyUnsigned {
180-
gas,
181-
nonce,
182-
to,
183-
value,
184-
input,
185-
gas_price,
186-
chain_id,
187-
..Default::default()
188-
};
198+
println!("Gas estimate: {gas:?}");
189199

200+
let mut unsigned_tx: TransactionUnsigned = match tx_type {
201+
TransactionType::Legacy => TransactionLegacyUnsigned {
202+
gas,
203+
nonce,
204+
to,
205+
value,
206+
input,
207+
gas_price,
208+
chain_id: Some(chain_id),
209+
..Default::default()
210+
}
211+
.into(),
212+
TransactionType::Eip2930 => Transaction2930Unsigned {
213+
gas,
214+
nonce,
215+
to,
216+
value,
217+
input,
218+
gas_price,
219+
chain_id,
220+
access_list: vec![],
221+
r#type: TypeEip2930,
222+
}
223+
.into(),
224+
TransactionType::Eip1559 => Transaction1559Unsigned {
225+
gas,
226+
nonce,
227+
to,
228+
value,
229+
input,
230+
gas_price,
231+
max_fee_per_gas: gas_price,
232+
max_priority_fee_per_gas: U256::zero(),
233+
chain_id,
234+
access_list: vec![],
235+
r#type: TypeEip1559,
236+
}
237+
.into(),
238+
TransactionType::Eip4844 => {
239+
// For EIP-4844, we need a destination address (cannot be None for blob
240+
// transactions)
241+
let to = to.ok_or_else(|| {
242+
anyhow::anyhow!("EIP-4844 transactions require a destination address")
243+
})?;
244+
let max_priority_fee_per_gas = gas_price / 10; // 10% of gas price as priority fee
245+
Transaction4844Unsigned {
246+
gas,
247+
nonce,
248+
to,
249+
value,
250+
input,
251+
max_fee_per_gas: gas_price,
252+
max_priority_fee_per_gas,
253+
max_fee_per_blob_gas: gas_price, // Use gas_price as blob gas fee
254+
chain_id,
255+
access_list: vec![],
256+
blob_versioned_hashes: vec![],
257+
r#type: TypeEip4844,
258+
}
259+
.into()
260+
},
261+
};
190262
mutate(&mut unsigned_tx);
191263

192-
let signed_tx = signer.sign_transaction(unsigned_tx.into());
264+
let signed_tx = signer.sign_transaction(unsigned_tx);
193265
let bytes = signed_tx.signed_payload();
194266

195267
let hash = client

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use pallet_revive::{
3434
create1,
3535
evm::{
3636
Account, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, BlockTag,
37-
HashesOrTransactionInfos, TransactionInfo, H256, U256,
37+
HashesOrTransactionInfos, TransactionInfo, TransactionUnsigned, H256, U256,
3838
},
3939
};
4040
use std::{sync::Arc, thread};
@@ -450,7 +450,13 @@ async fn test_invalid_transaction(client: Arc<WsClient>) -> anyhow::Result<()> {
450450
let err = TransactionBuilder::new(&client)
451451
.value(U256::from(1_000_000_000_000u128))
452452
.to(ethan.address())
453-
.mutate(|tx| tx.chain_id = Some(42u32.into()))
453+
.mutate(|tx| match tx {
454+
TransactionUnsigned::TransactionLegacyUnsigned(tx) => tx.chain_id = Some(42u32.into()),
455+
TransactionUnsigned::Transaction1559Unsigned(tx) => tx.chain_id = 42u32.into(),
456+
TransactionUnsigned::Transaction2930Unsigned(tx) => tx.chain_id = 42u32.into(),
457+
TransactionUnsigned::Transaction4844Unsigned(tx) => tx.chain_id = 42u32.into(),
458+
TransactionUnsigned::Transaction7702Unsigned(tx) => tx.chain_id = 42u32.into(),
459+
})
454460
.send()
455461
.await
456462
.unwrap_err();

substrate/frame/revive/src/evm/api/rlp_codec.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,6 @@ mod test {
637637
),
638638
// type 3: EIP4844
639639
(
640-
641640
"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
642641
r#"
643642
{
@@ -662,7 +661,7 @@ mod test {
662661
"s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
663662
"yParity": "0x0"
664663
}
665-
"#
664+
"#,
666665
)
667666
];
668667

@@ -693,4 +692,32 @@ mod test {
693692
let payload = Account::default().sign_transaction(tx).signed_payload();
694693
assert_eq!(dummy_signed_payload.len(), payload.len());
695694
}
695+
696+
#[test]
697+
fn rlp_codec_is_compatible_with_ethereum() {
698+
// RLP encoded transactions
699+
let test_cases = [
700+
// Legacy
701+
"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
702+
// EIP-2930
703+
"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
704+
// EIP-1559
705+
"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8",
706+
// EIP4844
707+
"03f89783aa36a701832dc6c083fc546c8261a8947f8b1ca29f95274e06367b60fc4a539e4910fd0c865af3107a400080c0831e8480e1a0018fd423d1ad106395f04abac797217d4dece29da3ba649d9aa4da70e98fa6ff80a028d2350a1bfa5043de1533911143eb5c43815a58039121a0ccf124870620fca6a0157eca4963615cd3926538af88e529cfa3baf6c55787a33f79c25babe9f5db2b",
708+
];
709+
710+
for hex_tx in test_cases {
711+
let rlp_encoded_tx = alloy_core::hex::decode(hex_tx).unwrap();
712+
713+
// RLP decode using this implementation
714+
let tx_revive = TransactionSigned::decode(&rlp_encoded_tx).unwrap();
715+
716+
// RLP encode using this implementation
717+
let rlp_encoded_revive = tx_revive.signed_payload();
718+
719+
// Verify round-trip: our encoding should decode back to the same transaction
720+
assert_eq!(rlp_encoded_tx, rlp_encoded_revive);
721+
}
722+
}
696723
}

0 commit comments

Comments
 (0)