Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.

Commit bb6c220

Browse files
authored
Add integration testing with geth (#221)
Introduce a new crate `integration-tests` to do integration tests of the rest of the crates with geth. This crate contains a binary used to generate data for the geth dev blockchain, and groups of integration tests. For now we only have the `rpc` test group. See `integration-tests/README.md` for more details. Add a github action workflow that runs the integration test for the bus-mapping rpc methods. The action runs the `run.sh` integration test script by steps to make it easier to see how long steps take, and to quickly find which step fails in case of an error. bus-mapping: Add `get_code_by_address` rpc method. bus-mapping: Set the fields in the types returned by `get_proof` public, and move them to `eth_types`. Co-authored by: NoCtrlZ <phantomofrotten@gmail.com>
1 parent 1ef27da commit bb6c220

File tree

18 files changed

+690
-147
lines changed

18 files changed

+690
-147
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: CI checks
22

3-
on:
3+
on:
44
pull_request:
55
types: [synchronize, opened, reopened, ready_for_review]
66

7-
## `actions-rs/toolchain@v1` overwrite set to false so that
7+
## `actions-rs/toolchain@v1` overwrite set to false so that
88
## `rust-toolchain` is always used and the only source of truth.
99

1010
jobs:
@@ -28,7 +28,7 @@ jobs:
2828
uses: actions-rs/cargo@v1
2929
with:
3030
command: test
31-
args: --verbose --release --all --all-features
31+
args: --verbose --release --all --all-features --exclude integration-tests
3232

3333
build:
3434
if: github.event.pull_request.draft == false
@@ -98,7 +98,7 @@ jobs:
9898

9999
fmt:
100100
if: github.event.pull_request.draft == false
101-
101+
102102
name: Rustfmt
103103
timeout-minutes: 30
104104
runs-on: ubuntu-latest

.github/workflows/integration.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Integration Tests
2+
3+
on:
4+
pull_request:
5+
types: [synchronize, opened, reopened, ready_for_review]
6+
7+
jobs:
8+
integration-tests:
9+
if: github.event.pull_request.draft == false
10+
11+
name: Integration Tests
12+
runs-on: ubuntu-latest
13+
14+
defaults:
15+
run:
16+
working-directory: ./integration-tests
17+
steps:
18+
- uses: actions/checkout@v2
19+
- uses: actions-rs/toolchain@v1
20+
with:
21+
override: false
22+
- name: Set PATH
23+
run: echo "${HOME}/bin" >> $GITHUB_PATH
24+
- name: Install Solc
25+
run: |
26+
mkdir -p "$HOME/bin"
27+
wget -q https://github.com/ethereum/solidity/releases/download/v0.8.0/solc-static-linux -O $HOME/bin/solc
28+
chmod u+x "$HOME/bin/solc"
29+
solc --version
30+
# Run an initial build in a sepparate step to split the build time from execution time
31+
- name: Build gendata bin
32+
run: cargo build --bin gen_blockchain_data
33+
- run: ./run.sh --steps "setup"
34+
- run: ./run.sh --steps "gendata"
35+
- run: ./run.sh --steps "tests" --tests "rpc"
36+
- run: ./run.sh --steps "cleanup"

.github/workflows/lints.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
name: Lints
33

44
# We only run these lints on trial-merges of PRs to reduce noise.
5-
on:
5+
on:
66
pull_request:
77
types: [synchronize, opened, reopened, ready_for_review]
88

99
jobs:
1010
clippy:
1111
if: github.event.pull_request.draft == false
12-
12+
1313
name: Clippy
1414
timeout-minutes: 30
1515
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"bus-mapping",
55
"keccak256",
66
"geth-utils",
7+
"integration-tests",
78
]
89

910
[patch.crates-io]

bus-mapping/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ serde_json = "1.0.66"
1414
hex = "0.4"
1515
geth-utils = { path = "../geth-utils" }
1616
uint = "0.9.1"
17-
ethers-providers = "0.6.1"
18-
ethers-core = "0.6.1"
17+
ethers-providers = "0.6.2"
18+
ethers-core = "0.6.2"
1919
regex = "1.5.4"
2020

2121
[dev-dependencies]

bus-mapping/src/eth_types.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use crate::evm::{memory::Memory, stack::Stack, storage::Storage};
44
use crate::evm::{Gas, GasCost, OpcodeId, ProgramCounter};
55
use ethers_core::types;
66
pub use ethers_core::types::{
7-
transaction::response::Transaction, Address, Block, Bytes,
8-
EIP1186ProofResponse, H160, H256, U256, U64,
7+
transaction::response::Transaction, Address, Block, Bytes, H160, H256,
8+
U256, U64,
99
};
1010
use pairing::arithmetic::FieldExt;
1111
use serde::{de, Deserialize};
@@ -133,6 +133,37 @@ impl<F: FieldExt> ToScalar<F> for Address {
133133
}
134134
}
135135

136+
/// Struct used to define the storage proof
137+
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
138+
pub struct StorageProof {
139+
/// Storage key
140+
pub key: U256,
141+
/// Storage Value
142+
pub value: U256,
143+
/// Storage proof: rlp-encoded trie nodes from root to value.
144+
pub proof: Vec<Bytes>,
145+
}
146+
147+
/// Struct used to define the result of `eth_getProof` call
148+
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
149+
#[serde(rename_all = "camelCase")]
150+
pub struct EIP1186ProofResponse {
151+
/// Account address
152+
pub address: Address,
153+
/// The balance of the account
154+
pub balance: U256,
155+
/// The hash of the code of the account
156+
pub code_hash: H256,
157+
/// The nonce of the account
158+
pub nonce: U256,
159+
/// SHA3 of the StorageRoot
160+
pub storage_hash: H256,
161+
/// Array of rlp-serialized MerkleTree-Nodes
162+
pub account_proof: Vec<Bytes>,
163+
/// Array of storage-entries as requested
164+
pub storage_proof: Vec<StorageProof>,
165+
}
166+
136167
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
137168
#[doc(hidden)]
138169
struct GethExecStepInternal {

bus-mapping/src/rpc.rs

Lines changed: 37 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
//! query a Geth node in order to get a Block, Tx or Trace info.
33
44
use crate::eth_types::{
5-
Address, Block, EIP1186ProofResponse, GethExecTrace, Hash,
5+
Address, Block, Bytes, EIP1186ProofResponse, GethExecTrace, Hash,
66
ResultGethExecTraces, Transaction, Word, U64,
77
};
88
use crate::Error;
99
use ethers_providers::JsonRpcClient;
10+
use serde::{Serialize, Serializer};
1011

1112
/// Serialize a type.
1213
///
@@ -22,7 +23,7 @@ pub fn serialize<T: serde::Serialize>(t: &T) -> serde_json::Value {
2223
#[derive(Debug)]
2324
pub enum BlockNumber {
2425
/// Specific block number
25-
Num(U64),
26+
Num(u64),
2627
/// Earliest block
2728
Earliest,
2829
/// Latest block
@@ -33,26 +34,27 @@ pub enum BlockNumber {
3334

3435
impl From<u64> for BlockNumber {
3536
fn from(num: u64) -> Self {
36-
BlockNumber::Num(U64::from(num))
37+
BlockNumber::Num(num)
3738
}
3839
}
3940

40-
impl BlockNumber {
41-
/// Serializes a BlockNumber as a [`Value`](serde_json::Value) to be able to
42-
/// throw it into a JSON-RPC request.
43-
pub fn serialize(self) -> serde_json::Value {
41+
impl Serialize for BlockNumber {
42+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43+
where
44+
S: Serializer,
45+
{
4446
match self {
45-
BlockNumber::Num(num) => serialize(&num),
46-
BlockNumber::Earliest => serialize(&"earliest"),
47-
BlockNumber::Latest => serialize(&"latest"),
48-
BlockNumber::Pending => serialize(&"pending"),
47+
BlockNumber::Num(num) => U64::from(*num).serialize(serializer),
48+
BlockNumber::Earliest => "earliest".serialize(serializer),
49+
BlockNumber::Latest => "latest".serialize(serializer),
50+
BlockNumber::Pending => "pending".serialize(serializer),
4951
}
5052
}
5153
}
5254

5355
/// Placeholder structure designed to contain the methods that the BusMapping
5456
/// needs in order to enable Geth queries.
55-
pub struct GethClient<P: JsonRpcClient>(P);
57+
pub struct GethClient<P: JsonRpcClient>(pub P);
5658

5759
impl<P: JsonRpcClient> GethClient<P> {
5860
/// Generates a new `GethClient` instance.
@@ -81,7 +83,7 @@ impl<P: JsonRpcClient> GethClient<P> {
8183
&self,
8284
block_num: BlockNumber,
8385
) -> Result<Block<Transaction>, Error> {
84-
let num = block_num.serialize();
86+
let num = serialize(&block_num);
8587
let flag = serialize(&true);
8688
self.0
8789
.request("eth_getBlockByNumber", [num, flag])
@@ -112,7 +114,7 @@ impl<P: JsonRpcClient> GethClient<P> {
112114
&self,
113115
block_num: BlockNumber,
114116
) -> Result<Vec<GethExecTrace>, Error> {
115-
let num = block_num.serialize();
117+
let num = serialize(&block_num);
116118
let resp: ResultGethExecTraces = self
117119
.0
118120
.request("debug_traceBlockByNumber", [num])
@@ -121,9 +123,25 @@ impl<P: JsonRpcClient> GethClient<P> {
121123
Ok(resp.0.into_iter().map(|step| step.result).collect())
122124
}
123125

124-
/// Calls `eth_getProof` via JSON-RPC returning a [`EIP1186ProofResponse`]
125-
/// returning the account and storage-values of the specified
126-
/// account including the Merkle-proof.
126+
/// Calls `eth_getCode` via JSON-RPC returning a contract code
127+
pub async fn get_code_by_address(
128+
&self,
129+
contract_address: Address,
130+
block_num: BlockNumber,
131+
) -> Result<Vec<u8>, Error> {
132+
let address = serialize(&contract_address);
133+
let num = serialize(&block_num);
134+
let resp: Bytes = self
135+
.0
136+
.request("eth_getCode", [address, num])
137+
.await
138+
.map_err(|e| Error::JSONRpcError(e.into()))?;
139+
Ok(resp.to_vec())
140+
}
141+
142+
/// Calls `eth_getProof` via JSON-RPC returning a
143+
/// [`EIP1186ProofResponse`] returning the account and
144+
/// storage-values of the specified account including the Merkle-proof.
127145
pub async fn get_proof(
128146
&self,
129147
account: Address,
@@ -132,130 +150,12 @@ impl<P: JsonRpcClient> GethClient<P> {
132150
) -> Result<EIP1186ProofResponse, Error> {
133151
let account = serialize(&account);
134152
let keys = serialize(&keys);
135-
let num = block_num.serialize();
153+
let num = serialize(&block_num);
136154
self.0
137155
.request("eth_getProof", [account, keys, num])
138156
.await
139157
.map_err(|e| Error::JSONRpcError(e.into()))
140158
}
141159
}
142160

143-
#[cfg(test)]
144-
mod rpc_tests {
145-
use super::*;
146-
use ethers_providers::Http;
147-
use std::str::FromStr;
148-
use url::Url;
149-
150-
// The test is ignored as the values used depend on the Geth instance used
151-
// each time you run the tests. And we can't assume that everyone will
152-
// have a Geth client synced with mainnet to have unified "test-vectors".
153-
#[ignore]
154-
#[tokio::test]
155-
async fn test_get_block_by_hash() {
156-
let transport = Http::new(Url::parse("http://localhost:8545").unwrap());
157-
158-
let hash = Hash::from_str("0xe4f7aa19a76fcf31a6adff3b400300849e39dd84076765fb3af09d05ee9d787a").unwrap();
159-
let prov = GethClient::new(transport);
160-
let block_by_hash = prov.get_block_by_hash(hash).await.unwrap();
161-
assert!(hash == block_by_hash.hash.unwrap());
162-
}
163-
164-
// The test is ignored as the values used depend on the Geth instance used
165-
// each time you run the tests. And we can't assume that everyone will
166-
// have a Geth client synced with mainnet to have unified "test-vectors".
167-
#[ignore]
168-
#[tokio::test]
169-
async fn test_get_block_by_number() {
170-
let transport = Http::new(Url::parse("http://localhost:8545").unwrap());
171-
172-
let hash = Hash::from_str("0xe4f7aa19a76fcf31a6adff3b400300849e39dd84076765fb3af09d05ee9d787a").unwrap();
173-
let prov = GethClient::new(transport);
174-
let block_by_num_latest =
175-
prov.get_block_by_number(BlockNumber::Latest).await.unwrap();
176-
assert!(hash == block_by_num_latest.hash.unwrap());
177-
let block_by_num = prov.get_block_by_number(1u64.into()).await.unwrap();
178-
assert!(
179-
block_by_num.transactions[0].hash
180-
== block_by_num_latest.transactions[0].hash
181-
);
182-
}
183-
184-
// The test is ignored as the values used depend on the Geth instance used
185-
// each time you run the tests. And we can't assume that everyone will
186-
// have a Geth client synced with mainnet to have unified "test-vectors".
187-
#[ignore]
188-
#[tokio::test]
189-
async fn test_trace_block_by_hash() {
190-
let transport = Http::new(Url::parse("http://localhost:8545").unwrap());
191-
192-
let hash = Hash::from_str("0xe2d191e9f663a3a950519eadeadbd614965b694a65a318a0b8f053f2d14261ff").unwrap();
193-
let prov = GethClient::new(transport);
194-
let trace_by_hash = prov.trace_block_by_hash(hash).await.unwrap();
195-
// Since we called in the test block the same transaction twice the len
196-
// should be the same and != 0.
197-
assert!(
198-
trace_by_hash[0].struct_logs.len()
199-
== trace_by_hash[1].struct_logs.len()
200-
);
201-
assert!(!trace_by_hash[0].struct_logs.is_empty());
202-
}
203-
204-
// The test is ignored as the values used depend on the Geth instance used
205-
// each time you run the tests. And we can't assume that everyone will
206-
// have a Geth client synced with mainnet to have unified "test-vectors".
207-
#[ignore]
208-
#[tokio::test]
209-
async fn test_trace_block_by_number() {
210-
let transport = Http::new(Url::parse("http://localhost:8545").unwrap());
211-
let prov = GethClient::new(transport);
212-
let trace_by_hash = prov.trace_block_by_number(5.into()).await.unwrap();
213-
// Since we called in the test block the same transaction twice the len
214-
// should be the same and != 0.
215-
assert!(
216-
trace_by_hash[0].struct_logs.len()
217-
== trace_by_hash[1].struct_logs.len()
218-
);
219-
assert!(!trace_by_hash[0].struct_logs.is_empty());
220-
}
221-
222-
// The test is ignored as the values used depend on the Geth instance used
223-
// each time you run the tests. And we can't assume that everyone will
224-
// have a Geth client synced with mainnet to have unified "test-vectors".
225-
#[ignore]
226-
#[tokio::test]
227-
async fn test_get_proof() {
228-
let transport = Http::new(Url::parse("http://localhost:8545").unwrap());
229-
let prov = GethClient::new(transport);
230-
231-
let address =
232-
Address::from_str("0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842")
233-
.unwrap();
234-
let keys = vec![Word::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").unwrap()];
235-
let proof = prov
236-
.get_proof(address, keys, BlockNumber::Latest)
237-
.await
238-
.unwrap();
239-
const TARGET_PROOF: &str = r#"{
240-
"address": "0x7f0d15c7faae65896648c8273b6d7e43f58fa842",
241-
"accountProof": [
242-
"0xf873a12050fb4d3174ec89ef969c09fd4391602169760fb005ad516f5d172cbffb80e955b84ff84d8089056bc75e2d63100000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
243-
],
244-
"balance": "0x0",
245-
"codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
246-
"nonce": "0x0",
247-
"storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
248-
"storageProof": [
249-
{
250-
"key": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
251-
"value": "0x0",
252-
"proof": []
253-
}
254-
]
255-
}"#;
256-
assert!(
257-
serde_json::from_str::<EIP1186ProofResponse>(TARGET_PROOF).unwrap()
258-
== proof
259-
);
260-
}
261-
}
161+
// Integration tests found in `integration-tests/tests/rpc.rs`.

integration-tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gendata_output.json

0 commit comments

Comments
 (0)