diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index e9752134db..7f5b4803ed 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -10,6 +10,7 @@ path = "common.rs" doctest = false [features] +for-tests = [] track-ctx-pointer = ["shared_ref_counter/enable", "shared_ref_counter/log"] [dependencies] diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 288882d0ae..117a84bff9 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -1080,7 +1080,17 @@ impl Default for PagingOptionsEnum { } #[inline(always)] -pub fn get_utc_timestamp() -> i64 { Utc::now().timestamp() } +pub fn get_utc_timestamp() -> i64 { + // get_utc_timestamp for tests allowing to add some bias to 'now' + #[cfg(feature = "for-tests")] + return Utc::now().timestamp() + + std::env::var("TEST_TIMESTAMP_OFFSET") + .map(|s| s.as_str().parse::().unwrap_or_default()) + .unwrap_or_default(); + + #[cfg(not(feature = "for-tests"))] + return Utc::now().timestamp(); +} #[inline(always)] pub fn get_utc_timestamp_nanos() -> i64 { Utc::now().timestamp_nanos() } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 22bb3e705b..03ff237fb4 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -124,6 +124,7 @@ winapi = "0.3" [dev-dependencies] coins = { path = "../coins", features = ["for-tests"] } coins_activation = { path = "../coins_activation", features = ["for-tests"] } +common = { path = "../common", features = ["for-tests"] } mm2_test_helpers = { path = "../mm2_test_helpers" } trading_api = { path = "../trading_api", features = ["mocktopus"] } mocktopus = "0.8.0" diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 2a253760bc..b4f074857f 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -16,6 +16,7 @@ use common::{block_on, block_on_f01, executor::Timer, get_utc_timestamp, now_sec use crypto::privkey::key_pair_from_seed; use crypto::{CryptoCtx, DerivationPath, KeyPairPolicy}; use http::StatusCode; +use mm2_libp2p::behaviours::atomicdex::MAX_TIME_GAP_FOR_CONNECTED_PEER; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_eth_coin, enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, get_locked_amount, @@ -25,6 +26,7 @@ use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, di use mm2_test_helpers::{get_passphrase, structs::*}; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; use std::env; use std::iter::FromIterator; use std::str::FromStr; @@ -5471,3 +5473,82 @@ fn test_approve_erc20() { block_on(mm.stop()).unwrap(); } + +#[test] +fn test_peer_time_sync_validation() { + let timeoffset_tolerable = TryInto::::try_into(MAX_TIME_GAP_FOR_CONNECTED_PEER).unwrap() - 1; + let timeoffset_too_big = TryInto::::try_into(MAX_TIME_GAP_FOR_CONNECTED_PEER).unwrap() + 1; + + let start_peers_with_time_offset = |offset: i64| -> (Json, Json) { + let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 10.into()); + let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN1", 10.into()); + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let bob_conf = Mm2TestConf::seednode(&hex::encode(bob_priv_key), &coins); + let mut mm_bob = block_on(MarketMakerIt::start_with_envs( + bob_conf.conf, + bob_conf.rpc_password, + None, + &[], + )) + .unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); + block_on(mm_bob.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); + let alice_conf = + Mm2TestConf::light_node(&hex::encode(alice_priv_key), &coins, &[mm_bob.ip.to_string().as_str()]); + let mut mm_alice = block_on(MarketMakerIt::start_with_envs( + alice_conf.conf, + alice_conf.rpc_password, + None, + &[("TEST_TIMESTAMP_OFFSET", offset.to_string().as_str())], + )) + .unwrap(); + let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); + block_on(mm_alice.wait_for_log(22., |log| log.contains(">>>>>>>>> DEX stats "))).unwrap(); + + let res_bob = block_on(mm_bob.rpc(&json!({ + "userpass": mm_bob.userpass, + "method": "get_directly_connected_peers", + }))) + .unwrap(); + assert!(res_bob.0.is_success(), "!get_directly_connected_peers: {}", res_bob.1); + let bob_peers = serde_json::from_str::(&res_bob.1).unwrap(); + + let res_alice = block_on(mm_alice.rpc(&json!({ + "userpass": mm_alice.userpass, + "method": "get_directly_connected_peers", + }))) + .unwrap(); + assert!( + res_alice.0.is_success(), + "!get_directly_connected_peers: {}", + res_alice.1 + ); + let alice_peers = serde_json::from_str::(&res_alice.1).unwrap(); + + block_on(mm_bob.stop()).unwrap(); + block_on(mm_alice.stop()).unwrap(); + (bob_peers, alice_peers) + }; + + // check with small time offset: + let (bob_peers, alice_peers) = start_peers_with_time_offset(timeoffset_tolerable); + assert!( + bob_peers["result"].as_object().unwrap().len() == 1, + "bob must have one peer" + ); + assert!( + alice_peers["result"].as_object().unwrap().len() == 1, + "alice must have one peer" + ); + + // check with too big time offset: + let (bob_peers, alice_peers) = start_peers_with_time_offset(timeoffset_too_big); + assert!( + bob_peers["result"].as_object().unwrap().is_empty(), + "bob must have no peers" + ); + assert!( + alice_peers["result"].as_object().unwrap().is_empty(), + "alice must have no peers" + ); +} diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index 41a82c3c16..f4213ecd8c 100644 --- a/mm2src/mm2_p2p/Cargo.toml +++ b/mm2src/mm2_p2p/Cargo.toml @@ -51,3 +51,4 @@ timed-map = { version = "1.1.1", features = ["rustc-hash"] } [dev-dependencies] async-std = "1.6.2" env_logger = "0.9.3" +common = { path = "../common", features = ["for-tests"] }