diff --git a/Cargo.lock b/Cargo.lock index 662166499a1..fee08e1b48d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,6 +1829,33 @@ dependencies = [ "uint", ] +[[package]] +name = "ethers" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16142eeb3155cfa5aec6be3f828a28513a28bd995534f945fa70e7d608f16c10" +dependencies = [ + "ethers-addressbook", + "ethers-contract", + "ethers-core", + "ethers-etherscan", + "ethers-middleware", + "ethers-providers", + "ethers-signers", +] + +[[package]] +name = "ethers-addressbook" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e23f8992ecf45ea9dd2983696aabc566c108723585f07f5dc8c9efb24e52d3db" +dependencies = [ + "ethers-core", + "once_cell", + "serde", + "serde_json", +] + [[package]] name = "ethers-contract" version = "0.17.0" @@ -2448,11 +2475,14 @@ version = "0.0.0" dependencies = [ "async-std", "chrono", + "ethers", "fuel-core", "fuel-core-interfaces", "fuel-gql-client", + "fuel-relayer", "fuel-txpool", "futures", + "hyper", "insta", "itertools", "rand 0.8.5", @@ -2461,6 +2491,8 @@ dependencies = [ "serde_json", "tempfile", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -4321,9 +4353,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048" +checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" dependencies = [ "thiserror", "ucd-trie", @@ -5551,9 +5583,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snow" @@ -5843,18 +5875,18 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-case" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aea929e9488998b64adc414c29fe5620398f01c2e3f58164122b17e567a6d5" +checksum = "21d6cf5a7dffb3f9dceec8e6b8ca528d9bd71d36c9f074defb548ce161f598c0" dependencies = [ "test-case-macros", ] [[package]] name = "test-case-macros" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95968eedc6fc4f5c21920e0f4264f78ec5e4c56bb394f319becc1a5830b3e54" +checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26" dependencies = [ "cfg-if", "proc-macro-error", @@ -5937,9 +5969,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ "itoa", "libc", @@ -6152,7 +6184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e" dependencies = [ "crossbeam-channel", - "time 0.3.14", + "time 0.3.15", "tracing-subscriber", ] diff --git a/fuel-tests/Cargo.toml b/fuel-tests/Cargo.toml index 733aaec0046..193ded16f93 100644 --- a/fuel-tests/Cargo.toml +++ b/fuel-tests/Cargo.toml @@ -19,11 +19,14 @@ harness = true [dependencies] async-std = "1.12" chrono = { version = "0.4", features = ["serde"] } +ethers = "0.17" fuel-core = { path = "../fuel-core", default-features = false } fuel-core-interfaces = { path = "../fuel-core-interfaces", features = ["test-helpers"] } fuel-gql-client = { path = "../fuel-client", features = ["test-helpers"] } +fuel-relayer = { path = "../fuel-relayer", features = ["test-helpers"] } fuel-txpool = { path = "../fuel-txpool" } futures = "0.3" +hyper = { version = "0.14", features = ["server"] } insta = "1.8" itertools = "0.10" rand = "0.8" @@ -32,10 +35,12 @@ rstest = "0.15" serde_json = "1.0" tempfile = "3.3" tokio = { version = "1.21", features = ["macros", "rt-multi-thread"] } +tracing = "0.1" +tracing-subscriber = "0.3" [features] metrics = ["fuel-core/rocksdb", "fuel-core/metrics"] -default = ["fuel-core/default", "metrics"] +default = ["fuel-core/default", "metrics", "relayer"] debug = ["fuel-core-interfaces/debug"] p2p = ["fuel-core/p2p"] relayer = ["fuel-core/relayer"] diff --git a/fuel-tests/tests/lib.rs b/fuel-tests/tests/lib.rs index bc0ed4684d4..9283f39522d 100644 --- a/fuel-tests/tests/lib.rs +++ b/fuel-tests/tests/lib.rs @@ -11,6 +11,8 @@ mod messages; #[cfg(feature = "metrics")] mod metrics; mod node_info; +#[cfg(feature = "relayer")] +mod relayer; mod resource; mod snapshot; mod tx; diff --git a/fuel-tests/tests/relayer.rs b/fuel-tests/tests/relayer.rs new file mode 100644 index 00000000000..fa282058228 --- /dev/null +++ b/fuel-tests/tests/relayer.rs @@ -0,0 +1,161 @@ +use std::{ + net::Ipv4Addr, + sync::Arc, + time::Duration, +}; + +use ethers::providers::Middleware; +use fuel_core::{ + database::Database, + service::{ + Config, + FuelService, + }, +}; +use fuel_core_interfaces::db::Messages; +use fuel_gql_client::prelude::StorageAsRef; + +use fuel_relayer::test_helpers::{ + middleware::MockMiddleware, + EvtToLog, + LogTestHelper, +}; +use hyper::{ + service::{ + make_service_fn, + service_fn, + }, + Body, + Request, + Response, + Server, +}; +use serde_json::json; +use std::{ + convert::Infallible, + net::SocketAddr, +}; + +async fn handle( + mock: Arc, + req: Request, +) -> Result, Infallible> { + let body = hyper::body::to_bytes(req).await.unwrap(); + + let v: serde_json::Value = serde_json::from_slice(body.as_ref()).unwrap(); + let mut o = match v { + serde_json::Value::Object(o) => o, + _ => unreachable!(), + }; + let id = o.get("id").unwrap().as_u64().unwrap(); + let method = o.get("method").unwrap().as_str().unwrap(); + let r = match method { + "eth_blockNumber" => { + let r = mock.get_block_number().await.unwrap(); + json!({ "id": id, "jsonrpc": "2.0", "result": r }) + } + "eth_syncing" => { + let r = mock.syncing().await.unwrap(); + match r { + ethers::providers::SyncingStatus::IsFalse => { + json!({ "id": id, "jsonrpc": "2.0", "result": false }) + } + ethers::providers::SyncingStatus::IsSyncing { + starting_block, + current_block, + highest_block, + } => { + json!({ "id": id, "jsonrpc": "2.0", "result": { + "starting_block": starting_block, + "current_block": current_block, + "highest_block": highest_block, + } }) + } + } + } + "eth_getLogs" => { + let params = o.remove("params").unwrap(); + let params: Vec<_> = serde_json::from_value(params).unwrap(); + let r = mock.get_logs(¶ms[0]).await.unwrap(); + json!({ "id": id, "jsonrpc": "2.0", "result": r }) + } + _ => unreachable!(), + }; + + let r = serde_json::to_vec(&r).unwrap(); + + Ok(Response::new(Body::from(r))) +} + +#[tokio::test(flavor = "multi_thread")] +async fn relayer_can_download_logs() { + let mut config = Config::local_node(); + let eth_node = MockMiddleware::default(); + let contract_address = config.relayer.eth_v2_listening_contracts[0]; + let message = |nonce, block_number: u64| { + let message = fuel_relayer::bridge::SentMessageFilter { + nonce, + ..Default::default() + }; + let mut log = message.into_log(); + log.address = contract_address; + log.block_number = Some(block_number.into()); + log + }; + + let logs = vec![message(1, 3), message(2, 5)]; + let expected_messages: Vec<_> = logs.iter().map(|l| l.to_msg()).collect(); + eth_node.update_data(|data| data.logs_batch = vec![logs.clone()]); + // Setup the eth node with a block high enough that there + // will be some finalized blocks. + eth_node.update_data(|data| data.best_block.number = Some(200.into())); + let eth_node = Arc::new(eth_node); + + // Construct our SocketAddr to listen on... + let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, 0)); + + // And a MakeService to handle each connection... + let make_service = make_service_fn(move |_conn| { + let eth_node = eth_node.clone(); + async move { + Ok::<_, Infallible>(service_fn({ + let eth_node = eth_node.clone(); + move |req| handle(eth_node.clone(), req) + })) + } + }); + + // Then bind and serve... + let server = Server::bind(&addr).serve(make_service); + let addr = server.local_addr(); + + let (shutdown, rx) = tokio::sync::oneshot::channel(); + + tokio::spawn(async move { + let graceful = server.with_graceful_shutdown(async { + rx.await.ok(); + }); + // And run forever... + if let Err(e) = graceful.await { + eprintln!("server error: {}", e); + } + }); + + config.relayer.eth_client = + Some(format!("http://{}", addr).as_str().try_into().unwrap()); + let db = Database::in_memory(); + + let srv = FuelService::from_database(db.clone(), config) + .await + .unwrap(); + + tokio::time::sleep(Duration::from_secs(10)).await; + for msg in expected_messages { + assert_eq!( + &*db.storage::().get(msg.id()).unwrap().unwrap(), + &*msg + ); + } + srv.stop().await; + shutdown.send(()).unwrap(); +}