Skip to content

Commit 98e4df8

Browse files
authored
Add integration tests for server
1 parent 86314ae commit 98e4df8

7 files changed

Lines changed: 276 additions & 23 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ log = "0.4"
5454
murmur3 = "0.5"
5555
num = "0.4"
5656
once_cell = "1.16"
57+
port_check = "0.1"
5758
rand = { version = "0.8", features = ["std_rng", "small_rng"] }
5859
reqwest = { version = "0.11", default-features = false }
5960
rlp = "0.5"

src/evm-block-extractor/Cargo.toml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,28 @@ path = "src/bin/server.rs"
1515

1616
[dependencies]
1717
anyhow = { workspace = true }
18+
async-trait = { workspace = true }
1819
clap = { workspace = true }
20+
did = { path = "../did" }
1921
env_logger = { workspace = true }
22+
ethereum-json-rpc-client = { path = "../ethereum-json-rpc-client", features = ["reqwest"] }
2023
ethers-core = { workspace = true }
24+
futures = { workspace = true }
25+
gcp-bigquery-client = { workspace = true }
26+
hex = { workspace = true }
2127
itertools = { workspace = true }
28+
jsonrpsee = { workspace = true }
2229
log = { workspace = true }
23-
ethereum-json-rpc-client = { path = "../ethereum-json-rpc-client", features = [
24-
"reqwest",
25-
] }
2630
serde = { workspace = true }
2731
serde_json = { workspace = true }
2832
sqlx = { workspace = true, features = ["postgres", "tls-rustls"] }
2933
tokio = { workspace = true }
30-
zip = { workspace = true }
31-
gcp-bigquery-client = { workspace = true }
32-
async-trait = { workspace = true }
33-
jsonrpsee = { workspace = true }
3434
yup-oauth2 = { workspace = true }
35-
did = { path = "../did" }
36-
futures = { workspace = true }
35+
zip = { workspace = true }
3736

3837
[dev-dependencies]
38+
jsonrpc-core = { workspace = true }
39+
port_check = { workspace = true }
3940
rand = { workspace = true }
4041
tempfile = { workspace = true }
4142
testcontainers = { workspace = true }

src/evm-block-extractor/src/bin/server.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clap::{arg, Parser};
22
use evm_block_extractor::config::Database;
3-
use evm_block_extractor::rpc::{EthImpl, EthServer};
3+
use evm_block_extractor::rpc::{EthImpl, EthServer, ICServer};
44
use jsonrpsee::server::Server;
55
use jsonrpsee::RpcModule;
66

@@ -34,7 +34,8 @@ async fn main() -> anyhow::Result<()> {
3434

3535
let mut module = RpcModule::new(());
3636

37-
module.merge(EthServer::into_rpc(eth))?;
37+
module.merge(EthServer::into_rpc(eth.clone()))?;
38+
module.merge(ICServer::into_rpc(eth))?;
3839

3940
log::info!("Server started on {}", server.local_addr()?);
4041

src/evm-block-extractor/src/rpc.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ pub trait Eth {
4343
#[rpc(server, namespace = "ic")]
4444
pub trait IC {
4545
#[method(name = "getBlocksRLP")]
46-
async fn get_blocks_rlp(&self, from: BlockNumber, max_number: U64) -> RpcResult<Vec<u8>>;
46+
async fn get_blocks_rlp(&self, from: BlockNumber, max_number: U64) -> RpcResult<String>;
4747
}
4848

4949
#[async_trait::async_trait]
5050
impl ICServer for EthImpl {
51-
async fn get_blocks_rlp(&self, from: BlockNumber, max_number: U64) -> RpcResult<Vec<u8>> {
51+
async fn get_blocks_rlp(&self, from: BlockNumber, max_number: U64) -> RpcResult<String> {
5252
let db = &self.blockchain;
5353
let from = match from {
5454
BlockNumber::Latest => db
@@ -64,8 +64,8 @@ impl ICServer for EthImpl {
6464
jsonrpsee::types::error::ErrorCode::InternalError
6565
})?,
6666
BlockNumber::Number(num) => num.as_u64(),
67-
BlockNumber::Pending => return Ok(EMPTY_LIST_RLP.into()),
68-
_ => return Ok(EMPTY_LIST_RLP.into()),
67+
BlockNumber::Pending => return Ok(hex::encode(EMPTY_LIST_RLP)),
68+
_ => return Ok(hex::encode(EMPTY_LIST_RLP)),
6969
};
7070

7171
let block_count = db
@@ -81,7 +81,7 @@ impl ICServer for EthImpl {
8181
let end_block = std::cmp::min(from + std::cmp::min(10, max_number.as_u64()), block_count);
8282

8383
if end_block <= from {
84-
return Ok(EMPTY_LIST_RLP.into());
84+
return Ok(hex::encode(EMPTY_LIST_RLP));
8585
}
8686

8787
let mut rlp = RlpStream::new_list((end_block - from) as usize);
@@ -94,15 +94,10 @@ impl ICServer for EthImpl {
9494
jsonrpsee::types::error::ErrorCode::InternalError
9595
})?;
9696

97-
let block = serde_json::to_vec(&block).map_err(|e| {
98-
log::error!("Error serializing block: {:?}", e);
99-
jsonrpsee::types::error::ErrorCode::InternalError
100-
})?;
101-
10297
rlp.append(&block);
10398
}
10499

105-
Ok(rlp.out().to_vec())
100+
Ok(hex::encode(rlp.out()))
106101
}
107102
}
108103

src/evm-block-extractor/tests/evm_block_extractor_it.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::future::Future;
2-
use std::sync::Arc;
2+
use std::sync::{Arc, Once};
33

44
use evm_block_extractor::config::Database;
55
use evm_block_extractor::database::DatabaseClient;
@@ -9,7 +9,11 @@ use testcontainers::testcontainers::Container;
99
mod client;
1010
mod tests;
1111

12+
static INIT_LOG: Once = Once::new();
13+
1214
async fn test_with_clients<T: Fn(Arc<dyn DatabaseClient>) -> F, F: Future<Output = ()>>(test: T) {
15+
INIT_LOG.call_once(env_logger::init);
16+
1317
let docker = Cli::default();
1418

1519
println!("----------------------------------");
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pub mod block_extractor_it;
22
pub mod database_client_it;
3+
pub mod server_it;
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
use did::{
2+
block::{ExeResult, TransactOut},
3+
transaction::{Bloom, StorableExecutionResult},
4+
H160,
5+
};
6+
use ethereum_json_rpc_client::{reqwest::ReqwestClient, Client, EthJsonRcpClient};
7+
use ethers_core::types::{BlockNumber, Transaction, H256};
8+
use evm_block_extractor::{
9+
database::DatabaseClient,
10+
rpc::{EthImpl, EthServer, ICServer},
11+
};
12+
use jsonrpc_core::{Call, Id, MethodCall, Output, Params, Request, Response, Version};
13+
use jsonrpsee::{server::Server, RpcModule};
14+
use serde_json::json;
15+
use std::future::Future;
16+
use std::sync::Arc;
17+
18+
use crate::test_with_clients;
19+
20+
const BLOCK_COUNT: u64 = 10;
21+
22+
async fn with_filled_db<Func: Fn(Arc<dyn DatabaseClient>) -> Fut, Fut: Future<Output = ()>>(
23+
func: Func,
24+
) {
25+
test_with_clients(|db_client| async {
26+
db_client.init(None, false).await.unwrap();
27+
28+
for i in 0..BLOCK_COUNT {
29+
let tx_hash = H256::random();
30+
let dummy_transaction: did::Transaction = Transaction {
31+
hash: tx_hash,
32+
block_number: Some(i.into()),
33+
..Default::default()
34+
}
35+
.into();
36+
let dummy_block: did::Block<did::H256> = ethers_core::types::Block::<H256> {
37+
number: Some(ethers_core::types::U64::from(i)),
38+
hash: Some(H256::random()),
39+
transactions: vec![tx_hash],
40+
..Default::default()
41+
}
42+
.into();
43+
let dummy_receipt = StorableExecutionResult {
44+
transaction_hash: tx_hash.into(),
45+
block_number: i.into(),
46+
block_hash: dummy_block.hash.clone(),
47+
exe_result: ExeResult::Success {
48+
gas_used: (i * 1000).into(),
49+
logs: vec![],
50+
logs_bloom: Box::new(Bloom::zeros()),
51+
output: TransactOut::None,
52+
},
53+
transaction_index: 0u64.into(),
54+
from: H160::default(),
55+
to: None,
56+
transaction_type: None,
57+
cumulative_gas_used: (i * 1000).into(),
58+
max_fee_per_gas: None,
59+
gas_price: None,
60+
max_priority_fee_per_gas: None,
61+
};
62+
63+
db_client
64+
.insert_block_data(&[dummy_block], &[dummy_receipt], &[dummy_transaction])
65+
.await
66+
.unwrap();
67+
}
68+
69+
func(db_client).await
70+
})
71+
.await
72+
}
73+
74+
#[tokio::test]
75+
async fn test_get_blocks_and_receipts() {
76+
with_filled_db(|db_client| async {
77+
let eth = EthImpl::new(db_client);
78+
let mut module = RpcModule::new(());
79+
module.merge(EthServer::into_rpc(eth.clone())).unwrap();
80+
module.merge(ICServer::into_rpc(eth)).unwrap();
81+
82+
let port = port_check::free_local_port_in_range(9000, 9099).unwrap();
83+
let server = Server::builder()
84+
.build(format!("0.0.0.0:{port}"))
85+
.await
86+
.unwrap();
87+
let handle = server.start(module);
88+
89+
let http_client =
90+
EthJsonRcpClient::new(ReqwestClient::new(format!("http://127.0.0.1:{port}")));
91+
92+
let block_count = http_client.get_block_number().await.unwrap();
93+
assert_eq!(block_count, BLOCK_COUNT - 1);
94+
for i in 0u64..BLOCK_COUNT {
95+
let block = http_client
96+
.get_block_by_number(BlockNumber::Number(i.into()))
97+
.await
98+
.unwrap();
99+
assert_eq!(block.number, Some(i.into()));
100+
assert_eq!(block.transactions.len(), 1);
101+
102+
let full_block = http_client
103+
.get_full_block_by_number(BlockNumber::Number(i.into()))
104+
.await
105+
.unwrap();
106+
assert_eq!(full_block.number, Some(i.into()));
107+
assert_eq!(full_block.transactions.len(), 1);
108+
assert_eq!(full_block.transactions[0].hash, block.transactions[0]);
109+
110+
let receipt = http_client
111+
.get_receipt_by_hash(block.transactions[0])
112+
.await
113+
.unwrap();
114+
assert_eq!(receipt.block_number, Some(i.into()));
115+
assert_eq!(receipt.block_hash, block.hash);
116+
assert_eq!(receipt.transaction_hash, block.transactions[0]);
117+
}
118+
119+
{
120+
handle.stop().unwrap();
121+
handle.stopped().await;
122+
}
123+
})
124+
.await
125+
}
126+
127+
#[tokio::test]
128+
async fn test_get_blocks_rlp() {
129+
with_filled_db(|db_client| async {
130+
let eth = EthImpl::new(db_client);
131+
let mut module = RpcModule::new(());
132+
module.merge(EthServer::into_rpc(eth.clone())).unwrap();
133+
module.merge(ICServer::into_rpc(eth)).unwrap();
134+
135+
let port = port_check::free_local_port_in_range(9100, 9199).unwrap();
136+
let server = Server::builder()
137+
.build(format!("0.0.0.0:{port}"))
138+
.await
139+
.unwrap();
140+
let handle = server.start(module);
141+
142+
let http_client = ReqwestClient::new(format!("http://127.0.0.1:{port}"));
143+
// Test first five blocks
144+
let request = Request::Single(Call::MethodCall(MethodCall {
145+
jsonrpc: Some(Version::V2),
146+
method: "ic_getBlocksRLP".to_string(),
147+
params: Params::Array(vec![json!("0x0"), json!("0x5")]),
148+
id: Id::Str("ic_getBlocksRLP".to_string()),
149+
}));
150+
151+
let Response::Single(Output::Success(result)) =
152+
http_client.send_rpc_request(request).await.unwrap()
153+
else {
154+
panic!("unexpected return type")
155+
};
156+
157+
let data: String = serde_json::from_value(result.result).unwrap();
158+
let blocks: Vec<did::Block<did::Transaction>> =
159+
ethers_core::utils::rlp::decode_list(&hex::decode(data).unwrap());
160+
assert_eq!(blocks.len(), 5);
161+
assert_eq!(blocks[0].number, 0u64.into());
162+
assert_eq!(blocks[4].number, 4u64.into());
163+
164+
// Test last 2 blocks
165+
let request = Request::Single(Call::MethodCall(MethodCall {
166+
jsonrpc: Some(Version::V2),
167+
method: "ic_getBlocksRLP".to_string(),
168+
params: Params::Array(vec![json!("0x8"), json!("0x5")]),
169+
id: Id::Str("ic_getBlocksRLP".to_string()),
170+
}));
171+
172+
let Response::Single(Output::Success(result)) =
173+
http_client.send_rpc_request(request).await.unwrap()
174+
else {
175+
panic!("unexpected return type")
176+
};
177+
178+
let data: String = serde_json::from_value(result.result).unwrap();
179+
let blocks: Vec<did::Block<did::Transaction>> =
180+
ethers_core::utils::rlp::decode_list(&hex::decode(data).unwrap());
181+
assert_eq!(blocks.len(), 2);
182+
assert_eq!(blocks[0].number, 8u64.into());
183+
assert_eq!(blocks[1].number, 9u64.into());
184+
185+
{
186+
handle.stop().unwrap();
187+
handle.stopped().await;
188+
}
189+
})
190+
.await
191+
}
192+
193+
#[tokio::test]
194+
async fn test_batched_request() {
195+
with_filled_db(|db_client| async {
196+
let eth = EthImpl::new(db_client);
197+
let mut module = RpcModule::new(());
198+
module.merge(EthServer::into_rpc(eth.clone())).unwrap();
199+
module.merge(ICServer::into_rpc(eth)).unwrap();
200+
201+
let port = port_check::free_local_port_in_range(9200, 9299).unwrap();
202+
let server = Server::builder()
203+
.build(format!("0.0.0.0:{port}"))
204+
.await
205+
.unwrap();
206+
let handle = server.start(module);
207+
208+
let http_client = ReqwestClient::new(format!("http://127.0.0.1:{port}"));
209+
let request = Request::Batch(vec![
210+
Call::MethodCall(MethodCall {
211+
jsonrpc: Some(Version::V2),
212+
method: "ic_getBlocksRLP".to_string(),
213+
params: Params::Array(vec![json!("0x0"), json!("0x5")]),
214+
id: Id::Str("ic_getBlocksRLP".to_string()),
215+
}),
216+
Call::MethodCall(MethodCall {
217+
jsonrpc: Some(Version::V2),
218+
method: "eth_blockNumber".to_string(),
219+
params: Params::Array(vec![]),
220+
id: Id::Str("eth_blockNumber".to_string()),
221+
}),
222+
]);
223+
224+
let Response::Batch(results) = http_client.send_rpc_request(request).await.unwrap() else {
225+
panic!("unexpected return type")
226+
};
227+
228+
match &results[..] {
229+
[Output::Success(result_1), Output::Success(result_2)] => {
230+
assert_eq!(result_1.id, Id::Str("ic_getBlocksRLP".to_string()));
231+
let data: String = serde_json::from_value(result_1.result.clone()).unwrap();
232+
let blocks: Vec<did::Block<did::Transaction>> =
233+
ethers_core::utils::rlp::decode_list(&hex::decode(data).unwrap());
234+
assert_eq!(blocks.len(), 5);
235+
236+
assert_eq!(result_2.id, Id::Str("eth_blockNumber".to_string()));
237+
let data: String = serde_json::from_value(result_2.result.clone()).unwrap();
238+
let result = u64::from_str_radix(data.trim_start_matches("0x"), 16).unwrap();
239+
assert_eq!(result, BLOCK_COUNT - 1);
240+
}
241+
_ => panic!("unexpected results"),
242+
}
243+
244+
{
245+
handle.stop().unwrap();
246+
handle.stopped().await;
247+
}
248+
})
249+
.await
250+
}

0 commit comments

Comments
 (0)