From 50acc0e86e92ce54edeedd04370fabda791149d8 Mon Sep 17 00:00:00 2001 From: dimxy Date: Fri, 8 Sep 2023 22:55:39 +0500 Subject: [PATCH 01/40] fix(tests): fix regex parsing passphrase from .env file (#1931) Fix regex to parse passphrase from .env file if newline char or space is added --- docs/DEV_ENVIRONMENT.md | 4 +- mm2src/coins/eth/eth_tests.rs | 2 +- .../tests/docker_tests/swap_watcher_tests.rs | 4 +- .../tests/mm2_tests/best_orders_tests.rs | 2 +- mm2src/mm2_main/tests/mm2_tests/eth_tests.rs | 4 +- .../tests/mm2_tests/mm2_tests_inner.rs | 38 +++++++++---------- mm2src/mm2_test_helpers/src/for_tests.rs | 31 ++++++++++++++- 7 files changed, 57 insertions(+), 28 deletions(-) diff --git a/docs/DEV_ENVIRONMENT.md b/docs/DEV_ENVIRONMENT.md index 317bb4eb2c..d348b8a64f 100644 --- a/docs/DEV_ENVIRONMENT.md +++ b/docs/DEV_ENVIRONMENT.md @@ -9,11 +9,11 @@ [Unix/Linux](https://github.com/KomodoPlatform/komodo/blob/master/zcutil/fetch-params.sh) 5. Create `.env.client` file with the following content ``` - PASSPHRASE=spice describe gravity federal blast come thank unfair canal monkey style afraid + ALICE_PASSPHRASE=spice describe gravity federal blast come thank unfair canal monkey style afraid ``` 6. Create `.env.seed` file with the following content ``` - PASSPHRASE=also shoot benefit prefer juice shell elder veteran woman mimic image kidney + BOB_PASSPHRASE=also shoot benefit prefer juice shell elder veteran woman mimic image kidney ``` 7. MacOS specific: run script (required after each reboot) ```shell diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 5ab37c7e9b..9014a47860 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -60,7 +60,7 @@ pub fn jst_distributor() -> EthCoin { "urls": ETH_DEV_NODES, "swap_contract_address": ETH_DEV_SWAP_CONTRACT, }); - let seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let seed = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); let keypair = key_pair_from_seed(&seed).unwrap(); let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(keypair.private().secret); block_on(eth_coin_from_conf_and_request( diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 383ebf0879..1936955c06 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -1313,7 +1313,7 @@ fn test_watcher_validate_taker_payment_eth() { let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); let taker_pub = taker_keypair.public(); - let maker_seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let maker_seed = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); let maker_keypair = key_pair_from_seed(&maker_seed).unwrap(); let maker_pub = maker_keypair.public(); @@ -1557,7 +1557,7 @@ fn test_watcher_validate_taker_payment_erc20() { let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); let taker_pub = taker_keypair.public(); - let maker_seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let maker_seed = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); let maker_keypair = key_pair_from_seed(&maker_seed).unwrap(); let maker_pub = maker_keypair.public(); diff --git a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs index fcf6629642..283169115b 100644 --- a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs @@ -468,7 +468,7 @@ fn test_best_orders_v2_exclude_mine() { assert!(status.is_success(), "!setprice: {}", data); } - let alice_passphrase = get_passphrase(&".env.seed", "ALICE_PASSPHRASE").unwrap(); + let alice_passphrase = get_passphrase(&".env.client", "ALICE_PASSPHRASE").unwrap(); let mm_alice = MarketMakerIt::start( json! ({ "gui": "nogui", diff --git a/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs b/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs index 676145fc84..9d62aaf97f 100644 --- a/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs @@ -78,7 +78,7 @@ async fn enable_eth_with_tokens_without_balance( #[test] #[cfg(not(target_arch = "wasm32"))] fn test_disable_eth_coin_with_token() { - let passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([eth_testnet_conf(), eth_jst_testnet_conf(),]); let conf = Mm2TestConf::seednode(&passphrase, &coins); let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); @@ -123,7 +123,7 @@ fn test_disable_eth_coin_with_token() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_disable_eth_coin_with_token_without_balance() { - let passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([eth_testnet_conf(), eth_jst_testnet_conf(),]); let conf = Mm2TestConf::seednode(&passphrase, &coins); let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 2bfff0f3b6..87420af2d4 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -1328,7 +1328,7 @@ fn test_withdraw_legacy() { let alice_passphrase = var("ALICE_PASSPHRASE") .ok() .or(alice_file_passphrase) - .expect("No ALICE_PASSPHRASE or .env.client/PASSPHRASE"); + .expect("No ALICE_PASSPHRASE or .env.client/ALICE_PASSPHRASE"); let coins = json!([ {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, @@ -2535,7 +2535,7 @@ fn setprice_buy_sell_too_low_volume() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_fill_or_kill_taker_order_should_not_transform_to_maker() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -2603,7 +2603,7 @@ fn test_fill_or_kill_taker_order_should_not_transform_to_maker() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_gtc_taker_order_should_transform_to_maker() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -2676,7 +2676,7 @@ fn test_gtc_taker_order_should_transform_to_maker() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_set_price_must_save_order_to_db() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -2729,7 +2729,7 @@ fn test_set_price_must_save_order_to_db() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_set_price_response_format() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -3881,7 +3881,7 @@ fn test_validateaddress() { let bob_passphrase = var("BOB_PASSPHRASE") .ok() .or(bob_file_passphrase) - .expect("No BOB_PASSPHRASE or .env.seed/PASSPHRASE"); + .expect("No BOB_PASSPHRASE or .env.seed/BOB_PASSPHRASE"); let mm = MarketMakerIt::start( json!({ @@ -4835,7 +4835,7 @@ fn test_tx_history_tbtc_non_segwit() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_buy_conf_settings() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":ETH_DEV_TOKEN_CONTRACT}},"required_confirmations":2},]); @@ -4908,7 +4908,7 @@ fn test_buy_conf_settings() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_buy_response_format() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -4958,7 +4958,7 @@ fn test_buy_response_format() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sell_response_format() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -5008,7 +5008,7 @@ fn test_sell_response_format() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_my_orders_response_format() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -5168,7 +5168,7 @@ fn test_my_orders_after_matched() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sell_conf_settings() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address": ETH_DEV_TOKEN_CONTRACT}},"required_confirmations":2},]); @@ -5241,7 +5241,7 @@ fn test_sell_conf_settings() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_set_price_conf_settings() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address": ETH_DEV_TOKEN_CONTRACT}},"required_confirmations":2},]); @@ -5314,7 +5314,7 @@ fn test_set_price_conf_settings() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_update_maker_order() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json! ([ {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, @@ -5454,7 +5454,7 @@ fn test_update_maker_order() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_update_maker_order_fail() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json! ([ {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, @@ -5987,7 +5987,7 @@ fn test_orderbook_is_mine_orders() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sell_min_volume() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -6060,7 +6060,7 @@ fn test_sell_min_volume() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sell_min_volume_dust() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json! ([ {"coin":"RICK","asset":"RICK","dust":10000000,"required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, @@ -6111,7 +6111,7 @@ fn test_sell_min_volume_dust() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_setprice_min_volume_dust() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json! ([ {"coin":"RICK","asset":"RICK","dust":10000000,"required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, @@ -6159,7 +6159,7 @@ fn test_setprice_min_volume_dust() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_buy_min_volume() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); @@ -6368,7 +6368,7 @@ fn test_orderbook_depth() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_mm2_db_migration() { - let bob_passphrase = get_passphrase(&".env.client", "BOB_PASSPHRASE").unwrap(); + let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let coins = json!([rick_conf(), morty_conf(), eth_testnet_conf(), eth_jst_testnet_conf(),]); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index d6f1d75fbd..ce23e32558 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -1535,7 +1535,7 @@ pub async fn enable_qrc20( pub fn from_env_file(env: Vec) -> (Option, Option) { use regex::bytes::Regex; let (mut passphrase, mut userpass) = (None, None); - for cap in Regex::new(r"(?m)^(PASSPHRASE|USERPASS)=(\w[\w ]+)$") + for cap in Regex::new(r"^\w+_(PASSPHRASE|USERPASS)=(\w+( \w+)+)\s*") .unwrap() .captures_iter(&env) { @@ -1571,6 +1571,9 @@ macro_rules! get_passphrase { } /// Reads passphrase from file or environment. +/// Note that if you try to read the passphrase file from the current directory +/// the current directory could be different depending on how you run tests +/// (it could be either the workspace directory or the module source directory) #[cfg(not(target_arch = "wasm32"))] pub fn get_passphrase(path: &dyn AsRef, env: &str) -> Result { if let (Some(file_passphrase), _file_userpass) = from_env_file(try_s!(slurp(path))) { @@ -2985,3 +2988,29 @@ pub async fn get_locked_amount(mm: &MarketMakerIt, coin: &str) -> GetLockedAmoun let response: RpcV2Response = json::from_str(&request.1).unwrap(); response.result } + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_parse_env_file() { + let env_client = + b"ALICE_PASSPHRASE=spice describe gravity federal blast come thank unfair canal monkey style afraid"; + let env_client_new_line = + b"ALICE_PASSPHRASE=spice describe gravity federal blast come thank unfair canal monkey style afraid\n"; + let env_client_space = + b"ALICE_PASSPHRASE=spice describe gravity federal blast come thank unfair canal monkey style afraid "; + + let parsed1 = from_env_file(env_client.to_vec()); + let parsed2 = from_env_file(env_client_new_line.to_vec()); + let parsed3 = from_env_file(env_client_space.to_vec()); + assert_eq!(parsed1, parsed2); + assert_eq!(parsed1, parsed3); + assert_eq!( + parsed1, + ( + Some(String::from( + "spice describe gravity federal blast come thank unfair canal monkey style afraid" + )), + None + ) + ); +} From 06a095744d2c4ac2ebbbbb0aee9b26964c05c638 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Mon, 11 Sep 2023 07:39:55 +0100 Subject: [PATCH 02/40] chore(docs): run WASM tests with Cargo (#1965) * Running WASM tests with Cargo --- docs/DEV_ENVIRONMENT.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/DEV_ENVIRONMENT.md b/docs/DEV_ENVIRONMENT.md index d348b8a64f..f6185b7249 100644 --- a/docs/DEV_ENVIRONMENT.md +++ b/docs/DEV_ENVIRONMENT.md @@ -52,7 +52,7 @@ export BOB_PASSPHRASE="also shoot benefit prefer juice shell elder veteran woman mimic image kidney" export ALICE_PASSPHRASE="spice describe gravity federal blast come thank unfair canal monkey style afraid" ``` -6. Run WASM tests +5. Run WASM tests - for Linux users: ``` wasm-pack test --firefox --headless mm2src/mm2_main @@ -66,5 +66,16 @@ CC=/opt/homebrew/opt/llvm/bin/clang AR=/opt/homebrew/opt/llvm/bin/llvm-ar wasm-pack test --firefox --headless mm2src/mm2_main ``` Please note `CC` and `AR` must be specified in the same line as `wasm-pack test mm2src/mm2_main`. +#### Running specific WASM tests with Cargo
+ - Install `wasm-bindgen-cli`:
+ Make sure you have wasm-bindgen-cli installed with a version that matches the one specified in your Cargo.toml file. + You can install it using Cargo with the following command: + ``` + cargo install -f wasm-bindgen-cli --version + ``` + - Run + ``` + cargo test --target wasm32-unknown-unknown --package coins --lib utxo::utxo_block_header_storage::wasm::indexeddb_block_header_storage + ``` PS If you notice that this guide is outdated, please submit a PR. From d055cbaf6922adf828dc3f8058f2e7d4fe1a840d Mon Sep 17 00:00:00 2001 From: smk762 <35845239+smk762@users.noreply.github.com> Date: Tue, 12 Sep 2023 00:22:11 +0800 Subject: [PATCH 03/40] test(zhtlc): Use alternative grpc service name for pirate lightwalletd (#1963) Infrastructure for ARRR lightwallet servers uses a fork of lightwalletd, this commit renames the grpc service from cash.z.wallet.sdk.rpc; to pirate.wallet.sdk.rpc; to use the lightwalletd fork. --- mm2src/coins/z_coin/compact_formats.proto | 2 +- mm2src/coins/z_coin/service.proto | 2 +- mm2src/coins/z_coin/z_rpc.rs | 2 +- .../mm2_main/tests/integration_tests_common/mod.rs | 2 +- mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs | 2 +- mm2src/mm2_test_helpers/src/for_tests.rs | 14 ++++++++++++-- 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/z_coin/compact_formats.proto b/mm2src/coins/z_coin/compact_formats.proto index e20c05a56b..9a88707355 100644 --- a/mm2src/coins/z_coin/compact_formats.proto +++ b/mm2src/coins/z_coin/compact_formats.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package cash.z.wallet.sdk.rpc; +package pirate.wallet.sdk.rpc; option go_package = "walletrpc"; option swift_prefix = ""; // Remember that proto3 fields are all optional. A field that is not present will be set to its zero value. diff --git a/mm2src/coins/z_coin/service.proto b/mm2src/coins/z_coin/service.proto index d0a7085675..e8cd6f6ea2 100644 --- a/mm2src/coins/z_coin/service.proto +++ b/mm2src/coins/z_coin/service.proto @@ -3,7 +3,7 @@ // file COPYING or https://www.opensource.org/licenses/mit-license.php . syntax = "proto3"; -package cash.z.wallet.sdk.rpc; +package pirate.wallet.sdk.rpc; option go_package = ".;walletrpc"; option swift_prefix = ""; import "compact_formats.proto"; diff --git a/mm2src/coins/z_coin/z_rpc.rs b/mm2src/coins/z_coin/z_rpc.rs index 6615847287..070c00e53f 100644 --- a/mm2src/coins/z_coin/z_rpc.rs +++ b/mm2src/coins/z_coin/z_rpc.rs @@ -49,7 +49,7 @@ cfg_native!( use zcash_client_sqlite::WalletDb; mod z_coin_grpc { - tonic::include_proto!("cash.z.wallet.sdk.rpc"); + tonic::include_proto!("pirate.wallet.sdk.rpc"); } use z_coin_grpc::TreeState; use z_coin_grpc::compact_tx_streamer_client::CompactTxStreamerClient; diff --git a/mm2src/mm2_main/tests/integration_tests_common/mod.rs b/mm2src/mm2_main/tests/integration_tests_common/mod.rs index 3568b610ad..12075d2974 100644 --- a/mm2src/mm2_main/tests/integration_tests_common/mod.rs +++ b/mm2src/mm2_main/tests/integration_tests_common/mod.rs @@ -93,7 +93,7 @@ pub async fn enable_z_coin_light( ) -> ZCoinActivationResult { let init = init_z_coin_light(mm, coin, electrums, lightwalletd_urls, starting_date, account).await; let init: RpcV2Response = json::from_value(init).unwrap(); - let timeout = wait_until_ms(60000); + let timeout = wait_until_ms(600000); loop { if now_ms() > timeout { diff --git a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs index 1d2b58362c..7d9d1e45b8 100644 --- a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs @@ -82,7 +82,7 @@ fn activate_z_coin_light_with_changing_height() { ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, None, - Some(0), + None, )); let old_first_sync_block = activation_result.first_sync_block; diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index ce23e32558..307ad1e856 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -140,8 +140,18 @@ pub const MORTY_ELECTRUM_ADDRS: &[&str] = &[ ]; pub const ZOMBIE_TICKER: &str = "ZOMBIE"; pub const ARRR: &str = "ARRR"; -pub const ZOMBIE_ELECTRUMS: &[&str] = &["zombie.dragonhound.info:10033"]; -pub const ZOMBIE_LIGHTWALLETD_URLS: &[&str] = &["http://zombie.dragonhound.info:443"]; +pub const ZOMBIE_ELECTRUMS: &[&str] = &[ + "electrum1.cipig.net:10008", + "electrum2.cipig.net:10008", + "electrum3.cipig.net:10008", +]; +pub const ZOMBIE_LIGHTWALLETD_URLS: &[&str] = &[ + "https://lightd1.pirate.black:443", + "https://piratelightd1.cryptoforge.cc:443", + "https://piratelightd2.cryptoforge.cc:443", + "https://piratelightd3.cryptoforge.cc:443", + "https://piratelightd4.cryptoforge.cc:443", +]; pub const PIRATE_ELECTRUMS: &[&str] = &["node1.chainkeeper.pro:10132"]; pub const PIRATE_LIGHTWALLETD_URLS: &[&str] = &["http://node1.chainkeeper.pro:443"]; pub const DEFAULT_RPC_PASSWORD: &str = "pass"; From b34c91a02924306f7ac04b25bf9dc0a094ec194e Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Mon, 18 Sep 2023 09:20:54 +0100 Subject: [PATCH 04/40] fix(zcoin): resume previous sync if sync_params are not provided (#1967) --- mm2src/coins/z_coin/storage/walletdb/mod.rs | 2 ++ mm2src/coins/z_coin/z_rpc.rs | 27 +++++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/mm2src/coins/z_coin/storage/walletdb/mod.rs b/mm2src/coins/z_coin/storage/walletdb/mod.rs index 698a54a60b..656993cd4b 100644 --- a/mm2src/coins/z_coin/storage/walletdb/mod.rs +++ b/mm2src/coins/z_coin/storage/walletdb/mod.rs @@ -40,6 +40,7 @@ impl<'a> WalletDbShared { zcoin_builder: &ZCoinBuilder<'a>, checkpoint_block: Option, z_spending_key: &ExtendedSpendingKey, + continue_from_prev_sync: bool, ) -> MmResult { let wallet_db = create_wallet_db( zcoin_builder @@ -48,6 +49,7 @@ impl<'a> WalletDbShared { zcoin_builder.protocol_info.consensus_params.clone(), checkpoint_block, ExtendedFullViewingKey::from(z_spending_key), + continue_from_prev_sync, ) .await .mm_err(WalletDbError::ZcoinClientInitError)?; diff --git a/mm2src/coins/z_coin/z_rpc.rs b/mm2src/coins/z_coin/z_rpc.rs index 070c00e53f..5996762444 100644 --- a/mm2src/coins/z_coin/z_rpc.rs +++ b/mm2src/coins/z_coin/z_rpc.rs @@ -365,6 +365,7 @@ pub async fn create_wallet_db( consensus_params: ZcoinConsensusParams, checkpoint_block: Option, evk: ExtendedFullViewingKey, + continue_from_prev_sync: bool, ) -> Result, MmError> { async_blocking({ move || -> Result, MmError> { @@ -380,8 +381,14 @@ pub async fn create_wallet_db( // Check if the initial block height is less than the previous synchronization height and // Rewind walletdb to the minimum possible height. - if db.get_extended_full_viewing_keys()?.is_empty() || init_block_height != min_sync_height { - info!("Older/Newer sync height detected!, rewinding walletdb to new height: {init_block_height:?}"); + if db.get_extended_full_viewing_keys()?.is_empty() + || (!continue_from_prev_sync && init_block_height != min_sync_height) + { + // let user know we're clearing cache and resyncing from new provided height. + if min_sync_height.unwrap_or(0) > 0 { + info!("Older/Newer sync height detected!, rewinding walletdb to new height: {init_block_height:?}"); + } + let mut wallet_ops = db.get_update_ops().expect("get_update_ops always returns Ok"); wallet_ops .rewind_to_height(u32::MIN.into()) @@ -477,14 +484,18 @@ pub(super) async fn init_light_client<'a>( let maybe_checkpoint_block = light_rpc_clients .checkpoint_block_from_height(sync_height.max(sapling_activation_height)) .await?; - - let wallet_db = WalletDbShared::new(builder, maybe_checkpoint_block, z_spending_key) + let min_height = blocks_db.get_earliest_block().await?; + // check if no sync_params was provided and continue syncing from last height in db if it's > 0. + let continue_from_prev_sync = min_height > 0 && sync_params.is_none(); + let wallet_db = WalletDbShared::new(builder, maybe_checkpoint_block, z_spending_key, continue_from_prev_sync) .await .mm_err(|err| ZcoinClientInitError::ZcashDBError(err.to_string()))?; - // Get min_height in blocks_db and rewind blocks_db to 0 if sync_height != min_height - let min_height = blocks_db.get_earliest_block().await?; - if sync_height != min_height as u64 { + if !continue_from_prev_sync && (sync_height != min_height as u64) { + // let user know we're clearing cache and resyncing from new provided height. + if min_height > 0 { + info!("Older/Newer sync height detected!, rewinding blocks_db to new height: {sync_height:?}"); + } blocks_db .rewind_to_height(u32::MIN) .map_err(|err| ZcoinClientInitError::ZcashDBError(err.to_string()))?; @@ -546,7 +557,7 @@ pub(super) async fn init_native_client<'a>( is_pre_sapling: false, actual: checkpoint_height, }; - let wallet_db = WalletDbShared::new(builder, checkpoint_block, z_spending_key) + let wallet_db = WalletDbShared::new(builder, checkpoint_block, z_spending_key, true) .await .mm_err(|err| ZcoinClientInitError::ZcashDBError(err.to_string()))?; From b34e9526384056f04a7cb409c8e48c7d14c76fb1 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:37:17 +0300 Subject: [PATCH 05/40] chore(release): bump mm2 version to 1.1.0-beta (#1969) --- Cargo.lock | 2 +- mm2src/mm2_bin_lib/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bdd4d52cd..a89dedce61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4079,7 +4079,7 @@ dependencies = [ [[package]] name = "mm2_bin_lib" -version = "1.0.7-beta" +version = "1.1.0-beta" dependencies = [ "chrono", "common", diff --git a/mm2src/mm2_bin_lib/Cargo.toml b/mm2src/mm2_bin_lib/Cargo.toml index bd3bf5aac9..3abda9470d 100644 --- a/mm2src/mm2_bin_lib/Cargo.toml +++ b/mm2src/mm2_bin_lib/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "mm2_bin_lib" -version = "1.0.7-beta" +version = "1.1.0-beta" authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann "] edition = "2018" default-run = "mm2" From bd69fbdebf8424526772ef75beb443e5597c7909 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Mon, 18 Sep 2023 18:11:26 +0300 Subject: [PATCH 06/40] fix(tests): fix failing tests due to RICK/MORTY (#1970) This PR fixes failing wasm tests and test_utxo_lock by making these tests use the DOC/MARTY electrums --- mm2src/coins/utxo/utxo_tests.rs | 4 ++-- mm2src/mm2_test_helpers/src/electrums.rs | 24 ++++++++++++------------ mm2src/mm2_test_helpers/src/for_tests.rs | 6 ++++++ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index be56824f6a..8f2bf4158e 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -42,7 +42,7 @@ use futures::future::join_all; use futures::TryFutureExt; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_number::bigdecimal::{BigDecimal, Signed}; -use mm2_test_helpers::for_tests::{mm_ctx_with_custom_db, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; +use mm2_test_helpers::for_tests::{mm_ctx_with_custom_db, DOC_ELECTRUM_ADDRS, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; use mocktopus::mocking::*; use rpc::v1::types::H256 as H256Json; use serialization::{deserialize, CoinVariant}; @@ -953,7 +953,7 @@ fn test_withdraw_rick_rewards_none() { #[test] fn test_utxo_lock() { // send several transactions concurrently to check that they are not using same inputs - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(client.into(), None, false); let output = TransactionOutput { value: 1000000, diff --git a/mm2src/mm2_test_helpers/src/electrums.rs b/mm2src/mm2_test_helpers/src/electrums.rs index c47b970579..c1cdbfe963 100644 --- a/mm2src/mm2_test_helpers/src/electrums.rs +++ b/mm2src/mm2_test_helpers/src/electrums.rs @@ -3,18 +3,18 @@ use serde_json::{json, Value as Json}; #[cfg(target_arch = "wasm32")] pub fn rick_electrums() -> Vec { vec![ - json!({ "url": "electrum1.cipig.net:30017", "protocol": "WSS" }), - json!({ "url": "electrum2.cipig.net:30017", "protocol": "WSS" }), - json!({ "url": "electrum3.cipig.net:30017", "protocol": "WSS" }), + json!({ "url": "electrum1.cipig.net:30020", "protocol": "WSS" }), + json!({ "url": "electrum2.cipig.net:30020", "protocol": "WSS" }), + json!({ "url": "electrum3.cipig.net:30020", "protocol": "WSS" }), ] } #[cfg(not(target_arch = "wasm32"))] pub fn rick_electrums() -> Vec { vec![ - json!({ "url": "electrum1.cipig.net:10017" }), - json!({ "url": "electrum2.cipig.net:10017" }), - json!({ "url": "electrum3.cipig.net:10017" }), + json!({ "url": "electrum1.cipig.net:10020" }), + json!({ "url": "electrum2.cipig.net:10020" }), + json!({ "url": "electrum3.cipig.net:10020" }), ] } @@ -22,9 +22,9 @@ pub fn rick_electrums() -> Vec { #[cfg(target_arch = "wasm32")] pub fn morty_electrums() -> Vec { vec![ - json!({ "url": "electrum1.cipig.net:30018", "protocol": "WSS" }), - json!({ "url": "electrum2.cipig.net:30018", "protocol": "WSS" }), - json!({ "url": "electrum3.cipig.net:30018", "protocol": "WSS" }), + json!({ "url": "electrum1.cipig.net:30021", "protocol": "WSS" }), + json!({ "url": "electrum2.cipig.net:30021", "protocol": "WSS" }), + json!({ "url": "electrum3.cipig.net:30021", "protocol": "WSS" }), ] } @@ -32,9 +32,9 @@ pub fn morty_electrums() -> Vec { #[cfg(not(target_arch = "wasm32"))] pub fn morty_electrums() -> Vec { vec![ - json!({ "url": "electrum1.cipig.net:10018" }), - json!({ "url": "electrum2.cipig.net:10018" }), - json!({ "url": "electrum3.cipig.net:10018" }), + json!({ "url": "electrum1.cipig.net:10021" }), + json!({ "url": "electrum2.cipig.net:10021" }), + json!({ "url": "electrum3.cipig.net:10021" }), ] } diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 307ad1e856..2b49419c6c 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -138,6 +138,12 @@ pub const MORTY_ELECTRUM_ADDRS: &[&str] = &[ "electrum2.cipig.net:10018", "electrum3.cipig.net:10018", ]; +pub const DOC: &str = "DOC"; +pub const DOC_ELECTRUM_ADDRS: &[&str] = &[ + "electrum1.cipig.net:10020", + "electrum2.cipig.net:10020", + "electrum3.cipig.net:10020", +]; pub const ZOMBIE_TICKER: &str = "ZOMBIE"; pub const ARRR: &str = "ARRR"; pub const ZOMBIE_ELECTRUMS: &[&str] = &[ From 6370fa5e815bd0ba5c47c8b4393bb62287639e8e Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Thu, 21 Sep 2023 19:27:38 +0700 Subject: [PATCH 07/40] feat(trading-proto-upgrade): swap UTXO PoC using Storable State Machine (#1958) This commit: - Adds Storable State Machine abstraction with a goal to have as fewer changes to existing state machines as possible. - Implements successful swap v2 of UTXO to UTXO coin. Adds tests for such swap using dockerized komodod daemons. - Adds Swap V2 message exchange using Protobuf. --- Cargo.lock | 14 + Cargo.toml | 1 + mm2src/coins/Cargo.toml | 1 + mm2src/coins/coin_errors.rs | 17 +- mm2src/coins/eth.rs | 25 +- mm2src/coins/eth/eth_tests.rs | 10 +- mm2src/coins/lightning.rs | 4 +- mm2src/coins/lp_coins.rs | 229 +++-- mm2src/coins/qrc20.rs | 45 +- mm2src/coins/solana.rs | 4 +- mm2src/coins/solana/spl.rs | 4 +- mm2src/coins/tendermint/tendermint_coin.rs | 4 +- mm2src/coins/tendermint/tendermint_token.rs | 4 +- .../tendermint/tendermint_tx_history_v2.rs | 16 +- mm2src/coins/test_coin.rs | 65 +- mm2src/coins/utxo/bch.rs | 10 +- mm2src/coins/utxo/qtum.rs | 10 +- mm2src/coins/utxo/slp.rs | 26 +- mm2src/coins/utxo/swap_proto_v2_scripts.rs | 12 +- mm2src/coins/utxo/utxo_common.rs | 344 +++---- mm2src/coins/utxo/utxo_standard.rs | 62 +- mm2src/coins/utxo/utxo_tx_history_v2.rs | 21 +- mm2src/coins/utxo_signer/src/with_key_pair.rs | 48 +- mm2src/coins/z_coin.rs | 19 +- mm2src/coins/z_coin/z_coin_native_tests.rs | 6 +- mm2src/common/common.rs | 10 - mm2src/mm2_bitcoin/script/src/sign.rs | 444 +++++++- mm2src/mm2_core/src/mm_ctx.rs | 3 + mm2src/mm2_main/Cargo.toml | 3 + mm2src/mm2_main/build.rs | 7 + mm2src/mm2_main/src/lp_network.rs | 24 + mm2src/mm2_main/src/lp_ordermatch.rs | 162 ++- mm2src/mm2_main/src/lp_swap.rs | 179 +++- mm2src/mm2_main/src/lp_swap/check_balance.rs | 6 +- .../src/lp_swap/komodefi.swap_v2.pb.rs | 114 +++ mm2src/mm2_main/src/lp_swap/maker_swap.rs | 50 +- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 896 +++++++++++++++++ mm2src/mm2_main/src/lp_swap/swap_v2.proto | 72 ++ mm2src/mm2_main/src/lp_swap/swap_watcher.rs | 19 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 65 +- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 951 ++++++++++++++++++ .../tests/docker_tests/docker_tests_common.rs | 4 +- .../tests/docker_tests/docker_tests_inner.rs | 12 +- .../tests/docker_tests/qrc20_tests.rs | 25 +- .../tests/docker_tests/swap_proto_v2_tests.rs | 120 ++- .../tests/docker_tests/swap_watcher_tests.rs | 20 +- mm2src/mm2_net/Cargo.toml | 3 +- mm2src/mm2_net/src/wasm_ws.rs | 12 +- mm2src/mm2_state_machine/Cargo.toml | 13 + mm2src/mm2_state_machine/src/lib.rs | 12 + mm2src/mm2_state_machine/src/prelude.rs | 4 + .../src}/state_machine.rs | 81 +- .../src/storable_state_machine.rs | 444 ++++++++ mm2src/mm2_test_helpers/src/for_tests.rs | 32 + 54 files changed, 4147 insertions(+), 641 deletions(-) create mode 100644 mm2src/mm2_main/build.rs create mode 100644 mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs create mode 100644 mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs create mode 100644 mm2src/mm2_main/src/lp_swap/swap_v2.proto create mode 100644 mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs create mode 100644 mm2src/mm2_state_machine/Cargo.toml create mode 100644 mm2src/mm2_state_machine/src/lib.rs create mode 100644 mm2src/mm2_state_machine/src/prelude.rs rename mm2src/{common/patterns => mm2_state_machine/src}/state_machine.rs (72%) create mode 100644 mm2src/mm2_state_machine/src/storable_state_machine.rs diff --git a/Cargo.lock b/Cargo.lock index a89dedce61..5bf69cbb31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1033,6 +1033,7 @@ dependencies = [ "mm2_net", "mm2_number", "mm2_rpc", + "mm2_state_machine", "mm2_test_helpers", "mocktopus", "num-traits", @@ -4283,12 +4284,15 @@ dependencies = [ "mm2_net", "mm2_number", "mm2_rpc", + "mm2_state_machine", "mm2_test_helpers", "mocktopus", "num-traits", "parity-util-mem", "parking_lot 0.12.0", "primitives", + "prost", + "prost-build", "rand 0.6.5", "rand 0.7.3", "rcgen", @@ -4384,6 +4388,7 @@ dependencies = [ "lazy_static", "mm2_core", "mm2_err_handle", + "mm2_state_machine", "prost", "rand 0.7.3", "rustls 0.20.4", @@ -4429,6 +4434,15 @@ dependencies = [ "uuid 1.2.2", ] +[[package]] +name = "mm2_state_machine" +version = "0.1.0" +dependencies = [ + "async-trait", + "common", + "futures 0.3.15", +] + [[package]] name = "mm2_test_helpers" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0004c9dadd..a3f3e98803 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "mm2src/mm2_net", "mm2src/mm2_number", "mm2src/mm2_rpc", + "mm2src/mm2_state_machine", "mm2src/rpc_task", "mm2src/mm2_test_helpers", "mm2src/trezor", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 72255359b3..1e45499ae6 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -69,6 +69,7 @@ mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number"} mm2_rpc = { path = "../mm2_rpc" } +mm2_state_machine = { path = "../mm2_state_machine" } mocktopus = "0.8.0" num-traits = "0.2" parking_lot = { version = "0.12.0", features = ["nightly"] } diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index 441677b76d..c9672082c7 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -3,23 +3,36 @@ use crate::{eth::Web3RpcError, my_tx_history_v2::MyTxHistoryErrorV2, utxo::rpc_c use futures01::Future; use mm2_err_handle::prelude::MmError; use spv_validation::helpers_validation::SPVError; +use std::num::TryFromIntError; +/// Helper type used as result for swap payment validation function(s) pub type ValidatePaymentFut = Box> + Send>; +/// Enum covering possible error cases of swap payment validation #[derive(Debug, Display)] pub enum ValidatePaymentError { + /// Should be used to indicate internal MM2 state problems (e.g., DB errors, etc.). InternalError(String), - // Problem with deserializing the transaction, or one of the transaction parts is invalid. + /// Problem with deserializing the transaction, or one of the transaction parts is invalid. TxDeserializationError(String), + /// One of the input parameters is invalid. InvalidParameter(String), + /// Coin's RPC returned unexpected/invalid response during payment validation. InvalidRpcResponse(String), + /// Payment transaction doesn't exist on-chain. TxDoesNotExist(String), + /// SPV client error. SPVError(SPVError), + /// Payment transaction is in unexpected state. E.g., `Uninitialized` instead of `Sent` for ETH payment. UnexpectedPaymentState(String), + /// Transport (RPC) error. Transport(String), - // Transaction has wrong properties, for example, it has been sent to a wrong address + /// Transaction has wrong properties, for example, it has been sent to a wrong address. WrongPaymentTx(String), + /// Indicates error during watcher reward calculation. WatcherRewardError(String), + /// Input payment timelock overflows the type used by specific coin. + TimelockOverflow(TryFromIntError), } impl From for ValidatePaymentError { diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cba9ece714..ef20bcd52e 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -61,7 +61,7 @@ use serde_json::{self as json, Value as Json}; use serialization::{CompactInteger, Serializable, Stream}; use sha3::{Digest, Keccak256}; use std::collections::HashMap; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::ops::Deref; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; use std::str::FromStr; @@ -1137,7 +1137,10 @@ impl SwapOps for EthCoin { &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs, ) -> Box, Error = String> + Send> { - let id = self.etomic_swap_id(if_my_payment_sent_args.time_lock, if_my_payment_sent_args.secret_hash); + let id = self.etomic_swap_id( + try_fus!(if_my_payment_sent_args.time_lock.try_into()), + if_my_payment_sent_args.secret_hash, + ); let swap_contract_address = try_fus!(if_my_payment_sent_args.swap_contract_address.try_to_address()); let selfi = self.clone(); let from_block = if_my_payment_sent_args.search_from_block; @@ -1420,7 +1423,7 @@ impl WatcherOps for EthCoin { fn create_maker_payment_spend_preimage( &self, maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -1435,7 +1438,7 @@ impl WatcherOps for EthCoin { fn create_taker_payment_refund_preimage( &self, taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, @@ -1476,9 +1479,13 @@ impl WatcherOps for EthCoin { .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); let sender = try_f!(addr_from_raw_pubkey(&input.taker_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); let receiver = try_f!(addr_from_raw_pubkey(&input.maker_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); let selfi = self.clone(); - let swap_id = selfi.etomic_swap_id(input.time_lock, &input.secret_hash); + let swap_id = selfi.etomic_swap_id(time_lock, &input.secret_hash); let secret_hash = if input.secret_hash.len() == 32 { ripemd160(&input.secret_hash).to_vec() } else { @@ -3023,7 +3030,7 @@ impl EthCoin { fn send_hash_time_locked_payment(&self, args: SendPaymentArgs<'_>) -> EthTxFut { let receiver_addr = try_tx_fus!(addr_from_raw_pubkey(args.other_pubkey)); let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); - let id = self.etomic_swap_id(args.time_lock, args.secret_hash); + let id = self.etomic_swap_id(try_tx_fus!(args.time_lock.try_into()), args.secret_hash); let trade_amount = try_tx_fus!(wei_from_big_decimal(&args.amount, self.decimals)); let time_lock = U256::from(args.time_lock); @@ -3882,9 +3889,13 @@ impl EthCoin { try_f!(SignedEthTx::new(unsigned) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); let sender = try_f!(addr_from_raw_pubkey(&input.other_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); let selfi = self.clone(); - let swap_id = selfi.etomic_swap_id(input.time_lock, &input.secret_hash); + let swap_id = selfi.etomic_swap_id(time_lock, &input.secret_hash); let decimals = self.decimals; let secret_hash = if input.secret_hash.len() == 32 { ripemd160(&input.secret_hash).to_vec() diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 9014a47860..dfeff254f9 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::IguanaPrivKey; -use common::{block_on, now_sec_u32, wait_until_sec}; +use common::{block_on, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use ethkey::{Generator, Random}; use mm2_core::mm_ctx::{MmArc, MmCtxBuilder}; @@ -343,7 +343,7 @@ fn send_and_refund_erc20_payment() { abortable_system: AbortableQueue::default(), })); - let time_lock = now_sec_u32() - 200; + let time_lock = now_sec() - 200; let secret_hash = &[1; 20]; let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, @@ -360,7 +360,7 @@ fn send_and_refund_erc20_payment() { let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); log!("{:?}", payment); - let swap_id = coin.etomic_swap_id(time_lock, secret_hash); + let swap_id = coin.etomic_swap_id(time_lock.try_into().unwrap(), secret_hash); let status = block_on( coin.payment_status( Address::from_str(ETH_DEV_SWAP_CONTRACT).unwrap(), @@ -429,7 +429,7 @@ fn send_and_refund_eth_payment() { abortable_system: AbortableQueue::default(), })); - let time_lock = now_sec_u32() - 200; + let time_lock = now_sec() - 200; let secret_hash = &[1; 20]; let send_maker_payment_args = SendPaymentArgs { time_lock_duration: 0, @@ -447,7 +447,7 @@ fn send_and_refund_eth_payment() { log!("{:?}", payment); - let swap_id = coin.etomic_swap_id(time_lock, secret_hash); + let swap_id = coin.etomic_swap_id(time_lock.try_into().unwrap(), secret_hash); let status = block_on( coin.payment_status( Address::from_str(ETH_DEV_SWAP_CONTRACT).unwrap(), diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 979276d230..59b026121b 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -959,7 +959,7 @@ impl WatcherOps for LightningCoin { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -974,7 +974,7 @@ impl WatcherOps for LightningCoin { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 9213115d31..564863ad98 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -313,9 +313,12 @@ pub type RawTransactionResult = Result = Box> + Send + 'a>; pub type RefundResult = Result>; -pub type GenAndSignDexFeeSpendResult = MmResult; -pub type ValidateDexFeeResult = MmResult<(), ValidateDexFeeError>; -pub type ValidateDexFeeSpendPreimageResult = MmResult<(), ValidateDexFeeSpendPreimageError>; +/// Helper type used for taker payment's spend preimage generation result +pub type GenTakerPaymentSpendResult = MmResult; +/// Helper type used for taker payment's validation result +pub type ValidateTakerPaymentResult = MmResult<(), ValidateTakerPaymentError>; +/// Helper type used for taker payment's spend preimage validation result +pub type ValidateTakerPaymentSpendPreimageResult = MmResult<(), ValidateTakerPaymentSpendPreimageError>; pub type IguanaPrivKey = Secp256k1Secret; @@ -614,31 +617,53 @@ pub struct WatcherValidateTakerFeeInput { pub lock_duration: u64, } +/// Helper struct wrapping arguments for [WatcherOps::watcher_validate_taker_payment]. #[derive(Clone)] pub struct WatcherValidatePaymentInput { + /// Taker payment serialized to raw bytes. pub payment_tx: Vec, + /// Payment refund preimage generated by taker. pub taker_payment_refund_preimage: Vec, - pub time_lock: u32, + /// Taker payment can be refunded after this timestamp. + pub time_lock: u64, + /// Taker's pubkey. pub taker_pub: Vec, + /// Maker's pubkey. pub maker_pub: Vec, + /// Hash of the secret generated by maker. pub secret_hash: Vec, + /// Validation timeout. pub wait_until: u64, + /// Required number of taker payment's on-chain confirmations. pub confirmations: u64, + /// Maker coin. pub maker_coin: MmCoinEnum, } +/// Helper struct wrapping arguments for [SwapOps::validate_taker_payment] and [SwapOps::validate_maker_payment]. #[derive(Clone, Debug)] pub struct ValidatePaymentInput { + /// Payment transaction serialized to raw bytes. pub payment_tx: Vec, + /// Time lock duration in seconds. pub time_lock_duration: u64, - pub time_lock: u32, + /// Payment can be refunded after this timestamp. + pub time_lock: u64, + /// Pubkey of other side of the swap. pub other_pub: Vec, + /// Hash of the secret generated by maker. pub secret_hash: Vec, + /// Expected payment amount. pub amount: BigDecimal, + /// Swap contract address if applicable. pub swap_contract_address: Option, + /// SPV proof check timeout. pub try_spv_proof_until: u64, + /// Required number of payment's on-chain confirmations. pub confirmations: u64, + /// Unique data of specific swap. pub unique_swap_data: Vec, + /// The reward assigned to watcher for providing help to complete the swap. pub watcher_reward: Option, } @@ -663,7 +688,7 @@ pub struct SendMakerPaymentSpendPreimageInput<'a> { } pub struct SearchForSwapTxSpendInput<'a> { - pub time_lock: u32, + pub time_lock: u64, pub other_pub: &'a [u8], pub secret_hash: &'a [u8], pub tx: &'a [u8], @@ -690,20 +715,30 @@ pub struct WatcherReward { pub send_contract_reward_on_spend: bool, } +/// Helper struct wrapping arguments for [SwapOps::send_taker_payment] and [SwapOps::send_maker_payment]. #[derive(Clone, Debug)] pub struct SendPaymentArgs<'a> { + /// Time lock duration in seconds. pub time_lock_duration: u64, - pub time_lock: u32, + /// Payment can be refunded after this timestamp. + pub time_lock: u64, /// This is either: /// * Taker's pubkey if this structure is used in [`SwapOps::send_maker_payment`]. /// * Maker's pubkey if this structure is used in [`SwapOps::send_taker_payment`]. pub other_pubkey: &'a [u8], + /// Hash of the secret generated by maker. pub secret_hash: &'a [u8], + /// Payment amount pub amount: BigDecimal, + /// Swap contract address if applicable. pub swap_contract_address: &'a Option, + /// Unique data of specific swap. pub swap_unique_data: &'a [u8], + /// Instructions for the next step of the swap (e.g., Lightning invoice). pub payment_instructions: &'a Option, + /// The reward assigned to watcher for providing help to complete the swap. pub watcher_reward: Option, + /// As of now, this field is specifically used to wait for confirmations of ERC20 approval transaction. pub wait_for_confirmation_until: u64, } @@ -713,7 +748,7 @@ pub struct SpendPaymentArgs<'a> { /// * Taker's payment tx if this structure is used in [`SwapOps::send_maker_spends_taker_payment`]. /// * Maker's payment tx if this structure is used in [`SwapOps::send_taker_spends_maker_payment`]. pub other_payment_tx: &'a [u8], - pub time_lock: u32, + pub time_lock: u64, /// This is either: /// * Taker's pubkey if this structure is used in [`SwapOps::send_maker_spends_taker_payment`]. /// * Maker's pubkey if this structure is used in [`SwapOps::send_taker_spends_maker_payment`]. @@ -728,7 +763,7 @@ pub struct SpendPaymentArgs<'a> { #[derive(Clone, Debug)] pub struct RefundPaymentArgs<'a> { pub payment_tx: &'a [u8], - pub time_lock: u32, + pub time_lock: u64, /// This is either: /// * Taker's pubkey if this structure is used in [`SwapOps::send_maker_refunds_payment`]. /// * Maker's pubkey if this structure is used in [`SwapOps::send_taker_refunds_payment`]. @@ -739,15 +774,24 @@ pub struct RefundPaymentArgs<'a> { pub watcher_reward: bool, } +/// Helper struct wrapping arguments for [SwapOps::check_if_my_payment_sent]. #[derive(Clone, Debug)] pub struct CheckIfMyPaymentSentArgs<'a> { - pub time_lock: u32, + /// Payment can be refunded after this timestamp. + pub time_lock: u64, + /// Pubkey of other side of the swap. pub other_pub: &'a [u8], + /// Hash of the secret generated by maker. pub secret_hash: &'a [u8], + /// Search after specific block to avoid scanning entire blockchain. pub search_from_block: u64, + /// Swap contract address if applicable. pub swap_contract_address: &'a Option, + /// Unique data of specific swap. pub swap_unique_data: &'a [u8], + /// Payment amount. pub amount: &'a BigDecimal, + /// Instructions for the next step of the swap (e.g., Lightning invoice). pub payment_instructions: &'a Option, } @@ -974,26 +1018,26 @@ pub trait WatcherOps { fn create_taker_payment_refund_preimage( &self, - _taker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_contract_address: &Option, - _swap_unique_data: &[u8], + taker_payment_tx: &[u8], + time_lock: u64, + maker_pub: &[u8], + secret_hash: &[u8], + swap_contract_address: &Option, + swap_unique_data: &[u8], ) -> TransactionFut; fn create_maker_payment_spend_preimage( &self, - _maker_payment_tx: &[u8], - _time_lock: u32, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], + maker_payment_tx: &[u8], + time_lock: u64, + maker_pub: &[u8], + secret_hash: &[u8], + swap_unique_data: &[u8], ) -> TransactionFut; fn watcher_validate_taker_fee(&self, input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()>; - fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()>; + fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()>; async fn watcher_search_for_swap_tx_spend( &self, @@ -1017,51 +1061,95 @@ pub trait WatcherOps { ) -> Result, MmError>; } -pub struct SendDexFeeWithPremiumArgs<'a> { - pub time_lock: u32, +/// Helper struct wrapping arguments for [SwapOpsV2::send_combined_taker_payment] +pub struct SendCombinedTakerPaymentArgs<'a> { + /// Taker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by maker pub secret_hash: &'a [u8], + /// Maker's pubkey pub other_pub: &'a [u8], + /// DEX fee amount pub dex_fee_amount: BigDecimal, + /// Additional reward for maker (premium) pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, + /// Unique data of specific swap pub swap_unique_data: &'a [u8], } -pub struct ValidateDexFeeArgs<'a> { - pub dex_fee_tx: &'a [u8], - pub time_lock: u32, +/// Helper struct wrapping arguments for [SwapOpsV2::validate_combined_taker_payment] +pub struct ValidateTakerPaymentArgs<'a> { + /// Taker payment transaction serialized to raw bytes + pub taker_tx: &'a [u8], + /// Taker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by maker pub secret_hash: &'a [u8], + /// Taker's pubkey pub other_pub: &'a [u8], + /// DEX fee amount pub dex_fee_amount: BigDecimal, + /// Additional reward for maker (premium) pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, + /// Unique data of specific swap pub swap_unique_data: &'a [u8], } -pub struct GenDexFeeSpendArgs<'a> { - pub dex_fee_tx: &'a [u8], - pub time_lock: u32, +/// Helper struct wrapping arguments for taker payment's spend generation, used in +/// [SwapOpsV2::gen_taker_payment_spend_preimage], [SwapOpsV2::validate_taker_payment_spend_preimage] and +/// [SwapOpsV2::sign_and_broadcast_taker_payment_spend] +pub struct GenTakerPaymentSpendArgs<'a> { + /// Taker payment transaction serialized to raw bytes + pub taker_tx: &'a [u8], + /// Taker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by maker pub secret_hash: &'a [u8], + /// Maker's pubkey pub maker_pub: &'a [u8], + /// Taker's pubkey pub taker_pub: &'a [u8], + /// Pubkey of address, receiving DEX fees pub dex_fee_pub: &'a [u8], + /// DEX fee amount pub dex_fee_amount: BigDecimal, + /// Additional reward for maker (premium) pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, } +/// Taker payment spend preimage with taker's signature pub struct TxPreimageWithSig { - preimage: Vec, - signature: Vec, + /// The preimage tx serialized to raw bytes, might be empty for certain coin protocols + pub preimage: Vec, + /// Taker's signature + pub signature: Vec, } -#[derive(Debug)] +/// Enum covering error cases that can happen during taker payment spend preimage generation. +#[derive(Debug, Display)] pub enum TxGenError { + /// RPC error Rpc(String), + /// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.). NumConversion(String), + /// Address derivation error. AddressDerivation(String), + /// Error during transaction raw bytes deserialization. TxDeserialization(String), + /// Error during pubkey deserialization. InvalidPubkey(String), + /// Problem with tx preimage signing. Signing(String), - MinerFeeExceedsPremium { miner_fee: BigDecimal, premium: BigDecimal }, + /// Legacy error produced by usage of try_s/try_fus and other similar macros. Legacy(String), + /// Input payment timelock overflows the type used by specific coin. + LocktimeOverflow(String), } impl From for TxGenError { @@ -1076,69 +1164,96 @@ impl From for TxGenError { fn from(err: UtxoSignWithKeyPairError) -> Self { TxGenError::Signing(err.to_string()) } } +/// Enum covering error cases that can happen during taker payment validation. #[derive(Debug)] -pub enum ValidateDexFeeError { +pub enum ValidateTakerPaymentError { + /// Payment sent to wrong address or has invalid amount. InvalidDestinationOrAmount(String), + /// Error during pubkey deserialization. InvalidPubkey(String), + /// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.). NumConversion(String), + /// RPC error. Rpc(String), + /// Serialized tx bytes doesn't match ones received from coin's RPC. TxBytesMismatch { from_rpc: BytesJson, actual: BytesJson }, + /// Error during transaction raw bytes deserialization. TxDeserialization(String), + /// Provided transaction doesn't have output with specific index TxLacksOfOutputs, + /// Input payment timelock overflows the type used by specific coin. + LocktimeOverflow(String), } -impl From for ValidateDexFeeError { - fn from(err: NumConversError) -> Self { ValidateDexFeeError::NumConversion(err.to_string()) } +impl From for ValidateTakerPaymentError { + fn from(err: NumConversError) -> Self { ValidateTakerPaymentError::NumConversion(err.to_string()) } } -impl From for ValidateDexFeeError { - fn from(err: UtxoRpcError) -> Self { ValidateDexFeeError::Rpc(err.to_string()) } +impl From for ValidateTakerPaymentError { + fn from(err: UtxoRpcError) -> Self { ValidateTakerPaymentError::Rpc(err.to_string()) } } -#[derive(Debug)] -pub enum ValidateDexFeeSpendPreimageError { +/// Enum covering error cases that can happen during taker payment spend preimage validation. +#[derive(Debug, Display)] +pub enum ValidateTakerPaymentSpendPreimageError { + /// Error during pubkey deserialization. InvalidPubkey(String), + /// Error during signature deserialization. InvalidTakerSignature, + /// Error during preimage comparison to an expected one. InvalidPreimage(String), + /// Error during taker's signature check. SignatureVerificationFailure(String), + /// Error during preimage raw bytes deserialization. TxDeserialization(String), + /// Error during generation of an expected preimage. TxGenError(String), + /// Input payment timelock overflows the type used by specific coin. + LocktimeOverflow(String), } -impl From for ValidateDexFeeSpendPreimageError { +impl From for ValidateTakerPaymentSpendPreimageError { fn from(err: UtxoSignWithKeyPairError) -> Self { - ValidateDexFeeSpendPreimageError::SignatureVerificationFailure(err.to_string()) + ValidateTakerPaymentSpendPreimageError::SignatureVerificationFailure(err.to_string()) } } -impl From for ValidateDexFeeSpendPreimageError { - fn from(err: TxGenError) -> Self { ValidateDexFeeSpendPreimageError::TxGenError(format!("{:?}", err)) } +impl From for ValidateTakerPaymentSpendPreimageError { + fn from(err: TxGenError) -> Self { ValidateTakerPaymentSpendPreimageError::TxGenError(format!("{:?}", err)) } } +/// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait SwapOpsV2 { - async fn send_dex_fee_with_premium(&self, args: SendDexFeeWithPremiumArgs<'_>) -> TransactionResult; +pub trait SwapOpsV2: Send + Sync + 'static { + /// Generate and broadcast taker payment transaction that includes dex fee, maker premium and actual trading volume. + async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult; - async fn validate_dex_fee_with_premium(&self, args: ValidateDexFeeArgs<'_>) -> ValidateDexFeeResult; + /// Validates taker payment transaction. + async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult; - async fn refund_dex_fee_with_premium(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; + /// Refunds taker payment transaction. + async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; - async fn gen_and_sign_dex_fee_spend_preimage( + /// Generates and signs taker payment spend preimage. The preimage and signature should be + /// shared with maker to proceed with protocol execution. + async fn gen_taker_payment_spend_preimage( &self, - args: &GenDexFeeSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_>, swap_unique_data: &[u8], - ) -> GenAndSignDexFeeSpendResult; + ) -> GenTakerPaymentSpendResult; - async fn validate_dex_fee_spend_preimage( + /// Validate taker payment spend preimage on maker's side. + async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenDexFeeSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_>, preimage: &TxPreimageWithSig, - ) -> ValidateDexFeeSpendPreimageResult; + ) -> ValidateTakerPaymentSpendPreimageResult; - async fn sign_and_broadcast_dex_fee_spend( + /// Sign and broadcast taker payment spend on maker's side. + async fn sign_and_broadcast_taker_payment_spend( &self, preimage: &TxPreimageWithSig, - gen_args: &GenDexFeeSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult; diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index c99f2d47b7..68cdf5a35a 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -53,6 +53,7 @@ use script_pubkey::generate_contract_call_script_pubkey; use serde_json::{self as json, Value as Json}; use serialization::{deserialize, serialize, CoinVariant}; use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; use std::ops::{Deref, Neg}; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; use std::str::FromStr; @@ -766,7 +767,7 @@ impl SwapOps for Qrc20Coin { } fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - let time_lock = maker_payment_args.time_lock; + let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); let taker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(maker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, maker_payment_args.secret_hash); let value = try_tx_fus!(wei_from_big_decimal(&maker_payment_args.amount, self.utxo.decimals)); @@ -784,7 +785,7 @@ impl SwapOps for Qrc20Coin { #[inline] fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - let time_lock = taker_payment_args.time_lock; + let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); let maker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(taker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, taker_payment_args.secret_hash); let value = try_tx_fus!(wei_from_big_decimal(&taker_payment_args.amount, self.utxo.decimals)); @@ -896,12 +897,16 @@ impl SwapOps for Qrc20Coin { .try_to_address() .map_to_mm(ValidatePaymentError::InvalidParameter)); + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); let selfi = self.clone(); let fut = async move { selfi .validate_payment( payment_tx, - input.time_lock, + time_lock, sender, input.secret_hash, input.amount, @@ -922,13 +927,16 @@ impl SwapOps for Qrc20Coin { let sender = try_f!(self .contract_address_from_raw_pubkey(&input.other_pub) .map_to_mm(ValidatePaymentError::InvalidParameter)); - + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); let selfi = self.clone(); let fut = async move { selfi .validate_payment( payment_tx, - input.time_lock, + time_lock, sender, input.secret_hash, input.amount, @@ -945,7 +953,10 @@ impl SwapOps for Qrc20Coin { if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, ) -> Box, Error = String> + Send> { let search_from_block = if_my_payment_sent_args.search_from_block; - let swap_id = qrc20_swap_id(if_my_payment_sent_args.time_lock, if_my_payment_sent_args.secret_hash); + let swap_id = qrc20_swap_id( + try_fus!(if_my_payment_sent_args.time_lock.try_into()), + if_my_payment_sent_args.secret_hash, + ); let swap_contract_address = try_fus!(if_my_payment_sent_args.swap_contract_address.try_to_address()); let selfi = self.clone(); @@ -964,8 +975,13 @@ impl SwapOps for Qrc20Coin { ) -> Result, String> { let tx: UtxoTx = try_s!(deserialize(input.tx).map_err(|e| ERRL!("{:?}", e))); - self.search_for_swap_tx_spend(input.time_lock, input.secret_hash, tx, input.search_from_block) - .await + self.search_for_swap_tx_spend( + try_s!(input.time_lock.try_into()), + input.secret_hash, + tx, + input.search_from_block, + ) + .await } async fn search_for_swap_tx_spend_other( @@ -974,8 +990,13 @@ impl SwapOps for Qrc20Coin { ) -> Result, String> { let tx: UtxoTx = try_s!(deserialize(input.tx).map_err(|e| ERRL!("{:?}", e))); - self.search_for_swap_tx_spend(input.time_lock, input.secret_hash, tx, input.search_from_block) - .await + self.search_for_swap_tx_spend( + try_s!(input.time_lock.try_into()), + input.secret_hash, + tx, + input.search_from_block, + ) + .await } #[inline] @@ -1090,7 +1111,7 @@ impl WatcherOps for Qrc20Coin { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -1105,7 +1126,7 @@ impl WatcherOps for Qrc20Coin { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 1c4964f3a0..5f7bb9e44d 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -611,7 +611,7 @@ impl WatcherOps for SolanaCoin { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -626,7 +626,7 @@ impl WatcherOps for SolanaCoin { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 06b43688d7..134d8204ba 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -436,7 +436,7 @@ impl WatcherOps for SplToken { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, @@ -448,7 +448,7 @@ impl WatcherOps for SplToken { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 33e48d1885..8bc284b9e6 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2701,7 +2701,7 @@ impl WatcherOps for TendermintCoin { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -2716,7 +2716,7 @@ impl WatcherOps for TendermintCoin { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index f985ccec3c..ab1c3573c3 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -462,7 +462,7 @@ impl WatcherOps for TendermintToken { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -477,7 +477,7 @@ impl WatcherOps for TendermintToken { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, diff --git a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs index 5b16fe04e3..adead7550e 100644 --- a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs +++ b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs @@ -9,17 +9,18 @@ use async_trait::async_trait; use bitcrypto::sha256; use common::executor::Timer; use common::log; -use common::state_machine::prelude::*; -use common::state_machine::StateMachineTrait; use cosmrs::tendermint::abci::Code as TxCode; use cosmrs::tendermint::abci::Event; use cosmrs::tx::Fee; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MmResult; use mm2_number::BigDecimal; +use mm2_state_machine::prelude::*; +use mm2_state_machine::state_machine::StateMachineTrait; use primitives::hash::H256; use rpc::v1::types::Bytes as BytesJson; use std::cmp; +use std::convert::Infallible; macro_rules! try_or_return_stopped_as_err { ($exp:expr, $reason: expr, $fmt:literal) => { @@ -111,6 +112,12 @@ impl StateMachineTrait for TendermintTxHistoryStateMachine { type Result = (); + type Error = Infallible; +} + +impl StandardStateMachine + for TendermintTxHistoryStateMachine +{ } struct TendermintInit { @@ -909,5 +916,8 @@ pub async fn tendermint_history_loop( last_spent_page: 1, }; - state_machine.run(Box::new(TendermintInit::new())).await; + state_machine + .run(Box::new(TendermintInit::new())) + .await + .expect("The error of this machine is Infallible"); } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index c731b12b01..24408cf506 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -3,15 +3,18 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, - ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenTakerPaymentSpendArgs, + GenTakerPaymentSpendResult, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionResult, TxMarshalingErr, TxPreimageWithSig, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateTakerPaymentArgs, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, + WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -240,7 +243,7 @@ impl WatcherOps for TestCoin { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -255,7 +258,7 @@ impl WatcherOps for TestCoin { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, @@ -378,3 +381,43 @@ impl MmCoin for TestCoin { fn on_token_deactivated(&self, _ticker: &str) { () } } + +#[async_trait] +#[mockable] +impl SwapOpsV2 for TestCoin { + async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } + + async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult { + unimplemented!() + } + + async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { unimplemented!() } + + async fn gen_taker_payment_spend_preimage( + &self, + args: &GenTakerPaymentSpendArgs<'_>, + swap_unique_data: &[u8], + ) -> GenTakerPaymentSpendResult { + unimplemented!() + } + + async fn validate_taker_payment_spend_preimage( + &self, + gen_args: &GenTakerPaymentSpendArgs<'_>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerPaymentSpendPreimageResult { + unimplemented!() + } + + async fn sign_and_broadcast_taker_payment_spend( + &self, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_>, + secret: &[u8], + swap_unique_data: &[u8], + ) -> TransactionResult { + unimplemented!() + } +} diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index e85f2a8d98..393f25cd54 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -907,7 +907,7 @@ impl SwapOps for BchCoin { ) -> Box, Error = String> + Send> { utxo_common::check_if_my_payment_sent( self.clone(), - if_my_payment_sent_args.time_lock, + try_fus!(if_my_payment_sent_args.time_lock.try_into()), if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, @@ -1041,7 +1041,7 @@ impl WatcherOps for BchCoin { fn create_maker_payment_spend_preimage( &self, maker_payment_tx: &[u8], - time_lock: u32, + time_lock: u64, maker_pub: &[u8], secret_hash: &[u8], swap_unique_data: &[u8], @@ -1049,7 +1049,7 @@ impl WatcherOps for BchCoin { utxo_common::create_maker_payment_spend_preimage( self, maker_payment_tx, - time_lock, + try_tx_fus!(time_lock.try_into()), maker_pub, secret_hash, swap_unique_data, @@ -1065,7 +1065,7 @@ impl WatcherOps for BchCoin { fn create_taker_payment_refund_preimage( &self, taker_payment_tx: &[u8], - time_lock: u32, + time_lock: u64, maker_pub: &[u8], secret_hash: &[u8], _swap_contract_address: &Option, @@ -1074,7 +1074,7 @@ impl WatcherOps for BchCoin { utxo_common::create_taker_payment_refund_preimage( self, taker_payment_tx, - time_lock, + try_tx_fus!(time_lock.try_into()), maker_pub, secret_hash, swap_unique_data, diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 8a4e8b4eae..2f4c57ac6e 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -593,7 +593,7 @@ impl SwapOps for QtumCoin { ) -> Box, Error = String> + Send> { utxo_common::check_if_my_payment_sent( self.clone(), - if_my_payment_sent_args.time_lock, + try_fus!(if_my_payment_sent_args.time_lock.try_into()), if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, @@ -723,7 +723,7 @@ impl WatcherOps for QtumCoin { fn create_maker_payment_spend_preimage( &self, maker_payment_tx: &[u8], - time_lock: u32, + time_lock: u64, maker_pub: &[u8], secret_hash: &[u8], swap_unique_data: &[u8], @@ -731,7 +731,7 @@ impl WatcherOps for QtumCoin { utxo_common::create_maker_payment_spend_preimage( self, maker_payment_tx, - time_lock, + try_tx_fus!(time_lock.try_into()), maker_pub, secret_hash, swap_unique_data, @@ -747,7 +747,7 @@ impl WatcherOps for QtumCoin { fn create_taker_payment_refund_preimage( &self, taker_payment_tx: &[u8], - time_lock: u32, + time_lock: u64, maker_pub: &[u8], secret_hash: &[u8], _swap_contract_address: &Option, @@ -756,7 +756,7 @@ impl WatcherOps for QtumCoin { utxo_common::create_taker_payment_refund_preimage( self, taker_payment_tx, - time_lock, + try_tx_fus!(time_lock.try_into()), maker_pub, secret_hash, swap_unique_data, diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 24ccf72448..07379fcbbf 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -496,6 +496,10 @@ impl SlpToken { let htlc_keypair = self.derive_htlc_key_pair(&input.unique_swap_data); let first_pub = &Public::from_slice(&input.other_pub) .map_to_mm(|err| ValidatePaymentError::InvalidParameter(err.to_string()))?; + let time_lock = input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)?; let validate_fut = utxo_common::validate_payment( self.platform_coin.clone(), tx, @@ -505,7 +509,7 @@ impl SlpToken { &input.secret_hash, self.platform_dust_dec(), None, - input.time_lock, + time_lock, wait_until_sec(60), input.confirmations, ); @@ -1216,7 +1220,7 @@ impl SwapOps for SlpToken { let amount = try_tx_fus!(sat_from_big_decimal(&maker_payment_args.amount, self.decimals())); let secret_hash = maker_payment_args.secret_hash.to_owned(); let maker_htlc_keypair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); - let time_lock = maker_payment_args.time_lock; + let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); let coin = self.clone(); let fut = async move { @@ -1235,7 +1239,7 @@ impl SwapOps for SlpToken { let secret_hash = taker_payment_args.secret_hash.to_owned(); let taker_htlc_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); - let time_lock = taker_payment_args.time_lock; + let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); let coin = self.clone(); let fut = async move { @@ -1255,7 +1259,7 @@ impl SwapOps for SlpToken { let secret_hash = maker_spends_payment_args.secret_hash.to_owned(); let htlc_keypair = self.derive_htlc_key_pair(maker_spends_payment_args.swap_unique_data); let coin = self.clone(); - let time_lock = maker_spends_payment_args.time_lock; + let time_lock = try_tx_fus!(maker_spends_payment_args.time_lock.try_into()); let fut = async move { let tx = try_tx_s!( @@ -1274,7 +1278,7 @@ impl SwapOps for SlpToken { let secret_hash = taker_spends_payment_args.secret_hash.to_owned(); let htlc_keypair = self.derive_htlc_key_pair(taker_spends_payment_args.swap_unique_data); let coin = self.clone(); - let time_lock = taker_spends_payment_args.time_lock; + let time_lock = try_tx_fus!(taker_spends_payment_args.time_lock.try_into()); let fut = async move { let tx = try_tx_s!( @@ -1291,7 +1295,7 @@ impl SwapOps for SlpToken { let maker_pub = try_tx_s!(Public::from_slice(taker_refunds_payment_args.other_pubkey)); let secret_hash = taker_refunds_payment_args.secret_hash.to_owned(); let htlc_keypair = self.derive_htlc_key_pair(taker_refunds_payment_args.swap_unique_data); - let time_lock = taker_refunds_payment_args.time_lock; + let time_lock = try_tx_s!(taker_refunds_payment_args.time_lock.try_into()); let tx = try_tx_s!( self.refund_htlc(&tx, &maker_pub, time_lock, &secret_hash, &htlc_keypair) @@ -1305,7 +1309,7 @@ impl SwapOps for SlpToken { let taker_pub = try_tx_s!(Public::from_slice(maker_refunds_payment_args.other_pubkey)); let secret_hash = maker_refunds_payment_args.secret_hash.to_owned(); let htlc_keypair = self.derive_htlc_key_pair(maker_refunds_payment_args.swap_unique_data); - let time_lock = maker_refunds_payment_args.time_lock; + let time_lock = try_tx_s!(maker_refunds_payment_args.time_lock.try_into()); let tx = try_tx_s!( self.refund_htlc(&tx, &taker_pub, time_lock, &secret_hash, &htlc_keypair) @@ -1359,7 +1363,7 @@ impl SwapOps for SlpToken { ) -> Box, Error = String> + Send> { utxo_common::check_if_my_payment_sent( self.platform_coin.clone(), - if_my_payment_sent_args.time_lock, + try_fus!(if_my_payment_sent_args.time_lock.try_into()), if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, @@ -1475,7 +1479,7 @@ impl WatcherOps for SlpToken { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], @@ -1490,7 +1494,7 @@ impl WatcherOps for SlpToken { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, @@ -2245,7 +2249,7 @@ mod slp_tests { payment_tx, other_pub: other_pub_bytes, time_lock_duration: 0, - time_lock: lock_time, + time_lock: lock_time as u64, secret_hash, amount, swap_contract_address: None, diff --git a/mm2src/coins/utxo/swap_proto_v2_scripts.rs b/mm2src/coins/utxo/swap_proto_v2_scripts.rs index d204f7e26b..153f0bc4bb 100644 --- a/mm2src/coins/utxo/swap_proto_v2_scripts.rs +++ b/mm2src/coins/utxo/swap_proto_v2_scripts.rs @@ -5,7 +5,7 @@ use keys::Public; use script::{Builder, Opcode, Script}; /// Builds a script for refundable dex_fee + premium taker transaction -pub fn dex_fee_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: &Public) -> Script { +pub fn taker_payment_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: &Public) -> Script { let mut builder = Builder::default() // Dex fee refund path, same lock time as for taker payment .push_opcode(Opcode::OP_IF) @@ -38,13 +38,3 @@ pub fn dex_fee_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: .push_opcode(Opcode::OP_ENDIF) .into_script() } - -#[cfg(test)] -mod swap_proto_v2_scripts_tests { - use super::*; - - #[test] - fn it_builds_the_dex_fee_script() { - let _script = dex_fee_script(1689069073, &[0; 20], &Public::default(), &Public::default()); - } -} diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 1a4c8e6c69..4f5970afc2 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,18 +15,18 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::watcher_common::validate_watcher_reward; -use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenAndSignDexFeeSpendResult, - GenDexFeeSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenTakerPaymentSpendArgs, + GenTakerPaymentSpendResult, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, - SendDexFeeWithPremiumArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TransactionResult, - TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateDexFeeArgs, - ValidateDexFeeError, ValidateDexFeeResult, ValidateDexFeeSpendPreimageError, - ValidateDexFeeSpendPreimageResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, WithdrawSenderAddress, - EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, - INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; + TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, + ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, + ValidateTakerPaymentError, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageError, + ValidateTakerPaymentSpendPreimageResult, VerificationError, VerificationResult, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, + WithdrawResult, WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, + INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; @@ -58,7 +58,8 @@ use std::cmp::Ordering; use std::collections::hash_map::{Entry, HashMap}; use std::str::FromStr; use std::sync::atomic::Ordering as AtomicOrdering; -use utxo_signer::with_key_pair::{calc_and_sign_sighash, p2sh_spend, signature_hash_to_sign}; +use utxo_signer::with_key_pair::{calc_and_sign_sighash, p2sh_spend, signature_hash_to_sign, SIGHASH_ALL, + SIGHASH_SINGLE}; use utxo_signer::UtxoSignerOps; pub use chain::Transaction as UtxoTx; @@ -1212,40 +1213,16 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI pub type GenDexFeeSpendResult = MmResult; -enum CalcPremiumBy { - DeductMinerFee, - UseExactAmount(u64), -} - -async fn gen_dex_fee_spend_preimage( +async fn gen_taker_payment_spend_preimage( coin: &T, - args: &GenDexFeeSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_>, lock_time: LocktimeSetting, - calc_premium: CalcPremiumBy, ) -> GenDexFeeSpendResult { - let mut prev_tx: UtxoTx = - deserialize(args.dex_fee_tx).map_to_mm(|e| TxGenError::TxDeserialization(e.to_string()))?; + let mut prev_tx: UtxoTx = deserialize(args.taker_tx).map_to_mm(|e| TxGenError::TxDeserialization(e.to_string()))?; prev_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_tx); let dex_fee_sat = sat_from_big_decimal(&args.dex_fee_amount, coin.as_ref().decimals)?; - let premium_sat = match calc_premium { - CalcPremiumBy::UseExactAmount(sat) => sat, - CalcPremiumBy::DeductMinerFee => { - let miner_fee = coin - .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await?; - - let premium_sat = sat_from_big_decimal(&args.premium_amount, coin.as_ref().decimals)?; - if miner_fee + coin.as_ref().dust_amount > premium_sat { - return MmError::err(TxGenError::MinerFeeExceedsPremium { - miner_fee: big_decimal_from_sat_unsigned(miner_fee, coin.as_ref().decimals), - premium: args.premium_amount.clone(), - }); - } - premium_sat - miner_fee - }, - }; let dex_fee_address = address_from_raw_pubkey( args.dex_fee_pub, @@ -1261,51 +1238,34 @@ async fn gen_dex_fee_spend_preimage( script_pubkey: Builder::build_p2pkh(&dex_fee_address.hash).to_bytes(), }; - let premium_address = address_from_raw_pubkey( - args.maker_pub, - coin.as_ref().conf.pub_addr_prefix, - coin.as_ref().conf.pub_t_addr_prefix, - coin.as_ref().conf.checksum_type, - coin.as_ref().conf.bech32_hrp.clone(), - coin.addr_format().clone(), - ) - .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive premium_address: {}", e)))?; - let premium_output = TransactionOutput { - value: premium_sat, - script_pubkey: Builder::build_p2pkh(&premium_address.hash).to_bytes(), - }; - - p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, SEQUENCE_FINAL, vec![ - dex_fee_output, - premium_output, - ]) - .await - .map_to_mm(TxGenError::Legacy) + p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, SEQUENCE_FINAL, vec![dex_fee_output]) + .await + .map_to_mm(TxGenError::Legacy) } -pub async fn gen_and_sign_dex_fee_spend_preimage( +pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, - args: &GenDexFeeSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_>, htlc_keypair: &KeyPair, -) -> GenAndSignDexFeeSpendResult { +) -> GenTakerPaymentSpendResult { let maker_pub = Public::from_slice(args.maker_pub).map_to_mm(|e| TxGenError::InvalidPubkey(e.to_string()))?; let taker_pub = Public::from_slice(args.taker_pub).map_to_mm(|e| TxGenError::InvalidPubkey(e.to_string()))?; + let time_lock = args + .time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; - let preimage = gen_dex_fee_spend_preimage( - coin, - args, - LocktimeSetting::CalcByHtlcLocktime(args.time_lock), - CalcPremiumBy::DeductMinerFee, - ) - .await?; + let preimage = gen_taker_payment_spend_preimage(coin, args, LocktimeSetting::CalcByHtlcLocktime(time_lock)).await?; - let redeem_script = swap_proto_v2_scripts::dex_fee_script(args.time_lock, args.secret_hash, &taker_pub, &maker_pub); + let redeem_script = + swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, &taker_pub, &maker_pub); let signature = calc_and_sign_sighash( &preimage, DEFAULT_SWAP_VOUT, &redeem_script, htlc_keypair, coin.as_ref().conf.signature_version, + SIGHASH_SINGLE, coin.as_ref().conf.fork_id, )?; let preimage_tx: UtxoTx = preimage.into(); @@ -1315,94 +1275,109 @@ pub async fn gen_and_sign_dex_fee_spend_preimage( }) } -pub async fn validate_dex_fee_spend_preimage( +/// Common implementation of taker payment spend preimage validation for UTXO coins. +/// Checks taker's signature and compares received preimage with the expected tx. +pub async fn validate_taker_payment_spend_preimage( coin: &T, - gen_args: &GenDexFeeSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_>, preimage: &TxPreimageWithSig, -) -> ValidateDexFeeSpendPreimageResult { +) -> ValidateTakerPaymentSpendPreimageResult { // TODO validate that preimage has exactly 2 outputs let actual_preimage_tx: UtxoTx = deserialize(preimage.preimage.as_slice()) - .map_to_mm(|e| ValidateDexFeeSpendPreimageError::TxDeserialization(e.to_string()))?; + .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::TxDeserialization(e.to_string()))?; let maker_pub = Public::from_slice(gen_args.maker_pub) - .map_to_mm(|e| ValidateDexFeeSpendPreimageError::InvalidPubkey(e.to_string()))?; + .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::InvalidPubkey(e.to_string()))?; let taker_pub = Public::from_slice(gen_args.taker_pub) - .map_to_mm(|e| ValidateDexFeeSpendPreimageError::InvalidPubkey(e.to_string()))?; + .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::InvalidPubkey(e.to_string()))?; // TODO validate premium amount. Might be a bit tricky in the case of dynamic miner fee // TODO validate that output amounts are larger than dust - let premium = match actual_preimage_tx.outputs.get(1) { - Some(o) => o.value, - None => { - return MmError::err(ValidateDexFeeSpendPreimageError::InvalidPreimage( - "Preimage doesn't have output 1".into(), - )) - }, - }; - - // Here, we have to use the exact lock time and premium amount from the preimage because maker + // Here, we have to use the exact lock time from the preimage because maker // can get different values (e.g. if MTP advances during preimage exchange/fee rate changes) - let expected_preimage = gen_dex_fee_spend_preimage( - coin, - gen_args, - LocktimeSetting::UseExact(actual_preimage_tx.lock_time), - CalcPremiumBy::UseExactAmount(premium), - ) - .await?; + let expected_preimage = + gen_taker_payment_spend_preimage(coin, gen_args, LocktimeSetting::UseExact(actual_preimage_tx.lock_time)) + .await?; + + let time_lock = gen_args + .time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| ValidateTakerPaymentSpendPreimageError::LocktimeOverflow(e.to_string()))?; let redeem_script = - swap_proto_v2_scripts::dex_fee_script(gen_args.time_lock, gen_args.secret_hash, &taker_pub, &maker_pub); + swap_proto_v2_scripts::taker_payment_script(time_lock, gen_args.secret_hash, &taker_pub, &maker_pub); let sig_hash = signature_hash_to_sign( &expected_preimage, DEFAULT_SWAP_VOUT, &redeem_script, coin.as_ref().conf.signature_version, + SIGHASH_SINGLE, coin.as_ref().conf.fork_id, )?; if !taker_pub .verify(&sig_hash, &preimage.signature.clone().into()) - .map_to_mm(|e| ValidateDexFeeSpendPreimageError::SignatureVerificationFailure(e.to_string()))? + .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::SignatureVerificationFailure(e.to_string()))? { - return MmError::err(ValidateDexFeeSpendPreimageError::InvalidTakerSignature); + return MmError::err(ValidateTakerPaymentSpendPreimageError::InvalidTakerSignature); }; let expected_preimage_tx: UtxoTx = expected_preimage.into(); if expected_preimage_tx != actual_preimage_tx { - return MmError::err(ValidateDexFeeSpendPreimageError::InvalidPreimage( + return MmError::err(ValidateTakerPaymentSpendPreimageError::InvalidPreimage( "Preimage is not equal to expected".into(), )); } Ok(()) } -pub async fn sign_and_broadcast_dex_fee_spend( +/// Common implementation of taker payment spend finalization and broadcast for UTXO coins. +/// Appends maker output to the preimage, signs it with SIGHASH_ALL and submits the resulting tx to coin's RPC. +pub async fn sign_and_broadcast_taker_payment_spend( coin: &T, preimage: &TxPreimageWithSig, - gen_args: &GenDexFeeSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_>, secret: &[u8], htlc_keypair: &KeyPair, ) -> TransactionResult { let taker_pub = try_tx_s!(Public::from_slice(gen_args.taker_pub)); - let mut dex_fee_tx: UtxoTx = try_tx_s!(deserialize(gen_args.dex_fee_tx)); - dex_fee_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - drop_mutability!(dex_fee_tx); + let mut taker_tx: UtxoTx = try_tx_s!(deserialize(gen_args.taker_tx)); + taker_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; + drop_mutability!(taker_tx); let mut preimage_tx: UtxoTx = try_tx_s!(deserialize(preimage.preimage.as_slice())); preimage_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(preimage_tx); let secret_hash = dhash160(secret); - let redeem_script = swap_proto_v2_scripts::dex_fee_script( - gen_args.time_lock, + let redeem_script = swap_proto_v2_scripts::taker_payment_script( + try_tx_s!(gen_args.time_lock.try_into()), secret_hash.as_slice(), &taker_pub, htlc_keypair.public(), ); let mut signer: TransactionInputSigner = preimage_tx.clone().into(); - signer.inputs[0].amount = dex_fee_tx.outputs[0].value; + signer.inputs[0].amount = taker_tx.outputs[0].value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; + + let miner_fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); + + let maker_amount = &gen_args.trading_amount + &gen_args.premium_amount; + let maker_sat = try_tx_s!(sat_from_big_decimal(&maker_amount, coin.as_ref().decimals)); + if miner_fee + coin.as_ref().dust_amount > maker_sat { + return TX_PLAIN_ERR!("Maker amount is too small to cover miner fee"); + } + + let maker_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()); + let maker_output = TransactionOutput { + value: maker_sat - miner_fee, + script_pubkey: output_script(maker_address, ScriptType::P2PKH).to_bytes(), + }; + signer.outputs.push(maker_output); drop_mutability!(signer); let maker_signature = try_tx_s!(calc_and_sign_sighash( @@ -1411,13 +1386,15 @@ pub async fn sign_and_broadcast_dex_fee_spend( &redeem_script, htlc_keypair, coin.as_ref().conf.signature_version, + SIGHASH_ALL, coin.as_ref().conf.fork_id )); - let sig_hash_all_fork_id = 1 | coin.as_ref().conf.fork_id as u8; + let sig_hash_single_fork_id = (SIGHASH_SINGLE | coin.as_ref().conf.fork_id) as u8; let mut taker_signature_with_sighash = preimage.signature.clone(); - taker_signature_with_sighash.push(sig_hash_all_fork_id); + taker_signature_with_sighash.push(sig_hash_single_fork_id); drop_mutability!(taker_signature_with_sighash); + let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; let mut maker_signature_with_sighash: Vec = maker_signature.take(); maker_signature_with_sighash.push(sig_hash_all_fork_id); drop_mutability!(maker_signature_with_sighash); @@ -1468,7 +1445,7 @@ where outputs, } = try_tx_fus!(generate_swap_payment_outputs( &coin, - args.time_lock, + try_tx_fus!(args.time_lock.try_into()), maker_htlc_key_pair.public_slice(), args.other_pubkey, args.secret_hash, @@ -1505,7 +1482,7 @@ where outputs, } = try_tx_fus!(generate_swap_payment_outputs( &coin, - args.time_lock, + try_tx_fus!(args.time_lock.try_into()), taker_htlc_key_pair.public_slice(), args.other_pubkey, args.secret_hash, @@ -1543,14 +1520,14 @@ pub fn send_maker_spends_taker_payment(coin: T, args .push_opcode(Opcode::OP_0) .into_script(); + let time_lock = try_tx_fus!(args.time_lock.try_into()); let redeem_script = payment_script( - args.time_lock, + time_lock, args.secret_hash, &try_tx_fus!(Public::from_slice(args.other_pubkey)), key_pair.public(), ) .into(); - let time_lock = args.time_lock; let fut = async move { let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1769,15 +1746,16 @@ pub fn send_taker_spends_maker_payment(coin: T, args .push_data(args.secret) .push_opcode(Opcode::OP_0) .into_script(); + + let time_lock = try_tx_fus!(args.time_lock.try_into()); let redeem_script = payment_script( - args.time_lock, + time_lock, args.secret_hash, &try_tx_fus!(Public::from_slice(args.other_pubkey)), key_pair.public(), ) .into(); - let time_lock = args.time_lock; let fut = async move { let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1832,16 +1810,17 @@ async fn refund_htlc_payment( let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); + let time_lock = try_tx_s!(args.time_lock.try_into()); + let redeem_script = match payment_type { SwapPaymentType::TakerOrMakerPayment => { - payment_script(args.time_lock, args.secret_hash, key_pair.public(), &other_public).into() + payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public).into() }, - SwapPaymentType::DexFeeWithPremium => { - swap_proto_v2_scripts::dex_fee_script(args.time_lock, args.secret_hash, key_pair.public(), &other_public) + SwapPaymentType::TakerPaymentV2 => { + swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public) .into() }, }; - let time_lock = args.time_lock; let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await @@ -2194,6 +2173,10 @@ pub fn validate_maker_payment( let other_pub = &try_f!(Public::from_slice(&input.other_pub) .map_to_mm(|err| ValidatePaymentError::InvalidParameter(err.to_string()))); + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); validate_payment( coin.clone(), tx, @@ -2203,7 +2186,7 @@ pub fn validate_maker_payment( &input.secret_hash, input.amount, input.watcher_reward, - input.time_lock, + time_lock, input.try_spv_proof_until, input.confirmations, ) @@ -2221,7 +2204,11 @@ pub fn watcher_validate_taker_payment( let maker_pub = &try_f!( Public::from_slice(&input.maker_pub).map_err(|err| ValidatePaymentError::InvalidParameter(err.to_string())) ); - let expected_redeem = payment_script(input.time_lock, &input.secret_hash, taker_pub, maker_pub); + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); + let expected_redeem = payment_script(time_lock, &input.secret_hash, taker_pub, maker_pub); let coin = coin.clone(); let fut = async move { @@ -2293,7 +2280,10 @@ pub fn validate_taker_payment( let other_pub = &try_f!(Public::from_slice(&input.other_pub) .map_to_mm(|err| ValidatePaymentError::InvalidParameter(err.to_string()))); - + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); validate_payment( coin.clone(), tx, @@ -2303,7 +2293,7 @@ pub fn validate_taker_payment( &input.secret_hash, input.amount, input.watcher_reward, - input.time_lock, + time_lock, input.try_spv_proof_until, input.confirmations, ) @@ -2395,7 +2385,7 @@ pub async fn search_for_swap_tx_spend_my + SwapOps>( ) -> Result, String> { search_for_swap_output_spend( coin.as_ref(), - input.time_lock, + try_s!(input.time_lock.try_into()), coin.derive_htlc_key_pair(input.swap_unique_data).public(), &try_s!(Public::from_slice(input.other_pub)), input.secret_hash, @@ -2413,7 +2403,7 @@ pub async fn search_for_swap_tx_spend_other + SwapOps>( ) -> Result, String> { search_for_swap_output_spend( coin.as_ref(), - input.time_lock, + try_s!(input.time_lock.try_into()), &try_s!(Public::from_slice(input.other_pub)), coin.derive_htlc_key_pair(input.swap_unique_data).public(), input.secret_hash, @@ -2479,52 +2469,23 @@ pub async fn get_taker_watcher_reward Result, String> { let spend_tx: UtxoTx = try_s!(deserialize(spend_tx).map_err(|e| ERRL!("{:?}", e))); - for (input_idx, input) in spend_tx.inputs.into_iter().enumerate() { + let expected_secret_hash = if secret_hash.len() == 32 { + ripemd160(secret_hash) + } else { + H160::from(secret_hash) + }; + for input in spend_tx.inputs.into_iter() { let script: Script = input.script_sig.clone().into(); - let instruction = match script.get_instruction(1) { - Some(Ok(instr)) => instr, - Some(Err(e)) => { - warn!("{:?}", e); - continue; - }, - None => { - warn!("Couldn't find secret in {:?} input", input_idx); - continue; - }, - }; - - if instruction.opcode != Opcode::OP_PUSHBYTES_32 { - warn!( - "Expected {:?} opcode, found {:?} in {:?} input", - Opcode::OP_PUSHBYTES_32, - instruction.opcode, - input_idx - ); - continue; - } - - let secret = match instruction.data { - Some(data) => data.to_vec(), - None => { - warn!("Secret is empty in {:?} input", input_idx); - continue; - }, - }; - - let expected_secret_hash = if secret_hash.len() == 32 { - ripemd160(secret_hash) - } else { - H160::from(secret_hash) - }; - let actual_secret_hash = dhash160(&secret); - if actual_secret_hash != expected_secret_hash { - warn!( - "Invalid secret hash {:?}, expected {:?}", - actual_secret_hash, expected_secret_hash - ); - continue; + for instruction in script.iter().flatten() { + if instruction.opcode == Opcode::OP_PUSHBYTES_32 { + if let Some(secret) = instruction.data { + let actual_secret_hash = dhash160(secret); + if actual_secret_hash == expected_secret_hash { + return Ok(secret.to_vec()); + } + } + } } - return Ok(secret); } ERR!("Couldn't extract secret") } @@ -4210,7 +4171,7 @@ struct SwapPaymentOutputsResult { enum SwapPaymentType { TakerOrMakerPayment, - DexFeeWithPremium, + TakerPaymentV2, } fn generate_swap_payment_outputs( @@ -4229,8 +4190,8 @@ where let other_public = try_s!(Public::from_slice(other_pub)); let redeem_script = match payment_type { SwapPaymentType::TakerOrMakerPayment => payment_script(time_lock, secret_hash, &my_public, &other_public), - SwapPaymentType::DexFeeWithPremium => { - swap_proto_v2_scripts::dex_fee_script(time_lock, secret_hash, &my_public, &other_public) + SwapPaymentType::TakerPaymentV2 => { + swap_proto_v2_scripts::taker_payment_script(time_lock, secret_hash, &my_public, &other_public) }, }; let redeem_script_hash = dhash160(&redeem_script); @@ -4586,24 +4547,25 @@ where .collect() } -pub async fn send_dex_fee_with_premium(coin: T, args: SendDexFeeWithPremiumArgs<'_>) -> TransactionResult +/// Common implementation of combined taker payment generation and broadcast for UTXO coins. +pub async fn send_combined_taker_payment(coin: T, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { let taker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); - let total_amount = &args.dex_fee_amount + &args.premium_amount; + let total_amount = &args.dex_fee_amount + &args.premium_amount + &args.trading_amount; let SwapPaymentOutputsResult { payment_address, outputs, } = try_tx_s!(generate_swap_payment_outputs( &coin, - args.time_lock, + try_tx_s!(args.time_lock.try_into()), taker_htlc_key_pair.public_slice(), args.other_pub, args.secret_hash, total_amount, - SwapPaymentType::DexFeeWithPremium, + SwapPaymentType::TakerPaymentV2, )); if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { let addr_string = try_tx_s!(payment_address.display_address()); @@ -4616,25 +4578,34 @@ where send_outputs_from_my_address(coin, outputs).compat().await } -pub async fn validate_dex_fee_with_premium(coin: &T, args: ValidateDexFeeArgs<'_>) -> ValidateDexFeeResult +/// Common implementation of combined taker payment validation for UTXO coins. +pub async fn validate_combined_taker_payment( + coin: &T, + args: ValidateTakerPaymentArgs<'_>, +) -> ValidateTakerPaymentResult where T: UtxoCommonOps + SwapOps, { let dex_fee_tx: UtxoTx = - deserialize(args.dex_fee_tx).map_to_mm(|e| ValidateDexFeeError::TxDeserialization(e.to_string()))?; + deserialize(args.taker_tx).map_to_mm(|e| ValidateTakerPaymentError::TxDeserialization(e.to_string()))?; if dex_fee_tx.outputs.len() < 2 { - return MmError::err(ValidateDexFeeError::TxLacksOfOutputs); + return MmError::err(ValidateTakerPaymentError::TxLacksOfOutputs); } let taker_pub = - Public::from_slice(args.other_pub).map_to_mm(|e| ValidateDexFeeError::InvalidPubkey(e.to_string()))?; + Public::from_slice(args.other_pub).map_to_mm(|e| ValidateTakerPaymentError::InvalidPubkey(e.to_string()))?; let maker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); - let total_expected_amount = &args.dex_fee_amount + &args.premium_amount; + let total_expected_amount = &args.dex_fee_amount + &args.premium_amount + &args.trading_amount; let expected_amount_sat = sat_from_big_decimal(&total_expected_amount, coin.as_ref().decimals)?; - let redeem_script = swap_proto_v2_scripts::dex_fee_script( - args.time_lock, + let time_lock = args + .time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| ValidateTakerPaymentError::LocktimeOverflow(e.to_string()))?; + + let redeem_script = swap_proto_v2_scripts::taker_payment_script( + time_lock, args.secret_hash, &taker_pub, maker_htlc_key_pair.public(), @@ -4645,7 +4616,7 @@ where }; if dex_fee_tx.outputs[0] != expected_output { - return MmError::err(ValidateDexFeeError::InvalidDestinationOrAmount(format!( + return MmError::err(ValidateTakerPaymentError::InvalidDestinationOrAmount(format!( "Expected {:?}, got {:?}", expected_output, dex_fee_tx.outputs[0] ))); @@ -4657,20 +4628,21 @@ where .get_transaction_bytes(&dex_fee_tx.hash().reversed().into()) .compat() .await?; - if tx_bytes_from_rpc.0 != args.dex_fee_tx { - return MmError::err(ValidateDexFeeError::TxBytesMismatch { + if tx_bytes_from_rpc.0 != args.taker_tx { + return MmError::err(ValidateTakerPaymentError::TxBytesMismatch { from_rpc: tx_bytes_from_rpc, - actual: args.dex_fee_tx.into(), + actual: args.taker_tx.into(), }); } Ok(()) } -pub async fn refund_dex_fee_with_premium(coin: T, args: RefundPaymentArgs<'_>) -> TransactionResult +/// Common implementation of combined taker payment refund for UTXO coins. +pub async fn refund_combined_taker_payment(coin: T, args: RefundPaymentArgs<'_>) -> TransactionResult where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { - refund_htlc_payment(coin, args, SwapPaymentType::DexFeeWithPremium).await + refund_htlc_payment(coin, args, SwapPaymentType::TakerPaymentV2).await } #[test] diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 431faaee86..47582f8524 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,17 +23,17 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GenAndSignDexFeeSpendResult, GenDexFeeSpendArgs, GetWithdrawSenderAddress, IguanaPrivKey, + GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendDexFeeWithPremiumArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, TradePreimageValue, - TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, - ValidateDexFeeArgs, ValidateDexFeeResult, ValidateDexFeeSpendPreimageResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; + SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, + TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, + ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; @@ -360,7 +360,7 @@ impl SwapOps for UtxoStandardCoin { ) -> Box, Error = String> + Send> { utxo_common::check_if_my_payment_sent( self.clone(), - if_my_payment_sent_args.time_lock, + try_fus!(if_my_payment_sent_args.time_lock.try_into()), if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, @@ -490,7 +490,7 @@ impl WatcherOps for UtxoStandardCoin { fn create_taker_payment_refund_preimage( &self, taker_tx: &[u8], - time_lock: u32, + time_lock: u64, maker_pub: &[u8], secret_hash: &[u8], _swap_contract_address: &Option, @@ -499,7 +499,7 @@ impl WatcherOps for UtxoStandardCoin { utxo_common::create_taker_payment_refund_preimage( self, taker_tx, - time_lock, + try_tx_fus!(time_lock.try_into()), maker_pub, secret_hash, swap_unique_data, @@ -510,7 +510,7 @@ impl WatcherOps for UtxoStandardCoin { fn create_maker_payment_spend_preimage( &self, maker_payment_tx: &[u8], - time_lock: u32, + time_lock: u64, maker_pub: &[u8], secret_hash: &[u8], swap_unique_data: &[u8], @@ -518,7 +518,7 @@ impl WatcherOps for UtxoStandardCoin { utxo_common::create_maker_payment_spend_preimage( self, maker_payment_tx, - time_lock, + try_tx_fus!(time_lock.try_into()), maker_pub, secret_hash, swap_unique_data, @@ -584,44 +584,44 @@ impl WatcherOps for UtxoStandardCoin { #[async_trait] impl SwapOpsV2 for UtxoStandardCoin { - async fn send_dex_fee_with_premium(&self, args: SendDexFeeWithPremiumArgs<'_>) -> TransactionResult { - utxo_common::send_dex_fee_with_premium(self.clone(), args).await + async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_combined_taker_payment(self.clone(), args).await } - async fn validate_dex_fee_with_premium(&self, args: ValidateDexFeeArgs<'_>) -> ValidateDexFeeResult { - utxo_common::validate_dex_fee_with_premium(self, args).await + async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult { + utxo_common::validate_combined_taker_payment(self, args).await } - async fn refund_dex_fee_with_premium(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { - utxo_common::refund_dex_fee_with_premium(self.clone(), args).await + async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::refund_combined_taker_payment(self.clone(), args).await } - async fn gen_and_sign_dex_fee_spend_preimage( + async fn gen_taker_payment_spend_preimage( &self, - args: &GenDexFeeSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_>, swap_unique_data: &[u8], - ) -> GenAndSignDexFeeSpendResult { + ) -> GenTakerPaymentSpendResult { let key_pair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::gen_and_sign_dex_fee_spend_preimage(self, args, &key_pair).await + utxo_common::gen_and_sign_taker_payment_spend_preimage(self, args, &key_pair).await } - async fn validate_dex_fee_spend_preimage( + async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenDexFeeSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_>, preimage: &TxPreimageWithSig, - ) -> ValidateDexFeeSpendPreimageResult { - utxo_common::validate_dex_fee_spend_preimage(self, gen_args, preimage).await + ) -> ValidateTakerPaymentSpendPreimageResult { + utxo_common::validate_taker_payment_spend_preimage(self, gen_args, preimage).await } - async fn sign_and_broadcast_dex_fee_spend( + async fn sign_and_broadcast_taker_payment_spend( &self, preimage: &TxPreimageWithSig, - gen_args: &GenDexFeeSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::sign_and_broadcast_dex_fee_spend(self, preimage, gen_args, secret, &htlc_keypair).await + utxo_common::sign_and_broadcast_taker_payment_spend(self, preimage, gen_args, secret, &htlc_keypair).await } } diff --git a/mm2src/coins/utxo/utxo_tx_history_v2.rs b/mm2src/coins/utxo/utxo_tx_history_v2.rs index ca3b3a6dcd..29861a23dc 100644 --- a/mm2src/coins/utxo/utxo_tx_history_v2.rs +++ b/mm2src/coins/utxo/utxo_tx_history_v2.rs @@ -10,15 +10,16 @@ use crate::{BalanceError, BalanceResult, BlockHeightAndTime, HistorySyncState, M use async_trait::async_trait; use common::executor::Timer; use common::log::{error, info}; -use common::state_machine::prelude::*; -use common::state_machine::StateMachineTrait; use derive_more::Display; use keys::Address; use mm2_err_handle::prelude::*; use mm2_metrics::MetricsArc; use mm2_number::BigDecimal; +use mm2_state_machine::prelude::*; +use mm2_state_machine::state_machine::StateMachineTrait; use rpc::v1::types::H256 as H256Json; use std::collections::{hash_map::Entry, HashMap, HashSet}; +use std::convert::Infallible; use std::iter::FromIterator; use std::str::FromStr; @@ -148,6 +149,12 @@ struct UtxoTxHistoryStateMachine StateMachineTrait for UtxoTxHistoryStateMachine { type Result = (); + type Error = Infallible; +} + +impl StandardStateMachine + for UtxoTxHistoryStateMachine +{ } impl UtxoTxHistoryStateMachine @@ -733,7 +740,10 @@ pub async fn bch_and_slp_history_loop( metrics, balances, }; - state_machine.run(Box::new(Init::new())).await; + state_machine + .run(Box::new(Init::new())) + .await + .expect("The error of this machine is Infallible"); } pub async fn utxo_history_loop( @@ -751,7 +761,10 @@ pub async fn utxo_history_loop( metrics, balances: current_balances, }; - state_machine.run(Box::new(Init::new())).await; + state_machine + .run(Box::new(Init::new())) + .await + .expect("The error of this machine is Infallible"); } fn to_filtering_addresses(addresses: &HashSet
) -> FilteringAddresses { diff --git a/mm2src/coins/utxo_signer/src/with_key_pair.rs b/mm2src/coins/utxo_signer/src/with_key_pair.rs index a061fe1017..38b67b3b7b 100644 --- a/mm2src/coins/utxo_signer/src/with_key_pair.rs +++ b/mm2src/coins/utxo_signer/src/with_key_pair.rs @@ -9,6 +9,10 @@ use mm2_err_handle::prelude::*; use primitives::hash::H256; use script::{Builder, Script, SignatureVersion, TransactionInputSigner, UnsignedTransactionInput}; +pub const SIGHASH_ALL: u32 = 1; +pub const _SIGHASH_NONE: u32 = 2; +pub const SIGHASH_SINGLE: u32 = 3; + pub type UtxoSignWithKeyPairResult = Result>; #[derive(Debug, Display)] @@ -82,7 +86,15 @@ pub fn p2pk_spend( let unsigned_input = get_input(signer, input_index)?; let script = Builder::build_p2pk(key_pair.public()); - let signature = calc_and_sign_sighash(signer, input_index, &script, key_pair, signature_version, fork_id)?; + let signature = calc_and_sign_sighash( + signer, + input_index, + &script, + key_pair, + signature_version, + SIGHASH_ALL, + fork_id, + )?; Ok(p2pk_spend_with_signature(unsigned_input, fork_id, signature)) } @@ -106,7 +118,15 @@ pub fn p2pkh_spend( }); } - let signature = calc_and_sign_sighash(signer, input_index, &script, key_pair, signature_version, fork_id)?; + let signature = calc_and_sign_sighash( + signer, + input_index, + &script, + key_pair, + signature_version, + SIGHASH_ALL, + fork_id, + )?; Ok(p2pkh_spend_with_signature( unsigned_input, key_pair.public(), @@ -133,6 +153,7 @@ pub fn p2sh_spend( &redeem_script, key_pair, signature_version, + SIGHASH_ALL, fork_id, )?; Ok(p2sh_spend_with_signature( @@ -164,7 +185,15 @@ pub fn p2wpkh_spend( }); } - let signature = calc_and_sign_sighash(signer, input_index, &script, key_pair, signature_version, fork_id)?; + let signature = calc_and_sign_sighash( + signer, + input_index, + &script, + key_pair, + signature_version, + SIGHASH_ALL, + fork_id, + )?; Ok(p2wpkh_spend_with_signature( unsigned_input, key_pair.public(), @@ -180,9 +209,17 @@ pub fn calc_and_sign_sighash( output_script: &Script, key_pair: &KeyPair, signature_version: SignatureVersion, + sighash_type: u32, fork_id: u32, ) -> UtxoSignWithKeyPairResult { - let sighash = signature_hash_to_sign(signer, input_index, output_script, signature_version, fork_id)?; + let sighash = signature_hash_to_sign( + signer, + input_index, + output_script, + signature_version, + sighash_type, + fork_id, + )?; sign_message(&sighash, key_pair) } @@ -191,11 +228,12 @@ pub fn signature_hash_to_sign( input_index: usize, output_script: &Script, signature_version: SignatureVersion, + sighash_type: u32, fork_id: u32, ) -> UtxoSignWithKeyPairResult { let input_amount = get_input(signer, input_index)?.amount; - let sighash_type = 1 | fork_id; + let sighash_type = sighash_type | fork_id; Ok(signer.signature_hash( input_index, input_amount, diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index d838db26f6..047a1cf1fd 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -52,6 +52,7 @@ use script::{Builder as ScriptBuilder, Opcode, Script, TransactionInputSigner}; use serde_json::Value as Json; use serialization::CoinVariant; use std::collections::{HashMap, HashSet}; +use std::convert::TryInto; use std::iter; use std::path::PathBuf; use std::sync::Arc; @@ -1193,7 +1194,7 @@ impl SwapOps for ZCoin { let maker_key_pair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); let secret_hash = maker_payment_args.secret_hash.to_vec(); - let time_lock = maker_payment_args.time_lock; + let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); let amount = maker_payment_args.amount; let fut = async move { let utxo_tx = try_tx_s!( @@ -1217,7 +1218,7 @@ impl SwapOps for ZCoin { let taker_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); let secret_hash = taker_payment_args.secret_hash.to_vec(); - let time_lock = taker_payment_args.time_lock; + let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); let amount = taker_payment_args.amount; let fut = async move { let utxo_tx = try_tx_s!( @@ -1239,7 +1240,7 @@ impl SwapOps for ZCoin { fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(ZTransaction::read(maker_spends_payment_args.other_payment_tx)); let key_pair = self.derive_htlc_key_pair(maker_spends_payment_args.swap_unique_data); - let time_lock = maker_spends_payment_args.time_lock; + let time_lock = try_tx_fus!(maker_spends_payment_args.time_lock.try_into()); let redeem_script = payment_script( time_lock, maker_spends_payment_args.secret_hash, @@ -1270,7 +1271,7 @@ impl SwapOps for ZCoin { fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(ZTransaction::read(taker_spends_payment_args.other_payment_tx)); let key_pair = self.derive_htlc_key_pair(taker_spends_payment_args.swap_unique_data); - let time_lock = taker_spends_payment_args.time_lock; + let time_lock = try_tx_fus!(taker_spends_payment_args.time_lock.try_into()); let redeem_script = payment_script( time_lock, taker_spends_payment_args.secret_hash, @@ -1301,7 +1302,7 @@ impl SwapOps for ZCoin { async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { let tx = try_tx_s!(ZTransaction::read(taker_refunds_payment_args.payment_tx)); let key_pair = self.derive_htlc_key_pair(taker_refunds_payment_args.swap_unique_data); - let time_lock = taker_refunds_payment_args.time_lock; + let time_lock = try_tx_s!(taker_refunds_payment_args.time_lock.try_into()); let redeem_script = payment_script( time_lock, taker_refunds_payment_args.secret_hash, @@ -1326,7 +1327,7 @@ impl SwapOps for ZCoin { async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { let tx = try_tx_s!(ZTransaction::read(maker_refunds_payment_args.payment_tx)); let key_pair = self.derive_htlc_key_pair(maker_refunds_payment_args.swap_unique_data); - let time_lock = maker_refunds_payment_args.time_lock; + let time_lock = try_tx_s!(maker_refunds_payment_args.time_lock.try_into()); let redeem_script = payment_script( time_lock, maker_refunds_payment_args.secret_hash, @@ -1450,7 +1451,7 @@ impl SwapOps for ZCoin { ) -> Box, Error = String> + Send> { utxo_common::check_if_my_payment_sent( self.clone(), - if_my_payment_sent_args.time_lock, + try_fus!(if_my_payment_sent_args.time_lock.try_into()), if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, @@ -1579,7 +1580,7 @@ impl WatcherOps for ZCoin { fn create_taker_payment_refund_preimage( &self, _taker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_contract_address: &Option, @@ -1591,7 +1592,7 @@ impl WatcherOps for ZCoin { fn create_maker_payment_spend_preimage( &self, _maker_payment_tx: &[u8], - _time_lock: u32, + _time_lock: u64, _maker_pub: &[u8], _secret_hash: &[u8], _swap_unique_data: &[u8], diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index efe4079ea2..12789b2090 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -1,5 +1,5 @@ use bitcrypto::dhash160; -use common::{block_on, now_sec_u32}; +use common::{block_on, now_sec}; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_test_helpers::for_tests::zombie_conf; use std::path::PathBuf; @@ -37,7 +37,7 @@ fn zombie_coin_send_and_refund_maker_payment() { )) .unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let taker_pub = coin.utxo_arc.priv_key_policy.activated_key_or_err().unwrap().public(); let secret_hash = [0; 20]; @@ -94,7 +94,7 @@ fn zombie_coin_send_and_spend_maker_payment() { )) .unwrap(); - let lock_time = now_sec_u32() - 1000; + let lock_time = now_sec() - 1000; let taker_pub = coin.utxo_arc.priv_key_policy.activated_key_or_err().unwrap().public(); let secret = [0; 32]; let secret_hash = dhash160(&secret); diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 653ad11353..058661f42c 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -11,12 +11,9 @@ //! binary #![allow(uncommon_codepoints)] -#![feature(allocator_api)] #![feature(integer_atomics, panic_info_message)] #![feature(async_closure)] #![feature(hash_raw_entry)] -#![feature(negative_impls)] -#![feature(auto_traits)] #![feature(drain_filter)] #[macro_use] extern crate arrayref; @@ -120,7 +117,6 @@ pub mod custom_iter; pub mod number_type_casting; pub mod password_policy; pub mod seri; -#[path = "patterns/state_machine.rs"] pub mod state_machine; pub mod time_cache; #[cfg(not(target_arch = "wasm32"))] @@ -145,7 +141,6 @@ use rand::{rngs::SmallRng, SeedableRng}; use serde::{de, ser}; use serde_json::{self as json, Value as Json}; use sha2::{Digest, Sha256}; -use std::alloc::Allocator; use std::convert::TryInto; use std::fmt::Write as FmtWrite; use std::fs::File; @@ -198,11 +193,6 @@ lazy_static! { pub(crate) static ref LOG_FILE: Mutex> = Mutex::new(open_log_file()); } -pub auto trait NotSame {} -impl !NotSame for (X, X) {} -// Makes the error conversion work for structs/enums containing Box -impl NotSame for Box {} - /// Converts u64 satoshis to f64 pub fn sat_to_f(sat: u64) -> f64 { sat as f64 / SATOSHIS as f64 } diff --git a/mm2src/mm2_bitcoin/script/src/sign.rs b/mm2src/mm2_bitcoin/script/src/sign.rs index 3dbb168434..1bd01127a4 100644 --- a/mm2src/mm2_bitcoin/script/src/sign.rs +++ b/mm2src/mm2_bitcoin/script/src/sign.rs @@ -289,16 +289,16 @@ impl TransactionInputSigner { return 1u8.into(); } - if sighash.base == SighashBase::Single && input_index >= self.outputs.len() { - return 1u8.into(); - } - if self.version >= 3 && self.overwintered { return self .signature_hash_overwintered(input_index, script_pubkey, sighashtype, sighash) .unwrap(); } + if sighash.base == SighashBase::Single && input_index >= self.outputs.len() { + return 1u8.into(); + } + let script_pubkey = script_pubkey.without_separators(); let inputs = if sighash.anyone_can_pay { @@ -437,7 +437,7 @@ impl TransactionInputSigner { input_index: usize, script_pubkey: &Script, sighashtype: u32, - _sighash: Sighash, + sighash: Sighash, ) -> Result { let mut sig_hash_stream = Stream::new(); @@ -458,34 +458,54 @@ impl TransactionInputSigner { sig_hash_stream.append(&header); sig_hash_stream.append(&self.version_group_id); - let mut prev_out_stream = Stream::new(); - for input in self.inputs.iter() { - prev_out_stream.append(&input.previous_output); - } - sig_hash_stream.append(&blake_2b_256_personal( - &prev_out_stream.out(), - ZCASH_PREVOUTS_HASH_PERSONALIZATION, - )); - - let mut sequence_stream = Stream::new(); - for input in self.inputs.iter() { - sequence_stream.append(&input.sequence); + if !sighash.anyone_can_pay { + let mut prev_out_stream = Stream::new(); + for input in self.inputs.iter() { + prev_out_stream.append(&input.previous_output); + } + sig_hash_stream.append(&blake_2b_256_personal( + &prev_out_stream.out(), + ZCASH_PREVOUTS_HASH_PERSONALIZATION, + )); + } else { + sig_hash_stream.append(&H256::default()); } - sig_hash_stream.append(&blake_2b_256_personal( - &sequence_stream.out(), - ZCASH_SEQUENCE_HASH_PERSONALIZATION, - )); + if !sighash.anyone_can_pay && sighash.base != SighashBase::Single && sighash.base != SighashBase::None { + let mut sequence_stream = Stream::new(); + for input in self.inputs.iter() { + sequence_stream.append(&input.sequence); + } - let mut outputs_stream = Stream::new(); - for output in self.outputs.iter() { - outputs_stream.append(output); + sig_hash_stream.append(&blake_2b_256_personal( + &sequence_stream.out(), + ZCASH_SEQUENCE_HASH_PERSONALIZATION, + )); + } else { + sig_hash_stream.append(&H256::default()); } - sig_hash_stream.append(&blake_2b_256_personal( - &outputs_stream.out(), - ZCASH_OUTPUTS_HASH_PERSONALIZATION, - )); + if sighash.base != SighashBase::Single && sighash.base != SighashBase::None { + let mut outputs_stream = Stream::new(); + for output in self.outputs.iter() { + outputs_stream.append(output); + } + + sig_hash_stream.append(&blake_2b_256_personal( + &outputs_stream.out(), + ZCASH_OUTPUTS_HASH_PERSONALIZATION, + )); + } else if sighash.base == SighashBase::Single && input_index < self.outputs.len() { + let mut outputs_stream = Stream::new(); + outputs_stream.append(&self.outputs[input_index]); + + sig_hash_stream.append(&blake_2b_256_personal( + &outputs_stream.out(), + ZCASH_OUTPUTS_HASH_PERSONALIZATION, + )); + } else { + sig_hash_stream.append(&H256::default()); + } if !self.join_splits.is_empty() { let mut join_splits_stream = Stream::new(); @@ -525,7 +545,6 @@ impl TransactionInputSigner { } let hash_shielded_outputs = blake_2b_256_personal(&s_outputs_stream.out(), ZCASH_SHIELDED_OUTPUTS_HASH_PERSONALIZATION); - println!("hash_shielded_outputs {:?}", hash_shielded_outputs.reversed()); sig_hash_stream.append(&hash_shielded_outputs); } else { sig_hash_stream.append(&H256::default()); @@ -610,6 +629,7 @@ mod tests { use hash::{H160, H256}; use keys::{Address, AddressHashEnum, Private}; use script::Script; + use ser::deserialize; use sign::SignerHashAlgo; // http://www.righto.com/2014/02/bitcoins-hard-way-using-raw-bitcoin.html @@ -873,4 +893,370 @@ mod tests { hash.reversed() ); } + + #[test] + fn test_sapling_sig_hash_single() { + let tx = vec![ + 0x04, 0x00, 0x00, 0x80, 0x85, 0x20, 0x2f, 0x89, 0x02, 0x88, 0x1d, 0xdf, 0x4f, 0x95, 0x78, 0x97, 0x34, 0xfc, + 0xc1, 0x65, 0xee, 0x1e, 0x04, 0x40, 0x85, 0xb6, 0xe7, 0xa1, 0x77, 0x50, 0x8c, 0x29, 0xda, 0x0c, 0xe7, 0x7d, + 0xed, 0x75, 0x08, 0x98, 0xde, 0x89, 0xd2, 0x60, 0xd3, 0x02, 0x63, 0x52, 0x44, 0xcc, 0x75, 0xe1, 0x98, 0x34, + 0x52, 0x5f, 0xba, 0x56, 0x90, 0x0d, 0xe9, 0x93, 0x85, 0x44, 0x2e, 0xb9, 0xec, 0x9a, 0x5f, 0x18, 0x2b, 0x87, + 0x5d, 0x70, 0xb5, 0xb1, 0x53, 0x79, 0x0a, 0x1e, 0xe7, 0x9c, 0x0e, 0x86, 0x78, 0x37, 0x95, 0xfa, 0x06, 0x6a, + 0x00, 0x63, 0x00, 0x00, 0x63, 0xfc, 0x92, 0x29, 0x92, 0x00, 0x83, 0x64, 0xff, 0xfc, 0x7c, 0x00, 0xc0, 0x0e, + 0x0f, 0x99, 0xde, 0x47, 0x42, 0x89, 0x06, 0x00, 0x01, 0x39, 0x21, 0x97, 0xd6, 0x23, 0xf7, 0xeb, 0xda, 0x07, + 0xcd, 0x00, 0x58, 0xd9, 0xa1, 0xd1, 0x72, 0x04, 0x3c, 0x2f, 0xc9, 0x4f, 0x14, 0x19, 0x3e, 0x27, 0x0e, 0xef, + 0xe8, 0x3c, 0x3f, 0x01, 0xb2, 0x65, 0x05, 0x4c, 0x3f, 0x6a, 0x60, 0xe2, 0xb7, 0x6e, 0x17, 0x56, 0x08, 0x8b, + 0x87, 0xda, 0x83, 0x9f, 0x77, 0x2c, 0xbd, 0x0f, 0x27, 0x5c, 0x92, 0x28, 0x38, 0x5a, 0x04, 0xbb, 0x50, 0xec, + 0x3c, 0xfa, 0x9e, 0xe2, 0xe1, 0x5b, 0x15, 0x3d, 0x4c, 0x85, 0xfe, 0x50, 0xb6, 0x00, 0x62, 0x58, 0xe9, 0xe8, + 0xc2, 0x52, 0x99, 0xc0, 0x9d, 0xf8, 0xb4, 0x55, 0x46, 0x6b, 0xa2, 0x5f, 0x7e, 0x4c, 0x8f, 0xe7, 0xe2, 0x50, + 0xed, 0xba, 0x60, 0x69, 0x5d, 0xa4, 0x7f, 0xaa, 0xfd, 0xd6, 0x26, 0xba, 0x7e, 0x9d, 0x48, 0x96, 0xe4, 0xb8, + 0xa8, 0xa1, 0xa1, 0xdc, 0x21, 0x5b, 0x0a, 0x25, 0xee, 0xb0, 0x4e, 0xd1, 0xbe, 0xfb, 0x5b, 0x31, 0x38, 0xc6, + 0x9f, 0xe5, 0x28, 0xe7, 0x29, 0x11, 0x23, 0xfc, 0xdf, 0x8a, 0x36, 0x6c, 0x25, 0x7d, 0x32, 0x95, 0x38, 0x25, + 0x0a, 0x0c, 0xb7, 0xf5, 0x4e, 0x1c, 0x01, 0x6c, 0xe1, 0xc6, 0x23, 0xb2, 0xe2, 0x76, 0xa5, 0x2c, 0x6e, 0x41, + 0x24, 0x1b, 0x2a, 0xc5, 0x09, 0x37, 0x3c, 0x18, 0x81, 0x40, 0xe8, 0x36, 0x5c, 0x94, 0xf5, 0x8c, 0x63, 0xf2, + 0x7f, 0xf8, 0xe6, 0xe8, 0x69, 0xa9, 0x85, 0xaf, 0xb6, 0x1e, 0x97, 0xd8, 0xce, 0xec, 0x2a, 0x78, 0x24, 0xa5, + 0xc1, 0x07, 0xb0, 0xba, 0xa4, 0xd6, 0xe7, 0x9a, 0x6c, 0x71, 0x87, 0x2a, 0x7b, 0x3b, 0x17, 0xef, 0x91, 0x8a, + 0xe4, 0xe2, 0x5f, 0x98, 0xa7, 0x2d, 0xb5, 0x3b, 0xa7, 0xf2, 0x6e, 0x40, 0x8b, 0xd4, 0xd1, 0xf9, 0xe3, 0x47, + 0x4d, 0xdc, 0xa5, 0x83, 0x3f, 0xf5, 0xff, 0x8d, 0x11, 0xb1, 0xbf, 0x1e, 0x2b, 0xb4, 0xd1, 0x96, 0x8a, 0x82, + 0x38, 0x88, 0xbd, 0x91, 0xa2, 0x1a, 0x76, 0x79, 0x6b, 0xca, 0x44, 0x53, 0xe2, 0x89, 0x2d, 0x1b, 0x6e, 0x13, + 0x63, 0xed, 0x10, 0x7a, 0x9e, 0x7e, 0xd9, 0x3f, 0xb1, 0xda, 0x99, 0x4a, 0x9d, 0x4e, 0x7e, 0xc9, 0x2e, 0x29, + 0xa6, 0x87, 0xf2, 0x18, 0xd2, 0x8a, 0x76, 0x46, 0x06, 0x9b, 0xca, 0xcb, 0x4d, 0xa7, 0xba, 0xdf, 0x4e, 0xb1, + 0x33, 0x1a, 0xab, 0x21, 0x2b, 0x92, 0xc6, 0xea, 0x64, 0x76, 0xa0, 0xa0, 0x9d, 0x6b, 0xd2, 0xe0, 0xf7, 0x6f, + 0xa8, 0x73, 0x79, 0xab, 0xfd, 0x17, 0x58, 0x2f, 0x3e, 0xb2, 0x3b, 0x86, 0xc9, 0x66, 0x9f, 0x86, 0x73, 0x70, + 0x48, 0xd7, 0x71, 0x84, 0x9b, 0x8f, 0x70, 0xbd, 0x87, 0x99, 0x01, 0x3b, 0xe0, 0xbf, 0xbd, 0x7b, 0x57, 0xbe, + 0xa1, 0xa4, 0x9a, 0x4a, 0x39, 0x14, 0x79, 0x12, 0xd7, 0xba, 0xf6, 0x80, 0x04, 0xd4, 0x15, 0x02, 0x6b, 0xbc, + 0x6f, 0x69, 0x32, 0x5f, 0x4f, 0xf7, 0x87, 0x28, 0x77, 0x5a, 0x67, 0xaa, 0xdd, 0x72, 0x2c, 0x73, 0x31, 0x1d, + 0xba, 0x5c, 0x2c, 0xf1, 0x4c, 0xcb, 0xd5, 0x7e, 0xab, 0xed, 0x71, 0x92, 0x0f, 0xf9, 0x62, 0x32, 0x89, 0xbb, + 0x76, 0x05, 0x1c, 0x73, 0xa2, 0x06, 0xa3, 0xc2, 0xb4, 0x0c, 0xac, 0x01, 0xd5, 0xf1, 0x1f, 0xa6, 0x4c, 0x1b, + 0x7d, 0xed, 0x70, 0xea, 0x17, 0x42, 0x9c, 0x66, 0x21, 0xca, 0x9b, 0x92, 0x3c, 0x48, 0x11, 0x85, 0x0c, 0x3d, + 0xf4, 0x01, 0x3d, 0x17, 0xbd, 0xc5, 0x10, 0x1c, 0x8d, 0x80, 0xb3, 0xa0, 0x4a, 0x4c, 0xc2, 0x3d, 0x13, 0xfe, + 0x31, 0x84, 0xe8, 0xb1, 0xad, 0xe6, 0x35, 0x17, 0x59, 0x3f, 0x7b, 0xe6, 0x69, 0x48, 0xc0, 0x85, 0x7a, 0xec, + 0xe0, 0x1b, 0xc2, 0x72, 0x29, 0x5e, 0x60, 0xb1, 0x80, 0x69, 0x46, 0xc9, 0x3b, 0xc8, 0xc7, 0xd2, 0xa2, 0xed, + 0xc3, 0x7f, 0xa3, 0x7c, 0x47, 0x7a, 0x69, 0xa9, 0x0b, 0x59, 0xb4, 0xc6, 0x91, 0x2e, 0x91, 0x3a, 0x57, 0xef, + 0xa9, 0xd5, 0x4c, 0x7e, 0x80, 0xd5, 0xac, 0x8a, 0x42, 0x94, 0xd0, 0xfd, 0x31, 0xa4, 0x02, 0xe4, 0xb4, 0x7e, + 0xc7, 0xbf, 0x03, 0x31, 0xb2, 0xc9, 0xa4, 0x8f, 0x44, 0x57, 0x3f, 0xc7, 0xe7, 0xf1, 0x02, 0xed, 0x48, 0xc9, + 0x75, 0x08, 0xcb, 0xe4, 0x30, 0x65, 0xa9, 0xe9, 0x9f, 0xb4, 0xce, 0x13, 0x62, 0xbb, 0x8a, 0x76, 0xb1, 0x41, + 0x9d, 0x95, 0x03, 0x0e, 0x9c, 0x24, 0xee, 0xba, 0x9f, 0xf8, 0xcf, 0xda, 0x95, 0x7b, 0x17, 0x09, 0x8c, 0xdf, + 0x8c, 0x9a, 0x91, 0x9e, 0x47, 0xa1, 0x3a, 0x5b, 0x33, 0x46, 0xe3, 0x7e, 0x82, 0x7c, 0xc8, 0x3b, 0x3c, 0x9a, + 0xab, 0xf2, 0xd0, 0xba, 0x17, 0xff, 0x3d, 0x9e, 0x0d, 0x22, 0x3c, 0x41, 0xc8, 0x8e, 0xc2, 0x39, 0x1c, 0x76, + 0x62, 0x2d, 0x7b, 0xd6, 0x21, 0x17, 0x33, 0x1e, 0x21, 0xff, 0xec, 0x32, 0x72, 0xc1, 0xe1, 0x42, 0x39, 0x82, + 0xc6, 0xb6, 0x3a, 0xec, 0x8d, 0xbf, 0x5c, 0xa2, 0xdd, 0x15, 0x81, 0x0f, 0x53, 0x42, 0xaf, 0x49, 0xfa, 0xd2, + 0x79, 0xb7, 0xca, 0x23, 0xde, 0xd3, 0x08, 0x24, 0x79, 0x96, 0x30, 0xde, 0xdc, 0x6d, 0xb7, 0x24, 0xbc, 0xe1, + 0x11, 0x36, 0x21, 0xc4, 0xa6, 0x47, 0x9d, 0xd5, 0x55, 0xf4, 0x85, 0x21, 0x7c, 0xb5, 0x67, 0x13, 0x9e, 0xea, + 0xdd, 0x7e, 0xe8, 0xdc, 0x5b, 0x26, 0x62, 0xf1, 0x06, 0x6a, 0x7c, 0x60, 0xde, 0xe0, 0x09, 0x3c, 0x92, 0x46, + 0xde, 0x7a, 0x05, 0xe8, 0xb0, 0xf6, 0xbe, 0xf0, 0x03, 0x3d, 0xde, 0x2e, 0x87, 0xcb, 0xa6, 0x8d, 0x23, 0x6e, + 0xf6, 0x6a, 0x23, 0xd5, 0x5e, 0x7b, 0xd2, 0x8d, 0x02, 0x59, 0x9c, 0xca, 0x0d, 0xf7, 0xa9, 0x00, 0x63, 0x7b, + 0xb3, 0x46, 0x4d, 0x62, 0x2b, 0x7c, 0x9c, 0x9c, 0x8c, 0x91, 0x46, 0x89, 0x74, 0x88, 0x01, 0x64, 0xde, 0xf7, + 0x99, 0x90, 0x8a, 0x11, 0xa5, 0x91, 0xab, 0xb3, 0xc8, 0xd8, 0xbd, 0x9c, 0x12, 0xb1, 0xf6, 0xf3, 0xcd, 0xc9, + 0xed, 0x8e, 0x16, 0xe5, 0x7d, 0x23, 0x34, 0xb2, 0x17, 0x79, 0x7d, 0xf1, 0x90, 0x52, 0xfe, 0xeb, 0xed, 0x6c, + 0xdb, 0x99, 0xac, 0x44, 0xea, 0x13, 0xaf, 0xea, 0xc4, 0x37, 0x7d, 0x0f, 0xa3, 0x7e, 0xf5, 0x16, 0xdd, 0xac, + 0xea, 0xb0, 0xd9, 0x39, 0x5b, 0xd4, 0x40, 0x46, 0x0e, 0x28, 0xb5, 0xf5, 0x7a, 0x6e, 0xfd, 0x37, 0xd2, 0x68, + 0xa8, 0x64, 0xcb, 0x5c, 0xa3, 0x4b, 0xe2, 0x87, 0xe1, 0x04, 0x8e, 0xfc, 0x1e, 0x40, 0xcd, 0xf4, 0xfc, 0xfc, + 0x02, 0x4c, 0xf1, 0x82, 0x03, 0x8b, 0x9d, 0x80, 0xed, 0x1c, 0x07, 0x63, 0x62, 0x00, 0xc8, 0x19, 0xa7, 0xe7, + 0xc2, 0x40, 0xc3, 0xc4, 0xf7, 0xa9, 0x17, 0x32, 0xe3, 0xff, 0x13, 0xe2, 0xa5, 0x6a, 0x64, 0x66, 0x66, 0x10, + 0xca, 0xd9, 0x84, 0x1c, 0x1a, 0x93, 0x4f, 0xe9, 0x33, 0xb0, 0xf1, 0x9f, 0xb7, 0x1d, 0x06, 0x1c, 0x58, 0xf2, + 0x1a, 0x49, 0x81, 0xce, 0x3e, 0x68, 0xc5, 0x02, 0x39, 0x03, 0x60, 0x8d, 0xe5, 0x83, 0x02, 0xc6, 0xc8, 0xde, + 0xf4, 0xe5, 0x61, 0x9e, 0xc0, 0xd9, 0x1c, 0xf9, 0x35, 0x44, 0x75, 0x97, 0x2b, 0xfe, 0x0d, 0x75, 0x75, 0x60, + 0x2a, 0xaf, 0x0e, 0x9e, 0x88, 0x5c, 0x6b, 0xaf, 0x9d, 0x56, 0x7b, 0x1f, 0xcb, 0x63, 0x19, 0x0c, 0xb7, 0x92, + 0xf1, 0xd8, 0x71, 0x61, 0x1a, 0xdb, 0x4f, 0x3d, 0x1e, 0xd3, 0x28, 0x02, 0x69, 0x18, 0xe2, 0x8d, 0x2f, 0xd4, + 0x5a, 0xb9, 0xd3, 0x70, 0xe7, 0x29, 0x2e, 0xd7, 0x54, 0xce, 0x29, 0xfb, 0x78, 0x7f, 0xd5, 0xd0, 0x9e, 0x6d, + 0x47, 0xcb, 0xc8, 0x00, 0x21, 0xab, 0xf7, 0xd2, 0xef, 0xeb, 0xdb, 0xe0, 0xad, 0xd8, 0x70, 0x16, 0x8f, 0x51, + 0xdc, 0xc4, 0x09, 0x57, 0xa4, 0xa3, 0xc8, 0xe1, 0x92, 0x60, 0x13, 0x83, 0xb7, 0x68, 0x41, 0x36, 0xdc, 0xa2, + 0x82, 0x62, 0x3f, 0x31, 0xba, 0x7a, 0xe5, 0x36, 0x6b, 0x45, 0x3c, 0x6a, 0x26, 0xf6, 0x8a, 0x14, 0xdb, 0x65, + 0x59, 0xbc, 0xb1, 0x02, 0x37, 0x37, 0x9a, 0x27, 0xa9, 0x50, 0x2f, 0xf9, 0xd6, 0x4a, 0x33, 0x83, 0x20, 0x75, + 0x15, 0x30, 0xf1, 0xf8, 0x92, 0xa6, 0xd4, 0x6f, 0x50, 0x31, 0x1b, 0x5e, 0x18, 0xf0, 0x33, 0x6f, 0xc4, 0x77, + 0x21, 0x56, 0x66, 0xe1, 0x88, 0x93, 0x3c, 0x69, 0x39, 0x98, 0x9f, 0x6e, 0x6a, 0x3a, 0xdb, 0xa2, 0x29, 0x96, + 0xaa, 0xe6, 0xa0, 0xfe, 0x1b, 0xdd, 0xcb, 0xe1, 0x49, 0x6d, 0x96, 0x8d, 0xe0, 0x93, 0xdf, 0x44, 0xa3, 0x30, + 0x0f, 0x75, 0x15, 0xa1, 0x2c, 0x9d, 0x82, 0x22, 0x6d, 0x6b, 0x4d, 0x62, 0xc4, 0x6a, 0x21, 0x3d, 0x5f, 0x01, + 0x07, 0x10, 0x6f, 0xd2, 0xa2, 0x2d, 0x3b, 0x59, 0x86, 0x13, 0xdb, 0x49, 0x1f, 0x70, 0xcc, 0xb1, 0xf0, 0x3b, + 0x86, 0x59, 0x66, 0x9e, 0xd7, 0x44, 0x34, 0xe4, 0x3b, 0x77, 0x1f, 0x22, 0x78, 0x07, 0x10, 0xfb, 0xd8, 0xf2, + 0xf2, 0x0e, 0x98, 0x97, 0xdf, 0x5c, 0xc2, 0x35, 0x48, 0x77, 0x9c, 0x6c, 0x08, 0x30, 0x83, 0x9d, 0x23, 0x1c, + 0x3f, 0xf9, 0xac, 0x54, 0x40, 0x7d, 0xfd, 0xfc, 0xc5, 0x90, 0x14, 0xbf, 0x67, 0xd9, 0x68, 0x57, 0x06, 0xa5, + 0x62, 0x2e, 0x38, 0xf7, 0xa9, 0x33, 0xc3, 0x4a, 0xfb, 0xb6, 0xaa, 0x8c, 0xdf, 0xd9, 0x3b, 0xd2, 0xec, 0x91, + 0xad, 0x37, 0x90, 0x4c, 0xe1, 0x3b, 0x8a, 0xb8, 0xef, 0x77, 0x23, 0x66, 0xfa, 0xd3, 0xc3, 0xeb, 0xee, 0x8f, + 0x26, 0x11, 0xee, 0x7b, 0x6c, 0x2a, 0xf7, 0xe6, 0x53, 0xef, 0xbe, 0xc4, 0xdc, 0x4c, 0xbf, 0x13, 0xac, 0xf3, + 0x7e, 0x39, 0x9e, 0x2b, 0x0b, 0x05, 0xb6, 0x1c, 0xb7, 0xe1, 0x7b, 0x15, 0x62, 0x7b, 0x62, 0x96, 0x2e, 0x21, + 0x00, 0xb1, 0x95, 0xfe, 0xfe, 0x94, 0xbc, 0x48, 0x4e, 0x88, 0x13, 0x97, 0x00, 0x73, 0x7d, 0xe1, 0xa5, 0xec, + 0x7d, 0x9c, 0xc8, 0x5d, 0x53, 0x3b, 0x61, 0xec, 0xad, 0x86, 0x53, 0xce, 0xdb, 0xb7, 0x71, 0xf6, 0x75, 0xaf, + 0x61, 0xe4, 0xc6, 0xf7, 0xef, 0xaa, 0xcc, 0x9f, 0x7e, 0x42, 0x4c, 0x16, 0x71, 0x5b, 0x0a, 0x98, 0xc4, 0x46, + 0x05, 0x9a, 0x27, 0x1a, 0x27, 0xbd, 0x56, 0x9d, 0x1b, 0x5d, 0xbf, 0xae, 0x8f, 0x53, 0x89, 0x85, 0x24, 0xca, + 0xe8, 0x70, 0x59, 0xff, 0x34, 0xfb, 0x2a, 0x53, 0x32, 0x26, 0xbd, 0x29, 0xa0, 0xba, 0x6f, 0x8d, 0x08, 0x36, + 0xfd, 0x0a, 0x4c, 0x0d, 0x60, 0x9a, 0x72, 0xe1, 0x05, 0x39, 0xa4, 0x4f, 0x8c, 0x39, 0xf6, 0x27, 0x9b, 0xe3, + 0x96, 0xe4, 0x1c, 0xa9, 0xf2, 0x9a, 0x28, 0xce, 0x9f, 0xa0, 0xdd, 0x51, 0xa3, 0x02, 0xe7, 0x70, 0xe1, 0xe3, + 0xdb, 0x70, 0x6a, 0x34, 0xcb, 0x90, 0x4e, 0xf0, 0x8d, 0x9c, 0x82, 0xc5, 0x5b, 0xc7, 0x28, 0xc9, 0x55, 0xb1, + 0x20, 0xbb, 0x2e, 0xc3, 0x73, 0xfc, 0xff, 0xff, 0x3c, 0x46, 0xd6, 0x03, 0xab, 0x38, 0x78, 0x96, 0xd4, 0x9c, + 0xd2, 0x1b, 0x2f, 0x77, 0xec, 0xfb, 0xbb, 0x02, 0xa5, 0xe1, 0x53, 0xb1, 0x71, 0xaf, 0xed, 0x98, 0x6c, 0x15, + 0xda, 0x6f, 0x2d, 0x4c, 0xf7, 0x45, 0xd1, 0x99, 0x5f, 0x51, 0x36, 0xe1, 0xb3, 0xe6, 0x8a, 0x67, 0xa8, 0x99, + 0x6f, 0xe7, 0x65, 0x61, 0x6d, 0x8a, 0xa1, 0x1b, 0xcd, 0x9f, 0x8b, 0x59, 0x1d, 0xb8, 0x7e, 0xfc, 0xda, 0xaf, + 0xfd, 0x41, 0x00, 0x3e, 0xc7, 0x29, 0x36, 0x05, 0x42, 0x62, 0x08, 0x54, 0xfb, 0x04, 0xb8, 0x0c, 0xb8, 0x61, + 0xa6, 0x36, 0xa4, 0x71, 0x7d, 0x66, 0x68, 0x94, 0xc3, 0x2f, 0x1f, 0x2b, 0xf2, 0x24, 0x7c, 0xc4, 0x15, 0xde, + 0x1d, 0x0c, 0x4e, 0x71, 0x2b, 0x95, 0x88, 0x42, 0xd6, 0xa4, 0xb2, 0x76, 0xde, 0xa5, 0xdb, 0x88, 0x42, 0x3f, + 0x2b, 0x4c, 0x66, 0x4b, 0x1d, 0x2b, 0x18, 0x77, 0xba, 0xf3, 0x37, 0x47, 0x34, 0x36, 0x14, 0xe5, 0xeb, 0xe9, + 0xb7, 0xe1, 0x2e, 0xd0, 0x15, 0x3f, 0x9c, 0xa7, 0x45, 0x8e, 0x4d, 0xa4, 0x97, 0x63, 0x9d, 0xff, 0x13, 0x52, + 0xff, 0x0e, 0xfa, 0xe0, 0x1d, 0x14, 0x03, 0x21, 0xc2, 0x8d, 0xd0, 0xb6, 0x7b, 0x06, 0x98, 0x90, 0xf6, 0x13, + 0x0f, 0x82, 0x46, 0xab, 0x85, 0x44, 0x71, 0x75, 0x32, 0xd3, 0xa5, 0xf6, 0x36, 0x39, 0xa9, 0x9d, 0x7f, 0x8e, + 0x98, 0x31, 0xc6, 0x48, 0x51, 0xb7, 0xef, 0x68, 0x93, 0xb3, 0xc9, 0x74, 0x0f, 0x98, 0x44, 0xd1, 0x8a, 0x61, + 0x3b, 0x5f, 0x9a, 0x6a, 0xb4, 0xbd, 0x6e, 0x6a, 0x93, 0xe8, 0xe4, 0xbe, 0xa5, 0x57, 0x5d, 0x2c, 0xb4, 0x33, + 0x0c, 0x0a, 0xf8, 0x55, 0x83, 0x19, 0xa9, 0x09, 0xa5, 0x98, 0x8a, 0x99, 0x2e, 0x40, 0x63, 0x43, 0xdd, 0x1c, + 0x74, 0x2d, 0x64, 0xcd, 0x4a, 0x17, 0xa2, 0xf3, 0x79, 0x5e, 0x8d, 0xb4, 0xd3, 0x0c, 0xcd, 0xf4, 0x41, 0x56, + 0x55, 0xed, 0xa7, 0xb4, 0x37, 0xe3, 0x39, 0x73, 0x23, 0x89, 0x6b, 0x11, 0xb1, 0xbe, 0xd7, 0x2d, 0x63, 0xe3, + 0x10, 0xaa, 0x49, 0x67, 0x1d, 0x85, 0x53, 0x4f, 0x6d, 0xbc, 0x18, 0x1f, 0xeb, 0xb5, 0xbd, 0xc0, 0x8a, 0xc0, + 0xd1, 0x23, 0x82, 0x9d, 0x10, 0x8c, 0xd2, 0x69, 0xf3, 0xb0, 0xa3, 0x96, 0xf4, 0x24, 0x1e, 0x7d, 0xda, 0x72, + 0xf5, 0x48, 0x62, 0xbe, 0xde, 0xf0, 0x1c, 0x12, 0xe3, 0xc6, 0xcf, 0xdf, 0x75, 0xf6, 0x76, 0xc2, 0xdd, 0xef, + 0x91, 0xaf, 0x7f, 0x8a, 0x8a, 0x76, 0x9c, 0x25, 0xe1, 0x77, 0xcd, 0x43, 0x0b, 0xed, 0xe7, 0x4b, 0x57, 0x69, + 0x05, 0x19, 0xa9, 0x8d, 0xb1, 0xfb, 0x5c, 0x36, 0x12, 0x80, 0xf7, 0x54, 0x0a, 0xc8, 0x27, 0xa9, 0x1b, 0x2d, + 0x08, 0x75, 0x2d, 0xec, 0xfb, 0x71, 0x56, 0xfc, 0xdb, 0x61, 0x75, 0x78, 0xb0, 0x53, 0xee, 0xe4, 0x1f, 0x66, + 0xa6, 0x0e, 0x04, 0x5c, 0x3a, 0x56, 0x9f, 0x3f, 0x7e, 0xdb, 0x76, 0x31, 0x68, 0x2f, 0xde, 0x9e, 0xf9, 0x1e, + 0xa8, 0x81, 0x1f, 0xc2, 0xc7, 0x8f, 0x64, 0x6a, 0xf6, 0xb4, 0x71, 0x0e, 0xdb, 0xb8, 0xbf, 0x23, 0x28, 0xbd, + 0x32, 0x73, 0xa2, 0xcb, 0x72, 0xff, 0xcc, 0xa7, 0xc2, 0x17, 0xb8, 0x27, 0x19, 0x2d, 0xd2, 0xea, 0x92, 0x9e, + 0x97, 0x6d, 0x13, 0x1c, 0x9d, 0x20, 0x2e, 0xc5, 0x06, 0xa3, 0x5d, 0x93, 0xab, 0x21, 0x6f, 0x64, 0xbd, 0x73, + 0xfe, 0x5d, 0x8a, 0xba, 0xe4, 0x57, 0x1f, 0x85, 0xbe, 0xb8, 0x4a, 0x7f, 0x93, 0xa3, 0xde, 0x37, 0xa4, 0x51, + 0xf3, 0x08, 0xf7, 0xde, 0x6c, 0xcd, 0x1a, 0x6e, 0xef, 0xef, 0x24, 0x69, 0x9f, 0x21, 0x58, 0xd1, 0x26, 0x1f, + 0xe2, 0x51, 0x82, 0xb5, 0x02, 0xda, 0x3e, 0x74, 0x61, 0x1a, 0x61, 0x16, 0xfc, 0x30, 0x64, 0xfa, 0x72, 0x3c, + 0x5a, 0x81, 0xad, 0xc0, 0xa3, 0x2f, 0x1e, 0xd6, 0x29, 0x91, 0x57, 0xd1, 0xc1, 0x1c, 0x0a, 0xd9, 0x90, 0x41, + 0x89, 0x46, 0x96, 0x30, 0x1d, 0x5b, 0x3f, 0x1b, 0xf4, 0x32, 0x05, 0xd7, 0xdc, 0xcf, 0xa6, 0x8b, 0xbb, 0x4a, + 0x1f, 0x5e, 0x24, 0x2b, 0x3e, 0x69, 0x0b, 0xfc, 0x97, 0xb9, 0x43, 0x66, 0xa3, 0x43, 0xf5, 0xdd, 0x16, 0xdf, + 0x67, 0xb2, 0xed, 0x2b, 0xe2, 0x1c, 0x74, 0x71, 0x18, 0x87, 0x2b, 0x46, 0x2e, 0xe2, 0x0c, 0x77, 0x8c, 0xed, + 0x85, 0x6f, 0xa9, 0x80, 0x40, 0x3f, 0xb2, 0x4b, 0x78, 0x61, 0x37, 0xd0, 0xef, 0x02, 0x78, 0x53, 0x9b, 0x00, + 0xce, 0x6e, 0x23, 0xc0, 0x7e, 0xf2, 0xa0, 0x7c, 0xb2, 0x4c, 0x51, 0xc5, 0xb4, 0x85, 0xe4, 0x54, 0xed, 0xf6, + 0x61, 0xdb, 0x4b, 0x93, 0x1a, 0xb8, 0xcb, 0x49, 0x4e, 0xb3, 0x94, 0xfd, 0x13, 0xc1, 0xb3, 0x20, 0x85, 0xf2, + 0x7b, 0x20, 0x4a, 0x4b, 0x87, 0xee, 0x6c, 0x80, 0x63, 0x45, 0xd7, 0x58, 0x4c, 0xb1, 0x61, 0x00, 0x6a, 0xd9, + 0x84, 0x8a, 0x24, 0xa2, 0x2a, 0x57, 0x71, 0xe3, 0xa2, 0xab, 0x65, 0x46, 0x3f, 0x55, 0x3d, 0x52, 0xcd, 0x53, + 0x5e, 0xf1, 0x0b, 0xdd, 0x40, 0xd8, 0x87, 0x73, 0x72, 0xa5, 0x32, 0xe3, 0x73, 0x1b, 0x0e, 0xe9, 0x0c, 0x04, + 0xe8, 0xe4, 0x37, 0x47, 0xcc, 0x3e, 0xb9, 0x6b, 0xb8, 0x79, 0xbd, 0x94, 0xd7, 0x01, 0x2a, 0xf4, 0x6a, 0x93, + 0xba, 0x17, 0x70, 0x37, 0xf0, 0x62, 0x74, 0x4d, 0x3f, 0xdf, 0xcc, 0xd3, 0x6a, 0xab, 0xe0, 0xf8, 0xcc, 0xca, + 0x19, 0xdc, 0xf7, 0x84, 0x1b, 0x1e, 0xe2, 0xf4, 0xfe, 0xb1, 0x80, 0x0e, 0x75, 0x44, 0x1c, 0x51, 0xe9, 0x5c, + 0xce, 0x94, 0xce, 0xee, 0xcd, 0x85, 0x87, 0xfb, 0xf5, 0x74, 0x30, 0x8d, 0xd7, 0x63, 0x63, 0x1b, 0x73, 0x35, + 0x78, 0x30, 0x91, 0xf4, 0xc8, 0xb3, 0xc8, 0xfb, 0x3c, 0xd9, 0x39, 0x70, 0xce, 0xf0, 0xed, 0xa4, 0xca, 0x08, + 0x44, 0x75, 0x68, 0x23, 0x9c, 0x02, 0xfe, 0x8f, 0x67, 0x5e, 0x15, 0xc4, 0x9b, 0x51, 0x21, 0xb1, 0x00, 0xcc, + 0x19, 0xfc, 0xc2, 0xb2, 0x91, 0x3d, 0xf7, 0x4f, 0x75, 0x8f, 0x70, 0xbd, 0x6e, 0xeb, 0x73, 0x39, 0x51, 0x6e, + 0x5f, 0x1e, 0xff, 0x97, 0x00, 0xf8, 0xee, 0x13, 0x0e, 0x5c, 0x84, 0xce, 0xd7, 0xb1, 0xce, 0xd6, 0x6b, 0xe9, + 0xa0, 0x55, 0x96, 0xbe, 0x8e, 0x55, 0xf6, 0xd9, 0xfd, 0xf7, 0xcf, 0x0f, 0xa6, 0x22, 0x90, 0xec, 0x67, 0x0b, + 0x6b, 0xdd, 0x67, 0x38, 0xbb, 0x5c, 0xfb, 0x34, 0x1e, 0xf5, 0xff, 0xb4, 0x2b, 0xc2, 0xab, 0xc5, 0x08, 0xff, + 0x23, 0x12, 0x48, 0xf2, 0xc2, 0xdc, 0x15, 0x77, 0x0d, 0x33, 0x72, 0x2b, 0x9c, 0x9d, 0xae, + ]; + let script_code = vec![0xac, 0x65]; + let input_index = 0; + let hash_type = 3; + let amount = 391892287957268; + let consensus_branch_id = 1991772603; + let expected_sighash = H256::from([ + 0x6a, 0x3b, 0x2b, 0xcc, 0x15, 0x57, 0x89, 0xa2, 0x74, 0x39, 0xaa, 0x27, 0x5c, 0xa9, 0x9e, 0xc6, 0x48, 0xdd, + 0xd5, 0x88, 0xe8, 0x2e, 0xfa, 0xe4, 0xac, 0x46, 0xba, 0x3f, 0xd0, 0xe3, 0xbb, 0xa0, + ]); + let tx: Transaction = deserialize(tx.as_slice()).unwrap(); + let mut signer = TransactionInputSigner::from(tx); + signer.inputs[0].amount = amount; + signer.consensus_branch_id = consensus_branch_id; + + let sig_hash = signer.signature_hash( + input_index, + amount, + &script_code.into(), + SignatureVersion::Base, + hash_type, + ); + + assert_eq!(expected_sighash, sig_hash); + } + + #[test] + fn test_sapling_sig_hash_none() { + let tx = vec![ + 0x04, 0x00, 0x00, 0x80, 0x85, 0x20, 0x2f, 0x89, 0x02, 0x0b, 0xbe, 0x32, 0xa5, 0x98, 0xc2, 0x2a, 0xdf, 0xb4, + 0x8c, 0xef, 0x72, 0xba, 0x5d, 0x42, 0x87, 0xc0, 0xce, 0xfb, 0xac, 0xfd, 0x8c, 0xe1, 0x95, 0xb4, 0x96, 0x3c, + 0x34, 0xa9, 0x4b, 0xba, 0x7a, 0x17, 0x5d, 0xae, 0x4b, 0x04, 0x65, 0xac, 0x65, 0x63, 0x53, 0x70, 0x89, 0x15, + 0x09, 0x0f, 0x47, 0xa0, 0x68, 0xe2, 0x27, 0x43, 0x3f, 0x9e, 0x49, 0xd3, 0xaa, 0x09, 0xe3, 0x56, 0xd8, 0xd6, + 0x6d, 0x0c, 0x01, 0x21, 0xe9, 0x1a, 0x3c, 0x4a, 0xa3, 0xf2, 0x7f, 0xa1, 0xb6, 0x33, 0x96, 0xe2, 0xb4, 0x1d, + 0x09, 0x00, 0x63, 0x53, 0x53, 0x00, 0xac, 0x53, 0xac, 0x51, 0x4e, 0x97, 0x05, 0x68, 0x02, 0xda, 0x07, 0x1b, + 0x97, 0x0d, 0x48, 0x07, 0x00, 0x01, 0x52, 0xa8, 0x44, 0x55, 0x0b, 0xdc, 0x20, 0x02, 0x00, 0x07, 0x52, 0x52, + 0x6a, 0x65, 0x52, 0x00, 0x52, 0xd7, 0x03, 0x43, 0x02, 0x01, 0x1b, 0x9a, 0x07, 0x66, 0x20, 0xed, 0xc0, 0x67, + 0xff, 0x02, 0x00, 0x00, 0x03, 0x53, 0xe3, 0xb8, 0xa7, 0x1f, 0xac, 0xe1, 0xc9, 0xf3, 0x77, 0x45, 0xed, 0x36, + 0x88, 0x35, 0x29, 0x30, 0x4b, 0xfd, 0x5a, 0x39, 0x0b, 0x37, 0xbc, 0x5a, 0x34, 0x45, 0x24, 0x1f, 0x03, 0xf6, + 0x4a, 0x81, 0x88, 0x20, 0xdf, 0xed, 0xdd, 0x75, 0x37, 0x51, 0x59, 0xfb, 0xd2, 0x1e, 0xca, 0x98, 0x72, 0x10, + 0x4f, 0x8d, 0x7b, 0x3c, 0x8c, 0x86, 0x97, 0x03, 0xa1, 0xe7, 0x84, 0x8a, 0x5c, 0x94, 0x1e, 0x45, 0xa9, 0xc7, + 0x94, 0x34, 0x46, 0xd0, 0xdc, 0x96, 0x27, 0xcb, 0x31, 0xf8, 0x0e, 0x7a, 0xa5, 0x96, 0xd4, 0x82, 0x1d, 0xc9, + 0x9a, 0x7d, 0x77, 0x7c, 0xd5, 0x7e, 0x19, 0x48, 0x42, 0xa0, 0x23, 0x47, 0x1f, 0x0f, 0x62, 0x88, 0xa1, 0x50, + 0x64, 0x7b, 0x2a, 0xfe, 0x9d, 0xf7, 0xcc, 0xcf, 0x01, 0xf5, 0xcd, 0xe5, 0xf0, 0x46, 0x80, 0xbb, 0xfe, 0xd8, + 0x7f, 0x6c, 0xf4, 0x29, 0xfb, 0x27, 0xad, 0x6b, 0xab, 0xe7, 0x91, 0x76, 0x66, 0x11, 0xcf, 0x5b, 0xc2, 0x0e, + 0x48, 0xbe, 0xf1, 0x19, 0x25, 0x9b, 0x9b, 0x8a, 0x0e, 0x39, 0xc3, 0xdf, 0x28, 0xcb, 0x95, 0x82, 0xea, 0x33, + 0x86, 0x01, 0xcd, 0xc4, 0x81, 0xb3, 0x2f, 0xb8, 0x2a, 0xde, 0xeb, 0xb3, 0xda, 0xde, 0x25, 0xd1, 0xa3, 0xdf, + 0x20, 0xc3, 0x7e, 0x71, 0x25, 0x06, 0xb5, 0xd9, 0x96, 0xc4, 0x9a, 0x9f, 0x0f, 0x30, 0xdd, 0xcb, 0x91, 0xfe, + 0x90, 0x04, 0xe1, 0xe8, 0x32, 0x94, 0xa6, 0xc9, 0x20, 0x3d, 0x94, 0xe8, 0xdc, 0x2c, 0xbb, 0x44, 0x9d, 0xe4, + 0x15, 0x50, 0x32, 0x60, 0x4e, 0x47, 0x99, 0x70, 0x16, 0xb3, 0x04, 0xfd, 0x43, 0x7d, 0x82, 0x35, 0x04, 0x5e, + 0x25, 0x5a, 0x19, 0xb7, 0x43, 0xa0, 0xa9, 0xf2, 0xe3, 0x36, 0xb4, 0x4c, 0xae, 0x30, 0x7b, 0xb3, 0x98, 0x7b, + 0xd3, 0xe4, 0xe7, 0x77, 0xfb, 0xb3, 0x4c, 0x0a, 0xb8, 0xcc, 0x3d, 0x67, 0x46, 0x6c, 0x0a, 0x88, 0xdd, 0x4c, + 0xca, 0xd1, 0x8a, 0x07, 0xa8, 0xd1, 0x06, 0x8d, 0xf5, 0xb6, 0x29, 0xe5, 0x71, 0x8d, 0x0f, 0x6d, 0xf5, 0xc9, + 0x57, 0xcf, 0x71, 0xbb, 0x00, 0xa5, 0x17, 0x8f, 0x17, 0x5c, 0xac, 0xa9, 0x44, 0xe6, 0x35, 0xc5, 0x15, 0x9f, + 0x73, 0x8e, 0x24, 0x02, 0xa2, 0xd2, 0x1a, 0xa0, 0x81, 0xe1, 0x0e, 0x45, 0x6a, 0xfb, 0x00, 0xb9, 0xf6, 0x24, + 0x16, 0xc8, 0xb9, 0xc0, 0xf7, 0x22, 0x8f, 0x51, 0x07, 0x29, 0xe0, 0xbe, 0x3f, 0x30, 0x53, 0x13, 0xd7, 0x7f, + 0x73, 0x79, 0xdc, 0x2a, 0xf2, 0x48, 0x69, 0xc6, 0xc7, 0x4e, 0xe4, 0x47, 0x14, 0x98, 0x86, 0x1d, 0x19, 0x2f, + 0x0f, 0xf0, 0xf5, 0x08, 0x28, 0x5d, 0xab, 0x6b, 0x6a, 0x36, 0xcc, 0xf7, 0xd1, 0x22, 0x56, 0xcc, 0x76, 0xb9, + 0x55, 0x03, 0x72, 0x0a, 0xc6, 0x72, 0xd0, 0x82, 0x68, 0xd2, 0xcf, 0x77, 0x73, 0xb6, 0xba, 0x2a, 0x5f, 0x66, + 0x48, 0x47, 0xbf, 0x70, 0x7f, 0x2f, 0xc1, 0x0c, 0x98, 0xf2, 0xf0, 0x06, 0xec, 0x22, 0xcc, 0xb5, 0xa8, 0xc8, + 0xb7, 0xc4, 0x0c, 0x7c, 0x2d, 0x49, 0xa6, 0x63, 0x9b, 0x9f, 0x2c, 0xe3, 0x3c, 0x25, 0xc0, 0x4b, 0xc4, 0x61, + 0xe7, 0x44, 0xdf, 0xa5, 0x36, 0xb0, 0x0d, 0x94, 0xba, 0xdd, 0xf4, 0xf4, 0xd1, 0x40, 0x44, 0xc6, 0x95, 0xa3, + 0x38, 0x81, 0x47, 0x7d, 0xf1, 0x24, 0xf0, 0xfc, 0xf2, 0x06, 0xa9, 0xfb, 0x2e, 0x65, 0xe3, 0x04, 0xcd, 0xbf, + 0x0c, 0x4d, 0x23, 0x90, 0x17, 0x0c, 0x13, 0x0a, 0xb8, 0x49, 0xc2, 0xf2, 0x2b, 0x5c, 0xdd, 0x39, 0x21, 0x64, + 0x0c, 0x8c, 0xf1, 0x97, 0x6a, 0xe1, 0x01, 0x0b, 0x0d, 0xfd, 0x9c, 0xb2, 0x54, 0x3e, 0x45, 0xf9, 0x97, 0x49, + 0xcc, 0x4d, 0x61, 0xf2, 0xe8, 0xaa, 0xbf, 0xe9, 0x8b, 0xd9, 0x05, 0xfa, 0x39, 0x95, 0x1b, 0x33, 0xea, 0x76, + 0x9c, 0x45, 0xab, 0x95, 0x31, 0xc5, 0x72, 0x09, 0x86, 0x2a, 0xd1, 0x2f, 0xd7, 0x6b, 0xa4, 0x80, 0x7e, 0x65, + 0x41, 0x7b, 0x6c, 0xd1, 0x2f, 0xa8, 0xec, 0x91, 0x6f, 0x01, 0x3e, 0xbb, 0x87, 0x06, 0xa9, 0x6e, 0xff, 0xed, + 0xa0, 0x6c, 0x4b, 0xe2, 0x4b, 0x04, 0x84, 0x63, 0x92, 0xe9, 0xd1, 0xe6, 0x93, 0x0e, 0xae, 0x01, 0xfa, 0x21, + 0xfb, 0xd7, 0x00, 0x58, 0x3f, 0xb5, 0x98, 0xb9, 0x2c, 0x8f, 0x4e, 0xb8, 0xa6, 0x1a, 0xa6, 0x23, 0x5d, 0xb6, + 0x0f, 0x28, 0x41, 0xcf, 0x3a, 0x1c, 0x6a, 0xb5, 0x4c, 0x67, 0x06, 0x68, 0x44, 0x71, 0x1d, 0x09, 0x1e, 0xb9, + 0x31, 0xa1, 0xbd, 0x62, 0x81, 0xae, 0xdf, 0x2a, 0x0e, 0x8f, 0xab, 0x18, 0x81, 0x72, 0x02, 0xa9, 0xbe, 0x06, + 0x40, 0x2e, 0xd9, 0xcc, 0x72, 0x0c, 0x16, 0xbf, 0xe8, 0x81, 0xe4, 0xdf, 0x42, 0x55, 0xe8, 0x7a, 0xfb, 0x7f, + 0xc6, 0x2f, 0x38, 0x11, 0x6b, 0xbe, 0x03, 0xcd, 0x8a, 0x3c, 0xb1, 0x1a, 0x27, 0xd5, 0x68, 0x41, 0x47, 0x82, + 0xf4, 0x7b, 0x1a, 0x44, 0xc9, 0x7c, 0x68, 0x04, 0x67, 0x69, 0x4b, 0xc9, 0x70, 0x9d, 0x32, 0x91, 0x6c, 0x97, + 0xe8, 0x00, 0x6c, 0xbb, 0x07, 0xba, 0x0e, 0x41, 0x80, 0xa3, 0x73, 0x80, 0x38, 0xc3, 0x74, 0xc4, 0xcc, 0xe8, + 0xf3, 0x29, 0x59, 0xaf, 0xb2, 0x5f, 0x30, 0x3f, 0x58, 0x15, 0xc4, 0x53, 0x31, 0x24, 0xac, 0xf9, 0xd1, 0x89, + 0x40, 0xe7, 0x75, 0x22, 0xac, 0x5d, 0xc4, 0xb9, 0x57, 0x0a, 0xae, 0x8f, 0x47, 0xb7, 0xf5, 0x7f, 0xd8, 0x76, + 0x7b, 0xea, 0x1a, 0x24, 0xae, 0x7b, 0xed, 0x65, 0xb4, 0xaf, 0xdc, 0x8f, 0x12, 0x78, 0xc3, 0x0e, 0x2d, 0xb9, + 0x8f, 0xd1, 0x72, 0x73, 0x0a, 0xc6, 0xbb, 0xed, 0x4f, 0x11, 0x27, 0xcd, 0x32, 0xb0, 0x4a, 0x95, 0xb2, 0x05, + 0x52, 0x6c, 0xfc, 0xb4, 0xc4, 0xe1, 0xcc, 0x95, 0x51, 0x75, 0xb3, 0xe8, 0xde, 0x1f, 0x5d, 0x81, 0xb1, 0x86, + 0x69, 0x69, 0x23, 0x50, 0xaa, 0xa1, 0xa1, 0xd7, 0x97, 0x61, 0x75, 0x82, 0xe5, 0x4d, 0x7a, 0x5b, 0x57, 0xa6, + 0x83, 0xb3, 0x2f, 0xb1, 0x09, 0x80, 0x62, 0xda, 0xd7, 0xb0, 0xc2, 0xeb, 0x51, 0x8f, 0x68, 0x62, 0xe8, 0x3d, + 0xb2, 0x5e, 0x3d, 0xba, 0xf7, 0xae, 0xd5, 0x04, 0xde, 0x93, 0x2a, 0xcb, 0x99, 0xd7, 0x35, 0x99, 0x2c, 0xe6, + 0x2b, 0xae, 0x9e, 0xf8, 0x93, 0xff, 0x6a, 0xcc, 0x0f, 0xfc, 0xf8, 0xe3, 0x48, 0x3e, 0x14, 0x6b, 0x9d, 0x49, + 0xdd, 0x8c, 0x78, 0x35, 0xf4, 0x3a, 0x37, 0xdc, 0xa0, 0x78, 0x7e, 0x3e, 0xc9, 0xf6, 0x60, 0x52, 0x23, 0xd5, + 0xba, 0x7a, 0xe0, 0xab, 0x90, 0x25, 0xb7, 0x3b, 0xc0, 0x3f, 0x7f, 0xac, 0x36, 0xc0, 0x09, 0xa5, 0x6d, 0x4d, + 0x95, 0xd1, 0xe8, 0x1d, 0x3b, 0x3e, 0xbc, 0xa7, 0xe5, 0x4c, 0xc1, 0xa1, 0x2d, 0x12, 0x7b, 0x57, 0xc8, 0x13, + 0x89, 0x76, 0xe7, 0x91, 0x01, 0x3b, 0x01, 0x5f, 0x06, 0xa6, 0x24, 0xf5, 0x21, 0xb6, 0xee, 0x04, 0xec, 0x98, + 0x08, 0x93, 0xc7, 0xe5, 0xe0, 0x1a, 0x33, 0x62, 0x03, 0x59, 0x40, 0x94, 0xf8, 0x28, 0x33, 0xd7, 0x44, 0x27, + 0x88, 0x00, 0x84, 0xd3, 0x58, 0x63, 0xc8, 0xe7, 0xeb, 0xb5, 0xc9, 0xee, 0xd9, 0x8e, 0x72, 0x57, 0x2e, 0xc4, + 0x0c, 0x79, 0xb2, 0x66, 0x23, 0xb5, 0x80, 0x22, 0xf4, 0x89, 0xb0, 0x89, 0x3d, 0x88, 0xbe, 0x63, 0xf3, 0xf8, + 0xc0, 0xd2, 0x32, 0x49, 0xeb, 0xcd, 0xe1, 0x3d, 0xb9, 0x31, 0x29, 0x41, 0xc3, 0x6c, 0x1d, 0x1c, 0xbc, 0xab, + 0xac, 0x0c, 0x78, 0xcb, 0x3b, 0x19, 0x12, 0xdb, 0x0d, 0xcb, 0xfe, 0x18, 0x93, 0xd9, 0xb5, 0x1b, 0xe4, 0xaf, + 0x1d, 0x00, 0x0b, 0xac, 0x1a, 0xd0, 0xa3, 0xae, 0x2c, 0xe1, 0xe7, 0x32, 0x25, 0xfb, 0x11, 0x4d, 0x05, 0xaf, + 0x4c, 0xef, 0xc0, 0x6e, 0x87, 0x5f, 0x07, 0x4f, 0xfe, 0xae, 0x0c, 0xba, 0x7d, 0xa3, 0xa5, 0x16, 0xc1, 0x73, + 0xbe, 0x1c, 0x51, 0x33, 0x23, 0xe1, 0x19, 0xf6, 0x35, 0xe8, 0x20, 0x9a, 0x07, 0x4b, 0x21, 0x6b, 0x70, 0x23, + 0xfa, 0xdc, 0x2d, 0x25, 0x94, 0x9c, 0x90, 0x03, 0x7e, 0x71, 0xe3, 0xe5, 0x50, 0x72, 0x6d, 0x21, 0x0a, 0x2c, + 0x68, 0x83, 0x42, 0xe5, 0x24, 0x40, 0x63, 0x5e, 0x9c, 0xc1, 0x4a, 0xfe, 0x10, 0x10, 0x26, 0x21, 0xa9, 0xc9, + 0xac, 0xcb, 0x78, 0x2e, 0x9e, 0x4a, 0x5f, 0xa8, 0x7f, 0x0a, 0x95, 0x6f, 0x5b, 0x85, 0x50, 0x99, 0x60, 0x28, + 0x5c, 0x22, 0x62, 0x7c, 0x59, 0x48, 0x3a, 0x5a, 0x4c, 0x28, 0xcc, 0xe4, 0xb1, 0x56, 0xe5, 0x51, 0x40, 0x6a, + 0x7e, 0xe8, 0x35, 0x56, 0x56, 0xa2, 0x1e, 0x43, 0xe3, 0x8c, 0xe1, 0x29, 0xfd, 0xad, 0xb7, 0x59, 0xed, 0xdf, + 0xa0, 0x8f, 0x00, 0xfc, 0x8e, 0x56, 0x7c, 0xef, 0x93, 0xc6, 0x79, 0x2d, 0x01, 0xdf, 0x05, 0xe6, 0xd5, 0x80, + 0xf4, 0xd5, 0xd4, 0x8d, 0xf0, 0x42, 0x45, 0x1a, 0x33, 0x59, 0x0d, 0x3e, 0x8c, 0xf4, 0x9b, 0x26, 0x27, 0x21, + 0x8f, 0x0c, 0x29, 0x2f, 0xa6, 0x6a, 0xda, 0x94, 0x5f, 0xa5, 0x5b, 0xb2, 0x35, 0x48, 0xe3, 0x3a, 0x83, 0xa5, + 0x62, 0x95, 0x7a, 0x31, 0x49, 0xa9, 0x93, 0xcc, 0x47, 0x23, 0x62, 0x29, 0x87, 0x36, 0xa8, 0xb7, 0x78, 0xd9, + 0x7c, 0xe4, 0x23, 0x01, 0x3d, 0x64, 0xb3, 0x2c, 0xd1, 0x72, 0xef, 0xa5, 0x51, 0xbf, 0x7f, 0x36, 0x8f, 0x04, + 0xbd, 0xae, 0xc6, 0x09, 0x1a, 0x30, 0x04, 0xa7, 0x57, 0x59, 0x8b, 0x80, 0x1d, 0xcf, 0x67, 0x5c, 0xb8, 0x3e, + 0x43, 0xa5, 0x3a, 0xe8, 0xb2, 0x54, 0xd3, 0x33, 0xbc, 0xda, 0x20, 0xd4, 0x81, 0x7d, 0x34, 0x77, 0xab, 0xfb, + 0xa2, 0x5b, 0xb8, 0x3d, 0xf5, 0x94, 0x9c, 0x12, 0x6f, 0x14, 0x9b, 0x1d, 0x99, 0x34, 0x1e, 0x4e, 0x6f, 0x91, + 0x20, 0xf4, 0xd4, 0x1e, 0x62, 0x91, 0x85, 0x00, 0x2c, 0x72, 0xc0, 0x12, 0xc4, 0x14, 0xd2, 0x38, 0x2a, 0x6d, + 0x47, 0xc7, 0xb3, 0xde, 0xab, 0xa7, 0x70, 0xc4, 0x00, 0xca, 0x96, 0xb2, 0x81, 0x4f, 0x6b, 0x26, 0xc3, 0xef, + 0x17, 0x42, 0x9f, 0x1a, 0x98, 0xc8, 0x5d, 0x83, 0xdb, 0x20, 0xef, 0xad, 0x48, 0xbe, 0x89, 0x96, 0xfb, 0x1b, + 0xff, 0x59, 0x1e, 0xff, 0xf3, 0x60, 0xfe, 0x11, 0x99, 0x05, 0x6c, 0x56, 0xe5, 0xfe, 0xec, 0x61, 0xa7, 0xb8, + 0xb9, 0xf6, 0x99, 0xd6, 0x01, 0x2c, 0x28, 0x49, 0x23, 0x2f, 0x32, 0x9f, 0xef, 0x95, 0xc7, 0xaf, 0x37, 0x00, + 0x98, 0xff, 0xe4, 0x91, 0x8e, 0x0c, 0xa1, 0xdf, 0x47, 0xf2, 0x75, 0x86, 0x7b, 0x73, 0x9e, 0x0a, 0x51, 0x4d, + 0x32, 0x09, 0x32, 0x5e, 0x21, 0x70, 0x45, 0x92, 0x7b, 0x47, 0x9c, 0x1c, 0xe2, 0xe5, 0xd5, 0x4f, 0x25, 0x48, + 0x8c, 0xad, 0x15, 0x13, 0xe3, 0xf4, 0x4a, 0x21, 0x26, 0x6c, 0xfd, 0x84, 0x16, 0x33, 0x32, 0x7d, 0xee, 0x6c, + 0xf8, 0x10, 0xfb, 0xf7, 0x39, 0x3e, 0x31, 0x7d, 0x9e, 0x53, 0xd1, 0xbe, 0x1d, 0x5a, 0xe7, 0x83, 0x9b, 0x66, + 0xb9, 0x43, 0xb9, 0xed, 0x18, 0xf2, 0xc5, 0x30, 0xe9, 0x75, 0x42, 0x23, 0x32, 0xc3, 0x43, 0x9c, 0xce, 0x49, + 0xa2, 0x9f, 0x2a, 0x33, 0x6a, 0x48, 0x51, 0x26, 0x3c, 0x5e, 0x9b, 0xd1, 0x3d, 0x73, 0x11, 0x09, 0xe8, 0x44, + 0xb7, 0xf8, 0xc3, 0x92, 0xa5, 0xc1, 0xdc, 0xaa, 0x2a, 0xe5, 0xf5, 0x0f, 0xf6, 0x3f, 0xab, 0x97, 0x65, 0xe0, + 0x16, 0x70, 0x2c, 0x35, 0xa6, 0x7c, 0xd7, 0x36, 0x4d, 0x3f, 0xab, 0x55, 0x2f, 0xb3, 0x49, 0xe3, 0x5c, 0x15, + 0xc5, 0x02, 0x50, 0x45, 0x3f, 0xd1, 0x8f, 0x7b, 0x85, 0x59, 0x92, 0x63, 0x2e, 0x2c, 0x76, 0xc0, 0xfb, 0xf1, + 0xef, 0x96, 0x3e, 0xa8, 0x0e, 0x32, 0x23, 0xde, 0x32, 0x77, 0xbc, 0x55, 0x92, 0x51, 0x72, 0x58, 0x29, 0xec, + 0x03, 0xf2, 0x13, 0xba, 0x89, 0x55, 0xca, 0xb2, 0x82, 0x2f, 0xf2, 0x1a, 0x9b, 0x0a, 0x49, 0x04, 0xd6, 0x68, + 0xfc, 0xd7, 0x72, 0x24, 0xbd, 0xe3, 0xdd, 0x01, 0xf6, 0xff, 0xc4, 0x82, 0x8f, 0x6b, 0x64, 0x23, 0x0b, 0x35, + 0xc6, 0xa0, 0x49, 0x87, 0x34, 0x94, 0x27, 0x6e, 0xa1, 0xd7, 0xed, 0x5e, 0x92, 0xcb, 0x4f, 0x90, 0xba, 0x83, + 0xa9, 0xe4, 0x96, 0x01, 0xb1, 0x94, 0x04, 0x2f, 0x29, 0x00, 0xd9, 0x9d, 0x31, 0x2d, 0x7b, 0x70, 0x50, 0x8c, + 0xf1, 0x76, 0x06, 0x6d, 0x15, 0x4d, 0xbe, 0x96, 0xef, 0x9d, 0x43, 0x67, 0xe4, 0xc8, 0x40, 0xe4, 0xa1, 0x7b, + 0x5e, 0x51, 0x22, 0xe8, 0xeb, 0xe2, 0x15, 0x8a, 0x3c, 0x5f, 0x4c, 0xba, 0xe2, 0x1e, 0xa3, 0xfa, 0x1a, 0xe6, + 0xc2, 0x5a, 0x94, 0x62, 0xeb, 0xcb, 0xb0, 0xfd, 0x5f, 0x14, 0x55, 0x4b, 0xc9, 0x77, 0x47, 0xc3, 0x3e, 0x34, + 0xda, 0x90, 0xc8, 0x16, 0xd8, 0xd0, 0xd5, 0x0b, 0xfe, 0x37, 0x61, 0x8c, 0x58, 0x12, 0x89, 0x14, 0x84, 0xfa, + 0x25, 0x93, 0x22, 0xc1, 0x50, 0x92, 0xd4, 0x15, 0x5d, 0x86, 0x96, 0xd6, 0xf1, 0x2f, 0x24, 0xfd, 0x36, 0x44, + 0x96, 0xb3, 0xbe, 0x08, 0x71, 0xca, 0x3d, 0xd9, 0x62, 0x53, 0x48, 0xa6, 0x14, 0xb5, 0x9b, 0xde, 0x45, 0x88, + 0x56, 0x49, 0xba, 0xe3, 0x6d, 0xe3, 0x4d, 0xef, 0x8f, 0xce, 0xc8, 0x53, 0x43, 0x47, 0x5d, 0x97, 0x6a, 0xe1, + 0xe9, 0xb2, 0x78, 0x29, 0xce, 0x2a, 0xc5, 0xef, 0xd0, 0xb3, 0x99, 0xa8, 0xb4, 0x48, 0xbe, 0x65, 0x04, 0x29, + 0x4e, 0xe6, 0xb3, 0xc1, 0xc6, 0xa5, 0x34, 0x2d, 0x7c, 0x01, 0xae, 0x9d, 0x8a, 0xd3, 0x07, 0x0c, 0x2b, 0x1a, + 0x91, 0x57, 0x3a, 0xf5, 0xe0, 0xc5, 0xe4, 0xcb, 0xbf, 0x4a, 0xcd, 0xc6, 0xb5, 0x4c, 0x92, 0x72, 0x20, 0x0d, + 0x99, 0x70, 0x25, 0x0c, 0x17, 0xc1, 0x03, 0x6f, 0x06, 0x08, 0x5c, 0x41, 0x85, 0x8e, 0xd3, 0xa0, 0xc4, 0x81, + 0x50, 0xbc, 0x69, 0x7e, 0x4a, 0x69, 0x5f, 0xef, 0x33, 0x5f, 0x7a, 0xd0, 0x7e, 0x1a, 0x46, 0xdc, 0x76, 0x7f, + 0xf8, 0x22, 0xdb, 0x70, 0xe6, 0x66, 0x90, 0x80, 0xb9, 0x81, 0x6b, 0x22, 0x32, 0xc8, 0x1a, 0x4c, 0x66, 0xcc, + 0x58, 0x6a, 0xbf, 0xe1, 0xea, 0xa8, 0xca, 0x6c, 0xf4, 0x1f, 0xc3, 0x0e, 0xb8, 0xdc, 0x57, 0xc3, 0x7a, 0x3c, + 0x39, 0xc5, 0x9c, 0x94, 0x23, 0x2d, 0xf9, 0xd3, 0x88, 0xdb, 0xfa, 0x35, 0xc2, 0xcd, 0x5c, 0x75, 0xf3, 0x28, + 0xe9, 0xfe, 0xa7, 0x8f, 0x65, 0x56, 0x8f, 0x2b, 0xb9, 0x34, 0xc8, 0x2c, 0x41, 0x42, 0xda, 0x69, 0xd1, 0x2c, + 0xa7, 0xde, 0x9a, 0x7d, 0xf7, 0x06, 0x40, 0x0e, 0xc7, 0x98, 0x78, 0xd8, 0x68, 0xe1, 0x7e, 0x8f, 0x71, 0xea, + 0x31, 0x49, 0x5a, 0x8b, 0xae, 0x7b, 0xdc, 0x2e, 0x48, 0xb5, 0x11, 0x87, 0x71, 0xc2, 0xfc, 0xa0, 0x78, 0xcc, + 0xa1, 0xfc, 0xe0, 0xd7, 0xef, 0x0a, 0xf3, 0x47, 0x8c, 0xf3, 0x6f, 0x69, 0xe8, 0x5a, 0x41, 0xdd, 0x29, 0xb4, + 0x29, 0x4a, 0x65, 0xd3, 0xe0, 0x55, 0xff, 0x71, 0x8d, 0xd9, 0xdc, 0x8c, 0x75, 0xe7, 0xe5, 0xb2, 0xef, 0xe4, + 0x42, 0x63, 0x73, 0x71, 0xb7, 0xc4, 0x8f, 0x6e, 0xe9, 0x9e, 0x3e, 0xa3, 0x8a, 0x4b, 0x0f, 0x2f, 0x67, 0xfc, + 0x2b, 0x90, 0x8c, 0xda, 0x65, 0x7e, 0xae, 0x75, 0x4e, 0x03, 0x7e, 0x26, 0x2e, 0x9a, 0x9f, 0x9b, 0xd7, 0xec, + 0x42, 0x67, 0xed, 0x8e, 0x96, 0x93, 0x0e, 0x10, 0x84, 0x78, 0x3c, 0x37, 0xd6, 0xf9, 0xdd, 0x15, 0xfd, 0x29, + 0xf4, 0xcc, 0x47, 0x7e, 0x66, 0xf1, 0x30, 0xd6, 0x30, 0x43, 0x0d, 0xcc, 0x01, 0x04, 0x89, 0x9b, 0x4f, 0x9f, + 0x46, 0xeb, 0x09, 0x0e, 0xf7, 0xfc, 0x90, 0xb4, 0x79, 0xab, 0xf6, 0x1f, 0x93, 0x95, 0x5e, 0xe0, 0x0e, 0x6a, + 0x18, 0x48, 0xf1, 0xab, 0x14, 0xad, 0x33, 0x4f, 0x2b, 0x68, 0x03, 0x58, 0x08, 0xcd, 0xf1, 0xbb, 0x9e, 0x9d, + 0x9a, 0x81, 0x6b, 0xaf, 0x72, 0x8a, 0x95, 0x5b, 0x96, 0x0b, 0x77, 0x01, 0xfa, 0x62, 0x66, 0x87, 0xdc, 0x3c, + 0x9c, 0xba, 0x64, 0x63, 0x37, 0xb5, 0x3e, 0x29, 0x81, 0x6e, 0x94, 0x82, 0xdd, 0xf5, 0x57, 0x8a, 0x87, 0x68, + 0xaa, 0xe4, 0x77, 0xfc, 0xe4, 0x10, 0xac, 0x2d, 0x5d, 0xe6, 0x09, 0x58, 0x61, 0xc1, 0x11, 0xd7, 0xfe, 0xb3, + 0xe6, 0xbb, 0x4f, 0xbb, 0x5a, 0x54, 0x95, 0x54, 0x95, 0x97, 0x27, 0x98, 0x35, 0x0a, 0x25, 0x3f, 0x05, 0xf6, + 0x6c, 0x2e, 0xcf, 0xcb, 0xc0, 0xed, 0x43, 0xf5, 0xec, 0x2e, 0x6d, 0x8d, 0xba, 0x15, 0xa5, 0x12, 0x54, 0xd9, + 0x7b, 0x18, 0x21, 0x10, 0x7c, 0x07, 0xdd, 0x9a, 0x16, 0xef, 0x84, 0x06, 0xf9, 0x43, 0xe2, 0x82, 0xb9, 0x5d, + 0x4b, 0x36, 0x25, 0x30, 0xc9, 0x13, 0xd6, 0xba, 0x42, 0x1d, 0xf6, 0x02, 0x7d, 0xe5, 0xaf, 0x1e, 0x47, 0x45, + 0xd5, 0x86, 0x81, 0x06, 0x95, 0x4b, 0xe6, 0xc1, 0x96, 0x27, 0x80, 0xa2, 0x94, 0x10, 0x72, 0xe9, 0x51, 0x31, + 0xb1, 0x67, 0x9d, 0xf0, 0x63, 0x76, 0x25, 0x04, 0x2c, 0x37, 0xd4, 0x8f, 0xfb, 0x15, 0x2e, 0x5e, 0xbc, 0x18, + 0x5c, 0x8a, 0x2b, 0x7d, 0x43, 0x85, 0xf1, 0xc9, 0x5a, 0xf9, 0x37, 0xdf, 0x78, 0xdf, 0xd8, 0x75, 0x7f, 0xab, + 0x43, 0x49, 0x68, 0xb0, 0xb5, 0x7c, 0x66, 0x57, 0x44, 0x68, 0xf1, 0x60, 0xb4, 0x47, 0xac, 0x82, 0x21, 0xe5, + 0x06, 0x06, 0x76, 0xa8, 0x42, 0xa1, 0xc6, 0xb7, 0x17, 0x2d, 0xd3, 0x34, 0x0f, 0x76, 0x40, 0x70, 0xab, 0x1f, + 0xe0, 0x91, 0xc5, 0xc7, 0x4c, 0x95, 0xa5, 0xdc, 0x04, 0x33, 0x90, 0x72, 0x3a, 0x4c, 0x12, 0x7d, 0xa1, 0x4c, + 0xdd, 0xe1, 0xdc, 0x26, 0x75, 0xa6, 0x23, 0x40, 0xb3, 0xe6, 0xaf, 0xd0, 0x52, 0x2a, 0x31, 0xde, 0x26, 0xe7, + 0xd1, 0xec, 0x3a, 0x9c, 0x8a, 0x09, 0x1f, 0xfd, 0xc7, 0x5b, 0x7e, 0xcf, 0xdc, 0x7c, 0x12, 0x99, 0x5a, 0x5e, + 0x37, 0xce, 0x34, 0x88, 0xbd, 0x29, 0xf8, 0x62, 0x9d, 0x68, 0xf6, 0x96, 0x49, 0x24, 0x48, 0xdd, 0x52, 0x66, + 0x97, 0x47, 0x6d, 0xc0, 0x61, 0x34, 0x6e, 0xbe, 0x3f, 0x67, 0x72, 0x17, 0xff, 0x9c, 0x60, 0xef, 0xce, 0x94, + 0x3a, 0xf2, 0x8d, 0xfd, 0x3f, 0x9e, 0x59, 0x69, 0x25, 0x98, 0xa6, 0x04, 0x7c, 0x23, 0xc4, 0xc0, 0x14, 0x00, + 0xf1, 0xab, 0x57, 0x30, 0xea, 0xc0, 0xae, 0x8d, 0x58, 0x43, 0xd5, 0x05, 0x1c, 0x37, 0x62, 0x40, 0x17, 0x2a, + 0xf2, 0x18, 0xd7, 0xa1, 0xec, 0xfe, 0x65, 0xb4, 0xf7, 0x51, 0x00, 0x63, 0x89, 0x83, 0xc1, 0x4d, 0xe4, 0x97, + 0x47, 0x55, 0xda, 0xde, 0x80, 0x18, 0xc9, 0xb8, 0xf4, 0x54, 0x3f, 0xb0, 0x95, 0x96, 0x15, 0x13, 0xe6, 0x7c, + 0x61, 0xdb, 0xc5, 0x9c, 0x60, 0x7f, 0x9b, 0x51, 0xf8, 0xd0, 0x9b, 0xdc, 0xad, 0x28, 0xbc, 0xfb, 0x9e, 0x5d, + 0x27, 0x44, 0xea, 0x88, 0x48, 0xb2, 0x62, 0x3a, 0xc0, 0x7f, 0x8e, 0xf6, 0x1a, 0x81, 0xa3, 0x59, 0x10, 0xb8, + 0xa1, 0xba, 0xf3, 0x9a, 0x91, 0x9a, 0x7b, 0x60, 0xbc, 0x60, 0x4d, 0x63, 0x18, 0x5f, 0x75, 0x92, 0x21, 0xd8, + 0x47, 0xcc, 0x54, 0xa2, 0x27, 0x65, 0xa4, 0xc3, 0x34, 0x75, 0xb5, 0x79, 0x1e, 0x9a, 0xf3, 0x27, 0x1f, 0xc8, + 0xd9, 0x35, 0x06, 0x67, 0x09, 0x0d, 0x81, 0x84, 0xec, 0x50, 0x52, 0x2d, 0x80, 0x4f, 0x23, 0xc4, 0xfb, 0x44, + 0xff, 0xa4, 0x81, 0xbc, 0x92, 0xae, 0x40, 0x8d, 0x1b, 0x9f, 0x2b, 0x13, 0x19, 0x04, 0xf9, 0x70, 0x5c, 0x59, + 0xe2, 0xf4, 0xbd, 0xe7, 0xa3, 0xb2, 0xc0, 0x85, 0xd9, 0x3f, 0xd2, 0xab, 0xc5, 0xe1, 0x4d, 0x16, 0x30, 0x01, + 0xa1, 0x2f, 0x51, 0x93, 0x8d, 0x02, 0x1a, 0xfa, 0x92, 0x23, 0x9b, 0x87, 0x3d, 0xc6, 0xc3, 0x57, 0xea, 0xa8, + 0xaf, 0x4e, 0xe6, 0xd0, 0x05, 0x40, 0x65, 0x7f, 0xe3, 0x29, 0x14, 0x10, 0x3b, 0x5d, 0x98, 0xf6, 0x8b, 0xd3, + 0xe2, 0xb5, 0x35, 0x9f, 0x08, 0xcc, 0xd8, 0x8d, 0x0c, 0x81, 0x1e, 0x4c, 0x31, 0xfb, 0xb4, 0x9f, 0x3a, 0x90, + 0xbb, 0xd0, 0x5d, 0xce, 0x62, 0xf3, 0x44, 0xe7, 0x07, 0x75, 0x93, 0x15, 0x9a, 0xe3, 0x50, 0x50, 0xb0, 0x4c, + 0x9e, 0x6b, 0x86, 0xbc, 0x43, 0x2d, 0xc8, 0xb0, 0x48, 0xc7, 0x3c, 0x00, 0x18, 0xca, 0x5b, 0x69, 0x41, 0x12, + 0x97, 0x73, 0x2a, 0x4e, 0x1a, 0xa9, 0x9a, 0x92, 0x8c, 0x71, 0xe7, 0xa2, 0x4f, 0xd2, 0x77, 0x85, 0x6a, 0xa4, + 0x25, 0x01, 0xe5, 0x1b, 0x01, 0x2a, 0xea, 0x94, 0x46, 0xa2, 0x10, 0x4e, 0x93, 0xf8, 0x15, 0xa0, 0xb3, 0xa2, + 0x9b, 0x45, 0x83, 0x14, 0xf3, 0xd8, 0xbe, 0x2b, 0x98, 0x23, 0xd3, 0x42, 0xf4, 0x62, 0x13, 0xe9, 0x42, 0xa7, + 0xe1, 0x9a, 0x46, 0xe9, 0x70, 0xb5, 0xc5, 0x06, 0x70, 0x84, 0x30, 0x31, 0x7b, 0x1b, 0xb3, 0xb3, 0x5d, 0xf6, + 0x8a, 0xe3, 0x3a, 0x49, 0x26, 0xa0, 0x3e, 0x6b, 0xfe, 0xb5, 0x51, 0x04, 0x16, 0xfc, 0xbb, 0x05, 0x24, 0xc9, + 0xca, 0x50, 0x74, 0x15, 0x6c, 0xc5, 0xa5, 0xd6, 0xfe, 0x1c, 0x99, 0x5e, 0xdc, 0x60, 0xa2, 0xf5, 0x50, 0x41, + 0x1a, 0xa4, 0x1e, 0x3d, 0xa3, 0xbd, 0xcf, 0x64, 0xbc, 0xf0, 0x4a, 0x05, 0x10, 0x57, 0x1b, 0x93, 0x6d, 0x47, + 0xe5, 0x5c, 0xec, 0x03, 0x30, 0x00, 0x8d, 0xfe, 0x73, 0x56, 0x34, 0x04, 0xf0, 0x47, 0xd7, 0xf3, 0xa8, 0xa3, + 0xd7, 0x74, 0x3b, 0xc5, 0x54, 0x95, 0x52, 0x10, 0xf1, 0xeb, 0x0d, 0x08, 0x59, 0x9e, 0xa7, 0x7d, 0x5f, 0x97, + 0x4d, 0x87, 0x17, 0x6d, 0x37, 0xd9, 0x8b, 0x9c, 0x0a, 0xd4, 0x40, 0x40, 0x72, 0x09, 0xed, 0x6a, 0x9f, 0x08, + 0x46, 0x4d, 0x56, 0x55, 0x93, 0xe1, 0xa6, 0x3b, 0x93, 0x85, 0x36, 0xb4, 0x92, 0x44, 0xe9, 0x7d, + ]; + let script_code = vec![]; + let input_index = 1; + let hash_type = 2; + let amount = 652655344020909; + let consensus_branch_id = 1991772603; + let expected_sighash = H256::from([ + 0xbb, 0xe6, 0xd8, 0x4f, 0x57, 0xc5, 0x6b, 0x29, 0xb9, 0x14, 0xc6, 0x94, 0xba, 0xac, 0xcb, 0x89, 0x12, 0x97, + 0xe9, 0x61, 0xde, 0x3e, 0xb4, 0x6c, 0x68, 0xe3, 0xc8, 0x9c, 0x47, 0xb1, 0xa1, 0xdb, + ]); + + let tx: Transaction = deserialize(tx.as_slice()).unwrap(); + let mut signer = TransactionInputSigner::from(tx); + signer.inputs[1].amount = amount; + signer.consensus_branch_id = consensus_branch_id; + + let sig_hash = signer.signature_hash( + input_index, + amount, + &script_code.into(), + SignatureVersion::Base, + hash_type, + ); + + assert_eq!(expected_sighash, sig_hash); + } } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index 3717b76606..d2bd527d2b 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -289,6 +289,9 @@ impl MmCtx { pub fn p2p_in_memory_port(&self) -> Option { self.conf["p2p_in_memory_port"].as_u64() } + /// Returns whether node is configured to use [Upgraded Trading Protocol](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) + pub fn use_trading_proto_v2(&self) -> bool { self.conf["use_trading_proto_v2"].as_bool().unwrap_or_default() } + /// Returns the cloneable `MmFutSpawner`. pub fn spawner(&self) -> MmFutSpawner { MmFutSpawner::new(&self.abortable_system) } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index f1258d1827..48976dda2b 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -64,10 +64,12 @@ mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"]} +mm2_state_machine = { path = "../mm2_state_machine" } num-traits = "0.2" parity-util-mem = "0.11" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } +prost = "0.10" rand = { version = "0.7", features = ["std", "small_rng"] } rand6 = { version = "0.6", package = "rand" } # TODO: Reduce the size of regex by disabling the features we don't use. @@ -122,4 +124,5 @@ testcontainers = { git = "https://github.com/KomodoPlatform/mm2-testcontainers-r [build-dependencies] chrono = "0.4" gstuff = { version = "0.7", features = ["nightly"] } +prost-build = { version = "0.10.4", default-features = false } regex = "1" diff --git a/mm2src/mm2_main/build.rs b/mm2src/mm2_main/build.rs new file mode 100644 index 0000000000..60a4b74c3d --- /dev/null +++ b/mm2src/mm2_main/build.rs @@ -0,0 +1,7 @@ +fn main() { + let mut prost = prost_build::Config::new(); + prost.out_dir("src/lp_swap"); + prost + .compile_protos(&["src/lp_swap/swap_v2.proto"], &["src/lp_swap"]) + .unwrap(); +} diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index 7616429bf0..ab4232e07e 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -43,6 +43,7 @@ use crate::mm2::lp_ordermatch; use crate::mm2::{lp_stats, lp_swap}; pub type P2PRequestResult = Result>; +pub type P2PProcessResult = Result>; pub trait Libp2pPeerId { fn libp2p_peer_id(&self) -> PeerId; @@ -64,6 +65,16 @@ pub enum P2PRequestError { ExpectedSingleResponseError(usize), } +/// Enum covering error cases that can happen during P2P message processing. +#[derive(Debug, Display)] +#[allow(clippy::enum_variant_names)] +pub enum P2PProcessError { + /// The message could not be decoded. + DecodeError(String), + /// Message signature is invalid. + InvalidSignature(String), +} + impl From for P2PRequestError { fn from(e: rmp_serde::encode::Error) -> Self { P2PRequestError::EncodeError(e.to_string()) } } @@ -213,6 +224,19 @@ async fn process_p2p_message( inform_about_break(topic.as_str(), &message.topics); break; }, + Some(lp_swap::SWAP_V2_PREFIX) => { + if let Err(e) = + lp_swap::process_swap_v2_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data) + { + log::error!("{}", e); + return; + } + + to_propagate = true; + + inform_about_break(topic.as_str(), &message.topics); + break; + }, Some(lp_swap::WATCHER_PREFIX) => { if ctx.is_watcher() { if let Err(e) = lp_swap::process_watcher_msg(ctx.clone(), &message.data) { diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index df54276698..b43f8838ea 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -26,7 +26,7 @@ use blake2::digest::{Update, VariableOutput}; use blake2::Blake2bVar; use coins::utxo::{compressed_pub_key_from_priv_raw, ChecksumType, UtxoAddressFormat}; use coins::{coin_conf, find_pair, lp_coinfind, BalanceTradeFeeUpdatedHandler, CoinProtocol, CoinsContext, - FeeApproxStage, MmCoinEnum}; + FeeApproxStage, MarketCoinOps, MmCoinEnum}; use common::executor::{simple_map::AbortableSimpleMap, AbortSettings, AbortableSystem, AbortedError, SpawnAbortable, SpawnFuture, Timer}; use common::log::{error, warn, LogOnError}; @@ -48,6 +48,7 @@ use mm2_metrics::mm_gauge; use mm2_number::{BigDecimal, BigRational, MmNumber, MmNumberMultiRepr}; use mm2_rpc::data::legacy::{MatchBy, Mm2RpcResult, OrderConfirmationsSettings, OrderType, RpcOrderbookEntry, SellBuyRequest, SellBuyResponse, TakerAction, TakerRequestForRpc}; +use mm2_state_machine::prelude::*; #[cfg(test)] use mocktopus::macros::*; use my_orders_storage::{delete_my_maker_order, delete_my_taker_order, save_maker_order_on_update, save_my_new_maker_order, save_my_new_taker_order, MyActiveOrders, MyOrdersFilteringHistory, @@ -61,6 +62,7 @@ use std::collections::hash_map::{Entry, HashMap, RawEntryMut}; use std::collections::{BTreeSet, HashSet}; use std::convert::TryInto; use std::fmt; +use std::ops::Deref; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -69,12 +71,15 @@ use uuid::Uuid; use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest, P2PRequestError}; +use crate::mm2::lp_swap::maker_swap_v2::{self, DummyMakerSwapStorage, MakerSwapStateMachine}; +use crate::mm2::lp_swap::taker_swap_v2::{self, DummyTakerSwapStorage, TakerSwapStateMachine}; use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, - check_other_coin_balance_for_swap, get_max_maker_vol, insert_new_swap_to_db, - is_pubkey_banned, lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast, - p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, AtomicLocktimeVersion, - CheckBalanceError, CheckBalanceResult, CoinVolumeInfo, MakerSwap, RunMakerSwapInput, - RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap}; + check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, get_max_maker_vol, + insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, + p2p_keypair_and_peer_id_to_broadcast, p2p_private_and_peer_id_to_broadcast, run_maker_swap, + run_taker_swap, swap_v2_topic, AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, + CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SecretHashAlgo, + SwapConfirmationsSettings, TakerSwap}; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; @@ -2905,8 +2910,8 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }, }; let alice = bits256::from(maker_match.request.sender_pubkey.0); - let maker_amount = maker_match.reserved.get_base_amount().to_decimal(); - let taker_amount = maker_match.reserved.get_rel_amount().to_decimal(); + let maker_amount = maker_match.reserved.get_base_amount().clone(); + let taker_amount = maker_match.reserved.get_rel_amount().clone(); // lp_connect_start_bob is called only from process_taker_connect, which returns if CryptoCtx is not initialized let crypto_ctx = CryptoCtx::from_ctx(&ctx).expect("'CryptoCtx' must be initialized already"); @@ -2962,22 +2967,53 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }, }; - let maker_swap = MakerSwap::new( - ctx.clone(), - alice, - maker_amount, - taker_amount, - my_persistent_pub, - uuid, - Some(maker_order.uuid), - my_conf_settings, - maker_coin, - taker_coin, - lock_time, - maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - secret, - ); - run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), ctx).await; + if ctx.use_trading_proto_v2() { + match (maker_coin, taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + let mut maker_swap_state_machine = MakerSwapStateMachine { + ctx, + storage: DummyMakerSwapStorage::default(), + started_at: now_sec(), + maker_coin: m.clone(), + maker_volume: maker_amount, + secret, + taker_coin: t.clone(), + dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount), + taker_volume: taker_amount, + taker_premium: Default::default(), + conf_settings: my_conf_settings, + p2p_topic: swap_v2_topic(&uuid), + uuid, + p2p_keypair: maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + secret_hash_algo: SecretHashAlgo::DHASH160, + lock_duration: lock_time, + }; + #[allow(clippy::box_default)] + maker_swap_state_machine + .run(Box::new(maker_swap_v2::Initialize::default())) + .await + .error_log(); + }, + _ => todo!("implement fallback to the old protocol here"), + } + } else { + let maker_swap = MakerSwap::new( + ctx.clone(), + alice, + maker_amount.to_decimal(), + taker_amount.to_decimal(), + my_persistent_pub, + uuid, + Some(maker_order.uuid), + my_conf_settings, + maker_coin, + taker_coin, + lock_time, + maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + secret, + ); + run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), ctx).await; + } }; let settings = AbortSettings::info_on_abort(format!("swap {uuid} stopped!")); @@ -3065,21 +3101,51 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat if let Err(e) = insert_new_swap_to_db(ctx.clone(), taker_coin.ticker(), maker_coin.ticker(), uuid, now).await { error!("Error {} on new swap insertion", e); } - let taker_swap = TakerSwap::new( - ctx.clone(), - maker, - maker_amount, - taker_amount, - my_persistent_pub, - uuid, - Some(uuid), - my_conf_settings, - maker_coin, - taker_coin, - locktime, - taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - ); - run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx).await + + if ctx.use_trading_proto_v2() { + match (maker_coin, taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + let mut taker_swap_state_machine = TakerSwapStateMachine { + ctx, + storage: DummyTakerSwapStorage::default(), + started_at: now_sec(), + lock_duration: locktime, + maker_coin: m.clone(), + maker_volume: maker_amount, + taker_coin: t.clone(), + dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), + taker_volume: taker_amount, + taker_premium: Default::default(), + conf_settings: my_conf_settings, + p2p_topic: swap_v2_topic(&uuid), + uuid, + p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + }; + #[allow(clippy::box_default)] + taker_swap_state_machine + .run(Box::new(taker_swap_v2::Initialize::default())) + .await + .error_log(); + }, + _ => todo!("implement fallback to the old protocol here"), + } + } else { + let taker_swap = TakerSwap::new( + ctx.clone(), + maker, + maker_amount, + taker_amount, + my_persistent_pub, + uuid, + Some(uuid), + my_conf_settings, + maker_coin, + taker_coin, + locktime, + taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + ); + run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx).await + } }; let settings = AbortSettings::info_on_abort(format!("swap {uuid} stopped!")); @@ -3688,8 +3754,8 @@ pub async fn buy(ctx: MmArc, req: Json) -> Result>, String> { try_s!( check_balance_for_taker_swap( &ctx, - &rel_coin, - &base_coin, + rel_coin.deref(), + base_coin.deref(), my_amount, None, None, @@ -3719,8 +3785,8 @@ pub async fn sell(ctx: MmArc, req: Json) -> Result>, String> { try_s!( check_balance_for_taker_swap( &ctx, - &base_coin, - &rel_coin, + base_coin.deref(), + rel_coin.deref(), input.volume.clone(), None, None, @@ -4471,7 +4537,7 @@ pub async fn check_other_coin_balance_for_order_issue(ctx: &MmArc, other_coin: & .compat() .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, other_coin.ticker()))?; - check_other_coin_balance_for_swap(ctx, other_coin, None, trade_fee).await + check_other_coin_balance_for_swap(ctx, other_coin.deref(), None, trade_fee).await } pub async fn check_balance_update_loop(ctx: MmWeak, ticker: String, balance: Option) { @@ -4527,8 +4593,8 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result Result try_s!( check_balance_for_maker_swap( ctx, - &base_coin, - &rel_coin, + base_coin.deref(), + rel_coin.deref(), volume.clone(), None, None, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 1507d97771..ba69c3d199 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -58,9 +58,9 @@ // use super::lp_network::P2PRequestResult; -use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PRequestError}; +use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; use bitcrypto::{dhash160, sha256}; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, MmCoinEnum, TradeFee, TransactionEnum}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::now_sec; use common::time_cache::DuplicateCache; @@ -76,6 +76,7 @@ use mm2_libp2p::{decode_signed, encode_and_sign, pub_sub_topic, PeerId, TopicPre use mm2_number::{BigDecimal, BigRational, MmNumber, MmNumberMultiRepr}; use parking_lot::Mutex as PaMutex; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; +use secp256k1::{PublicKey, SecretKey, Signature}; use serde::Serialize; use serde_json::{self as json, Value as Json}; use std::collections::{HashMap, HashSet}; @@ -91,14 +92,19 @@ use std::sync::atomic::{AtomicU64, Ordering}; #[path = "lp_swap/check_balance.rs"] mod check_balance; #[path = "lp_swap/maker_swap.rs"] mod maker_swap; +#[path = "lp_swap/maker_swap_v2.rs"] pub mod maker_swap_v2; #[path = "lp_swap/max_maker_vol_rpc.rs"] mod max_maker_vol_rpc; #[path = "lp_swap/my_swaps_storage.rs"] mod my_swaps_storage; #[path = "lp_swap/pubkey_banning.rs"] mod pubkey_banning; #[path = "lp_swap/recreate_swap_data.rs"] mod recreate_swap_data; #[path = "lp_swap/saved_swap.rs"] mod saved_swap; #[path = "lp_swap/swap_lock.rs"] mod swap_lock; +#[path = "lp_swap/komodefi.swap_v2.pb.rs"] +#[rustfmt::skip] +mod swap_v2_pb; #[path = "lp_swap/swap_watcher.rs"] pub(crate) mod swap_watcher; #[path = "lp_swap/taker_swap.rs"] mod taker_swap; +#[path = "lp_swap/taker_swap_v2.rs"] pub mod taker_swap_v2; #[path = "lp_swap/trade_preimage.rs"] mod trade_preimage; #[cfg(target_arch = "wasm32")] @@ -107,7 +113,7 @@ mod swap_wasm_db; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use crypto::CryptoCtx; -use keys::KeyPair; +use keys::{KeyPair, SECP_SIGN, SECP_VERIFY}; use maker_swap::MakerSwapEvent; pub use maker_swap::{calc_max_maker_vol, check_balance_for_maker_swap, get_max_maker_vol, maker_swap_trade_preimage, run_maker_swap, CoinVolumeInfo, MakerSavedEvent, MakerSavedSwap, MakerSwap, @@ -118,6 +124,7 @@ use pubkey_banning::BanReason; pub use pubkey_banning::{ban_pubkey_rpc, is_pubkey_banned, list_banned_pubkeys_rpc, unban_pubkeys_rpc}; pub use recreate_swap_data::recreate_swap_data; pub use saved_swap::{SavedSwap, SavedSwapError, SavedSwapIo, SavedSwapResult}; +use swap_v2_pb::*; pub use swap_watcher::{process_watcher_msg, watcher_topic, TakerSwapWatcherData, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, TAKER_SWAP_ENTRY_TIMEOUT_SEC, WATCHER_PREFIX}; @@ -129,8 +136,15 @@ pub use trade_preimage::trade_preimage_rpc; pub const SWAP_PREFIX: TopicPrefix = "swap"; +pub const SWAP_V2_PREFIX: TopicPrefix = "swapv2"; + pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; +const NEGOTIATE_SEND_INTERVAL: f64 = 30.; + +/// If a certain P2P message is not received, swap will be aborted after this time expires. +const NEGOTIATION_TIMEOUT_SEC: u64 = 90; + cfg_wasm32! { use mm2_db::indexed_db::{ConstructibleDb, DbLocked}; use swap_wasm_db::{InitDbResult, SwapDb}; @@ -168,6 +182,29 @@ impl SwapMsgStore { } } +/// Storage for P2P messages, which are exchanged during SwapV2 protocol execution. +#[derive(Debug, Default)] +pub struct SwapV2MsgStore { + maker_negotiation: Option, + taker_negotiation: Option, + maker_negotiated: Option, + taker_payment: Option, + maker_payment: Option, + taker_payment_spend_preimage: Option, + #[allow(dead_code)] + accept_only_from: bits256, +} + +impl SwapV2MsgStore { + /// Creates new SwapV2MsgStore + pub fn new(accept_only_from: bits256) -> Self { + SwapV2MsgStore { + accept_only_from, + ..Default::default() + } + } +} + /// Returns key-pair for signing P2P messages and an optional `PeerId` if it should be used forcibly /// instead of local peer ID. /// @@ -442,6 +479,7 @@ struct SwapsContext { running_swaps: Mutex>>, banned_pubkeys: Mutex>, swap_msgs: Mutex>, + swap_v2_msgs: Mutex>, taker_swap_watchers: PaMutex>>, #[cfg(target_arch = "wasm32")] swap_db: ConstructibleDb, @@ -455,6 +493,7 @@ impl SwapsContext { running_swaps: Mutex::new(vec![]), banned_pubkeys: Mutex::new(HashMap::new()), swap_msgs: Mutex::new(HashMap::new()), + swap_v2_msgs: Mutex::new(HashMap::new()), taker_swap_watchers: PaMutex::new(DuplicateCache::new(Duration::from_secs( TAKER_SWAP_ENTRY_TIMEOUT_SEC, ))), @@ -469,6 +508,12 @@ impl SwapsContext { self.swap_msgs.lock().unwrap().insert(uuid, store); } + /// Initializes storage for the swap with specific uuid. + pub fn init_msg_v2_store(&self, uuid: Uuid, accept_only_from: bits256) { + let store = SwapV2MsgStore::new(accept_only_from); + self.swap_v2_msgs.lock().unwrap().insert(uuid, store); + } + #[cfg(target_arch = "wasm32")] pub async fn swap_db(&self) -> InitDbResult> { self.swap_db.get_or_initialize().await } } @@ -704,7 +749,8 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, dex_fee_th } } -pub fn dex_fee_amount_from_taker_coin(taker_coin: &MmCoinEnum, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { +/// Calculates DEX fee with a threshold based on min tx amount of the taker coin. +pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { let min_tx_amount = MmNumber::from(taker_coin.min_tx_amount()); let dex_fee_threshold = dex_fee_threshold(min_tx_amount); dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &dex_fee_threshold) @@ -1368,7 +1414,8 @@ pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result> Ok(try_s!(Response::builder().body(res))) } -enum SecretHashAlgo { +/// Algorithm used to hash swap secret. +pub enum SecretHashAlgo { /// ripemd160(sha256(secret)) DHASH160, /// sha256(secret) @@ -1415,6 +1462,128 @@ pub struct SwapPubkeys { pub taker: String, } +/// P2P topic used to broadcast messages during execution of the upgraded swap protocol. +pub fn swap_v2_topic(uuid: &Uuid) -> String { pub_sub_topic(SWAP_V2_PREFIX, &uuid.to_string()) } + +/// Broadcast the swap v2 message once +pub fn broadcast_swap_v2_message( + ctx: &MmArc, + topic: String, + msg: &T, + p2p_privkey: &Option, +) { + use prost::Message; + + let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); + let encoded_msg = msg.encode_to_vec(); + + let secp_secret = SecretKey::from_slice(&p2p_private).expect("valid secret key"); + let secp_message = + secp256k1::Message::from_slice(sha256(&encoded_msg).as_slice()).expect("sha256 is 32 bytes hash"); + let signature = SECP_SIGN.sign(&secp_message, &secp_secret); + + let signed_message = SignedMessage { + from: PublicKey::from_secret_key(&*SECP_SIGN, &secp_secret).serialize().into(), + signature: signature.serialize_compact().into(), + payload: encoded_msg, + }; + broadcast_p2p_msg(ctx, vec![topic], signed_message.encode_to_vec(), from); +} + +/// Spawns the loop that broadcasts message every `interval` seconds returning the AbortOnDropHandle +/// to stop it +pub fn broadcast_swap_v2_msg_every( + ctx: MmArc, + topic: String, + msg: T, + interval_sec: f64, + p2p_privkey: Option, +) -> AbortOnDropHandle { + let fut = async move { + loop { + broadcast_swap_v2_message(&ctx, topic.clone(), &msg, &p2p_privkey); + Timer::sleep(interval_sec).await; + } + }; + spawn_abortable(fut) +} + +/// Processes messages received during execution of the upgraded swap protocol. +pub fn process_swap_v2_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PProcessResult<()> { + use prost::Message; + + let uuid = Uuid::from_str(topic).map_to_mm(|e| P2PProcessError::DecodeError(e.to_string()))?; + + let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); + let mut msgs = swap_ctx.swap_v2_msgs.lock().unwrap(); + if let Some(msg_store) = msgs.get_mut(&uuid) { + let signed_message = SignedMessage::decode(msg).map_to_mm(|e| P2PProcessError::DecodeError(e.to_string()))?; + + let pubkey = + PublicKey::from_slice(&signed_message.from).map_to_mm(|e| P2PProcessError::DecodeError(e.to_string()))?; + let signature = Signature::from_compact(&signed_message.signature) + .map_to_mm(|e| P2PProcessError::DecodeError(e.to_string()))?; + let secp_message = secp256k1::Message::from_slice(sha256(&signed_message.payload).as_slice()) + .expect("sha256 is 32 bytes hash"); + + SECP_VERIFY + .verify(&secp_message, &signature, &pubkey) + .map_to_mm(|e| P2PProcessError::InvalidSignature(e.to_string()))?; + + let swap_message = SwapMessage::decode(signed_message.payload.as_slice()) + .map_to_mm(|e| P2PProcessError::DecodeError(e.to_string()))?; + + debug!("Processing swap v2 msg {:?} for uuid {}", swap_message, uuid); + match swap_message.inner { + Some(swap_v2_pb::swap_message::Inner::MakerNegotiation(maker_negotiation)) => { + msg_store.maker_negotiation = Some(maker_negotiation) + }, + Some(swap_v2_pb::swap_message::Inner::TakerNegotiation(taker_negotiation)) => { + msg_store.taker_negotiation = Some(taker_negotiation) + }, + Some(swap_v2_pb::swap_message::Inner::MakerNegotiated(maker_negotiated)) => { + msg_store.maker_negotiated = Some(maker_negotiated) + }, + Some(swap_v2_pb::swap_message::Inner::TakerPaymentInfo(taker_payment)) => { + msg_store.taker_payment = Some(taker_payment) + }, + Some(swap_v2_pb::swap_message::Inner::MakerPaymentInfo(maker_payment)) => { + msg_store.maker_payment = Some(maker_payment) + }, + Some(swap_v2_pb::swap_message::Inner::TakerPaymentSpendPreimage(preimage)) => { + msg_store.taker_payment_spend_preimage = Some(preimage) + }, + None => return MmError::err(P2PProcessError::DecodeError("swap_message.inner is None".into())), + } + } + Ok(()) +} + +async fn recv_swap_v2_msg( + ctx: MmArc, + mut getter: impl FnMut(&mut SwapV2MsgStore) -> Option, + uuid: &Uuid, + timeout: u64, +) -> Result { + let started = now_sec(); + let timeout = BASIC_COMM_TIMEOUT + timeout; + let wait_until = started + timeout; + loop { + Timer::sleep(1.).await; + let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); + let mut msgs = swap_ctx.swap_v2_msgs.lock().unwrap(); + if let Some(msg_store) = msgs.get_mut(uuid) { + if let Some(msg) = getter(msg_store) { + return Ok(msg); + } + } + let now = now_sec(); + if now > wait_until { + return ERR!("Timeout ({} > {})", now - started, timeout); + } + } +} + #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; diff --git a/mm2src/mm2_main/src/lp_swap/check_balance.rs b/mm2src/mm2_main/src/lp_swap/check_balance.rs index fb007331cc..5590679f6f 100644 --- a/mm2src/mm2_main/src/lp_swap/check_balance.rs +++ b/mm2src/mm2_main/src/lp_swap/check_balance.rs @@ -1,6 +1,6 @@ use super::taker_swap::MaxTakerVolumeLessThanDust; use super::{get_locked_amount, get_locked_amount_by_other_swaps}; -use coins::{BalanceError, MmCoinEnum, TradeFee, TradePreimageError}; +use coins::{BalanceError, MmCoin, TradeFee, TradePreimageError}; use common::log::debug; use derive_more::Display; use futures::compat::Future01CompatExt; @@ -16,7 +16,7 @@ pub type CheckBalanceResult = Result>; /// `swap_uuid` is used if our swap is running already and we should except this swap locked amount from the following calculations. pub async fn check_my_coin_balance_for_swap( ctx: &MmArc, - coin: &MmCoinEnum, + coin: &dyn MmCoin, swap_uuid: Option<&Uuid>, volume: MmNumber, mut trade_fee: TradeFee, @@ -85,7 +85,7 @@ pub async fn check_my_coin_balance_for_swap( pub async fn check_other_coin_balance_for_swap( ctx: &MmArc, - coin: &MmCoinEnum, + coin: &dyn MmCoin, swap_uuid: Option<&Uuid>, trade_fee: TradeFee, ) -> CheckBalanceResult<()> { diff --git a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs new file mode 100644 index 0000000000..d677f903a0 --- /dev/null +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -0,0 +1,114 @@ +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SignedMessage { + #[prost(bytes="vec", tag="1")] + pub from: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="2")] + pub signature: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="3")] + pub payload: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MakerNegotiation { + #[prost(uint64, tag="1")] + pub started_at: u64, + #[prost(uint64, tag="2")] + pub payment_locktime: u64, + #[prost(bytes="vec", tag="3")] + pub secret_hash: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="4")] + pub maker_coin_htlc_pub: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="5")] + pub taker_coin_htlc_pub: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="6")] + pub maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", optional, tag="7")] + pub taker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Abort { + #[prost(string, tag="1")] + pub reason: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TakerNegotiationData { + #[prost(uint64, tag="1")] + pub started_at: u64, + #[prost(uint64, tag="2")] + pub payment_locktime: u64, + /// add bytes secret_hash = 3 if required + #[prost(bytes="vec", tag="4")] + pub maker_coin_htlc_pub: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="5")] + pub taker_coin_htlc_pub: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="6")] + pub maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", optional, tag="7")] + pub taker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TakerNegotiation { + #[prost(oneof="taker_negotiation::Action", tags="1, 2")] + pub action: ::core::option::Option, +} +/// Nested message and enum types in `TakerNegotiation`. +pub mod taker_negotiation { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Action { + #[prost(message, tag="1")] + Continue(super::TakerNegotiationData), + #[prost(message, tag="2")] + Abort(super::Abort), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MakerNegotiated { + #[prost(bool, tag="1")] + pub negotiated: bool, + /// used when negotiated is false + #[prost(string, optional, tag="2")] + pub reason: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TakerPaymentInfo { + #[prost(bytes="vec", tag="1")] + pub tx_bytes: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="2")] + pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MakerPaymentInfo { + #[prost(bytes="vec", tag="1")] + pub tx_bytes: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="2")] + pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TakerPaymentSpendPreimage { + #[prost(bytes="vec", tag="1")] + pub signature: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="2")] + pub tx_preimage: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SwapMessage { + #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6")] + pub inner: ::core::option::Option, +} +/// Nested message and enum types in `SwapMessage`. +pub mod swap_message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Inner { + #[prost(message, tag="1")] + MakerNegotiation(super::MakerNegotiation), + #[prost(message, tag="2")] + TakerNegotiation(super::TakerNegotiation), + #[prost(message, tag="3")] + MakerNegotiated(super::MakerNegotiated), + #[prost(message, tag="4")] + TakerPaymentInfo(super::TakerPaymentInfo), + #[prost(message, tag="5")] + MakerPaymentInfo(super::MakerPaymentInfo), + #[prost(message, tag="6")] + TakerPaymentSpendPreimage(super::TakerPaymentSpendPreimage), + } +} diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index e0fa771ff2..7c51fe0c30 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -15,7 +15,7 @@ use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::MakerOrderBuilder; use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration}; use coins::lp_price::fetch_swap_coins_price; -use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, +use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput}; @@ -34,7 +34,7 @@ use parking_lot::Mutex as PaMutex; use primitives::hash::{H256, H264}; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json, H264 as H264Json}; use std::any::TypeId; -use std::convert::TryInto; +use std::ops::Deref; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -504,8 +504,8 @@ impl MakerSwap { }; match check_balance_for_maker_swap( &self.ctx, - &self.maker_coin, - &self.taker_coin, + self.maker_coin.deref(), + self.taker_coin.deref(), self.maker_amount.clone().into(), Some(&self.uuid), Some(params), @@ -754,7 +754,8 @@ impl MakerSwap { info!("Taker fee tx {:02x}", hash); let taker_amount = MmNumber::from(self.taker_amount.clone()); - let fee_amount = dex_fee_amount_from_taker_coin(&self.taker_coin, &self.r().data.maker_coin, &taker_amount); + let fee_amount = + dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; let taker_coin_start_block = self.r().data.taker_coin_start_block; @@ -810,7 +811,7 @@ impl MakerSwap { let transaction_f = self .maker_coin .check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: self.r().data.maker_payment_lock as u32, + time_lock: self.r().data.maker_payment_lock, other_pub: &*self.r().other_maker_coin_htlc_pub, secret_hash: secret_hash.as_slice(), search_from_block: self.r().data.maker_coin_start_block, @@ -846,7 +847,7 @@ impl MakerSwap { None => { let payment_fut = self.maker_coin.send_maker_payment(SendPaymentArgs { time_lock_duration: self.r().data.lock_duration, - time_lock: self.r().data.maker_payment_lock as u32, + time_lock: self.r().data.maker_payment_lock, other_pubkey: &*self.r().other_maker_coin_htlc_pub, secret_hash: secret_hash.as_slice(), amount: self.maker_amount.clone(), @@ -1034,7 +1035,7 @@ impl MakerSwap { let validate_input = ValidatePaymentInput { payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, - time_lock: self.taker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.taker_payment_lock.load(Ordering::Relaxed), time_lock_duration: self.r().data.lock_duration, other_pub: self.r().other_taker_coin_htlc_pub.to_vec(), unique_swap_data: self.unique_swap_data(), @@ -1083,7 +1084,7 @@ impl MakerSwap { let spend_fut = self.taker_coin.send_maker_spends_taker_payment(SpendPaymentArgs { other_payment_tx: &self.r().taker_payment.clone().unwrap().tx_hex, - time_lock: self.taker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.taker_payment_lock.load(Ordering::Relaxed), other_pubkey: &*self.r().other_taker_coin_htlc_pub, secret: &self.r().data.secret.0, secret_hash: &self.secret_hash(), @@ -1220,19 +1221,11 @@ impl MakerSwap { let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub; let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); let watcher_reward = self.r().watcher_reward; - let time_lock: u32 = match locktime.try_into() { - Ok(t) => t, - Err(e) => { - return Ok((Some(MakerSwapCommand::Finish), vec![ - MakerSwapEvent::MakerPaymentRefundFailed(ERRL!("!locktime.try_into: {}", e.to_string()).into()), - ])) - }, - }; let spend_result = self .maker_coin .send_maker_refunds_payment(RefundPaymentArgs { payment_tx: &maker_payment, - time_lock, + time_lock: locktime, other_pubkey: other_maker_coin_htlc_pub.as_slice(), secret_hash: self.secret_hash().as_slice(), swap_contract_address: &maker_coin_swap_contract_address, @@ -1387,7 +1380,7 @@ impl MakerSwap { // have to do this because std::sync::RwLockReadGuard returned by r() is not Send, // so it can't be used across await - let timelock = selfi.taker_payment_lock.load(Ordering::Relaxed) as u32; + let timelock = selfi.taker_payment_lock.load(Ordering::Relaxed); let other_taker_coin_htlc_pub = selfi.r().other_taker_coin_htlc_pub; let taker_coin_start_block = selfi.r().data.taker_coin_start_block; @@ -1461,7 +1454,7 @@ impl MakerSwap { // have to do this because std::sync::RwLockReadGuard returned by r() is not Send, // so it can't be used across await - let maker_payment_lock = self.r().data.maker_payment_lock as u32; + let maker_payment_lock = self.r().data.maker_payment_lock; let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub; let maker_coin_start_block = self.r().data.maker_coin_start_block; let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); @@ -1527,12 +1520,7 @@ impl MakerSwap { return ERR!("Maker payment will be refunded automatically!"); } - let can_refund_htlc = try_s!( - self.maker_coin - .can_refund_htlc(maker_payment_lock as u64) - .compat() - .await - ); + let can_refund_htlc = try_s!(self.maker_coin.can_refund_htlc(maker_payment_lock).compat().await); if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund_htlc { return ERR!("Too early to refund, wait until {}", wait_until_sec(seconds_to_wait)); } @@ -2174,8 +2162,8 @@ pub struct MakerSwapPreparedParams { pub async fn check_balance_for_maker_swap( ctx: &MmArc, - my_coin: &MmCoinEnum, - other_coin: &MmCoinEnum, + my_coin: &dyn MmCoin, + other_coin: &dyn MmCoin, volume: MmNumber, swap_uuid: Option<&Uuid>, prepared_params: Option, @@ -2255,7 +2243,7 @@ pub async fn maker_swap_trade_preimage( if req.max { // Note the `calc_max_maker_vol` returns [`CheckBalanceError::NotSufficientBalance`] error if the balance of `base_coin` is not sufficient. // So we have to check the balance of the other coin only. - check_other_coin_balance_for_swap(ctx, &rel_coin, None, rel_coin_fee.clone()).await? + check_other_coin_balance_for_swap(ctx, rel_coin.deref(), None, rel_coin_fee.clone()).await? } else { let prepared_params = MakerSwapPreparedParams { maker_payment_trade_fee: base_coin_fee.clone(), @@ -2263,8 +2251,8 @@ pub async fn maker_swap_trade_preimage( }; check_balance_for_maker_swap( ctx, - &base_coin, - &rel_coin, + base_coin.deref(), + rel_coin.deref(), volume.clone(), None, Some(prepared_params), diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs new file mode 100644 index 0000000000..eb601df2e2 --- /dev/null +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -0,0 +1,896 @@ +use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; +use crate::mm2::lp_network::subscribe_to_topic; +use crate::mm2::lp_swap::swap_v2_pb::*; +use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier}; +use async_trait::async_trait; +use bitcrypto::{dhash160, sha256}; +use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, SendPaymentArgs, + SwapOpsV2, TxPreimageWithSig}; +use common::log::{debug, info, warn}; +use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; +use keys::KeyPair; +use mm2_core::mm_ctx::MmArc; +use mm2_number::MmNumber; +use mm2_state_machine::prelude::*; +use mm2_state_machine::storable_state_machine::*; +use primitives::hash::H256; +use std::collections::HashMap; +use std::marker::PhantomData; +use uuid::Uuid; + +// This is needed to have Debug on messages +#[allow(unused_imports)] use prost::Message; + +/// Represents events produced by maker swap states. +#[derive(Debug, PartialEq)] +pub enum MakerSwapEvent { + /// Swap has been successfully initialized. + Initialized { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + }, + /// Started waiting for taker payment. + WaitingForTakerPayment { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + }, + /// Received taker payment info. + TakerPaymentReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment: TransactionIdentifier, + }, + /// Sent maker payment. + MakerPaymentSent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + }, + /// Something went wrong, so maker payment refund is required. + MakerPaymentRefundRequired { maker_payment: TransactionIdentifier }, + /// Taker payment has been confirmed on-chain. + TakerPaymentConfirmed { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + }, + /// Maker successfully spent taker's payment. + TakerPaymentSpent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_spend: TransactionIdentifier, + }, + /// Swap has been aborted before maker payment was sent. + Aborted { reason: String }, + /// Swap completed successfully. + Completed, +} + +/// Represents errors that can be produced by [`MakerSwapStateMachine`] run. +#[derive(Debug, Display)] +pub enum MakerSwapStateMachineError {} + +/// Dummy storage for maker swap events (used temporary). +#[derive(Default)] +pub struct DummyMakerSwapStorage { + events: HashMap>, +} + +#[async_trait] +impl StateMachineStorage for DummyMakerSwapStorage { + type MachineId = Uuid; + type Event = MakerSwapEvent; + type Error = MakerSwapStateMachineError; + + async fn store_event(&mut self, id: Self::MachineId, event: Self::Event) -> Result<(), Self::Error> { + self.events.entry(id).or_insert_with(Vec::new).push(event); + Ok(()) + } + + async fn get_unfinished(&self) -> Result, Self::Error> { + Ok(self.events.keys().copied().collect()) + } + + async fn mark_finished(&mut self, _id: Self::MachineId) -> Result<(), Self::Error> { Ok(()) } +} + +/// Represents the state machine for maker's side of the Trading Protocol Upgrade swap (v2). +pub struct MakerSwapStateMachine { + /// MM2 context + pub ctx: MmArc, + /// Storage + pub storage: DummyMakerSwapStorage, + /// Maker coin + pub maker_coin: MakerCoin, + /// The amount swapped by maker. + pub maker_volume: MmNumber, + /// The secret used in HTLC hashlock. + pub secret: H256, + /// Algorithm used to hash the swap secret. + pub secret_hash_algo: SecretHashAlgo, + /// The timestamp when the swap was started. + pub started_at: u64, + /// The duration of HTLC timelock in seconds. + pub lock_duration: u64, + /// Taker coin + pub taker_coin: TakerCoin, + /// The amount swapped by taker. + pub taker_volume: MmNumber, + /// Premium amount, which might be paid to maker as additional reward. + pub taker_premium: MmNumber, + /// DEX fee amount + pub dex_fee_amount: MmNumber, + /// Swap transactions' confirmations settings + pub conf_settings: SwapConfirmationsSettings, + /// UUID of the swap + pub uuid: Uuid, + /// The gossipsub topic used for peer-to-peer communication in swap process. + pub p2p_topic: String, + /// If Some, used to sign P2P messages of this swap. + pub p2p_keypair: Option, +} + +impl MakerSwapStateMachine { + /// Timeout for taker payment's on-chain confirmation. + #[inline] + fn taker_payment_conf_timeout(&self) -> u64 { self.started_at + self.lock_duration * 2 / 3 } + + /// Returns timestamp of maker payment's locktime. + #[inline] + fn maker_payment_locktime(&self) -> u64 { self.started_at + 2 * self.lock_duration } + + /// Returns secret hash generated using selected [SecretHashAlgo]. + fn secret_hash(&self) -> Vec { + match self.secret_hash_algo { + SecretHashAlgo::DHASH160 => dhash160(self.secret.as_slice()).take().into(), + SecretHashAlgo::SHA256 => sha256(self.secret.as_slice()).take().into(), + } + } + + /// Returns data that is unique for this swap. + #[inline] + fn unique_data(&self) -> Vec { self.secret_hash() } +} + +impl StorableStateMachine + for MakerSwapStateMachine +{ + type Storage = DummyMakerSwapStorage; + type Result = (); + + fn storage(&mut self) -> &mut Self::Storage { &mut self.storage } + + fn id(&self) -> ::MachineId { self.uuid } + + fn restore_from_storage( + _id: ::MachineId, + _storage: Self::Storage, + ) -> Result, ::Error> { + todo!() + } +} + +/// Represents a state used to start a new maker swap. +pub struct Initialize { + maker_coin: PhantomData, + taker_coin: PhantomData, +} + +impl Default for Initialize { + fn default() -> Self { + Initialize { + maker_coin: Default::default(), + taker_coin: Default::default(), + } + } +} + +impl InitialState for Initialize { + type StateMachine = MakerSwapStateMachine; +} + +#[async_trait] +impl State + for Initialize +{ + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + subscribe_to_topic(&state_machine.ctx, state_machine.p2p_topic.clone()); + let swap_ctx = SwapsContext::from_ctx(&state_machine.ctx).expect("SwapsContext::from_ctx should not fail"); + swap_ctx.init_msg_v2_store(state_machine.uuid, bits256::default()); + + let maker_coin_start_block = match state_machine.maker_coin.current_block().compat().await { + Ok(b) => b, + Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + }; + + let taker_coin_start_block = match state_machine.taker_coin.current_block().compat().await { + Ok(b) => b, + Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + }; + + if let Err(e) = check_balance_for_maker_swap( + &state_machine.ctx, + &state_machine.maker_coin, + &state_machine.taker_coin, + state_machine.maker_volume.clone(), + Some(&state_machine.uuid), + None, + FeeApproxStage::StartSwap, + ) + .await + { + return Self::change_state(Aborted::new(e.to_string()), state_machine).await; + } + + info!("Maker swap {} has successfully started", state_machine.uuid); + let negotiate = Initialized { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block, + taker_coin_start_block, + }; + Self::change_state(negotiate, state_machine).await + } +} + +struct Initialized { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, +} + +impl TransitionFrom> for Initialized {} + +impl StorableState for Initialized { + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::Initialized { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + } + } +} + +#[async_trait] +impl State for Initialized { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let unique_data = state_machine.unique_data(); + + let maker_negotiation_msg = MakerNegotiation { + started_at: state_machine.started_at, + payment_locktime: state_machine.maker_payment_locktime(), + secret_hash: state_machine.secret_hash(), + maker_coin_htlc_pub: state_machine.maker_coin.derive_htlc_pubkey(&unique_data), + taker_coin_htlc_pub: state_machine.taker_coin.derive_htlc_pubkey(&unique_data), + maker_coin_swap_contract: state_machine.maker_coin.swap_contract_address().map(|bytes| bytes.0), + taker_coin_swap_contract: state_machine.taker_coin.swap_contract_address().map(|bytes| bytes.0), + }; + debug!("Sending maker negotiation message {:?}", maker_negotiation_msg); + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::MakerNegotiation(maker_negotiation_msg)), + }; + let abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + NEGOTIATE_SEND_INTERVAL, + state_machine.p2p_keypair, + ); + + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.taker_negotiation.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + let taker_negotiation = match recv_fut.await { + Ok(d) => d, + Err(e) => { + let next_state = Aborted::new(format!("Failed to receive TakerNegotiation: {}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + + debug!("Received taker negotiation message {:?}", taker_negotiation); + let taker_data = match taker_negotiation.action { + Some(taker_negotiation::Action::Continue(data)) => data, + Some(taker_negotiation::Action::Abort(abort)) => { + let next_state = Aborted::new(abort.reason); + return Self::change_state(next_state, state_machine).await; + }, + None => { + let next_state = Aborted::new("received invalid negotiation message from taker".into()); + return Self::change_state(next_state, state_machine).await; + }, + }; + + let next_state = WaitingForTakerPayment { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment_locktime: taker_data.payment_locktime, + maker_coin_htlc_pub_from_taker: taker_data.maker_coin_htlc_pub, + taker_coin_htlc_pub_from_taker: taker_data.taker_coin_htlc_pub, + maker_coin_swap_contract: taker_data.maker_coin_swap_contract, + taker_coin_swap_contract: taker_data.taker_coin_swap_contract, + }; + Self::change_state(next_state, state_machine).await + } +} + +struct WaitingForTakerPayment { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment_locktime: u64, + maker_coin_htlc_pub_from_taker: Vec, + taker_coin_htlc_pub_from_taker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, +} + +impl TransitionFrom> + for WaitingForTakerPayment +{ +} + +#[async_trait] +impl State for WaitingForTakerPayment { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let maker_negotiated_msg = MakerNegotiated { + negotiated: true, + reason: None, + }; + debug!("Sending maker negotiated message {:?}", maker_negotiated_msg); + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::MakerNegotiated(maker_negotiated_msg)), + }; + let abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + NEGOTIATE_SEND_INTERVAL, + state_machine.p2p_keypair, + ); + + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.taker_payment.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + let taker_payment = match recv_fut.await { + Ok(p) => p, + Err(e) => { + let next_state = Aborted::new(format!("Failed to receive TakerPaymentInfo: {}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + + debug!("Received taker payment info message {:?}", taker_payment); + let next_state = TakerPaymentReceived { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment_locktime: self.taker_payment_locktime, + maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker, + taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker, + maker_coin_swap_contract: self.maker_coin_swap_contract, + taker_coin_swap_contract: self.taker_coin_swap_contract, + taker_payment: TransactionIdentifier { + tx_hex: taker_payment.tx_bytes.into(), + tx_hash: Default::default(), + }, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState + for WaitingForTakerPayment +{ + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::WaitingForTakerPayment { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + } + } +} + +struct TakerPaymentReceived { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment_locktime: u64, + maker_coin_htlc_pub_from_taker: Vec, + taker_coin_htlc_pub_from_taker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, + taker_payment: TransactionIdentifier, +} + +impl TransitionFrom> + for TakerPaymentReceived +{ +} + +#[async_trait] +impl State for TakerPaymentReceived { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let args = SendPaymentArgs { + time_lock_duration: state_machine.lock_duration, + time_lock: state_machine.maker_payment_locktime(), + other_pubkey: &self.maker_coin_htlc_pub_from_taker, + secret_hash: &state_machine.secret_hash(), + amount: state_machine.maker_volume.to_decimal(), + swap_contract_address: &None, + swap_unique_data: &state_machine.unique_data(), + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until: 0, + }; + let maker_payment = match state_machine.maker_coin.send_maker_payment(args).compat().await { + Ok(tx) => tx, + Err(e) => { + let next_state = Aborted::new(format!("Failed to send maker payment {:?}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + info!( + "Sent maker payment {} tx {:02x} during swap {}", + state_machine.maker_coin.ticker(), + maker_payment.tx_hash(), + state_machine.uuid + ); + let next_state = MakerPaymentSent { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment_locktime: self.taker_payment_locktime, + maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker, + taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker, + maker_coin_swap_contract: self.maker_coin_swap_contract, + taker_coin_swap_contract: self.taker_coin_swap_contract, + taker_payment: self.taker_payment, + maker_payment: TransactionIdentifier { + tx_hex: maker_payment.tx_hex().into(), + tx_hash: maker_payment.tx_hash(), + }, + }; + + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState + for TakerPaymentReceived +{ + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::TakerPaymentReceived { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment: self.taker_payment.clone(), + } + } +} + +struct MakerPaymentSent { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment_locktime: u64, + maker_coin_htlc_pub_from_taker: Vec, + taker_coin_htlc_pub_from_taker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, + taker_payment: TransactionIdentifier, + maker_payment: TransactionIdentifier, +} + +impl TransitionFrom> + for MakerPaymentSent +{ +} + +#[async_trait] +impl State for MakerPaymentSent { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let maker_payment_info = MakerPaymentInfo { + tx_bytes: self.maker_payment.tx_hex.0.clone(), + next_step_instructions: None, + }; + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::MakerPaymentInfo(maker_payment_info)), + }; + + debug!("Sending maker payment info message {:?}", swap_msg); + let _abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + 600., + state_machine.p2p_keypair, + ); + let input = ConfirmPaymentInput { + payment_tx: self.taker_payment.tx_hex.0.clone(), + confirmations: state_machine.conf_settings.taker_coin_confs, + requires_nota: state_machine.conf_settings.taker_coin_nota, + wait_until: state_machine.taker_payment_conf_timeout(), + check_every: 10, + }; + if let Err(e) = state_machine.taker_coin.wait_for_confirmations(input).compat().await { + let next_state = MakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::TakerPaymentNotConfirmedInTime(e), + }; + return Self::change_state(next_state, state_machine).await; + } + + let next_state = TakerPaymentConfirmed { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment, + taker_payment: self.taker_payment, + taker_payment_locktime: self.taker_payment_locktime, + maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker, + taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker, + maker_coin_swap_contract: self.maker_coin_swap_contract, + taker_coin_swap_contract: self.taker_coin_swap_contract, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState for MakerPaymentSent { + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::MakerPaymentSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment.clone(), + } + } +} + +#[derive(Debug)] +enum MakerPaymentRefundReason { + TakerPaymentNotConfirmedInTime(String), + DidNotGetTakerPaymentSpendPreimage(String), + TakerPaymentSpendPreimageIsNotValid(String), + TakerPaymentSpendBroadcastFailed(String), +} + +struct MakerPaymentRefundRequired { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_payment: TransactionIdentifier, + reason: MakerPaymentRefundReason, +} + +impl TransitionFrom> + for MakerPaymentRefundRequired +{ +} +impl TransitionFrom> + for MakerPaymentRefundRequired +{ +} + +#[async_trait] +impl State + for MakerPaymentRefundRequired +{ + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + warn!( + "Entered MakerPaymentRefundRequired state for swap {} with reason {:?}", + state_machine.uuid, self.reason + ); + unimplemented!() + } +} + +impl StorableState + for MakerPaymentRefundRequired +{ + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::MakerPaymentRefundRequired { + maker_payment: self.maker_payment.clone(), + } + } +} + +#[allow(dead_code)] +struct TakerPaymentConfirmed { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_locktime: u64, + maker_coin_htlc_pub_from_taker: Vec, + taker_coin_htlc_pub_from_taker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, +} + +impl TransitionFrom> + for TakerPaymentConfirmed +{ +} + +#[async_trait] +impl State for TakerPaymentConfirmed { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.taker_payment_spend_preimage.take(), + &state_machine.uuid, + state_machine.taker_payment_conf_timeout(), + ); + let preimage = match recv_fut.await { + Ok(preimage) => preimage, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::DidNotGetTakerPaymentSpendPreimage(e), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + debug!("Received taker payment spend preimage message {:?}", preimage); + + let unique_data = state_machine.unique_data(); + + let gen_args = GenTakerPaymentSpendArgs { + taker_tx: &self.taker_payment.tx_hex.0, + time_lock: self.taker_payment_locktime, + secret_hash: &state_machine.secret_hash(), + maker_pub: &state_machine.maker_coin.derive_htlc_pubkey(&unique_data), + taker_pub: &self.taker_coin_htlc_pub_from_taker, + dex_fee_amount: state_machine.dex_fee_amount.to_decimal(), + premium_amount: Default::default(), + trading_amount: state_machine.taker_volume.to_decimal(), + dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, + }; + let tx_preimage = TxPreimageWithSig { + preimage: preimage.tx_preimage.unwrap_or_default(), + signature: preimage.signature, + }; + if let Err(e) = state_machine + .taker_coin + .validate_taker_payment_spend_preimage(&gen_args, &tx_preimage) + .await + { + let next_state = MakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::TakerPaymentSpendPreimageIsNotValid(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + } + + let taker_payment_spend = match state_machine + .taker_coin + .sign_and_broadcast_taker_payment_spend( + &tx_preimage, + &gen_args, + state_machine.secret.as_slice(), + &unique_data, + ) + .await + { + Ok(tx) => tx, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::TakerPaymentSpendBroadcastFailed(format!("{:?}", e)), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + info!( + "Spent taker payment {} tx {:02x} during swap {}", + state_machine.taker_coin.ticker(), + taker_payment_spend.tx_hash(), + state_machine.uuid + ); + let next_state = TakerPaymentSpent { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment, + taker_payment: self.taker_payment, + taker_payment_spend: TransactionIdentifier { + tx_hex: taker_payment_spend.tx_hex().into(), + tx_hash: taker_payment_spend.tx_hash(), + }, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState + for TakerPaymentConfirmed +{ + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::TakerPaymentConfirmed { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment.clone(), + taker_payment: self.taker_payment.clone(), + } + } +} + +struct TakerPaymentSpent { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_spend: TransactionIdentifier, +} + +impl TransitionFrom> + for TakerPaymentSpent +{ +} + +#[async_trait] +impl State + for TakerPaymentSpent +{ + type StateMachine = MakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + Self::change_state(Completed::new(), state_machine).await + } +} + +impl StorableState for TakerPaymentSpent { + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::TakerPaymentSpent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment.clone(), + taker_payment: self.taker_payment.clone(), + taker_payment_spend: self.taker_payment_spend.clone(), + } + } +} + +struct Aborted { + maker_coin: PhantomData, + taker_coin: PhantomData, + reason: String, +} + +impl Aborted { + fn new(reason: String) -> Aborted { + Aborted { + maker_coin: Default::default(), + taker_coin: Default::default(), + reason, + } + } +} + +#[async_trait] +impl LastState for Aborted { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed( + self: Box, + state_machine: &mut Self::StateMachine, + ) -> ::Result { + warn!("Swap {} was aborted with reason {}", state_machine.uuid, self.reason); + } +} + +impl StorableState for Aborted { + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::Aborted { + reason: self.reason.clone(), + } + } +} + +impl TransitionFrom> for Aborted {} +impl TransitionFrom> for Aborted {} +impl TransitionFrom> + for Aborted +{ +} +impl TransitionFrom> + for Aborted +{ +} + +struct Completed { + maker_coin: PhantomData, + taker_coin: PhantomData, +} + +impl Completed { + fn new() -> Completed { + Completed { + maker_coin: Default::default(), + taker_coin: Default::default(), + } + } +} + +impl StorableState for Completed { + type StateMachine = MakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + MakerSwapEvent::Completed + } +} + +#[async_trait] +impl LastState for Completed { + type StateMachine = MakerSwapStateMachine; + + async fn on_changed( + self: Box, + state_machine: &mut Self::StateMachine, + ) -> ::Result { + info!("Swap {} has been completed", state_machine.uuid); + } +} + +impl TransitionFrom> for Completed {} diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2.proto b/mm2src/mm2_main/src/lp_swap/swap_v2.proto new file mode 100644 index 0000000000..5ef75bc241 --- /dev/null +++ b/mm2src/mm2_main/src/lp_swap/swap_v2.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; + +package komodefi.swap_v2.pb; + +message SignedMessage { + bytes from = 1; + bytes signature = 2; + bytes payload = 3; +} + +message MakerNegotiation { + uint64 started_at = 1; + uint64 payment_locktime = 2; + bytes secret_hash = 3; + bytes maker_coin_htlc_pub = 4; + bytes taker_coin_htlc_pub = 5; + optional bytes maker_coin_swap_contract = 6; + optional bytes taker_coin_swap_contract = 7; +} + +message Abort { + string reason = 1; +} + +message TakerNegotiationData { + uint64 started_at = 1; + uint64 payment_locktime = 2; + // add bytes secret_hash = 3 if required + bytes maker_coin_htlc_pub = 4; + bytes taker_coin_htlc_pub = 5; + optional bytes maker_coin_swap_contract = 6; + optional bytes taker_coin_swap_contract = 7; +} + +message TakerNegotiation { + oneof action { + TakerNegotiationData continue = 1; + Abort abort = 2; + } +} + +message MakerNegotiated { + bool negotiated = 1; + // used when negotiated is false + optional string reason = 2; +} + +message TakerPaymentInfo { + bytes tx_bytes = 1; + optional bytes next_step_instructions = 2; +} + +message MakerPaymentInfo { + bytes tx_bytes = 1; + optional bytes next_step_instructions = 2; +} + +message TakerPaymentSpendPreimage { + bytes signature = 1; + optional bytes tx_preimage = 2; +} + +message SwapMessage { + oneof inner { + MakerNegotiation maker_negotiation = 1; + TakerNegotiation taker_negotiation = 2; + MakerNegotiated maker_negotiated = 3; + TakerPaymentInfo taker_payment_info = 4; + MakerPaymentInfo maker_payment_info = 5; + TakerPaymentSpendPreimage taker_payment_spend_preimage = 6; + } +} diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index f85faf2bae..bd0d0f564f 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -8,16 +8,17 @@ use coins::{CanRefundHtlc, ConfirmPaymentInput, FoundSwapTxSpend, MmCoinEnum, Re WatcherValidatePaymentInput, WatcherValidateTakerFeeInput}; use common::executor::{AbortSettings, SpawnAbortable, Timer}; use common::log::{debug, error, info}; -use common::state_machine::prelude::*; -use common::state_machine::StateMachineTrait; use common::{now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::MapToMmResult; use mm2_libp2p::{decode_signed, pub_sub_topic, TopicPrefix}; +use mm2_state_machine::prelude::*; +use mm2_state_machine::state_machine::StateMachineTrait; use serde::{Deserialize, Serialize}; use serde_json as json; use std::cmp::min; +use std::convert::Infallible; use std::sync::Arc; use uuid::Uuid; @@ -41,8 +42,11 @@ struct WatcherStateMachine { impl StateMachineTrait for WatcherStateMachine { type Result = (); + type Error = Infallible; } +impl StandardStateMachine for WatcherStateMachine {} + impl WatcherStateMachine { fn taker_locktime(&self) -> u64 { self.data.swap_started_at + self.data.lock_duration } @@ -244,8 +248,8 @@ impl State for ValidateTakerPayment { payment_tx: taker_payment_hex.clone(), taker_payment_refund_preimage: watcher_ctx.data.taker_payment_refund_preimage.clone(), time_lock: match std::env::var("USE_TEST_LOCKTIME") { - Ok(_) => watcher_ctx.data.swap_started_at as u32, - Err(_) => watcher_ctx.taker_locktime() as u32, + Ok(_) => watcher_ctx.data.swap_started_at, + Err(_) => watcher_ctx.taker_locktime(), }, taker_pub: watcher_ctx.verified_pub.clone(), maker_pub: watcher_ctx.data.maker_pub.clone(), @@ -455,7 +459,7 @@ impl State for RefundTakerPayment { swap_contract_address: &None, secret_hash: &watcher_ctx.data.secret_hash, other_pubkey: &watcher_ctx.verified_pub, - time_lock: watcher_ctx.taker_locktime() as u32, + time_lock: watcher_ctx.taker_locktime(), swap_unique_data: &[], watcher_reward: watcher_ctx.watcher_reward, }); @@ -633,7 +637,10 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri conf, watcher_reward, }; - state_machine.run(Box::new(ValidateTakerFee {})).await; + state_machine + .run(Box::new(ValidateTakerFee {})) + .await + .expect("The error of this machine is Infallible"); // This allows to move the `taker_watcher_lock` value into this async block to keep it alive // until the Swap Watcher finishes. diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index ad7e9d0ca4..c6fc2a774e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -16,7 +16,7 @@ use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed wait_for_maker_payment_conf_duration, TakerSwapWatcherData}; use coins::lp_price::fetch_swap_coins_price; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::executor::Timer; @@ -34,7 +34,7 @@ use parking_lot::Mutex as PaMutex; use primitives::hash::H264; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json, H264 as H264Json}; use serde_json::{self as json, Value as Json}; -use std::convert::{TryFrom, TryInto}; +use std::ops::Deref; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -965,7 +965,8 @@ impl TakerSwap { async fn start(&self) -> Result<(Option, Vec), String> { // do not use self.r().data here as it is not initialized at this step yet let stage = FeeApproxStage::StartSwap; - let dex_fee = dex_fee_amount_from_taker_coin(&self.taker_coin, self.maker_coin.ticker(), &self.taker_amount); + let dex_fee = + dex_fee_amount_from_taker_coin(self.taker_coin.deref(), self.maker_coin.ticker(), &self.taker_amount); let preimage_value = TradePreimageValue::Exact(self.taker_amount.to_decimal()); let fee_to_send_dex_fee_fut = self @@ -1006,8 +1007,8 @@ impl TakerSwap { }; let check_balance_f = check_balance_for_taker_swap( &self.ctx, - &self.taker_coin, - &self.maker_coin, + self.taker_coin.deref(), + self.maker_coin.deref(), self.taker_amount.clone(), Some(&self.uuid), Some(params), @@ -1241,7 +1242,7 @@ impl TakerSwap { } let fee_amount = - dex_fee_amount_from_taker_coin(&self.taker_coin, &self.r().data.maker_coin, &self.taker_amount); + dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &self.taker_amount); let fee_tx = self .taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), self.uuid.as_bytes()) @@ -1396,7 +1397,7 @@ impl TakerSwap { let validate_input = ValidatePaymentInput { payment_tx: self.r().maker_payment.clone().unwrap().tx_hex.0, time_lock_duration: self.r().data.lock_duration, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pub: self.r().other_maker_coin_htlc_pub.to_vec(), secret_hash: self.r().secret_hash.0.to_vec(), amount: self.maker_amount.to_decimal(), @@ -1463,7 +1464,7 @@ impl TakerSwap { let unique_data = self.unique_swap_data(); let f = self.taker_coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: self.r().data.taker_payment_lock as u32, + time_lock: self.r().data.taker_payment_lock, other_pub: self.r().other_taker_coin_htlc_pub.as_slice(), secret_hash: &self.r().secret_hash.0, search_from_block: self.r().data.taker_coin_start_block, @@ -1505,8 +1506,8 @@ impl TakerSwap { Some(tx) => tx, None => { let time_lock = match std::env::var("USE_TEST_LOCKTIME") { - Ok(_) => self.r().data.started_at as u32, - Err(_) => self.r().data.taker_payment_lock as u32, + Ok(_) => self.r().data.started_at, + Err(_) => self.r().data.taker_payment_lock, }; let payment_fut = self.taker_coin.send_taker_payment(SendPaymentArgs { time_lock_duration: self.r().data.lock_duration, @@ -1555,15 +1556,15 @@ impl TakerSwap { { let maker_payment_spend_preimage_fut = self.maker_coin.create_maker_payment_spend_preimage( &self.r().maker_payment.as_ref().unwrap().tx_hex, - self.maker_payment_lock.load(Ordering::Relaxed) as u32, + self.maker_payment_lock.load(Ordering::Relaxed), self.r().other_maker_coin_htlc_pub.as_slice(), &self.r().secret_hash.0, &self.unique_swap_data()[..], ); let time_lock = match std::env::var("USE_TEST_LOCKTIME") { - Ok(_) => try_s!(u32::try_from(self.r().data.started_at)), - Err(_) => try_s!(u32::try_from(self.r().data.taker_payment_lock)), + Ok(_) => self.r().data.started_at, + Err(_) => self.r().data.taker_payment_lock, }; let taker_payment_refund_preimage_fut = self.taker_coin.create_taker_payment_refund_preimage( &transaction.tx_hex(), @@ -1736,7 +1737,7 @@ impl TakerSwap { } let spend_fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { other_payment_tx: &self.r().maker_payment.clone().unwrap().tx_hex, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pubkey: &*self.r().other_maker_coin_htlc_pub, secret: &self.r().secret.0, secret_hash: &self.r().secret_hash.0, @@ -1832,19 +1833,11 @@ impl TakerSwap { let secret_hash = self.r().secret_hash.clone(); let swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); let watcher_reward = self.r().watcher_reward; - let time_lock: u32 = match locktime.try_into() { - Ok(t) => t, - Err(e) => { - return Ok((Some(TakerSwapCommand::Finish), vec![ - TakerSwapEvent::TakerPaymentRefundFailed(ERRL!("!locktime.try_into: {}", e.to_string()).into()), - ])) - }, - }; let refund_result = self .taker_coin .send_taker_refunds_payment(RefundPaymentArgs { payment_tx: &taker_payment, - time_lock, + time_lock: locktime, other_pubkey: other_taker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, swap_contract_address: &swap_contract_address, @@ -2015,7 +2008,7 @@ impl TakerSwap { // validate that maker payment is not spent () => { let search_input = SearchForSwapTxSpendInput { - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pub: other_maker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, tx: &maker_payment, @@ -2055,7 +2048,7 @@ impl TakerSwap { let maybe_sent = try_s!( self.taker_coin .check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: taker_payment_lock as u32, + time_lock: taker_payment_lock, other_pub: other_taker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, search_from_block: taker_coin_start_block, @@ -2084,7 +2077,7 @@ impl TakerSwap { let fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { other_payment_tx: &maker_payment, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pubkey: other_maker_coin_htlc_pub.as_slice(), secret: &secret, secret_hash: &secret_hash, @@ -2117,7 +2110,7 @@ impl TakerSwap { } let search_input = SearchForSwapTxSpendInput { - time_lock: taker_payment_lock as u32, + time_lock: taker_payment_lock, other_pub: other_taker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, tx: &taker_payment, @@ -2142,7 +2135,7 @@ impl TakerSwap { let fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { other_payment_tx: &maker_payment, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed), other_pubkey: other_maker_coin_htlc_pub.as_slice(), secret: &secret, secret_hash: &secret_hash, @@ -2190,7 +2183,7 @@ impl TakerSwap { let fut = self.taker_coin.send_taker_refunds_payment(RefundPaymentArgs { payment_tx: &taker_payment, - time_lock: taker_payment_lock as u32, + time_lock: taker_payment_lock, other_pubkey: other_taker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, swap_contract_address: &taker_coin_swap_contract_address, @@ -2230,7 +2223,7 @@ impl AtomicSwap for TakerSwap { // if taker fee is not sent yet it must be virtually locked let taker_fee_amount = - dex_fee_amount_from_taker_coin(&self.taker_coin, &self.r().data.maker_coin, &self.taker_amount); + dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &self.taker_amount); let trade_fee = self.r().data.fee_to_send_taker_fee.clone().map(TradeFee::from); if self.r().taker_fee.is_none() { result.push(LockedAmount { @@ -2288,8 +2281,8 @@ pub struct TakerSwapPreparedParams { pub async fn check_balance_for_taker_swap( ctx: &MmArc, - my_coin: &MmCoinEnum, - other_coin: &MmCoinEnum, + my_coin: &dyn MmCoin, + other_coin: &dyn MmCoin, volume: MmNumber, swap_uuid: Option<&Uuid>, prepared_params: Option, @@ -2386,7 +2379,7 @@ pub async fn taker_swap_trade_preimage( TakerAction::Buy => rel_amount.clone(), }; - let dex_amount = dex_fee_amount_from_taker_coin(&my_coin, other_coin_ticker, &my_coin_volume); + let dex_amount = dex_fee_amount_from_taker_coin(my_coin.deref(), other_coin_ticker, &my_coin_volume); let taker_fee = TradeFee { coin: my_coin_ticker.to_owned(), amount: dex_amount.clone(), @@ -2417,8 +2410,8 @@ pub async fn taker_swap_trade_preimage( }; check_balance_for_taker_swap( ctx, - &my_coin, - &other_coin, + my_coin.deref(), + other_coin.deref(), my_coin_volume.clone(), None, Some(prepared_params), @@ -2532,7 +2525,7 @@ pub async fn calc_max_taker_vol( let max_vol = if my_coin == max_trade_fee.coin { // second case let max_possible_2 = &max_possible - &max_trade_fee.amount; - let max_dex_fee = dex_fee_amount_from_taker_coin(coin, other_coin, &max_possible_2); + let max_dex_fee = dex_fee_amount_from_taker_coin(coin.deref(), other_coin, &max_possible_2); let max_fee_to_send_taker_fee = coin .get_fee_to_send_taker_fee(max_dex_fee.to_decimal(), stage) .await diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs new file mode 100644 index 0000000000..e4ae68b89b --- /dev/null +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -0,0 +1,951 @@ +use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; +use crate::mm2::lp_network::subscribe_to_topic; +use crate::mm2::lp_swap::swap_v2_pb::*; +use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier}; +use async_trait::async_trait; +use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, SendCombinedTakerPaymentArgs, + SpendPaymentArgs, SwapOpsV2, WaitForHTLCTxSpendArgs}; +use common::log::{debug, info, warn}; +use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; +use keys::KeyPair; +use mm2_core::mm_ctx::MmArc; +use mm2_number::{BigDecimal, MmNumber}; +use mm2_state_machine::prelude::*; +use mm2_state_machine::storable_state_machine::*; +use rpc::v1::types::Bytes as BytesJson; +use std::collections::HashMap; +use std::marker::PhantomData; +use uuid::Uuid; + +// This is needed to have Debug on messages +#[allow(unused_imports)] use prost::Message; + +/// Represents events produced by taker swap states. +#[derive(Debug, PartialEq)] +pub enum TakerSwapEvent { + /// Swap has been successfully initialized. + Initialized { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + }, + /// Negotiated swap data with maker. + Negotiated { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + secret_hash: BytesJson, + }, + /// Sent taker payment. + TakerPaymentSent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment: TransactionIdentifier, + secret_hash: BytesJson, + }, + /// Something went wrong, so taker payment refund is required. + TakerPaymentRefundRequired { + taker_payment: TransactionIdentifier, + secret_hash: BytesJson, + }, + /// Both payments are confirmed on-chain + BothPaymentsSentAndConfirmed { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + secret_hash: BytesJson, + }, + /// Maker spent taker's payment and taker discovered the tx on-chain. + TakerPaymentSpent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_spend: TransactionIdentifier, + secret: BytesJson, + }, + /// Taker spent maker's payment. + MakerPaymentSpent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_spend: TransactionIdentifier, + maker_payment_spend: TransactionIdentifier, + }, + /// Swap has been aborted before taker payment was sent. + Aborted { reason: String }, + /// Swap completed successfully. + Completed, +} + +/// Represents errors that can be produced by [`TakerSwapStateMachine`] run. +#[derive(Debug, Display)] +pub enum TakerSwapStateMachineError {} + +/// Dummy storage for taker swap events (used temporary). +#[derive(Default)] +pub struct DummyTakerSwapStorage { + events: HashMap>, +} + +#[async_trait] +impl StateMachineStorage for DummyTakerSwapStorage { + type MachineId = Uuid; + type Event = TakerSwapEvent; + type Error = TakerSwapStateMachineError; + + async fn store_event(&mut self, id: Self::MachineId, event: Self::Event) -> Result<(), Self::Error> { + self.events.entry(id).or_insert_with(Vec::new).push(event); + Ok(()) + } + + async fn get_unfinished(&self) -> Result, Self::Error> { + Ok(self.events.keys().copied().collect()) + } + + async fn mark_finished(&mut self, _id: Self::MachineId) -> Result<(), Self::Error> { Ok(()) } +} + +/// Represents the state machine for taker's side of the Trading Protocol Upgrade swap (v2). +pub struct TakerSwapStateMachine { + /// MM2 context. + pub ctx: MmArc, + /// Storage. + pub storage: DummyTakerSwapStorage, + /// The timestamp when the swap was started. + pub started_at: u64, + /// The duration of HTLC timelock in seconds. + pub lock_duration: u64, + /// Maker coin. + pub maker_coin: MakerCoin, + /// The amount swapped by maker. + pub maker_volume: MmNumber, + /// Taker coin. + pub taker_coin: TakerCoin, + /// The amount swapped by taker. + pub taker_volume: MmNumber, + /// DEX fee amount. + pub dex_fee: MmNumber, + /// Premium amount, which might be paid to maker as additional reward. + pub taker_premium: MmNumber, + /// Swap transactions' confirmations settings. + pub conf_settings: SwapConfirmationsSettings, + /// UUID of the swap. + pub uuid: Uuid, + /// The gossipsub topic used for peer-to-peer communication in swap process. + pub p2p_topic: String, + /// If Some, used to sign P2P messages of this swap. + pub p2p_keypair: Option, +} + +impl TakerSwapStateMachine { + fn maker_payment_conf_timeout(&self) -> u64 { self.started_at + self.lock_duration * 2 / 3 } + + fn taker_payment_locktime(&self) -> u64 { self.started_at + self.lock_duration } + + fn unique_data(&self) -> Vec { self.uuid.as_bytes().to_vec() } +} + +impl StorableStateMachine + for TakerSwapStateMachine +{ + type Storage = DummyTakerSwapStorage; + type Result = (); + + fn storage(&mut self) -> &mut Self::Storage { &mut self.storage } + + fn id(&self) -> ::MachineId { self.uuid } + + fn restore_from_storage( + _id: ::MachineId, + _storage: Self::Storage, + ) -> Result, ::Error> { + todo!() + } +} + +/// Represents a state used to start a new taker swap. +pub struct Initialize { + maker_coin: PhantomData, + taker_coin: PhantomData, +} + +impl Default for Initialize { + fn default() -> Self { + Initialize { + maker_coin: Default::default(), + taker_coin: Default::default(), + } + } +} + +impl InitialState for Initialize { + type StateMachine = TakerSwapStateMachine; +} + +#[async_trait] +impl State + for Initialize +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + subscribe_to_topic(&state_machine.ctx, state_machine.p2p_topic.clone()); + let swap_ctx = SwapsContext::from_ctx(&state_machine.ctx).expect("SwapsContext::from_ctx should not fail"); + swap_ctx.init_msg_v2_store(state_machine.uuid, bits256::default()); + + let maker_coin_start_block = match state_machine.maker_coin.current_block().compat().await { + Ok(b) => b, + Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + }; + + let taker_coin_start_block = match state_machine.taker_coin.current_block().compat().await { + Ok(b) => b, + Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + }; + + if let Err(e) = check_balance_for_taker_swap( + &state_machine.ctx, + &state_machine.taker_coin, + &state_machine.maker_coin, + state_machine.taker_volume.clone(), + Some(&state_machine.uuid), + None, + FeeApproxStage::StartSwap, + ) + .await + { + return Self::change_state(Aborted::new(e.to_string()), state_machine).await; + } + + info!("Taker swap {} has successfully started", state_machine.uuid); + let next_state = Initialized { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block, + taker_coin_start_block, + }; + Self::change_state(next_state, state_machine).await + } +} + +struct Initialized { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, +} + +impl TransitionFrom> for Initialized {} + +impl StorableState for Initialized { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::Initialized { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + } + } +} + +#[async_trait] +impl State + for Initialized +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.maker_negotiation.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + + let maker_negotiation = match recv_fut.await { + Ok(d) => d, + Err(e) => { + let next_state = Aborted::new(format!("Failed to receive MakerNegotiation: {}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + + debug!("Received maker negotiation message {:?}", maker_negotiation); + + let unique_data = state_machine.unique_data(); + let taker_negotiation = TakerNegotiation { + action: Some(taker_negotiation::Action::Continue(TakerNegotiationData { + started_at: state_machine.started_at, + payment_locktime: state_machine.taker_payment_locktime(), + maker_coin_htlc_pub: state_machine.maker_coin.derive_htlc_pubkey(&unique_data), + taker_coin_htlc_pub: state_machine.taker_coin.derive_htlc_pubkey(&unique_data), + maker_coin_swap_contract: state_machine.maker_coin.swap_contract_address().map(|bytes| bytes.0), + taker_coin_swap_contract: state_machine.taker_coin.swap_contract_address().map(|bytes| bytes.0), + })), + }; + + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::TakerNegotiation(taker_negotiation)), + }; + let abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + NEGOTIATE_SEND_INTERVAL, + state_machine.p2p_keypair, + ); + + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.maker_negotiated.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + + let maker_negotiated = match recv_fut.await { + Ok(d) => d, + Err(e) => { + let next_state = Aborted::new(format!("Failed to receive MakerNegotiated: {}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + + debug!("Received maker negotiated message {:?}", maker_negotiated); + if !maker_negotiated.negotiated { + let next_state = Aborted::new(format!( + "Maker did not negotiate with the reason: {}", + maker_negotiated.reason.unwrap_or_default() + )); + return Self::change_state(next_state, state_machine).await; + } + + let next_state = Negotiated { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + secret_hash: maker_negotiation.secret_hash, + maker_payment_locktime: maker_negotiation.payment_locktime, + maker_coin_htlc_pub_from_maker: maker_negotiation.maker_coin_htlc_pub, + taker_coin_htlc_pub_from_maker: maker_negotiation.taker_coin_htlc_pub, + maker_coin_swap_contract: maker_negotiation.maker_coin_swap_contract, + taker_coin_swap_contract: maker_negotiation.taker_coin_swap_contract, + }; + Self::change_state(next_state, state_machine).await + } +} + +struct Negotiated { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + secret_hash: Vec, + maker_payment_locktime: u64, + maker_coin_htlc_pub_from_maker: Vec, + taker_coin_htlc_pub_from_maker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, +} + +impl TransitionFrom> for Negotiated {} + +#[async_trait] +impl State for Negotiated { + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let args = SendCombinedTakerPaymentArgs { + time_lock: state_machine.taker_payment_locktime(), + secret_hash: &self.secret_hash, + other_pub: &self.taker_coin_htlc_pub_from_maker, + dex_fee_amount: state_machine.dex_fee.to_decimal(), + premium_amount: BigDecimal::from(0), + trading_amount: state_machine.taker_volume.to_decimal(), + swap_unique_data: &state_machine.unique_data(), + }; + + let taker_payment = match state_machine.taker_coin.send_combined_taker_payment(args).await { + Ok(tx) => tx, + Err(e) => { + let next_state = Aborted::new(format!("Failed to send taker payment {:?}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + info!( + "Sent combined taker payment {} tx {:02x} during swap {}", + state_machine.taker_coin.ticker(), + taker_payment.tx_hash(), + state_machine.uuid + ); + + let next_state = TakerPaymentSent { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment: TransactionIdentifier { + tx_hex: taker_payment.tx_hex().into(), + tx_hash: taker_payment.tx_hash(), + }, + secret_hash: self.secret_hash, + maker_payment_locktime: self.maker_payment_locktime, + maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker, + taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker, + maker_coin_swap_contract: self.maker_coin_swap_contract, + taker_coin_swap_contract: self.taker_coin_swap_contract, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState for Negotiated { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::Negotiated { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + secret_hash: Default::default(), + } + } +} + +struct TakerPaymentSent { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment: TransactionIdentifier, + secret_hash: Vec, + maker_payment_locktime: u64, + maker_coin_htlc_pub_from_maker: Vec, + taker_coin_htlc_pub_from_maker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, +} + +impl TransitionFrom> for TakerPaymentSent {} + +#[async_trait] +impl State for TakerPaymentSent { + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let taker_payment_info = TakerPaymentInfo { + tx_bytes: self.taker_payment.tx_hex.clone().0, + next_step_instructions: None, + }; + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::TakerPaymentInfo(taker_payment_info)), + }; + let abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + 600., + state_machine.p2p_keypair, + ); + + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.maker_payment.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + + let maker_payment_info = match recv_fut.await { + Ok(p) => p, + Err(e) => { + let next_state = TakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + taker_payment: self.taker_payment, + secret_hash: self.secret_hash, + reason: TakerPaymentRefundReason::DidNotReceiveMakerPayment(e), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + debug!("Received maker payment info message {:?}", maker_payment_info); + + let input = ConfirmPaymentInput { + payment_tx: maker_payment_info.tx_bytes.clone(), + confirmations: state_machine.conf_settings.taker_coin_confs, + requires_nota: state_machine.conf_settings.taker_coin_nota, + wait_until: state_machine.maker_payment_conf_timeout(), + check_every: 10, + }; + + if let Err(e) = state_machine.maker_coin.wait_for_confirmations(input).compat().await { + let next_state = TakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + taker_payment: self.taker_payment, + secret_hash: self.secret_hash, + reason: TakerPaymentRefundReason::MakerPaymentNotConfirmedInTime(e), + }; + return Self::change_state(next_state, state_machine).await; + } + + let next_state = MakerPaymentConfirmed { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: TransactionIdentifier { + tx_hex: maker_payment_info.tx_bytes.into(), + tx_hash: Default::default(), + }, + taker_payment: self.taker_payment, + secret_hash: self.secret_hash, + maker_payment_locktime: self.maker_payment_locktime, + maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker, + taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker, + maker_coin_swap_contract: self.maker_coin_swap_contract, + taker_coin_swap_contract: self.taker_coin_swap_contract, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState for TakerPaymentSent { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerPaymentSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment: self.taker_payment.clone(), + secret_hash: self.secret_hash.clone().into(), + } + } +} + +#[derive(Debug)] +enum TakerPaymentRefundReason { + DidNotReceiveMakerPayment(String), + MakerPaymentNotConfirmedInTime(String), + FailedToGenerateSpendPreimage(String), + MakerDidNotSpendInTime(String), +} + +struct TakerPaymentRefundRequired { + maker_coin: PhantomData, + taker_coin: PhantomData, + taker_payment: TransactionIdentifier, + secret_hash: Vec, + reason: TakerPaymentRefundReason, +} + +impl TransitionFrom> + for TakerPaymentRefundRequired +{ +} +impl TransitionFrom> + for TakerPaymentRefundRequired +{ +} + +#[async_trait] +impl State + for TakerPaymentRefundRequired +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + warn!( + "Entered TakerPaymentRefundRequired state for swap {} with reason {:?}", + state_machine.uuid, self.reason + ); + unimplemented!() + } +} + +impl StorableState + for TakerPaymentRefundRequired +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerPaymentRefundRequired { + taker_payment: self.taker_payment.clone(), + secret_hash: self.secret_hash.clone().into(), + } + } +} + +struct MakerPaymentConfirmed { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + secret_hash: Vec, + maker_payment_locktime: u64, + maker_coin_htlc_pub_from_maker: Vec, + taker_coin_htlc_pub_from_maker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, +} + +impl TransitionFrom> + for MakerPaymentConfirmed +{ +} + +#[async_trait] +impl State for MakerPaymentConfirmed { + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let unique_data = state_machine.unique_data(); + + let args = GenTakerPaymentSpendArgs { + taker_tx: &self.taker_payment.tx_hex.0, + time_lock: state_machine.taker_payment_locktime(), + secret_hash: &self.secret_hash, + maker_pub: &self.maker_coin_htlc_pub_from_maker, + taker_pub: &state_machine.taker_coin.derive_htlc_pubkey(&unique_data), + dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee_amount: state_machine.dex_fee.to_decimal(), + premium_amount: Default::default(), + trading_amount: state_machine.taker_volume.to_decimal(), + }; + + let preimage = match state_machine + .taker_coin + .gen_taker_payment_spend_preimage(&args, &unique_data) + .await + { + Ok(p) => p, + Err(e) => { + let next_state = TakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + taker_payment: self.taker_payment, + secret_hash: self.secret_hash, + reason: TakerPaymentRefundReason::FailedToGenerateSpendPreimage(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + let preimage_msg = TakerPaymentSpendPreimage { + signature: preimage.signature, + tx_preimage: if !preimage.preimage.is_empty() { + Some(preimage.preimage) + } else { + None + }, + }; + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::TakerPaymentSpendPreimage(preimage_msg)), + }; + + let _abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + 600., + state_machine.p2p_keypair, + ); + + let wait_args = WaitForHTLCTxSpendArgs { + tx_bytes: &self.taker_payment.tx_hex.0, + secret_hash: &self.secret_hash, + wait_until: state_machine.taker_payment_locktime(), + from_block: self.taker_coin_start_block, + swap_contract_address: &self.taker_coin_swap_contract.clone().map(|bytes| bytes.into()), + check_every: 10.0, + watcher_reward: false, + }; + let taker_payment_spend = match state_machine + .taker_coin + .wait_for_htlc_tx_spend(wait_args) + .compat() + .await + { + Ok(tx) => tx, + Err(e) => { + let next_state = TakerPaymentRefundRequired { + maker_coin: Default::default(), + taker_coin: Default::default(), + taker_payment: self.taker_payment, + secret_hash: self.secret_hash, + reason: TakerPaymentRefundReason::MakerDidNotSpendInTime(format!("{:?}", e)), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + info!( + "Found taker payment spend {} tx {:02x} during swap {}", + state_machine.taker_coin.ticker(), + taker_payment_spend.tx_hash(), + state_machine.uuid + ); + + let next_state = TakerPaymentSpent { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment, + taker_payment: self.taker_payment, + taker_payment_spend: TransactionIdentifier { + tx_hex: taker_payment_spend.tx_hex().into(), + tx_hash: taker_payment_spend.tx_hash(), + }, + secret_hash: self.secret_hash, + maker_payment_locktime: self.maker_payment_locktime, + maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker, + taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker, + maker_coin_swap_contract: self.maker_coin_swap_contract, + taker_coin_swap_contract: self.taker_coin_swap_contract, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState + for MakerPaymentConfirmed +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::BothPaymentsSentAndConfirmed { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment.clone(), + taker_payment: self.taker_payment.clone(), + secret_hash: self.secret_hash.clone().into(), + } + } +} + +#[allow(dead_code)] +struct TakerPaymentSpent { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_spend: TransactionIdentifier, + secret_hash: Vec, + maker_payment_locktime: u64, + maker_coin_htlc_pub_from_maker: Vec, + taker_coin_htlc_pub_from_maker: Vec, + maker_coin_swap_contract: Option>, + taker_coin_swap_contract: Option>, +} + +impl TransitionFrom> + for TakerPaymentSpent +{ +} + +#[async_trait] +impl State + for TakerPaymentSpent +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let secret = match state_machine + .taker_coin + .extract_secret(&self.secret_hash, &self.taker_payment_spend.tx_hex.0, false) + .await + { + Ok(s) => s, + Err(e) => { + let next_state = Aborted::new(format!("Couldn't extract secret from taker payment spend {}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + + let args = SpendPaymentArgs { + other_payment_tx: &self.maker_payment.tx_hex.0, + time_lock: self.maker_payment_locktime, + other_pubkey: &self.maker_coin_htlc_pub_from_maker, + secret: &secret, + secret_hash: &self.secret_hash, + swap_contract_address: &self.maker_coin_swap_contract.clone().map(|bytes| bytes.into()), + swap_unique_data: &state_machine.unique_data(), + watcher_reward: false, + }; + let maker_payment_spend = match state_machine + .maker_coin + .send_taker_spends_maker_payment(args) + .compat() + .await + { + Ok(tx) => tx, + Err(e) => { + let next_state = Aborted::new(format!("Failed to spend maker payment {:?}", e)); + return Self::change_state(next_state, state_machine).await; + }, + }; + info!( + "Spent maker payment {} tx {:02x} during swap {}", + state_machine.maker_coin.ticker(), + maker_payment_spend.tx_hash(), + state_machine.uuid + ); + let next_state = MakerPaymentSpent { + maker_coin: Default::default(), + taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment, + taker_payment: self.taker_payment, + taker_payment_spend: self.taker_payment_spend, + maker_payment_spend: TransactionIdentifier { + tx_hex: maker_payment_spend.tx_hex().into(), + tx_hash: maker_payment_spend.tx_hash(), + }, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl StorableState for TakerPaymentSpent { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerPaymentSpent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment.clone(), + taker_payment: self.taker_payment.clone(), + taker_payment_spend: self.taker_payment_spend.clone(), + secret: Vec::new().into(), + } + } +} + +struct MakerPaymentSpent { + maker_coin: PhantomData, + taker_coin: PhantomData, + maker_coin_start_block: u64, + taker_coin_start_block: u64, + maker_payment: TransactionIdentifier, + taker_payment: TransactionIdentifier, + taker_payment_spend: TransactionIdentifier, + maker_payment_spend: TransactionIdentifier, +} + +impl TransitionFrom> + for MakerPaymentSpent +{ +} + +impl StorableState for MakerPaymentSpent { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::MakerPaymentSpent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + maker_payment: self.maker_payment.clone(), + taker_payment: self.taker_payment.clone(), + taker_payment_spend: self.taker_payment_spend.clone(), + maker_payment_spend: self.maker_payment_spend.clone(), + } + } +} + +#[async_trait] +impl State + for MakerPaymentSpent +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + Self::change_state(Completed::new(), state_machine).await + } +} + +struct Aborted { + maker_coin: PhantomData, + taker_coin: PhantomData, + reason: String, +} + +impl Aborted { + fn new(reason: String) -> Aborted { + Aborted { + maker_coin: Default::default(), + taker_coin: Default::default(), + reason, + } + } +} + +#[async_trait] +impl LastState for Aborted { + type StateMachine = TakerSwapStateMachine; + + async fn on_changed( + self: Box, + state_machine: &mut Self::StateMachine, + ) -> ::Result { + warn!("Swap {} was aborted with reason {}", state_machine.uuid, self.reason); + } +} + +impl StorableState for Aborted { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::Aborted { + reason: self.reason.clone(), + } + } +} + +impl TransitionFrom> for Aborted {} +impl TransitionFrom> for Aborted {} +impl TransitionFrom> for Aborted {} +impl TransitionFrom> for Aborted {} + +struct Completed { + maker_coin: PhantomData, + taker_coin: PhantomData, +} + +impl Completed { + fn new() -> Completed { + Completed { + maker_coin: Default::default(), + taker_coin: Default::default(), + } + } +} + +impl StorableState for Completed { + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::Completed + } +} + +#[async_trait] +impl LastState for Completed { + type StateMachine = TakerSwapStateMachine; + + async fn on_changed( + self: Box, + state_machine: &mut Self::StateMachine, + ) -> ::Result { + info!("Swap {} has been completed", state_machine.uuid); + } +} + +impl TransitionFrom> for Completed {} diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 39dbcbe6e6..3ddddf6801 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -69,8 +69,10 @@ pub const UTXO_ASSET_DOCKER_IMAGE: &str = "docker.io/artempikulin/testblockchain pub const QTUM_ADDRESS_LABEL: &str = "MM2_ADDRESS_LABEL"; +/// Ticker of MYCOIN dockerized blockchain. pub const MYCOIN: &str = "MYCOIN"; -pub const _MYCOIN1: &str = "MYCOIN1"; +/// Ticker of MYCOIN1 dockerized blockchain. +pub const MYCOIN1: &str = "MYCOIN1"; pub trait CoinDockerOps { fn rpc_client(&self) -> &UtxoRpcClientEnum; 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 922bff0623..a6f3f2cd7f 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -8,7 +8,7 @@ use coins::utxo::rpc_clients::UnspentInfo; use coins::utxo::{GetUtxoListOps, UtxoCommonOps}; use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; -use common::{block_on, now_sec_u32, wait_until_sec}; +use common::{block_on, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use futures01::Future; use mm2_number::{BigDecimal, MmNumber}; @@ -28,7 +28,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { let (_ctx, coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let my_public_key = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, @@ -113,7 +113,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { let (_ctx, coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let my_public_key = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, @@ -180,7 +180,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { let my_pubkey = coin.my_public_key().unwrap(); let secret_hash = dhash160(&secret); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, @@ -250,7 +250,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { let secret = [0; 32]; let my_pubkey = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let secret_hash = dhash160(&secret); let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, @@ -322,7 +322,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { let secret = [0; 32]; let my_pubkey = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let mut unspents = vec![]; let mut sent_tx = vec![]; for i in 0..100 { diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index dc6f915948..bba1a82e3c 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -10,7 +10,6 @@ use coins::{CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, Found RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionEnum, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::debug; -use common::now_sec_u32; use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; use ethereum_types::H160; @@ -176,7 +175,7 @@ fn test_taker_spends_maker_payment() { assert_eq!(maker_old_balance, BigDecimal::from(10)); assert_eq!(taker_old_balance, BigDecimal::from(1)); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap().to_vec(); let taker_pub = taker_coin.my_public_key().unwrap().to_vec(); let secret = &[1; 32]; @@ -281,7 +280,7 @@ fn test_maker_spends_taker_payment() { assert_eq!(maker_old_balance, BigDecimal::from(10)); assert_eq!(taker_old_balance, BigDecimal::from(10)); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap().to_vec(); let taker_pub = taker_coin.my_public_key().unwrap().to_vec(); let secret = &[1; 32]; @@ -377,7 +376,7 @@ fn test_maker_refunds_payment() { let expected_balance = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance, BigDecimal::from(10)); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); @@ -447,7 +446,7 @@ fn test_taker_refunds_payment() { let expected_balance = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance, BigDecimal::from(10)); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let maker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); @@ -514,7 +513,7 @@ fn test_taker_refunds_payment() { #[test] fn test_check_if_my_payment_sent() { let (_ctx, coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); @@ -569,7 +568,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); let search_from_block = maker_coin.current_block().wait().expect("!current_block"); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap(); let taker_pub = taker_coin.my_public_key().unwrap(); let secret = &[1; 32]; @@ -652,7 +651,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let search_from_block = maker_coin.current_block().wait().expect("!current_block"); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret = &[1; 32]; let secret_hash = &*dhash160(secret); @@ -730,7 +729,7 @@ fn test_search_for_swap_tx_spend_not_spent() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let search_from_block = maker_coin.current_block().wait().expect("!current_block"); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret = &[1; 32]; let secret_hash = &*dhash160(secret); @@ -786,7 +785,7 @@ fn test_wait_for_tx_spend() { let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); let from_block = maker_coin.current_block().wait().expect("!current_block"); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap(); let taker_pub = taker_coin.my_public_key().unwrap(); let secret = &[1; 32]; @@ -1108,7 +1107,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ block_on(mm.stop()).unwrap(); - let timelock = now_sec_u32() - 200; + let timelock = now_sec() - 200; let secret_hash = &[0; 20]; let dex_fee_amount = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_dex_fee_threshold); @@ -1515,7 +1514,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { let (_ctx, coin, _) = generate_segwit_qtum_coin_with_random_privkey("QTUM", 1000u64.into(), Some(0)); let my_public_key = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let maker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock, @@ -1581,7 +1580,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { let (_ctx, coin, _) = generate_segwit_qtum_coin_with_random_privkey("QTUM", 1000u64.into(), Some(0)); let my_public_key = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let taker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock, diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index a661b6cdc7..331b5b918b 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -1,61 +1,65 @@ -use crate::{generate_utxo_coin_with_random_privkey, MYCOIN}; +use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; -use coins::{GenDexFeeSpendArgs, RefundPaymentArgs, SendDexFeeWithPremiumArgs, SwapOpsV2, Transaction, TransactionEnum, - ValidateDexFeeArgs}; -use common::{block_on, now_sec_u32, DEX_FEE_ADDR_RAW_PUBKEY}; +use coins::{GenTakerPaymentSpendArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, SwapOpsV2, Transaction, + TransactionEnum, ValidateTakerPaymentArgs}; +use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use mm2_test_helpers::for_tests::{enable_native, mm_dump, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, + Mm2TestConf}; use script::{Builder, Opcode}; #[test] -fn send_and_refund_dex_fee() { +fn send_and_refund_taker_payment() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec_u32() - 1000; + let time_lock = now_sec() - 1000; let secret_hash = &[0; 20]; let other_pub = coin.my_public_key().unwrap(); - let send_args = SendDexFeeWithPremiumArgs { + let send_args = SendCombinedTakerPaymentArgs { time_lock, secret_hash, other_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), swap_unique_data: &[], }; - let dex_fee_tx = block_on(coin.send_dex_fee_with_premium(send_args)).unwrap(); - println!("{:02x}", dex_fee_tx.tx_hash()); - let dex_fee_utxo_tx = match dex_fee_tx { + let taker_payment_tx = block_on(coin.send_combined_taker_payment(send_args)).unwrap(); + println!("{:02x}", taker_payment_tx.tx_hash()); + let taker_payment_utxo_tx = match taker_payment_tx { TransactionEnum::UtxoTx(tx) => tx, unexpected => panic!("Unexpected tx {:?}", unexpected), }; // tx must have 3 outputs: actual payment, OP_RETURN containing the secret hash and change - assert_eq!(3, dex_fee_utxo_tx.outputs.len()); + assert_eq!(3, taker_payment_utxo_tx.outputs.len()); - // dex_fee_amount + premium_amount - let expected_amount = 11000000u64; - assert_eq!(expected_amount, dex_fee_utxo_tx.outputs[0].value); + // dex_fee_amount + premium_amount + trading_amount + let expected_amount = 111000000u64; + assert_eq!(expected_amount, taker_payment_utxo_tx.outputs[0].value); let expected_op_return = Builder::default() .push_opcode(Opcode::OP_RETURN) .push_data(&[0; 20]) .into_bytes(); - assert_eq!(expected_op_return, dex_fee_utxo_tx.outputs[1].script_pubkey); + assert_eq!(expected_op_return, taker_payment_utxo_tx.outputs[1].script_pubkey); - let dex_fee_bytes = dex_fee_utxo_tx.tx_hex(); + let taker_payment_bytes = taker_payment_utxo_tx.tx_hex(); - let validate_args = ValidateDexFeeArgs { - dex_fee_tx: &dex_fee_bytes, + let validate_args = ValidateTakerPaymentArgs { + taker_tx: &taker_payment_bytes, time_lock, secret_hash, other_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), swap_unique_data: &[], }; - block_on(coin.validate_dex_fee_with_premium(validate_args)).unwrap(); + block_on(coin.validate_combined_taker_payment(validate_args)).unwrap(); let refund_args = RefundPaymentArgs { - payment_tx: &dex_fee_bytes, + payment_tx: &taker_payment_bytes, time_lock, other_pubkey: coin.my_public_key().unwrap(), secret_hash: &[0; 20], @@ -64,47 +68,49 @@ fn send_and_refund_dex_fee() { watcher_reward: false, }; - let refund_tx = block_on(coin.refund_dex_fee_with_premium(refund_args)).unwrap(); + let refund_tx = block_on(coin.refund_combined_taker_payment(refund_args)).unwrap(); println!("{:02x}", refund_tx.tx_hash()); } #[test] -fn send_and_spend_dex_fee() { +fn send_and_spend_taker_payment() { let (_, taker_coin, _) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let (_, maker_coin, _) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec_u32() - 1000; + let time_lock = now_sec() - 1000; let secret = [1; 32]; let secret_hash = dhash160(&secret); - let send_args = SendDexFeeWithPremiumArgs { + let send_args = SendCombinedTakerPaymentArgs { time_lock, secret_hash: secret_hash.as_slice(), other_pub: maker_coin.my_public_key().unwrap(), dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), swap_unique_data: &[], }; - let dex_fee_tx = block_on(taker_coin.send_dex_fee_with_premium(send_args)).unwrap(); - println!("dex_fee_tx hash {:02x}", dex_fee_tx.tx_hash()); - let dex_fee_utxo_tx = match dex_fee_tx { + let taker_payment_tx = block_on(taker_coin.send_combined_taker_payment(send_args)).unwrap(); + println!("taker_payment_tx hash {:02x}", taker_payment_tx.tx_hash()); + let taker_payment_utxo_tx = match taker_payment_tx { TransactionEnum::UtxoTx(tx) => tx, unexpected => panic!("Unexpected tx {:?}", unexpected), }; - let dex_fee_bytes = dex_fee_utxo_tx.tx_hex(); - let validate_args = ValidateDexFeeArgs { - dex_fee_tx: &dex_fee_bytes, + let taker_payment_bytes = taker_payment_utxo_tx.tx_hex(); + let validate_args = ValidateTakerPaymentArgs { + taker_tx: &taker_payment_bytes, time_lock, secret_hash: secret_hash.as_slice(), other_pub: taker_coin.my_public_key().unwrap(), dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), swap_unique_data: &[], }; - block_on(maker_coin.validate_dex_fee_with_premium(validate_args)).unwrap(); + block_on(maker_coin.validate_combined_taker_payment(validate_args)).unwrap(); - let gen_preimage_args = GenDexFeeSpendArgs { - dex_fee_tx: &dex_fee_utxo_tx.tx_hex(), + let gen_preimage_args = GenTakerPaymentSpendArgs { + taker_tx: &taker_payment_utxo_tx.tx_hex(), time_lock, secret_hash: secret_hash.as_slice(), maker_pub: maker_coin.my_public_key().unwrap(), @@ -112,18 +118,58 @@ fn send_and_spend_dex_fee() { dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), }; let preimage_with_taker_sig = - block_on(taker_coin.gen_and_sign_dex_fee_spend_preimage(&gen_preimage_args, &[])).unwrap(); + block_on(taker_coin.gen_taker_payment_spend_preimage(&gen_preimage_args, &[])).unwrap(); - block_on(maker_coin.validate_dex_fee_spend_preimage(&gen_preimage_args, &preimage_with_taker_sig)).unwrap(); + block_on(maker_coin.validate_taker_payment_spend_preimage(&gen_preimage_args, &preimage_with_taker_sig)).unwrap(); - let dex_fee_spend = block_on(maker_coin.sign_and_broadcast_dex_fee_spend( + let taker_payment_spend = block_on(maker_coin.sign_and_broadcast_taker_payment_spend( &preimage_with_taker_sig, &gen_preimage_args, &secret, &[], )) .unwrap(); - println!("dex_fee_spend hash {:02x}", dex_fee_spend.tx_hash()); + println!("taker_payment_spend hash {:02x}", taker_payment_spend.tx_hash()); +} + +#[test] +fn test_v2_swap_utxo_utxo() { + let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN1, 1000.into()); + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + + let bob_conf = Mm2TestConf::seednode_trade_v2(&format!("0x{}", hex::encode(bob_priv_key)), &coins); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); + + let alice_conf = + Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob + .ip + .to_string()]); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); + + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN", &[], None))); + log!("{:?}", block_on(enable_native(&mm_alice, "MYCOIN1", &[], None))); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[(MYCOIN, MYCOIN1)], + 1.0, + 1.0, + 100., + )); + println!("{:?}", uuids); + + for uuid in uuids { + let expected_msg = format!("Swap {} has been completed", uuid); + block_on(mm_bob.wait_for_log(60., |log| log.contains(&expected_msg))).unwrap(); + block_on(mm_alice.wait_for_log(60., |log| log.contains(&expected_msg))).unwrap(); + } } diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 1936955c06..41b3bf105e 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -8,7 +8,7 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin WatcherValidateTakerFeeInput, EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; -use common::{block_on, now_sec_u32, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; use futures01::Future; use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, dex_fee_threshold, get_payment_locktime, @@ -773,11 +773,7 @@ fn test_watcher_validate_taker_fee_utxo() { let taker_pubkey = taker_coin.my_public_key().unwrap(); let taker_amount = MmNumber::from((10, 1)); - let fee_amount = dex_fee_amount_from_taker_coin( - &MmCoinEnum::UtxoCoin(taker_coin.clone()), - maker_coin.ticker(), - &taker_amount, - ); + let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, maker_coin.ticker(), &taker_amount); let taker_fee = taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) @@ -899,7 +895,7 @@ fn test_watcher_validate_taker_fee_eth() { let taker_pubkey = taker_keypair.public(); let taker_amount = MmNumber::from((1, 1)); - let fee_amount = dex_fee_amount_from_taker_coin(&MmCoinEnum::EthCoin(taker_coin.clone()), "ETH", &taker_amount); + let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); let taker_fee = taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) .wait() @@ -1002,7 +998,7 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_pubkey = taker_keypair.public(); let taker_amount = MmNumber::from((1, 1)); - let fee_amount = dex_fee_amount_from_taker_coin(&MmCoinEnum::EthCoin(taker_coin.clone()), "ETH", &taker_amount); + let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); let taker_fee = taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) .wait() @@ -1099,7 +1095,7 @@ fn test_watcher_validate_taker_payment_utxo() { let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run let time_lock_duration = get_payment_locktime(); let wait_for_confirmation_until = wait_until_sec(time_lock_duration); - let time_lock = wait_for_confirmation_until as u32; + let time_lock = wait_for_confirmation_until; let (_ctx, taker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let taker_pubkey = taker_coin.my_public_key().unwrap(); @@ -1319,7 +1315,7 @@ fn test_watcher_validate_taker_payment_eth() { let time_lock_duration = get_payment_locktime(); let wait_for_confirmation_until = wait_until_sec(time_lock_duration); - let time_lock = wait_for_confirmation_until as u32; + let time_lock = wait_for_confirmation_until; let taker_amount = BigDecimal::from_str("0.01").unwrap(); let maker_amount = BigDecimal::from_str("0.01").unwrap(); let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); @@ -1563,7 +1559,7 @@ fn test_watcher_validate_taker_payment_erc20() { let time_lock_duration = get_payment_locktime(); let wait_for_confirmation_until = wait_until_sec(time_lock_duration); - let time_lock = wait_for_confirmation_until as u32; + let time_lock = wait_for_confirmation_until; let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); @@ -1801,7 +1797,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { let (_ctx, coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let my_public_key = coin.my_public_key().unwrap(); - let time_lock = now_sec_u32() - 3600; + let time_lock = now_sec() - 3600; let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index e567546f6c..c2da301fa6 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -14,8 +14,9 @@ bytes = "1.1" cfg-if = "1.0" common = { path = "../common" } ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } -mm2_err_handle = { path = "../mm2_err_handle" } mm2_core = { path = "../mm2_core" } +mm2_err_handle = { path = "../mm2_err_handle" } +mm2_state_machine = { path = "../mm2_state_machine" } derive_more = "0.99" http = "0.2" rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } diff --git a/mm2src/mm2_net/src/wasm_ws.rs b/mm2src/mm2_net/src/wasm_ws.rs index fefdfa74cf..900e08b53a 100644 --- a/mm2src/mm2_net/src/wasm_ws.rs +++ b/mm2src/mm2_net/src/wasm_ws.rs @@ -1,13 +1,15 @@ use async_trait::async_trait; use common::executor::SpawnFuture; use common::log::{debug, error}; -use common::state_machine::{LastState, State, StateExt, StateMachineTrait, StateResult, TransitionFrom}; use common::stringify_js_error; use futures::channel::mpsc::{self, SendError, TrySendError}; use futures::channel::oneshot; use futures::{FutureExt, SinkExt, Stream, StreamExt}; use mm2_err_handle::prelude::*; +use mm2_state_machine::prelude::*; +use mm2_state_machine::state_machine::{ChangeStateExt, LastState, State, StateMachineTrait, StateResult}; use serde_json::{self as json, Value as Json}; +use std::convert::Infallible; use std::future::Future; use std::pin::Pin; use std::sync::Arc; @@ -214,7 +216,10 @@ fn spawn_ws_transport( }; let fut = async move { - state_machine.run(Box::new(ConnectingState)).await; + state_machine + .run(Box::new(ConnectingState)) + .await + .expect("The error of this machine is Infallible"); }; spawner.spawn(fut); @@ -377,8 +382,11 @@ struct WsStateMachine { impl StateMachineTrait for WsStateMachine { type Result = (); + type Error = Infallible; } +impl StandardStateMachine for WsStateMachine {} + impl WsStateMachine { /// Send the `event` to the corresponding `WebSocketReceiver` instance. fn notify_listener(&mut self, event: WebSocketEvent) { diff --git a/mm2src/mm2_state_machine/Cargo.toml b/mm2src/mm2_state_machine/Cargo.toml new file mode 100644 index 0000000000..4ba4aa0782 --- /dev/null +++ b/mm2src/mm2_state_machine/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mm2_state_machine" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1" + +[dev-dependencies] +common = { path = "../common" } +futures = { version = "0.3" } \ No newline at end of file diff --git a/mm2src/mm2_state_machine/src/lib.rs b/mm2src/mm2_state_machine/src/lib.rs new file mode 100644 index 0000000000..f71d25d771 --- /dev/null +++ b/mm2src/mm2_state_machine/src/lib.rs @@ -0,0 +1,12 @@ +#![feature(allocator_api, auto_traits, negative_impls)] + +pub mod prelude; +pub mod state_machine; +pub mod storable_state_machine; + +use std::alloc::Allocator; + +pub auto trait NotSame {} +impl !NotSame for (X, X) {} +// Makes the error conversion work for structs/enums containing Box +impl NotSame for Box {} diff --git a/mm2src/mm2_state_machine/src/prelude.rs b/mm2src/mm2_state_machine/src/prelude.rs new file mode 100644 index 0000000000..403570780c --- /dev/null +++ b/mm2src/mm2_state_machine/src/prelude.rs @@ -0,0 +1,4 @@ +pub use crate::state_machine::{ChangeStateExt, LastState, State, StateMachineTrait, StateResult}; + +pub trait TransitionFrom {} +pub trait StandardStateMachine {} diff --git a/mm2src/common/patterns/state_machine.rs b/mm2src/mm2_state_machine/src/state_machine.rs similarity index 72% rename from mm2src/common/patterns/state_machine.rs rename to mm2src/mm2_state_machine/src/state_machine.rs index bd00be8857..63ce6a96a3 100644 --- a/mm2src/common/patterns/state_machine.rs +++ b/mm2src/mm2_state_machine/src/state_machine.rs @@ -2,60 +2,74 @@ //! //! See the usage examples in the `tests` module. +use crate::prelude::*; use crate::NotSame; use async_trait::async_trait; -pub mod prelude { - pub use super::{LastState, State, StateExt, StateResult, TransitionFrom}; -} - -pub trait TransitionFrom {} - +/// A trait that state machine implementations should implement. #[async_trait] pub trait StateMachineTrait: Send + Sized + 'static { + /// The associated type for the result of the state machine. type Result: Send; - async fn run(&mut self, mut state: Box>) -> Self::Result { + /// The associated type for errors that can occur during the state machine's execution. + type Error: Send; + + /// Asynchronous method called when the state machine finishes its execution. + /// This method can be overridden by implementing types. + async fn on_finished(&mut self) -> Result<(), Self::Error> { Ok(()) } + + /// Asynchronous method to run the state machine. + /// It transitions between states and handles state-specific logic. + async fn run(&mut self, mut state: Box>) -> Result { loop { let result = state.on_changed(self).await; match result { StateResult::ChangeState(ChangeGuard { next }) => { state = next; }, - StateResult::Finish(ResultGuard { result }) => return result, + StateResult::Finish(ResultGuard { result }) => { + self.on_finished().await?; + return Ok(result); + }, + StateResult::Error(ErrorGuard { error }) => return Err(error), }; } } } -/// Prevent implementing [`TransitionFrom`] for `Next` If `T` implements `LastState` already. +// Prevent implementing `TransitionFrom` for `Next` if `T` implements `LastState` already. impl !TransitionFrom for Next where T: LastState, - // this bound is required to prevent conflicting implementation with `impl !TransitionFrom for T`. + // This bound is required to prevent conflicting implementation with `impl !TransitionFrom for T`. (T, Next): NotSame, { } -/// Prevent implementing [`TransitionFrom`] for itself. +// Prevent implementing [`TransitionFrom`] for itself. impl !TransitionFrom for T {} +/// A trait that individual states in the state machine should implement. #[async_trait] pub trait State: Send + Sync + 'static { + /// The associated type for the state machine that this state belongs to. type StateMachine: StateMachineTrait; + /// An action is called on entering this state. - /// To change the state to another one in the end of processing, use [`StateExt::change_state`]. + /// To change the state to another one at the end of processing, use `ChangeStateExt::change_state`. /// For example: + /// /// ```rust /// return Self::change_state(next_state); /// ``` async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult; } -pub trait StateExt { +/// A trait for transitioning between states in the state machine. +pub trait ChangeStateExt { /// Change the state to the `next_state`. - /// This function performs the compile-time validation whether this state can transition to the `Next` state, - /// i.e checks if `Next` implements [`Transition::from(ThisState)`]. + /// This function performs compile-time validation to ensure a valid state transition. fn change_state(next_state: Next) -> StateResult where Self: Sized, @@ -65,12 +79,17 @@ pub trait StateExt { } } -impl StateExt for T {} +// Implement ChangeStateExt for states that belong to StandardStateMachine. +impl> ChangeStateExt for T {} +/// A trait representing the last state(s) if the state machine. #[async_trait] pub trait LastState: Send + Sync + 'static { + /// The associated type for the state machine that this last state belongs to. type StateMachine: StateMachineTrait; + /// Asynchronous method called when the last state is entered. + /// It returns the result of the state machine's calculations. async fn on_changed( self: Box, ctx: &mut Self::StateMachine, @@ -88,14 +107,14 @@ impl State for T { } } +/// An enum representing the possible outcomes of state transitions. pub enum StateResult { ChangeState(ChangeGuard), Finish(ResultGuard), + Error(ErrorGuard), } -/* vvv The access guards that prevents the user using this pattern from entering an invalid state vvv */ - -/// An instance of `ChangeGuard` can be initialized within `state_machine` module only. +/// An instance of `ChangeGuard` can be initialized within the `state_machine` module only. pub struct ChangeGuard { /// The private field. next: Box>, @@ -103,14 +122,14 @@ pub struct ChangeGuard { impl ChangeGuard { /// The private constructor. - fn next>(next_state: Next) -> Self { + pub(crate) fn next>(next_state: Next) -> Self { ChangeGuard { next: Box::new(next_state), } } } -/// An instance of `ResultGuard` can be initialized within `state_machine` module only. +/// An instance of `ResultGuard` can be initialized within the `state_machine` module only. pub struct ResultGuard { /// The private field. result: T, @@ -121,14 +140,25 @@ impl ResultGuard { fn new(result: T) -> Self { ResultGuard { result } } } +/// An instance of `ErrorGuard` can be initialized within the `mm2_state_machine` crate only. +pub struct ErrorGuard { + error: E, +} + +impl ErrorGuard { + /// The private constructor. + pub(crate) fn new(error: E) -> Self { ErrorGuard { error } } +} + #[cfg(test)] mod tests { use super::*; - use crate::block_on; - use crate::executor::spawn; + use common::block_on; + use common::executor::spawn; use futures::channel::mpsc; use futures::{SinkExt, StreamExt}; use std::collections::HashMap; + use std::convert::Infallible; type UserId = usize; type Login = String; @@ -148,8 +178,11 @@ mod tests { impl StateMachineTrait for AuthStateMachine { type Result = AuthResult; + type Error = Infallible; } + impl StandardStateMachine for AuthStateMachine {} + struct ReadingState { rx: mpsc::Receiver, } @@ -256,7 +289,7 @@ mod tests { let fut = async move { let initial_state: ReadingState = ReadingState { rx }; let mut state_machine = AuthStateMachine { users }; - state_machine.run(Box::new(initial_state)).await + state_machine.run(Box::new(initial_state)).await.unwrap() }; block_on(fut) } diff --git a/mm2src/mm2_state_machine/src/storable_state_machine.rs b/mm2src/mm2_state_machine/src/storable_state_machine.rs new file mode 100644 index 0000000000..c3238f5904 --- /dev/null +++ b/mm2src/mm2_state_machine/src/storable_state_machine.rs @@ -0,0 +1,444 @@ +use crate::prelude::*; +use crate::state_machine::{ChangeGuard, ErrorGuard}; +use async_trait::async_trait; + +/// A trait representing the initial state of a state machine. +pub trait InitialState { + /// The type of state machine associated with this initial state. + type StateMachine: StorableStateMachine; +} + +/// A trait for handling new states in a state machine. +#[async_trait] +pub trait OnNewState: StateMachineTrait { + /// Handles a new state. + /// + /// # Parameters + /// + /// - `state`: A reference to the new state to be handled. + /// + /// # Returns + /// + /// A `Result` indicating success (`Ok(())`) or an error (`Err(::Error)`). + async fn on_new_state(&mut self, state: &S) -> Result<(), ::Error>; +} + +/// A trait for the storage of state machine events. +#[async_trait] +pub trait StateMachineStorage: Send + Sync { + /// The type representing a unique identifier for a state machine. + type MachineId: Send; + /// The type representing an event that can be stored. + type Event: Send; + /// The type representing an error that can occur during storage operations. + type Error: Send; + + /// Stores an event for a given state machine. + /// + /// # Parameters + /// + /// - `id`: The unique identifier of the state machine. + /// - `event`: The event to be stored. + /// + /// # Returns + /// + /// A `Result` indicating success (`Ok(())`) or an error (`Err(Self::Error)`). + async fn store_event(&mut self, id: Self::MachineId, event: Self::Event) -> Result<(), Self::Error>; + + /// Retrieves a list of unfinished state machines. + /// + /// # Returns + /// + /// A `Result` containing a vector of machine IDs or an error (`Err(Self::Error)`). + async fn get_unfinished(&self) -> Result, Self::Error>; + + /// Marks a state machine as finished. + /// + /// # Parameters + /// + /// - `id`: The unique identifier of the state machine to be marked as finished. + /// + /// # Returns + /// + /// A `Result` indicating success (`Ok(())`) or an error (`Err(Self::Error)`). + async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error>; +} + +/// A struct representing a restored state machine. +#[allow(dead_code)] +pub struct RestoredMachine { + machine: M, + current_state: Box>, +} + +/// A trait for storable state machines. +#[async_trait] +pub trait StorableStateMachine: Send + Sized + 'static { + /// The type of storage for the state machine. + type Storage: StateMachineStorage; + /// The result type of the state machine. + type Result: Send; + + /// Gets a mutable reference to the storage for the state machine. + fn storage(&mut self) -> &mut Self::Storage; + + /// Gets the unique identifier of the state machine. + fn id(&self) -> ::MachineId; + + /// Restores a state machine from storage. + /// + /// # Parameters + /// + /// - `id`: The unique identifier of the state machine to be restored. + /// - `storage`: The storage containing the state machine's data. + /// + /// # Returns + /// + /// A `Result` containing a `RestoredMachine` or an error. + fn restore_from_storage( + id: ::MachineId, + storage: Self::Storage, + ) -> Result, ::Error>; + + /// Stores an event for the state machine. + /// + /// # Parameters + /// + /// - `event`: The event to be stored. + /// + /// # Returns + /// + /// A `Result` indicating success (`Ok(())`) or an error (`Err(Self::Error)`). + async fn store_event( + &mut self, + event: ::Event, + ) -> Result<(), ::Error> { + let id = self.id(); + self.storage().store_event(id, event).await + } + + /// Marks the state machine as finished. + /// + /// # Returns + /// + /// A `Result` indicating success (`Ok(())`) or an error (`Err(Self::Error)`). + async fn mark_finished(&mut self) -> Result<(), ::Error> { + let id = self.id(); + self.storage().mark_finished(id).await + } +} + +// Ensure that StandardStateMachine won't be occasionally implemented for StorableStateMachine. +// Users of StorableStateMachine must be prevented from using ChangeStateExt::change_state +// because it doesn't call machine.on_new_state. +impl !StandardStateMachine for T {} +// Prevent implementing both StorableState and InitialState at the same time +impl !InitialState for T {} + +#[async_trait] +impl StateMachineTrait for T { + type Result = T::Result; + type Error = ::Error; + + async fn on_finished(&mut self) -> Result<(), ::Error> { + self.mark_finished().await + } +} + +/// A trait for storable states. +pub trait StorableState { + /// The type of state machine associated with this state. + type StateMachine: StorableStateMachine; + + /// Gets the event associated with this state. + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event; +} + +/// Implementation of `OnNewState` for storable state machines and their related states. +#[async_trait] +impl + Sync> OnNewState for T { + /// Handles a new state. + /// + /// # Parameters + /// + /// - `state`: A reference to the new state to be handled. + /// + /// # Returns + /// + /// A `Result` indicating success (`Ok(())`) or an error (`Err(Self::Error)`). + async fn on_new_state(&mut self, state: &S) -> Result<(), ::Error> { + let event = state.get_event(); + self.store_event(event).await + } +} + +/// An asynchronous function for changing the state of a storable state machine. +/// +/// # Parameters +/// +/// - `next_state`: The next state to transition to. +/// - `machine`: A mutable reference to the state machine. +/// +/// # Returns +/// +/// A `StateResult` indicating success or an error. +/// +/// # Generic Parameters +/// +/// - `Next`: The type of the next state. +async fn change_state_impl(next_state: Next, machine: &mut Next::StateMachine) -> StateResult +where + Next: State + ChangeStateOnNewExt, + Next::StateMachine: OnNewState + Sync, +{ + if let Err(e) = machine.on_new_state(&next_state).await { + return StateResult::Error(ErrorGuard::new(e)); + } + StateResult::ChangeState(ChangeGuard::next(next_state)) +} + +/// A trait for state transition functionality. +#[async_trait] +pub trait ChangeStateOnNewExt { + /// Change the state to the `next_state`. + /// + /// # Parameters + /// + /// - `next_state`: The next state to transition to. + /// - `machine`: A mutable reference to the state machine. + /// + /// # Returns + /// + /// A `StateResult` indicating success or an error. + /// + /// # Generic Parameters + /// + /// - `Next`: The type of the next state. + async fn change_state(next_state: Next, machine: &mut Next::StateMachine) -> StateResult + where + Self: Sized, + Next: State + TransitionFrom + ChangeStateOnNewExt, + Next::StateMachine: OnNewState + Sync, + { + change_state_impl(next_state, machine).await + } +} + +impl> ChangeStateOnNewExt for T {} + +/// A trait for initial state change functionality. +#[async_trait] +pub trait ChangeInitialStateExt: InitialState { + /// Change the state to the `next_state`. + /// + /// # Parameters + /// + /// - `next_state`: The next state to transition to. + /// - `machine`: A mutable reference to the state machine. + /// + /// # Returns + /// + /// A `StateResult` indicating success or an error. + /// + /// # Generic Parameters + /// + /// - `Next`: The type of the next state. + async fn change_state(next_state: Next, machine: &mut Next::StateMachine) -> StateResult + where + Self: Sized, + Next: State + TransitionFrom + ChangeStateOnNewExt, + Next::StateMachine: OnNewState + Sync, + { + change_state_impl(next_state, machine).await + } +} + +impl> ChangeInitialStateExt for T {} + +#[cfg(test)] +mod tests { + use super::*; + use common::block_on; + use std::collections::HashMap; + use std::convert::Infallible; + + struct StorageTest { + events_unfinished: HashMap>, + events_finished: HashMap>, + } + + impl StorageTest { + fn empty() -> Self { + StorageTest { + events_unfinished: HashMap::new(), + events_finished: HashMap::new(), + } + } + } + + struct StorableStateMachineTest { + id: usize, + storage: StorageTest, + } + + #[derive(Debug, Eq, PartialEq)] + enum TestEvent { + ForState2, + ForState3, + ForState4, + } + + #[async_trait] + impl StateMachineStorage for StorageTest { + type MachineId = usize; + type Event = TestEvent; + type Error = Infallible; + + async fn store_event(&mut self, machine_id: usize, events: Self::Event) -> Result<(), Self::Error> { + self.events_unfinished + .entry(machine_id) + .or_insert_with(Vec::new) + .push(events); + Ok(()) + } + + async fn get_unfinished(&self) -> Result, Self::Error> { + Ok(self.events_unfinished.keys().copied().collect()) + } + + async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { + let events = self.events_unfinished.remove(&id).unwrap(); + self.events_finished.insert(id, events); + Ok(()) + } + } + + impl StorableStateMachine for StorableStateMachineTest { + type Storage = StorageTest; + type Result = (); + + fn storage(&mut self) -> &mut Self::Storage { &mut self.storage } + + fn id(&self) -> ::MachineId { self.id } + + fn restore_from_storage( + id: ::MachineId, + storage: Self::Storage, + ) -> Result, ::Error> { + let events = storage.events_unfinished.get(&id).unwrap(); + let current_state: Box> = match events.last() { + None => Box::new(State1 {}), + Some(TestEvent::ForState2) => Box::new(State2 {}), + _ => unimplemented!(), + }; + let machine = StorableStateMachineTest { id, storage }; + Ok(RestoredMachine { machine, current_state }) + } + } + + struct State1 {} + + impl InitialState for State1 { + type StateMachine = StorableStateMachineTest; + } + + struct State2 {} + + impl StorableState for State2 { + type StateMachine = StorableStateMachineTest; + + fn get_event(&self) -> TestEvent { TestEvent::ForState2 } + } + + impl TransitionFrom for State2 {} + + struct State3 {} + + impl StorableState for State3 { + type StateMachine = StorableStateMachineTest; + + fn get_event(&self) -> TestEvent { TestEvent::ForState3 } + } + + impl TransitionFrom for State3 {} + + struct State4 {} + + impl StorableState for State4 { + type StateMachine = StorableStateMachineTest; + + fn get_event(&self) -> TestEvent { TestEvent::ForState4 } + } + + impl TransitionFrom for State4 {} + + #[async_trait] + impl LastState for State4 { + type StateMachine = StorableStateMachineTest; + + async fn on_changed(self: Box, _ctx: &mut Self::StateMachine) -> () {} + } + + #[async_trait] + impl State for State1 { + type StateMachine = StorableStateMachineTest; + + async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult { + Self::change_state(State2 {}, ctx).await + } + } + + #[async_trait] + impl State for State2 { + type StateMachine = StorableStateMachineTest; + + async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult { + Self::change_state(State3 {}, ctx).await + } + } + + #[async_trait] + impl State for State3 { + type StateMachine = StorableStateMachineTest; + + async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult { + Self::change_state(State4 {}, ctx).await + } + } + + #[test] + fn run_storable_state_machine() { + let mut machine = StorableStateMachineTest { + id: 1, + storage: StorageTest::empty(), + }; + block_on(machine.run(Box::new(State1 {}))).unwrap(); + + let expected_events = HashMap::from_iter([(1, vec![ + TestEvent::ForState2, + TestEvent::ForState3, + TestEvent::ForState4, + ])]); + assert_eq!(expected_events, machine.storage.events_finished); + } + + #[test] + fn restore_state_machine() { + let mut storage = StorageTest::empty(); + let id = 1; + storage.events_unfinished.insert(1, vec![TestEvent::ForState2]); + let RestoredMachine { + mut machine, + current_state, + } = StorableStateMachineTest::restore_from_storage(id, storage).unwrap(); + + block_on(machine.run(current_state)).unwrap(); + + let expected_events = HashMap::from_iter([(1, vec![ + TestEvent::ForState2, + TestEvent::ForState3, + TestEvent::ForState4, + ])]); + assert_eq!(expected_events, machine.storage.events_finished); + } +} diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 2b49419c6c..cb3abed0ba 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -207,6 +207,22 @@ impl Mm2TestConf { } } + /// Generates a seed node conf enabling use_trading_proto_v2 + pub fn seednode_trade_v2(passphrase: &str, coins: &Json) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "passphrase": passphrase, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "i_am_seed": true, + "use_trading_proto_v2": true, + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn seednode_with_hd_account(passphrase: &str, coins: &Json) -> Self { Mm2TestConf { conf: json!({ @@ -236,6 +252,22 @@ impl Mm2TestConf { } } + /// Generates a light node conf enabling use_trading_proto_v2 + pub fn light_node_trade_v2(passphrase: &str, coins: &Json, seednodes: &[&str]) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "passphrase": passphrase, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "seednodes": seednodes, + "use_trading_proto_v2": true, + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn watcher_light_node(passphrase: &str, coins: &Json, seednodes: &[&str], conf: WatcherConf) -> Self { Mm2TestConf { conf: json!({ From 2ce6e4a3c9aea6e93882551ba99328f33574ed63 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Thu, 28 Sep 2023 18:55:38 +0300 Subject: [PATCH 08/40] fix(evm): reduce web3 request timeout to 20s (#1973) --- mm2src/coins/eth/web3_transport/http_transport.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index 5272974310..02822ebe5d 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -3,7 +3,7 @@ use crate::eth::{web3_transport::Web3SendOut, EthCoin, GuiAuthMessages, RpcTrans use common::APPLICATION_JSON; use futures::lock::Mutex as AsyncMutex; use http::header::CONTENT_TYPE; -use jsonrpc_core::{Call, Response}; +use jsonrpc_core::{Call, Id, Response}; use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; use serde_json::Value as Json; #[cfg(not(target_arch = "wasm32"))] use std::ops::Deref; @@ -183,7 +183,7 @@ async fn send_request( use http::header::HeaderValue; use mm2_net::transport::slurp_req; - const REQUEST_TIMEOUT_S: f64 = 60.; + const REQUEST_TIMEOUT_S: f64 = 20.; let mut errors = Vec::new(); @@ -215,9 +215,14 @@ async fn send_request( let res = match rc { Either::Left((r, _t)) => r, Either::Right((_t, _r)) => { + let (method, id) = match &request { + Call::MethodCall(m) => (m.method.clone(), m.id.clone()), + Call::Notification(n) => (n.method.clone(), Id::Null), + Call::Invalid { id } => ("Invalid call".to_string(), id.clone()), + }; let error = format!( - "Error requesting '{}': {}s timeout expired", - node.uri, REQUEST_TIMEOUT_S + "Error requesting '{}': {}s timeout expired, method: '{}', id: {:?}", + node.uri, REQUEST_TIMEOUT_S, method, id ); warn!("{}", error); errors.push(Web3RpcError::Transport(error)); From e9559c2290f0f81c56455e05e1d478ba675dc03a Mon Sep 17 00:00:00 2001 From: Onur Date: Thu, 28 Sep 2023 19:46:04 +0300 Subject: [PATCH 09/40] feat(event streaming): implement push-only streaming channels and SSE (#1945) This implements streaming channels using mpsc(underlying part of SSE) and SSE for sending data to clients continuously, NETWORK event is implemented to show the new functionality. All platforms other than WASM are supported. --------- Signed-off-by: onur-ozkan --- Cargo.lock | 20 ++ Cargo.toml | 17 +- examples/sse/README.md | 14 ++ examples/sse/index.html | 26 +++ examples/wasm/README.md | 2 +- examples/wasm/index.html | 37 +-- mm2src/mm2_core/Cargo.toml | 3 +- mm2src/mm2_core/src/mm_ctx.rs | 18 +- mm2src/mm2_event_stream/Cargo.toml | 18 ++ mm2src/mm2_event_stream/src/behaviour.rs | 15 ++ mm2src/mm2_event_stream/src/controller.rs | 210 ++++++++++++++++++ mm2src/mm2_event_stream/src/lib.rs | 66 ++++++ mm2src/mm2_main/Cargo.toml | 1 + mm2src/mm2_main/src/lp_native_dex.rs | 22 +- mm2src/mm2_main/src/lp_network.rs | 35 +-- mm2src/mm2_main/src/ordermatch_tests.rs | 2 +- mm2src/mm2_main/src/rpc.rs | 20 +- .../src/rpc/lp_commands/lp_commands_legacy.rs | 6 +- mm2src/mm2_net/Cargo.toml | 23 +- mm2src/mm2_net/src/lib.rs | 3 + mm2src/mm2_net/src/network_event.rs | 61 +++++ mm2src/mm2_net/src/p2p.rs | 40 ++++ mm2src/mm2_net/src/sse_handler.rs | 78 +++++++ 23 files changed, 659 insertions(+), 78 deletions(-) create mode 100644 examples/sse/README.md create mode 100644 examples/sse/index.html create mode 100644 mm2src/mm2_event_stream/Cargo.toml create mode 100644 mm2src/mm2_event_stream/src/behaviour.rs create mode 100644 mm2src/mm2_event_stream/src/controller.rs create mode 100644 mm2src/mm2_event_stream/src/lib.rs create mode 100644 mm2src/mm2_net/src/network_event.rs create mode 100644 mm2src/mm2_net/src/p2p.rs create mode 100644 mm2src/mm2_net/src/sse_handler.rs diff --git a/Cargo.lock b/Cargo.lock index 5bf69cbb31..1f7f1ee9f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4115,6 +4115,7 @@ dependencies = [ "gstuff", "hex 0.4.3", "lazy_static", + "mm2_event_stream", "mm2_metrics", "mm2_rpc", "primitives", @@ -4183,6 +4184,19 @@ dependencies = [ "web3", ] +[[package]] +name = "mm2_event_stream" +version = "0.1.0" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "common", + "parking_lot 0.12.0", + "serde", + "tokio", + "wasm-bindgen-test", +] + [[package]] name = "mm2_git" version = "0.1.0" @@ -4278,6 +4292,7 @@ dependencies = [ "mm2_core", "mm2_db", "mm2_err_handle", + "mm2_event_stream", "mm2_gui_storage", "mm2_io", "mm2_metrics", @@ -4373,6 +4388,7 @@ dependencies = [ name = "mm2_net" version = "0.1.0" dependencies = [ + "async-stream", "async-trait", "bytes 1.1.0", "cfg-if 1.0.0", @@ -4386,9 +4402,13 @@ dependencies = [ "hyper", "js-sys", "lazy_static", + "mm2-libp2p", "mm2_core", "mm2_err_handle", + "mm2_event_stream", "mm2_state_machine", + "mocktopus", + "parking_lot 0.12.0", "prost", "rand 0.7.3", "rustls 0.20.4", diff --git a/Cargo.toml b/Cargo.toml index a3f3e98803..a7cf3badf3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,44 +1,45 @@ [workspace] members = [ + "mm2src/coins_activation", "mm2src/coins", "mm2src/coins/utxo_signer", - "mm2src/coins_activation", "mm2src/common/shared_ref_counter", "mm2src/crypto", "mm2src/db_common", "mm2src/derives/enum_from", - "mm2src/derives/ser_error", "mm2src/derives/ser_error_derive", + "mm2src/derives/ser_error", "mm2src/floodsub", "mm2src/gossipsub", - "mm2src/mm2_gui_storage", "mm2src/hw_common", "mm2src/mm2_bin_lib", - "mm2src/mm2_bitcoin/crypto", "mm2src/mm2_bitcoin/chain", + "mm2src/mm2_bitcoin/crypto", "mm2src/mm2_bitcoin/keys", - "mm2src/mm2_bitcoin/rpc", "mm2src/mm2_bitcoin/primitives", + "mm2src/mm2_bitcoin/rpc", "mm2src/mm2_bitcoin/script", - "mm2src/mm2_bitcoin/serialization", "mm2src/mm2_bitcoin/serialization_derive", + "mm2src/mm2_bitcoin/serialization", "mm2src/mm2_bitcoin/test_helpers", "mm2src/mm2_core", "mm2src/mm2_db", "mm2src/mm2_err_handle", "mm2src/mm2_eth", + "mm2src/mm2_event_stream", "mm2src/mm2_git", + "mm2src/mm2_gui_storage", "mm2src/mm2_io", "mm2src/mm2_libp2p", + "mm2src/mm2_main", "mm2src/mm2_metamask", "mm2src/mm2_metrics", - "mm2src/mm2_main", "mm2src/mm2_net", "mm2src/mm2_number", "mm2src/mm2_rpc", "mm2src/mm2_state_machine", - "mm2src/rpc_task", "mm2src/mm2_test_helpers", + "mm2src/rpc_task", "mm2src/trezor", ] diff --git a/examples/sse/README.md b/examples/sse/README.md new file mode 100644 index 0000000000..b43c213d02 --- /dev/null +++ b/examples/sse/README.md @@ -0,0 +1,14 @@ +# Listening event-stream from komodo-defi-framework + +1. Start komodo-defi-framework with event streaming activated +2. Run a local HTTP server + - if you use Python 3, run: + ``` + python3 -m http.server 8000 + ``` + - if you use Python 2, run: + ``` + python -m SimpleHTTPServer 8000 + ``` + +You should now be able to observe events from the komodo-defi-framework through the SSE. diff --git a/examples/sse/index.html b/examples/sse/index.html new file mode 100644 index 0000000000..e780004ccb --- /dev/null +++ b/examples/sse/index.html @@ -0,0 +1,26 @@ + + + + + +

Events

+
+ + + + + + diff --git a/examples/wasm/README.md b/examples/wasm/README.md index 6f2faff136..849ff63773 100644 --- a/examples/wasm/README.md +++ b/examples/wasm/README.md @@ -15,4 +15,4 @@ via [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly) ``` Read more about [running a simple local HTTP server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/set_up_a_local_testing_server#running_a_simple_local_http_server) -3. Open webpage in your browser http://localhost:8000/wasm_build/index.html +3. Open webpage in your browser http://localhost:8000/wasm_build/index.html \ No newline at end of file diff --git a/examples/wasm/index.html b/examples/wasm/index.html index bd19234020..b9243b9bf8 100644 --- a/examples/wasm/index.html +++ b/examples/wasm/index.html @@ -1,27 +1,32 @@ + MM2 example + -
- -
- -
- - -
-
- -
- -
- -
+
+ +
+ +
+ + +
+
+ +
+ +
+ +
- + + \ No newline at end of file diff --git a/mm2src/mm2_core/Cargo.toml b/mm2src/mm2_core/Cargo.toml index b8e34b34e4..566d778b8b 100644 --- a/mm2src/mm2_core/Cargo.toml +++ b/mm2src/mm2_core/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" doctest = false [dependencies] -async-trait = "0.1" arrayref = "0.3" +async-trait = "0.1" cfg-if = "1.0" common = { path = "../common" } db_common = { path = "../db_common" } @@ -16,6 +16,7 @@ derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } hex = "0.4.2" lazy_static = "1.4" +mm2_event_stream = { path = "../mm2_event_stream" } mm2_metrics = { path = "../mm2_metrics" } primitives = { path = "../mm2_bitcoin/primitives" } rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index d2bd527d2b..fc7fc5f0d9 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -6,6 +6,7 @@ use common::log::{self, LogLevel, LogOnError, LogState}; use common::{cfg_native, cfg_wasm32, small_rng}; use gstuff::{try_s, Constructible, ERR, ERRL}; use lazy_static::lazy_static; +use mm2_event_stream::{controller::Controller, Event, EventStreamConfiguration}; use mm2_metrics::{MetricsArc, MetricsOps}; use primitives::hash::H160; use rand::Rng; @@ -72,6 +73,10 @@ pub struct MmCtx { pub initialized: Constructible, /// True if the RPC HTTP server was started. pub rpc_started: Constructible, + /// Controller for continuously streaming data using streaming channels of `mm2_event_stream`. + pub stream_channel_controller: Controller, + /// Configuration of event streaming used for SSE. + pub event_stream_configuration: Option, /// True if the MarketMaker instance needs to stop. pub stop: Constructible, /// Unique context identifier, allowing us to more easily pass the context through the FFI boundaries. @@ -133,6 +138,8 @@ impl MmCtx { metrics: MetricsArc::new(), initialized: Constructible::default(), rpc_started: Constructible::default(), + stream_channel_controller: Controller::new(), + event_stream_configuration: None, stop: Constructible::default(), ffi_handle: Constructible::default(), ordermatch_ctx: Mutex::new(None), @@ -680,8 +687,17 @@ impl MmCtxBuilder { let mut ctx = MmCtx::with_log_state(log); ctx.mm_version = self.version; ctx.datetime = self.datetime; + if let Some(conf) = self.conf { - ctx.conf = conf + ctx.conf = conf; + + let event_stream_configuration = &ctx.conf["event_stream_configuration"]; + if !event_stream_configuration.is_null() { + let event_stream_configuration: EventStreamConfiguration = + json::from_value(event_stream_configuration.clone()) + .expect("Invalid json value in 'event_stream_configuration'."); + ctx.event_stream_configuration = Some(event_stream_configuration); + } } #[cfg(target_arch = "wasm32")] diff --git a/mm2src/mm2_event_stream/Cargo.toml b/mm2src/mm2_event_stream/Cargo.toml new file mode 100644 index 0000000000..2865e0a01f --- /dev/null +++ b/mm2src/mm2_event_stream/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mm2_event_stream" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1" +cfg-if = "1.0" +common = { path = "../common" } +parking_lot = "0.12" +serde = { version = "1", features = ["derive", "rc"] } +tokio = { version = "1", features = ["sync"] } + +[dev-dependencies] +tokio = { version = "1", features = ["sync", "macros", "time", "rt"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-test = { version = "0.3.2" } diff --git a/mm2src/mm2_event_stream/src/behaviour.rs b/mm2src/mm2_event_stream/src/behaviour.rs new file mode 100644 index 0000000000..bb905af3fc --- /dev/null +++ b/mm2src/mm2_event_stream/src/behaviour.rs @@ -0,0 +1,15 @@ +use crate::EventStreamConfiguration; +use async_trait::async_trait; + +#[async_trait] +pub trait EventBehaviour { + /// Unique name of the event. + const EVENT_NAME: &'static str; + + /// Event handler that is responsible for broadcasting event data to the streaming channels. + async fn handle(self, interval: f64); + + /// Spawns the `Self::handle` in a separate thread if the event is active according to the mm2 configuration. + /// Does nothing if the event is not active. + fn spawn_if_active(self, config: &EventStreamConfiguration); +} diff --git a/mm2src/mm2_event_stream/src/controller.rs b/mm2src/mm2_event_stream/src/controller.rs new file mode 100644 index 0000000000..098c6e4bb7 --- /dev/null +++ b/mm2src/mm2_event_stream/src/controller.rs @@ -0,0 +1,210 @@ +use parking_lot::Mutex; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::mpsc::{self, Receiver, Sender}; + +type ChannelId = u64; + +/// Root controller of streaming channels +pub struct Controller(Arc>>); + +impl Clone for Controller { + fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } +} + +/// Inner part of the controller +pub struct ChannelsInner { + last_id: u64, + channels: HashMap>, +} + +struct Channel { + tx: Sender>, +} + +/// guard to trace channels disconnection +pub struct ChannelGuard { + channel_id: ChannelId, + controller: Controller, +} + +/// Receiver to cleanup resources on `Drop` +pub struct GuardedReceiver { + rx: Receiver>, + #[allow(dead_code)] + guard: ChannelGuard, +} + +impl Controller { + /// Creates a new channels controller + pub fn new() -> Self { Default::default() } + + /// Creates a new channel and returns it's events receiver + pub fn create_channel(&mut self, concurrency: usize) -> GuardedReceiver { + let (tx, rx) = mpsc::channel::>(concurrency); + let channel = Channel { tx }; + + let mut inner = self.0.lock(); + let channel_id = inner.last_id.overflowing_add(1).0; + inner.channels.insert(channel_id, channel); + inner.last_id = channel_id; + + let guard = ChannelGuard::new(channel_id, self.clone()); + GuardedReceiver { rx, guard } + } + + /// Returns number of active channels + pub fn num_connections(&self) -> usize { self.0.lock().channels.len() } + + /// Broadcast message to all channels + pub async fn broadcast(&self, message: M) { + let msg = Arc::new(message); + for rx in self.all_senders() { + rx.send(Arc::clone(&msg)).await.ok(); + } + } + + /// Removes the channel from the controller + fn remove_channel(&mut self, channel_id: &ChannelId) { + let mut inner = self.0.lock(); + inner.channels.remove(channel_id); + } + + /// Returns all the active channels + fn all_senders(&self) -> Vec>> { self.0.lock().channels.values().map(|c| c.tx.clone()).collect() } +} + +impl Default for Controller { + fn default() -> Self { + let inner = ChannelsInner { + last_id: 0, + channels: HashMap::new(), + }; + Self(Arc::new(Mutex::new(inner))) + } +} + +impl ChannelGuard { + fn new(channel_id: ChannelId, controller: Controller) -> Self { Self { channel_id, controller } } +} + +impl Drop for ChannelGuard { + fn drop(&mut self) { + common::log::debug!("Dropping event channel with id: {}", self.channel_id); + + self.controller.remove_channel(&self.channel_id); + } +} + +impl GuardedReceiver { + /// Receives the next event from the channel + pub async fn recv(&mut self) -> Option> { self.rx.recv().await } +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use super::*; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + macro_rules! cross_test { + ($test_name:ident, $test_code:block) => { + #[cfg(not(target_arch = "wasm32"))] + #[tokio::test(flavor = "multi_thread")] + async fn $test_name() { $test_code } + + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen_test] + async fn $test_name() { $test_code } + }; + } + + cross_test!(test_create_channel_and_broadcast, { + let mut controller = Controller::new(); + let mut guard_receiver = controller.create_channel(1); + + controller.broadcast("Message".to_string()).await; + + let received_msg = guard_receiver.recv().await.unwrap(); + assert_eq!(*received_msg, "Message".to_string()); + }); + + cross_test!(test_multiple_channels_and_broadcast, { + let mut controller = Controller::new(); + + let mut receivers = Vec::new(); + for _ in 0..3 { + receivers.push(controller.create_channel(1)); + } + + controller.broadcast("Message".to_string()).await; + + for receiver in &mut receivers { + let received_msg = receiver.recv().await.unwrap(); + assert_eq!(*received_msg, "Message".to_string()); + } + }); + + cross_test!(test_channel_cleanup_on_drop, { + let mut controller: Controller<()> = Controller::new(); + let guard_receiver = controller.create_channel(1); + + assert_eq!(controller.num_connections(), 1); + + drop(guard_receiver); + + common::executor::Timer::sleep(0.1).await; // Give time for the drop to execute + + assert_eq!(controller.num_connections(), 0); + }); + + cross_test!(test_broadcast_across_channels, { + let mut controller = Controller::new(); + + let mut receivers = Vec::new(); + for _ in 0..3 { + receivers.push(controller.create_channel(1)); + } + + controller.broadcast("Message".to_string()).await; + + for receiver in &mut receivers { + let received_msg = receiver.recv().await.unwrap(); + assert_eq!(*received_msg, "Message".to_string()); + } + }); + + cross_test!(test_multiple_messages_and_drop, { + let mut controller = Controller::new(); + let mut guard_receiver = controller.create_channel(6); + + controller.broadcast("Message 1".to_string()).await; + controller.broadcast("Message 2".to_string()).await; + controller.broadcast("Message 3".to_string()).await; + controller.broadcast("Message 4".to_string()).await; + controller.broadcast("Message 5".to_string()).await; + controller.broadcast("Message 6".to_string()).await; + + let mut received_msgs = Vec::new(); + for _ in 0..6 { + let received_msg = guard_receiver.recv().await.unwrap(); + received_msgs.push(received_msg); + } + + assert_eq!(*received_msgs[0], "Message 1".to_string()); + assert_eq!(*received_msgs[1], "Message 2".to_string()); + assert_eq!(*received_msgs[2], "Message 3".to_string()); + assert_eq!(*received_msgs[3], "Message 4".to_string()); + assert_eq!(*received_msgs[4], "Message 5".to_string()); + assert_eq!(*received_msgs[5], "Message 6".to_string()); + + // Consume the GuardedReceiver to trigger drop and channel cleanup + drop(guard_receiver); + + common::executor::Timer::sleep(0.1).await; // Give time for the drop to execute + + assert_eq!(controller.num_connections(), 0); + }); +} diff --git a/mm2src/mm2_event_stream/src/lib.rs b/mm2src/mm2_event_stream/src/lib.rs new file mode 100644 index 0000000000..afa5c7e1be --- /dev/null +++ b/mm2src/mm2_event_stream/src/lib.rs @@ -0,0 +1,66 @@ +use serde::Deserialize; +use std::collections::HashMap; + +/// Multi-purpose/generic event type that can easily be used over the event streaming +pub struct Event { + _type: String, + message: String, +} + +impl Event { + /// Creates a new `Event` instance with the specified event type and message. + #[inline] + pub fn new(event_type: String, message: String) -> Self { + Self { + _type: event_type, + message, + } + } + + /// Gets the event type. + #[inline] + pub fn event_type(&self) -> &str { &self._type } + + /// Gets the event message. + #[inline] + pub fn message(&self) -> &str { &self.message } +} + +/// Configuration for event streaming +#[derive(Deserialize)] +pub struct EventStreamConfiguration { + /// The value to set for the `Access-Control-Allow-Origin` header. + #[serde(default)] + pub access_control_allow_origin: String, + #[serde(default)] + active_events: HashMap, +} + +/// Represents the configuration for a specific event within the event stream. +#[derive(Clone, Default, Deserialize)] +pub struct EventConfig { + /// The interval in seconds at which the event should be streamed. + pub stream_interval_seconds: f64, +} + +impl Default for EventStreamConfiguration { + fn default() -> Self { + Self { + access_control_allow_origin: String::from("*"), + active_events: Default::default(), + } + } +} + +impl EventStreamConfiguration { + /// Retrieves the configuration for a specific event by its name. + #[inline] + pub fn get_event(&self, event_name: &str) -> Option { self.active_events.get(event_name).cloned() } + + /// Gets the total number of active events in the configuration. + #[inline] + pub fn total_active_events(&self) -> usize { self.active_events.len() } +} + +pub mod behaviour; +pub mod controller; diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 48976dda2b..2416e93a58 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -57,6 +57,7 @@ lazy_static = "1.4" libc = "0.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } +mm2_event_stream = { path = "../mm2_event_stream" } mm2_gui_storage = { path = "../mm2_gui_storage" } mm2_io = { path = "../mm2_io" } mm2-libp2p = { path = "../mm2_libp2p" } diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 8401f2a0fd..45f7a2f126 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -31,6 +31,7 @@ use mm2_err_handle::prelude::*; use mm2_libp2p::{spawn_gossipsub, AdexBehaviourError, NodeType, RelayAddress, RelayAddressError, SwarmRuntime, WssCerts}; use mm2_metrics::mm_gauge; +use mm2_net::p2p::P2PContext; use rpc_task::RpcTaskError; use serde_json::{self as json}; use std::fs; @@ -42,7 +43,7 @@ use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] use crate::mm2::database::init_and_migrate_db; use crate::mm2::lp_message_service::{init_message_service, InitMessageServiceError}; -use crate::mm2::lp_network::{lp_network_ports, p2p_event_process_loop, NetIdError, P2PContext}; +use crate::mm2::lp_network::{lp_network_ports, p2p_event_process_loop, NetIdError}; use crate::mm2::lp_ordermatch::{broadcast_maker_orders_keep_alive_loop, clean_memory_loop, init_ordermatch_context, lp_ordermatch_loop, orders_kick_start, BalanceUpdateOrdermatchHandler, OrdermatchInitError}; @@ -50,9 +51,11 @@ use crate::mm2::lp_swap::{running_swaps_num, swap_kick_starts}; use crate::mm2::rpc::spawn_rpc; cfg_native! { + use db_common::sqlite::rusqlite::Error as SqlError; + use mm2_event_stream::behaviour::EventBehaviour; use mm2_io::fs::{ensure_dir_is_writable, ensure_file_is_writable}; use mm2_net::ip_addr::myipaddr; - use db_common::sqlite::rusqlite::Error as SqlError; + use mm2_net::network_event::NetworkEvent; } #[path = "lp_init/init_context.rs"] mod init_context; @@ -376,6 +379,15 @@ fn migrate_db(ctx: &MmArc) -> MmInitResult<()> { #[cfg(not(target_arch = "wasm32"))] fn migration_1(_ctx: &MmArc) {} +#[cfg(not(target_arch = "wasm32"))] +fn init_event_streaming(ctx: &MmArc) { + // This condition only executed if events were enabled in mm2 configuration. + if let Some(config) = &ctx.event_stream_configuration { + // Network event handling + NetworkEvent::new(ctx.clone()).spawn_if_active(config); + } +} + pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { init_ordermatch_context(&ctx)?; init_p2p(ctx.clone()).await?; @@ -406,11 +418,15 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { // an order and start new swap that might get started 2 times because of kick-start kick_start(ctx.clone()).await?; + #[cfg(not(target_arch = "wasm32"))] + init_event_streaming(&ctx); + ctx.spawner().spawn(lp_ordermatch_loop(ctx.clone())); ctx.spawner().spawn(broadcast_maker_orders_keep_alive_loop(ctx.clone())); ctx.spawner().spawn(clean_memory_loop(ctx.weak())); + Ok(()) } @@ -439,11 +455,13 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes spawn_rpc(ctx_id); let ctx_c = ctx.clone(); + ctx.spawner().spawn(async move { if let Err(err) = ctx_c.init_metrics() { warn!("Couldn't initialize metrics system: {}", err); } }); + // In the mobile version we might depend on `lp_init` staying around until the context stops. loop { if ctx.is_stopping() { diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index ab4232e07e..859434026b 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -1,3 +1,5 @@ +// TODO: a lof of these implementations should be handled in `mm2_net` + /****************************************************************************** * Copyright © 2022 Atomic Private Limited and its contributors * * * @@ -27,17 +29,15 @@ use instant::Instant; use keys::KeyPair; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; -use mm2_libp2p::atomicdex_behaviour::{AdexBehaviourCmd, AdexBehaviourEvent, AdexCmdTx, AdexEventRx, AdexResponse, +use mm2_libp2p::atomicdex_behaviour::{AdexBehaviourCmd, AdexBehaviourEvent, AdexEventRx, AdexResponse, AdexResponseChannel}; use mm2_libp2p::peers_exchange::PeerAddresses; use mm2_libp2p::{decode_message, encode_message, DecodingError, GossipsubMessage, Libp2pPublic, Libp2pSecpPublic, MessageId, NetworkPorts, PeerId, TopicHash, TOPIC_SEPARATOR}; use mm2_metrics::{mm_label, mm_timing}; -#[cfg(test)] use mocktopus::macros::*; -use parking_lot::Mutex as PaMutex; +use mm2_net::p2p::P2PContext; use serde::de; use std::net::ToSocketAddrs; -use std::sync::Arc; use crate::mm2::lp_ordermatch; use crate::mm2::{lp_stats, lp_swap}; @@ -89,33 +89,6 @@ pub enum P2PRequest { NetworkInfo(lp_stats::NetworkInfoRequest), } -pub struct P2PContext { - /// Using Mutex helps to prevent cloning which can actually result to channel being unbounded in case of using 1 tx clone per 1 message. - pub cmd_tx: PaMutex, -} - -#[cfg_attr(test, mockable)] -impl P2PContext { - pub fn new(cmd_tx: AdexCmdTx) -> Self { - P2PContext { - cmd_tx: PaMutex::new(cmd_tx), - } - } - - pub fn store_to_mm_arc(self, ctx: &MmArc) { *ctx.p2p_ctx.lock().unwrap() = Some(Arc::new(self)) } - - pub fn fetch_from_mm_arc(ctx: &MmArc) -> Arc { - ctx.p2p_ctx - .lock() - .unwrap() - .as_ref() - .unwrap() - .clone() - .downcast() - .unwrap() - } -} - pub async fn p2p_event_process_loop(ctx: MmWeak, mut rx: AdexEventRx, i_am_relay: bool) { loop { let adex_event = rx.next().await; diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 6f2af8a757..ec96d8f484 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -1,5 +1,4 @@ use super::*; -use crate::mm2::lp_network::P2PContext; use crate::mm2::lp_ordermatch::new_protocol::{MakerOrderUpdated, PubkeyKeepAlive}; use coins::{MmCoin, TestCoin}; use common::{block_on, executor::spawn}; @@ -9,6 +8,7 @@ use futures::{channel::mpsc, StreamExt}; use mm2_core::mm_ctx::{MmArc, MmCtx}; use mm2_libp2p::atomicdex_behaviour::AdexBehaviourCmd; use mm2_libp2p::{decode_message, PeerId}; +use mm2_net::p2p::P2PContext; use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use mocktopus::mocking::*; use rand::{seq::SliceRandom, thread_rng, Rng}; diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index 8ca80d5274..001f8bea28 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -28,8 +28,6 @@ use futures::future::{join_all, FutureExt}; use http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}; use http::request::Parts; use http::{Method, Request, Response, StatusCode}; -#[cfg(not(target_arch = "wasm32"))] -use hyper::{self, Body, Server}; use lazy_static::lazy_static; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -40,6 +38,11 @@ use serde_json::{self as json, Value as Json}; use std::borrow::Cow; use std::net::SocketAddr; +cfg_native! { + use hyper::{self, Body, Server}; + use mm2_net::sse_handler::{handle_sse, SSE_ENDPOINT}; +} + #[path = "rpc/dispatcher/dispatcher.rs"] mod dispatcher; #[path = "rpc/dispatcher/dispatcher_legacy.rs"] mod dispatcher_legacy; @@ -301,6 +304,8 @@ async fn rpc_service(req: Request, ctx_h: u32, client: SocketAddr) -> Resp Response::from_parts(parts, Body::from(body_escaped)) } +// TODO: This should exclude TCP internals, as including them results in having to +// handle various protocols within this function. #[cfg(not(target_arch = "wasm32"))] pub extern "C" fn spawn_rpc(ctx_h: u32) { use common::now_sec; @@ -351,8 +356,17 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { // then we might want to refactor into starting it ideomatically in order to benefit from a more graceful shutdown, // cf. https://github.com/hyperium/hyper/pull/1640. + let ctx = MmArc::from_ffi_handle(ctx_h).expect("No context"); + + let is_event_stream_enabled = ctx.event_stream_configuration.is_some(); + let make_svc_fut = move |remote_addr: SocketAddr| async move { Ok::<_, Infallible>(service_fn(move |req: Request| async move { + if is_event_stream_enabled && req.uri().path() == SSE_ENDPOINT { + let res = handle_sse(req, ctx_h).await?; + return Ok::<_, Infallible>(res); + } + let res = rpc_service(req, ctx_h, remote_addr).await; Ok::<_, Infallible>(res) })) @@ -417,8 +431,6 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { }; } - let ctx = MmArc::from_ffi_handle(ctx_h).expect("No context"); - let rpc_ip_port = ctx .rpc_ip_port() .unwrap_or_else(|err| panic!("Invalid RPC port: {}", err)); diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs index 7b5d68b8d0..e060175858 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs @@ -27,6 +27,7 @@ use futures::compat::Future01CompatExt; use http::Response; use mm2_core::mm_ctx::MmArc; use mm2_metrics::MetricsOps; +use mm2_net::p2p::P2PContext; use mm2_number::construct_detailed; use mm2_rpc::data::legacy::{BalanceResponse, CoinInitResponse, Mm2RpcResult, MmVersionResponse, Status}; use serde_json::{self as json, Value as Json}; @@ -306,7 +307,6 @@ pub fn version(ctx: MmArc) -> HyRes { } pub async fn get_peers_info(ctx: MmArc) -> Result>, String> { - use crate::mm2::lp_network::P2PContext; use mm2_libp2p::atomicdex_behaviour::get_peers_info; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); @@ -319,7 +319,6 @@ pub async fn get_peers_info(ctx: MmArc) -> Result>, String> { } pub async fn get_gossip_mesh(ctx: MmArc) -> Result>, String> { - use crate::mm2::lp_network::P2PContext; use mm2_libp2p::atomicdex_behaviour::get_gossip_mesh; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); @@ -332,7 +331,6 @@ pub async fn get_gossip_mesh(ctx: MmArc) -> Result>, String> { } pub async fn get_gossip_peer_topics(ctx: MmArc) -> Result>, String> { - use crate::mm2::lp_network::P2PContext; use mm2_libp2p::atomicdex_behaviour::get_gossip_peer_topics; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); @@ -345,7 +343,6 @@ pub async fn get_gossip_peer_topics(ctx: MmArc) -> Result>, Str } pub async fn get_gossip_topic_peers(ctx: MmArc) -> Result>, String> { - use crate::mm2::lp_network::P2PContext; use mm2_libp2p::atomicdex_behaviour::get_gossip_topic_peers; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); @@ -358,7 +355,6 @@ pub async fn get_gossip_topic_peers(ctx: MmArc) -> Result>, Str } pub async fn get_relay_mesh(ctx: MmArc) -> Result>, String> { - use crate::mm2::lp_network::P2PContext; use mm2_libp2p::atomicdex_behaviour::get_relay_mesh; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index c2da301fa6..62da4d0446 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -7,22 +7,26 @@ edition = "2018" doctest = false [dependencies] +async-stream = "0.3" async-trait = "0.1" -serde = "1" -serde_json = { version = "1", features = ["preserve_order", "raw_value"] } bytes = "1.1" cfg-if = "1.0" common = { path = "../common" } +derive_more = "0.99" ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } +futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } +http = "0.2" +lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } +mm2_event_stream = { path = "../mm2_event_stream"} +mm2-libp2p = { path = "../mm2_libp2p" } mm2_state_machine = { path = "../mm2_state_machine" } -derive_more = "0.99" -http = "0.2" -rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } -futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } -lazy_static = "1.4" +parking_lot = { version = "0.12.0", features = ["nightly"] } prost = "0.10" +rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } +serde = "1" +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } [target.'cfg(target_arch = "wasm32")'.dependencies] gstuff = { version = "0.7", features = ["nightly"] } @@ -34,8 +38,11 @@ js-sys = "0.3.27" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures-util = { version = "0.3" } -hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp"] } +hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp", "stream"] } gstuff = { version = "0.7", features = ["nightly"] } rustls = { version = "0.20", default-features = false } tokio = { version = "1.20" } tokio-rustls = { version = "0.23", default-features = false } + +[dev-dependencies] +mocktopus = "0.8.0" diff --git a/mm2src/mm2_net/src/lib.rs b/mm2src/mm2_net/src/lib.rs index 99935bd25b..ed70e093fa 100644 --- a/mm2src/mm2_net/src/lib.rs +++ b/mm2src/mm2_net/src/lib.rs @@ -1,8 +1,11 @@ pub mod grpc_web; +pub mod p2p; pub mod transport; #[cfg(not(target_arch = "wasm32"))] pub mod ip_addr; #[cfg(not(target_arch = "wasm32"))] pub mod native_http; #[cfg(not(target_arch = "wasm32"))] pub mod native_tls; +#[cfg(not(target_arch = "wasm32"))] pub mod network_event; +#[cfg(not(target_arch = "wasm32"))] pub mod sse_handler; #[cfg(target_arch = "wasm32")] pub mod wasm_http; #[cfg(target_arch = "wasm32")] pub mod wasm_ws; diff --git a/mm2src/mm2_net/src/network_event.rs b/mm2src/mm2_net/src/network_event.rs new file mode 100644 index 0000000000..6dd5ee4396 --- /dev/null +++ b/mm2src/mm2_net/src/network_event.rs @@ -0,0 +1,61 @@ +use crate::p2p::P2PContext; +use async_trait::async_trait; +use common::{executor::{SpawnFuture, Timer}, + log::info}; +use mm2_core::mm_ctx::MmArc; +pub use mm2_event_stream::behaviour::EventBehaviour; +use mm2_event_stream::{Event, EventStreamConfiguration}; +use mm2_libp2p::atomicdex_behaviour; +use serde_json::json; + +pub struct NetworkEvent { + ctx: MmArc, +} + +impl NetworkEvent { + pub fn new(ctx: MmArc) -> Self { Self { ctx } } +} + +#[async_trait] +impl EventBehaviour for NetworkEvent { + const EVENT_NAME: &'static str = "NETWORK"; + + async fn handle(self, interval: f64) { + let p2p_ctx = P2PContext::fetch_from_mm_arc(&self.ctx); + + loop { + let p2p_cmd_tx = p2p_ctx.cmd_tx.lock().clone(); + + let peers_info = atomicdex_behaviour::get_peers_info(p2p_cmd_tx.clone()).await; + let gossip_mesh = atomicdex_behaviour::get_gossip_mesh(p2p_cmd_tx.clone()).await; + let gossip_peer_topics = atomicdex_behaviour::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; + let gossip_topic_peers = atomicdex_behaviour::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; + let relay_mesh = atomicdex_behaviour::get_relay_mesh(p2p_cmd_tx).await; + + let event_data = json!({ + "peers_info": peers_info, + "gossip_mesh": gossip_mesh, + "gossip_peer_topics": gossip_peer_topics, + "gossip_topic_peers": gossip_topic_peers, + "relay_mesh": relay_mesh, + }); + + self.ctx + .stream_channel_controller + .broadcast(Event::new(Self::EVENT_NAME.to_string(), event_data.to_string())) + .await; + + Timer::sleep(interval).await; + } + } + + fn spawn_if_active(self, config: &EventStreamConfiguration) { + if let Some(event) = config.get_event(Self::EVENT_NAME) { + info!( + "NETWORK event is activated with {} seconds interval.", + event.stream_interval_seconds + ); + self.ctx.spawner().spawn(self.handle(event.stream_interval_seconds)); + } + } +} diff --git a/mm2src/mm2_net/src/p2p.rs b/mm2src/mm2_net/src/p2p.rs new file mode 100644 index 0000000000..74ee9dd9f1 --- /dev/null +++ b/mm2src/mm2_net/src/p2p.rs @@ -0,0 +1,40 @@ +use mm2_core::mm_ctx::MmArc; +use mm2_libp2p::atomicdex_behaviour::AdexCmdTx; +#[cfg(test)] use mocktopus::macros::*; +use parking_lot::Mutex; +use std::sync::Arc; + +pub struct P2PContext { + /// Using Mutex helps to prevent cloning which can actually result to channel being unbounded in case of using 1 tx clone per 1 message. + pub cmd_tx: Mutex, +} + +// `mockable` violates these +#[allow( + clippy::forget_ref, + clippy::forget_copy, + clippy::swap_ptr_to_ref, + clippy::forget_non_drop, + clippy::let_unit_value +)] +#[cfg_attr(test, mockable)] +impl P2PContext { + pub fn new(cmd_tx: AdexCmdTx) -> Self { + P2PContext { + cmd_tx: Mutex::new(cmd_tx), + } + } + + pub fn store_to_mm_arc(self, ctx: &MmArc) { *ctx.p2p_ctx.lock().unwrap() = Some(Arc::new(self)) } + + pub fn fetch_from_mm_arc(ctx: &MmArc) -> Arc { + ctx.p2p_ctx + .lock() + .unwrap() + .as_ref() + .unwrap() + .clone() + .downcast() + .unwrap() + } +} diff --git a/mm2src/mm2_net/src/sse_handler.rs b/mm2src/mm2_net/src/sse_handler.rs new file mode 100644 index 0000000000..f57018ca04 --- /dev/null +++ b/mm2src/mm2_net/src/sse_handler.rs @@ -0,0 +1,78 @@ +use hyper::{body::Bytes, Body, Request, Response}; +use mm2_core::mm_ctx::MmArc; +use serde_json::json; +use std::convert::Infallible; + +pub const SSE_ENDPOINT: &str = "/event-stream"; + +/// Handles broadcasted messages from `mm2_event_stream` continuously. +pub async fn handle_sse(request: Request, ctx_h: u32) -> Result, Infallible> { + // This is only called once for per client on the initialization, + // meaning this is not a resource intensive computation. + let ctx = match MmArc::from_ffi_handle(ctx_h) { + Ok(ctx) => ctx, + Err(err) => return handle_internal_error(err).await, + }; + + let config = match &ctx.event_stream_configuration { + Some(config) => config, + None => { + return handle_internal_error( + "Event stream configuration couldn't be found. This should never happen.".to_string(), + ) + .await + }, + }; + + let filtered_events = request + .uri() + .query() + .and_then(|query| { + query + .split('&') + .find(|param| param.starts_with("filter=")) + .map(|param| param.trim_start_matches("filter=")) + }) + .map_or(Vec::new(), |events_param| { + events_param.split(',').map(|event| event.to_string()).collect() + }); + + let mut channel_controller = ctx.stream_channel_controller.clone(); + let mut rx = channel_controller.create_channel(config.total_active_events()); + let body = Body::wrap_stream(async_stream::stream! { + while let Some(event) = rx.recv().await { + // If there are no filtered events, that means we want to + // stream out all the events. + if filtered_events.is_empty() || filtered_events.contains(&event.event_type().to_owned()) { + let data = json!({ + "_type": event.event_type(), + "message": event.message(), + }); + + yield Ok::<_, hyper::Error>(Bytes::from(format!("data: {data} \n\n"))); + } + } + }); + + let response = Response::builder() + .status(200) + .header("Content-Type", "text/event-stream") + .header("Cache-Control", "no-cache") + .header("Access-Control-Allow-Origin", &config.access_control_allow_origin) + .body(body); + + match response { + Ok(res) => Ok(res), + Err(err) => return handle_internal_error(err.to_string()).await, + } +} + +/// Fallback function for handling errors in SSE connections +async fn handle_internal_error(message: String) -> Result, Infallible> { + let response = Response::builder() + .status(500) + .body(Body::from(message)) + .expect("Returning 500 should never fail."); + + Ok(response) +} From a2cb99bfff328da74f9fb5cdfc877ffa1ef7bafe Mon Sep 17 00:00:00 2001 From: Onur Date: Mon, 2 Oct 2023 13:54:51 +0300 Subject: [PATCH 10/40] feat(network): upgrade p2p layer (#1878) This commit brings new p2p layer that uses the latest stable libp2p version and deprecates the old p2p layer. --------- Signed-off-by: onur-ozkan --- .github/workflows/dev-build.yml | 4 +- .github/workflows/release-build.yml | 4 +- Cargo.lock | 2021 ++++++++++------- Cargo.toml | 9 +- README.md | 4 +- docs/HEAPTRACK.md | 2 +- examples/wasm/index.html | 2 +- mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs | 6 +- .../src/tests/http_mock_data/orderbook.http | 2 +- .../eth/web3_transport/http_transport.rs | 4 +- mm2src/mm2_libp2p/src/atomicdex_behaviour.rs | 8 +- mm2src/mm2_libp2p/src/network.rs | 58 +- mm2src/mm2_main/Cargo.toml | 2 +- mm2src/mm2_main/src/lp_native_dex.rs | 15 +- mm2src/mm2_main/src/lp_network.rs | 212 +- mm2src/mm2_main/src/lp_ordermatch.rs | 78 +- .../src/lp_ordermatch/new_protocol.rs | 2 +- mm2src/mm2_main/src/lp_swap.rs | 8 +- mm2src/mm2_main/src/ordermatch_tests.rs | 2 +- .../src/rpc/lp_commands/lp_commands_legacy.rs | 15 +- mm2src/mm2_main/src/wasm_tests.rs | 4 +- .../tests/mm2_tests/best_orders_tests.rs | 4 + mm2src/mm2_main/tests/mm2_tests/iris_swap.rs | 273 --- .../tests/mm2_tests/mm2_tests_inner.rs | 7 +- mm2src/mm2_main/tests/mm2_tests/mod.rs | 1 - .../tests/mm2_tests/tendermint_tests.rs | 518 ++++- mm2src/mm2_net/Cargo.toml | 2 +- mm2src/mm2_net/src/network_event.rs | 12 +- mm2src/mm2_net/src/p2p.rs | 2 +- mm2src/mm2_p2p/Cargo.toml | 41 + mm2src/mm2_p2p/src/behaviours/atomicdex.rs | 1038 +++++++++ mm2src/mm2_p2p/src/behaviours/mod.rs | 461 ++++ mm2src/mm2_p2p/src/behaviours/peer_store.rs | 183 ++ .../mm2_p2p/src/behaviours/peers_exchange.rs | 394 ++++ mm2src/mm2_p2p/src/behaviours/ping.rs | 80 + .../src/behaviours/request_response.rs | 414 ++++ mm2src/mm2_p2p/src/lib.rs | 186 ++ mm2src/mm2_p2p/src/network.rs | 66 + mm2src/mm2_p2p/src/relay_address.rs | 187 ++ mm2src/mm2_p2p/src/swarm_runtime.rs | 34 + scripts/ci/android-ndk.sh | 9 +- 41 files changed, 5013 insertions(+), 1361 deletions(-) delete mode 100644 mm2src/mm2_main/tests/mm2_tests/iris_swap.rs create mode 100644 mm2src/mm2_p2p/Cargo.toml create mode 100644 mm2src/mm2_p2p/src/behaviours/atomicdex.rs create mode 100644 mm2src/mm2_p2p/src/behaviours/mod.rs create mode 100644 mm2src/mm2_p2p/src/behaviours/peer_store.rs create mode 100644 mm2src/mm2_p2p/src/behaviours/peers_exchange.rs create mode 100644 mm2src/mm2_p2p/src/behaviours/ping.rs create mode 100644 mm2src/mm2_p2p/src/behaviours/request_response.rs create mode 100644 mm2src/mm2_p2p/src/lib.rs create mode 100644 mm2src/mm2_p2p/src/network.rs create mode 100644 mm2src/mm2_p2p/src/relay_address.rs create mode 100644 mm2src/mm2_p2p/src/swarm_runtime.rs diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 90873357ea..4130b783ee 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -374,7 +374,7 @@ jobs: rustup target add aarch64-linux-android - name: Setup NDK - run: ./scripts/ci/android-ndk.sh x86 21 + run: ./scripts/ci/android-ndk.sh x86 23 - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -440,7 +440,7 @@ jobs: rustup target add armv7-linux-androideabi - name: Setup NDK - run: ./scripts/ci/android-ndk.sh x86 21 + run: ./scripts/ci/android-ndk.sh x86 23 - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 020d518434..d93777a1a8 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -336,7 +336,7 @@ jobs: rustup target add aarch64-linux-android - name: Setup NDK - run: ./scripts/ci/android-ndk.sh x86 21 + run: ./scripts/ci/android-ndk.sh x86 23 - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' @@ -396,7 +396,7 @@ jobs: rustup target add armv7-linux-androideabi - name: Setup NDK - run: ./scripts/ci/android-ndk.sh x86 21 + run: ./scripts/ci/android-ndk.sh x86 23 - name: Calculate commit hash for PR commit if: github.event_name == 'pull_request' diff --git a/Cargo.lock b/Cargo.lock index 1f7f1ee9f1..296fc68f66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -149,9 +158,9 @@ checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" [[package]] name = "asn1_der" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3" +checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" [[package]] name = "assert_matches" @@ -159,6 +168,35 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg 1.1.0", + "cfg-if 1.0.0", + "concurrent-queue 2.2.0", + "futures-lite", + "log", + "parking 2.1.0", + "polling", + "rustix 0.37.7", + "slab", + "socket2 0.4.9", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + [[package]] name = "async-std" version = "1.6.2" @@ -172,7 +210,7 @@ dependencies = [ "futures-io", "futures-timer", "kv-log-macro", - "log 0.4.17", + "log", "memchr", "num_cpus", "once_cell", @@ -199,8 +237,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -216,8 +254,8 @@ version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -227,41 +265,13 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-sink", "futures-util", "memchr", "pin-project-lite 0.2.9", ] -[[package]] -name = "atomicdex-gossipsub" -version = "0.20.0" -dependencies = [ - "async-std", - "base64 0.11.0", - "byteorder 1.4.3", - "bytes 0.5.6", - "common", - "env_logger", - "fnv", - "futures 0.3.15", - "futures_codec", - "libp2p-core", - "libp2p-plaintext", - "libp2p-swarm", - "libp2p-yamux", - "log 0.4.17", - "prost", - "prost-build", - "quickcheck", - "rand 0.7.3", - "sha2 0.9.9", - "smallvec 1.6.1", - "unsigned-varint 0.4.0", - "wasm-timer", -] - [[package]] name = "atty" version = "0.2.14" @@ -294,7 +304,7 @@ dependencies = [ "async-trait", "axum-core", "bitflags", - "bytes 1.1.0", + "bytes 1.4.0", "futures-util", "http 0.2.7", "http-body 0.4.5", @@ -321,7 +331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.4.0", "futures-util", "http 0.2.7", "http-body 0.4.5", @@ -345,6 +355,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.1.1" @@ -369,7 +385,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" dependencies = [ - "byteorder 1.4.3", + "byteorder", ] [[package]] @@ -416,7 +432,7 @@ checksum = "7089887635778eabf0038a166f586eee5413fb85c8fa6c9a754914f0f644f49f" dependencies = [ "bitvec 0.18.5", "blake2s_simd", - "byteorder 1.4.3", + "byteorder", "crossbeam 0.7.3", "ff 0.8.0", "futures 0.1.29", @@ -455,7 +471,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d0f0fc59c7ba0333eed9dcc1b6980baa7b7a4dc7c6c5885994d0674f7adf34" dependencies = [ - "bs58", + "bs58 0.4.0", "hkd32", "hmac 0.11.0", "ripemd160", @@ -543,7 +559,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -579,7 +595,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -590,7 +606,7 @@ checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ "block-padding 0.1.5", "byte-tools", - "byteorder 1.4.3", + "byteorder", "generic-array 0.12.4", ] @@ -647,7 +663,7 @@ dependencies = [ "futures-channel", "futures-util", "once_cell", - "parking", + "parking 1.0.2", "waker-fn", ] @@ -684,7 +700,7 @@ dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", - "proc-macro2 1.0.58", + "proc-macro2 1.0.63", "syn 1.0.95", ] @@ -694,8 +710,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -705,8 +721,8 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -719,6 +735,15 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -762,17 +787,11 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] -[[package]] -name = "byteorder" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" - [[package]] name = "byteorder" version = "1.4.3" @@ -785,21 +804,15 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" dependencies = [ - "byteorder 1.4.3", + "byteorder", "iovec", ] [[package]] name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - -[[package]] -name = "bytes" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "bzip2" @@ -834,7 +847,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61bf7211aad104ce2769ec05efcdfabf85ee84ac92461d142f22cf8badd0e54c" dependencies = [ - "errno", + "errno 0.2.8", "libc", "thiserror", ] @@ -862,9 +875,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", @@ -874,9 +887,9 @@ dependencies = [ [[package]] name = "chacha20poly1305" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", @@ -987,7 +1000,7 @@ dependencies = [ "bitcoin", "bitcoin_hashes", "bitcrypto", - "byteorder 1.4.3", + "byteorder", "bytes 0.4.12", "cfg-if 1.0.0", "chain", @@ -1006,7 +1019,7 @@ dependencies = [ "ethereum-types", "ethkey", "futures 0.1.29", - "futures 0.3.15", + "futures 0.3.28", "group 0.8.0", "gstuff", "hex 0.4.3", @@ -1083,7 +1096,7 @@ dependencies = [ "wasm-bindgen-test", "web-sys", "web3", - "webpki-roots", + "webpki-roots 0.22.3", "winapi", "zbase32", "zcash_client_backend", @@ -1102,7 +1115,7 @@ dependencies = [ "crypto", "derive_more", "ethereum-types", - "futures 0.3.15", + "futures 0.3.28", "hex 0.4.3", "lightning", "lightning-background-processor", @@ -1130,7 +1143,7 @@ dependencies = [ "arrayref", "async-trait", "backtrace", - "bytes 1.1.0", + "bytes 1.4.0", "cc", "cfg-if 1.0.0", "chrono", @@ -1140,7 +1153,7 @@ dependencies = [ "findshlibs", "fnv", "futures 0.1.29", - "futures 0.3.15", + "futures 0.3.28", "futures-timer", "gstuff", "hex 0.4.3", @@ -1154,7 +1167,7 @@ dependencies = [ "lazy_static", "libc", "lightning", - "log 0.4.17", + "log", "parking_lot 0.12.0", "parking_lot_core 0.6.2", "primitive-types", @@ -1187,6 +1200,15 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils 0.8.16", +] + [[package]] name = "console" version = "0.15.0" @@ -1218,7 +1240,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494" dependencies = [ - "log 0.4.17", + "log", "web-sys", ] @@ -1234,6 +1256,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1333,7 +1365,7 @@ dependencies = [ "crossbeam-deque 0.8.1", "crossbeam-epoch 0.9.5", "crossbeam-queue 0.3.8", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", ] [[package]] @@ -1353,7 +1385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", ] [[package]] @@ -1375,7 +1407,7 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch 0.9.5", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", ] [[package]] @@ -1400,7 +1432,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", "lazy_static", "memoffset 0.6.4", "scopeguard", @@ -1424,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", ] [[package]] @@ -1440,12 +1472,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", - "lazy_static", ] [[package]] @@ -1462,12 +1493,12 @@ dependencies = [ "async-trait", "bip32", "bitcrypto", - "bs58", + "bs58 0.4.0", "common", "derive_more", "enum-primitive-derive", "enum_from", - "futures 0.3.15", + "futures 0.3.28", "hex 0.4.3", "http 0.2.7", "hw_common", @@ -1572,16 +1603,6 @@ dependencies = [ "crypto_api", ] -[[package]] -name = "ctor" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" -dependencies = [ - "quote 1.0.27", - "syn 1.0.95", -] - [[package]] name = "ctr" version = "0.7.0" @@ -1591,23 +1612,13 @@ dependencies = [ "cipher", ] -[[package]] -name = "cuckoofilter" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dd43f7cfaffe0a386636a10baea2ee05cc50df3b77bea4a456c9572a939bf1f" -dependencies = [ - "byteorder 0.5.3", - "rand 0.3.23", -] - [[package]] name = "cuckoofilter" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" dependencies = [ - "byteorder 1.4.3", + "byteorder", "fnv", "rand 0.7.3", ] @@ -1618,7 +1629,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ - "byteorder 1.4.3", + "byteorder", "digest 0.9.0", "rand_core 0.5.1", "subtle 2.4.0", @@ -1627,13 +1638,14 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-pre.1" +version = "4.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" +checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" dependencies = [ - "byteorder 1.4.3", - "digest 0.9.0", - "rand_core 0.6.3", + "cfg-if 1.0.0", + "fiat-crypto", + "packed_simd_2", + "platforms", "subtle 2.4.0", "zeroize", ] @@ -1659,8 +1671,8 @@ dependencies = [ "cc", "codespan-reporting", "lazy_static", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "scratch", "syn 1.0.95", ] @@ -1677,8 +1689,8 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -1695,9 +1707,29 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.2.1" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + +[[package]] +name = "data-encoding-macro" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69" +checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +dependencies = [ + "data-encoding", + "syn 1.0.95", +] [[package]] name = "db_common" @@ -1705,7 +1737,7 @@ version = "0.1.0" dependencies = [ "common", "hex 0.4.3", - "log 0.4.17", + "log", "rusqlite", "sql-builder", "uuid 1.2.2", @@ -1751,8 +1783,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -1762,8 +1794,8 @@ version = "0.99.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -1799,9 +1831,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.2", "crypto-common", @@ -1955,7 +1987,7 @@ dependencies = [ "derivation-path 0.2.0", "ed25519-dalek", "hmac 0.12.1", - "sha2 0.10.2", + "sha2 0.10.7", ] [[package]] @@ -1966,9 +1998,9 @@ checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" [[package]] name = "either" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -2011,13 +2043,13 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-as-inner" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -2028,7 +2060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c375b9c5eadb68d0a6efee2999fef292f45854c3444c86f09d8ab086ba942b0e" dependencies = [ "num-traits", - "quote 1.0.27", + "quote 1.0.28", "syn 1.0.95", ] @@ -2037,8 +2069,8 @@ name = "enum_from" version = "0.1.0" dependencies = [ "itertools", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -2050,7 +2082,7 @@ checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime", - "log 0.4.17", + "log", "regex", "termcolor", ] @@ -2061,7 +2093,7 @@ version = "0.1.0" source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.3.0#443fef0cf301b04375f76128e7436b4de02d1c4d" dependencies = [ "blake2b_simd", - "byteorder 1.4.3", + "byteorder", ] [[package]] @@ -2075,6 +2107,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -2118,7 +2161,7 @@ dependencies = [ [[package]] name = "ethcore-transaction" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#223da7972113a548531804510708f87b629a48fd" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" dependencies = [ "ethereum-types", "ethkey", @@ -2145,12 +2188,12 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#223da7972113a548531804510708f87b629a48fd" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" dependencies = [ - "byteorder 1.4.3", + "byteorder", "edit-distance", "ethereum-types", - "log 0.3.9", + "log", "mem", "rand 0.6.5", "rustc-hex", @@ -2160,6 +2203,12 @@ dependencies = [ "tiny-keccak 1.4.4", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "eyre" version = "0.6.8" @@ -2186,8 +2235,8 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", "synstructure", ] @@ -2246,6 +2295,12 @@ dependencies = [ "subtle 2.4.0", ] +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + [[package]] name = "filetime" version = "0.2.15" @@ -2274,7 +2329,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ - "byteorder 1.4.3", + "byteorder", "rand 0.8.4", "rustc-hex", "static_assertions", @@ -2295,7 +2350,6 @@ dependencies = [ "cfg-if 1.0.0", "crc32fast", "libc", - "libz-sys", "miniz_oxide 0.4.0", ] @@ -2333,7 +2387,7 @@ checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", "cipher", - "libm", + "libm 0.2.7", "num-bigint", "num-integer", "num-traits", @@ -2371,9 +2425,9 @@ checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" [[package]] name = "futures" -version = "0.3.15" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -2386,9 +2440,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -2396,9 +2450,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-cpupool" @@ -2412,9 +2466,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.15" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -2424,19 +2478,34 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking 2.1.0", + "pin-project-lite 0.2.9", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] @@ -2463,15 +2532,26 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-ticker" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9763058047f713632a52e916cc7f6a4b3fc6e9fc1ff8c5b1dc49e5a89041682e" +dependencies = [ + "futures 0.3.28", + "futures-timer", + "instant", +] [[package]] name = "futures-timer" @@ -2480,14 +2560,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" dependencies = [ "gloo-timers", - "send_wrapper", + "send_wrapper 0.4.0", ] [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures 0.1.29", "futures-channel", @@ -2502,18 +2582,6 @@ dependencies = [ "slab", ] -[[package]] -name = "futures_codec" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce54d63f8b0c75023ed920d46fd71d0cbbb830b0ee012726b5b4f506fb6dea5b" -dependencies = [ - "bytes 0.5.6", - "futures 0.3.15", - "memchr", - "pin-project 0.4.29", -] - [[package]] name = "generic-array" version = "0.12.4" @@ -2615,7 +2683,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1" dependencies = [ - "byteorder 1.4.3", + "byteorder", "ff 0.8.0", "rand_core 0.5.1", "subtle 2.4.0", @@ -2648,7 +2716,7 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "fnv", "futures-core", "futures-sink", @@ -2739,6 +2807,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + [[package]] name = "hidapi" version = "1.4.1" @@ -2808,7 +2882,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -2850,7 +2924,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "fnv", "itoa 1.0.1", ] @@ -2873,7 +2947,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "http 0.2.7", "pin-project-lite 0.2.9", ] @@ -2910,7 +2984,7 @@ dependencies = [ "bip32", "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "js-sys", "mm2_err_handle", "rusb", @@ -2929,7 +3003,7 @@ version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-channel", "futures-core", "futures-util", @@ -2958,7 +3032,7 @@ dependencies = [ "rustls 0.20.4", "tokio", "tokio-rustls", - "webpki-roots", + "webpki-roots 0.22.3", ] [[package]] @@ -3018,6 +3092,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "if-watch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9465340214b296cd17a0009acdb890d6160010b8adf8f78a00d0d7ab270f79f" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures 0.3.28", + "if-addrs", + "ipnet", + "log", + "rtnetlink", + "system-configuration", + "tokio", + "windows", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -3051,8 +3144,8 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -3135,9 +3228,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "itertools" @@ -3192,9 +3285,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -3205,10 +3298,10 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.15", + "futures 0.3.28", "futures-executor", "futures-util", - "log 0.4.17", + "log", "serde", "serde_derive", "serde_json", @@ -3280,7 +3373,7 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ff57d6d215f7ca7eb35a9a64d656ba4d9d2bef114d741dc08048e75e2f5d418" dependencies = [ - "log 0.4.17", + "log", ] [[package]] @@ -3304,9 +3397,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libloading" @@ -3318,6 +3411,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libm" version = "0.2.7" @@ -3326,78 +3425,94 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libp2p" -version = "0.45.1" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.52.1" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "bytes 1.1.0", - "futures 0.3.15", + "bytes 1.4.0", + "futures 0.3.28", "futures-timer", "getrandom 0.2.9", "instant", - "lazy_static", + "libp2p-allow-block-list", + "libp2p-connection-limits", "libp2p-core", "libp2p-dns", - "libp2p-floodsub 0.36.0", + "libp2p-floodsub", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-mdns", "libp2p-metrics", - "libp2p-mplex", "libp2p-noise", "libp2p-ping", "libp2p-request-response", "libp2p-swarm", - "libp2p-swarm-derive", "libp2p-tcp", "libp2p-wasm-ext", "libp2p-websocket", + "libp2p-yamux", "multiaddr", - "parking_lot 0.12.0", - "pin-project 1.0.10", - "rand 0.7.3", - "smallvec 1.6.1", + "pin-project", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.2.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.2.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "void", ] [[package]] name = "libp2p-core" -version = "0.33.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.40.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", "either", "fnv", - "futures 0.3.15", + "futures 0.3.28", "futures-timer", "instant", - "lazy_static", - "libsecp256k1 0.7.0", - "log 0.4.17", + "libp2p-identity", + "log", "multiaddr", "multihash", "multistream-select", - "p256", + "once_cell", "parking_lot 0.12.0", - "pin-project 1.0.10", - "prost", - "prost-build", + "pin-project", + "quick-protobuf", "rand 0.8.4", - "ring", "rw-stream-sink", - "sha2 0.10.2", "smallvec 1.6.1", "thiserror", - "unsigned-varint 0.7.1", + "unsigned-varint", "void", - "zeroize", ] [[package]] name = "libp2p-dns" -version = "0.33.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.40.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "futures 0.3.15", + "futures 0.3.28", "libp2p-core", - "log 0.4.17", + "libp2p-identity", + "log", "parking_lot 0.12.0", "smallvec 1.6.1", "trust-dns-resolver", @@ -3405,215 +3520,279 @@ dependencies = [ [[package]] name = "libp2p-floodsub" -version = "0.22.0" +version = "0.43.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "cuckoofilter 0.3.2", - "futures 0.3.15", + "asynchronous-codec", + "cuckoofilter", + "fnv", + "futures 0.3.28", "libp2p-core", + "libp2p-identity", "libp2p-swarm", - "prost", - "prost-build", - "rand 0.7.3", + "log", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.4", "smallvec 1.6.1", + "thiserror", ] [[package]] -name = "libp2p-floodsub" -version = "0.36.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +name = "libp2p-gossipsub" +version = "0.45.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "cuckoofilter 0.5.0", + "asynchronous-codec", + "base64 0.21.2", + "byteorder", + "bytes 1.4.0", + "either", "fnv", - "futures 0.3.15", + "futures 0.3.28", + "futures-ticker", + "getrandom 0.2.9", + "hex_fmt", + "instant", "libp2p-core", + "libp2p-identity", "libp2p-swarm", - "log 0.4.17", - "prost", - "prost-build", - "rand 0.7.3", + "log", + "prometheus-client", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.4", + "regex", + "sha2 0.10.7", "smallvec 1.6.1", + "unsigned-varint", + "void", ] [[package]] -name = "libp2p-metrics" -version = "0.6.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +name = "libp2p-identify" +version = "0.43.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ + "asynchronous-codec", + "either", + "futures 0.3.28", + "futures-timer", "libp2p-core", - "libp2p-ping", + "libp2p-identity", "libp2p-swarm", - "prometheus-client", + "log", + "lru 0.10.1", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec 1.6.1", + "thiserror", + "void", ] [[package]] -name = "libp2p-mplex" -version = "0.33.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +name = "libp2p-identity" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2874d9c6575f1d7a151022af5c42bb0ffdcdfbafe0a6fd039de870b384835a2" dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.15", + "asn1_der", + "bs58 0.5.0", + "ed25519-dalek", + "libsecp256k1 0.7.0", + "log", + "multihash", + "quick-protobuf", + "rand 0.8.4", + "sha2 0.10.7", + "thiserror", + "zeroize", +] + +[[package]] +name = "libp2p-mdns" +version = "0.44.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +dependencies = [ + "data-encoding", + "futures 0.3.28", + "if-watch", "libp2p-core", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.12.0", - "rand 0.7.3", + "libp2p-identity", + "libp2p-swarm", + "log", + "rand 0.8.4", "smallvec 1.6.1", - "unsigned-varint 0.7.1", + "socket2 0.5.3", + "tokio", + "trust-dns-proto", + "void", +] + +[[package]] +name = "libp2p-metrics" +version = "0.13.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +dependencies = [ + "instant", + "libp2p-core", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-ping", + "libp2p-swarm", + "once_cell", + "prometheus-client", ] [[package]] name = "libp2p-noise" -version = "0.36.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.43.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "curve25519-dalek 3.2.0", - "futures 0.3.15", - "lazy_static", + "futures 0.3.28", "libp2p-core", - "log 0.4.17", - "prost", - "prost-build", + "libp2p-identity", + "log", + "multiaddr", + "multihash", + "once_cell", + "quick-protobuf", "rand 0.8.4", - "sha2 0.10.2", + "sha2 0.10.7", "snow", "static_assertions", + "thiserror", "x25519-dalek", "zeroize", ] [[package]] name = "libp2p-ping" -version = "0.36.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.43.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "futures 0.3.15", + "either", + "futures 0.3.28", "futures-timer", "instant", "libp2p-core", + "libp2p-identity", "libp2p-swarm", - "log 0.4.17", - "rand 0.7.3", - "void", -] - -[[package]] -name = "libp2p-plaintext" -version = "0.33.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.15", - "libp2p-core", - "log 0.4.17", - "prost", - "prost-build", - "unsigned-varint 0.7.1", + "log", + "rand 0.8.4", "void", ] [[package]] name = "libp2p-request-response" -version = "0.18.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.25.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ "async-trait", - "bytes 1.1.0", - "futures 0.3.15", + "futures 0.3.28", "instant", "libp2p-core", + "libp2p-identity", "libp2p-swarm", - "log 0.4.17", - "rand 0.7.3", + "log", + "rand 0.8.4", "smallvec 1.6.1", - "unsigned-varint 0.7.1", + "void", ] [[package]] name = "libp2p-swarm" -version = "0.36.1" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.43.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ "either", "fnv", - "futures 0.3.15", + "futures 0.3.28", "futures-timer", "instant", "libp2p-core", - "log 0.4.17", - "pin-project 1.0.10", - "rand 0.7.3", + "libp2p-identity", + "libp2p-swarm-derive", + "log", + "multistream-select", + "once_cell", + "rand 0.8.4", "smallvec 1.6.1", - "thiserror", + "tokio", "void", ] [[package]] name = "libp2p-swarm-derive" -version = "0.27.2" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.33.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "quote 1.0.27", - "syn 1.0.95", + "heck", + "proc-macro-warning", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] name = "libp2p-tcp" -version = "0.33.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.40.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "futures 0.3.15", + "futures 0.3.28", "futures-timer", - "if-addrs", - "ipnet", + "if-watch", "libc", "libp2p-core", - "log 0.4.17", - "socket2 0.4.9", + "libp2p-identity", + "log", + "socket2 0.5.3", "tokio", ] [[package]] name = "libp2p-wasm-ext" -version = "0.33.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.40.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "futures 0.3.15", + "futures 0.3.28", "js-sys", "libp2p-core", - "parity-send-wrapper", + "send_wrapper 0.6.0", "wasm-bindgen", "wasm-bindgen-futures", ] [[package]] name = "libp2p-websocket" -version = "0.35.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.42.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ "either", - "futures 0.3.15", + "futures 0.3.28", "futures-rustls 0.22.1", "libp2p-core", - "log 0.4.17", + "libp2p-identity", + "log", "parking_lot 0.12.0", "quicksink", "rw-stream-sink", "soketto", "url", - "webpki-roots", + "webpki-roots 0.23.1", ] [[package]] name = "libp2p-yamux" -version = "0.37.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.44.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "futures 0.3.15", + "futures 0.3.28", "libp2p-core", - "parking_lot 0.12.0", + "log", "thiserror", "yamux", ] @@ -3737,17 +3916,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "lightning" version = "0.0.113" @@ -3824,6 +3992,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lock_api" version = "0.4.6" @@ -3835,30 +4009,29 @@ dependencies = [ [[package]] name = "log" -version = "0.3.9" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" dependencies = [ - "log 0.4.17", + "value-bag", ] [[package]] -name = "log" -version = "0.4.17" +name = "lru" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" dependencies = [ - "cfg-if 1.0.0", - "value-bag", + "hashbrown 0.11.2", ] [[package]] name = "lru" -version = "0.7.5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" +checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.13.2", ] [[package]] @@ -3906,13 +4079,13 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#223da7972113a548531804510708f87b629a48fd" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" @@ -3987,9 +4160,9 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] @@ -3998,9 +4171,9 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" dependencies = [ - "aho-corasick", + "aho-corasick 0.7.18", "crossbeam-epoch 0.9.5", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", "hashbrown 0.13.2", "indexmap", "metrics", @@ -4042,42 +4215,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", - "log 0.4.17", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] -[[package]] -name = "mm2-libp2p" -version = "0.1.0" -dependencies = [ - "async-std", - "async-trait", - "atomicdex-gossipsub", - "common", - "derive_more", - "env_logger", - "futures 0.3.15", - "futures-rustls 0.21.1", - "hex 0.4.3", - "lazy_static", - "libp2p", - "libp2p-floodsub 0.22.0", - "log 0.4.17", - "rand 0.7.3", - "regex", - "rmp-serde", - "secp256k1 0.20.3", - "serde", - "serde_bytes", - "serde_json", - "sha2 0.9.9", - "tokio", - "void", - "wasm-bindgen-futures", - "wasm-timer", -] - [[package]] name = "mm2_bin_lib" version = "1.1.0-beta" @@ -4110,7 +4252,7 @@ dependencies = [ "common", "db_common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "futures-rustls 0.21.1", "gstuff", "hex 0.4.3", @@ -4134,7 +4276,7 @@ dependencies = [ "common", "derive_more", "enum_from", - "futures 0.3.15", + "futures 0.3.28", "hex 0.4.3", "itertools", "js-sys", @@ -4239,7 +4381,7 @@ dependencies = [ "async-std", "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "gstuff", "mm2_err_handle", "rand 0.7.3", @@ -4273,7 +4415,7 @@ dependencies = [ "enum_from", "ethereum-types", "futures 0.1.29", - "futures 0.3.15", + "futures 0.3.28", "futures-rustls 0.21.1", "gstuff", "hash-db", @@ -4288,7 +4430,6 @@ dependencies = [ "keys", "lazy_static", "libc", - "mm2-libp2p", "mm2_core", "mm2_db", "mm2_err_handle", @@ -4298,6 +4439,7 @@ dependencies = [ "mm2_metrics", "mm2_net", "mm2_number", + "mm2_p2p", "mm2_rpc", "mm2_state_machine", "mm2_test_helpers", @@ -4348,7 +4490,7 @@ dependencies = [ "async-trait", "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "itertools", "js-sys", "jsonrpc-core", @@ -4371,7 +4513,7 @@ dependencies = [ "base64 0.10.1", "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "hyper", "hyper-rustls", "itertools", @@ -4390,22 +4532,22 @@ version = "0.1.0" dependencies = [ "async-stream", "async-trait", - "bytes 1.1.0", + "bytes 1.4.0", "cfg-if 1.0.0", "common", "derive_more", "ethkey", - "futures 0.3.15", + "futures 0.3.28", "futures-util", "gstuff", "http 0.2.7", "hyper", "js-sys", "lazy_static", - "mm2-libp2p", "mm2_core", "mm2_err_handle", "mm2_event_stream", + "mm2_p2p", "mm2_state_machine", "mocktopus", "parking_lot 0.12.0", @@ -4435,13 +4577,43 @@ dependencies = [ "serde_json", ] +[[package]] +name = "mm2_p2p" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "common", + "derive_more", + "env_logger", + "futures 0.3.28", + "futures-rustls 0.21.1", + "futures-ticker", + "hex 0.4.3", + "instant", + "lazy_static", + "libp2p", + "log", + "rand 0.7.3", + "regex", + "rmp-serde", + "secp256k1 0.20.3", + "serde", + "serde_bytes", + "sha2 0.9.9", + "smallvec 1.6.1", + "syn 2.0.23", + "tokio", + "void", +] + [[package]] name = "mm2_rpc" version = "0.1.0" dependencies = [ "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "gstuff", "http 0.2.7", "mm2_err_handle", @@ -4460,20 +4632,20 @@ version = "0.1.0" dependencies = [ "async-trait", "common", - "futures 0.3.15", + "futures 0.3.28", ] [[package]] name = "mm2_test_helpers" version = "0.1.0" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "cfg-if 1.0.0", "chrono", "common", "crypto", "db_common", - "futures 0.3.15", + "futures 0.3.28", "gstuff", "http 0.2.7", "lazy_static", @@ -4507,54 +4679,49 @@ version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3048ef3680533a27f9f8e7d6a0bce44dc61e4895ea0f42709337fa1c8616fefe" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] [[package]] name = "multiaddr" -version = "0.14.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" +checksum = "92a651988b3ed3ad1bc8c87d016bb92f6f395b84ed1db9b926b32b1fc5a2c8b5" dependencies = [ "arrayref", - "bs58", - "byteorder 1.4.3", + "byteorder", "data-encoding", + "libp2p-identity", + "multibase", "multihash", "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.7.1", + "unsigned-varint", "url", ] [[package]] -name = "multihash" -version = "0.16.2" +name = "multibase" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3db354f401db558759dfc1e568d010a5d4146f4d3f637be1275ec4a3cf09689" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" dependencies = [ - "core2", - "digest 0.10.3", - "multihash-derive", - "sha2 0.10.2", - "unsigned-varint 0.7.1", + "base-x", + "data-encoding", + "data-encoding-macro", ] [[package]] -name = "multihash-derive" -version = "0.8.0" +name = "multihash" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" +checksum = "2fd59dcc2bbe70baabeac52cd22ae52c55eefe6c38ff11a9439f16a350a939f2" dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro-error", - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", - "synstructure", + "core2", + "unsigned-varint", ] [[package]] @@ -4565,15 +4732,81 @@ checksum = "d8883adfde9756c1d30b0f519c9b8c502a94b41ac62f696453c37c7fc0a958ce" [[package]] name = "multistream-select" -version = "0.11.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.13.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "bytes 1.1.0", - "futures 0.3.15", - "log 0.4.17", - "pin-project 1.0.10", + "bytes 1.4.0", + "futures 0.3.28", + "log", + "pin-project", "smallvec 1.6.1", - "unsigned-varint 0.7.1", + "unsigned-varint", +] + +[[package]] +name = "netlink-packet-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +dependencies = [ + "anyhow", + "bitflags", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +dependencies = [ + "bytes 1.4.0", + "futures 0.3.28", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +dependencies = [ + "bytes 1.4.0", + "futures 0.3.28", + "libc", + "log", + "tokio", ] [[package]] @@ -4598,6 +4831,17 @@ dependencies = [ "memoffset 0.6.4", ] +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -4635,8 +4879,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -4699,8 +4943,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -4730,9 +4974,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -4774,30 +5018,19 @@ checksum = "44a0b52c2cbaef7dffa5fec1a43274afe8bd2a644fa9fc50a9ef4ff0269b1257" dependencies = [ "Inflector", "proc-macro-error", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] [[package]] -name = "owning_ref" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "p256" -version = "0.10.1" +name = "packed_simd_2" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19736d80675fbe9fe33426268150b951a3fb8f5cfca2a23a17c85ef3adb24e3b" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" dependencies = [ - "ecdsa", - "elliptic-curve", - "sec1", - "sha2 0.9.9", + "cfg-if 1.0.0", + "libm 0.1.4", ] [[package]] @@ -4831,17 +5064,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] -[[package]] -name = "parity-send-wrapper" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" - [[package]] name = "parity-util-mem" version = "0.11.0" @@ -4852,7 +5079,7 @@ dependencies = [ "ethereum-types", "hashbrown 0.12.1", "impl-trait-for-tuples", - "lru", + "lru 0.7.5", "parity-util-mem-derive", "parking_lot 0.12.0", "primitive-types", @@ -4866,7 +5093,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.58", + "proc-macro2 1.0.63", "syn 1.0.95", "synstructure", ] @@ -4875,7 +5102,13 @@ dependencies = [ name = "parking" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a" +checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a" + +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -4982,8 +5215,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" dependencies = [ "peg-runtime", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", ] [[package]] @@ -5019,42 +5252,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" -dependencies = [ - "pin-project-internal 0.4.29", -] - -[[package]] -name = "pin-project" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" -dependencies = [ - "pin-project-internal 1.0.10", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.29" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", + "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] @@ -5092,6 +5305,28 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg 1.1.0", + "bitflags", + "cfg-if 1.0.0", + "concurrent-queue 2.2.0", + "libc", + "log", + "pin-project-lite 0.2.9", + "windows-sys 0.48.0", +] + [[package]] name = "poly1305" version = "0.7.0" @@ -5133,7 +5368,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" dependencies = [ - "proc-macro2 1.0.58", + "proc-macro2 1.0.63", "syn 1.0.95", ] @@ -5156,7 +5391,7 @@ name = "primitives" version = "0.1.0" dependencies = [ "bitcoin_hashes", - "byteorder 1.4.3", + "byteorder", "rustc-hex", "uint", ] @@ -5187,8 +5422,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", "version_check", ] @@ -5199,11 +5434,22 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "version_check", ] +[[package]] +name = "proc-macro-warning" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" +dependencies = [ + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -5215,33 +5461,33 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] [[package]] name = "prometheus-client" -version = "0.16.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1abe0255c04d15f571427a2d1e00099016506cf3297b53853acd2b7eb87825" +checksum = "78c2f43e8969d51935d2a7284878ae053ba30034cd563f673cde37ba5205685e" dependencies = [ "dtoa", "itoa 1.0.1", - "owning_ref", - "prometheus-client-derive-text-encode", + "parking_lot 0.12.0", + "prometheus-client-derive-encode", ] [[package]] -name = "prometheus-client-derive-text-encode" -version = "0.2.0" +name = "prometheus-client-derive-encode" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e12d01b9d66ad9eb4529c57666b6263fc1993cb30261d83ead658fdd932652" +checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -5251,7 +5497,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "prost-derive", ] @@ -5261,13 +5507,13 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "cfg-if 1.0.0", "cmake", "heck", "itertools", "lazy_static", - "log 0.4.17", + "log", "multimap", "petgraph", "prost", @@ -5285,8 +5531,8 @@ checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -5296,7 +5542,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "prost", ] @@ -5340,7 +5586,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" dependencies = [ - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", "libc", "mach2", "once_cell", @@ -5357,13 +5603,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quickcheck" -version = "0.9.2" +name = "quick-protobuf" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" dependencies = [ - "rand 0.7.3", - "rand_core 0.5.1", + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.2.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +dependencies = [ + "asynchronous-codec", + "bytes 1.4.0", + "quick-protobuf", + "thiserror", + "unsigned-varint", ] [[package]] @@ -5394,11 +5651,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ - "proc-macro2 1.0.58", + "proc-macro2 1.0.63", ] [[package]] @@ -5429,29 +5686,6 @@ dependencies = [ "nibble_vec", ] -[[package]] -name = "rand" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" -dependencies = [ - "libc", - "rand 0.4.6", -] - -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.6.5" @@ -5678,7 +5912,7 @@ checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" dependencies = [ "crossbeam-channel 0.5.1", "crossbeam-deque 0.8.1", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.16", "lazy_static", "num_cpus", ] @@ -5755,27 +5989,27 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c523ccaed8ac4b0288948849a350b37d3035827413c458b6a40ddb614bb4f72" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.2", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "reqwest" @@ -5784,7 +6018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.4.0", "encoding_rs", "futures-core", "futures-util", @@ -5796,7 +6030,7 @@ dependencies = [ "ipnet", "js-sys", "lazy_static", - "log 0.4.17", + "log", "mime", "percent-encoding", "pin-project-lite 0.2.9", @@ -5811,7 +6045,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.22.3", "winreg", ] @@ -5868,7 +6102,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "rustc-hex", ] @@ -5878,7 +6112,7 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" dependencies = [ - "byteorder 1.4.3", + "byteorder", "num-traits", "paste", ] @@ -5889,7 +6123,7 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" dependencies = [ - "byteorder 1.4.3", + "byteorder", "rmp", "serde", ] @@ -5911,7 +6145,7 @@ dependencies = [ "chain", "keys", "lazy_static", - "log 0.4.17", + "log", "primitives", "rustc-hex", "script", @@ -5928,7 +6162,7 @@ dependencies = [ "async-trait", "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "mm2_err_handle", "ser_error", "ser_error_derive", @@ -5936,6 +6170,21 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rtnetlink" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +dependencies = [ + "futures 0.3.28", + "log", + "netlink-packet-route", + "netlink-proto", + "nix 0.24.3", + "thiserror", + "tokio", +] + [[package]] name = "rusb" version = "0.7.0" @@ -6022,10 +6271,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", + "io-lifetimes", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.37.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +dependencies = [ + "bitflags", + "errno 0.3.1", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", "windows-sys 0.45.0", ] @@ -6036,7 +6299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", - "log 0.4.17", + "log", "ring", "sct 0.6.0", "webpki 0.21.3", @@ -6048,7 +6311,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ - "log 0.4.17", + "log", "ring", "sct 0.7.0", "webpki 0.22.0", @@ -6072,6 +6335,16 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.6" @@ -6080,11 +6353,11 @@ checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rw-stream-sink" -version = "0.3.0" -source = "git+https://github.com/libp2p/rust-libp2p.git?tag=v0.45.1#802d00e645894d8895f2f9f665b921452d992b86" +version = "0.4.0" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" dependencies = [ - "futures 0.3.15", - "pin-project 1.0.10", + "futures 0.3.28", + "pin-project", "static_assertions", ] @@ -6122,8 +6395,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -6153,7 +6426,7 @@ dependencies = [ "blake2b_simd", "chain", "keys", - "log 0.4.17", + "log", "primitives", "serde", "serialization", @@ -6266,6 +6539,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "ser_error" version = "0.1.0" @@ -6277,8 +6556,8 @@ dependencies = [ name = "ser_error_derive" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "ser_error", "syn 1.0.95", ] @@ -6318,9 +6597,9 @@ version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] @@ -6341,8 +6620,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -6374,7 +6653,7 @@ dependencies = [ name = "serialization" version = "0.1.0" dependencies = [ - "byteorder 1.4.3", + "byteorder", "derive_more", "primitives", "test_helpers", @@ -6429,13 +6708,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.1", - "digest 0.10.3", + "digest 0.10.7", ] [[package]] @@ -6456,7 +6735,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaedf34ed289ea47c2b741bb72e5357a209512d67bcd4bda44359e5bf0470f56" dependencies = [ - "digest 0.10.3", + "digest 0.10.7", "keccak", ] @@ -6464,7 +6743,7 @@ dependencies = [ name = "shared_ref_counter" version = "0.1.0" dependencies = [ - "log 0.4.17", + "log", ] [[package]] @@ -6500,9 +6779,12 @@ checksum = "68a406c1882ed7f29cd5e248c9848a80e7cb6ae0fea82346d2746f2f941c07e1" [[package]] name = "slab" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "smallvec" @@ -6527,7 +6809,7 @@ checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" dependencies = [ "async-task", "blocking", - "concurrent-queue", + "concurrent-queue 1.1.1", "fastrand", "futures-io", "futures-util", @@ -6542,18 +6824,18 @@ dependencies = [ [[package]] name = "snow" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" +checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-pre.1", + "curve25519-dalek 4.0.0-rc.1", "rand_core 0.6.3", "ring", "rustc_version 0.4.0", - "sha2 0.10.2", + "sha2 0.10.7", "subtle 2.4.0", ] @@ -6578,6 +6860,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "soketto" version = "0.7.0" @@ -6585,11 +6877,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "083624472e8817d44d02c0e55df043737ff11f279af924abdf93845717c2b75c" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", - "flate2", - "futures 0.3.15", + "bytes 1.4.0", + "futures 0.3.28", "httparse", - "log 0.4.17", + "log", "rand 0.8.4", "sha-1", ] @@ -6603,7 +6894,7 @@ dependencies = [ "Inflector", "base64 0.12.3", "bincode", - "bs58", + "bs58 0.4.0", "bv", "lazy_static", "serde", @@ -6625,7 +6916,7 @@ checksum = "0c60728aec35d772e6614319558cdccbe3f845102699b65ba5ac7497da0b626a" dependencies = [ "bincode", "bytemuck", - "log 0.4.17", + "log", "num-derive", "num-traits", "rustc_version 0.4.0", @@ -6645,7 +6936,7 @@ checksum = "1ddcd7c6adb802bc812a5a80c8de06ba0f0e8df0cca296a8b4e67cd04c16218f" dependencies = [ "bv", "fnv", - "log 0.4.17", + "log", "rand 0.7.3", "rayon", "rustc_version 0.4.0", @@ -6663,7 +6954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3435b145894971a58a08a7b6be997ec782239fdecd5edd9846cd1d6aa5986" dependencies = [ "fs_extra", - "log 0.4.17", + "log", "memmap2", "rand 0.7.3", "rayon", @@ -6713,11 +7004,11 @@ checksum = "e20f4df8cee4a1819f1c5b0d3d85d50c30f27133b2ae68c2fd92655e4aede34a" dependencies = [ "base64 0.13.0", "bincode", - "bs58", + "bs58 0.4.0", "clap", "indicatif", "jsonrpc-core", - "log 0.4.17", + "log", "rayon", "reqwest", "semver 1.0.6", @@ -6770,9 +7061,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a11e1b6d5ce435bb3df95f2a970cd80500a8abf94ea87558c35fe0cce8456ab" dependencies = [ "bincode", - "byteorder 1.4.3", + "byteorder", "clap", - "log 0.4.17", + "log", "serde", "serde_derive", "solana-clap-utils", @@ -6792,10 +7083,10 @@ version = "1.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5f69a79200f5ba439eb8b790c5e00beab4d1fae4da69ce023c69c6ac1b55bf1" dependencies = [ - "bs58", + "bs58 0.4.0", "bv", "generic-array 0.14.5", - "log 0.4.17", + "log", "memmap2", "rustc_version 0.4.0", "serde", @@ -6812,8 +7103,8 @@ version = "1.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402fffb54bf5d335e6df26fc1719feecfbd7a22fafdf6649fe78380de3c47384" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "rustc_version 0.4.0", "syn 1.0.95", ] @@ -6826,7 +7117,7 @@ checksum = "942dc59fc9da66d178362051b658646b65dc11cea0bc804e4ecd2528d3c1279f" dependencies = [ "env_logger", "lazy_static", - "log 0.4.17", + "log", ] [[package]] @@ -6835,7 +7126,7 @@ version = "1.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ccd5b1278b115249d6ca5a203fd852f7d856e048488c24442222ee86e682bd9" dependencies = [ - "log 0.4.17", + "log", "solana-sdk", ] @@ -6848,7 +7139,7 @@ dependencies = [ "env_logger", "gethostname", "lazy_static", - "log 0.4.17", + "log", "reqwest", "solana-sdk", ] @@ -6861,8 +7152,8 @@ checksum = "4cb530af085d8aab563530ed39703096aa526249d350082823882fdd59cdf839" dependencies = [ "bincode", "clap", - "log 0.4.17", - "nix", + "log", + "nix 0.23.1", "rand 0.7.3", "serde", "serde_derive", @@ -6890,8 +7181,8 @@ dependencies = [ "fnv", "lazy_static", "libc", - "log 0.4.17", - "nix", + "log", + "nix 0.23.1", "rand 0.7.3", "rayon", "serde", @@ -6915,7 +7206,7 @@ dependencies = [ "blake3", "borsh", "borsh-derive", - "bs58", + "bs58 0.4.0", "bv", "bytemuck", "console_error_panic_hook", @@ -6926,7 +7217,7 @@ dependencies = [ "js-sys", "lazy_static", "libsecp256k1 0.6.0", - "log 0.4.17", + "log", "num-derive", "num-traits", "parking_lot 0.11.1", @@ -6957,7 +7248,7 @@ dependencies = [ "itertools", "libc", "libloading", - "log 0.4.17", + "log", "num-derive", "num-traits", "rustc_version 0.4.0", @@ -6990,7 +7281,7 @@ dependencies = [ "console", "dialoguer", "hidapi", - "log 0.4.17", + "log", "num-derive", "num-traits", "parking_lot 0.11.1", @@ -7012,7 +7303,7 @@ dependencies = [ "blake3", "bv", "bytemuck", - "byteorder 1.4.3", + "byteorder", "bzip2", "crossbeam-channel 0.5.1", "dashmap", @@ -7022,7 +7313,7 @@ dependencies = [ "index_list", "itertools", "lazy_static", - "log 0.4.17", + "log", "memmap2", "num-derive", "num-traits", @@ -7067,9 +7358,9 @@ dependencies = [ "bincode", "bitflags", "borsh", - "bs58", + "bs58 0.4.0", "bytemuck", - "byteorder 1.4.3", + "byteorder", "chrono", "derivation-path 0.1.3", "digest 0.9.0", @@ -7081,7 +7372,7 @@ dependencies = [ "js-sys", "lazy_static", "libsecp256k1 0.6.0", - "log 0.4.17", + "log", "memmap2", "num-derive", "num-traits", @@ -7113,9 +7404,9 @@ version = "1.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c834b4e02ac911b13c13aed08b3f847e722f6be79d31b1c660c1dbd2dee83cdb" dependencies = [ - "bs58", - "proc-macro2 1.0.58", - "quote 1.0.27", + "bs58 0.4.0", + "proc-macro2 1.0.63", + "quote 1.0.28", "rustversion", "syn 1.0.95", ] @@ -7127,7 +7418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92597c0ed16d167d5ee48e5b13e92dfaed9c55b23a13ec261440136cd418649" dependencies = [ "bincode", - "log 0.4.17", + "log", "num-derive", "num-traits", "rustc_version 0.4.0", @@ -7152,9 +7443,9 @@ dependencies = [ "Inflector", "base64 0.12.3", "bincode", - "bs58", + "bs58 0.4.0", "lazy_static", - "log 0.4.17", + "log", "serde", "serde_derive", "serde_json", @@ -7176,7 +7467,7 @@ version = "1.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222e2c91640d45cd9617dfc07121555a9bdac10e6e105f6931b758f46db6faaa" dependencies = [ - "log 0.4.17", + "log", "rustc_version 0.4.0", "serde", "serde_derive", @@ -7192,7 +7483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4cc64945010e9e76d368493ad091aa5cf43ee16f69296290ebb5c815e433232" dependencies = [ "bincode", - "log 0.4.17", + "log", "num-derive", "num-traits", "rustc_version 0.4.0", @@ -7214,10 +7505,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77963e2aa8fadb589118c3aede2e78b6c4bcf1c01d588fbf33e915b390825fbd" dependencies = [ "bitflags", - "byteorder 1.4.3", + "byteorder", "hash-db", "hash256-std-hasher", - "log 0.4.17", + "log", "num-traits", "parity-scale-codec", "parity-util-mem", @@ -7238,8 +7529,8 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d676664972e22a0796176e81e7bec41df461d1edf52090955cdab55f2c956ff2" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -7268,8 +7559,8 @@ checksum = "22ecb916b9664ed9f90abef0ff5a3e61454c1efea5861b2997e03f39b59b955f" dependencies = [ "Inflector", "proc-macro-crate 1.1.3", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", ] @@ -7416,8 +7707,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f9799e6d412271cb2414597581128b03f3285f260ea49f5363d07df6a332b3e" dependencies = [ "Inflector", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "serde", "serde_json", "unicode-xid 0.2.0", @@ -7496,19 +7787,19 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.16" +version = "2.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "unicode-ident", ] @@ -7533,12 +7824,33 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", "unicode-xid 0.2.0", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -7561,7 +7873,7 @@ name = "tc_cli_client" version = "0.2.0" source = "git+https://github.com/KomodoPlatform/mm2-testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.17", + "log", "serde", "serde_derive", "serde_json", @@ -7575,7 +7887,7 @@ source = "git+https://github.com/KomodoPlatform/mm2-testcontainers-rs.git#65e738 dependencies = [ "hex 0.3.2", "hmac 0.7.1", - "log 0.4.17", + "log", "rand 0.7.3", "sha2 0.8.2", "tc_core", @@ -7587,7 +7899,7 @@ version = "0.3.0" source = "git+https://github.com/KomodoPlatform/mm2-testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ "debug_stub_derive", - "log 0.4.17", + "log", ] [[package]] @@ -7595,7 +7907,7 @@ name = "tc_dynamodb_local" version = "0.2.0" source = "git+https://github.com/KomodoPlatform/mm2-testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.17", + "log", "tc_core", ] @@ -7620,7 +7932,7 @@ name = "tc_parity_parity" version = "0.5.0" source = "git+https://github.com/KomodoPlatform/mm2-testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.17", + "log", "tc_core", ] @@ -7629,7 +7941,7 @@ name = "tc_postgres" version = "0.2.0" source = "git+https://github.com/KomodoPlatform/mm2-testcontainers-rs.git#65e738093488f1b37185af0f1d3cf0ffd23e840d" dependencies = [ - "log 0.4.17", + "log", "tc_core", ] @@ -7658,7 +7970,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "redox_syscall 0.2.10", - "rustix", + "rustix 0.36.9", "windows-sys 0.42.0", ] @@ -7669,11 +7981,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.4.0", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.15", + "futures 0.3.28", "k256", "num-traits", "once_cell", @@ -7713,7 +8025,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "flex-error", "num-derive", "num-traits", @@ -7731,11 +8043,11 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13e63f57ee05a1e927887191c76d1b139de9fa40c180b9f8727ee44377242a6" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "flex-error", "getrandom 0.2.9", "peg", - "pin-project 1.0.10", + "pin-project", "serde", "serde_bytes", "serde_json", @@ -7804,22 +8116,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] @@ -7889,9 +8201,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -7904,14 +8216,13 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.25.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg 1.1.0", - "bytes 1.1.0", + "bytes 1.4.0", "libc", - "memchr", "mio", "num_cpus", "parking_lot 0.12.0", @@ -7919,7 +8230,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.4.9", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -7944,13 +8255,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] @@ -7981,7 +8292,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ - "bytes 1.1.0", + "bytes 1.4.0", "futures-core", "futures-sink", "pin-project-lite 0.2.9", @@ -8008,7 +8319,7 @@ dependencies = [ "async-trait", "axum", "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.4.0", "flate2", "futures-core", "futures-util", @@ -8018,7 +8329,7 @@ dependencies = [ "hyper", "hyper-timeout", "percent-encoding", - "pin-project 1.0.10", + "pin-project", "prost", "prost-derive", "rustls-pemfile 1.0.2", @@ -8031,7 +8342,7 @@ dependencies = [ "tower-service", "tracing", "tracing-futures", - "webpki-roots", + "webpki-roots 0.22.3", ] [[package]] @@ -8041,9 +8352,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9263bf4c9bfaae7317c1c2faf7f18491d2fe476f70c414b73bf5d445b00ffa1" dependencies = [ "prettyplease", - "proc-macro2 1.0.58", + "proc-macro2 1.0.63", "prost-build", - "quote 1.0.27", + "quote 1.0.28", "syn 1.0.95", ] @@ -8056,7 +8367,7 @@ dependencies = [ "futures-core", "futures-util", "indexmap", - "pin-project 1.0.10", + "pin-project", "pin-project-lite 0.2.9", "rand 0.8.4", "slab", @@ -8074,7 +8385,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d342c6d58709c0a6d48d48dabbb62d4ef955cf5f0f3bbfd845838e7ae88dbae" dependencies = [ "bitflags", - "bytes 1.1.0", + "bytes 1.4.0", "futures-core", "futures-util", "http 0.2.7", @@ -8100,12 +8411,12 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", - "log 0.4.17", + "log", "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", @@ -8113,22 +8424,22 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.20" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 1.0.95", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", ] [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -8137,7 +8448,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", + "pin-project", "tracing", ] @@ -8147,10 +8458,10 @@ version = "0.1.1" dependencies = [ "async-trait", "bip32", - "byteorder 1.4.3", + "byteorder", "common", "derive_more", - "futures 0.3.15", + "futures 0.3.28", "hw_common", "js-sys", "mm2_err_handle", @@ -8173,7 +8484,7 @@ checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" dependencies = [ "hash-db", "hashbrown 0.12.1", - "log 0.4.17", + "log", "smallvec 1.6.1", ] @@ -8197,9 +8508,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.21.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -8211,32 +8522,33 @@ dependencies = [ "idna", "ipnet", "lazy_static", - "log 0.4.17", "rand 0.8.4", "smallvec 1.6.1", + "socket2 0.4.9", "thiserror", "tinyvec", "tokio", + "tracing", "url", ] [[package]] name = "trust-dns-resolver" -version = "0.21.2" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", "lazy_static", - "log 0.4.17", "lru-cache", "parking_lot 0.12.0", "resolv-conf", "smallvec 1.6.1", "thiserror", "tokio", + "tracing", "trust-dns-proto", ] @@ -8253,11 +8565,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", - "byteorder 1.4.3", - "bytes 1.1.0", + "byteorder", + "bytes 1.4.0", "http 0.2.7", "httparse", - "log 0.4.17", + "log", "rand 0.8.4", "rustls 0.20.4", "sha-1", @@ -8265,7 +8577,7 @@ dependencies = [ "url", "utf-8", "webpki 0.22.0", - "webpki-roots", + "webpki-roots 0.22.3", ] [[package]] @@ -8280,7 +8592,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ - "byteorder 1.4.3", + "byteorder", "crunchy", "hex 0.4.3", "static_assertions", @@ -8289,7 +8601,7 @@ dependencies = [ [[package]] name = "unexpected" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#223da7972113a548531804510708f87b629a48fd" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" [[package]] name = "unicode-bidi" @@ -8349,16 +8661,6 @@ dependencies = [ "subtle 2.4.0", ] -[[package]] -name = "unsigned-varint" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "669d776983b692a906c881fcd0cfb34271a48e197e4d6cb8df32b05bfc3d3fa5" -dependencies = [ - "bytes 0.5.6", - "futures_codec", -] - [[package]] name = "unsigned-varint" version = "0.7.1" @@ -8366,9 +8668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures-io", - "futures-util", + "bytes 1.4.0", ] [[package]] @@ -8443,13 +8743,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.0.0-alpha.9" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] +checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e" [[package]] name = "vcpkg" @@ -8477,9 +8773,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "waker-fn" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" @@ -8498,7 +8794,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.17", + "log", "try-lock", ] @@ -8516,9 +8812,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -8526,24 +8822,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", - "log 0.4.17", + "log", "once_cell", - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.21" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e67a5806118af01f0d9045915676b22aaebecf4178ae7021bc171dab0b897ab" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -8553,32 +8849,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ - "quote 1.0.27", + "quote 1.0.28", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.63", + "quote 1.0.28", + "syn 2.0.23", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.86" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" @@ -8600,23 +8896,8 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c2e18093f11c19ca4e188c177fecc7c372304c311189f12c2f9bea5b7324ac7" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", -] - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures 0.3.15", - "js-sys", - "parking_lot 0.11.1", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "proc-macro2 1.0.63", + "quote 1.0.28", ] [[package]] @@ -8638,16 +8919,16 @@ dependencies = [ "derive_more", "ethabi", "ethereum-types", - "futures 0.3.15", + "futures 0.3.28", "futures-timer", "getrandom 0.2.9", "hex 0.4.3", "idna", "js-sys", "jsonrpc-core", - "log 0.4.17", + "log", "parking_lot 0.12.0", - "pin-project 1.0.10", + "pin-project", "rand 0.8.4", "rlp", "serde", @@ -8687,6 +8968,15 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "webpki-roots" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +dependencies = [ + "rustls-webpki", +] + [[package]] name = "wepoll-sys-stjepang" version = "1.0.6" @@ -8743,6 +9033,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + [[package]] name = "windows-sys" version = "0.32.0" @@ -8762,12 +9065,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] @@ -8777,7 +9080,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -8786,87 +9098,174 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.1", "windows_aarch64_msvc 0.42.1", "windows_i686_gnu 0.42.1", "windows_i686_msvc 0.42.1", "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.1", "windows_x86_64_msvc 0.42.1", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winreg" version = "0.7.0" @@ -8926,8 +9325,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" dependencies = [ - "futures 0.3.15", - "log 0.4.17", + "futures 0.3.28", + "log", "nohash-hasher", "parking_lot 0.12.0", "rand 0.8.4", @@ -8957,7 +9356,7 @@ dependencies = [ "base64 0.13.0", "bech32", "bls12_381", - "bs58", + "bs58 0.4.0", "ff 0.8.0", "group 0.8.0", "hex 0.4.3", @@ -8979,7 +9378,7 @@ version = "0.3.0" source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.3.0#443fef0cf301b04375f76128e7436b4de02d1c4d" dependencies = [ "bech32", - "bs58", + "bs58 0.4.0", "ff 0.8.0", "group 0.8.0", "jubjub", @@ -8998,7 +9397,7 @@ version = "0.0.0" source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.3.0#443fef0cf301b04375f76128e7436b4de02d1c4d" dependencies = [ "blake2b_simd", - "byteorder 1.4.3", + "byteorder", "crypto_api_chachapoly", "ff 0.8.0", "group 0.8.0", @@ -9016,7 +9415,7 @@ dependencies = [ "blake2b_simd", "blake2s_simd", "bls12_381", - "byteorder 1.4.3", + "byteorder", "crypto_api_chachapoly", "equihash", "ff 0.8.0", @@ -9026,7 +9425,7 @@ dependencies = [ "hex 0.4.3", "jubjub", "lazy_static", - "log 0.4.17", + "log", "rand 0.7.3", "rand_core 0.5.1", "ripemd160", @@ -9044,7 +9443,7 @@ dependencies = [ "bellman", "blake2b_simd", "bls12_381", - "byteorder 1.4.3", + "byteorder", "directories", "ff 0.8.0", "group 0.8.0", @@ -9056,9 +9455,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.4.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] @@ -9069,8 +9468,8 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.63", + "quote 1.0.28", "syn 1.0.95", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index a7cf3badf3..ac0439b17b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,6 @@ members = [ "mm2src/derives/enum_from", "mm2src/derives/ser_error_derive", "mm2src/derives/ser_error", - "mm2src/floodsub", - "mm2src/gossipsub", "mm2src/hw_common", "mm2src/mm2_bin_lib", "mm2src/mm2_bitcoin/chain", @@ -30,12 +28,12 @@ members = [ "mm2src/mm2_git", "mm2src/mm2_gui_storage", "mm2src/mm2_io", - "mm2src/mm2_libp2p", "mm2src/mm2_main", "mm2src/mm2_metamask", "mm2src/mm2_metrics", "mm2src/mm2_net", "mm2src/mm2_number", + "mm2src/mm2_p2p", "mm2src/mm2_rpc", "mm2src/mm2_state_machine", "mm2src/mm2_test_helpers", @@ -44,7 +42,10 @@ members = [ ] exclude = [ - "mm2src/adex_cli" + "mm2src/adex_cli", + "mm2src/floodsub", + "mm2src/gossipsub", + "mm2src/mm2_libp2p", ] # https://doc.rust-lang.org/beta/cargo/reference/features.html#feature-resolver-version-2 diff --git a/README.md b/README.md index 5d17c4e4e4..d48c24090c 100755 --- a/README.md +++ b/README.md @@ -101,13 +101,13 @@ Please refer to the [WASM Build Guide](./docs/WASM_BUILD.md). Basic config is contained in two files, `MM2.json` and `coins` -The user configuration [MM2.json file](https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/configure-mm2-json.html) contains rpc credentials, your mnemonic seed phrase, a `netid` (7777 is the current main network) and some extra [optional parameters](https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/get-started-atomicdex.html). +The user configuration [MM2.json file](https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/configure-mm2-json.html) contains rpc credentials, your mnemonic seed phrase, a `netid` (8762 is the current main network) and some extra [optional parameters](https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/get-started-atomicdex.html). For example: ```json { "gui": "core_readme", - "netid": 7777, + "netid": 8762, "rpc_password": "Ent3r_Un1Qu3_Pa$$w0rd", "passphrase": "ENTER_UNIQUE_SEED_PHRASE_DONT_USE_THIS_CHANGE_IT_OR_FUNDS_NOT_SAFU" } diff --git a/docs/HEAPTRACK.md b/docs/HEAPTRACK.md index b25d8b901f..646b4a3bb3 100644 --- a/docs/HEAPTRACK.md +++ b/docs/HEAPTRACK.md @@ -11,7 +11,7 @@ sudo apt install heaptrack heaptrack-gui 3. Use heaptrack to run MM2 binary and pass parameters as usual. An example for this would be: ``` -heaptrack ./mm2 "{\"gui\":\"MM2GUI\",\"netid\":7777, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"YOUR_PASSPHRASE_HERE\", \"rpc_password\":\"YOUR_PASSWORD_HERE\",\"i_am_seed\":true}" & +heaptrack ./mm2 "{\"gui\":\"MM2GUI\",\"netid\":8762, \"userhome\":\"/${HOME#"/"}\", \"passphrase\":\"YOUR_PASSPHRASE_HERE\", \"rpc_password\":\"YOUR_PASSWORD_HERE\",\"i_am_seed\":true}" & ``` Running heaptrack like this writes a gzipped result file in the same folder the above command ran from. We can now take a look at using the next step. diff --git a/examples/wasm/index.html b/examples/wasm/index.html index b9243b9bf8..18db436124 100644 --- a/examples/wasm/index.html +++ b/examples/wasm/index.html @@ -12,7 +12,7 @@
+ rows="7">{"gui":"WASMTEST","mm2":1,"passphrase":"wasmtest","allow_weak_password":true,"rpc_password":"testpsw","netid":8762,"coins":[{"coin":"ETH","protocol":{"type":"ETH"}},{"coin":"RICK","overwintered":1,"txversion":4,"protocol":{"type":"UTXO"}},{"coin":"MORTY","overwintered":1,"txversion":4,"protocol":{"type":"UTXO"}}]}
diff --git a/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs b/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs index 60b3fbe445..d58b035f73 100644 --- a/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs +++ b/mm2src/adex_cli/src/scenarios/init_mm2_cfg.rs @@ -14,7 +14,7 @@ use super::inquire_extentions::{InquireOption, DEFAULT_DEFAULT_OPTION_BOOL_FORMA use crate::helpers; use crate::logging::error_anyhow; -const DEFAULT_NET_ID: u16 = 7777; +const DEFAULT_NET_ID: u16 = 8762; const DEFAULT_GID: &str = "adex-cli"; const DEFAULT_OPTION_PLACEHOLDER: &str = "Tap enter to skip"; const RPC_PORT_MIN: u16 = 1024; @@ -128,7 +128,7 @@ impl Mm2Cfg { fn inquire_net_id(&mut self) -> Result<()> { self.netid = CustomType::::new("What is the network `mm2` is going to be a part, netid:") .with_default(DEFAULT_NET_ID) - .with_help_message(r#"Network ID number, telling the AtomicDEX API which network to join. 7777 is the current main network, though alternative netids can be used for testing or "private" trades"#) + .with_help_message(r#"Network ID number, telling the AtomicDEX API which network to join. 8762 is the current main network, though alternative netids can be used for testing or "private" trades"#) .with_placeholder(format!("{DEFAULT_NET_ID}").as_str()) .prompt() .map_err(|error| @@ -283,7 +283,7 @@ impl Mm2Cfg { .with_formatter(DEFAULT_OPTION_BOOL_FORMATTER) .with_default_value_formatter(DEFAULT_DEFAULT_OPTION_BOOL_FORMATTER) .with_default(InquireOption::None) - .with_help_message("Runs AtomicDEX API as a seed node mode (acting as a relay for AtomicDEX API clients). Optional, defaults to false. Use of this mode is not reccomended on the main network (7777) as it could result in a pubkey ban if non-compliant. on alternative testing or private networks, at least one seed node is required to relay information to other AtomicDEX API clients using the same netID.") + .with_help_message("Runs AtomicDEX API as a seed node mode (acting as a relay for AtomicDEX API clients). Optional, defaults to false. Use of this mode is not reccomended on the main network (8762) as it could result in a pubkey ban if non-compliant. on alternative testing or private networks, at least one seed node is required to relay information to other AtomicDEX API clients using the same netID.") .prompt() .map_err(|error| error_anyhow!("Failed to get i_am_a_seed: {error}") diff --git a/mm2src/adex_cli/src/tests/http_mock_data/orderbook.http b/mm2src/adex_cli/src/tests/http_mock_data/orderbook.http index e0270e2d23..6a3c060cc2 100644 --- a/mm2src/adex_cli/src/tests/http_mock_data/orderbook.http +++ b/mm2src/adex_cli/src/tests/http_mock_data/orderbook.http @@ -3,4 +3,4 @@ access-control-allow-origin: http://localhost:3000 content-length: 20013 date: Fri, 21 Apr 2023 10:33:36 GMT -{"askdepth":0,"asks":[{"coin":"RICK","address":"R9gWj7fzSxZtJZCSDMQz5G5J7x4rg6UmiQ","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"0.23173","max_volume_rat":[[1,[23173]],[1,[100000]]],"max_volume_fraction":{"numer":"23173","denom":"100000"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846","age":1682073216,"zcredits":0,"uuid":"c7585a1b-6060-4319-9da6-c67321628a06","is_mine":false,"base_max_volume":"0.23173","base_max_volume_fraction":{"numer":"23173","denom":"100000"},"base_max_volume_rat":[[1,[23173]],[1,[100000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"0.23173","rel_max_volume_fraction":{"numer":"23173","denom":"100000"},"rel_max_volume_rat":[[1,[23173]],[1,[100000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"340660.26950721","base_max_volume_aggr_fraction":{"numer":"34066026950721","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2641326145,7931]],[1,[100000000]]],"rel_max_volume_aggr":"340660.26950715","rel_max_volume_aggr_fraction":{"numer":"6813205390143","denom":"20000000"},"rel_max_volume_aggr_rat":[[1,[1387258687,1586]],[1,[20000000]]]},{"coin":"RICK","address":"RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"340654.03777721","max_volume_rat":[[1,[2018153145,7931]],[1,[100000000]]],"max_volume_fraction":{"numer":"34065403777721","denom":"100000000"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","age":1682073216,"zcredits":0,"uuid":"d69fe2a9-51ca-4d69-96ad-b141a01d8bb4","is_mine":false,"base_max_volume":"340654.03777721","base_max_volume_fraction":{"numer":"34065403777721","denom":"100000000"},"base_max_volume_rat":[[1,[2018153145,7931]],[1,[100000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"340654.03777721","rel_max_volume_fraction":{"numer":"34065403777721","denom":"100000000"},"rel_max_volume_rat":[[1,[2018153145,7931]],[1,[100000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"340660.03777721","base_max_volume_aggr_fraction":{"numer":"34066003777721","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2618153145,7931]],[1,[100000000]]],"rel_max_volume_aggr":"340660.03777715","rel_max_volume_aggr_fraction":{"numer":"6813200755543","denom":"20000000"},"rel_max_volume_aggr_rat":[[1,[1382624087,1586]],[1,[20000000]]]},{"coin":"RICK","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"0.99999999","price_rat":[[1,[99999999]],[1,[100000000]]],"price_fraction":{"numer":"99999999","denom":"100000000"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"a2337218-7f6f-46a1-892e-6febfb7f5403","is_mine":false,"base_max_volume":"2","base_max_volume_fraction":{"numer":"2","denom":"1"},"base_max_volume_rat":[[1,[2]],[1,[1]]],"base_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","base_min_volume_fraction":{"numer":"10000","denom":"99999999"},"base_min_volume_rat":[[1,[10000]],[1,[99999999]]],"rel_max_volume":"1.99999998","rel_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"6","base_max_volume_aggr_fraction":{"numer":"6","denom":"1"},"base_max_volume_aggr_rat":[[1,[6]],[1,[1]]],"rel_max_volume_aggr":"5.99999994","rel_max_volume_aggr_fraction":{"numer":"299999997","denom":"50000000"},"rel_max_volume_aggr_rat":[[1,[299999997]],[1,[50000000]]]},{"coin":"RICK","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"0.99999999","price_rat":[[1,[99999999]],[1,[100000000]]],"price_fraction":{"numer":"99999999","denom":"100000000"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"c172c295-7fe3-4131-9c81-c3a7182f0617","is_mine":false,"base_max_volume":"2","base_max_volume_fraction":{"numer":"2","denom":"1"},"base_max_volume_rat":[[1,[2]],[1,[1]]],"base_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","base_min_volume_fraction":{"numer":"10000","denom":"99999999"},"base_min_volume_rat":[[1,[10000]],[1,[99999999]]],"rel_max_volume":"1.99999998","rel_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"4","base_max_volume_aggr_fraction":{"numer":"4","denom":"1"},"base_max_volume_aggr_rat":[[1,[4]],[1,[1]]],"rel_max_volume_aggr":"3.99999996","rel_max_volume_aggr_fraction":{"numer":"99999999","denom":"25000000"},"rel_max_volume_aggr_rat":[[1,[99999999]],[1,[25000000]]]},{"coin":"RICK","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"0.99999999","price_rat":[[1,[99999999]],[1,[100000000]]],"price_fraction":{"numer":"99999999","denom":"100000000"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"fbbc44d2-fb50-4b4b-8ac3-d9857cae16b6","is_mine":false,"base_max_volume":"2","base_max_volume_fraction":{"numer":"2","denom":"1"},"base_max_volume_rat":[[1,[2]],[1,[1]]],"base_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","base_min_volume_fraction":{"numer":"10000","denom":"99999999"},"base_min_volume_rat":[[1,[10000]],[1,[99999999]]],"rel_max_volume":"1.99999998","rel_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"2","base_max_volume_aggr_fraction":{"numer":"2","denom":"1"},"base_max_volume_aggr_rat":[[1,[2]],[1,[1]]],"rel_max_volume_aggr":"1.99999998","rel_max_volume_aggr_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_aggr_rat":[[1,[99999999]],[1,[50000000]]]}],"base":"RICK","biddepth":0,"bids":[{"coin":"MORTY","address":"RSqn8JX4rjee7Tb6WbvHcJy87FTPseb2ND","price":"1.024380249948780987502560950624871952468756402376562179881171891005941405449702929727514853513624257","price_rat":[[1,[5000]],[1,[4881]]],"price_fraction":{"numer":"5000","denom":"4881"},"maxvolume":"0.9897561975005121901249743904937512804753124359762343782011882810899405859455029707027248514648637574","max_volume_rat":[[1,[4831]],[1,[4881]]],"max_volume_fraction":{"numer":"4831","denom":"4881"},"min_volume":"0.0001024380249948780987502560950624871952468756402376562179881171891005941405449702929727514853513624257","min_volume_rat":[[1,[1]],[1,[9762]]],"min_volume_fraction":{"numer":"1","denom":"9762"},"pubkey":"02d6c3e22a419a4034272acb215f1d39cd6a0413cfd83ac0c68f482db80accd89a","age":1682073216,"zcredits":0,"uuid":"c480675b-3352-4159-9b3c-55cb2b1329de","is_mine":false,"base_max_volume":"0.9662","base_max_volume_fraction":{"numer":"4831","denom":"5000"},"base_max_volume_rat":[[1,[4831]],[1,[5000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"0.9897561975005121901249743904937512804753124359762343782011882810899405859455029707027248514648637574","rel_max_volume_fraction":{"numer":"4831","denom":"4881"},"rel_max_volume_rat":[[1,[4831]],[1,[4881]]],"rel_min_volume":"0.0001024380249948780987502560950624871952468756402376562179881171891005941405449702929727514853513624257","rel_min_volume_fraction":{"numer":"1","denom":"9762"},"rel_min_volume_rat":[[1,[1]],[1,[9762]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"0.9662","base_max_volume_aggr_fraction":{"numer":"4831","denom":"5000"},"base_max_volume_aggr_rat":[[1,[4831]],[1,[5000]]],"rel_max_volume_aggr":"0.9897561975005121901249743904937512804753124359762343782011882810899405859455029707027248514648637574","rel_max_volume_aggr_fraction":{"numer":"4831","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[4831]],[1,[4881]]]},{"coin":"MORTY","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"1.000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","price_rat":[[1,[100000000]],[1,[99999999]]],"price_fraction":{"numer":"100000000","denom":"99999999"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"fdb0de9c-e283-48c3-9de6-8117fecf0aff","is_mine":false,"base_max_volume":"1.99999998","base_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"base_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"2","rel_max_volume_fraction":{"numer":"2","denom":"1"},"rel_max_volume_rat":[[1,[2]],[1,[1]]],"rel_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","rel_min_volume_fraction":{"numer":"10000","denom":"99999999"},"rel_min_volume_rat":[[1,[10000]],[1,[99999999]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"2.96619998","base_max_volume_aggr_fraction":{"numer":"148309999","denom":"50000000"},"base_max_volume_aggr_rat":[[1,[148309999]],[1,[50000000]]],"rel_max_volume_aggr":"2.989756197500512190124974390493751280475312435976234378201188281089940585945502970702724851464863757","rel_max_volume_aggr_fraction":{"numer":"14593","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[14593]],[1,[4881]]]},{"coin":"MORTY","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"1.000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","price_rat":[[1,[100000000]],[1,[99999999]]],"price_fraction":{"numer":"100000000","denom":"99999999"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"6a3bb75d-8e91-4192-bf50-d8190a69600d","is_mine":false,"base_max_volume":"1.99999998","base_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"base_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"2","rel_max_volume_fraction":{"numer":"2","denom":"1"},"rel_max_volume_rat":[[1,[2]],[1,[1]]],"rel_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","rel_min_volume_fraction":{"numer":"10000","denom":"99999999"},"rel_min_volume_rat":[[1,[10000]],[1,[99999999]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"4.96619996","base_max_volume_aggr_fraction":{"numer":"124154999","denom":"25000000"},"base_max_volume_aggr_rat":[[1,[124154999]],[1,[25000000]]],"rel_max_volume_aggr":"4.989756197500512190124974390493751280475312435976234378201188281089940585945502970702724851464863757","rel_max_volume_aggr_fraction":{"numer":"24355","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[24355]],[1,[4881]]]},{"coin":"MORTY","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"1.000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","price_rat":[[1,[100000000]],[1,[99999999]]],"price_fraction":{"numer":"100000000","denom":"99999999"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"b24b40de-e93d-4218-8d93-1940ceadce7f","is_mine":false,"base_max_volume":"1.99999998","base_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"base_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"2","rel_max_volume_fraction":{"numer":"2","denom":"1"},"rel_max_volume_rat":[[1,[2]],[1,[1]]],"rel_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","rel_min_volume_fraction":{"numer":"10000","denom":"99999999"},"rel_min_volume_rat":[[1,[10000]],[1,[99999999]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"6.96619994","base_max_volume_aggr_fraction":{"numer":"348309997","denom":"50000000"},"base_max_volume_aggr_rat":[[1,[348309997]],[1,[50000000]]],"rel_max_volume_aggr":"6.989756197500512190124974390493751280475312435976234378201188281089940585945502970702724851464863757","rel_max_volume_aggr_fraction":{"numer":"34117","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[34117]],[1,[4881]]]},{"coin":"MORTY","address":"RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"32229.14223005","max_volume_rat":[[1,[337750201,150]],[1,[20000000]]],"max_volume_fraction":{"numer":"644582844601","denom":"20000000"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","age":1682073216,"zcredits":0,"uuid":"652a7e97-f42c-4f87-bc26-26bd1a0fea24","is_mine":false,"base_max_volume":"32229.14223005","base_max_volume_fraction":{"numer":"644582844601","denom":"20000000"},"base_max_volume_rat":[[1,[337750201,150]],[1,[20000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"32229.14223005","rel_max_volume_fraction":{"numer":"644582844601","denom":"20000000"},"rel_max_volume_rat":[[1,[337750201,150]],[1,[20000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"32236.10842999","base_max_volume_aggr_fraction":{"numer":"3223610842999","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2385370999,750]],[1,[100000000]]],"rel_max_volume_aggr":"32236.13198624750051219012497439049375128047531243597623437820118828108994058594550297070272485146486","rel_max_volume_aggr_fraction":{"numer":"3146891204497481","denom":"97620000000"},"rel_max_volume_aggr_rat":[[1,[3026456649,732692]],[1,[3130719488,22]]]},{"coin":"MORTY","address":"R9gWj7fzSxZtJZCSDMQz5G5J7x4rg6UmiQ","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"0.22784","max_volume_rat":[[1,[712]],[1,[3125]]],"max_volume_fraction":{"numer":"712","denom":"3125"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846","age":1682073216,"zcredits":0,"uuid":"1082c93c-8c23-4944-b8f1-a92ec703b03a","is_mine":false,"base_max_volume":"0.22784","base_max_volume_fraction":{"numer":"712","denom":"3125"},"base_max_volume_rat":[[1,[712]],[1,[3125]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"0.22784","rel_max_volume_fraction":{"numer":"712","denom":"3125"},"rel_max_volume_rat":[[1,[712]],[1,[3125]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"32236.33626999","base_max_volume_aggr_fraction":{"numer":"3223633626999","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2408154999,750]],[1,[100000000]]],"rel_max_volume_aggr":"32236.35982624750051219012497439049375128047531243597623437820118828108994058594550297070272485146486","rel_max_volume_aggr_fraction":{"numer":"3146913446238281","denom":"97620000000"},"rel_max_volume_aggr_rat":[[1,[3793360969,732697]],[1,[3130719488,22]]]}],"netid":7777,"numasks":5,"numbids":6,"rel":"MORTY","timestamp":1682073216,"total_asks_base_vol":"340660.26950721","total_asks_base_vol_fraction":{"numer":"34066026950721","denom":"100000000"},"total_asks_base_vol_rat":[[1,[2641326145,7931]],[1,[100000000]]],"total_asks_rel_vol":"340660.26950715","total_asks_rel_vol_fraction":{"numer":"6813205390143","denom":"20000000"},"total_asks_rel_vol_rat":[[1,[1387258687,1586]],[1,[20000000]]],"total_bids_base_vol":"32236.33626999","total_bids_base_vol_fraction":{"numer":"3223633626999","denom":"100000000"},"total_bids_base_vol_rat":[[1,[2408154999,750]],[1,[100000000]]],"total_bids_rel_vol":"32236.35982624750051219012497439049375128047531243597623437820118828108994058594550297070272485146486","total_bids_rel_vol_fraction":{"numer":"3146913446238281","denom":"97620000000"},"total_bids_rel_vol_rat":[[1,[3793360969,732697]],[1,[3130719488,22]]]} \ No newline at end of file +{"askdepth":0,"asks":[{"coin":"RICK","address":"R9gWj7fzSxZtJZCSDMQz5G5J7x4rg6UmiQ","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"0.23173","max_volume_rat":[[1,[23173]],[1,[100000]]],"max_volume_fraction":{"numer":"23173","denom":"100000"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846","age":1682073216,"zcredits":0,"uuid":"c7585a1b-6060-4319-9da6-c67321628a06","is_mine":false,"base_max_volume":"0.23173","base_max_volume_fraction":{"numer":"23173","denom":"100000"},"base_max_volume_rat":[[1,[23173]],[1,[100000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"0.23173","rel_max_volume_fraction":{"numer":"23173","denom":"100000"},"rel_max_volume_rat":[[1,[23173]],[1,[100000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"340660.26950721","base_max_volume_aggr_fraction":{"numer":"34066026950721","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2641326145,7931]],[1,[100000000]]],"rel_max_volume_aggr":"340660.26950715","rel_max_volume_aggr_fraction":{"numer":"6813205390143","denom":"20000000"},"rel_max_volume_aggr_rat":[[1,[1387258687,1586]],[1,[20000000]]]},{"coin":"RICK","address":"RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"340654.03777721","max_volume_rat":[[1,[2018153145,7931]],[1,[100000000]]],"max_volume_fraction":{"numer":"34065403777721","denom":"100000000"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","age":1682073216,"zcredits":0,"uuid":"d69fe2a9-51ca-4d69-96ad-b141a01d8bb4","is_mine":false,"base_max_volume":"340654.03777721","base_max_volume_fraction":{"numer":"34065403777721","denom":"100000000"},"base_max_volume_rat":[[1,[2018153145,7931]],[1,[100000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"340654.03777721","rel_max_volume_fraction":{"numer":"34065403777721","denom":"100000000"},"rel_max_volume_rat":[[1,[2018153145,7931]],[1,[100000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"340660.03777721","base_max_volume_aggr_fraction":{"numer":"34066003777721","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2618153145,7931]],[1,[100000000]]],"rel_max_volume_aggr":"340660.03777715","rel_max_volume_aggr_fraction":{"numer":"6813200755543","denom":"20000000"},"rel_max_volume_aggr_rat":[[1,[1382624087,1586]],[1,[20000000]]]},{"coin":"RICK","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"0.99999999","price_rat":[[1,[99999999]],[1,[100000000]]],"price_fraction":{"numer":"99999999","denom":"100000000"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"a2337218-7f6f-46a1-892e-6febfb7f5403","is_mine":false,"base_max_volume":"2","base_max_volume_fraction":{"numer":"2","denom":"1"},"base_max_volume_rat":[[1,[2]],[1,[1]]],"base_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","base_min_volume_fraction":{"numer":"10000","denom":"99999999"},"base_min_volume_rat":[[1,[10000]],[1,[99999999]]],"rel_max_volume":"1.99999998","rel_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"6","base_max_volume_aggr_fraction":{"numer":"6","denom":"1"},"base_max_volume_aggr_rat":[[1,[6]],[1,[1]]],"rel_max_volume_aggr":"5.99999994","rel_max_volume_aggr_fraction":{"numer":"299999997","denom":"50000000"},"rel_max_volume_aggr_rat":[[1,[299999997]],[1,[50000000]]]},{"coin":"RICK","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"0.99999999","price_rat":[[1,[99999999]],[1,[100000000]]],"price_fraction":{"numer":"99999999","denom":"100000000"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"c172c295-7fe3-4131-9c81-c3a7182f0617","is_mine":false,"base_max_volume":"2","base_max_volume_fraction":{"numer":"2","denom":"1"},"base_max_volume_rat":[[1,[2]],[1,[1]]],"base_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","base_min_volume_fraction":{"numer":"10000","denom":"99999999"},"base_min_volume_rat":[[1,[10000]],[1,[99999999]]],"rel_max_volume":"1.99999998","rel_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"4","base_max_volume_aggr_fraction":{"numer":"4","denom":"1"},"base_max_volume_aggr_rat":[[1,[4]],[1,[1]]],"rel_max_volume_aggr":"3.99999996","rel_max_volume_aggr_fraction":{"numer":"99999999","denom":"25000000"},"rel_max_volume_aggr_rat":[[1,[99999999]],[1,[25000000]]]},{"coin":"RICK","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"0.99999999","price_rat":[[1,[99999999]],[1,[100000000]]],"price_fraction":{"numer":"99999999","denom":"100000000"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"fbbc44d2-fb50-4b4b-8ac3-d9857cae16b6","is_mine":false,"base_max_volume":"2","base_max_volume_fraction":{"numer":"2","denom":"1"},"base_max_volume_rat":[[1,[2]],[1,[1]]],"base_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","base_min_volume_fraction":{"numer":"10000","denom":"99999999"},"base_min_volume_rat":[[1,[10000]],[1,[99999999]]],"rel_max_volume":"1.99999998","rel_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"2","base_max_volume_aggr_fraction":{"numer":"2","denom":"1"},"base_max_volume_aggr_rat":[[1,[2]],[1,[1]]],"rel_max_volume_aggr":"1.99999998","rel_max_volume_aggr_fraction":{"numer":"99999999","denom":"50000000"},"rel_max_volume_aggr_rat":[[1,[99999999]],[1,[50000000]]]}],"base":"RICK","biddepth":0,"bids":[{"coin":"MORTY","address":"RSqn8JX4rjee7Tb6WbvHcJy87FTPseb2ND","price":"1.024380249948780987502560950624871952468756402376562179881171891005941405449702929727514853513624257","price_rat":[[1,[5000]],[1,[4881]]],"price_fraction":{"numer":"5000","denom":"4881"},"maxvolume":"0.9897561975005121901249743904937512804753124359762343782011882810899405859455029707027248514648637574","max_volume_rat":[[1,[4831]],[1,[4881]]],"max_volume_fraction":{"numer":"4831","denom":"4881"},"min_volume":"0.0001024380249948780987502560950624871952468756402376562179881171891005941405449702929727514853513624257","min_volume_rat":[[1,[1]],[1,[9762]]],"min_volume_fraction":{"numer":"1","denom":"9762"},"pubkey":"02d6c3e22a419a4034272acb215f1d39cd6a0413cfd83ac0c68f482db80accd89a","age":1682073216,"zcredits":0,"uuid":"c480675b-3352-4159-9b3c-55cb2b1329de","is_mine":false,"base_max_volume":"0.9662","base_max_volume_fraction":{"numer":"4831","denom":"5000"},"base_max_volume_rat":[[1,[4831]],[1,[5000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"0.9897561975005121901249743904937512804753124359762343782011882810899405859455029707027248514648637574","rel_max_volume_fraction":{"numer":"4831","denom":"4881"},"rel_max_volume_rat":[[1,[4831]],[1,[4881]]],"rel_min_volume":"0.0001024380249948780987502560950624871952468756402376562179881171891005941405449702929727514853513624257","rel_min_volume_fraction":{"numer":"1","denom":"9762"},"rel_min_volume_rat":[[1,[1]],[1,[9762]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"0.9662","base_max_volume_aggr_fraction":{"numer":"4831","denom":"5000"},"base_max_volume_aggr_rat":[[1,[4831]],[1,[5000]]],"rel_max_volume_aggr":"0.9897561975005121901249743904937512804753124359762343782011882810899405859455029707027248514648637574","rel_max_volume_aggr_fraction":{"numer":"4831","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[4831]],[1,[4881]]]},{"coin":"MORTY","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"1.000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","price_rat":[[1,[100000000]],[1,[99999999]]],"price_fraction":{"numer":"100000000","denom":"99999999"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"fdb0de9c-e283-48c3-9de6-8117fecf0aff","is_mine":false,"base_max_volume":"1.99999998","base_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"base_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"2","rel_max_volume_fraction":{"numer":"2","denom":"1"},"rel_max_volume_rat":[[1,[2]],[1,[1]]],"rel_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","rel_min_volume_fraction":{"numer":"10000","denom":"99999999"},"rel_min_volume_rat":[[1,[10000]],[1,[99999999]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"2.96619998","base_max_volume_aggr_fraction":{"numer":"148309999","denom":"50000000"},"base_max_volume_aggr_rat":[[1,[148309999]],[1,[50000000]]],"rel_max_volume_aggr":"2.989756197500512190124974390493751280475312435976234378201188281089940585945502970702724851464863757","rel_max_volume_aggr_fraction":{"numer":"14593","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[14593]],[1,[4881]]]},{"coin":"MORTY","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"1.000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","price_rat":[[1,[100000000]],[1,[99999999]]],"price_fraction":{"numer":"100000000","denom":"99999999"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"6a3bb75d-8e91-4192-bf50-d8190a69600d","is_mine":false,"base_max_volume":"1.99999998","base_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"base_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"2","rel_max_volume_fraction":{"numer":"2","denom":"1"},"rel_max_volume_rat":[[1,[2]],[1,[1]]],"rel_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","rel_min_volume_fraction":{"numer":"10000","denom":"99999999"},"rel_min_volume_rat":[[1,[10000]],[1,[99999999]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"4.96619996","base_max_volume_aggr_fraction":{"numer":"124154999","denom":"25000000"},"base_max_volume_aggr_rat":[[1,[124154999]],[1,[25000000]]],"rel_max_volume_aggr":"4.989756197500512190124974390493751280475312435976234378201188281089940585945502970702724851464863757","rel_max_volume_aggr_fraction":{"numer":"24355","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[24355]],[1,[4881]]]},{"coin":"MORTY","address":"RMaprYNUp8ErJ9ZAKcxMfpC4ioVycYCCCc","price":"1.000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","price_rat":[[1,[100000000]],[1,[99999999]]],"price_fraction":{"numer":"100000000","denom":"99999999"},"maxvolume":"2","max_volume_rat":[[1,[2]],[1,[1]]],"max_volume_fraction":{"numer":"2","denom":"1"},"min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","min_volume_rat":[[1,[10000]],[1,[99999999]]],"min_volume_fraction":{"numer":"10000","denom":"99999999"},"pubkey":"037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5","age":1682073216,"zcredits":0,"uuid":"b24b40de-e93d-4218-8d93-1940ceadce7f","is_mine":false,"base_max_volume":"1.99999998","base_max_volume_fraction":{"numer":"99999999","denom":"50000000"},"base_max_volume_rat":[[1,[99999999]],[1,[50000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"2","rel_max_volume_fraction":{"numer":"2","denom":"1"},"rel_max_volume_rat":[[1,[2]],[1,[1]]],"rel_min_volume":"0.0001000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000","rel_min_volume_fraction":{"numer":"10000","denom":"99999999"},"rel_min_volume_rat":[[1,[10000]],[1,[99999999]]],"base_confs":0,"base_nota":false,"rel_confs":0,"rel_nota":false,"base_max_volume_aggr":"6.96619994","base_max_volume_aggr_fraction":{"numer":"348309997","denom":"50000000"},"base_max_volume_aggr_rat":[[1,[348309997]],[1,[50000000]]],"rel_max_volume_aggr":"6.989756197500512190124974390493751280475312435976234378201188281089940585945502970702724851464863757","rel_max_volume_aggr_fraction":{"numer":"34117","denom":"4881"},"rel_max_volume_aggr_rat":[[1,[34117]],[1,[4881]]]},{"coin":"MORTY","address":"RB8yufv3YTfdzYnwz5paNnnDynGJG6WsqD","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"32229.14223005","max_volume_rat":[[1,[337750201,150]],[1,[20000000]]],"max_volume_fraction":{"numer":"644582844601","denom":"20000000"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"0315d9c51c657ab1be4ae9d3ab6e76a619d3bccfe830d5363fa168424c0d044732","age":1682073216,"zcredits":0,"uuid":"652a7e97-f42c-4f87-bc26-26bd1a0fea24","is_mine":false,"base_max_volume":"32229.14223005","base_max_volume_fraction":{"numer":"644582844601","denom":"20000000"},"base_max_volume_rat":[[1,[337750201,150]],[1,[20000000]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"32229.14223005","rel_max_volume_fraction":{"numer":"644582844601","denom":"20000000"},"rel_max_volume_rat":[[1,[337750201,150]],[1,[20000000]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"32236.10842999","base_max_volume_aggr_fraction":{"numer":"3223610842999","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2385370999,750]],[1,[100000000]]],"rel_max_volume_aggr":"32236.13198624750051219012497439049375128047531243597623437820118828108994058594550297070272485146486","rel_max_volume_aggr_fraction":{"numer":"3146891204497481","denom":"97620000000"},"rel_max_volume_aggr_rat":[[1,[3026456649,732692]],[1,[3130719488,22]]]},{"coin":"MORTY","address":"R9gWj7fzSxZtJZCSDMQz5G5J7x4rg6UmiQ","price":"1","price_rat":[[1,[1]],[1,[1]]],"price_fraction":{"numer":"1","denom":"1"},"maxvolume":"0.22784","max_volume_rat":[[1,[712]],[1,[3125]]],"max_volume_fraction":{"numer":"712","denom":"3125"},"min_volume":"0.0001","min_volume_rat":[[1,[1]],[1,[10000]]],"min_volume_fraction":{"numer":"1","denom":"10000"},"pubkey":"022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846","age":1682073216,"zcredits":0,"uuid":"1082c93c-8c23-4944-b8f1-a92ec703b03a","is_mine":false,"base_max_volume":"0.22784","base_max_volume_fraction":{"numer":"712","denom":"3125"},"base_max_volume_rat":[[1,[712]],[1,[3125]]],"base_min_volume":"0.0001","base_min_volume_fraction":{"numer":"1","denom":"10000"},"base_min_volume_rat":[[1,[1]],[1,[10000]]],"rel_max_volume":"0.22784","rel_max_volume_fraction":{"numer":"712","denom":"3125"},"rel_max_volume_rat":[[1,[712]],[1,[3125]]],"rel_min_volume":"0.0001","rel_min_volume_fraction":{"numer":"1","denom":"10000"},"rel_min_volume_rat":[[1,[1]],[1,[10000]]],"base_confs":1,"base_nota":false,"rel_confs":1,"rel_nota":false,"base_max_volume_aggr":"32236.33626999","base_max_volume_aggr_fraction":{"numer":"3223633626999","denom":"100000000"},"base_max_volume_aggr_rat":[[1,[2408154999,750]],[1,[100000000]]],"rel_max_volume_aggr":"32236.35982624750051219012497439049375128047531243597623437820118828108994058594550297070272485146486","rel_max_volume_aggr_fraction":{"numer":"3146913446238281","denom":"97620000000"},"rel_max_volume_aggr_rat":[[1,[3793360969,732697]],[1,[3130719488,22]]]}],"netid":8762,"numasks":5,"numbids":6,"rel":"MORTY","timestamp":1682073216,"total_asks_base_vol":"340660.26950721","total_asks_base_vol_fraction":{"numer":"34066026950721","denom":"100000000"},"total_asks_base_vol_rat":[[1,[2641326145,7931]],[1,[100000000]]],"total_asks_rel_vol":"340660.26950715","total_asks_rel_vol_fraction":{"numer":"6813205390143","denom":"20000000"},"total_asks_rel_vol_rat":[[1,[1387258687,1586]],[1,[20000000]]],"total_bids_base_vol":"32236.33626999","total_bids_base_vol_fraction":{"numer":"3223633626999","denom":"100000000"},"total_bids_base_vol_rat":[[1,[2408154999,750]],[1,[100000000]]],"total_bids_rel_vol":"32236.35982624750051219012497439049375128047531243597623437820118828108994058594550297070272485146486","total_bids_rel_vol_fraction":{"numer":"3146913446238281","denom":"97620000000"},"total_bids_rel_vol_rat":[[1,[3793360969,732697]],[1,[3130719488,22]]]} \ No newline at end of file diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index 02822ebe5d..ac45850f1d 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -3,7 +3,7 @@ use crate::eth::{web3_transport::Web3SendOut, EthCoin, GuiAuthMessages, RpcTrans use common::APPLICATION_JSON; use futures::lock::Mutex as AsyncMutex; use http::header::CONTENT_TYPE; -use jsonrpc_core::{Call, Id, Response}; +use jsonrpc_core::{Call, Response}; use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; use serde_json::Value as Json; #[cfg(not(target_arch = "wasm32"))] use std::ops::Deref; @@ -217,7 +217,7 @@ async fn send_request( Either::Right((_t, _r)) => { let (method, id) = match &request { Call::MethodCall(m) => (m.method.clone(), m.id.clone()), - Call::Notification(n) => (n.method.clone(), Id::Null), + Call::Notification(n) => (n.method.clone(), jsonrpc_core::Id::Null), Call::Invalid { id } => ("Invalid call".to_string(), id.clone()), }; let error = format!( diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs index 56dc93cbb5..8b1930c9e0 100644 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs +++ b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs @@ -1,5 +1,5 @@ use crate::{adex_ping::AdexPing, - network::{get_all_network_seednodes, NETID_7777}, + network::{get_all_network_seednodes, NETID_8762}, peers_exchange::{PeerAddresses, PeersExchange}, request_response::{build_request_response_behaviour, PeerRequest, PeerResponse, RequestResponseBehaviour, RequestResponseBehaviourEvent, RequestResponseSender}, @@ -419,8 +419,8 @@ impl NetworkBehaviourEventProcess for AtomicDexBehaviour { impl NetworkBehaviourEventProcess for AtomicDexBehaviour { fn inject_event(&mut self, event: FloodsubEvent) { - // do not process peer announce on 7777 temporary - if self.netid != NETID_7777 { + // do not process peer announce on 8762 temporary + if self.netid != NETID_8762 { if let FloodsubEvent::Message(message) = &event { for topic in &message.topics { if topic == &FloodsubTopic::new(PEERS_TOPIC) { @@ -698,7 +698,7 @@ fn start_gossipsub( // build a gossipsub network behaviour let mut gossipsub = Gossipsub::new(local_peer_id, gossipsub_config); - let floodsub = Floodsub::new(local_peer_id, netid != NETID_7777); + let floodsub = Floodsub::new(local_peer_id, netid != NETID_8762); let mut peers_exchange = PeersExchange::new(network_info); if !network_info.in_memory() { diff --git a/mm2src/mm2_libp2p/src/network.rs b/mm2src/mm2_libp2p/src/network.rs index 41f5cf0ae4..0b09c444db 100644 --- a/mm2src/mm2_libp2p/src/network.rs +++ b/mm2src/mm2_libp2p/src/network.rs @@ -1,17 +1,13 @@ use crate::RelayAddress; use libp2p::PeerId; -pub const NETID_7777: u16 = 7777; +pub const NETID_8762: u16 = 8762; #[cfg_attr(target_arch = "wasm32", allow(dead_code))] -const ALL_NETID_7777_SEEDNODES: &[(&str, &str)] = &[ +const ALL_NETID_8762_SEEDNODES: &[(&str, &str)] = &[ ( - "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", - "168.119.236.241", - ), - ( - "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", - "168.119.236.249", + "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", + "168.119.236.251", ), ( "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", @@ -22,31 +18,49 @@ const ALL_NETID_7777_SEEDNODES: &[(&str, &str)] = &[ "168.119.236.239", ), ( - "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", - "168.119.236.251", + "12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", + "135.181.34.220", ), - ("12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", "168.119.237.8"), ( - "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", - "168.119.236.233", + "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", + "168.119.236.241", ), ( "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", "168.119.236.243", ), + ( + "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", + "168.119.236.249", + ), ( "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", "168.119.236.246", ), - ("12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", "168.119.237.13"), - ("12D3KooWKu8pMTgteWacwFjN7zRWWHb3bctyTvHU3xx5x4x6qDYY", "195.201.91.96"), - ("12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", "195.201.91.53"), ( - "12D3KooWGrUpCAbkxhPRioNs64sbUmPmpEcou6hYfrqQvxfWDEuf", - "168.119.174.126", + "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", + "168.119.236.233", + ), + ( + "12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", + "168.119.237.8", + ), + ( + "12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", + "168.119.237.13", + ), + ( + "12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", + "65.108.90.210", + ), + ( + "12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", + "46.4.78.11", + ), + ( + "12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", + "46.4.87.18", ), - ("12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", "46.4.78.11"), - ("12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", "46.4.87.18"), ]; #[cfg(target_arch = "wasm32")] @@ -56,10 +70,10 @@ pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { V pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { use std::str::FromStr; - if netid != NETID_7777 { + if netid != NETID_8762 { return Vec::new(); } - ALL_NETID_7777_SEEDNODES + ALL_NETID_8762_SEEDNODES .iter() .map(|(peer_id, ipv4)| { let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 2416e93a58..d9607adcb3 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -60,7 +60,7 @@ mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } mm2_gui_storage = { path = "../mm2_gui_storage" } mm2_io = { path = "../mm2_io" } -mm2-libp2p = { path = "../mm2_libp2p" } +mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number" } diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 45f7a2f126..db0600c633 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -64,7 +64,11 @@ cfg_native! { #[path = "lp_init/init_metamask.rs"] pub mod init_metamask; -const NETID_7777_SEEDNODES: [&str; 3] = ["seed1.komodo.earth", "seed2.komodo.earth", "seed3.komodo.earth"]; +const NETID_8762_SEEDNODES: [&str; 3] = [ + "streamseed1.komodo.earth", + "streamseed2.komodo.earth", + "streamseed3.komodo.earth", +]; pub type P2PResult = Result>; pub type MmInitResult = Result>; @@ -104,6 +108,7 @@ impl From for P2PInitError { fn from(e: AdexBehaviourError) -> Self { match e { AdexBehaviourError::ParsingRelayAddress(e) => P2PInitError::InvalidRelayAddress(e), + error => P2PInitError::Internal(error.to_string()), } } } @@ -255,8 +260,8 @@ impl MmInitError { #[cfg(target_arch = "wasm32")] fn default_seednodes(netid: u16) -> Vec { - if netid == 7777 { - NETID_7777_SEEDNODES + if netid == 8762 { + NETID_8762_SEEDNODES .iter() .map(|seed| RelayAddress::Dns(seed.to_string())) .collect() @@ -268,8 +273,8 @@ fn default_seednodes(netid: u16) -> Vec { #[cfg(not(target_arch = "wasm32"))] fn default_seednodes(netid: u16) -> Vec { use crate::mm2::lp_network::addr_to_ipv4_string; - if netid == 7777 { - NETID_7777_SEEDNODES + if netid == 8762 { + NETID_8762_SEEDNODES .iter() .filter_map(|seed| addr_to_ipv4_string(seed).ok()) .map(RelayAddress::IPv4) diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index 859434026b..bba7ca0ed8 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -29,11 +29,10 @@ use instant::Instant; use keys::KeyPair; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; -use mm2_libp2p::atomicdex_behaviour::{AdexBehaviourCmd, AdexBehaviourEvent, AdexEventRx, AdexResponse, - AdexResponseChannel}; -use mm2_libp2p::peers_exchange::PeerAddresses; -use mm2_libp2p::{decode_message, encode_message, DecodingError, GossipsubMessage, Libp2pPublic, Libp2pSecpPublic, - MessageId, NetworkPorts, PeerId, TopicHash, TOPIC_SEPARATOR}; +use mm2_libp2p::{decode_message, encode_message, DecodingError, GossipsubEvent, GossipsubMessage, Libp2pPublic, + Libp2pSecpPublic, MessageId, NetworkPorts, PeerId, TOPIC_SEPARATOR}; +use mm2_libp2p::{AdexBehaviourCmd, AdexBehaviourEvent, AdexEventRx, AdexResponse}; +use mm2_libp2p::{PeerAddresses, RequestResponseBehaviourEvent}; use mm2_metrics::{mm_label, mm_timing}; use mm2_net::p2p::P2PContext; use serde::de; @@ -97,21 +96,36 @@ pub async fn p2p_event_process_loop(ctx: MmWeak, mut rx: AdexEventRx, i_am_relay None => return, }; match adex_event { - Some(AdexBehaviourEvent::Message(peer_id, message_id, message)) => { - let spawner = ctx.spawner(); - spawner.spawn(process_p2p_message(ctx, peer_id, message_id, message, i_am_relay)); + Some(AdexBehaviourEvent::Gossipsub(event)) => match event { + GossipsubEvent::Message { + propagation_source, + message_id, + message, + } => { + let spawner = ctx.spawner(); + spawner.spawn(process_p2p_message( + ctx, + propagation_source, + message_id, + message, + i_am_relay, + )); + }, + GossipsubEvent::GossipsubNotSupported { peer_id } => { + log::error!("Received unsupported event from Peer: {peer_id}"); + }, + _ => {}, }, - Some(AdexBehaviourEvent::PeerRequest { + Some(AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { peer_id, request, response_channel, - }) => { - if let Err(e) = process_p2p_request(ctx, peer_id, request, response_channel) { + })) => { + if let Err(e) = process_p2p_request(ctx, peer_id, request.req, response_channel.into()) { log::error!("Error on process P2P request: {:?}", e); } }, - None => break, - _ => (), + _ => {}, } } } @@ -120,119 +134,71 @@ async fn process_p2p_message( ctx: MmArc, peer_id: PeerId, message_id: MessageId, - mut message: GossipsubMessage, + message: GossipsubMessage, i_am_relay: bool, ) { - fn is_valid(topics: &[TopicHash]) -> Result<(), String> { - if topics.is_empty() { - return Err("At least one topic must be provided.".to_string()); - } - - let first_topic_prefix = topics[0].as_str().split(TOPIC_SEPARATOR).next().unwrap_or_default(); - for item in topics.iter().skip(1) { - if !item.as_str().starts_with(first_topic_prefix) { - return Err(format!( - "Topics are invalid, received more than one topic kind. Topics '{:?}", - topics - )); - } - } - - Ok(()) - } - let mut to_propagate = false; - message.topics.dedup(); - drop_mutability!(message); - - if let Err(err) = is_valid(&message.topics) { - log::error!("{}", err); - return; - } - - let inform_about_break = |used: &str, all: &[TopicHash]| { - log::debug!( - "Topic '{}' proceed and loop is killed. Whole topic list was '{:?}'", - used, - all - ); - }; - - for topic in message.topics.iter() { - let mut split = topic.as_str().split(TOPIC_SEPARATOR); - - match split.next() { - Some(lp_ordermatch::ORDERBOOK_PREFIX) => { - let fut = lp_ordermatch::handle_orderbook_msg( - ctx.clone(), - &message.topics, - peer_id.to_string(), - &message.data, - i_am_relay, - ); - - if let Err(e) = fut.await { - if e.get_inner().is_warning() { - log::warn!("{}", e); - } else { - log::error!("{}", e); - } - return; - } - - to_propagate = true; - break; - }, - Some(lp_swap::SWAP_PREFIX) => { - if let Err(e) = - lp_swap::process_swap_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data).await - { + let mut split = message.topic.as_str().split(TOPIC_SEPARATOR); + match split.next() { + Some(lp_ordermatch::ORDERBOOK_PREFIX) => { + if let Err(e) = lp_ordermatch::handle_orderbook_msg( + ctx.clone(), + &message.topic, + peer_id.to_string(), + &message.data, + i_am_relay, + ) + .await + { + if e.get_inner().is_warning() { + log::warn!("{}", e); + } else { log::error!("{}", e); - return; } + return; + } - to_propagate = true; + to_propagate = true; + }, + Some(lp_swap::SWAP_PREFIX) => { + if let Err(e) = + lp_swap::process_swap_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data).await + { + log::error!("{}", e); + return; + } - inform_about_break(topic.as_str(), &message.topics); - break; - }, - Some(lp_swap::SWAP_V2_PREFIX) => { - if let Err(e) = - lp_swap::process_swap_v2_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data) - { + to_propagate = true; + }, + Some(lp_swap::SWAP_V2_PREFIX) => { + if let Err(e) = lp_swap::process_swap_v2_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data) { + log::error!("{}", e); + return; + } + + to_propagate = true; + }, + Some(lp_swap::WATCHER_PREFIX) => { + if ctx.is_watcher() { + if let Err(e) = lp_swap::process_watcher_msg(ctx.clone(), &message.data) { log::error!("{}", e); return; } + } - to_propagate = true; - - inform_about_break(topic.as_str(), &message.topics); - break; - }, - Some(lp_swap::WATCHER_PREFIX) => { - if ctx.is_watcher() { - if let Err(e) = lp_swap::process_watcher_msg(ctx.clone(), &message.data) { - log::error!("{}", e); + to_propagate = true; + }, + Some(lp_swap::TX_HELPER_PREFIX) => { + if let Some(pair) = split.next() { + if let Ok(Some(coin)) = lp_coinfind(&ctx, pair).await { + if let Err(e) = coin.tx_enum_from_bytes(&message.data) { + log::error!("Message cannot continue the process due to: {:?}", e); return; - } - } + }; - to_propagate = true; - - inform_about_break(topic.as_str(), &message.topics); - break; - }, - Some(lp_swap::TX_HELPER_PREFIX) => { - if let Some(pair) = split.next() { - if let Ok(Some(coin)) = lp_coinfind(&ctx, pair).await { - if let Err(e) = coin.tx_enum_from_bytes(&message.data) { - log::error!("Message cannot continue the process due to: {:?}", e); - return; - }; - - let fut = coin.send_raw_tx_bytes(&message.data); - ctx.spawner().spawn(async { + let fut = coin.send_raw_tx_bytes(&message.data); + ctx.spawner().spawn(async { match fut.compat().await { Ok(id) => log::debug!("Transaction broadcasted successfully: {:?} ", id), // TODO (After https://github.com/KomodoPlatform/atomicDEX-API/pull/1433) @@ -241,14 +207,10 @@ async fn process_p2p_message( Err(e) => log::error!("Broadcast transaction failed (ignore this error if the transaction already sent by another seednode). {}", e), }; }) - } } - - inform_about_break(topic.as_str(), &message.topics); - break; - }, - None | Some(_) => (), - } + } + }, + None | Some(_) => (), } if to_propagate && i_am_relay { @@ -260,7 +222,7 @@ fn process_p2p_request( ctx: MmArc, _peer_id: PeerId, request: Vec, - response_channel: AdexResponseChannel, + response_channel: mm2_libp2p::AdexResponseChannel, ) -> P2PRequestResult<()> { let request = decode_message::(&request)?; let result = match request { @@ -284,11 +246,11 @@ fn process_p2p_request( Ok(()) } -pub fn broadcast_p2p_msg(ctx: &MmArc, topics: Vec, msg: Vec, from: Option) { +pub fn broadcast_p2p_msg(ctx: &MmArc, topic: String, msg: Vec, from: Option) { let ctx = ctx.clone(); let cmd = match from { - Some(from) => AdexBehaviourCmd::PublishMsgFrom { topics, msg, from }, - None => AdexBehaviourCmd::PublishMsg { topics, msg }, + Some(from) => AdexBehaviourCmd::PublishMsgFrom { topic, msg, from }, + None => AdexBehaviourCmd::PublishMsg { topic, msg }, }; let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); if let Err(e) = p2p_ctx.cmd_tx.lock().try_send(cmd) { @@ -530,6 +492,6 @@ pub fn lp_network_ports(netid: u16) -> Result> } pub fn peer_id_from_secp_public(secp_public: &[u8]) -> Result> { - let public_key = Libp2pSecpPublic::decode(secp_public)?; - Ok(PeerId::from_public_key(&Libp2pPublic::Secp256k1(public_key))) + let public_key = Libp2pSecpPublic::try_from_bytes(secp_public)?; + Ok(PeerId::from_public_key(&Libp2pPublic::from(public_key))) } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index b43f8838ea..d18db2f10f 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -524,36 +524,19 @@ fn remove_pubkey_pair_orders(orderbook: &mut Orderbook, pubkey: &str, alb_pair: pub async fn handle_orderbook_msg( ctx: MmArc, - topics: &[TopicHash], + topic: &TopicHash, from_peer: String, msg: &[u8], i_am_relay: bool, ) -> OrderbookP2PHandlerResult { - if let Err(e) = decode_signed::(msg) { - return MmError::err(OrderbookP2PHandlerError::DecodeError(e.to_string())); - }; - - let mut orderbook_pairs = vec![]; - - for topic in topics { - let mut split = topic.as_str().split(TOPIC_SEPARATOR); - match (split.next(), split.next()) { - (Some(ORDERBOOK_PREFIX), Some(pair)) => { - orderbook_pairs.push(pair.to_string()); - }, - _ => { - return MmError::err(OrderbookP2PHandlerError::InvalidTopic(topic.as_str().to_owned())); - }, - }; - } - - drop_mutability!(orderbook_pairs); - - if !orderbook_pairs.is_empty() { + let topic_str = topic.as_str(); + let mut split = topic_str.split(TOPIC_SEPARATOR); + if let (Some(ORDERBOOK_PREFIX), Some(_pair)) = (split.next(), split.next()) { process_msg(ctx, from_peer, msg, i_am_relay).await?; + Ok(()) + } else { + MmError::err(OrderbookP2PHandlerError::InvalidTopic(topic_str.to_owned())) } - - Ok(()) } /// Attempts to decode a message and process it returning whether the message is valid and worth rebroadcasting @@ -563,6 +546,7 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: if is_pubkey_banned(&ctx, &pubkey.unprefixed().into()) { return MmError::err(OrderbookP2PHandlerError::PubkeyNotAllowed(pubkey.to_hex())); } + log::debug!("received ordermatch message {:?}", message); match message { new_protocol::OrdermatchMessage::MakerOrderCreated(created_msg) => { let order: OrderbookItem = (created_msg, hex::encode(pubkey.to_bytes().as_slice())).into(); @@ -1061,7 +1045,7 @@ fn maker_order_created_p2p_notify( let encoded_msg = encode_and_sign(&to_broadcast, key_pair.private_ref()).unwrap(); let item: OrderbookItem = (message, hex::encode(key_pair.public_slice())).into(); insert_or_update_my_order(&ctx, item, order); - broadcast_p2p_msg(&ctx, vec![topic], encoded_msg, peer_id); + broadcast_p2p_msg(&ctx, topic, encoded_msg, peer_id); } fn process_my_maker_order_updated(ctx: &MmArc, message: &new_protocol::MakerOrderUpdated) { @@ -1085,7 +1069,7 @@ fn maker_order_updated_p2p_notify( let (secret, peer_id) = p2p_private_and_peer_id_to_broadcast(&ctx, p2p_privkey); let encoded_msg = encode_and_sign(&msg, &secret).unwrap(); process_my_maker_order_updated(&ctx, &message); - broadcast_p2p_msg(&ctx, vec![topic], encoded_msg, peer_id); + broadcast_p2p_msg(&ctx, topic, encoded_msg, peer_id); } fn maker_order_cancelled_p2p_notify(ctx: MmArc, order: &MakerOrder) { @@ -1096,7 +1080,7 @@ fn maker_order_cancelled_p2p_notify(ctx: MmArc, order: &MakerOrder) { }); delete_my_order(&ctx, order.uuid, order.p2p_privkey); log::debug!("maker_order_cancelled_p2p_notify called, message {:?}", message); - broadcast_ordermatch_message(&ctx, vec![order.orderbook_topic()], message, order.p2p_keypair()); + broadcast_ordermatch_message(&ctx, order.orderbook_topic(), message, order.p2p_keypair()); } pub struct BalanceUpdateOrdermatchHandler { @@ -2275,22 +2259,27 @@ fn broadcast_keep_alive_for_pub(ctx: &MmArc, pubkey: &str, orderbook: &Orderbook None => return, }; - let mut trie_roots = HashMap::new(); - let mut topics = HashSet::new(); for (alb_pair, root) in state.trie_roots.iter() { + let mut trie_roots = HashMap::new(); + if *root == H64::default() && *root == hashed_null_node::() { continue; } - topics.insert(orderbook_topic_from_ordered_pair(alb_pair)); + trie_roots.insert(alb_pair.clone(), *root); - } - let message = new_protocol::PubkeyKeepAlive { - trie_roots, - timestamp: now_sec(), - }; + let message = new_protocol::PubkeyKeepAlive { + trie_roots, + timestamp: now_sec(), + }; - broadcast_ordermatch_message(ctx, topics, message.into(), p2p_privkey); + broadcast_ordermatch_message( + ctx, + orderbook_topic_from_ordered_pair(alb_pair), + message.clone().into(), + p2p_privkey, + ); + } } pub async fn broadcast_maker_orders_keep_alive_loop(ctx: MmArc) { @@ -2324,13 +2313,13 @@ pub async fn broadcast_maker_orders_keep_alive_loop(ctx: MmArc) { fn broadcast_ordermatch_message( ctx: &MmArc, - topics: impl IntoIterator, + topic: String, msg: new_protocol::OrdermatchMessage, p2p_privkey: Option<&KeyPair>, ) { let (secret, peer_id) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey); let encoded_msg = encode_and_sign(&msg, &secret).unwrap(); - broadcast_p2p_msg(ctx, topics.into_iter().collect(), encoded_msg, peer_id); + broadcast_p2p_msg(ctx, topic, encoded_msg, peer_id); } /// The order is ordered by [`OrderbookItem::price`] and [`OrderbookItem::uuid`]. @@ -3499,7 +3488,7 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: maker_order_uuid: reserved_msg.maker_order_uuid, }; let topic = my_order.orderbook_topic(); - broadcast_ordermatch_message(&ctx, vec![topic], connect.clone().into(), my_order.p2p_keypair()); + broadcast_ordermatch_message(&ctx, topic, connect.clone().into(), my_order.p2p_keypair()); let taker_match = TakerMatch { reserved: reserved_msg, connect, @@ -3646,7 +3635,7 @@ async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: }; let topic = order.orderbook_topic(); log::debug!("Request matched sending reserved {:?}", reserved); - broadcast_ordermatch_message(&ctx, vec![topic], reserved.clone().into(), order.p2p_keypair()); + broadcast_ordermatch_message(&ctx, topic, reserved.clone().into(), order.p2p_keypair()); let maker_match = MakerMatch { request: taker_request, reserved, @@ -3720,7 +3709,7 @@ async fn process_taker_connect(ctx: MmArc, sender_pubkey: H256Json, connect_msg: my_order.started_swaps.push(order_match.request.uuid); lp_connect_start_bob(ctx.clone(), order_match, my_order.clone()); let topic = my_order.orderbook_topic(); - broadcast_ordermatch_message(&ctx, vec![topic.clone()], connected.into(), my_order.p2p_keypair()); + broadcast_ordermatch_message(&ctx, topic.clone(), connected.into(), my_order.p2p_keypair()); // If volume is less order will be cancelled a bit later if my_order.available_amount() >= my_order.min_base_vol { @@ -3890,12 +3879,7 @@ pub async fn lp_auto_buy( ) .await ); - broadcast_ordermatch_message( - ctx, - vec![order.orderbook_topic()], - order.clone().into(), - order.p2p_keypair(), - ); + broadcast_ordermatch_message(ctx, order.orderbook_topic(), order.clone().into(), order.p2p_keypair()); let res = try_s!(json::to_vec(&Mm2RpcResult::new(SellBuyResponse { request: (&order.request).into(), diff --git a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs index d00cd50050..b15f5e6d38 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs @@ -129,7 +129,7 @@ pub struct MakerOrderCreated { pub rel_protocol_info: Vec, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Clone, Serialize)] pub struct PubkeyKeepAlive { pub trie_roots: HashMap, pub timestamp: u64, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index ba69c3d199..5c972c2df9 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -277,7 +277,7 @@ pub fn broadcast_swap_msg_every_delayed( pub fn broadcast_swap_message(ctx: &MmArc, topic: String, msg: T, p2p_privkey: &Option) { let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); let encoded_msg = encode_and_sign(&msg, &p2p_private).unwrap(); - broadcast_p2p_msg(ctx, vec![topic], encoded_msg, from); + broadcast_p2p_msg(ctx, topic, encoded_msg, from); } /// Broadcast the tx message once @@ -288,7 +288,7 @@ pub fn broadcast_p2p_tx_msg(ctx: &MmArc, topic: String, msg: &TransactionEnum, p let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); let encoded_msg = encode_and_sign(&msg.tx_hex(), &p2p_private).unwrap(); - broadcast_p2p_msg(ctx, vec![topic], encoded_msg, from); + broadcast_p2p_msg(ctx, topic, encoded_msg, from); } pub async fn process_swap_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PRequestResult<()> { @@ -1072,7 +1072,7 @@ async fn broadcast_my_swap_status(ctx: &MmArc, uuid: Uuid) -> Result<(), String> data: status, }; let msg = json::to_vec(&status).expect("Swap status ser should never fail"); - broadcast_p2p_msg(ctx, vec![swap_topic(&uuid)], msg, None); + broadcast_p2p_msg(ctx, swap_topic(&uuid), msg, None); Ok(()) } @@ -1487,7 +1487,7 @@ pub fn broadcast_swap_v2_message( signature: signature.serialize_compact().into(), payload: encoded_msg, }; - broadcast_p2p_msg(ctx, vec![topic], signed_message.encode_to_vec(), from); + broadcast_p2p_msg(ctx, topic, signed_message.encode_to_vec(), from); } /// Spawns the loop that broadcasts message every `interval` seconds returning the AbortOnDropHandle diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index ec96d8f484..4377f803ac 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -6,7 +6,7 @@ use crypto::privkey::key_pair_from_seed; use db_common::sqlite::rusqlite::Connection; use futures::{channel::mpsc, StreamExt}; use mm2_core::mm_ctx::{MmArc, MmCtx}; -use mm2_libp2p::atomicdex_behaviour::AdexBehaviourCmd; +use mm2_libp2p::AdexBehaviourCmd; use mm2_libp2p::{decode_message, PeerId}; use mm2_net::p2p::P2PContext; use mm2_test_helpers::for_tests::mm_ctx_with_iguana; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs index e060175858..d5f3c41a50 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs @@ -307,10 +307,9 @@ pub fn version(ctx: MmArc) -> HyRes { } pub async fn get_peers_info(ctx: MmArc) -> Result>, String> { - use mm2_libp2p::atomicdex_behaviour::get_peers_info; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = get_peers_info(cmd_tx).await; + let result = mm2_libp2p::get_peers_info(cmd_tx).await; let result = json!({ "result": result, }); @@ -319,10 +318,9 @@ pub async fn get_peers_info(ctx: MmArc) -> Result>, String> { } pub async fn get_gossip_mesh(ctx: MmArc) -> Result>, String> { - use mm2_libp2p::atomicdex_behaviour::get_gossip_mesh; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = get_gossip_mesh(cmd_tx).await; + let result = mm2_libp2p::get_gossip_mesh(cmd_tx).await; let result = json!({ "result": result, }); @@ -331,10 +329,9 @@ pub async fn get_gossip_mesh(ctx: MmArc) -> Result>, String> { } pub async fn get_gossip_peer_topics(ctx: MmArc) -> Result>, String> { - use mm2_libp2p::atomicdex_behaviour::get_gossip_peer_topics; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = get_gossip_peer_topics(cmd_tx).await; + let result = mm2_libp2p::get_gossip_peer_topics(cmd_tx).await; let result = json!({ "result": result, }); @@ -343,10 +340,9 @@ pub async fn get_gossip_peer_topics(ctx: MmArc) -> Result>, Str } pub async fn get_gossip_topic_peers(ctx: MmArc) -> Result>, String> { - use mm2_libp2p::atomicdex_behaviour::get_gossip_topic_peers; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = get_gossip_topic_peers(cmd_tx).await; + let result = mm2_libp2p::get_gossip_topic_peers(cmd_tx).await; let result = json!({ "result": result, }); @@ -355,10 +351,9 @@ pub async fn get_gossip_topic_peers(ctx: MmArc) -> Result>, Str } pub async fn get_relay_mesh(ctx: MmArc) -> Result>, String> { - use mm2_libp2p::atomicdex_behaviour::get_relay_mesh; let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = get_relay_mesh(cmd_tx).await; + let result = mm2_libp2p::get_relay_mesh(cmd_tx).await; let result = json!({ "result": result, }); diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index 680ecb3667..f5025e85d3 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -38,13 +38,14 @@ async fn test_mm2_stops_impl( .await .unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); - Timer::sleep(1.).await; + Timer::sleep(2.).await; let alice_conf = Mm2TestConf::light_node(&alice_passphrase, &coins, &[&mm_bob.my_seed_addr()]); let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, Some(wasm_start)) .await .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + Timer::sleep(2.).await; // Enable coins on Bob side. Print the replies in case we need the address. let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums(), None).await; @@ -106,6 +107,7 @@ async fn trade_base_rel_electrum( let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, Some(wasm_start)) .await .unwrap(); + Timer::sleep(2.).await; let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); diff --git a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs index 283169115b..9be563b420 100644 --- a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs @@ -443,6 +443,7 @@ fn test_best_orders_v2_exclude_mine() { None, ) .unwrap(); + thread::sleep(Duration::from_secs(2)); let _ = block_on(enable_coins_eth_electrum(&mm_bob, ETH_DEV_NODES, None)); let bob_orders = [ @@ -484,6 +485,7 @@ fn test_best_orders_v2_exclude_mine() { None, ) .unwrap(); + thread::sleep(Duration::from_secs(2)); let _ = block_on(enable_coins_eth_electrum(&mm_alice, ETH_DEV_NODES, None)); let alice_orders = [("RICK", "MORTY", "0.85", "1", None)]; @@ -504,6 +506,8 @@ fn test_best_orders_v2_exclude_mine() { alice_order_ids.insert(result.result.uuid); assert!(status.is_success(), "!setprice: {}", data); } + thread::sleep(Duration::from_secs(2)); + let response = block_on(best_orders_v2_by_number(&mm_alice, "RICK", "buy", 100, false)); log!("all orders response: {response:?}"); assert_eq!(response.result.orders.get("MORTY").unwrap().len(), 3); diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs deleted file mode 100644 index 561e97774d..0000000000 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ /dev/null @@ -1,273 +0,0 @@ -use crate::integration_tests_common::enable_electrum; -use common::executor::Timer; -use common::{block_on, log}; -use mm2_number::BigDecimal; -use mm2_rpc::data::legacy::OrderbookResponse; -use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, check_stats_swap_status, enable_eth_coin, - enable_tendermint, iris_nimda_testnet_conf, iris_testnet_conf, rick_conf, tbnb_conf, - usdc_ibc_iris_testnet_conf, MarketMakerIt, RICK_ELECTRUM_ADDRS}; -use serde_json::{json, Value as Json}; -use std::convert::TryFrom; -use std::env; - -// https://academy.binance.com/en/articles/connecting-metamask-to-binance-smart-chain -const TBNB_URLS: &[&str] = &["https://data-seed-prebsc-1-s1.binance.org:8545/"]; -// https://testnet.bscscan.com/address/0xb1ad803ea4f57401639c123000c75f5b66e4d123 -const TBNB_SWAP_CONTRACT: &str = "0xB1Ad803ea4F57401639c123000C75F5B66E4D123"; - -#[test] -fn start_swap_operation() { - let pairs = [ - ("USDC-IBC-IRIS", "IRIS-NIMDA"), - ("IRIS-NIMDA", "RICK"), - // ("USDC-IBC-IRIS", "tBNB"), having fund problems - ]; - block_on(trade_base_rel_iris(&pairs, 1, 2, 0.008)); -} - -pub async fn trade_base_rel_iris( - pairs: &[(&'static str, &'static str)], - maker_price: i32, - taker_price: i32, - volume: f64, -) { - let bob_passphrase = String::from("iris test seed"); - let alice_passphrase = String::from("iris test2 seed"); - - let coins = json!([ - usdc_ibc_iris_testnet_conf(), - iris_nimda_testnet_conf(), - iris_testnet_conf(), - rick_conf(), - tbnb_conf(), - ]); - - println!("coins config {}", serde_json::to_string(&coins).unwrap()); - - let mut mm_bob = MarketMakerIt::start_async( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("BOB_TRADE_IP") .ok(), - "rpcip": env::var("BOB_TRADE_IP") .ok(), - "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .await - .unwrap(); - - Timer::sleep(1.).await; - - let mut mm_alice = MarketMakerIt::start_async( - json!({ - "gui": "nogui", - "netid": 8999, - "dht": "on", - "myipaddr": env::var("ALICE_TRADE_IP") .ok(), - "rpcip": env::var("ALICE_TRADE_IP") .ok(), - "passphrase": alice_passphrase, - "coins": coins, - "seednodes": [mm_bob.my_seed_addr()], - "rpc_password": "password", - "skip_startup_checks": true, - }), - "password".into(), - None, - ) - .await - .unwrap(); - - dbg!( - enable_tendermint( - &mm_bob, - "IRIS-TEST", - &["IRIS-NIMDA", "USDC-IBC-IRIS"], - &["http://34.80.202.172:26657"], - false - ) - .await - ); - dbg!(enable_electrum(&mm_bob, "RICK", false, RICK_ELECTRUM_ADDRS, None).await); - - dbg!( - enable_tendermint( - &mm_alice, - "IRIS-TEST", - &["IRIS-NIMDA", "USDC-IBC-IRIS"], - &["http://34.80.202.172:26657"], - false - ) - .await - ); - dbg!(enable_electrum(&mm_alice, "RICK", false, RICK_ELECTRUM_ADDRS, None).await); - dbg!(enable_eth_coin(&mm_bob, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None, false).await); - dbg!(enable_eth_coin(&mm_alice, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None, false).await); - - for (base, rel) in pairs.iter() { - log!("Issue bob {}/{} sell request", base, rel); - let rc = mm_bob - .rpc(&json!({ - "userpass": mm_bob.userpass, - "method": "setprice", - "base": base, - "rel": rel, - "price": maker_price, - "volume": volume - })) - .await - .unwrap(); - assert!(rc.0.is_success(), "!setprice: {}", rc.1); - } - - let mut uuids = vec![]; - - for (base, rel) in pairs.iter() { - common::log::info!( - "Trigger alice subscription to {}/{} orderbook topic first and sleep for 1 second", - base, - rel - ); - let rc = mm_alice - .rpc(&json!({ - "userpass": mm_alice.userpass, - "method": "orderbook", - "base": base, - "rel": rel, - })) - .await - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - Timer::sleep(1.).await; - common::log::info!("Issue alice {}/{} buy request", base, rel); - let rc = mm_alice - .rpc(&json!({ - "userpass": mm_alice.userpass, - "method": "buy", - "base": base, - "rel": rel, - "volume": volume, - "price": taker_price - })) - .await - .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); - let buy_json: Json = serde_json::from_str(&rc.1).unwrap(); - uuids.push(buy_json["result"]["uuid"].as_str().unwrap().to_owned()); - } - - for (base, rel) in pairs.iter() { - // ensure the swaps are started - let expected_log = format!("Entering the taker_swap_loop {}/{}", base, rel); - mm_alice - .wait_for_log(5., |log| log.contains(&expected_log)) - .await - .unwrap(); - let expected_log = format!("Entering the maker_swap_loop {}/{}", base, rel); - mm_bob - .wait_for_log(5., |log| log.contains(&expected_log)) - .await - .unwrap() - } - - for uuid in uuids.iter() { - // ensure the swaps are indexed to the SQLite database - let expected_log = format!("Inserting new swap {} to the SQLite database", uuid); - mm_alice - .wait_for_log(5., |log| log.contains(&expected_log)) - .await - .unwrap(); - mm_bob - .wait_for_log(5., |log| log.contains(&expected_log)) - .await - .unwrap() - } - - for uuid in uuids.iter() { - match mm_bob - .wait_for_log(900., |log| log.contains(&format!("[swap uuid={}] Finished", uuid))) - .await - { - Ok(_) => (), - Err(_) => { - println!("{}", mm_bob.log_as_utf8().unwrap()); - }, - } - - match mm_alice - .wait_for_log(900., |log| log.contains(&format!("[swap uuid={}] Finished", uuid))) - .await - { - Ok(_) => (), - Err(_) => { - println!("{}", mm_alice.log_as_utf8().unwrap()); - }, - } - - log!("Waiting a few second for the fresh swap status to be saved.."); - Timer::sleep(5.).await; - - println!("{}", mm_alice.log_as_utf8().unwrap()); - log!("Checking alice/taker status.."); - check_my_swap_status( - &mm_alice, - uuid, - BigDecimal::try_from(volume).unwrap(), - BigDecimal::try_from(volume).unwrap(), - ) - .await; - - println!("{}", mm_bob.log_as_utf8().unwrap()); - log!("Checking bob/maker status.."); - check_my_swap_status( - &mm_bob, - uuid, - BigDecimal::try_from(volume).unwrap(), - BigDecimal::try_from(volume).unwrap(), - ) - .await; - } - - log!("Waiting 3 seconds for nodes to broadcast their swaps data.."); - Timer::sleep(3.).await; - - for uuid in uuids.iter() { - log!("Checking alice status.."); - check_stats_swap_status(&mm_alice, uuid).await; - - log!("Checking bob status.."); - check_stats_swap_status(&mm_bob, uuid).await; - } - - log!("Checking alice recent swaps.."); - check_recent_swaps(&mm_alice, uuids.len()).await; - log!("Checking bob recent swaps.."); - check_recent_swaps(&mm_bob, uuids.len()).await; - for (base, rel) in pairs.iter() { - log!("Get {}/{} orderbook", base, rel); - let rc = mm_bob - .rpc(&json!({ - "userpass": mm_bob.userpass, - "method": "orderbook", - "base": base, - "rel": rel, - })) - .await - .unwrap(); - assert!(rc.0.is_success(), "!orderbook: {}", rc.1); - - let bob_orderbook: OrderbookResponse = serde_json::from_str(&rc.1).unwrap(); - log!("{}/{} orderbook {:?}", base, rel, bob_orderbook); - - assert_eq!(0, bob_orderbook.bids.len(), "{} {} bids must be empty", base, rel); - assert_eq!(0, bob_orderbook.asks.len(), "{} {} asks must be empty", base, rel); - } - mm_bob.stop().await.unwrap(); - mm_alice.stop().await.unwrap(); -} diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 87420af2d4..fff8b409ab 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -771,7 +771,7 @@ async fn trade_base_rel_electrum( log!("Bob log path: {}", mm_bob.log_path.display()) } - Timer::sleep(1.).await; + Timer::sleep(2.).await; let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(&alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, None) @@ -784,6 +784,8 @@ async fn trade_base_rel_electrum( log!("Alice log path: {}", mm_alice.log_path.display()) } + Timer::sleep(2.).await; + #[cfg(all(feature = "zhtlc-native-tests", not(target_arch = "wasm32")))] { let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); @@ -1735,6 +1737,7 @@ fn test_cancel_order() { None, ) .unwrap(); + thread::sleep(Duration::from_secs(2)); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); // Enable coins on Bob side. Print the replies in case we need the "address". @@ -1773,6 +1776,7 @@ fn test_cancel_order() { None, ) .unwrap(); + thread::sleep(Duration::from_secs(2)); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -6180,6 +6184,7 @@ fn test_buy_min_volume() { None, ) .unwrap(); + thread::sleep(Duration::from_secs(2)); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); diff --git a/mm2src/mm2_main/tests/mm2_tests/mod.rs b/mm2src/mm2_main/tests/mm2_tests/mod.rs index e5953479bc..cfc3aa1e15 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mod.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mod.rs @@ -1,7 +1,6 @@ mod bch_and_slp_tests; mod best_orders_tests; mod eth_tests; -mod iris_swap; mod lightning_tests; mod lp_bot_tests; mod mm2_tests_inner; diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 07fad8599f..3892e85c23 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -7,7 +7,7 @@ use mm2_test_helpers::for_tests::{atom_testnet_conf, disable_coin, disable_coin_ iris_testnet_conf, my_balance, send_raw_transaction, withdraw_v1, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::structs::{RpcV2Response, TendermintActivationResult, TransactionDetails}; -use serde_json::{self as json, json}; +use serde_json::json; const ATOM_TEST_BALANCE_SEED: &str = "atom test seed"; const ATOM_TEST_WITHDRAW_SEED: &str = "atom test withdraw seed"; @@ -36,7 +36,7 @@ fn test_tendermint_activation_and_balance() { false, )); - let result: RpcV2Response = json::from_value(activation_result).unwrap(); + let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); assert_eq!(result.result.address, expected_address); let expected_balance: BigDecimal = "0.575457".parse().unwrap(); assert_eq!(result.result.balance.unwrap().spendable, expected_balance); @@ -63,7 +63,7 @@ fn test_tendermint_activation_without_balance() { false, )); - let result: RpcV2Response = json::from_value(activation_result).unwrap(); + let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); assert!(result.result.balance.is_none()); assert!(result.result.tokens_balances.is_none()); @@ -87,7 +87,7 @@ fn test_tendermint_hd_address() { false, )); - let result: RpcV2Response = json::from_value(activation_result).unwrap(); + let result: RpcV2Response = serde_json::from_value(activation_result).unwrap(); assert_eq!(result.result.address, expected_address); } @@ -105,7 +105,7 @@ fn test_tendermint_withdraw() { ATOM_TENDERMINT_RPC_URLS, false, )); - println!("Activation {}", json::to_string(&activation_res).unwrap()); + println!("Activation {}", serde_json::to_string(&activation_res).unwrap()); // just call withdraw without sending to check response correctness let tx_details = block_on(withdraw_v1( @@ -115,7 +115,7 @@ fn test_tendermint_withdraw() { "0.1", None, )); - println!("Withdraw to other {}", json::to_string(&tx_details).unwrap()); + println!("Withdraw to other {}", serde_json::to_string(&tx_details).unwrap()); // TODO how to check it if the fee is dynamic? /* let expected_total: BigDecimal = "0.15".parse().unwrap(); @@ -139,7 +139,7 @@ fn test_tendermint_withdraw() { "0.1", None, )); - println!("Withdraw to self {}", json::to_string(&tx_details).unwrap()); + println!("Withdraw to self {}", serde_json::to_string(&tx_details).unwrap()); // TODO how to check it if the fee is dynamic? /* @@ -167,7 +167,7 @@ fn test_tendermint_withdraw() { None, )); let send_raw_tx = block_on(send_raw_transaction(&mm, ATOM_TICKER, &tx_details.tx_hex)); - println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); + println!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } #[test] @@ -179,7 +179,10 @@ fn test_tendermint_withdraw_hd() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let activation_res = block_on(enable_tendermint(&mm, coin, &[], IRIS_TESTNET_RPC_URLS, false)); - println!("Activation with assets {}", json::to_string(&activation_res).unwrap()); + println!( + "Activation with assets {}", + serde_json::to_string(&activation_res).unwrap() + ); // We will withdraw from HD account 0 and change 0 and address_index 1 let path_to_address = StandardHDCoinAddress { @@ -196,7 +199,7 @@ fn test_tendermint_withdraw_hd() { "0.1", Some(path_to_address.clone()), )); - println!("Withdraw to other {}", json::to_string(&tx_details).unwrap()); + println!("Withdraw to other {}", serde_json::to_string(&tx_details).unwrap()); // TODO how to check it if the fee is dynamic? /* let expected_total: BigDecimal = "0.15".parse().unwrap(); @@ -220,7 +223,7 @@ fn test_tendermint_withdraw_hd() { "0.1", Some(path_to_address.clone()), )); - println!("Withdraw to self {}", json::to_string(&tx_details).unwrap()); + println!("Withdraw to self {}", serde_json::to_string(&tx_details).unwrap()); // TODO how to check it if the fee is dynamic? /* @@ -248,7 +251,7 @@ fn test_tendermint_withdraw_hd() { Some(path_to_address), )); let send_raw_tx = block_on(send_raw_transaction(&mm, coin, &tx_details.tx_hex)); - println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); + println!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } #[test] @@ -265,7 +268,7 @@ fn test_custom_gas_limit_on_tendermint_withdraw() { ATOM_TENDERMINT_RPC_URLS, false, )); - println!("Activation {}", json::to_string(&activation_res).unwrap()); + println!("Activation {}", serde_json::to_string(&activation_res).unwrap()); let request = block_on(mm.rpc(&json!({ "userpass": mm.userpass, @@ -281,7 +284,7 @@ fn test_custom_gas_limit_on_tendermint_withdraw() { }))) .unwrap(); assert_eq!(request.0, common::StatusCode::OK, "'withdraw' failed: {}", request.1); - let tx_details: TransactionDetails = json::from_str(&request.1).unwrap(); + let tx_details: TransactionDetails = serde_json::from_str(&request.1).unwrap(); assert_eq!(tx_details.fee_details["gas_limit"], 150000); } @@ -302,10 +305,13 @@ fn test_tendermint_ibc_withdraw() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); - println!("Activation with assets {}", json::to_string(&activation_res).unwrap()); + println!( + "Activation with assets {}", + serde_json::to_string(&activation_res).unwrap() + ); let activation_res = block_on(enable_tendermint_token(&mm, token)); - println!("Token activation {}", json::to_string(&activation_res).unwrap()); + println!("Token activation {}", serde_json::to_string(&activation_res).unwrap()); let tx_details = block_on(ibc_withdraw( &mm, @@ -315,7 +321,10 @@ fn test_tendermint_ibc_withdraw() { "0.1", None, )); - println!("IBC transfer to atom address {}", json::to_string(&tx_details).unwrap()); + println!( + "IBC transfer to atom address {}", + serde_json::to_string(&tx_details).unwrap() + ); let expected_spent: BigDecimal = "0.1".parse().unwrap(); assert_eq!(tx_details.spent_by_me, expected_spent); @@ -332,7 +341,7 @@ fn test_tendermint_ibc_withdraw() { None, )); let send_raw_tx = block_on(send_raw_transaction(&mm, token, &tx_details.tx_hex)); - println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); + println!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } #[test] fn test_tendermint_ibc_withdraw_hd() { @@ -349,7 +358,10 @@ fn test_tendermint_ibc_withdraw_hd() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let activation_res = block_on(enable_tendermint(&mm, coin, &[], IRIS_TESTNET_RPC_URLS, false)); - println!("Activation with assets {}", json::to_string(&activation_res).unwrap()); + println!( + "Activation with assets {}", + serde_json::to_string(&activation_res).unwrap() + ); // We will withdraw from HD account 0 and change 0 and address_index 1 let path_to_address = StandardHDCoinAddress { @@ -366,7 +378,10 @@ fn test_tendermint_ibc_withdraw_hd() { "0.1", Some(path_to_address.clone()), )); - println!("IBC transfer to atom address {}", json::to_string(&tx_details).unwrap()); + println!( + "IBC transfer to atom address {}", + serde_json::to_string(&tx_details).unwrap() + ); assert_eq!(tx_details.to, vec![IBC_TARGET_ADDRESS.to_owned()]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); @@ -380,7 +395,7 @@ fn test_tendermint_ibc_withdraw_hd() { Some(path_to_address), )); let send_raw_tx = block_on(send_raw_transaction(&mm, coin, &tx_details.tx_hex)); - println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); + println!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } #[test] @@ -393,10 +408,13 @@ fn test_tendermint_token_activation_and_withdraw() { let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); - println!("Activation with assets {}", json::to_string(&activation_res).unwrap()); + println!( + "Activation with assets {}", + serde_json::to_string(&activation_res).unwrap() + ); let activation_res = block_on(enable_tendermint_token(&mm, token)); - println!("Token activation {}", json::to_string(&activation_res).unwrap()); + println!("Token activation {}", serde_json::to_string(&activation_res).unwrap()); // just call withdraw without sending to check response correctness let tx_details = block_on(withdraw_v1( @@ -407,7 +425,7 @@ fn test_tendermint_token_activation_and_withdraw() { None, )); - println!("Withdraw to other {}", json::to_string(&tx_details).unwrap()); + println!("Withdraw to other {}", serde_json::to_string(&tx_details).unwrap()); let expected_total: BigDecimal = "0.1".parse().unwrap(); assert_eq!(tx_details.total_amount, expected_total); @@ -437,7 +455,7 @@ fn test_tendermint_token_activation_and_withdraw() { "0.1", None, )); - println!("Withdraw to self {}", json::to_string(&tx_details).unwrap()); + println!("Withdraw to self {}", serde_json::to_string(&tx_details).unwrap()); let expected_total: BigDecimal = "0.1".parse().unwrap(); let expected_received: BigDecimal = "0.1".parse().unwrap(); @@ -468,7 +486,7 @@ fn test_tendermint_token_activation_and_withdraw() { None, )); let send_raw_tx = block_on(send_raw_transaction(&mm, token, &tx_details.tx_hex)); - println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); + println!("Send raw tx {}", serde_json::to_string(&send_raw_tx).unwrap()); } #[test] @@ -620,3 +638,451 @@ fn test_passive_coin_and_force_disable() { // This should failed, because platform coin was purged with it's tokens. block_on(disable_coin_err(&mm, "IRIS-NIMDA", false)); } + +mod swap { + use super::*; + + use crate::integration_tests_common::enable_electrum; + use common::executor::Timer; + use common::log; + use instant::Duration; + use mm2_rpc::data::legacy::OrderbookResponse; + use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, check_stats_swap_status, + enable_eth_coin, rick_conf, tbnb_conf, usdc_ibc_iris_testnet_conf, + RICK_ELECTRUM_ADDRS}; + use std::convert::TryFrom; + use std::{env, thread}; + + const BOB_PASSPHRASE: &str = "iris test seed"; + const ALICE_PASSPHRASE: &str = "iris test2 seed"; + + // https://academy.binance.com/en/articles/connecting-metamask-to-binance-smart-chain + const TBNB_URLS: &[&str] = &["https://data-seed-prebsc-1-s1.binance.org:8545/"]; + // https://testnet.bscscan.com/address/0xb1ad803ea4f57401639c123000c75f5b66e4d123 + const TBNB_SWAP_CONTRACT: &str = "0xB1Ad803ea4F57401639c123000C75F5B66E4D123"; + + #[test] + fn swap_usdc_ibc_with_nimda() { + let bob_passphrase = String::from(BOB_PASSPHRASE); + let alice_passphrase = String::from(ALICE_PASSPHRASE); + + let coins = json!([ + usdc_ibc_iris_testnet_conf(), + iris_nimda_testnet_conf(), + iris_testnet_conf(), + ]); + + let mm_bob = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 8999, + "dht": "on", + "myipaddr": env::var("BOB_TRADE_IP") .ok(), + "rpcip": env::var("BOB_TRADE_IP") .ok(), + "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), + "passphrase": bob_passphrase, + "coins": coins, + "rpc_password": "password", + "i_am_seed": true, + }), + "password".into(), + None, + ) + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + let mm_alice = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 8999, + "dht": "on", + "myipaddr": env::var("ALICE_TRADE_IP") .ok(), + "rpcip": env::var("ALICE_TRADE_IP") .ok(), + "passphrase": alice_passphrase, + "coins": coins, + "seednodes": [mm_bob.my_seed_addr()], + "rpc_password": "password", + "skip_startup_checks": true, + }), + "password".into(), + None, + ) + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + dbg!(block_on(enable_tendermint( + &mm_bob, + "IRIS-TEST", + &["IRIS-NIMDA", "USDC-IBC-IRIS"], + &["http://34.80.202.172:26657"], + false + ))); + + dbg!(block_on(enable_tendermint( + &mm_alice, + "IRIS-TEST", + &["IRIS-NIMDA", "USDC-IBC-IRIS"], + &["http://34.80.202.172:26657"], + false + ))); + + block_on(trade_base_rel_tendermint( + mm_bob, + mm_alice, + "USDC-IBC-IRIS", + "IRIS-NIMDA", + 1, + 2, + 0.008, + )); + } + + #[test] + fn swap_iris_with_rick() { + let bob_passphrase = String::from(BOB_PASSPHRASE); + let alice_passphrase = String::from(ALICE_PASSPHRASE); + + let coins = json!([iris_testnet_conf(), rick_conf()]); + + let mm_bob = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 8999, + "dht": "on", + "myipaddr": env::var("BOB_TRADE_IP") .ok(), + "rpcip": env::var("BOB_TRADE_IP") .ok(), + "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), + "passphrase": bob_passphrase, + "coins": coins, + "rpc_password": "password", + "i_am_seed": true, + }), + "password".into(), + None, + ) + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + let mm_alice = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 8999, + "dht": "on", + "myipaddr": env::var("ALICE_TRADE_IP") .ok(), + "rpcip": env::var("ALICE_TRADE_IP") .ok(), + "passphrase": alice_passphrase, + "coins": coins, + "seednodes": [mm_bob.my_seed_addr()], + "rpc_password": "password", + "skip_startup_checks": true, + }), + "password".into(), + None, + ) + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + dbg!(block_on(enable_tendermint( + &mm_bob, + "IRIS-TEST", + &[], + &["http://34.80.202.172:26657"], + false + ))); + + dbg!(block_on(enable_tendermint( + &mm_alice, + "IRIS-TEST", + &[], + &["http://34.80.202.172:26657"], + false + ))); + + dbg!(block_on(enable_electrum( + &mm_bob, + "RICK", + false, + RICK_ELECTRUM_ADDRS, + None + ))); + + dbg!(block_on(enable_electrum( + &mm_alice, + "RICK", + false, + RICK_ELECTRUM_ADDRS, + None + ))); + + block_on(trade_base_rel_tendermint( + mm_bob, + mm_alice, + "IRIS-TEST", + "RICK", + 1, + 2, + 0.008, + )); + } + + #[test] + #[ignore] // having fund problems with tBNB + fn swap_iris_with_tbnb() { + let bob_passphrase = String::from(BOB_PASSPHRASE); + let alice_passphrase = String::from(ALICE_PASSPHRASE); + + let coins = json!([iris_testnet_conf(), tbnb_conf()]); + + let mm_bob = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 8999, + "dht": "on", + "myipaddr": env::var("BOB_TRADE_IP") .ok(), + "rpcip": env::var("BOB_TRADE_IP") .ok(), + "canbind": env::var("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), + "passphrase": bob_passphrase, + "coins": coins, + "rpc_password": "password", + "i_am_seed": true, + }), + "password".into(), + None, + ) + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + let mm_alice = MarketMakerIt::start( + json!({ + "gui": "nogui", + "netid": 8999, + "dht": "on", + "myipaddr": env::var("ALICE_TRADE_IP") .ok(), + "rpcip": env::var("ALICE_TRADE_IP") .ok(), + "passphrase": alice_passphrase, + "coins": coins, + "seednodes": [mm_bob.my_seed_addr()], + "rpc_password": "password", + "skip_startup_checks": true, + }), + "password".into(), + None, + ) + .unwrap(); + + thread::sleep(Duration::from_secs(1)); + + dbg!(block_on(enable_tendermint( + &mm_bob, + "IRIS-TEST", + &[], + &["http://34.80.202.172:26657"], + false + ))); + + dbg!(block_on(enable_tendermint( + &mm_alice, + "IRIS-TEST", + &[], + &["http://34.80.202.172:26657"], + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_bob, + "tBNB", + TBNB_URLS, + TBNB_SWAP_CONTRACT, + None, + false + ))); + + dbg!(block_on(enable_eth_coin( + &mm_alice, + "tBNB", + TBNB_URLS, + TBNB_SWAP_CONTRACT, + None, + false + ))); + + block_on(trade_base_rel_tendermint( + mm_bob, + mm_alice, + "IRIS-TEST", + "tBNB", + 1, + 2, + 0.008, + )); + } + + pub async fn trade_base_rel_tendermint( + mut mm_bob: MarketMakerIt, + mut mm_alice: MarketMakerIt, + base: &str, + rel: &str, + maker_price: i32, + taker_price: i32, + volume: f64, + ) { + log!("Issue bob {}/{} sell request", base, rel); + let rc = mm_bob + .rpc(&json!({ + "userpass": mm_bob.userpass, + "method": "setprice", + "base": base, + "rel": rel, + "price": maker_price, + "volume": volume + })) + .await + .unwrap(); + assert!(rc.0.is_success(), "!setprice: {}", rc.1); + + let mut uuids = vec![]; + + common::log::info!( + "Trigger alice subscription to {}/{} orderbook topic first and sleep for 1 second", + base, + rel + ); + let rc = mm_alice + .rpc(&json!({ + "userpass": mm_alice.userpass, + "method": "orderbook", + "base": base, + "rel": rel, + })) + .await + .unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + Timer::sleep(1.).await; + common::log::info!("Issue alice {}/{} buy request", base, rel); + let rc = mm_alice + .rpc(&json!({ + "userpass": mm_alice.userpass, + "method": "buy", + "base": base, + "rel": rel, + "volume": volume, + "price": taker_price + })) + .await + .unwrap(); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + let buy_json: serde_json::Value = serde_json::from_str(&rc.1).unwrap(); + uuids.push(buy_json["result"]["uuid"].as_str().unwrap().to_owned()); + + // ensure the swaps are started + let expected_log = format!("Entering the taker_swap_loop {}/{}", base, rel); + mm_alice + .wait_for_log(5., |log| log.contains(&expected_log)) + .await + .unwrap(); + let expected_log = format!("Entering the maker_swap_loop {}/{}", base, rel); + mm_bob + .wait_for_log(5., |log| log.contains(&expected_log)) + .await + .unwrap(); + + for uuid in uuids.iter() { + // ensure the swaps are indexed to the SQLite database + let expected_log = format!("Inserting new swap {} to the SQLite database", uuid); + mm_alice + .wait_for_log(5., |log| log.contains(&expected_log)) + .await + .unwrap(); + mm_bob + .wait_for_log(5., |log| log.contains(&expected_log)) + .await + .unwrap() + } + + for uuid in uuids.iter() { + match mm_bob + .wait_for_log(900., |log| log.contains(&format!("[swap uuid={}] Finished", uuid))) + .await + { + Ok(_) => (), + Err(_) => { + println!("{}", mm_bob.log_as_utf8().unwrap()); + }, + } + + match mm_alice + .wait_for_log(900., |log| log.contains(&format!("[swap uuid={}] Finished", uuid))) + .await + { + Ok(_) => (), + Err(_) => { + println!("{}", mm_alice.log_as_utf8().unwrap()); + }, + } + + log!("Waiting a few second for the fresh swap status to be saved.."); + Timer::sleep(5.).await; + + println!("{}", mm_alice.log_as_utf8().unwrap()); + log!("Checking alice/taker status.."); + check_my_swap_status( + &mm_alice, + uuid, + BigDecimal::try_from(volume).unwrap(), + BigDecimal::try_from(volume).unwrap(), + ) + .await; + + println!("{}", mm_bob.log_as_utf8().unwrap()); + log!("Checking bob/maker status.."); + check_my_swap_status( + &mm_bob, + uuid, + BigDecimal::try_from(volume).unwrap(), + BigDecimal::try_from(volume).unwrap(), + ) + .await; + } + + log!("Waiting 3 seconds for nodes to broadcast their swaps data.."); + Timer::sleep(3.).await; + + for uuid in uuids.iter() { + log!("Checking alice status.."); + check_stats_swap_status(&mm_alice, uuid).await; + + log!("Checking bob status.."); + check_stats_swap_status(&mm_bob, uuid).await; + } + + log!("Checking alice recent swaps.."); + check_recent_swaps(&mm_alice, uuids.len()).await; + log!("Checking bob recent swaps.."); + check_recent_swaps(&mm_bob, uuids.len()).await; + log!("Get {}/{} orderbook", base, rel); + let rc = mm_bob + .rpc(&json!({ + "userpass": mm_bob.userpass, + "method": "orderbook", + "base": base, + "rel": rel, + })) + .await + .unwrap(); + assert!(rc.0.is_success(), "!orderbook: {}", rc.1); + + let bob_orderbook: OrderbookResponse = serde_json::from_str(&rc.1).unwrap(); + log!("{}/{} orderbook {:?}", base, rel, bob_orderbook); + + assert_eq!(0, bob_orderbook.bids.len(), "{} {} bids must be empty", base, rel); + assert_eq!(0, bob_orderbook.asks.len(), "{} {} asks must be empty", base, rel); + + mm_bob.stop().await.unwrap(); + mm_alice.stop().await.unwrap(); + } +} diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index 62da4d0446..bb80a455f2 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -20,7 +20,7 @@ lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream"} -mm2-libp2p = { path = "../mm2_libp2p" } +mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } mm2_state_machine = { path = "../mm2_state_machine" } parking_lot = { version = "0.12.0", features = ["nightly"] } prost = "0.10" diff --git a/mm2src/mm2_net/src/network_event.rs b/mm2src/mm2_net/src/network_event.rs index 6dd5ee4396..35ce5de40d 100644 --- a/mm2src/mm2_net/src/network_event.rs +++ b/mm2src/mm2_net/src/network_event.rs @@ -5,7 +5,7 @@ use common::{executor::{SpawnFuture, Timer}, use mm2_core::mm_ctx::MmArc; pub use mm2_event_stream::behaviour::EventBehaviour; use mm2_event_stream::{Event, EventStreamConfiguration}; -use mm2_libp2p::atomicdex_behaviour; +use mm2_libp2p::behaviours::atomicdex; use serde_json::json; pub struct NetworkEvent { @@ -26,11 +26,11 @@ impl EventBehaviour for NetworkEvent { loop { let p2p_cmd_tx = p2p_ctx.cmd_tx.lock().clone(); - let peers_info = atomicdex_behaviour::get_peers_info(p2p_cmd_tx.clone()).await; - let gossip_mesh = atomicdex_behaviour::get_gossip_mesh(p2p_cmd_tx.clone()).await; - let gossip_peer_topics = atomicdex_behaviour::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; - let gossip_topic_peers = atomicdex_behaviour::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; - let relay_mesh = atomicdex_behaviour::get_relay_mesh(p2p_cmd_tx).await; + let peers_info = atomicdex::get_peers_info(p2p_cmd_tx.clone()).await; + let gossip_mesh = atomicdex::get_gossip_mesh(p2p_cmd_tx.clone()).await; + let gossip_peer_topics = atomicdex::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; + let gossip_topic_peers = atomicdex::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; + let relay_mesh = atomicdex::get_relay_mesh(p2p_cmd_tx).await; let event_data = json!({ "peers_info": peers_info, diff --git a/mm2src/mm2_net/src/p2p.rs b/mm2src/mm2_net/src/p2p.rs index 74ee9dd9f1..5ac6396da2 100644 --- a/mm2src/mm2_net/src/p2p.rs +++ b/mm2src/mm2_net/src/p2p.rs @@ -1,5 +1,5 @@ use mm2_core::mm_ctx::MmArc; -use mm2_libp2p::atomicdex_behaviour::AdexCmdTx; +use mm2_libp2p::behaviours::atomicdex::AdexCmdTx; #[cfg(test)] use mocktopus::macros::*; use parking_lot::Mutex; use std::sync::Arc; diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml new file mode 100644 index 0000000000..a4331bcb4b --- /dev/null +++ b/mm2src/mm2_p2p/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "mm2_p2p" +version = "0.1.0" +edition = "2021" + +[lib] +doctest = false + +[dependencies] +async-trait = "0.1" +common = { path = "../common" } +derive_more = "0.99" +futures = { version = "0.3.1", default-features = false } +futures-rustls = "0.21.1" +futures-ticker = "0.0.3" +hex = "0.4.2" +lazy_static = "1.4" +log = "0.4" +rand = { version = "0.7", default-features = false, features = ["wasm-bindgen"] } +regex = "1" +rmp-serde = "0.14.3" +secp256k1 = { version = "0.20", features = ["rand"] } +serde = { version = "1.0", default-features = false } +serde_bytes = "0.11.5" +sha2 = "0.9" +smallvec = "1.6.1" +syn = "2.0.18" +void = "1.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +instant = "0.1.12" +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.1", default-features = false, features = ["dns", "identify", "floodsub", "gossipsub", "noise", "ping", "request-response", "secp256k1", "tcp", "tokio", "websocket", "macros", "yamux"] } +tokio = { version = "1.20", default-features = false } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +instant = { version = "0.1.12", features = ["wasm-bindgen"] } +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.1", default-features = false, features = ["identify", "floodsub", "noise", "gossipsub", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket", "macros", "yamux"] } + +[dev-dependencies] +async-std = "1.6.2" +env_logger = "0.9.3" diff --git a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs new file mode 100644 index 0000000000..c895466515 --- /dev/null +++ b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs @@ -0,0 +1,1038 @@ +use common::executor::SpawnFuture; +use derive_more::Display; +use futures::channel::mpsc::{channel, Receiver, Sender}; +use futures::{channel::oneshot, + future::{join_all, poll_fn}, + Future, FutureExt, SinkExt, StreamExt}; +use futures_rustls::rustls; +use futures_ticker::Ticker; +use instant::Duration; +use libp2p::core::transport::Boxed as BoxedTransport; +use libp2p::core::ConnectedPoint; +use libp2p::floodsub::{Floodsub, FloodsubEvent, Topic as FloodsubTopic}; +use libp2p::gossipsub::{PublishError, SubscriptionError, ValidationMode}; +use libp2p::multiaddr::Protocol; +use libp2p::request_response::ResponseChannel; +use libp2p::swarm::{NetworkBehaviour, SwarmEvent, ToSwarm}; +use libp2p::{identity, noise, PeerId, Swarm}; +use libp2p::{Multiaddr, Transport}; +use log::{debug, error, info}; +use rand::seq::SliceRandom; +use rand::Rng; +use std::collections::hash_map::DefaultHasher; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::iter; +use std::net::IpAddr; +use std::task::{Context, Poll}; + +use super::peers_exchange::{PeerAddresses, PeersExchange, PeersExchangeRequest, PeersExchangeResponse}; +use super::ping::AdexPing; +use super::request_response::{build_request_response_behaviour, PeerRequest, PeerResponse, RequestResponseBehaviour, + RequestResponseSender}; +use crate::network::{get_all_network_seednodes, NETID_8762}; +use crate::relay_address::{RelayAddress, RelayAddressError}; +use crate::swarm_runtime::SwarmRuntime; +use crate::{NetworkInfo, NetworkPorts, RequestResponseBehaviourEvent}; + +pub use libp2p::gossipsub::{Behaviour as Gossipsub, IdentTopic, MessageAuthenticity, MessageId, Topic, TopicHash}; +pub use libp2p::gossipsub::{ConfigBuilder as GossipsubConfigBuilder, Event as GossipsubEvent, + Message as GossipsubMessage}; + +pub type AdexCmdTx = Sender; +pub type AdexEventRx = Receiver; + +pub const PEERS_TOPIC: &str = "PEERS"; + +pub(crate) const MAX_BUFFER_SIZE: usize = 1024 * 1024 - 100; + +const CONNECTED_RELAYS_CHECK_INTERVAL: Duration = Duration::from_secs(30); +const ANNOUNCE_INTERVAL: Duration = Duration::from_secs(600); +const ANNOUNCE_INITIAL_DELAY: Duration = Duration::from_secs(60); +const CHANNEL_BUF_SIZE: usize = 1024 * 8; + +/// The structure is the same as `PeerResponse`, +/// but is used to prevent `PeerResponse` from being used outside the network implementation. +#[derive(Debug, Eq, PartialEq)] +pub enum AdexResponse { + Ok { response: Vec }, + None, + Err { error: String }, +} + +impl From for AdexResponse { + fn from(res: PeerResponse) -> Self { + match res { + PeerResponse::Ok { res } => AdexResponse::Ok { response: res }, + PeerResponse::None => AdexResponse::None, + PeerResponse::Err { err } => AdexResponse::Err { error: err }, + } + } +} + +impl From for PeerResponse { + fn from(res: AdexResponse) -> Self { + match res { + AdexResponse::Ok { response } => PeerResponse::Ok { res: response }, + AdexResponse::None => PeerResponse::None, + AdexResponse::Err { error } => PeerResponse::Err { err: error }, + } + } +} + +#[derive(Debug)] +pub struct AdexResponseChannel(pub ResponseChannel); + +impl From> for AdexResponseChannel { + fn from(res: ResponseChannel) -> Self { AdexResponseChannel(res) } +} + +impl From for ResponseChannel { + fn from(res: AdexResponseChannel) -> Self { res.0 } +} + +#[derive(Debug)] +pub enum AdexBehaviourCmd { + Subscribe { + /// Subscribe to this topic + topic: String, + }, + PublishMsg { + topic: String, + msg: Vec, + }, + PublishMsgFrom { + topic: String, + msg: Vec, + from: PeerId, + }, + /// Request relays sequential until a response is received. + RequestAnyRelay { + req: Vec, + response_tx: oneshot::Sender)>>, + }, + /// Request given peers and collect all their responses. + RequestPeers { + req: Vec, + peers: Vec, + response_tx: oneshot::Sender>, + }, + /// Request relays and collect all their responses. + RequestRelays { + req: Vec, + response_tx: oneshot::Sender>, + }, + /// Send a response using a `response_channel`. + SendResponse { + /// Response to a request. + res: AdexResponse, + /// Pass the same `response_channel` as that was obtained from [`AdexBehaviourEvent::PeerRequest`]. + response_channel: AdexResponseChannel, + }, + GetPeersInfo { + result_tx: oneshot::Sender>>, + }, + GetGossipMesh { + result_tx: oneshot::Sender>>, + }, + GetGossipPeerTopics { + result_tx: oneshot::Sender>>, + }, + GetGossipTopicPeers { + result_tx: oneshot::Sender>>, + }, + GetRelayMesh { + result_tx: oneshot::Sender>, + }, + /// Add a reserved peer to the peer exchange. + AddReservedPeer { + peer: PeerId, + addresses: PeerAddresses, + }, + PropagateMessage { + message_id: MessageId, + propagation_source: PeerId, + }, +} + +/// Returns info about connected peers +pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> HashMap> { + let (result_tx, rx) = oneshot::channel(); + let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; + cmd_tx.send(cmd).await.expect("Rx should be present"); + rx.await.expect("Tx should be present") +} + +/// Returns current gossipsub mesh state +pub async fn get_gossip_mesh(mut cmd_tx: AdexCmdTx) -> HashMap> { + let (result_tx, rx) = oneshot::channel(); + let cmd = AdexBehaviourCmd::GetGossipMesh { result_tx }; + cmd_tx.send(cmd).await.expect("Rx should be present"); + rx.await.expect("Tx should be present") +} + +pub async fn get_gossip_peer_topics(mut cmd_tx: AdexCmdTx) -> HashMap> { + let (result_tx, rx) = oneshot::channel(); + let cmd = AdexBehaviourCmd::GetGossipPeerTopics { result_tx }; + cmd_tx.send(cmd).await.expect("Rx should be present"); + rx.await.expect("Tx should be present") +} + +pub async fn get_gossip_topic_peers(mut cmd_tx: AdexCmdTx) -> HashMap> { + let (result_tx, rx) = oneshot::channel(); + let cmd = AdexBehaviourCmd::GetGossipTopicPeers { result_tx }; + cmd_tx.send(cmd).await.expect("Rx should be present"); + rx.await.expect("Tx should be present") +} + +pub async fn get_relay_mesh(mut cmd_tx: AdexCmdTx) -> Vec { + let (result_tx, rx) = oneshot::channel(); + let cmd = AdexBehaviourCmd::GetRelayMesh { result_tx }; + cmd_tx.send(cmd).await.expect("Rx should be present"); + rx.await.expect("Tx should be present") +} + +async fn request_one_peer(peer: PeerId, req: Vec, mut request_response_tx: RequestResponseSender) -> PeerResponse { + // Use the internal receiver to receive a response to this request. + let (internal_response_tx, internal_response_rx) = oneshot::channel(); + let request = PeerRequest { req }; + request_response_tx + .send((peer, request, internal_response_tx)) + .await + .unwrap(); + + match internal_response_rx.await { + Ok(response) => response, + Err(e) => PeerResponse::Err { + err: format!("Error on request the peer {:?}: \"{:?}\". Request next peer", peer, e), + }, + } +} + +/// Request the peers sequential until a `PeerResponse::Ok()` will not be received. +async fn request_any_peer( + peers: Vec, + request_data: Vec, + request_response_tx: RequestResponseSender, + response_tx: oneshot::Sender)>>, +) { + debug!("start request_any_peer loop: peers {}", peers.len()); + for peer in peers { + match request_one_peer(peer, request_data.clone(), request_response_tx.clone()).await { + PeerResponse::Ok { res } => { + debug!("Received a response from peer {:?}, stop the request loop", peer); + if response_tx.send(Some((peer, res))).is_err() { + error!("Response oneshot channel was closed"); + } + return; + }, + PeerResponse::None => { + debug!("Received None from peer {:?}, request next peer", peer); + }, + PeerResponse::Err { err } => { + error!("Error on request {:?} peer: {:?}. Request next peer", peer, err); + }, + }; + } + + debug!("None of the peers responded to the request"); + if response_tx.send(None).is_err() { + error!("Response oneshot channel was closed"); + }; +} + +/// Request the peers and collect all their responses. +async fn request_peers( + peers: Vec, + request_data: Vec, + request_response_tx: RequestResponseSender, + response_tx: oneshot::Sender>, +) { + debug!("start request_any_peer loop: peers {}", peers.len()); + let mut futures = Vec::with_capacity(peers.len()); + for peer in peers { + let request_data = request_data.clone(); + let request_response_tx = request_response_tx.clone(); + futures.push(async move { + let response = request_one_peer(peer, request_data, request_response_tx).await; + (peer, response) + }) + } + + let responses = join_all(futures) + .await + .into_iter() + .map(|(peer_id, res)| { + let res: AdexResponse = res.into(); + (peer_id, res) + }) + .collect(); + + if response_tx.send(responses).is_err() { + error!("Response oneshot channel was closed"); + }; +} + +pub struct AtomicDexBehaviour { + core: CoreBehaviour, + event_tx: Sender, + runtime: SwarmRuntime, + cmd_rx: Receiver, + netid: u16, +} + +#[derive(NetworkBehaviour)] +pub struct CoreBehaviour { + gossipsub: Gossipsub, + floodsub: Floodsub, + peers_exchange: PeersExchange, + ping: AdexPing, + request_response: RequestResponseBehaviour, +} + +#[derive(Debug)] +pub enum AdexBehaviourEvent { + Gossipsub(libp2p::gossipsub::Event), + Floodsub(FloodsubEvent), + PeersExchange(libp2p::request_response::Event), + Ping(libp2p::ping::Event), + RequestResponse(RequestResponseBehaviourEvent), +} + +impl From for AdexBehaviourEvent { + fn from(event: CoreBehaviourEvent) -> Self { + match event { + CoreBehaviourEvent::Gossipsub(event) => AdexBehaviourEvent::Gossipsub(event), + CoreBehaviourEvent::Floodsub(event) => AdexBehaviourEvent::Floodsub(event), + CoreBehaviourEvent::PeersExchange(event) => AdexBehaviourEvent::PeersExchange(event), + CoreBehaviourEvent::Ping(event) => AdexBehaviourEvent::Ping(event), + CoreBehaviourEvent::RequestResponse(event) => AdexBehaviourEvent::RequestResponse(event), + } + } +} + +impl AtomicDexBehaviour { + fn notify_on_adex_event(&mut self, event: AdexBehaviourEvent) { + if let Err(e) = self.event_tx.try_send(event) { + error!("notify_on_adex_event error {}", e); + } + } + + fn spawn(&self, fut: impl Future + Send + 'static) { self.runtime.spawn(fut) } + + fn process_cmd(&mut self, cmd: AdexBehaviourCmd) -> Result<(), AdexBehaviourError> { + match cmd { + AdexBehaviourCmd::Subscribe { topic } => { + self.core.gossipsub.subscribe(&IdentTopic::new(topic))?; + }, + AdexBehaviourCmd::PublishMsg { topic, msg } => { + self.core.gossipsub.publish(TopicHash::from_raw(topic), msg)?; + }, + AdexBehaviourCmd::PublishMsgFrom { topic, msg, from } => { + self.core + .gossipsub + .publish_from(TopicHash::from_raw(topic), msg, from)?; + }, + AdexBehaviourCmd::RequestAnyRelay { req, response_tx } => { + let relays = self.core.gossipsub.get_relay_mesh(); + // spawn the `request_any_peer` future + let future = request_any_peer(relays, req, self.core.request_response.sender(), response_tx); + self.spawn(future); + }, + AdexBehaviourCmd::RequestPeers { + req, + peers, + response_tx, + } => { + let peers = peers + .into_iter() + .filter_map(|peer| match peer.parse() { + Ok(p) => Some(p), + Err(e) => { + error!("Error on parse peer id {:?}: {:?}", peer, e); + None + }, + }) + .collect(); + let future = request_peers(peers, req, self.core.request_response.sender(), response_tx); + self.spawn(future); + }, + AdexBehaviourCmd::RequestRelays { req, response_tx } => { + let relays = self.core.gossipsub.get_relay_mesh(); + // spawn the `request_peers` future + let future = request_peers(relays, req, self.core.request_response.sender(), response_tx); + self.spawn(future); + }, + AdexBehaviourCmd::SendResponse { res, response_channel } => { + if let Err(response) = self + .core + .request_response + .send_response(response_channel.into(), res.into()) + { + error!("Error sending response: {:?}", response); + } + }, + AdexBehaviourCmd::GetPeersInfo { result_tx } => { + let result = self + .core + .gossipsub + .get_peers_connections() + .into_iter() + .map(|(peer_id, connected_points)| { + let peer_id = peer_id.to_base58(); + let connected_points = connected_points + .into_iter() + .map(|(_conn_id, point)| match point { + ConnectedPoint::Dialer { address, .. } => address.to_string(), + ConnectedPoint::Listener { send_back_addr, .. } => send_back_addr.to_string(), + }) + .collect(); + (peer_id, connected_points) + }) + .collect(); + if result_tx.send(result).is_err() { + debug!("Result rx is dropped"); + } + }, + AdexBehaviourCmd::GetGossipMesh { result_tx } => { + let result = self + .core + .gossipsub + .get_mesh() + .iter() + .map(|(topic, peers)| { + let topic = topic.to_string(); + let peers = peers.iter().map(|peer| peer.to_string()).collect(); + (topic, peers) + }) + .collect(); + if result_tx.send(result).is_err() { + debug!("Result rx is dropped"); + } + }, + AdexBehaviourCmd::GetGossipPeerTopics { result_tx } => { + let result = self + .core + .gossipsub + .get_all_peer_topics() + .iter() + .map(|(peer, topics)| { + let peer = peer.to_string(); + let topics = topics.iter().map(|topic| topic.to_string()).collect(); + (peer, topics) + }) + .collect(); + if result_tx.send(result).is_err() { + error!("Result rx is dropped"); + } + }, + AdexBehaviourCmd::GetGossipTopicPeers { result_tx } => { + let result = self + .core + .gossipsub + .get_all_topic_peers() + .iter() + .map(|(topic, peers)| { + let topic = topic.to_string(); + let peers = peers.iter().map(|peer| peer.to_string()).collect(); + (topic, peers) + }) + .collect(); + if result_tx.send(result).is_err() { + error!("Result rx is dropped"); + } + }, + AdexBehaviourCmd::GetRelayMesh { result_tx } => { + let result = self + .core + .gossipsub + .get_relay_mesh() + .into_iter() + .map(|peer| peer.to_string()) + .collect(); + if result_tx.send(result).is_err() { + error!("Result rx is dropped"); + } + }, + AdexBehaviourCmd::AddReservedPeer { peer, addresses } => { + self.core + .peers_exchange + .add_peer_addresses_to_reserved_peers(&peer, addresses); + }, + AdexBehaviourCmd::PropagateMessage { + message_id, + propagation_source, + } => { + self.core + .gossipsub + .propagate_message(&message_id, &propagation_source)?; + }, + } + + Ok(()) + } + + fn announce_listeners(&mut self, listeners: PeerAddresses) { + let serialized = rmp_serde::to_vec(&listeners).expect("PeerAddresses serialization should never fail"); + self.core.floodsub.publish(FloodsubTopic::new(PEERS_TOPIC), serialized); + } + + pub fn connected_relays_len(&self) -> usize { self.core.gossipsub.connected_relays_len() } + + pub fn relay_mesh_len(&self) -> usize { self.core.gossipsub.relay_mesh_len() } + + pub fn received_messages_in_period(&self) -> (Duration, usize) { + self.core.gossipsub.get_received_messages_in_period() + } + + pub fn connected_peers_len(&self) -> usize { self.core.gossipsub.get_num_peers() } +} + +pub enum NodeType { + Light { + network_ports: NetworkPorts, + }, + LightInMemory, + Relay { + ip: IpAddr, + network_ports: NetworkPorts, + wss_certs: Option, + }, + RelayInMemory { + port: u64, + }, +} + +pub struct WssCerts { + pub server_priv_key: rustls::PrivateKey, + pub certs: Vec, +} + +impl NodeType { + pub fn to_network_info(&self) -> NetworkInfo { + match self { + NodeType::Light { network_ports } | NodeType::Relay { network_ports, .. } => NetworkInfo::Distributed { + network_ports: *network_ports, + }, + NodeType::LightInMemory | NodeType::RelayInMemory { .. } => NetworkInfo::InMemory, + } + } + + pub fn is_relay(&self) -> bool { matches!(self, NodeType::Relay { .. } | NodeType::RelayInMemory { .. }) } + + pub fn wss_certs(&self) -> Option<&WssCerts> { + match self { + NodeType::Relay { wss_certs, .. } => wss_certs.as_ref(), + _ => None, + } + } +} + +#[derive(Debug, Display)] +pub enum AdexBehaviourError { + #[display(fmt = "{}", _0)] + ParsingRelayAddress(RelayAddressError), + #[display(fmt = "{}", _0)] + SubscriptionError(SubscriptionError), + #[display(fmt = "{}", _0)] + PublishError(PublishError), + #[display(fmt = "{}", _0)] + InitializationError(String), +} + +impl From for AdexBehaviourError { + fn from(e: RelayAddressError) -> Self { AdexBehaviourError::ParsingRelayAddress(e) } +} + +impl From for AdexBehaviourError { + fn from(e: SubscriptionError) -> Self { AdexBehaviourError::SubscriptionError(e) } +} + +impl From for AdexBehaviourError { + fn from(e: PublishError) -> Self { AdexBehaviourError::PublishError(e) } +} + +fn generate_ed25519_keypair(rng: &mut R, force_key: Option<[u8; 32]>) -> identity::Keypair { + let mut raw_key = match force_key { + Some(key) => key, + None => { + let mut key = [0; 32]; + rng.fill_bytes(&mut key); + key + }, + }; + let secret = identity::ed25519::SecretKey::try_from_bytes(&mut raw_key).expect("Secret length is 32 bytes"); + let keypair = identity::ed25519::Keypair::from(secret); + keypair.into() +} + +/// Custom types mapping the complex associated types of AtomicDexBehaviour to the ExpandedSwarm +type AtomicDexSwarm = Swarm; + +/// Creates and spawns new AdexBehaviour Swarm returning: +/// 1. tx to send control commands +/// 2. rx emitting gossip events to processing side +/// 3. our peer_id +/// 4. abort handle to stop the P2P processing fut +/// +/// Prefer using [`spawn_gossipsub`] to make sure the Swarm is initialized and spawned on the same runtime. +/// Otherwise, you can face the following error: +/// `panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime'`. +#[allow(clippy::too_many_arguments)] +fn start_gossipsub( + netid: u16, + force_key: Option<[u8; 32]>, + runtime: SwarmRuntime, + to_dial: Vec, + node_type: NodeType, + on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, +) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { + let i_am_relay = node_type.is_relay(); + let mut rng = rand::thread_rng(); + let local_key = generate_ed25519_keypair(&mut rng, force_key); + let local_peer_id = PeerId::from(local_key.public()); + info!("Local peer id: {:?}", local_peer_id); + + let noise_config = noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."); + + let network_info = node_type.to_network_info(); + let transport = match network_info { + NetworkInfo::InMemory => build_memory_transport(noise_config), + NetworkInfo::Distributed { .. } => build_dns_ws_transport(noise_config, node_type.wss_certs()), + }; + + let (cmd_tx, cmd_rx) = channel(CHANNEL_BUF_SIZE); + let (event_tx, event_rx) = channel(CHANNEL_BUF_SIZE); + + let bootstrap = to_dial + .into_iter() + .map(|addr| addr.try_to_multiaddr(network_info)) + .collect::, _>>()?; + + let (mesh_n_low, mesh_n, mesh_n_high) = if i_am_relay { (4, 8, 12) } else { (2, 4, 6) }; + + // Create a Swarm to manage peers and events + let mut swarm = { + // to set default parameters for gossipsub use: + // let gossipsub_config = gossipsub::GossipsubConfig::default(); + + // To content-address message, we can take the hash of message and use it as an ID. + let message_id_fn = |message: &GossipsubMessage| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + message.sequence_number.hash(&mut s); + MessageId(s.finish().to_be_bytes().to_vec()) + }; + + // set custom gossipsub + let gossipsub_config = GossipsubConfigBuilder::default() + .message_id_fn(message_id_fn) + .mesh_n_low(mesh_n_low) + .i_am_relay(i_am_relay) + .mesh_n(mesh_n) + .mesh_n_high(mesh_n_high) + .validate_messages() + .validation_mode(ValidationMode::Permissive) + .max_transmit_size(MAX_BUFFER_SIZE) + .build() + .map_err(|e| AdexBehaviourError::InitializationError(e.to_owned()))?; + + // build a gossipsub network behaviour + let mut gossipsub = Gossipsub::new(MessageAuthenticity::Author(local_peer_id), gossipsub_config) + .map_err(|e| AdexBehaviourError::InitializationError(e.to_owned()))?; + + let floodsub = Floodsub::new(local_peer_id, netid != NETID_8762); + + let mut peers_exchange = PeersExchange::new(network_info); + if !network_info.in_memory() { + // Please note WASM nodes don't support `PeersExchange` currently, + // so `get_all_network_seednodes` returns an empty list. + for (peer_id, addr) in get_all_network_seednodes(netid) { + let multiaddr = addr.try_to_multiaddr(network_info)?; + peers_exchange.add_peer_addresses_to_known_peers(&peer_id, iter::once(multiaddr).collect()); + if peer_id != local_peer_id { + gossipsub.add_explicit_relay(peer_id); + } + } + } + + // build a request-response network behaviour + let request_response = build_request_response_behaviour(); + + // use default ping config with 15s interval, 20s timeout and 1 max failure + let ping = AdexPing::new(); + + let core_behaviour = CoreBehaviour { + gossipsub, + floodsub, + peers_exchange, + request_response, + ping, + }; + + let adex_behavior = AtomicDexBehaviour { + core: core_behaviour, + event_tx, + runtime: runtime.clone(), + cmd_rx, + netid, + }; + + libp2p::swarm::SwarmBuilder::with_executor(transport, adex_behavior, local_peer_id, runtime.clone()).build() + }; + + swarm + .behaviour_mut() + .core + .floodsub + .subscribe(FloodsubTopic::new(PEERS_TOPIC.to_owned())); + + match node_type { + NodeType::Relay { + ip, + network_ports, + wss_certs, + } => { + let dns_addr: Multiaddr = format!("/ip4/{}/tcp/{}", ip, network_ports.tcp).parse().unwrap(); + libp2p::Swarm::listen_on(&mut swarm, dns_addr).unwrap(); + if wss_certs.is_some() { + let wss_addr: Multiaddr = format!("/ip4/{}/tcp/{}/wss", ip, network_ports.wss).parse().unwrap(); + libp2p::Swarm::listen_on(&mut swarm, wss_addr).unwrap(); + } + }, + NodeType::RelayInMemory { port } => { + let memory_addr: Multiaddr = format!("/memory/{}", port).parse().unwrap(); + libp2p::Swarm::listen_on(&mut swarm, memory_addr).unwrap(); + }, + _ => (), + } + + for relay in bootstrap.choose_multiple(&mut rng, mesh_n) { + match libp2p::Swarm::dial(&mut swarm, relay.clone()) { + Ok(_) => info!("Dialed {}", relay), + Err(e) => error!("Dial {:?} failed: {:?}", relay, e), + } + } + + let mut check_connected_relays_interval = + Ticker::new_with_next(CONNECTED_RELAYS_CHECK_INTERVAL, CONNECTED_RELAYS_CHECK_INTERVAL); + + let mut announce_interval = Ticker::new_with_next(ANNOUNCE_INTERVAL, ANNOUNCE_INITIAL_DELAY); + let mut listening = false; + + let polling_fut = poll_fn(move |cx: &mut Context| { + loop { + match swarm.behaviour_mut().cmd_rx.poll_next_unpin(cx) { + Poll::Ready(Some(cmd)) => swarm.behaviour_mut().process_cmd(cmd).unwrap(), + Poll::Ready(None) => return Poll::Ready(()), + Poll::Pending => break, + } + } + + loop { + match swarm.poll_next_unpin(cx) { + Poll::Ready(Some(event)) => { + debug!("Swarm event {:?}", event); + + if let SwarmEvent::Behaviour(event) = event { + if swarm.behaviour_mut().netid != NETID_8762 { + if let AdexBehaviourEvent::Floodsub(FloodsubEvent::Message(message)) = &event { + for topic in &message.topics { + if topic == &FloodsubTopic::new(PEERS_TOPIC) { + let addresses: PeerAddresses = match rmp_serde::from_slice(&message.data) { + Ok(a) => a, + Err(_) => break, + }; + swarm + .behaviour_mut() + .core + .peers_exchange + .add_peer_addresses_to_known_peers(&message.source, addresses); + } + } + } + } + swarm.behaviour_mut().notify_on_adex_event(event); + } + }, + Poll::Ready(None) => return Poll::Ready(()), + Poll::Pending => break, + } + } + + if swarm.behaviour().core.gossipsub.is_relay() { + while let Poll::Ready(Some(_)) = announce_interval.poll_next_unpin(cx) { + announce_my_addresses(&mut swarm); + } + } + + while let Poll::Ready(Some(_)) = check_connected_relays_interval.poll_next_unpin(cx) { + maintain_connection_to_relays(&mut swarm, &bootstrap); + } + + if !listening && i_am_relay { + for listener in Swarm::listeners(&swarm) { + info!("Listening on {}", listener); + listening = true; + } + } + on_poll(&swarm); + Poll::Pending + }); + + runtime.spawn(polling_fut.then(|_| futures::future::ready(()))); + Ok((cmd_tx, event_rx, local_peer_id)) +} + +fn maintain_connection_to_relays(swarm: &mut AtomicDexSwarm, bootstrap_addresses: &[Multiaddr]) { + let behaviour = swarm.behaviour(); + let connected_relays = behaviour.core.gossipsub.connected_relays(); + let mesh_n_low = behaviour.core.gossipsub.get_config().mesh_n_low(); + let mesh_n = behaviour.core.gossipsub.get_config().mesh_n(); + // allow 2 * mesh_n_high connections to other nodes + let max_n = behaviour.core.gossipsub.get_config().mesh_n_high() * 2; + + let mut rng = rand::thread_rng(); + if connected_relays.len() < mesh_n_low { + let to_connect_num = mesh_n - connected_relays.len(); + let to_connect = swarm + .behaviour_mut() + .core + .peers_exchange + .get_random_peers(to_connect_num, |peer| !connected_relays.contains(peer)); + + // choose some random bootstrap addresses to connect if peers exchange returned not enough peers + if to_connect.len() < to_connect_num { + let connect_bootstrap_num = to_connect_num - to_connect.len(); + for addr in bootstrap_addresses + .iter() + .filter(|addr| !swarm.behaviour().core.gossipsub.is_connected_to_addr(addr)) + .collect::>() + .choose_multiple(&mut rng, connect_bootstrap_num) + { + if let Err(e) = libp2p::Swarm::dial(swarm, (*addr).clone()) { + error!("Bootstrap addr {} dial error {}", addr, e); + } + } + } + for (peer, addresses) in to_connect { + for addr in addresses { + if swarm.behaviour().core.gossipsub.is_connected_to_addr(&addr) { + continue; + } + if let Err(e) = libp2p::Swarm::dial(swarm, addr.clone()) { + error!("Peer {} address {} dial error {}", peer, addr, e); + } + } + } + } + + if connected_relays.len() > max_n { + let to_disconnect_num = connected_relays.len() - max_n; + let relays_mesh = swarm.behaviour().core.gossipsub.get_relay_mesh(); + let not_in_mesh: Vec<_> = connected_relays + .iter() + .filter(|peer| !relays_mesh.contains(peer)) + .collect(); + for peer in not_in_mesh.choose_multiple(&mut rng, to_disconnect_num) { + if !swarm.behaviour().core.peers_exchange.is_reserved_peer(peer) { + info!("Disconnecting peer {}", peer); + if Swarm::disconnect_peer_id(swarm, **peer).is_err() { + error!("Peer {} disconnect error", peer); + } + } + } + } + + for relay in connected_relays { + if !swarm.behaviour().core.peers_exchange.is_known_peer(&relay) { + swarm.behaviour_mut().core.peers_exchange.add_known_peer(relay); + } + } +} + +fn announce_my_addresses(swarm: &mut AtomicDexSwarm) { + let global_listeners: PeerAddresses = Swarm::listeners(swarm) + .filter(|listener| { + for protocol in listener.iter() { + if let Protocol::Ip4(ip) = protocol { + return ip.is_global(); + } + } + false + }) + .take(1) + .cloned() + .collect(); + if !global_listeners.is_empty() { + swarm.behaviour_mut().announce_listeners(global_listeners); + } +} + +#[cfg(target_arch = "wasm32")] +fn build_dns_ws_transport( + noise_keys: noise::Config, + _wss_certs: Option<&WssCerts>, +) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { + let websocket = libp2p::wasm_ext::ffi::websocket_transport(); + let transport = libp2p::wasm_ext::ExtTransport::new(websocket); + upgrade_transport(transport, noise_keys) +} + +#[cfg(not(target_arch = "wasm32"))] +fn build_dns_ws_transport( + noise_keys: noise::Config, + wss_certs: Option<&WssCerts>, +) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { + use libp2p::websocket::tls as libp2p_tls; + + let ws_tcp = libp2p::dns::TokioDnsConfig::custom( + libp2p::tcp::tokio::Transport::new(libp2p::tcp::Config::new().nodelay(true)), + libp2p::dns::ResolverConfig::google(), + Default::default(), + ) + .unwrap(); + + let mut ws_dns_tcp = libp2p::websocket::WsConfig::new(ws_tcp); + + if let Some(certs) = wss_certs { + let server_priv_key = libp2p_tls::PrivateKey::new(certs.server_priv_key.0.clone()); + let certs = certs + .certs + .iter() + .map(|cert| libp2p_tls::Certificate::new(cert.0.clone())); + let wss_config = libp2p_tls::Config::new(server_priv_key, certs).unwrap(); + ws_dns_tcp.set_tls_config(wss_config); + } + + // This is for preventing port reuse of dns/tcp instead of + // websocket ports. + let dns_tcp = libp2p::dns::TokioDnsConfig::custom( + libp2p::tcp::tokio::Transport::new(libp2p::tcp::Config::new().nodelay(true)), + libp2p::dns::ResolverConfig::google(), + Default::default(), + ) + .unwrap(); + + let transport = dns_tcp.or_transport(ws_dns_tcp); + upgrade_transport(transport, noise_keys) +} + +fn build_memory_transport(noise_keys: noise::Config) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { + let transport = libp2p::core::transport::MemoryTransport::default(); + upgrade_transport(transport, noise_keys) +} + +/// Set up an encrypted Transport over the Mplex protocol. +fn upgrade_transport( + transport: T, + noise_config: noise::Config, +) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> +where + T: Transport + Send + Sync + 'static + std::marker::Unpin, + T::Output: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + T::ListenerUpgrade: Send, + T::Dial: Send, + T::Error: Send + Sync + 'static, +{ + transport + .upgrade(libp2p::core::upgrade::Version::V1) + .authenticate(noise_config) + .multiplex(libp2p::yamux::Config::default()) + .timeout(std::time::Duration::from_secs(20)) + .map(|(peer, muxer), _| (peer, libp2p::core::muxing::StreamMuxerBox::new(muxer))) + .boxed() +} + +impl NetworkBehaviour for AtomicDexBehaviour { + type ConnectionHandler = ::ConnectionHandler; + + type ToSwarm = AdexBehaviourEvent; + + fn handle_established_inbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.core + .handle_established_inbound_connection(connection_id, peer, local_addr, remote_addr) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: libp2p::core::Endpoint, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.core + .handle_established_outbound_connection(connection_id, peer, addr, role_override) + } + + fn on_swarm_event(&mut self, event: libp2p::swarm::FromSwarm) { + self.core.on_swarm_event(event) + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + connection_id: libp2p::swarm::ConnectionId, + event: libp2p::swarm::THandlerOutEvent, + ) { + self.core.on_connection_handler_event(peer_id, connection_id, event) + } + + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + params: &mut impl libp2p::swarm::PollParameters, + ) -> std::task::Poll>> { + self.core.poll(cx, params).map(|to_swarm| match to_swarm { + ToSwarm::GenerateEvent(event) => ToSwarm::GenerateEvent(event.into()), + ToSwarm::Dial { opts } => ToSwarm::Dial { opts }, + ToSwarm::ListenOn { opts } => ToSwarm::ListenOn { opts }, + ToSwarm::RemoveListener { id } => ToSwarm::RemoveListener { id }, + ToSwarm::NotifyHandler { + peer_id, + handler, + event, + } => ToSwarm::NotifyHandler { + peer_id, + handler, + event, + }, + ToSwarm::NewExternalAddrCandidate(multiaddr) => ToSwarm::NewExternalAddrCandidate(multiaddr), + ToSwarm::ExternalAddrConfirmed(multiaddr) => ToSwarm::ExternalAddrConfirmed(multiaddr), + ToSwarm::ExternalAddrExpired(multiaddr) => ToSwarm::ExternalAddrExpired(multiaddr), + ToSwarm::CloseConnection { peer_id, connection } => ToSwarm::CloseConnection { peer_id, connection }, + }) + } +} + +/// Creates and spawns new AdexBehaviour Swarm returning: +/// 1. tx to send control commands +/// 2. rx emitting gossip events to processing side +/// 3. our peer_id +/// 4. abort handle to stop the P2P processing fut. +pub async fn spawn_gossipsub( + netid: u16, + force_key: Option<[u8; 32]>, + runtime: SwarmRuntime, + to_dial: Vec, + node_type: NodeType, + on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, +) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { + let (result_tx, result_rx) = oneshot::channel(); + + let runtime_c = runtime.clone(); + let fut = async move { + let result = start_gossipsub(netid, force_key, runtime, to_dial, node_type, on_poll); + result_tx.send(result).unwrap(); + }; + + // `Libp2p` must be spawned on the tokio runtime + runtime_c.spawn(fut); + result_rx.await.expect("Fatal error on starting gossipsub") +} diff --git a/mm2src/mm2_p2p/src/behaviours/mod.rs b/mm2src/mm2_p2p/src/behaviours/mod.rs new file mode 100644 index 0000000000..dd78d7ed65 --- /dev/null +++ b/mm2src/mm2_p2p/src/behaviours/mod.rs @@ -0,0 +1,461 @@ +pub mod atomicdex; + +mod ping; +// mod peer_store; +pub(crate) mod peers_exchange; +pub(crate) mod request_response; + +#[cfg(test)] +mod tests { + use async_std::task::spawn; + use common::executor::abortable_queue::AbortableQueue; + use futures::channel::{mpsc, oneshot}; + use futures::{SinkExt, StreamExt}; + use lazy_static::lazy_static; + use libp2p::{Multiaddr, PeerId}; + use std::collections::{HashMap, HashSet}; + use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; + use std::sync::Arc; + #[cfg(not(windows))] use std::sync::Mutex; + use std::time::Duration; + + use crate::behaviours::peers_exchange::{PeerIdSerde, PeersExchange}; + use crate::{spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourEvent, AdexResponse, AdexResponseChannel, NetworkInfo, + NetworkPorts, NodeType, RelayAddress, RequestResponseBehaviourEvent, SwarmRuntime}; + + static TEST_LISTEN_PORT: AtomicU64 = AtomicU64::new(1); + + lazy_static! { + static ref SYSTEM: AbortableQueue = AbortableQueue::default(); + } + + fn next_port() -> u64 { TEST_LISTEN_PORT.fetch_add(1, Ordering::Relaxed) } + + struct Node { + peer_id: PeerId, + cmd_tx: mpsc::Sender, + } + + impl Node { + async fn spawn(port: u64, seednodes: Vec, on_event: F) -> Node + where + F: Fn(mpsc::Sender, AdexBehaviourEvent) + Send + 'static, + { + let spawner = SwarmRuntime::new(SYSTEM.weak_spawner()); + let node_type = NodeType::RelayInMemory { port }; + let seednodes = seednodes.into_iter().map(RelayAddress::Memory).collect(); + let (cmd_tx, mut event_rx, peer_id) = spawn_gossipsub(333, None, spawner, seednodes, node_type, |_| {}) + .await + .expect("Error spawning AdexBehaviour"); + + // spawn a response future + let cmd_tx_fut = cmd_tx.clone(); + spawn(async move { + loop { + let cmd_tx_fut = cmd_tx_fut.clone(); + match event_rx.next().await { + Some(r) => on_event(cmd_tx_fut, r), + _ => { + println!("Finish response future"); + break; + }, + } + } + }); + + Node { peer_id, cmd_tx } + } + + async fn send_cmd(&mut self, cmd: AdexBehaviourCmd) { self.cmd_tx.send(cmd).await.unwrap(); } + + async fn wait_peers(&mut self, number: usize) { + let mut attempts = 0; + loop { + let (tx, rx) = oneshot::channel(); + self.cmd_tx + .send(AdexBehaviourCmd::GetPeersInfo { result_tx: tx }) + .await + .unwrap(); + match rx.await { + Ok(map) => { + if map.len() >= number { + return; + } + async_std::task::sleep(Duration::from_millis(500)).await; + }, + Err(e) => panic!("{}", e), + } + attempts += 1; + if attempts >= 10 { + panic!("wait_peers {} attempts exceeded", attempts); + } + } + } + } + + #[tokio::test] + async fn test_request_response_ok() { + let _ = env_logger::try_init(); + + let request_received = Arc::new(AtomicBool::new(false)); + let request_received_cpy = request_received.clone(); + + let node1_port = next_port(); + let node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { + let (request, response_channel) = match event { + AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { + request, + response_channel, + .. + }) => (request.req, AdexResponseChannel(response_channel)), + _ => return, + }; + + request_received_cpy.store(true, Ordering::Relaxed); + assert_eq!(request, b"test request"); + + let res = AdexResponse::Ok { + response: b"test response".to_vec(), + }; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + }) + .await; + + let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; + + node2.wait_peers(1).await; + + let (response_tx, response_rx) = oneshot::channel(); + node2 + .send_cmd(AdexBehaviourCmd::RequestAnyRelay { + req: b"test request".to_vec(), + response_tx, + }) + .await; + + let response = response_rx.await.unwrap(); + assert_eq!(response, Some((node1.peer_id, b"test response".to_vec()))); + + assert!(request_received.load(Ordering::Relaxed)); + } + + #[tokio::test] + #[cfg(not(windows))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 + async fn test_request_response_ok_three_peers() { + let _ = env_logger::try_init(); + + #[derive(Default)] + struct RequestHandler { + requests: u8, + } + + impl RequestHandler { + fn handle(&mut self, mut cmd_tx: mpsc::Sender, event: AdexBehaviourEvent) { + let (request, response_channel) = match event { + AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { + request, + response_channel, + .. + }) => (request.req, AdexResponseChannel(response_channel)), + _ => return, + }; + + self.requests += 1; + + assert_eq!(request, b"test request"); + + // the first time we should respond the none + if self.requests == 1 { + let res = AdexResponse::None; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + return; + } + + // the second time we should respond an error + if self.requests == 2 { + let res = AdexResponse::Err { + error: "test error".into(), + }; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + return; + } + + // the third time we should respond an ok + if self.requests == 3 { + let res = AdexResponse::Ok { + response: format!("success {} request", self.requests).as_bytes().to_vec(), + }; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + return; + } + + panic!("Request received more than 3 times"); + } + } + + let request_handler = Arc::new(Mutex::new(RequestHandler::default())); + + let mut receivers = Vec::new(); + for _ in 0..3 { + let handler = request_handler.clone(); + let receiver_port = next_port(); + let receiver = Node::spawn(receiver_port, vec![], move |cmd_tx, event| { + let mut handler = handler.lock().unwrap(); + handler.handle(cmd_tx, event) + }) + .await; + receivers.push((receiver_port, receiver)); + } + + let mut sender = Node::spawn( + next_port(), + receivers.iter().map(|(port, _)| *port).collect(), + |_, _| (), + ) + .await; + + sender.wait_peers(3).await; + + let (response_tx, response_rx) = oneshot::channel(); + sender + .send_cmd(AdexBehaviourCmd::RequestAnyRelay { + req: b"test request".to_vec(), + response_tx, + }) + .await; + + let (_peer_id, res) = response_rx.await.unwrap().unwrap(); + assert_eq!(res, b"success 3 request".to_vec()); + } + + #[tokio::test] + async fn test_request_response_none() { + let _ = env_logger::try_init(); + + let request_received = Arc::new(AtomicBool::new(false)); + let request_received_cpy = request_received.clone(); + + let node1_port = next_port(); + let _node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { + let (request, response_channel) = match event { + AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { + request, + response_channel, + .. + }) => (request.req, AdexResponseChannel(response_channel)), + _ => return, + }; + + request_received_cpy.store(true, Ordering::Relaxed); + assert_eq!(request, b"test request"); + + let res = AdexResponse::None; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + }) + .await; + + let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; + + node2.wait_peers(1).await; + + let (response_tx, response_rx) = oneshot::channel(); + node2 + .send_cmd(AdexBehaviourCmd::RequestAnyRelay { + req: b"test request".to_vec(), + response_tx, + }) + .await; + + assert_eq!(response_rx.await.unwrap(), None); + assert!(request_received.load(Ordering::Relaxed)); + } + + #[tokio::test] + #[cfg(target_os = "linux")] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 + async fn test_request_peers_ok_three_peers() { + use crate::RequestResponseBehaviourEvent; + + let _ = env_logger::try_init(); + + let receiver1_port = next_port(); + let receiver1 = Node::spawn(receiver1_port, vec![], move |mut cmd_tx, event| { + let (request, response_channel) = match event { + AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { + request, + response_channel, + .. + }) => (request.req, AdexResponseChannel(response_channel)), + _ => return, + }; + + assert_eq!(request, b"test request"); + + let res = AdexResponse::None; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + }) + .await; + + let receiver2_port = next_port(); + let receiver2 = Node::spawn(receiver2_port, vec![], move |mut cmd_tx, event| { + let (request, response_channel) = match event { + AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { + request, + response_channel, + .. + }) => (request.req, AdexResponseChannel(response_channel)), + _ => return, + }; + + assert_eq!(request, b"test request"); + + let res = AdexResponse::Err { + error: "test error".into(), + }; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + }) + .await; + + let receiver3_port = next_port(); + let receiver3 = Node::spawn(receiver3_port, vec![], move |mut cmd_tx, event| { + let (request, response_channel) = match event { + AdexBehaviourEvent::RequestResponse(RequestResponseBehaviourEvent::InboundRequest { + request, + response_channel, + .. + }) => (request.req, AdexResponseChannel(response_channel)), + _ => return, + }; + + assert_eq!(request, b"test request"); + + let res = AdexResponse::Ok { + response: b"test response".to_vec(), + }; + cmd_tx + .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) + .unwrap(); + }) + .await; + let mut sender = Node::spawn( + next_port(), + vec![receiver1_port, receiver2_port, receiver3_port], + |_, _| (), + ) + .await; + + sender.wait_peers(3).await; + + let (response_tx, response_rx) = oneshot::channel(); + sender + .send_cmd(AdexBehaviourCmd::RequestRelays { + req: b"test request".to_vec(), + response_tx, + }) + .await; + + let mut expected = vec![ + (receiver1.peer_id, AdexResponse::None), + (receiver2.peer_id, AdexResponse::Err { + error: "test error".into(), + }), + (receiver3.peer_id, AdexResponse::Ok { + response: b"test response".to_vec(), + }), + ]; + expected.sort_by(|x, y| x.0.cmp(&y.0)); + + let mut responses = response_rx.await.unwrap(); + responses.sort_by(|x, y| x.0.cmp(&y.0)); + assert_eq!(responses, expected); + } + + #[test] + fn test_peer_id_serde() { + let peer_id = PeerIdSerde(PeerId::random()); + let serialized = rmp_serde::to_vec(&peer_id).unwrap(); + let deserialized: PeerIdSerde = rmp_serde::from_slice(&serialized).unwrap(); + assert_eq!(peer_id.0, deserialized.0); + } + + #[test] + fn test_validate_get_known_peers_response() { + let network_info = NetworkInfo::Distributed { + network_ports: NetworkPorts { tcp: 3000, wss: 3010 }, + }; + let behaviour = PeersExchange::new(network_info); + let response = HashMap::default(); + assert!(!behaviour.validate_get_known_peers_response(&response)); + + let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::new())]); + assert!(!behaviour.validate_get_known_peers_response(&response)); + + let address: Multiaddr = "/ip4/127.0.0.1/tcp/3000".parse().unwrap(); + let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); + assert!(!behaviour.validate_get_known_peers_response(&response)); + + let address: Multiaddr = "/ip4/216.58.210.142/tcp/3000".parse().unwrap(); + let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); + assert!(behaviour.validate_get_known_peers_response(&response)); + + let address: Multiaddr = "/ip4/216.58.210.142/tcp/3001".parse().unwrap(); + let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); + assert!(!behaviour.validate_get_known_peers_response(&response)); + + let address: Multiaddr = "/ip4/216.58.210.142".parse().unwrap(); + let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); + assert!(!behaviour.validate_get_known_peers_response(&response)); + + let address: Multiaddr = + "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" + .parse() + .unwrap(); + let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); + assert!(behaviour.validate_get_known_peers_response(&response)); + + let address1: Multiaddr = + "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" + .parse() + .unwrap(); + + let address2: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); + let response = HashMap::from_iter(vec![( + PeerIdSerde(PeerId::random()), + HashSet::from_iter(vec![address1, address2]), + )]); + assert!(behaviour.validate_get_known_peers_response(&response)); + } + + #[test] + fn test_get_random_known_peers() { + let mut behaviour = PeersExchange::new(NetworkInfo::InMemory); + let peer_id = PeerId::random(); + behaviour.add_known_peer(peer_id); + + let result = behaviour.get_random_known_peers(1); + assert!(result.is_empty()); + + let address: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); + behaviour.request_response.add_address(&peer_id, address.clone()); + + let result = behaviour.get_random_known_peers(1); + assert_eq!(result.len(), 1); + + let addresses = result.get(&peer_id.into()).unwrap(); + assert_eq!(addresses.len(), 1); + assert!(addresses.contains(&address)); + } +} diff --git a/mm2src/mm2_p2p/src/behaviours/peer_store.rs b/mm2src/mm2_p2p/src/behaviours/peer_store.rs new file mode 100644 index 0000000000..cd7b437f58 --- /dev/null +++ b/mm2src/mm2_p2p/src/behaviours/peer_store.rs @@ -0,0 +1,183 @@ +use libp2p::{core::ConnectedPoint, + swarm::{derive_prelude::ConnectionEstablished, dummy, ConnectionClosed, ConnectionId, FromSwarm, + NetworkBehaviour}, + Multiaddr, PeerId}; +use smallvec::SmallVec; +use std::{collections::HashMap, task::Poll}; +use void::Void; + +/// The ID of an inbound or outbound request. +/// +/// Note: [`RequestId`]'s uniqueness is only guaranteed between two +/// inbound and likewise between two outbound requests. There is no +/// uniqueness guarantee in a set of both inbound and outbound +/// [`RequestId`]s nor in a set of inbound or outbound requests +/// originating from different [`Behaviour`]'s. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct RequestId(u64); + +/// Internal information tracked for an established connection. +pub struct Connection { + pub id: ConnectionId, + pub address: Option, +} + +/// A request/response protocol for some message codec. +#[derive(Default)] +pub struct Behaviour { + /// The currently connected peers, their pending outbound and inbound responses and their known, + /// reachable addresses, if any. + pub connected: HashMap>, + /// Externally managed addresses via `add_address` and `remove_address`. + pub addresses: HashMap>, +} + +impl Behaviour { + /// Adds a known address for a peer that can be used for + /// dialing attempts by the `Swarm`, i.e. is returned + /// by [`NetworkBehaviour::handle_pending_outbound_connection`]. + /// + /// Addresses added in this way are only removed by `remove_address`. + pub fn add_address(&mut self, peer: &PeerId, address: Multiaddr) { + self.addresses.entry(*peer).or_default().push(address); + } + + /// Removes an address of a peer previously added via `add_address`. + pub fn remove_address(&mut self, peer: &PeerId, address: &Multiaddr) { + let mut last = false; + if let Some(addresses) = self.addresses.get_mut(peer) { + addresses.retain(|a| a != address); + last = addresses.is_empty(); + } + if last { + self.addresses.remove(peer); + } + } + + /// Checks whether a peer is currently connected. + pub fn is_connected(&self, peer: &PeerId) -> bool { + if let Some(connections) = self.connected.get(peer) { + !connections.is_empty() + } else { + false + } + } + + /// Addresses that this behaviour is aware of for this specific peer, and that may allow + /// reaching the peer. + /// + /// The addresses will be tried in the order returned by this function, which means that they + /// should be ordered by decreasing likelihood of reachability. In other words, the first + /// address should be the most likely to be reachable. + pub fn addresses_of_peer(&self, peer: &PeerId) -> Vec { + let mut addresses = Vec::new(); + if let Some(connections) = self.connected.get(peer) { + addresses.extend(connections.iter().filter_map(|c| c.address.clone())) + } + if let Some(more) = self.addresses.get(peer) { + addresses.extend(more.into_iter().cloned()); + } + addresses + } +} + +impl NetworkBehaviour for Behaviour { + type ConnectionHandler = dummy::ConnectionHandler; + + type ToSwarm = Void; + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + _peer: PeerId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result, libp2p::swarm::ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn handle_established_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _peer: PeerId, + _addr: &Multiaddr, + _role_override: libp2p::core::Endpoint, + ) -> Result, libp2p::swarm::ConnectionDenied> { + Ok(dummy::ConnectionHandler) + } + + fn on_swarm_event(&mut self, event: libp2p::swarm::FromSwarm) { + match event { + FromSwarm::ConnectionClosed(cc) => { + let ConnectionClosed { + peer_id, + connection_id, + remaining_established, + .. + } = cc; + + let connections = self + .connected + .get_mut(&peer_id) + .expect("Expected some established connection to peer before closing."); + + connections + .iter() + .position(|c| c.id == connection_id) + .map(|p: usize| connections.remove(p)) + .expect("Expected connection to be established before closing."); + + debug_assert_eq!(connections.is_empty(), remaining_established == 0); + if connections.is_empty() { + self.connected.remove(&peer_id); + } + }, + FromSwarm::ConnectionEstablished(ce) => { + let ConnectionEstablished { + peer_id, + connection_id, + endpoint, + .. + } = ce; + + let address = match endpoint { + ConnectedPoint::Dialer { address, .. } => Some(address.clone()), + ConnectedPoint::Listener { .. } => None, + }; + + self.connected.entry(peer_id).or_default().push(Connection { + id: connection_id, + address, + }); + }, + FromSwarm::AddressChange(_) => {}, + FromSwarm::DialFailure(_) => {}, + FromSwarm::ListenFailure(_) => {}, + FromSwarm::NewListener(_) => {}, + FromSwarm::NewListenAddr(_) => {}, + FromSwarm::ExpiredListenAddr(_) => {}, + FromSwarm::ListenerError(_) => {}, + FromSwarm::ListenerClosed(_) => {}, + FromSwarm::NewExternalAddrCandidate(_) => {}, + FromSwarm::ExternalAddrExpired(_) => {}, + FromSwarm::ExternalAddrConfirmed(_) => {}, + } + } + + fn on_connection_handler_event( + &mut self, + _peer_id: PeerId, + _connection_id: ConnectionId, + event: libp2p::swarm::THandlerOutEvent, + ) { + void::unreachable(event) + } + + fn poll( + &mut self, + _cx: &mut std::task::Context<'_>, + _params: &mut impl libp2p::swarm::PollParameters, + ) -> std::task::Poll>> { + Poll::Pending + } +} diff --git a/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs b/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs new file mode 100644 index 0000000000..38f3b35dc8 --- /dev/null +++ b/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs @@ -0,0 +1,394 @@ +use futures::StreamExt; +use futures_ticker::Ticker; +use libp2p::{multiaddr::Protocol, + request_response::{Behaviour as RequestResponse, Config as RequestResponseConfig, Event, HandlerEvent, + InboundFailure, OutboundFailure, ProtocolSupport, ResponseChannel}, + swarm::{NetworkBehaviour, ToSwarm}, + Multiaddr, PeerId}; +use log::{info, warn}; +use rand::seq::SliceRandom; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{collections::{HashMap, HashSet, VecDeque}, + iter, + task::Poll, + time::Duration}; + +use super::request_response::Codec; +use crate::NetworkInfo; +// use crate::peer_store::Behaviour as PeerStoreBehaviour; + +pub type PeerAddresses = HashSet; +type PeersExchangeCodec = Codec; + +const DEFAULT_PEERS_NUM: usize = 20; +const REQUEST_PEERS_INITIAL_DELAY: u64 = 20; +const REQUEST_PEERS_INTERVAL: u64 = 300; +const MAX_PEERS: usize = 100; + +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub struct PeerIdSerde(pub(crate) PeerId); + +impl From for PeerIdSerde { + fn from(peer_id: PeerId) -> PeerIdSerde { PeerIdSerde(peer_id) } +} + +impl Serialize for PeerIdSerde { + fn serialize(&self, serializer: S) -> Result { + self.0.clone().to_bytes().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PeerIdSerde { + fn deserialize>(deserializer: D) -> Result { + let bytes: Vec = Deserialize::deserialize(deserializer)?; + let peer_id = PeerId::from_bytes(&bytes).map_err(|_| serde::de::Error::custom("PeerId::from_bytes error"))?; + Ok(PeerIdSerde(peer_id)) + } +} + +#[derive(Debug, Clone)] +pub enum PeersExchangeProtocol { + Version1, +} + +impl AsRef for PeersExchangeProtocol { + fn as_ref(&self) -> &str { + match self { + PeersExchangeProtocol::Version1 => "/peers-exchange/1", + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PeersExchangeRequest { + GetKnownPeers { num: usize }, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PeersExchangeResponse { + KnownPeers { peers: HashMap }, +} + +pub struct PeersExchange { + pub(crate) request_response: RequestResponse, + known_peers: Vec, + reserved_peers: Vec, + events: VecDeque::ToSwarm, libp2p::swarm::THandlerInEvent>>, + maintain_peers_interval: Ticker, + network_info: NetworkInfo, + // peer_store: PeerStoreBehaviour, +} + +impl NetworkBehaviour for PeersExchange { + type ConnectionHandler = as NetworkBehaviour>::ConnectionHandler; + type ToSwarm = Event; + + fn handle_established_inbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: PeerId, + local_addr: &libp2p::Multiaddr, + remote_addr: &libp2p::Multiaddr, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.request_response + .handle_established_inbound_connection(connection_id, peer, local_addr, remote_addr) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: PeerId, + addr: &libp2p::Multiaddr, + role_override: libp2p::core::Endpoint, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.request_response + .handle_established_outbound_connection(connection_id, peer, addr, role_override) + } + + fn on_swarm_event(&mut self, event: libp2p::swarm::FromSwarm) { + self.request_response.on_swarm_event(event) + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + _connection_id: libp2p::swarm::ConnectionId, + event: libp2p::swarm::THandlerOutEvent, + ) { + match event { + HandlerEvent::Request { request, sender, .. } => { + match request { + PeersExchangeRequest::GetKnownPeers { num } => { + // Should not send a response in such case + if num > DEFAULT_PEERS_NUM { + return; + } + let response = PeersExchangeResponse::KnownPeers { + peers: self.get_random_known_peers(num), + }; + + let channel = ResponseChannel { sender }; + + if let Err(_response) = self.request_response.send_response(channel, response) { + warn!("Response channel has been closed already"); + } + }, + } + }, + HandlerEvent::Response { response, .. } => { + match response { + PeersExchangeResponse::KnownPeers { peers } => { + info!("Got peers {:?}", peers); + + if !self.validate_get_known_peers_response(&peers) { + // if peer provides invalid response forget it and try to request from other peer + self.forget_peer(&peer_id); + self.request_known_peers_from_random_peer(); + return; + } + + peers.into_iter().for_each(|(peer, addresses)| { + self.add_peer_addresses_to_known_peers(&peer.0, addresses); + }); + }, + } + }, + HandlerEvent::ResponseSent(_) => {}, + HandlerEvent::ResponseOmission(request_id) => { + log::error!( + "Inbound failure {:?} while requesting {:?} from peer {}", + InboundFailure::ResponseOmission, + request_id, + peer_id + ); + }, + HandlerEvent::OutboundTimeout(request_id) => { + log::error!( + "Outbound failure {:?} while requesting {:?} to peer {}", + OutboundFailure::Timeout, + request_id, + peer_id + ); + self.forget_peer(&peer_id); + self.request_known_peers_from_random_peer(); + }, + HandlerEvent::OutboundUnsupportedProtocols(request_id) => { + log::error!( + "Outbound failure {:?} while requesting {:?} to peer {}", + OutboundFailure::UnsupportedProtocols, + request_id, + peer_id + ); + self.forget_peer(&peer_id); + self.request_known_peers_from_random_peer(); + }, + } + } + + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + params: &mut impl libp2p::swarm::PollParameters, + ) -> std::task::Poll>> { + while let Poll::Ready(Some(_)) = self.maintain_peers_interval.poll_next_unpin(cx) { + self.maintain_known_peers(); + } + + if let Some(event) = self.events.pop_front() { + return Poll::Ready(event); + } + + self.request_response.poll(cx, params) + } +} + +#[allow(clippy::new_without_default)] +impl PeersExchange { + pub fn new(network_info: NetworkInfo) -> Self { + let protocol = iter::once((PeersExchangeProtocol::Version1, ProtocolSupport::Full)); + let config = RequestResponseConfig::default(); + let request_response = RequestResponse::new(protocol, config); + PeersExchange { + request_response, + known_peers: Vec::new(), + reserved_peers: Vec::new(), + events: VecDeque::new(), + maintain_peers_interval: Ticker::new_with_next( + Duration::from_secs(REQUEST_PEERS_INTERVAL), + Duration::from_secs(REQUEST_PEERS_INITIAL_DELAY), + ), + network_info, + // peer_store: Default::default(), + } + } + + pub(crate) fn get_random_known_peers(&mut self, num: usize) -> HashMap { + let mut result = HashMap::with_capacity(num); + let mut rng = rand::thread_rng(); + let peer_ids = self + .known_peers + .clone() + .into_iter() + .filter(|peer| !self.request_response.addresses_of_peer(peer).is_empty()) + .collect::>(); + + let peer_ids = peer_ids.choose_multiple(&mut rng, num); + for peer_id in peer_ids { + let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); + result.insert((*peer_id).into(), addresses); + } + result + } + + fn forget_peer(&mut self, peer: &PeerId) { + self.known_peers.retain(|known_peer| known_peer != peer); + self.forget_peer_addresses(peer); + } + + fn forget_peer_addresses(&mut self, peer: &PeerId) { + for address in self.request_response.addresses_of_peer(peer) { + if !self.is_reserved_peer(peer) { + self.request_response.remove_address(peer, &address); + } + } + } + + pub fn add_peer_addresses_to_known_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { + for address in addresses.iter() { + if !self.validate_global_multiaddr(address) { + warn!("Attempt adding a not valid address of the peer '{}': {}", peer, address); + return; + } + } + if !self.known_peers.contains(peer) && !addresses.is_empty() { + self.known_peers.push(*peer); + } + let already_known = self.request_response.addresses_of_peer(peer); + for address in addresses { + if !already_known.contains(&address) { + self.request_response.add_address(peer, address); + } + } + } + + pub fn add_peer_addresses_to_reserved_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { + for address in addresses.iter() { + if !self.validate_global_multiaddr(address) { + return; + } + } + + if !self.reserved_peers.contains(peer) && !addresses.is_empty() { + self.reserved_peers.push(*peer); + } + + let already_reserved = self.request_response.addresses_of_peer(peer); + for address in addresses { + if !already_reserved.contains(&address) { + self.request_response.add_address(peer, address); + } + } + } + + fn maintain_known_peers(&mut self) { + if self.known_peers.len() > MAX_PEERS { + let mut rng = rand::thread_rng(); + let to_remove_num = self.known_peers.len() - MAX_PEERS; + self.known_peers.shuffle(&mut rng); + let removed_peers: Vec<_> = self.known_peers.drain(..to_remove_num).collect(); + for peer in removed_peers { + self.forget_peer_addresses(&peer); + } + } + self.request_known_peers_from_random_peer(); + } + + fn request_known_peers_from_random_peer(&mut self) { + let mut rng = rand::thread_rng(); + if let Some(from_peer) = self.known_peers.choose(&mut rng) { + info!("Try to request {} peers from peer {}", DEFAULT_PEERS_NUM, from_peer); + let request = PeersExchangeRequest::GetKnownPeers { num: DEFAULT_PEERS_NUM }; + self.request_response.send_request(from_peer, request); + } + } + + pub fn get_random_peers( + &mut self, + num: usize, + mut filter: impl FnMut(&PeerId) -> bool, + ) -> HashMap { + let mut result = HashMap::with_capacity(num); + let mut rng = rand::thread_rng(); + let peer_ids = self.known_peers.iter().filter(|peer| filter(peer)).collect::>(); + + for peer_id in peer_ids.choose_multiple(&mut rng, num) { + let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); + result.insert(**peer_id, addresses); + } + + result + } + + pub fn is_known_peer(&self, peer: &PeerId) -> bool { self.known_peers.contains(peer) } + + pub fn is_reserved_peer(&self, peer: &PeerId) -> bool { self.reserved_peers.contains(peer) } + + pub fn add_known_peer(&mut self, peer: PeerId) { + if !self.is_known_peer(&peer) { + self.known_peers.push(peer) + } + } + + fn validate_global_multiaddr(&self, address: &Multiaddr) -> bool { + let network_ports = match self.network_info { + NetworkInfo::Distributed { network_ports } => network_ports, + NetworkInfo::InMemory => panic!("PeersExchange must not be used with in-memory network"), + }; + + let mut components = address.iter(); + match components.next() { + Some(Protocol::Ip4(addr)) => { + if !addr.is_global() { + return false; + } + }, + _ => return false, + } + + match components.next() { + Some(Protocol::Tcp(port)) => { + // currently, `NetworkPorts::ws` is not supported by `PeersExchange` + if port != network_ports.tcp { + return false; + } + }, + _ => return false, + } + + true + } + + pub(crate) fn validate_get_known_peers_response(&self, response: &HashMap) -> bool { + if response.is_empty() { + return false; + } + + if response.len() > DEFAULT_PEERS_NUM { + return false; + } + + for addresses in response.values() { + if addresses.is_empty() { + return false; + } + + for address in addresses { + if !self.validate_global_multiaddr(address) { + warn!("Received a not valid address: {}", address); + return false; + } + } + } + true + } +} diff --git a/mm2src/mm2_p2p/src/behaviours/ping.rs b/mm2src/mm2_p2p/src/behaviours/ping.rs new file mode 100644 index 0000000000..463d740cdb --- /dev/null +++ b/mm2src/mm2_p2p/src/behaviours/ping.rs @@ -0,0 +1,80 @@ +use libp2p::ping::{Behaviour, Config}; +use libp2p::swarm::{CloseConnection, NetworkBehaviour, PollParameters, ToSwarm}; +use log::error; +use std::{collections::VecDeque, task::Poll}; +use void::Void; + +pub struct AdexPing { + ping: Behaviour, + events: VecDeque::ToSwarm, Void>>, +} + +impl NetworkBehaviour for AdexPing { + type ConnectionHandler = ::ConnectionHandler; + type ToSwarm = ::ToSwarm; + + fn handle_established_inbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: libp2p::PeerId, + local_addr: &libp2p::Multiaddr, + remote_addr: &libp2p::Multiaddr, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.ping + .handle_established_inbound_connection(connection_id, peer, local_addr, remote_addr) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: libp2p::PeerId, + addr: &libp2p::Multiaddr, + role_override: libp2p::core::Endpoint, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.ping + .handle_established_outbound_connection(connection_id, peer, addr, role_override) + } + + fn on_swarm_event(&mut self, event: libp2p::swarm::FromSwarm) { + self.ping.on_swarm_event(event) + } + + fn on_connection_handler_event( + &mut self, + peer_id: libp2p::PeerId, + connection_id: libp2p::swarm::ConnectionId, + event: libp2p::swarm::THandlerOutEvent, + ) { + if let Err(e) = &event { + error!("Ping error {}. Disconnecting peer {}", e, peer_id); + self.events.push_back(ToSwarm::CloseConnection { + peer_id, + connection: CloseConnection::All, + }); + } + + self.ping.on_connection_handler_event(peer_id, connection_id, event) + } + + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + params: &mut impl PollParameters, + ) -> std::task::Poll>> { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(event); + } + + self.ping.poll(cx, params) + } +} + +#[allow(clippy::new_without_default)] +impl AdexPing { + pub fn new() -> Self { + AdexPing { + ping: Behaviour::new(Config::new()), + events: VecDeque::new(), + } + } +} diff --git a/mm2src/mm2_p2p/src/behaviours/request_response.rs b/mm2src/mm2_p2p/src/behaviours/request_response.rs new file mode 100644 index 0000000000..b3b949cedb --- /dev/null +++ b/mm2src/mm2_p2p/src/behaviours/request_response.rs @@ -0,0 +1,414 @@ +use async_trait::async_trait; +use futures::channel::{mpsc, oneshot}; +use futures::io::{AsyncRead, AsyncWrite}; +use futures::task::Poll; +use futures::StreamExt; +use futures_ticker::Ticker; +use instant::{Duration, Instant}; +use libp2p::core::upgrade::{read_length_prefixed, write_length_prefixed}; +use libp2p::request_response::{InboundFailure, Message, OutboundFailure, ProtocolSupport}; +use libp2p::swarm::ToSwarm; +use libp2p::{request_response::{Behaviour as RequestResponse, Config as RequestResponseConfig, + Event as RequestResponseEvent, RequestId, ResponseChannel}, + swarm::NetworkBehaviour, + PeerId}; +use log::{error, warn}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, VecDeque}; +use std::io; + +use super::atomicdex::MAX_BUFFER_SIZE; +use crate::{decode_message, encode_message}; + +macro_rules! try_io { + ($e: expr) => { + match $e { + Ok(ok) => ok, + Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), + } + }; +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct PeerRequest { + pub req: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum PeerResponse { + Ok { res: Vec }, + None, + Err { err: String }, +} + +pub type RequestResponseReceiver = mpsc::UnboundedReceiver<(PeerId, PeerRequest, oneshot::Sender)>; +pub type RequestResponseSender = mpsc::UnboundedSender<(PeerId, PeerRequest, oneshot::Sender)>; + +#[derive(Debug)] +pub enum RequestResponseBehaviourEvent { + InboundRequest { + peer_id: PeerId, + request: PeerRequest, + response_channel: ResponseChannel, + }, + NoAction, +} + +struct PendingRequest { + tx: oneshot::Sender, + initiated_at: Instant, +} + +#[derive(Debug, Clone)] +pub enum Protocol { + Version1, +} + +impl AsRef for Protocol { + fn as_ref(&self) -> &str { + match self { + Protocol::Version1 => "/request-response/1", + } + } +} + +#[derive(Clone)] +pub struct Codec { + phantom: std::marker::PhantomData<(Proto, Req, Res)>, +} + +impl Default for Codec { + fn default() -> Self { + Codec { + phantom: Default::default(), + } + } +} + +#[async_trait] +impl< + Proto: Clone + AsRef + Send + Sync, + Req: DeserializeOwned + Serialize + Send + Sync, + Res: DeserializeOwned + Serialize + Send + Sync, + > libp2p::request_response::Codec for Codec +{ + type Protocol = Proto; + type Request = Req; + type Response = Res; + + async fn read_request(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + read_to_end(io).await + } + + async fn read_response(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + read_to_end(io).await + } + + async fn write_request(&mut self, _protocol: &Self::Protocol, io: &mut T, req: Self::Request) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + write_all(io, &req).await + } + + async fn write_response(&mut self, _protocol: &Self::Protocol, io: &mut T, res: Self::Response) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + write_all(io, &res).await + } +} + +async fn read_to_end(io: &mut T) -> io::Result +where + T: AsyncRead + Unpin + Send, + M: DeserializeOwned, +{ + match read_length_prefixed(io, MAX_BUFFER_SIZE).await { + Ok(data) => Ok(try_io!(decode_message(&data))), + Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), + } +} + +async fn write_all(io: &mut T, msg: &M) -> io::Result<()> +where + T: AsyncWrite + Unpin + Send, + M: Serialize, +{ + let data = try_io!(encode_message(msg)); + if data.len() > MAX_BUFFER_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Try to send data size over maximum", + )); + } + write_length_prefixed(io, data).await +} + +pub struct RequestResponseBehaviour { + /// The inner RequestResponse network behaviour. + inner: RequestResponse>, + rx: RequestResponseReceiver, + tx: RequestResponseSender, + pending_requests: HashMap, + /// Events that need to be yielded to the outside when polling. + events: VecDeque, + /// Timeout for pending requests + timeout: Duration, + /// Interval for request timeout check + timeout_interval: Ticker, +} + +impl RequestResponseBehaviour { + pub fn sender(&self) -> RequestResponseSender { self.tx.clone() } + + pub fn send_response(&mut self, ch: ResponseChannel, rs: PeerResponse) -> Result<(), PeerResponse> { + self.inner.send_response(ch, rs) + } + + pub fn send_request( + &mut self, + peer_id: &PeerId, + request: PeerRequest, + response_tx: oneshot::Sender, + ) -> RequestId { + let request_id = self.inner.send_request(peer_id, request); + let pending_request = PendingRequest { + tx: response_tx, + initiated_at: Instant::now(), + }; + assert!(self.pending_requests.insert(request_id, pending_request).is_none()); + request_id + } + + fn process_request( + &mut self, + peer_id: PeerId, + request: PeerRequest, + response_channel: ResponseChannel, + ) { + self.events.push_back(RequestResponseBehaviourEvent::InboundRequest { + peer_id, + request, + response_channel, + }) + } + + fn process_response(&mut self, request_id: RequestId, response: PeerResponse) { + match self.pending_requests.remove(&request_id) { + Some(pending) => { + if let Err(e) = pending.tx.send(response) { + error!("{:?}. Request {:?} is not processed", e, request_id); + } + }, + _ => error!("Received unknown request {:?}", request_id), + } + } +} + +impl NetworkBehaviour for RequestResponseBehaviour { + type ConnectionHandler = + > as NetworkBehaviour>::ConnectionHandler; + + type ToSwarm = RequestResponseBehaviourEvent; + + fn handle_established_inbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: PeerId, + local_addr: &libp2p::Multiaddr, + remote_addr: &libp2p::Multiaddr, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.inner + .handle_established_inbound_connection(connection_id, peer, local_addr, remote_addr) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: libp2p::swarm::ConnectionId, + peer: PeerId, + addr: &libp2p::Multiaddr, + role_override: libp2p::core::Endpoint, + ) -> Result, libp2p::swarm::ConnectionDenied> { + self.inner + .handle_established_outbound_connection(connection_id, peer, addr, role_override) + } + + fn on_swarm_event(&mut self, event: libp2p::swarm::FromSwarm) { + self.inner.on_swarm_event(event) + } + + fn on_connection_handler_event( + &mut self, + peer_id: PeerId, + _connection_id: libp2p::swarm::ConnectionId, + event: libp2p::swarm::THandlerOutEvent, + ) { + let (peer_id, message) = match event { + libp2p::request_response::HandlerEvent::Request { + request_id, + request, + sender, + } => { + let channel = ResponseChannel { sender }; + let message: Message = Message::Request { + request_id, + request, + channel, + }; + + (peer_id, message) + }, + libp2p::request_response::HandlerEvent::Response { request_id, response } => { + let message = Message::Response { request_id, response }; + (peer_id, message) + }, + libp2p::request_response::HandlerEvent::ResponseSent(_) => return, + libp2p::request_response::HandlerEvent::ResponseOmission(_) => { + error!("Error on receive a request: {:?}", InboundFailure::ResponseOmission); + return; + }, + libp2p::request_response::HandlerEvent::OutboundTimeout(request_id) => { + let error = OutboundFailure::Timeout; + error!( + "Error on send request {:?} to peer {:?}: {:?}", + request_id, peer_id, error + ); + let err_response = PeerResponse::Err { + err: format!("{:?}", error), + }; + self.process_response(request_id, err_response); + return; + }, + libp2p::request_response::HandlerEvent::OutboundUnsupportedProtocols(request_id) => { + let error = OutboundFailure::UnsupportedProtocols; + error!( + "Error on send request {:?} to peer {:?}: {:?}", + request_id, peer_id, error + ); + let err_response = PeerResponse::Err { + err: format!("{:?}", error), + }; + self.process_response(request_id, err_response); + return; + }, + }; + + match message { + Message::Request { request, channel, .. } => { + log::debug!("Received a request from {:?} peer", peer_id); + self.process_request(peer_id, request, channel) + }, + Message::Response { request_id, response } => { + log::debug!( + "Received a response to the {:?} request from peer {:?}", + request_id, + peer_id + ); + self.process_response(request_id, response) + }, + } + } + + fn poll( + &mut self, + cx: &mut std::task::Context<'_>, + params: &mut impl libp2p::swarm::PollParameters, + ) -> std::task::Poll>> { + // poll the `rx` + match self.rx.poll_next_unpin(cx) { + // received a request, forward it through the network and put to the `pending_requests` + Poll::Ready(Some((peer_id, request, response_tx))) => { + let _request_id = self.send_request(&peer_id, request, response_tx); + }, + // the channel was closed + Poll::Ready(None) => panic!("request-response channel has been closed"), + Poll::Pending => (), + } + + if let Some(event) = self.events.pop_front() { + // forward a pending event to the top + return Poll::Ready(ToSwarm::GenerateEvent(event)); + } + + while let Poll::Ready(Some(_)) = self.timeout_interval.poll_next_unpin(cx) { + let now = Instant::now(); + let timeout = self.timeout; + self.pending_requests.retain(|request_id, pending_request| { + let retain = now.duration_since(pending_request.initiated_at) < timeout; + if !retain { + warn!("Request {} timed out", request_id); + } + retain + }); + } + + self.inner.poll(cx, params).map(|to_swarm| match to_swarm { + ToSwarm::GenerateEvent(event) => ToSwarm::GenerateEvent(event.into()), + ToSwarm::Dial { opts } => ToSwarm::Dial { opts }, + ToSwarm::ListenOn { opts } => ToSwarm::ListenOn { opts }, + ToSwarm::RemoveListener { id } => ToSwarm::RemoveListener { id }, + ToSwarm::NotifyHandler { + peer_id, + handler, + event, + } => ToSwarm::NotifyHandler { + peer_id, + handler, + event, + }, + ToSwarm::NewExternalAddrCandidate(multiaddr) => ToSwarm::NewExternalAddrCandidate(multiaddr), + ToSwarm::ExternalAddrConfirmed(multiaddr) => ToSwarm::ExternalAddrConfirmed(multiaddr), + ToSwarm::ExternalAddrExpired(multiaddr) => ToSwarm::ExternalAddrExpired(multiaddr), + ToSwarm::CloseConnection { peer_id, connection } => ToSwarm::CloseConnection { peer_id, connection }, + }) + } +} + +impl From> for RequestResponseBehaviourEvent { + fn from(event: libp2p::request_response::Event) -> Self { + match event { + RequestResponseEvent::Message { peer, message } => match message { + Message::Request { request, channel, .. } => Self::InboundRequest { + peer_id: peer, + request, + response_channel: channel, + }, + Message::Response { .. } => RequestResponseBehaviourEvent::NoAction, + }, + RequestResponseEvent::OutboundFailure { .. } => RequestResponseBehaviourEvent::NoAction, + RequestResponseEvent::InboundFailure { .. } => RequestResponseBehaviourEvent::NoAction, + RequestResponseEvent::ResponseSent { .. } => RequestResponseBehaviourEvent::NoAction, + } + } +} + +/// Build a request-response network behaviour. +pub fn build_request_response_behaviour() -> RequestResponseBehaviour { + let config = RequestResponseConfig::default(); + let protocol = core::iter::once((Protocol::Version1, ProtocolSupport::Full)); + let inner = RequestResponse::new(protocol, config); + + let (tx, rx) = mpsc::unbounded(); + let pending_requests = HashMap::new(); + let events = VecDeque::new(); + let timeout = Duration::from_secs(10); + let timeout_interval = Ticker::new(Duration::from_secs(1)); + + RequestResponseBehaviour { + inner, + rx, + tx, + pending_requests, + events, + timeout, + timeout_interval, + } +} diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs new file mode 100644 index 0000000000..fd13446f6e --- /dev/null +++ b/mm2src/mm2_p2p/src/lib.rs @@ -0,0 +1,186 @@ +#![feature(ip)] + +pub mod behaviours; + +mod network; +mod relay_address; +mod swarm_runtime; + +use lazy_static::lazy_static; +use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, + VerifyOnly}; +use serde::{de, Deserialize, Serialize, Serializer}; +use sha2::{Digest, Sha256}; + +pub use crate::swarm_runtime::SwarmRuntime; + +// atomicdex related re-exports +pub use behaviours::atomicdex::{get_gossip_mesh, get_gossip_peer_topics, get_gossip_topic_peers, get_peers_info, + get_relay_mesh, spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourError, + AdexBehaviourEvent, AdexCmdTx, AdexEventRx, AdexResponse, AdexResponseChannel, + GossipsubEvent, GossipsubMessage, MessageId, NodeType, TopicHash, WssCerts}; + +// peers-exchange re-exports +pub use behaviours::peers_exchange::PeerAddresses; + +// request-response related re-exports +pub use behaviours::request_response::RequestResponseBehaviourEvent; + +// libp2p related re-exports +pub use libp2p::identity::DecodingError; +pub use libp2p::identity::{secp256k1::PublicKey as Libp2pSecpPublic, PublicKey as Libp2pPublic}; +pub use libp2p::{Multiaddr, PeerId}; + +// relay-address related re-exports +pub use relay_address::RelayAddress; +pub use relay_address::RelayAddressError; + +lazy_static! { + static ref SECP_VERIFY: Secp256k1 = Secp256k1::verification_only(); + static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); +} + +#[derive(Clone, Copy)] +pub enum NetworkInfo { + /// The in-memory network. + InMemory, + /// The distributed network (out of the app memory). + Distributed { network_ports: NetworkPorts }, +} + +impl NetworkInfo { + pub fn in_memory(&self) -> bool { matches!(self, NetworkInfo::InMemory) } +} + +#[derive(Clone, Copy)] +pub struct NetworkPorts { + pub tcp: u16, + pub wss: u16, +} + +pub fn encode_message(message: &T) -> Result, rmp_serde::encode::Error> { + rmp_serde::to_vec(message) +} + +#[inline] +pub fn decode_message<'de, T: de::Deserialize<'de>>(bytes: &'de [u8]) -> Result { + rmp_serde::from_slice(bytes) +} + +#[derive(Deserialize, Serialize)] +struct SignedMessageSerdeHelper<'a> { + pubkey: PublicKey, + #[serde(with = "serde_bytes")] + signature: &'a [u8], + #[serde(with = "serde_bytes")] + payload: &'a [u8], +} + +pub fn encode_and_sign(message: &T, secret: &[u8; 32]) -> Result, rmp_serde::encode::Error> { + let secret = SecretKey::from_slice(secret).unwrap(); + let encoded = encode_message(message)?; + let sig_hash = SecpMessage::from_slice(&sha256(&encoded)).expect("Message::from_slice should never fail"); + let sig = SECP_SIGN.sign(&sig_hash, &secret); + let serialized_sig = sig.serialize_compact(); + let pubkey = PublicKey::from(Secp256k1Pubkey::from_secret_key(&*SECP_SIGN, &secret)); + let msg = SignedMessageSerdeHelper { + pubkey, + signature: &serialized_sig, + payload: &encoded, + }; + encode_message(&msg) +} + +pub fn decode_signed<'de, T: de::Deserialize<'de>>( + encoded: &'de [u8], +) -> Result<(T, Signature, PublicKey), rmp_serde::decode::Error> { + let helper: SignedMessageSerdeHelper = decode_message(encoded)?; + let signature = Signature::from_compact(helper.signature) + .map_err(|e| rmp_serde::decode::Error::Syntax(format!("Failed to parse signature {}", e)))?; + let sig_hash = SecpMessage::from_slice(&sha256(helper.payload)).expect("Message::from_slice should never fail"); + match &helper.pubkey { + PublicKey::Secp256k1(serialized_pub) => { + if SECP_VERIFY.verify(&sig_hash, &signature, &serialized_pub.0).is_err() { + return Err(rmp_serde::decode::Error::Syntax("Invalid message signature".into())); + } + }, + } + + let payload: T = decode_message(helper.payload)?; + Ok((payload, signature, helper.pubkey)) +} + +fn sha256(input: impl AsRef<[u8]>) -> [u8; 32] { Sha256::new().chain(input).finalize().into() } + +#[derive(Debug, Eq, PartialEq)] +pub struct Secp256k1PubkeySerialize(Secp256k1Pubkey); + +impl Serialize for Secp256k1PubkeySerialize { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(&self.0.serialize()) + } +} + +impl<'de> de::Deserialize<'de> for Secp256k1PubkeySerialize { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: de::Deserializer<'de>, + { + let slice: &[u8] = de::Deserialize::deserialize(deserializer)?; + let pubkey = + Secp256k1Pubkey::from_slice(slice).map_err(|e| de::Error::custom(format!("Error {} parsing pubkey", e)))?; + + Ok(Secp256k1PubkeySerialize(pubkey)) + } +} + +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum PublicKey { + Secp256k1(Secp256k1PubkeySerialize), +} + +impl PublicKey { + pub fn to_bytes(&self) -> Vec { + match self { + PublicKey::Secp256k1(pubkey) => pubkey.0.serialize().to_vec(), + } + } + + pub fn to_hex(&self) -> String { + match self { + PublicKey::Secp256k1(pubkey) => hex::encode(pubkey.0.serialize().as_ref()), + } + } + + pub fn unprefixed(&self) -> [u8; 32] { + let mut res = [0; 32]; + match self { + PublicKey::Secp256k1(pubkey) => res.copy_from_slice(&pubkey.0.serialize()[1..33]), + } + res + } +} + +impl From for PublicKey { + fn from(pubkey: Secp256k1Pubkey) -> Self { PublicKey::Secp256k1(Secp256k1PubkeySerialize(pubkey)) } +} + +pub type TopicPrefix = &'static str; +pub const TOPIC_SEPARATOR: char = '/'; + +pub fn pub_sub_topic(prefix: TopicPrefix, topic: &str) -> String { + let mut res = prefix.to_owned(); + res.push(TOPIC_SEPARATOR); + res.push_str(topic); + res +} + +#[test] +fn signed_message_serde() { + let secret = [1u8; 32]; + let initial_msg = vec![0u8; 32]; + let signed_encoded = encode_and_sign(&initial_msg, &secret).unwrap(); + + let (decoded, ..) = decode_signed::>(&signed_encoded).unwrap(); + assert_eq!(decoded, initial_msg); +} diff --git a/mm2src/mm2_p2p/src/network.rs b/mm2src/mm2_p2p/src/network.rs new file mode 100644 index 0000000000..364cee72cf --- /dev/null +++ b/mm2src/mm2_p2p/src/network.rs @@ -0,0 +1,66 @@ +use crate::relay_address::RelayAddress; +use libp2p::PeerId; + +pub const NETID_8762: u16 = 8762; + +#[cfg_attr(target_arch = "wasm32", allow(dead_code))] +const ALL_NETID_8762_SEEDNODES: &[(&str, &str)] = &[ + ( + "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", + "168.119.236.251", + ), + ( + "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", + "168.119.236.240", + ), + ( + "12D3KooWSmEi8ypaVzFA1AGde2RjxNW5Pvxw3qa2fVe48PjNs63R", + "168.119.236.239", + ), + ("12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", "135.181.34.220"), + ( + "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", + "168.119.236.241", + ), + ( + "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", + "168.119.236.243", + ), + ( + "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", + "168.119.236.249", + ), + ( + "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", + "168.119.236.246", + ), + ( + "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", + "168.119.236.233", + ), + ("12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", "168.119.237.8"), + ("12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", "168.119.237.13"), + ("12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", "65.108.90.210"), + ("12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", "46.4.78.11"), + ("12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", "46.4.87.18"), +]; + +#[cfg(target_arch = "wasm32")] +pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { Vec::new() } + +#[cfg(not(target_arch = "wasm32"))] +pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { + use std::str::FromStr; + + if netid != NETID_8762 { + return Vec::new(); + } + ALL_NETID_8762_SEEDNODES + .iter() + .map(|(peer_id, ipv4)| { + let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); + let address = RelayAddress::IPv4(ipv4.to_string()); + (peer_id, address) + }) + .collect() +} diff --git a/mm2src/mm2_p2p/src/relay_address.rs b/mm2src/mm2_p2p/src/relay_address.rs new file mode 100644 index 0000000000..a568ea9722 --- /dev/null +++ b/mm2src/mm2_p2p/src/relay_address.rs @@ -0,0 +1,187 @@ +use crate::{NetworkInfo, NetworkPorts}; +use derive_more::Display; +use lazy_static::lazy_static; +use libp2p::Multiaddr; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::str::FromStr; + +#[derive(Clone, Debug, Display, Serialize)] +pub enum RelayAddressError { + #[display( + fmt = "Error parsing 'RelayAddress' from {}: address has unknown protocol, expected either IPv4 or DNS or Memory address", + found + )] + FromStrError { found: String }, + #[display( + fmt = "Error converting '{:?}' to Multiaddr: unexpected IPv4/DNS address on a memory network", + self_str + )] + DistributedAddrOnMemoryNetwork { self_str: String }, + #[display( + fmt = "Error converting '{:?}' to Multiaddr: unexpected memory address on a distributed network", + self_str + )] + MemoryAddrOnDistributedNetwork { self_str: String }, +} + +impl std::error::Error for RelayAddressError {} + +impl RelayAddressError { + fn distributed_addr_on_memory_network(addr: &RelayAddress) -> RelayAddressError { + RelayAddressError::DistributedAddrOnMemoryNetwork { + self_str: format!("{:?}", addr), + } + } + + fn memory_addr_on_distributed_network(addr: &RelayAddress) -> RelayAddressError { + RelayAddressError::MemoryAddrOnDistributedNetwork { + self_str: format!("{:?}", addr), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub enum RelayAddress { + IPv4(String), + Dns(String), + Memory(u64), +} + +impl FromStr for RelayAddress { + type Err = RelayAddressError; + + fn from_str(s: &str) -> Result { + // check if the string is IPv4 + if std::net::Ipv4Addr::from_str(s).is_ok() { + return Ok(RelayAddress::IPv4(s.to_string())); + } + // check if the string is a domain name + if validate_domain_name(s) { + return Ok(RelayAddress::Dns(s.to_owned())); + } + // check if the string is a `/memory/` address + if let Some(port_str) = s.strip_prefix("/memory/") { + if let Ok(port) = port_str.parse() { + return Ok(RelayAddress::Memory(port)); + } + } + Err(RelayAddressError::FromStrError { found: s.to_owned() }) + } +} + +impl<'de> Deserialize<'de> for RelayAddress { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + let addr_str = String::deserialize(deserializer)?; + RelayAddress::from_str(&addr_str).map_err(de::Error::custom) + } +} + +impl RelayAddress { + /// Try to convert `RelayAddress` to `Multiaddr` using the given `network_info`. + pub fn try_to_multiaddr(&self, network_info: NetworkInfo) -> Result { + let network_ports = match network_info { + NetworkInfo::InMemory => match self { + RelayAddress::Memory(port) => return Ok(memory_multiaddr(*port)), + _ => return Err(RelayAddressError::distributed_addr_on_memory_network(self)), + }, + NetworkInfo::Distributed { network_ports } => network_ports, + }; + + match self { + RelayAddress::IPv4(ipv4) => Ok(ipv4_multiaddr(ipv4, network_ports)), + RelayAddress::Dns(dns) => Ok(dns_multiaddr(dns, network_ports)), + RelayAddress::Memory(_) => Err(RelayAddressError::memory_addr_on_distributed_network(self)), + } + } +} + +/// Use [this](https://regex101.com/r/94nCB5/1) regular expression to validate the domain name. +/// See examples at the linked resource above. +fn validate_domain_name(s: &str) -> bool { + use regex::Regex; + + lazy_static! { + static ref DNS_REGEX: Regex = Regex::new(r#"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$"#).unwrap(); + } + + DNS_REGEX.is_match(s) +} + +fn memory_multiaddr(port: u64) -> Multiaddr { format!("/memory/{}", port).parse().unwrap() } + +#[cfg(target_arch = "wasm32")] +fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { + format!("/ip4/{}/tcp/{}/wss", ipv4_addr, ports.wss).parse().unwrap() +} + +#[cfg(not(target_arch = "wasm32"))] +fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { + format!("/ip4/{}/tcp/{}", ipv4_addr, ports.tcp).parse().unwrap() +} + +#[cfg(target_arch = "wasm32")] +fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { + format!("/dns/{}/tcp/{}/wss", dns_addr, ports.wss).parse().unwrap() +} + +#[cfg(not(target_arch = "wasm32"))] +fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { + format!("/dns/{}/tcp/{}", dns_addr, ports.tcp).parse().unwrap() +} + +#[test] +fn test_relay_address_from_str() { + let valid_addresses = vec![ + ("127.0.0.1", RelayAddress::IPv4("127.0.0.1".to_owned())), + ("255.255.255.255", RelayAddress::IPv4("255.255.255.255".to_owned())), + ("google.com", RelayAddress::Dns("google.com".to_owned())), + ("www.google.com", RelayAddress::Dns("www.google.com".to_owned())), + ("g.co", RelayAddress::Dns("g.co".to_owned())), + ( + "stackoverflow.co.uk", + RelayAddress::Dns("stackoverflow.co.uk".to_owned()), + ), + ("1.2.3.4.com", RelayAddress::Dns("1.2.3.4.com".to_owned())), + ("/memory/123", RelayAddress::Memory(123)), + ("/memory/71428421981", RelayAddress::Memory(71428421981)), + ]; + for (s, expected) in valid_addresses { + let actual = RelayAddress::from_str(s).unwrap_or_else(|_| panic!("Error parsing '{}'", s)); + assert_eq!(actual, expected); + } + + let invalid_addresses = vec![ + "127.0.0", + "127.0.0.0.2", + "google.c", + "http://google.com", + "https://google.com/", + "google.com/", + "/memory/", + "/memory/9999999999999999999999999999999", + ]; + for s in invalid_addresses { + let _ = RelayAddress::from_str(s).expect_err("Expected an error"); + } +} + +#[test] +fn test_deserialize_relay_address() { + #[derive(Deserialize, PartialEq)] + struct Config { + addresses: Vec, + } + + let Config { addresses: actual } = + common::serde_json::from_str(r#"{"addresses": ["foo.bar.com", "127.0.0.2", "/memory/12345"]}"#) + .expect("Error deserializing a list of RelayAddress"); + let expected = vec![ + RelayAddress::Dns("foo.bar.com".to_owned()), + RelayAddress::IPv4("127.0.0.2".to_owned()), + RelayAddress::Memory(12345), + ]; + assert_eq!(actual, expected); +} diff --git a/mm2src/mm2_p2p/src/swarm_runtime.rs b/mm2src/mm2_p2p/src/swarm_runtime.rs new file mode 100644 index 0000000000..cd83c0181f --- /dev/null +++ b/mm2src/mm2_p2p/src/swarm_runtime.rs @@ -0,0 +1,34 @@ +use common::executor::{BoxFutureSpawner, SpawnFuture}; +use futures::Future; +use libp2p::swarm::Executor; +use std::pin::Pin; +use std::sync::Arc; + +#[derive(Clone)] +pub struct SwarmRuntime { + inner: Arc, +} + +impl SwarmRuntime { + pub fn new(spawner: S) -> SwarmRuntime + where + S: BoxFutureSpawner + Send + Sync + 'static, + { + SwarmRuntime { + inner: Arc::new(spawner), + } + } +} + +impl SpawnFuture for SwarmRuntime { + fn spawn(&self, f: F) + where + F: Future + Send + 'static, + { + self.inner.spawn_boxed(Box::new(Box::pin(f))) + } +} + +impl Executor for SwarmRuntime { + fn exec(&self, future: Pin + Send>>) { self.inner.spawn_boxed(Box::new(future)) } +} diff --git a/scripts/ci/android-ndk.sh b/scripts/ci/android-ndk.sh index 5a6f038ebe..955ce8ab4a 100755 --- a/scripts/ci/android-ndk.sh +++ b/scripts/ci/android-ndk.sh @@ -2,7 +2,8 @@ set -ex -NDK_URL=https://dl.google.com/android/repository/android-ndk-r21b-linux-x86_64.zip +NDK_URL=https://dl.google.com/android/repository/android-ndk-r23c-linux.zip +NDK_CHECKSUM=6ce94604b77d28113ecd588d425363624a5228d9662450c48d2e4053f8039242 main() { local arch=$1 \ @@ -28,7 +29,11 @@ main() { pushd $td curl -O $NDK_URL - unzip -q android-ndk-*.zip + if ! echo "$NDK_CHECKSUM android-ndk-r23c-linux.zip" | sha256sum -c -; then + echo "Error: SHA256 sum mismatch for android-ndk-r23c-linux.zip" + exit 1 + fi + unzip -q android-ndk-r23c-linux.zip pushd android-ndk-*/ sudo ./build/tools/make_standalone_toolchain.py \ --install-dir /android-ndk \ From 3c078b6a2aed2e4f9e534907e77deca466df091f Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:13:14 +0300 Subject: [PATCH 11/40] fix(fee): remove fixed 0.0001 min threshold for TakerFee (#1971) This commit also changes the minimum trading volume for evm and tendermint to be the smallest possible amount of the coin and reduce minimum trading price to be any value above 0 --- mm2src/coins/eth.rs | 6 +- mm2src/coins/qrc20.rs | 6 +- mm2src/coins/tendermint/tendermint_coin.rs | 4 +- mm2src/coins/tendermint/tendermint_token.rs | 5 +- mm2src/mm2_main/src/lp_ordermatch.rs | 4 +- mm2src/mm2_main/src/lp_swap.rs | 32 +++------ mm2src/mm2_main/src/lp_swap/taker_swap.rs | 71 +++++++++---------- .../tests/docker_tests/docker_tests_inner.rs | 8 +-- .../tests/docker_tests/qrc20_tests.rs | 15 ++-- .../tests/docker_tests/swap_watcher_tests.rs | 28 +++----- .../tests/mm2_tests/mm2_tests_inner.rs | 2 +- 11 files changed, 79 insertions(+), 102 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index ef20bcd52e..8514af4ab6 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2138,11 +2138,13 @@ impl MarketCoinOps for EthCoin { } } + #[inline] fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) } + #[inline] fn min_trading_vol(&self) -> MmNumber { - let pow = self.decimals / 3; - MmNumber::from(1) / MmNumber::from(10u64.pow(pow as u32)) + let pow = self.decimals as u32; + MmNumber::from(1) / MmNumber::from(10u64.pow(pow)) } } diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 68cdf5a35a..36f8086e70 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1291,11 +1291,13 @@ impl MarketCoinOps for Qrc20Coin { fn display_priv_key(&self) -> Result { utxo_common::display_priv_key(&self.utxo) } + #[inline] fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) } + #[inline] fn min_trading_vol(&self) -> MmNumber { - let pow = self.utxo.decimals / 3; - MmNumber::from(1) / MmNumber::from(10u64.pow(pow as u32)) + let pow = self.utxo.decimals as u32; + MmNumber::from(1) / MmNumber::from(10u64.pow(pow)) } } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 8bc284b9e6..1920c38709 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2394,9 +2394,11 @@ impl MarketCoinOps for TendermintCoin { .to_string()) } + #[inline] fn min_tx_amount(&self) -> BigDecimal { big_decimal_from_sat(MIN_TX_SATOSHIS, self.decimals) } - fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } + #[inline] + fn min_trading_vol(&self) -> MmNumber { self.min_tx_amount().into() } } #[async_trait] diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index ab1c3573c3..d753a0e61d 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -594,10 +594,11 @@ impl MarketCoinOps for TendermintToken { fn display_priv_key(&self) -> Result { self.platform_coin.display_priv_key() } + #[inline] fn min_tx_amount(&self) -> BigDecimal { big_decimal_from_sat(MIN_TX_SATOSHIS, self.decimals) } - /// !! This function includes dummy implementation for P.O.C work - fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } + #[inline] + fn min_trading_vol(&self) -> MmNumber { self.min_tx_amount().into() } } #[async_trait] diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index d18db2f10f..2ad35b86e4 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1777,9 +1777,9 @@ impl fmt::Display for MakerOrderBuildError { #[allow(clippy::result_large_err)] fn validate_price(price: MmNumber) -> Result<(), MakerOrderBuildError> { - let min_price = MmNumber::from(BigRational::new(1.into(), 100_000_000.into())); + let min_price = 0.into(); - if price < min_price { + if price <= min_price { return Err(MakerOrderBuildError::PriceTooLow { actual: price, threshold: min_price, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 5c972c2df9..3fa86a7bd2 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -714,17 +714,6 @@ pub fn lp_atomic_locktime(maker_coin: &str, taker_coin: &str, version: AtomicLoc } } -pub fn dex_fee_threshold(min_tx_amount: MmNumber) -> MmNumber { - // Todo: This should be reduced for lightning swaps. - // 0.0001 - let min_fee = MmNumber::from((1, 10000)); - if min_fee < min_tx_amount { - min_tx_amount - } else { - min_fee - } -} - fn dex_fee_rate(base: &str, rel: &str) -> MmNumber { let fee_discount_tickers: &[&str] = if var("MYCOIN_FEE_DISCOUNT").is_ok() { &["KMD", "MYCOIN"] @@ -739,11 +728,11 @@ fn dex_fee_rate(base: &str, rel: &str) -> MmNumber { } } -pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, dex_fee_threshold: &MmNumber) -> MmNumber { +pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amount: &MmNumber) -> MmNumber { let rate = dex_fee_rate(base, rel); let fee_amount = trade_amount * &rate; - if &fee_amount < dex_fee_threshold { - dex_fee_threshold.clone() + if &fee_amount < min_tx_amount { + min_tx_amount.clone() } else { fee_amount } @@ -752,8 +741,7 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, dex_fee_th /// Calculates DEX fee with a threshold based on min tx amount of the taker coin. pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { let min_tx_amount = MmNumber::from(taker_coin.min_tx_amount()); - let dex_fee_threshold = dex_fee_threshold(min_tx_amount); - dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &dex_fee_threshold) + dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount) } #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] @@ -1600,34 +1588,34 @@ mod lp_swap_tests { #[test] fn test_dex_fee_amount() { - let dex_fee_threshold = MmNumber::from("0.0001"); + let min_tx_amount = MmNumber::from("0.0001"); let base = "BTC"; let rel = "ETH"; let amount = 1.into(); - let actual_fee = dex_fee_amount(base, rel, &amount, &dex_fee_threshold); + let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); let expected_fee = amount / 777u64.into(); assert_eq!(expected_fee, actual_fee); let base = "KMD"; let rel = "ETH"; let amount = 1.into(); - let actual_fee = dex_fee_amount(base, rel, &amount, &dex_fee_threshold); + let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); let expected_fee = amount * (9, 7770).into(); assert_eq!(expected_fee, actual_fee); let base = "BTC"; let rel = "KMD"; let amount = 1.into(); - let actual_fee = dex_fee_amount(base, rel, &amount, &dex_fee_threshold); + let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); let expected_fee = amount * (9, 7770).into(); assert_eq!(expected_fee, actual_fee); let base = "BTC"; let rel = "KMD"; let amount: MmNumber = "0.001".parse::().unwrap().into(); - let actual_fee = dex_fee_amount(base, rel, &amount, &dex_fee_threshold); - assert_eq!(dex_fee_threshold, actual_fee); + let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); + assert_eq!(min_tx_amount, actual_fee); } #[test] diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index c6fc2a774e..168d8fc1ac 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -5,11 +5,11 @@ use super::swap_lock::{SwapLock, SwapLockOps}; use super::swap_watcher::{watcher_topic, SwapWatcherMsg}; use super::trade_preimage::{TradePreimageRequest, TradePreimageRpcError, TradePreimageRpcResult}; use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_msg_every, - check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, dex_fee_rate, dex_fee_threshold, - get_locked_amount, recv_swap_msg, swap_topic, wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, - MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, - SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, - SwapTxDataMsg, SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL_SEC}; + check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, dex_fee_rate, get_locked_amount, + recv_swap_msg, swap_topic, wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, + NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, + SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, + SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL_SEC}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::TakerOrderBuilder; use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, @@ -2570,13 +2570,12 @@ pub fn max_taker_vol_from_available( rel: &str, min_tx_amount: &MmNumber, ) -> Result> { - let fee_threshold = dex_fee_threshold(min_tx_amount.clone()); let dex_fee_rate = dex_fee_rate(base, rel); let threshold_coef = &(&MmNumber::from(1) + &dex_fee_rate) / &dex_fee_rate; - let max_vol = if available > &fee_threshold * &threshold_coef { + let max_vol = if available > min_tx_amount * &threshold_coef { available / (MmNumber::from(1) + dex_fee_rate) } else { - available - fee_threshold + &available - min_tx_amount }; if &max_vol <= min_tx_amount { @@ -2941,21 +2940,19 @@ mod taker_swap_tests { #[test] fn test_max_taker_vol_from_available() { - let dex_fee_threshold = MmNumber::from("0.0001"); let min_tx_amount = MmNumber::from("0.00001"); - // For these `availables` the dex_fee must be greater than threshold + // For these `availables` the dex_fee must be greater than min_tx_amount let source = vec![ - ("0.0779", false), - ("0.1", false), - ("0.135", false), - ("12.000001", false), - ("999999999999999999999999999999999999999999999999999999", false), - ("0.0778000000000000000000000000000000000000000000000002", false), - ("0.0779", false), - ("0.0778000000000000000000000000000000000000000000000001", false), - ("0.0863333333333333333333333333333333333333333333333334", true), - ("0.0863333333333333333333333333333333333333333333333333", true), + ("0.00779", false), + ("0.01", false), + ("0.0135", false), + ("1.2000001", false), + ("99999999999999999999999999999999999999999999999999999", false), + ("0.00778000000000000000000000000000000000000000000000002", false), + ("0.00778000000000000000000000000000000000000000000000001", false), + ("0.00863333333333333333333333333333333333333333333333334", true), + ("0.00863333333333333333333333333333333333333333333333333", true), ]; for (available, is_kmd) in source { let available = MmNumber::from(available); @@ -2964,19 +2961,19 @@ mod taker_swap_tests { let max_taker_vol = max_taker_vol_from_available(available.clone(), "RICK", "MORTY", &min_tx_amount) .expect("!max_taker_vol_from_available"); - let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &dex_fee_threshold); - assert!(dex_fee_threshold < dex_fee); + let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount); + assert!(min_tx_amount < dex_fee); assert!(min_tx_amount <= max_taker_vol); assert_eq!(max_taker_vol + dex_fee, available); } - // for these `availables` the dex_fee must be the same as `threshold` + // for these `availables` the dex_fee must be the same as min_tx_amount let source = vec![ - ("0.0863333333333333333333333333333333333333333333333332", true), - ("0.0863333333333333333333333333333333333333333333333331", true), - ("0.0777999999999999999999999999999999999999999999999999", false), - ("0.0777", false), - ("0.0002", false), + ("0.00863333333333333333333333333333333333333333333333332", true), + ("0.00863333333333333333333333333333333333333333333333331", true), + ("0.00777999999999999999999999999999999999999999999999999", false), + ("0.00777", false), + ("0.00002001", false), ]; for (available, is_kmd) in source { let available = MmNumber::from(available); @@ -2984,32 +2981,32 @@ mod taker_swap_tests { let base = if is_kmd { "KMD" } else { "RICK" }; let max_taker_vol = max_taker_vol_from_available(available.clone(), base, "MORTY", &min_tx_amount) .expect("!max_taker_vol_from_available"); - let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &dex_fee_threshold); + let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount); println!( "available={:?} max_taker_vol={:?} dex_fee={:?}", available.to_decimal(), max_taker_vol.to_decimal(), dex_fee.to_decimal() ); - assert_eq!(dex_fee_threshold, dex_fee); + assert_eq!(min_tx_amount, dex_fee); assert!(min_tx_amount <= max_taker_vol); assert_eq!(max_taker_vol + dex_fee, available); } // these `availables` must return an error let availables = vec![ - "0.0001999", - "0.00011", - "0.0001000000000000000000000000000000000000000000000001", - "0.0001", - "0.0000999999999999999999999999999999999999999999999999", - "0.0000000000000000000000000000000000000000000000000001", + "0.00002", + "0.000011", + "0.00001000000000000000000000000000000000000000000000001", + "0.00001", + "0.00000999999999999999999999999999999999999999999999999", + "0.00000000000000000000000000000000000000000000000000001", "0", "-2", ]; for available in availables { let available = MmNumber::from(available); - max_taker_vol_from_available(available.clone(), "KMD", "MORTY", &dex_fee_threshold) + max_taker_vol_from_available(available.clone(), "KMD", "MORTY", &min_tx_amount) .expect_err("!max_taker_vol_from_available success but should be error"); } } 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 a6f3f2cd7f..d05733e1b9 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -2005,8 +2005,8 @@ fn test_get_max_taker_vol() { // https://github.com/KomodoPlatform/atomicDEX-API/issues/733 #[test] -fn test_get_max_taker_vol_dex_fee_threshold() { - let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN1", "0.05328455".parse().unwrap()); +fn test_get_max_taker_vol_dex_fee_min_tx_amount() { + let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN1", "0.00532845".parse().unwrap()); let coins = json!([mycoin_conf(10000), mycoin1_conf(10000)]); let mm_alice = MarketMakerIt::start( json!({ @@ -2034,8 +2034,8 @@ fn test_get_max_taker_vol_dex_fee_threshold() { .unwrap(); assert!(rc.0.is_success(), "!max_taker_vol: {}", rc.1); let json: Json = serde_json::from_str(&rc.1).unwrap(); - // the result of equation x + 0.0001 (dex fee) + 0.0002 (miner fee * 2) = 0.05328455 - assert_eq!(json["result"]["numer"], Json::from("1059691")); + // the result of equation x + 0.00001 (dex fee) + 0.0002 (miner fee * 2) = 0.00532845 + assert_eq!(json["result"]["numer"], Json::from("102369")); assert_eq!(json["result"]["denom"], Json::from("20000000")); let rc = block_on(mm_alice.rpc(&json!({ diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index bba1a82e3c..03c484275d 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -1036,7 +1036,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); let qtum_balance = coin.my_spendable_balance().wait().expect("!my_balance"); - let qtum_dex_fee_threshold = MmNumber::from("0.000728"); + let qtum_min_tx_amount = MmNumber::from("0.000728"); // - `max_possible = balance - locked_amount`, where `locked_amount = 0` // - `max_trade_fee = trade_fee(balance)` @@ -1053,12 +1053,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ // - `max_possible_2 = balance - locked_amount - max_trade_fee`, where `locked_amount = 0` let max_possible_2 = &qtum_balance - &max_trade_fee; // - `max_dex_fee = dex_fee(max_possible_2)` - let max_dex_fee = dex_fee_amount( - "QTUM", - "MYCOIN", - &MmNumber::from(max_possible_2), - &qtum_dex_fee_threshold, - ); + let max_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &MmNumber::from(max_possible_2), &qtum_min_tx_amount); debug!("max_dex_fee: {:?}", max_dex_fee.to_fraction()); // - `max_fee_to_send_taker_fee = fee_to_send_taker_fee(max_dex_fee)` @@ -1073,11 +1068,11 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ // where `available = balance - locked_amount - max_trade_fee - max_fee_to_send_taker_fee` let available = &qtum_balance - &max_trade_fee - &max_fee_to_send_taker_fee; debug!("total_available: {}", available); - let min_tx_amount = qtum_dex_fee_threshold.clone(); + let min_tx_amount = qtum_min_tx_amount.clone(); let expected_max_taker_vol = max_taker_vol_from_available(MmNumber::from(available), "QTUM", "MYCOIN", &min_tx_amount) .expect("max_taker_vol_from_available"); - let real_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_dex_fee_threshold); + let real_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); debug!("real_max_dex_fee: {:?}", real_dex_fee.to_fraction()); // check if the actual max_taker_vol equals to the expected @@ -1110,7 +1105,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let timelock = now_sec() - 200; let secret_hash = &[0; 20]; - let dex_fee_amount = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_dex_fee_threshold); + let dex_fee_amount = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); let _taker_fee_tx = coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount.to_decimal(), &[]) .wait() diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 41b3bf105e..55ef6dc435 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -11,9 +11,9 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin use common::{block_on, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; use futures01::Future; -use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, dex_fee_threshold, get_payment_locktime, - MakerSwap, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, - MAKER_PAYMENT_SPEND_SENT_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; +use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, get_payment_locktime, MakerSwap, + MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, + TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{enable_eth_coin, eth_jst_testnet_conf, eth_testnet_conf, mm_dump, my_balance, @@ -352,15 +352,10 @@ fn test_watcher_spends_maker_payment_eth_utxo() { let eth_volume = BigDecimal::from_str("0.01").unwrap(); let mycoin_volume = BigDecimal::from_str("1").unwrap(); - let dex_fee_threshold = dex_fee_threshold(BigDecimal::from_str("0.00001").unwrap().into()); + let min_tx_amount = BigDecimal::from_str("0.00001").unwrap().into(); - let dex_fee: BigDecimal = dex_fee_amount( - "MYCOIN", - "ETH", - &MmNumber::from(mycoin_volume.clone()), - &dex_fee_threshold, - ) - .into(); + let dex_fee: BigDecimal = + dex_fee_amount("MYCOIN", "ETH", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount).into(); let alice_mycoin_reward_sent = balances.alice_acoin_balance_before - balances.alice_acoin_balance_after.clone() - mycoin_volume.clone() @@ -510,14 +505,9 @@ fn test_watcher_spends_maker_payment_erc20_utxo() { let mycoin_volume = BigDecimal::from_str("1").unwrap(); let jst_volume = BigDecimal::from_str("1").unwrap(); - let dex_fee_threshold = dex_fee_threshold(BigDecimal::from_str("0.00001").unwrap().into()); - let dex_fee: BigDecimal = dex_fee_amount( - "MYCOIN", - "JST", - &MmNumber::from(mycoin_volume.clone()), - &dex_fee_threshold, - ) - .into(); + let min_tx_amount = BigDecimal::from_str("0.00001").unwrap().into(); + let dex_fee: BigDecimal = + dex_fee_amount("MYCOIN", "JST", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount).into(); let alice_mycoin_reward_sent = balances.alice_acoin_balance_before - balances.alice_acoin_balance_after.clone() - mycoin_volume.clone() diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index fff8b409ab..10bdeb5a29 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -2470,7 +2470,7 @@ fn check_too_low_volume_order_creation_fails(mm: &MarketMakerIt, base: &str, rel "method": "setprice", "base": base, "rel": rel, - "price": "0.00000099", + "price": "0.00000000000000000099", "volume": "1", "cancel_previous": false, }))) From 1c8a9a853fb9b4234fea2a5aac61d49447ddabbd Mon Sep 17 00:00:00 2001 From: Onur Date: Tue, 3 Oct 2023 21:39:56 +0300 Subject: [PATCH 12/40] fix(tests): p2p ctx mocking issue (#1981) For some reason, mocktopus dependency can not mock the p2p context after the p2p stack upgrade. This commit fixes this by avoiding mocking p2p context for tests. --------- Signed-off-by: onur-ozkan --- Cargo.lock | 1 - mm2src/mm2_main/src/ordermatch_tests.rs | 22 ++++++++++------------ mm2src/mm2_net/Cargo.toml | 3 --- mm2src/mm2_net/src/p2p.rs | 10 ---------- 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 296fc68f66..c7d9b6952f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4549,7 +4549,6 @@ dependencies = [ "mm2_event_stream", "mm2_p2p", "mm2_state_machine", - "mocktopus", "parking_lot 0.12.0", "prost", "rand 0.7.3", diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 4377f803ac..e9a1c1cb36 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -1662,14 +1662,10 @@ fn pubkey_and_secret_for_test(passphrase: &str) -> (String, [u8; 32]) { (pubkey, secret) } -fn p2p_context_mock() -> (mpsc::Sender, mpsc::Receiver) { +fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Receiver) { let (cmd_tx, cmd_rx) = mpsc::channel(10); - let cmd_sender = cmd_tx.clone(); - P2PContext::fetch_from_mm_arc.mock_safe(move |_| { - MockResult::Return(Arc::new(P2PContext { - cmd_tx: PaMutex::new(cmd_sender.clone()), - })) - }); + let p2p_context = P2PContext::new(cmd_tx.clone()); + p2p_context.store_to_mm_arc(ctx); (cmd_tx, cmd_rx) } @@ -1775,7 +1771,7 @@ fn test_request_and_fill_orderbook() { const ORDERS_NUMBER: usize = 10; let (ctx, _pubkey, _secret) = make_ctx_for_tests(); - let (_, mut cmd_rx) = p2p_context_mock(); + let (_, mut cmd_rx) = init_p2p_context(&ctx); let other_pubkeys: Vec<(String, [u8; 32])> = (0..PUBKEYS_NUMBER) .map(|idx| { @@ -1927,9 +1923,10 @@ fn test_process_order_keep_alive_requested_from_peer() { let ordermatch_ctx = Arc::new(OrdermatchContext::default()); let ordermatch_ctx_clone = ordermatch_ctx.clone(); OrdermatchContext::from_ctx.mock_safe(move |_| MockResult::Return(Ok(ordermatch_ctx_clone.clone()))); - let (_, mut cmd_rx) = p2p_context_mock(); let (ctx, pubkey, secret) = make_ctx_for_tests(); + let (_, mut cmd_rx) = init_p2p_context(&ctx); + let uuid = new_uuid(); let peer = PeerId::random().to_string(); @@ -2048,7 +2045,7 @@ fn test_process_get_order_request() { #[test] fn test_subscribe_to_ordermatch_topic_not_subscribed() { let (ctx, _pubkey, _secret) = make_ctx_for_tests(); - let (_, mut cmd_rx) = p2p_context_mock(); + let (_, mut cmd_rx) = init_p2p_context(&ctx); spawn(async move { match cmd_rx.next().await.unwrap() { @@ -2092,7 +2089,7 @@ fn test_subscribe_to_ordermatch_topic_not_subscribed() { #[test] fn test_subscribe_to_ordermatch_topic_subscribed_not_filled() { let (ctx, _pubkey, _secret) = make_ctx_for_tests(); - let (_, mut cmd_rx) = p2p_context_mock(); + let (_, mut cmd_rx) = init_p2p_context(&ctx); { let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); @@ -2144,7 +2141,7 @@ fn test_subscribe_to_ordermatch_topic_subscribed_not_filled() { #[test] fn test_subscribe_to_ordermatch_topic_subscribed_filled() { let (ctx, _pubkey, _secret) = make_ctx_for_tests(); - let (_, mut cmd_rx) = p2p_context_mock(); + let (_, mut cmd_rx) = init_p2p_context(&ctx); // enough time has passed for the orderbook to be filled let subscribed_at = now_ms() / 1000 - ORDERBOOK_REQUESTING_TIMEOUT - 1; @@ -2174,6 +2171,7 @@ fn test_subscribe_to_ordermatch_topic_subscribed_filled() { assert_eq!(actual, expected); } */ + #[test] fn test_taker_request_can_match_with_maker_pubkey() { let coin = TestCoin::default().into(); diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index bb80a455f2..ac114f28a3 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -43,6 +43,3 @@ gstuff = { version = "0.7", features = ["nightly"] } rustls = { version = "0.20", default-features = false } tokio = { version = "1.20" } tokio-rustls = { version = "0.23", default-features = false } - -[dev-dependencies] -mocktopus = "0.8.0" diff --git a/mm2src/mm2_net/src/p2p.rs b/mm2src/mm2_net/src/p2p.rs index 5ac6396da2..d76cd550bd 100644 --- a/mm2src/mm2_net/src/p2p.rs +++ b/mm2src/mm2_net/src/p2p.rs @@ -1,6 +1,5 @@ use mm2_core::mm_ctx::MmArc; use mm2_libp2p::behaviours::atomicdex::AdexCmdTx; -#[cfg(test)] use mocktopus::macros::*; use parking_lot::Mutex; use std::sync::Arc; @@ -9,15 +8,6 @@ pub struct P2PContext { pub cmd_tx: Mutex, } -// `mockable` violates these -#[allow( - clippy::forget_ref, - clippy::forget_copy, - clippy::swap_ptr_to_ref, - clippy::forget_non_drop, - clippy::let_unit_value -)] -#[cfg_attr(test, mockable)] impl P2PContext { pub fn new(cmd_tx: AdexCmdTx) -> Self { P2PContext { From a9f606d5ad02e054b5e048eafb37767cb806178c Mon Sep 17 00:00:00 2001 From: Onur Date: Wed, 11 Oct 2023 00:14:58 +0300 Subject: [PATCH 13/40] refactor(chore): improve item uniqueness (#1988) HashSet is used instead of Vec in some places to remove use of dedup --- mm2src/coins/lp_coins.rs | 6 ++---- mm2src/mm2_err_handle/src/mm_error.rs | 3 +++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 564863ad98..eb15ae1ca9 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2904,20 +2904,18 @@ impl CoinsContext { } // Tokens can't be activated without platform coin so we can safely insert them without checking prior existence - let mut token_tickers = Vec::with_capacity(tokens.len()); + let mut token_tickers = HashSet::with_capacity(tokens.len()); // TODO // Handling for these case: // USDT was activated via enable RPC // We try to activate ETH coin and USDT token via enable_eth_with_tokens for token in tokens { - token_tickers.push(token.ticker().to_string()); + token_tickers.insert(token.ticker().to_string()); coins .entry(token.ticker().into()) .or_insert_with(|| MmCoinStruct::new(token)); } - token_tickers.dedup(); - platform_coin_tokens .entry(platform_ticker) .or_default() diff --git a/mm2src/mm2_err_handle/src/mm_error.rs b/mm2src/mm2_err_handle/src/mm_error.rs index e044ac6a6c..5f2f11e480 100644 --- a/mm2src/mm2_err_handle/src/mm_error.rs +++ b/mm2src/mm2_err_handle/src/mm_error.rs @@ -258,6 +258,9 @@ impl MmError { .iter() .map(|src| src.file) .rev() + // If we call functions x -> y -> z which are defined in the same module, the module + // name would be duplicated three times in the path chain. `dedup` solves this issue, + // and there is no need for a `sort` before this deduplication. .dedup() .collect::>() .join(".") From 17538498400d036ae228bf3742453553bc8b7c3c Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:14:07 +0300 Subject: [PATCH 14/40] fix(tests): fix unstable tbch tests and failing price threshold test (#1990) * fix test_trade_preimage_additional_validation test since price threshold is changed in #1971 * add more electrums to unstable tbch address tests --- mm2src/coins/utxo/utxo_tests.rs | 6 ++++++ mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 8f2bf4158e..0e1d56dddc 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -1372,6 +1372,8 @@ fn test_cashaddresses_in_tx_details_by_hash() { let req = json!({ "method": "electrum", "servers": [ + {"url":"bch0.kister.net:51002","protocol":"SSL"}, + {"url":"tbch.loping.net:60002","protocol":"SSL"}, {"url":"electroncash.de:50003"}, {"url":"tbch.loping.net:60001"}, {"url":"blackie.c3-soft.com:60001"}, @@ -1411,6 +1413,8 @@ fn test_address_from_str_with_cashaddress_activated() { let req = json!({ "method": "electrum", "servers": [ + {"url":"bch0.kister.net:51002","protocol":"SSL"}, + {"url":"tbch.loping.net:60002","protocol":"SSL"}, {"url":"electroncash.de:50003"}, {"url":"tbch.loping.net:60001"}, {"url":"blackie.c3-soft.com:60001"}, @@ -1449,6 +1453,8 @@ fn test_address_from_str_with_legacy_address_activated() { let req = json!({ "method": "electrum", "servers": [ + {"url":"bch0.kister.net:51002","protocol":"SSL"}, + {"url":"tbch.loping.net:60002","protocol":"SSL"}, {"url":"electroncash.de:50003"}, {"url":"tbch.loping.net:60001"}, {"url":"blackie.c3-soft.com:60001"}, 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 d05733e1b9..1241dac2be 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -1782,11 +1782,10 @@ fn test_trade_preimage_additional_validation() { assert!(!rc.0.is_success(), "trade_preimage success, but should fail: {}", rc.1); let actual: RpcErrorResponse = serde_json::from_str(&rc.1).unwrap(); assert_eq!(actual.error_type, "PriceTooLow"); - // currently the minimum price is 0.00000001 - let price_threshold = BigDecimal::from(1) / BigDecimal::from(100_000_000); + // currently the minimum price is any value above 0 let expected = trade_preimage_error::PriceTooLow { price: BigDecimal::from(0), - threshold: price_threshold, + threshold: BigDecimal::from(0), }; assert_eq!(actual.error_data, Some(expected)); From 5df7db33ce0ae59417f27aab2618347cd7aac987 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:48:59 +0300 Subject: [PATCH 15/40] fix(transactions): add cryptocondition script type (#1991) --- mm2src/mm2_bitcoin/rpc/src/v1/types/script.rs | 13 +++++++++++++ mm2src/mm2_bitcoin/script/src/script.rs | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/mm2src/mm2_bitcoin/rpc/src/v1/types/script.rs b/mm2src/mm2_bitcoin/rpc/src/v1/types/script.rs index dfc51b8866..0be24b7214 100644 --- a/mm2src/mm2_bitcoin/rpc/src/v1/types/script.rs +++ b/mm2src/mm2_bitcoin/rpc/src/v1/types/script.rs @@ -20,6 +20,8 @@ pub enum ScriptType { Create, LelantusMint, ColdStaking, + // Komodo smart chains specific + CryptoCondition, } impl From for ScriptType { @@ -38,6 +40,7 @@ impl From for ScriptType { GlobalScriptType::Call => ScriptType::Call, GlobalScriptType::Create => ScriptType::Create, GlobalScriptType::ColdStaking => ScriptType::ColdStaking, + GlobalScriptType::CryptoCondition => ScriptType::CryptoCondition, } } } @@ -62,6 +65,7 @@ impl Serialize for ScriptType { ScriptType::Create => "create".serialize(serializer), ScriptType::LelantusMint => "lelantusmint".serialize(serializer), ScriptType::ColdStaking => "cold_staking".serialize(serializer), + ScriptType::CryptoCondition => "cryptocondition".serialize(serializer), } } } @@ -99,6 +103,7 @@ impl<'a> Deserialize<'a> for ScriptType { "create" => Ok(ScriptType::Create), "lelantusmint" => Ok(ScriptType::LelantusMint), "cold_staking" => Ok(ScriptType::ColdStaking), + "cryptocondition" => Ok(ScriptType::CryptoCondition), _ => Err(E::invalid_value(Unexpected::Str(value), &self)), } } @@ -152,6 +157,10 @@ mod tests { serde_json::to_string(&ScriptType::ColdStaking).unwrap(), r#""cold_staking""# ); + assert_eq!( + serde_json::to_string(&ScriptType::CryptoCondition).unwrap(), + r#""cryptocondition""# + ); } #[test] @@ -208,5 +217,9 @@ mod tests { serde_json::from_str::(r#""cold_staking""#).unwrap(), ScriptType::ColdStaking ); + assert_eq!( + serde_json::from_str::(r#""cryptocondition""#).unwrap(), + ScriptType::CryptoCondition + ); } } diff --git a/mm2src/mm2_bitcoin/script/src/script.rs b/mm2src/mm2_bitcoin/script/src/script.rs index a644f00ed4..c3bc10fad2 100644 --- a/mm2src/mm2_bitcoin/script/src/script.rs +++ b/mm2src/mm2_bitcoin/script/src/script.rs @@ -25,6 +25,8 @@ pub enum ScriptType { Call, Create, ColdStaking, + // Komodo smart chains specific + CryptoCondition, } /// Address from Script @@ -467,6 +469,9 @@ impl Script { ScriptType::ColdStaking => { Ok(vec![]) // TODO }, + ScriptType::CryptoCondition => { + Ok(vec![]) // TODO + }, } } From 222a8516122df7aaca3c6b0e417f29111917668d Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:12:05 +0300 Subject: [PATCH 16/40] fix(tests): refactor tbch electrums list and remove non working ones (#1992) --- mm2src/coins/utxo/bch.rs | 4 +- mm2src/coins/utxo/utxo_tests.rs | 41 +++---------------- .../tests/mm2_tests/bch_and_slp_tests.rs | 34 ++++----------- .../tests/mm2_tests/mm2_tests_inner.rs | 16 +------- mm2src/mm2_test_helpers/src/for_tests.rs | 5 ++- 5 files changed, 21 insertions(+), 79 deletions(-) diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 393f25cd54..c94179e131 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1401,7 +1401,7 @@ pub fn tbch_coin_for_test() -> (MmArc, BchCoin) { use common::block_on; use crypto::privkey::key_pair_from_seed; use mm2_core::mm_ctx::MmCtxBuilder; - use mm2_test_helpers::for_tests::BCHD_TESTNET_URLS; + use mm2_test_helpers::for_tests::{electrum_servers_rpc, BCHD_TESTNET_URLS, T_BCH_ELECTRUMS}; let ctx = MmCtxBuilder::default().into_mm_arc(); let keypair = key_pair_from_seed("BCH SLP test").unwrap(); @@ -1411,7 +1411,7 @@ pub fn tbch_coin_for_test() -> (MmArc, BchCoin) { let req = json!({ "method": "electrum", "coin": "BCH", - "servers": [{"url":"blackie.c3-soft.com:60001"},{"url":"testnet.imaginary.cash:50001"},{"url":"tbch.loping.net:60001"},{"url":"electroncash.de:50003"}], + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), "bchd_urls": BCHD_TESTNET_URLS, "allow_slp_unsafe_conf": false, }); diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 0e1d56dddc..c9d823898f 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -42,7 +42,8 @@ use futures::future::join_all; use futures::TryFutureExt; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_number::bigdecimal::{BigDecimal, Signed}; -use mm2_test_helpers::for_tests::{mm_ctx_with_custom_db, DOC_ELECTRUM_ADDRS, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; +use mm2_test_helpers::for_tests::{electrum_servers_rpc, mm_ctx_with_custom_db, DOC_ELECTRUM_ADDRS, + MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS, T_BCH_ELECTRUMS}; use mocktopus::mocking::*; use rpc::v1::types::H256 as H256Json; use serialization::{deserialize, CoinVariant}; @@ -1371,15 +1372,7 @@ fn test_cashaddresses_in_tx_details_by_hash() { }); let req = json!({ "method": "electrum", - "servers": [ - {"url":"bch0.kister.net:51002","protocol":"SSL"}, - {"url":"tbch.loping.net:60002","protocol":"SSL"}, - {"url":"electroncash.de:50003"}, - {"url":"tbch.loping.net:60001"}, - {"url":"blackie.c3-soft.com:60001"}, - {"url":"bch0.kister.net:51001"}, - {"url":"testnet.imaginary.cash:50001"} - ], + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), }); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -1412,15 +1405,7 @@ fn test_address_from_str_with_cashaddress_activated() { }); let req = json!({ "method": "electrum", - "servers": [ - {"url":"bch0.kister.net:51002","protocol":"SSL"}, - {"url":"tbch.loping.net:60002","protocol":"SSL"}, - {"url":"electroncash.de:50003"}, - {"url":"tbch.loping.net:60001"}, - {"url":"blackie.c3-soft.com:60001"}, - {"url":"bch0.kister.net:51001"}, - {"url":"testnet.imaginary.cash:50001"} - ], + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), }); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -1452,15 +1437,7 @@ fn test_address_from_str_with_legacy_address_activated() { }); let req = json!({ "method": "electrum", - "servers": [ - {"url":"bch0.kister.net:51002","protocol":"SSL"}, - {"url":"tbch.loping.net:60002","protocol":"SSL"}, - {"url":"electroncash.de:50003"}, - {"url":"tbch.loping.net:60001"}, - {"url":"blackie.c3-soft.com:60001"}, - {"url":"bch0.kister.net:51001"}, - {"url":"testnet.imaginary.cash:50001"} - ], + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), }); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -2971,13 +2948,7 @@ fn test_tx_details_kmd_rewards_claimed_by_other() { fn test_tx_details_bch_no_rewards() { const TX_HASH: &str = "eb13d926f15cbb896e0bcc7a1a77a4ec63504e57a1524c13a7a9b80f43ecb05c"; - let electrum = electrum_client_for_test(&[ - "electroncash.de:50003", - "tbch.loping.net:60001", - "blackie.c3-soft.com:60001", - "bch0.kister.net:51001", - "testnet.imaginary.cash:50001", - ]); + let electrum = electrum_client_for_test(T_BCH_ELECTRUMS); let coin = utxo_coin_for_test(electrum.into(), None, false); let tx_details = get_tx_details_eq_for_both_versions(&coin, TX_HASH); diff --git a/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs b/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs index f4790b4b35..59e61647f9 100644 --- a/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/bch_and_slp_tests.rs @@ -3,9 +3,9 @@ use common::{block_on, log, repeatable}; use crypto::StandardHDCoinAddress; use http::StatusCode; use itertools::Itertools; -use mm2_test_helpers::for_tests::{disable_coin, enable_bch_with_tokens, enable_slp, my_tx_history_v2, sign_message, - tbch_for_slp_conf, tbch_usdf_conf, verify_message, MarketMakerIt, Mm2TestConf, - UtxoRpcMode}; +use mm2_test_helpers::for_tests::{disable_coin, electrum_servers_rpc, enable_bch_with_tokens, enable_slp, + my_tx_history_v2, sign_message, tbch_for_slp_conf, tbch_usdf_conf, verify_message, + MarketMakerIt, Mm2TestConf, UtxoRpcMode, T_BCH_ELECTRUMS}; use mm2_test_helpers::structs::{EnableBchWithTokensResponse, RpcV2Response, SignatureResponse, StandardHistoryV2Res, UtxoFeeDetails, VerificationResponse}; use serde_json::{self as json, json, Value as Json}; @@ -13,26 +13,8 @@ use std::env; use std::thread; use std::time::Duration; -#[cfg(not(target_arch = "wasm32"))] -const T_BCH_ELECTRUMS: &[&str] = &[ - "electroncash.de:50003", - "tbch.loping.net:60001", - "blackie.c3-soft.com:60001", - "bch0.kister.net:51001", - "testnet.imaginary.cash:50001", -]; - -#[cfg(target_arch = "wasm32")] -const T_BCH_ELECTRUMS: &[&str] = &[ - "electroncash.de:60003", - "electroncash.de:60004", - "blackie.c3-soft.com:60004", -]; - const BIP39_PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; -fn t_bch_electrums_legacy_json() -> Vec { T_BCH_ELECTRUMS.iter().map(|url| json!({ "url": url })).collect() } - #[test] #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_cashaddresses() { @@ -63,7 +45,7 @@ fn test_withdraw_cashaddresses() { "userpass": mm.userpass, "method": "electrum", "coin": "BCH", - "servers": t_bch_electrums_legacy_json(), + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), "mm2": 1, }))) .unwrap(); @@ -177,7 +159,7 @@ fn test_withdraw_cashaddresses() { "userpass": mm.userpass, "method": "electrum", "coin": "BCH", - "servers": t_bch_electrums_legacy_json(), + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), "address_format":{"format":"standard"}, "mm2": 1, }))) @@ -267,7 +249,7 @@ fn test_withdraw_to_different_cashaddress_network_should_fail() { "userpass": mm.userpass, "method": "electrum", "coin": "BCH", - "servers": t_bch_electrums_legacy_json(), + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), "mm2": 1, }))) .unwrap(); @@ -330,7 +312,7 @@ fn test_common_cashaddresses() { "userpass": mm.userpass, "method": "electrum", "coin": "BCH", - "servers": t_bch_electrums_legacy_json(), + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), "mm2": 1, }))) .unwrap(); @@ -531,7 +513,7 @@ fn test_sign_verify_message_bch() { "userpass": mm.userpass, "method": "electrum", "coin": "BCH", - "servers": t_bch_electrums_legacy_json(), + "servers": electrum_servers_rpc(T_BCH_ELECTRUMS), "mm2": 1, }))) .unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 10bdeb5a29..e5869e2896 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -22,7 +22,7 @@ use mm2_test_helpers::for_tests::{btc_segwit_conf, btc_with_spv_conf, btc_with_s MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, - TBTC_ELECTRUMS}; + TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; use crypto::StandardHDCoinAddress; use mm2_test_helpers::get_passphrase; @@ -3176,19 +3176,7 @@ fn test_convert_utxo_address() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum( - &mm, - "BCH", - false, - &[ - "electroncash.de:50003", - "tbch.loping.net:60001", - "blackie.c3-soft.com:60001", - "bch0.kister.net:51001", - "testnet.imaginary.cash:50001", - ], - None, - )); + let _electrum = block_on(enable_electrum(&mm, "BCH", false, T_BCH_ELECTRUMS, None)); // test standard to cashaddress let rc = block_on(mm.rpc(&json! ({ diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index cb3abed0ba..95e27ad771 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -166,6 +166,7 @@ pub const QRC20_ELECTRUMS: &[&str] = &[ "electrum2.cipig.net:10071", "electrum3.cipig.net:10071", ]; +pub const T_BCH_ELECTRUMS: &[&str] = &["tbch.loping.net:60001", "bch0.kister.net:51001"]; pub const TBTC_ELECTRUMS: &[&str] = &[ "electrum1.cipig.net:10068", "electrum2.cipig.net:10068", @@ -1732,7 +1733,7 @@ pub enum UtxoRpcMode { } #[cfg(not(target_arch = "wasm32"))] -fn electrum_servers_rpc(servers: &[&str]) -> Vec { +pub fn electrum_servers_rpc(servers: &[&str]) -> Vec { servers .iter() .map(|url| ElectrumRpcRequest { @@ -1743,7 +1744,7 @@ fn electrum_servers_rpc(servers: &[&str]) -> Vec { } #[cfg(target_arch = "wasm32")] -fn electrum_servers_rpc(servers: &[&str]) -> Vec { +pub fn electrum_servers_rpc(servers: &[&str]) -> Vec { servers .iter() .map(|url| ElectrumRpcRequest { From 3e323df320535d9c6ac4c7b1d235501e40b1da48 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:52:19 +0300 Subject: [PATCH 17/40] fix(cli): make mm2-libp2p optional in mm2_net for adex-cli (#1997) * This commit does the following: * Adds `p2p` and `event-stream` features to `mm2_net` crate to avoid adex-cli depending on `mm2-libp2p` and rust-libp2p in turn. * Moves all RICK/MORTY electrums in tests to DOC/MARTY except 4 tests * Fixes WASM `test_send` test by using `ALICE_PASSPHRASE` for the keypair (makes WASM tests green again). *Todo in another commit * Move these tests to DOC/MARTY electrums * `test_hd_utxo_tx_history` * `test_search_for_swap_tx_spend_electrum_was_refunded ` * `test_search_for_swap_tx_spend_electrum_was_spent ` * `test_send_maker_spends_taker_payment_recoverable_tx ` * Rename all instances of RICK/MORTY to DOC/MARTY --- mm2src/adex_cli/Cargo.lock | 22 ++ mm2src/coins/eth/eth_wasm_tests.rs | 9 +- .../eth/web3_transport/http_transport.rs | 20 +- mm2src/coins/for_tests/RICK_HEADERS.json | 2 +- mm2src/coins/utxo/utxo_common_tests.rs | 12 +- mm2src/coins/utxo/utxo_tests.rs | 115 ++++----- mm2src/coins/utxo/utxo_wasm_tests.rs | 9 +- mm2src/mm2_main/Cargo.toml | 2 +- mm2src/mm2_main/src/wasm_tests.rs | 18 +- .../tests/integration_tests_common/mod.rs | 10 +- .../tests/mm2_tests/best_orders_tests.rs | 28 +-- .../tests/mm2_tests/mm2_tests_inner.rs | 219 +++++------------- .../tests/mm2_tests/orderbook_sync_tests.rs | 140 ++--------- .../tests/mm2_tests/tendermint_tests.rs | 6 +- .../mm2_main/tests/mm2_tests/z_coin_tests.rs | 6 +- mm2src/mm2_net/Cargo.toml | 14 +- mm2src/mm2_net/src/lib.rs | 8 +- mm2src/mm2_net/src/wasm_ws.rs | 2 +- mm2src/mm2_test_helpers/src/electrums.rs | 13 +- mm2src/mm2_test_helpers/src/for_tests.rs | 13 ++ 20 files changed, 233 insertions(+), 435 deletions(-) diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 26fd951793..ab1f4548d9 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -1748,6 +1748,7 @@ dependencies = [ "gstuff", "hex", "lazy_static", + "mm2_event_stream", "mm2_metrics", "mm2_rpc", "primitives", @@ -1773,6 +1774,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "mm2_event_stream" +version = "0.1.0" +dependencies = [ + "async-trait", + "cfg-if 1.0.0", + "common", + "parking_lot", + "serde", + "tokio", + "wasm-bindgen-test", +] + [[package]] name = "mm2_metrics" version = "0.1.0" @@ -1812,6 +1826,7 @@ dependencies = [ "lazy_static", "mm2_core", "mm2_err_handle", + "mm2_state_machine", "prost", "rand 0.7.3", "rustls 0.20.8", @@ -1857,6 +1872,13 @@ dependencies = [ "uuid", ] +[[package]] +name = "mm2_state_machine" +version = "0.1.0" +dependencies = [ + "async-trait", +] + [[package]] name = "newline-converter" version = "0.2.2" diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index ae0d5482df..cc0e7de4ec 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -1,8 +1,10 @@ use super::*; use crate::lp_coininit; +use crypto::privkey::key_pair_from_seed; use crypto::CryptoCtx; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_test_helpers::for_tests::{ETH_DEV_NODE, ETH_DEV_SWAP_CONTRACT}; +use mm2_test_helpers::get_passphrase; use wasm_bindgen_test::*; use web_sys::console; @@ -16,10 +18,9 @@ fn pass() { #[wasm_bindgen_test] async fn test_send() { - let key_pair = KeyPair::from_secret_slice( - &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), - ) - .unwrap(); + let seed = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); + let keypair = key_pair_from_seed(&seed).unwrap(); + let key_pair = KeyPair::from_secret_slice(keypair.private_ref()).unwrap(); let transport = Web3Transport::single_node(ETH_DEV_NODE, false); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index ac45850f1d..75c747bc82 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -23,9 +23,16 @@ pub struct AuthPayload<'a> { /// Parse bytes RPC response into `Result`. /// Implementation copied from Web3 HTTP transport #[cfg(not(target_arch = "wasm32"))] -fn single_response>(response: T, rpc_url: &str) -> Result { - let response = - serde_json::from_slice(&response).map_err(|e| Error::InvalidResponse(format!("{}: {}", rpc_url, e)))?; +fn single_response(response: T, rpc_url: &str) -> Result +where + T: Deref + std::fmt::Debug, +{ + let response = serde_json::from_slice(&response).map_err(|e| { + Error::InvalidResponse(format!( + "url: {}, Error deserializing response: {}, raw response: {:?}", + rpc_url, e, response + )) + })?; match response { Response::Single(output) => to_result_from_output(output), @@ -325,7 +332,12 @@ async fn send_request_once( // account for incoming traffic event_handlers.on_incoming_response(response_str.as_bytes()); - let response: Response = serde_json::from_str(&response_str).map_err(|e| Error::InvalidResponse(e.to_string()))?; + let response: Response = serde_json::from_str(&response_str).map_err(|e| { + Error::InvalidResponse(format!( + "url: {}, Error deserializing response: {}, raw response: {:?}", + uri, e, response_str + )) + })?; match response { Response::Single(output) => to_result_from_output(output), Response::Batch(_) => Err(Error::InvalidResponse("Expected single, got batch.".to_owned())), diff --git a/mm2src/coins/for_tests/RICK_HEADERS.json b/mm2src/coins/for_tests/RICK_HEADERS.json index bcd0e66368..ca237b9b38 100644 --- a/mm2src/coins/for_tests/RICK_HEADERS.json +++ b/mm2src/coins/for_tests/RICK_HEADERS.json @@ -1 +1 @@ -["04000000f5cd86754336c59b0f9c81cc3be2567138a5214e8ca014c0d5f2d5f8a44b710c6715085d84a3f4e04c741815c5cce305626da08f7b1e73d9dfbca1222fd50e1dfbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e501a405d0f0f0f2038009c39d1194ceb3e142656c64d412963657aa13fc1d13e28781816a75c0000fd400500675327fb0d0ff8827bb4ee89e90c97196b3a11f10fd03ad7f2abb0317b8964347fb5338d73ee79d16f0a0116363017c0b9a99734a1fc365d77466a5a49271a35d9d8c337b87fbfbf030bcba7b7f9c25032d432190e3ba473c977d91ce441a1b044fc60b445c9d37b1c6eda7daa8d2681926663661af4acd690b09f61b1252feee26f5c0911015682c5d8d432ad1f5d4bf6dd2c0b139f915b5e8db8ba742c0976721dff99364e4502efc8b2198e4d97e3a1c75367e74c774d183e771322189a762e6701179b7773bd6aa4af25e8519360840455ffdbcec5580117bf1276e0ca4f64b7304c12e00a184d5eae932175aac56311e39fa58e3b9d983cc725e30d9eb2d04e31d6f5e511e2593945805cfe29807e764c9025b89f65e4cdfb923367ba17ccd57eea9930ef1df202d9a997e91fa31f05bb291709093f1f0950b37358a95cbecbbc09263b18e1fa872d80bb2cc800b07e8b93c6666a480b258adc3ad0af29eebcf3cb1e93fd4815959ef371142c8af16ba7f764c67d514403d3f7a5baae6eb9d96bc04b54096025248950d55c4ced86d9286b081f6602d90e4c7399eefe173d1cda03a12a70ab22b769a45250c1726f13c1ab401bbb5d060e73aad14b6db5189b622a395e104111b17dee92075b2f748a357637da8803151978e911e48b78eb9326c113c88ecff637a3d978d341742e4f69647ee8bf0125f37310dc5f33c8a374949de2f2414f088f7046029e59c46798569d72a021bcafe4a00de902593c5502dfc8f749051e299c2b40d3f969546adce61fc83c1c2d335132d228f143ec2b9d6c7a3d0321b7fd6bdc05771a9cac8699765caa7b78ce64aa9363bbbfcddb1ab5b751bc4bc046b0bbd2e8c2c52c04ba918ff53f1ed6fd8c5a87dc2c560ca6c1f262bfaea2893da1d7429c9585eee330f7b008551671ebfbe68159590a7f00aa5103e20047002d48c366aa30e8018fa55cc52754400c21d0d6da4ec64266d0da720cb24e3979724148a5772000dbb36fc1a895792bdfb826048cbf90da491a35e78b17a42bcf0647e6b64cfd96ec24b9bcfd01bb754a272d693b7120d645fc3dec6ae4eb5831fe17de31d63b9cdfa92a3e532761e3532d60a8330dda02489852662e349b85b0783c18ecefb322181dfede11e25f067ce4d4e13279582c49f12ed68e2a5a34ec02fa6b387199212d0f44a24503b0a7f4de62ef5e922f167d8cfa505c90d33d891e0478dbdf73a73d61602c3cc9f187910738d35aa2dd047c96ed72b434d86b45bae64eaa54a0e3a1f5890b13f10812d34cba6824043a9ab0c841e19e8641c0d699733449aee7df0dd0246111404899961f7c61b29a1799c14f289b5bd4d006ad930e7b8a2bbe5c0dd8e2176b32973dd35e5c991503c5dea08f6d4f899383ee53ecbcb545775eb7090313fc7e5e977fbbc330d2fc6fadeab676adf8844e088a4b4ab695dcb9bb83f2092dc4ba0e585c793b48182eba04ac8affc17e35c5919434e0625f89be4dcf25e6ea418514dda1b927b903397a990f59e41e73e70b894da3f8583e5d4a3087e6de507ca24c645a3b5150e58de4211d82eb3f17060f8fbcca131e2bbe154c14d939edea8c6d08e5e7f5541b36db4dc8582f32c71c89166acf0b89677bf59642bd3b43a364ef5c4c9703fbf845019276b919ffd572d1ef507af0809f4d2410e150b9d25185b5f2b9445d7842b6120a15ffa6bb0bc57c5cafcace1b4af3a293681f4c7543be522c470e18fd50a889024ac26f816f4ebac5e89164753cd40ce3e3c98a1315277bbeb637b879328a082c1b60b5394789fbb89167c0fce933e504ebcd9ddf4b5170101c886b583d2e870f953ee336d4f3d2d5f4a73b164640a03d7ca317ecd3246eb7badb58c42f211f1b37f9", "040000001450adf355d19840e46b7e02b321159436642efbb75b3e7499ffb20a7cace50eba42911576c5082d237d00d9f896a1740f1582fea84fac45006624d50666fa8efbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e351b405d0f0f0f20000042ad1f709d2b04deaaa327655f0948da4d0a690e36d4bb66bb867d4f0000fd40050182ddf7df4acfd59ea811130cb8db4a303b19a717150daaa4c9ccbd92a61061b09eacc93307f0990cda0186a2b2f64511b2c1031245019867a0ddf19e902022b7ea45aca347e984948586ff2cc6da261d58b5db02da5dfb2a8acea2d361d0370edcba914098ea63280a0026e2a94c6a8705b9f2a7d66dff595aa77434bb04cf2ac23f0483ff6bb32151c1a153134fba3a7eac0ff4e6c6a40a9713f0fae16a885d80787fdb52e4ab03b2a57d79969733cdbd90b6d36fe2292e6a38ee1b450e7c18a9d8798cd75535bc5ad68d91df5d1a6c9a0cc50b004fb17e6ba83a731ac5cf5a8e10a337021c0fb8cb66ecd7ae53f33d82267ea84134c4862fb818047516c404df1da1f617613e3c97f4fafdc37a2e0e4bf624e5aa22123f957316fee3650d7df5cd6fe6950aed518b0df32c7d9b4cb0c20a2fa08d54e8169e781dcaff53a36c32c5f5aab5c2cc62ad09d3d37f4bed0243a9a5cac47a42a447d14ccefb4ebdd38412f1ba3f1a768089180311180994d2b1d72dd2e1781aa7bc03a99db0b92bb84ba005a0d5d48704d918d9acf42534966f4882adbe43f005d8bd076a1d030184faa71e0a63a4b8dec52a102e701246e2223520d7f81ba10d1e6a7c44db22bd814f083355f059c0e72b217e9283177644e208ddee2d75caf6e5643ef092006635aca2514f5f9d7df314c3d1926667fe6e92d2300ef7f17f026976f32eded3077bff7472584ca7bdfda25e1b23300ccf0ad7a25e0fdcd533f8077ad799400e2bb2580873878983d213deedeb0185ccd466d0854c2ab22020b7856067d29900e2c0f5d48df96a19bf49564456047486257383e327d10422a622f52b4e0afcdeeece159a70c14b32df6faec9046e657a6b31b2bc9f37ae0ccb945d59065e7d58b576b30de90c0ab1ed9b1c2215015386c2992341c876053842ca1f7650573eaa32023937086a48c90d2125724cfbaea1e5edc9bb046d2e3c7d61bc639549d839d375faa4811a0e257071f02585415f106b3721624a14ad2f4aed5e2ae4971b9f342ea5aa47ef05fdc1a6987a1f644bc652ce79714e047f8aaf460bf241f4a0708a6562cafe41af3ba29611b573f123d42e09e2d76a408ddf63672766bede3308bb9f3311e23c517884d29d2546652cbb9a985961309c99ae7b20aab9501ad5f929fee5e28016b5b4490698a431abdc35a96082b13357faa9804eaedac4ca1645fecf5c9cd5cb26a8f1df1d4ead01f54355bd7d38c5742353df9b05bd1e6407da68137142687d457f39d8e4885897715b78b147a7b8dfb1f20aa0fe5ba4189edea2512501c7cb51767b36eef10e04cd3740d832e3e41b8b4ff3d7de08b3f65920c97d89dbf4e4cf2466cfb58f25b9c7ff4682c0e7664896e5d5bd34df26bb91cdfc8b38cf44bba3e1a12952e60130347d4703128691a9e6f473f265b2b81a1dd808c7c838a23f03f292e6a1b42097a43b9546dc332ec636f299714a06b4f10a8a8faccefd28e416c97945f959d811603d2c61eb3e46a431b73a5ca726357fda59d52192b46403acd2fd9943c3f440cbe0a7de5fe0cf59c49de9073c689d38141ad508f9fab3edab1fb9ae6f69f8ef1f1c49c199f08c7c3b6bcf645e3075caa6f5aa7ddbdb3221db93b5d729fb994563ba4c630ffcf100776c9908db93a1dc50deebaffc62c31b37d96134b0719fcd1c9df720009f32c76e8da59b9ada3296efbedf9c28137e5e01c7cda2f0b1f912296a67bb05545edca06420d72326e10e7fb3aacbb229f9cd5c1cc92c8cce3b0c048ed7180931b7701663904bd17209370bbb094c11bfc3f53ec94b4c5752c17179aeb1d45c61ae7df80c8569322f064046f936728e72703c1caf5717bd230e00742e3b0c25eff9341304012b1b521c37fc2917", "04000000b4e46aac1dd63c06d2b78f794c47daafb18b2fc74c91870fc989f2a94ea71304767bb8837b02490c0612d352e81c0fed786278778fcdf780266781f2cdecc154fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e361b405d0f0f0f2001000c670cee462e62478dce353c9f2ebdefc80efd6084670f8b4dc452fd0000fd4005007b63b32756ec00d366518ce6ed8adac030daf3c822e5b2e81e316fe7fb1d22607e3cbfd1c8f5f35b0a01e539c7ed5b72a5b55e513b40f6e90fdff63f8ae61eedfd5b34dcd3e96fd642df9c18897a92647ec20a030dc610bba73fafe05f35f9fd352c13563d3e36fd28d395244f32f9899d346358883d84292f3d93a2970bb86bf8c476c191ee2640dd60d64309c71e5bc5d443a28503f9a90cfde404fa59135f0df69cfef94f6b025ff9170fc6122b61698b166d60d10f0293dbe9441f25c25d5690099c9812630e452ce8797d55bd665312e97df8fed5233db783a2d507e74622e70cdad59819e405fbaf8797bb8c84953362c6d27abf2dfcaa54047840e8e5f200b5adbd210417d1127c90e90df54680d1b4e8036da53bee85da73abfdbf37575d5e90551bdbaf37c862a59f92d5032160ea3c017f157fe3832ed0aeca8099a22d4dbc14986ffd7eeea02fb829ff01c1fb57111a6a4f270c42a9c4e1a61e777118fe030faa246fd7fac6c9ec385813caec584a6d0abcf9380ce22f1532698f519e6103d57aef51aee0b1f7a4130f40038de819d74512a672008aef85ad3fbf8de5d005b387d0108de5f7a9e511481f26cfaa6076b4c85723e04334888f7a75055a9541fcf01511d5eeb84a850f83ae547a18483def8bb855967f770bacae9dd61a20d51b3a8aee495da43ba7e469f479bafa193ea82802f20023a04ffd777e5fe1d7a729358524598ebf1d1466f4f14b98571909a567e0e2cae8ced3407f1bf61387cf428a52e28d20e0142b1f2f3e7d15aab5c10f16b0d55def5328af38baf90ae67773ea6f3cdc643511257283880b617d9b688603fd576a0a24c03bd12b22a56e31a36b6835d7f7140c87729d9abb5c1feedc26ae9a9aa78b4e12d22d337d833835c8f56df27443295c93e18de227dbe143460b8eb546a1b17cdd0352012568ef12a02207495a7744ad6370e6fd55b8f65a19d799eb570c73051599f1a7ac5a192632963c081c1386f0d97412372ff02822cd4ce6c2355c87d1095921d72aa382a79c33a35bc23aaaf400553de0311b7c032cdf5e9581d9a9d101b1f33abaebd59dbade19e011ab00d3d8e08f7daa90c449ffeaf77da2a8d302e91c403f3d86088014c2faf74b6c7fe056e110fa1afd303274ed0f9d61bd500b27c8f546fd1a102ed1318606522c60c8027a72de1102bb43b03bf70680ff5e1617043565918f7dcfc6949642d75cf195a3771f400d17c2f265254baa4b6ea3b2a6e16eaa5b4ee59bf45821122d5aacc937e76b1c65cb1f6902e18a44f2ef59086a68781249df151357d0a9fb435c746ed4d77cf534bfeb2be2ba3b57e81f7d2006fd428b90aa5d27070f5324502904d30d788981b690132d14b7e15a6e292be514b2cd61325960630406f23c5566e9179920d702257e4b83edf7fde3b01253a5b6102a972f7f20831ecced78455a25c0e543c2389aec9e81f50b72217810a33ae1d68ee66d659d544c02714f82364995442d1877b4eaa4938240fffc370e82ec692eb2f37a955a02dc7d50b4df40e55f9902161b56ec25ea09deec8004614607d7e68e2fcf461116714a0134c94f565db8250405a6cb559ef96494f4f904e1a8f183f3b9bbcd27985bc38ca25e89257c229a98c57230deeefcd9c106299b8d3116af110b6b2555e937d4d97d03b35c4c0a93feefa8978b7de6eae81794f7047a28521f41c809da30df52a210d1b58e62fb3a7aa2f14a1c1f49b42fb21249366e567fd074c647e43f1fb6fa59fdd8d40e2e04a80691956e9833c2627aece015439a39ff740eb1ea900ae4b287b03d2345182bea1ace953a7f6b0f7713da30477deab111d4533d45760655b25ce62135cbc6b821e4f13bbfbc83a6ec248ee513812d0d22", "040000000dd4ced9028c6c992f05d91005ad37a8edd2d6ddcfe8f5ec41b4f664bd41d606b2d094878d17f583ea6e4adbafe21be9e5656d1eb62d6c4ce6c1a734cd7b3f14fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493ebb21405df0e400200000f1daebb7304adf6d200a4b082d3cee45a5adfead5434d9ca838b41e60000fd4005002872133865a783f1fbf4f2f2c722d971b076a54748a27ce99aa0faf70bd4abddd3f399e34a601e2b4509a9ffdeced2a54bbbdaa4c5de558aaa67e45c12ed320a5c2c86dc4cfb6c1256d79ec5011dd46db8642a00b35f9459908332a7df43ddaeeab2a22c82d6273b489463e195b6e7fbe4c946121bdc1ed639b5fbf7a20662dab7c10758b680a5e38726e432fd109e3f9ffc0a86628b73c34d82dbceb5b56ac32596198c7223d7099aaf03bf2816538da6d32d1b2842a362cb5bdfcb158c24427c9254a347ab021e466417849a08edfc041fd7990a690cbef32dd0922c49dd942504f67989026d0efe2e5620b90b5b3576ff0075c88b2c5abc884221ad098ea6b2c8e7f7ab839adae4ee2955dd0b4908240e81d529548f88e7588bf9227512e33a31dcda3346e23beeba399ceff52115f91043e48d995652fd874728231d81565b3542ded63aba5144531e811c5823002a933c4ad218a388e030488406a61de2daf1222909caf14686c9549538db55ed9d3ebb3a717eb6db1311e585aac68cf63159c2c327a81c2315b45bfbc72a21372d20851bf7bb9ae125bb3dba067b02f33a22c817b89bb1130cd8b7ac2a628e3a74b6fbb6089edbd519229f92d7d374251291e31ad3dd886e952a5a104e1b496fa4d1a153a5b5f3c4389b70d10af48459c31e567d6c665124ee6965cf96e4ed5c6d2e5ae9bbba4205db7a990de442f3c160821d9d213bdd32eb506dac0f16f274714fbbfd1897843367de90b9895937d71e11dea583a0dc4d9ff84f254913d35c45a481d0373d379eac67c5adee559ff02403ebb2c4ee846699725309b48b1c359c74e17087c7e1c6ee037e7e127b1d243f06f37b599d7ce59efa43f34f7f45b1a05ab91ea5200e9ca0fccc4e8d877833c8242fb269526cfd438342513eb4edd69e21a8f8553ea359091db1e8bf1ee1009e160910d7a5ed3c3521ee3dd6f450a6ac696fb907be1bebcfe095fd56cdb1f05ae2695c973fb9daa70ce237b8a093fa7fdb8a54d5e831799b94435e92ba79f773d8ef63a31970c3c8534cc4d4124aa85bc0fe0a824b0504da2d53aa8b583b91e1abdae2baff9e6d0da4451f8e5cfa5b0dba2140d224825d4e254c909318cac59aacaac4b59200a686346eb90249a8d5337a20a8f2e5d4c916087a9be9cd865c5b0b85f03ce614054c0e2b93d46b992ece6447c66553da4ad4bbfa2f725d57448b31e98ba6ebc7935ee59ece0d44b8badd10793e90190d1a23f7b0537b73bf0642d888bdf17923eb2b017551d0f3036772afba67ba3a390e96009809b66fd5bf832b7e35551396314d93d9204dfa451b0f4d7bd1cacc884fb7e0474dfe45fb4293db5efba10cd0a0aec7184a0d0eefb29e28792a4585f6bf79e22fde9ffc5c9587d3320e1320e2cabf591ab2c9ae8d01c73aa7f59f72679129507b14ff6b82eccdfab15607e7678554ac6175e7f1a7a20d4a01d7085edcf0cc0aca75557d88a9712545f177cd57826e2f099c711e309ff6e94c6a20b7f273978204f8482a378c72872701fee116fc45b69c4d6231ca1fa41771028ef4e77429abec23d0debfc5df1ef5abc7c5970b6c6f5f813d351d5b0bb2d249fcfdb6f639cdc04987a53a5ea6f54258eb76119f8063be67746b9ae2f22ed4f85a53a5065461a70360f6b5e2b10125cd7a06373d881b07ac261d7f0a2499a05902f304901ffd59a6fcf4d81aa616c588c8f71122cabefd7443805225c57f808c2c643c590493e0a637bfafa7d4f379afd2a298a4b7a7330796a0fbd45a4745c76fc6fc9ee14f5b399a9a65fa38b0b655f152750b6e81a3fc0e26ecb9b7be7e532b1149f6ce75849e08744b56b62b3781adcebd128a7221117cb2e5e21af3be06321d8bc3cc34be1d4fb67e", "040000003532dff71acc284484e922a49ba7616ebf113284b7768d0c95fc367c7d21ac063fe0673ff3306afaaa5fd17e95a008dbe8f568b179255b8652c1bb9ad48b0400fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e3a1b405d0f0f0f200b007a090b9293ce74165289d02df912f8f58a0ffd4b37bb77e507d253540000fd4005008dbe02ea6ee1bfa371530ef0d2d29ecb865f8e4f1a8053420c507c57a56313a96acfab52d442574d2b08ec0ef83dece6c38e6e912d32b40475b910fcde164556f71cb9a4ae43279095090cb8b4a584f3d7aeae00ccec16ef8e971bde9a1305c2be64edb1377adced0c5d79ba7d04097e90df223d02261cd22b389d4428180b7527ea9c4de7e6f2578bdbda200a395977beaa1b18d3959752e7a5c21a13fdca3ba85d9d97b80f0402949716143cfddbfadcd1488c29f730713c052c26276162a294cd394fef500396afaf6f567c3a7c65501f362b6793e2d34f5230d36f85799f4ed566fdd0e8708f07d36b667c6390150963cce8593aa4eab9db6503c5bdd501f37409ed1f51c1633a8619f8aad9ed9e2dea6e3d97e6ca0df11574b8c5c95e4ef433fc68ad08b71e268288ea1388632653bacde4631024bdec791d0629e306eaeb79585b21ef0957e0016273341414032cf9e75666c3e57b3150901a9e76a149f56b3fe231c5e9e25439e739ef135d28557e6737ebb47fea160c2d70bbd344113a7485f7f2ae4e1d97500b9fada04d60b6554aa376ffdb7bd4f12943086e4e129c809b0a2f0d13fb8b9306dc611132a2179160a2fde7967f19c0bcdd0258e696d7db968211c4220e678bf5fbad108ad4af84474f5ddc7383d362bba9055447b796051804d5f57906965132786634d352a241eb20114efa052115b9990764d88be5e2fa3147924976b58cc8cf16716330aa5dd79b71c11893ba6fd75368e43c79a1087f9477ec026fd7ac0904a08d595329b7ce73e35309000d686f1fbf3f0df521876f79f8d06c8adaedac0cacb790e15f6c43765391ee10b2da79466c5cf1aa26178f4d5b5f801b4e8ec2bf3e2e401eb7b0be8afa344b66a9b723cde7ea1435dffdb320c2ba6bbeb19e3c718d6b88b4cef9ec6b5629adb9e439c3dd1bf24101e090fa51d4e1b2f4fd91c1e8c17e21c88edbd59628761308ee320753fa72a300045d09595a203b87d2032bb4100a8131bab0bb920f56adc13d4e899a091b14f5e2e5f150360d76a8745923d63c191ee37fe1690686d7403a4e415af04b814584e7cab221993302cc3abf1ef561653dd14eb924e34d4634f9ef5a3a2a9d13d76d8b96ae1c61ea77e76253455362ec387cd365453d6c91ef2df8c1ce1a49326eff3cfa7a79db2613072efa7af263602b1eb992d757b6f197c29f9f512a19a7a9d750d99454d707d2446dd1fffca3a23df49f151b9979c876019bc24e131b48c61fbd9986d0a11a1923faae85dada013b4481b8a71c325a90aed7703b083f3c4612124122e0f7c3b3fdb997256300d639922498226398a8c865d170a4ce273b019a6000d58545097c068926518683eb3ca0c9967cc256b79178f55b27c4f41ca49b6c632ac283a5aab40a8d807bb24d0a0395f179950a2f968e2d659cc34317e6493eb70cfe50afd35feae38c0186e046805dba48bab71c5fd9b40e4ad362e7581d138d4a48220c71b5e2c96f39a4753b53bfb80899f7bbd2ed2547aefdd689f5b53a4b3d102845a9e81c53c8f307d5ee436c77ab11d7bba94d308c7487a023fbbfe9fea45e50fcd4e241bc5eac681e6b557aace4173f883862faf1eeddf4de9ecd827c2bbcbd6e08b9a725f96a29d62a7b0f5ac55c18a860055eff5b0e58f98b22c832f178431075223bd00fe12d3612ca04e25633783339aa697f11f32c68fd10121221ddd05195b5c190ecb1cf2e4f2e417688acdc041314b18ec69d3f9d63b202095932e642e0e5bea7e30b95122854a5a14bf6f0f3189ca5722539ed2c19131454ee96c41689fef63de6382051a796017179fb780d862d6ed6a80523edbcd2af169c428269b47f8b3c3a61131da6596401905b845bad541eeddc41f8c901", "04000000d88e7ae1c13594eed71400c7b9d016bf736bf353dfa580ac3d1e602a07ca1e04410f99559b142d8defbcef11273ac6e51c3a43d88613399a74bfd8e228d19041fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e431b405d0f0f0f200300eb230b78a5c8824775471507436c50191db43a4fd704de03ae971a9f0000fd400501a9cd20f5d9efc7684e5a4aeee34e02c53659e41b04fe6c79a9dc91773ff0019290c163898875fed44616580313c72014cd374fd306f0ac8db0dab5b82db93628fa382f9acc098491054d5e4f27c996af52fe70059aac1d4403b1908e71b455d3e323ee1efafc1d3815608b5a3e95d4ebb51c64cf73768742434edeaab521d617c7a78d0f3ab9eae2e8e6ca66b0fe734ccb4444f17cbd77df168914ff05893352223d8ab6709c6806e114f75a424481178c25b83b7575f600f292b8ac0ba8b588e71cc9b5448f918128dc24ef1ee21bace810bc5b3dae47250f9fbec253252ec208ceed9544b83c8e9f0041e98dd5b2a9b59b70b08f298f43736de5072ad30afd4d1aab5e5a91951a43458e5beada35013cb33fd15d296c196cdbf4a4324df96e214bd7d0f00ebf565f9c132e135e1e55197079328203cf1f1d9a0f258c309f94515f50d0720162ee3f2a1012185e9f03267ed649c573e6ad4f404c9ac1dcdcfc4d30115007c7734b6dd6f681d70da83455ecd6ce9fe17ef3371264d78f0beecafdf8d454b44e4036759b47f0cf831621faf781c76d71bd8625f288bf84e5ba0d16dfc503f43ebed5c50a0cbfad03b3aca3cd6617da946df420e7c927019dcf1b24d6e4ffa57b133d9bf7cf08550ea9d0b29c0f94fcbd749409d4e558c92528b2d66f1945f582b04bc7753cc1b3477471d3d0f730deadfe03b7426b96ac2249e936a4fa38e314124eb87a397c0c1fe409bac47a7ffe07225d0f59695e57b47dbf6d0e3bebdc035aab5985dd360a60f799523a393f38bc101fafa6bb5ee1dfa068318629cc10bd8ca64ebc6d0442a38a4b23f2439197b38ae34deefba552bf2ba10da5f0c58fb650f3b788151059b6e4deaa555c519710fbe4a5b893e83985208396c3d7af11fa7891f97f4f95bd31fc2009713912b7b3424d043715735cccb501b69a669d33e355ad99f050ea2ae53d7f3b33d85608f788b75fa1a1dbd33920b153883c2e10d1bd031c0714a91ba7dae34dc12ce1ff71d0730cc6b719355e3a7357339863575f81f00446a0b8382f084a3be954071402889c4dc6b8d4b4e15f16e16ebe58ed1da06e1cbaedc343913210ed02e2eb7f2fa90dcbf1af791507f97a4f78e1bf417a0345bd9c5074120a93bd55c110e09d7644d2891ccdf6084d6d4337f6a540ba1c31076e5e9e079cffcd64f543c706a52f2503f6143781306d87afe294e545839e340f4d244cea3dc134348b0d4fe7a9d3d2921d5790d3f4ae20a43162aeb7e2a5302396121b575d4fb62ad66adff65ff236063f19e31dada5555057beb5583e42eb8dbddd2e13f0fbf5ec546e4c3322649c636e2726385052617259015ded3a3acd5cb295d8e935bc8359020456c1967324f670cb435ea5dfeae1ef1d7ef89554c82fc58639055dcc9401d75639bf418b60fc1b53480561d4aa76c474f79a28653b7da9e3cc2d3023434877691a4585131b9b9002da41bcf4767165d26801793a1d561867c6150f15676cb49a376617cb756618f3056a6a567671fd870a064472b5ba8bf25273a8a986194f039abf2ff7099b3da1c39e9e5da0675cf0159d35324866e865f76a331b6a8aaf1e91d10b69c22277a1bb53952d1eb7b83026e8a7fada93ac70aa8ea4604b3d3b83697fbd311f04d702c5fa115ab4ce7c66c57f4360f6e3d3bf000b111508895c2706d54116516950a6a7a5c80e140e2d156d97cef7c7766d780e4342883a175dafc08f006c16346240fb8cffa7f4e561697dfe2d6da6e41e3e0b05415e3ed44b8c9914b9d32136b855577b615f3bcf8ee407807ef74e89f44de905355b4ea2e6a9ba45ab23c1b93e7edce483d9def7b15d3e8a3658e89c25cb25d1d2294257f087e2c678b011c907ca7a9b14e31a", "04000000165d2373fc86b149fd84258d07d4e8fc3e8cea0bd8e61679fe1b7052736eda0c23cadc619bec08ec6890bdcc40c98d59c58a8efc90171211d61c80a9ffb778d2fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e461b405d0f0f0f2009004e98b4610de39f26863ccebb0a035de209b4ac0591fc3f2dbc8698f30000fd4005004e276aff84f22cffdd982114d0ce7347727f5a3e0f6fa4eb095e98fba7f78321c6750b698a0056f7c523a8ed6d44f0f7f1df6082725349af55d9f4f8434d3da2244fdfeba7799912652001f48af67d3d54fed3087cf36bf1f068b5bd4713638a2f4e497f84f79137580d9f636b60591dec15c843a47f71526c7c1dbc9d1899490008e009f346c6824bd2cb88650be22949641a334510c95a2598d81a773d74de85c6443efc48c5048e8474b29a33c308fab86fff596f5241967fd05c1f35fd8bef3aa241e98155521f4e278765c4fc65de0b5c70d20b89ccd3770473280cf618d192e13224ac2cae4e10dd5bfc4149a193344319c7fd137868d7fa0c421ea240d58dc3339ee1ff025031f695e9bc9fae31c147b84d604b27a50d13914f5e770214a1f63b4c0ece40df949b19cfed07d5187ada6fd303049f0c7d1e4da49e7b14098310a103497fbb9be5d6afd90173027fa660fc86b2a451c2f35d101cb4de0910dbed82270ae3aeb52aad2797ccd6d7b67c7bfb0165fb8b05060439546809b9d57db982576e6d5d861008bbb6420e34ed926d4c4d04b9f062741442b4e129eedaef8a0c14c657c04597148245c43cf52418de2e34da6ae20ddbedb282c6c1f4ebba33f7f5a56f515de073068b163f6e1ca1b5ed5dc9bb63d0847405f3937cbdd3c326ca8c36d22d8a0184fba537d3b12acaf8cdd7de3b038189a1d5ad702d6c3300d5ae749baaf28b5d2e883525e9d653daafe31066d4aa63d77f5f3dbb7db8dd111995a5765a15a5791716b27cc2001a18a99ebe3315a4030c1f5a1c1f5ed182ef40bd4e651393d00fdd1cf56ea61ca57617913193c5b76907d1a6cdfc24932f197b855ae221c16675f5dcab315d6dd1042ff0401dadb6ede24e40456d31066cc2fdb7c9d7a5fbd9b31eb9b221b050eea1411a32f7befc55f670f83f72a601da847702c2227b4f6ea129686bebfa6b5ff95ab8104007b41752aa1b5d98e77d1f7b75228d60dbbf3336e5e358e2386503caf12423f9470d316d740cf5c548a59e17311d68d153b5e954b8789d16d1827d35da0c6aa2fdbde8883fc1367198a66d959b103f99e5e40ddab91c6d2191b597c9149182508939e91a98a450159fab2c41d9cb1bf6ec81c3976a2be592316e61173168c76ebf57f6497bf8159ff5f82352befc3e96380401dbc986589eb1be30b6a1417c643e4dc897a88a11ca3ea9f3e544c9de6664d4cb4d822606a39954601f31a1a079e0eb63672a895626d212f691147ead91582175bbf69d86d738b528afb34b0a9a877cfe0651061235ac6aa245f74638517fa322f6408807b30a2718e925a3bd47bc8bdcd6440faee7bd29553370403d23d9257977150d9f7175023eacb022c4bc6c3d6ed926271e1ca8537a770b35f55fe93e21d22a49356cc5029f95a75f950040b12283a6c46ecf2f7bc83c28b616e936fe530bb8c7b3daa7b634eab9f203225b19920a5bd71419880def2e10e4a859e82213a86ddd596e422fdee628350903e3dd95d6c541d8e98191de0def177f920347dc7ef7c9393467ae4f6042e06e1839db1ac0b377978ef3275956a285a9f34a110edaf9ebd2195e3ad249cc3476e4b8a9c5c7548def59369fa6c71d8b6f6e745742c7a8be35abfe43c8919b185c9171075da0a0838cf3c9048ac4e7e92ccbb1cf7d7ed53a27ed3f1e49a921776f286496a02956c6634afbe09433e1962da2e9d93b9433b37d289f9e462ac4fe3b374da487667fa68655bf0538beed4b9d274b12bb61b00b2a259f6905b914804f3135799df4863d811e7cd7366deeba3df07761f2d0949434fabbde27c2db17cc1771d6e661223d57c01542d2f53f4365cfb4bc24364c234ce4d8ec4e0f637636154c51629a6d7f551b16", "040000007c3ecd9449ebaa247f1667198b842e7639c72fc4799213a48567a09348f57a08bade5629b98b2faf63786a61f007fda03223a477960fbf3be1129cedfc1299dffbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e4e1b405d0f0f0f200f009e4d27e5ec0f3c3433e60fba4e73c740214bb8139db93b68c911540a0000fd400500159bf26f70e19f8c0e8601d9370161cd249dda861095388ff084abf6c407240b33ab620617295ea8fc0d1310cc408b4bd768f360de496b0ba49f0b94f44e4ffab74732e4248be151a696fade431a986d9dfa0b0560b660db065d9bf730a71692faa5569fcf75d6325432041eab6232a3de7849b7fcd07ed30b073f05d50ae3acd1e54cbc1fc141777072fe500b8c69bfe47225ff674b004d2ec91895b4f62051712b4e1bfdb1270b49b57ce318c14beeb87307156c565aed1618b6f22217e112ec21b46dedfca90360d885d2835e37897f0bd5af8bea591cdfd538b67ad16a57964a76bfcc00115aa8945a4519ef452323978c68e4acf934344eb70dc647bfcd94e5051ad9c11ea33503f94ba99a52b342d74663a457f2def43014caef7e571243c0f2f32647ac64e1a2ef6c03a703a5c953b1e4aa83811fdeb34bf16583c79d46fb9852ba21606cbb16ac3cb8fbf6021bccf030dce273a128c9f2f554f35284de1f7caa38cf7af175d0a86d0c69641833e1cbc619bb951b6110457f2719ec404df04f575056d55c424b3ddf24cf25cc89bf2b5fe3fda19652916fb6bced0d5d7bb33205a06aa03c108d04c023e4fe3470b101521ffc54fe082a0c6ce0e251d7c42212fb3b3fe17595622d2b7f23f1713ef98bb802c25be24ce8374fd55c6adb7ba13bcbae08e6dee325e402b56269c46771a3d45dc4f303aeb6a72e303749a1d5b7065b5b5c7b2eef3be11233a0c23c295bd00b10d25a597cde208ef2a25d392f0973110e34d0677c8839a12ece58f46cc15aa7bd72146e8a97a6a919cbee79e151f93c51b9cc4e38f7370deb7f4121e26a9fe598063af57e855619e01656891d4babfbafd34bd4d45f42e137e18e89df49f4c8c32405d2b1130fcba1d283a2cbcfc9c491cb55f21425272232674ded673bd51afa7008d5d5eea634d5e87500b204ac939ed84ba8c59120d05279ca62049aa63420bd3e01a10c7a2b351162378f612011a2969d62940f528d2b0010f4eb3219f20a8430830503652a5c5012755a2a57a9ae3754c7e380e360be8e62c097e535091e68cd5c4b0e3b829355b5bec681e30d35be8191309bcd41922e54efac4884854d716f65b2e394780d11ed92f8a3acb95fddd9d2299a25113e1c141d77c4338fff40fe8fdf51aafd850b1add8551539ed0837801b897ba4e2cf113d79390bb4b5a9be0e3391cde911dd75fb883c7a4372b65647e1cdf290d94f378c3dc04ca6ee5828787c31fc821fadd5eccb207465365b71ea7eac8cccda8fa731a690b56fa4ee7665c7be9a5146f026199c84317def4931dcd39e45f9f747fff941f6c6690bc8c34432bfd42298cc7067893b53c8a75293a61ae7f12b079d85da3241265b65760f15f0a0e554daf0375e2ad9d30ec58048358616ee43bb767c8038e4f48c4d42d31414cf2cc99af864e5067942c1a10164df22ef12879fc1fb338382686ba6bde3a964c0ebb1c2b50c3e7f3bc6e910eafd1fad85ba82d5aac15a50f3dec146a0164ab484fadf65e9ebab136673b095d231bf0980db362ccc3860b2bec19aaa338de8b2e6eaa057461b2cb5738337901369fb51b6b2d78e5147d02843cd4f860fda9429069b205891916cfc3631d39016e79196119839cb1e2a69043dd86f692855403f4f3f75e71ae9fe31d33c7135518198615bd6eb2228d57b179221c57f957d92c2371a7aad2b0faae2a042d51788dd7f01b43828c1ae5ef3343f03f7fa97b74be5640c0ba130ff082f955e8ea961ead811eb21408d081c3f442399d4f9771291dfc1c1b2923bdb26c2655cb40fa568d5b33c372bdb1bbae16d06b5a2b9f192ee7e0970a2fc755b0768403e662a21df45a36e51f7d7a6e06ded67f49f5641ce42e00f2060ad8832c", "0400000093e71a2420829fc0e52633ef4d5256d6af96240823f37c712b1f1eff50430a0cd42ed4f9c2ae78fbfc18e4636a9dfa805ccada07df816258ae39cdb05d82de33fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e5b1b405d0f0f0f200d00c38123c7c8da130a91962f83c03c48c5777d211e231610202b81c2530000fd400500740ce067ec8ae373e54aab7bfe9a2ad8dff842ea15551aa07e4eba47c17c7352ba29001a5fc5bd21822987465f355d97e3fddf445d59aba97dd670f511587fc563ffcc6cbc97721fb9007e5c7256c00e5c96ae00e3604d44551d9b9ea7d4269a68f49214abbc62b01a6064fb3b14493dfd40f2e906f8df7601a4b9b676037a86687ddcbf11f1905497305ee6c65a3a99e3d70c521aaff5a050373a1a7298ecd903f58d6838b98c0077812e18521fa147f591ecc9a5f00a1c415711c609cb00c741251dbd95c823c0fd49e0a10f8ab33453032548cbfb18626fbdd0035105ea611a321f930f152e754218715fc9a9865344abfef7e28ad51c3d2031036022902f99f557d2e1c3cc72f0f5cad2d69b8e5d14db4eff66dbacdb17f165045332d5ff0b693fa11013f06bba2fd5eaa0e002c383dd70ab12b40af748a47956f3e29f6e83f57740295d21dd25c2980adc250c010b129a77347e9bb97df26f7da95a6dabc1d20aca047196e08b91dd8ddd13d86778d5f98662553d637d176b01093c476ab93b76d367c2ca2cfe4cb7d5d2262fd7d4f4030f4976f3689408b7d31cbe3143f83969068a16814b1f14fd23c340880eec568e1a9fdb718d275c7a639ae726553a7e5a6c5b556e3b925fddd15611125b74b7378b83e64e83f8bc20da0d1152f44c042da1779b7416a3b3b43123169d6ce6990b99fa62ed01caef39d1dc995fe04a33f64077459dfcf61e6467221fca733bf540ffe49196ba98693476315f9625c302df4623e143c306453b6144512d4cb1316a186b380ec021bea4e458d5bd47b2e7c4b0aef54972b6939e10f2dd12739ae834e369c30b87dfa1be7e965b1e0120f66ecc11d9a2af7771b690da404146a22bfe0b912cd43e0518a3bbe783ce78f215de799f0692fab6dd699c7d9544e77ec1f6b5a720a4d2a0a61470b65929012957f00981315e584c2956196b8dd766f75eb62c071e8e62d25e10d1a85dc356a1c20c7254455b219d29d113ea1e112203dcc702a7d9c6d32e624f53882b4d4d5dca93142310b44075f588c63a4fc70cff9f9407339eab491dc3e310afd13ba37e9548daebae843610a66e161f475052c4ed79366cf28373b1c9be3e46169fc34924ec3c638df68522f0d6647a0527b07eb02e46dc9788cdf04973f7839edeb8158af6e51d71b602f360d1d70b1d6ebf11588972df856f773b5d0a2a0d3b2dc57e4bd3ad2d7fa32393a0edc8dc7d99371706c16beea2b5c8f3df0477671ebc8cde25f83647052ee6831b89507c26a7efb3c55cc32ba5419895a204053b9ae5bae13d514672498357d65982e5cc57dd1d507f4f1fbd22aec5ef188a3558dba9672f019df3cf1f2a8ce832e7d739fc6db3ff87f980b2bf237bcbd135f1f4c01f1c8133b8266a0c0b6a209f56619ec4fc02812740131616e7ddbb2089f6f9fb292b582d1c2f74647428c462f495562cbbb9116ec4bf3b55de30a103f26d3e70010934c9c0b05d885069286e4d480bfc19c6c8ceaed79025c43192df55a73244e6de4e200009f3c9fbb39fd41da463a65dea4f64bde28e77a93519000c5c4409eb58f121f9dc24d728c3123378cac7102b9bc2b1559533688ef7d1ad76b69e8cebddd8db13f593f3e00ae605f536227662ff6110c49b538fe3088fce2b63ce82c7229442fa13c1a52cc795a7e7b10f3772fe7c44764d3ca4e50f33ed7535cbc971cfc91bf6b70c82a935e5f3eb05c35f40b255f81f7b52381e61fd26724df09f57887603f8eb606f4476db205818f58c262ce92043abade2b93c76479d69e8f333363c870fa89b9c96212b2383d6b771924b6f61bf392d1b223b40df0aafac8fd9d4d0de73756e49ba734ec23311d9d94f783705c252157431f093319c9c7c7231"] \ No newline at end of file +["04000000f5f3ce14628bdea67503d18da0bd7f0125d373d0a41993f98da7ed6098161809b23d0d856d242a80dd68a8ec727ef447c1d01afdbb5090b8533a6aba43cec80efbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e3e3438640f0f0f200100d407508f2eb346339c90968ea002306f28149b3ac90d8e21e729ea080000fd400500fa3b4d07563706c8259274e317cad9653c97cb8441d80323ce618e5bc29325001bc2a0bdf891ff69c20bb604bb05e6731d9556845f004476e38b639f598633c431da48716423a2a3760204682651a9fcee1c58029d08fdad92b0d70c8da65f29c8c5019d285843eb1c55ca7a3017c4a9963fd3a5bfb16ec5898ff9f46f1cba694cc7876162a30667ce76d8b09b2f733a2b8b1e2e14ef264c52351ba5d2ae624f90c28997f602c102883bfccc9d40cfcd0ea5aa17748421b78e701b780e316e3be0105e2a99b833471678ea45a9b0cd6bec15b991295c937133cbc564c31ef34ca9740074249e3516a2bac1a5b121f89503b1d8a229a68b6c98deb917ac99e0ad5e6599130cc542885590ee8e3f169e6c1aaf2f39395bcd33dc60d3b06f5124360b22105f6821ebcfac3c70f8bbd85f0237cb4904e9585559e5703f0471fa5ad003a9f9b8960155d043de83435740ba026243167cc61e79f88142b6186d80a8f4a718fbae098c1230430f6c9af759a3e7f126a8ee4a209fce0a3be49230672a66df9691d424cbce1a07d657ff9e2c46777f1dc7994b29a0fc25ad0d5697d2f03bbfa5ab04bb7561f489d73a5e73a77899406d8a4e8e97d5e10bb98b65ea1875193871d1ff70476c88c3ec66be752d123dff671af3554ee8b51a74bb376aba013d64732fac2c962c96ce51550967bb5fbf205a28f352b943036ca639c5556a875b4481e499b03571f3f03c7cc2248ff22eef57dc32dac7425d10bc3e1a4e5816f7a3071beedbacc933b7fca5b3c1a57a2160fa8b0e4c1647bdf5f5d3a00c1dd945983bc74a9be216301a94950ef84c6b80cf8fc7f0fd15bf334b0805f85bb946b11c6a762159d5537dd795c2b84eb3ae0198d87a91d21d8c65be4f4d7aa0e9f5856472fa29966e523dbdfb2e4fecbe30948ab955d1c3177473841d12bb1770dd011091746e95a4eb5015f4e65be485d5a4305365fe01a7e7601950f6fac088f7ddd7f3a41a18e81b43350e74e44acb92c4a2f2de868eb5e37afb7538dccffb1d0016850a2e909d80d1c2d99752c165b2049e9c63072b29018784c3a655e1d1d3b81957170d8bbce2e6145c7d2ce551d264f61a12426f77465359845ca5cc0aaeeb0d530497674939e2b95597d1fed8a71bfd673ff4541bdf10b5c9a5d3960293473bff780adf03cd052602c61e4d8bf2ac7650de43675e3c8ef3cc441c256afb94ea4aba53dcdaf25ed69369edd884790c000eda00b3ab6b29e9cbbbf748066e6ea2ff85b957201c34126d2a162d799428465d83d53ceee5b29ac7c02296ff1f86131e4ac58bd8a18c6b4cf678c8daf32948334dd5f7d25058ca6118ee82e4d1b24a2a9853d740833a69b9d2a8dbbea79445de69813bc26dbe78364166ce8d1d187b1dc703690840714a2edb469b4cf7014b385eb694120dd822c07879aee9927db3b6d91a09b9e3c57cb4c3edb061d1bcfaff1fa1fd16dfef8d049b23dff1cd3f9efab5d9ad43ce085f32045a8ba716906e2b7d5073fb01527189f799f452bd8abf285d033f4b1382e4dc17e404a1fdf858dc2702db78d0292287e3509f733699b9c5f59b4febd55576b078ed9616be26d6cd1c90a1a9ccb17881d01ef0742bd741d2510362c672667fff5dd085c70ee6c1126dd4545ff901879dfd38cb25b85a14b41f1c2b338d2a367091281a626e4f6b1903478460a301f0bed5c523066fbb07041ad76497c5397b65d4c17c303d303922c1bc6360477a639577f447c9fd8f2b40636c929ee3451f708a0b602e1458d324fbf225a68ecf70c519bda0de98b8304e8711bfa2de73bac196901f4875beafe7f672aa0d687998cad1aabebb1da2555b9ca09b122559080a50f896360c60063f4c6d55f2fd5ef4861e5579ce7a", "04000000deb24f206135cb772775f732376235f83d42f5c569398af24f5eddf08fc124075f409ced37ce20818b70813dfb217e78f1eb6d10e5371bd69f01ec4342d8046cfbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e403438640f0f0f200400b2648324290b6e686d5ee6d313a08fb9bae7a3590bbd3885c04187030000fd4005000bf35480880f270e7a61b5a1fc4815cd57f3892402f4bb722eaa6dd9b740b634167ee6099705ce010f09f6b7ef72a3dc0d6efac3c1086828a6e9d0bc1e6d33741a58ef550749bf20b5281d5bbe96b16d9cbfb003bd83dad488879383b927325dd370cebb7039ad7509a2f8f9b9827d6d157027a7e7ff2d09eff2335ec210c6ba7656c939c0b3a1627ffe9afe929a66bf6d0e13df728dbfd2bc0d41de425d42d3dd6d5f6c7efc4400a9a4adbc5130a50f948081d5682c28a29d5a5ef902331985d17a1b41e7ba60d6d65905e7623ebc0c6a1a65b1715c94eab147a352034f2e6c624d3c1ccf991e001af650480a734133b4566eb07a8f0f18fb1ef40c8f35fd77e6b2dfda1ff1030baba321bb1c6fa154517746db1c9bb237c9d336d739e505425eed1c07a70ebda9db308f199bb8c1c22cb7406c3dee2ebe26de122e617622db5ceb089d423e295b5a94b9f55f89c70257f9cd8ddeb541bb7d41aeabc1a5be85ed74600604089eead3cf2fc6b20a30d283afcc014edbff837e0644d3657f4bdd70ab2b013d8637fef71daf9f2d1f06e2e271094300ffab6786ac37f1e29dfce4b54b5e02eb255b3a2089695213f49624285599c83671480b1a44c5997fdcbf4b01315364e3772d3a328292c1d40e1ad3969b8778fb59a6998c24f25cc39efc3f3a601656d5820556e8b57aab0307627cdbddd6193952da03fe2544f7d40f1b76082450aac4e21d39f7bc030b0daca78e4eb31d1db44971c4c9f18a749bd89e60080b0cb51a7e03689cca2015dbf93b6f317be22e31cf19ca69d5c81da1d9b9f522b2b67fa4edb21f7c725e06e3a07bdef65d6fe5abf18a0d32d5f53df6593e022413c1fa3f21b0051a18b3abbcf70f90f90e50eba30f058c409b20853fe74e1238219afe497e89ef32231221895abd4b600ce390669479421d52a1a3d67e380173727dd169cfed76a542bd16b281dea7f8191dfc435d778786e2ec87d027159a06bf639db1749a90d93bac5e79b454cf72b0145a0255e8d6bb19c87c978b5ddf2bfb689cc25bcf52e7f564d12f3aac7e7c42e208e0437b43894bacdcbcf5e38468d9db59a21ec7531a6df281121613715d004323486ccdf529268bc5650bdb040c76038c7af6343626b3c73a99ec527a81f051ee937d39a5ff4992eeb59e56f1b9e383ecfecc9b04aac60f5546c907f373b16cd1acf5ecb7696887f41264077e1775e817ddc5242b16e38f3aa4c495cc0310a40d63a8048aca84abb148f6247e1a7cb2f44d7543e79fdaee549dfea7a385ef38ea9f0eb871bb6f7a0594afb4e0197f7b1d26606fe2c38ef903be1221f319c80f15b409e2ad6ab8da7c40e9ab373d56dd6a4b16211c3290a0a0d75fa3a1b9f1d2a828cc073786d224093a89514d3027aefb12e73a9bc024e378471e1c01994d34b1e3bb1160b5827db0d2966ca4a3b1980405d64dc50b2e32fbd587144474ffb911730ef502d310e90c5e2508efc26af6867684bcf529ab77ff48c8254e62a432d8b8036f22b90fdae9489efbc6b8cb020360ae3e924419490220322d00924d5d5642b1688521958f0088f99147ec5247967ed7f22a6997bf731c08ce1abf1adf91314601b5662eeb6c1696d0bbd5d325af762c3fe63881636ff3e2884c3d31f850d4eb2102bf4083a88bdae8930cc5117a2e9e89b65c16ade504953ffd892e1fcbe2d9d0d1e7e1b5bdaf891e90b8200dc14789e499835e6245f5f04a1aaa1bdf51767f2dbfbe4ba9cfde38f22f04a9c3410f39db5d59e5ce0560d82c6b284a6379bf340e56e7a21145949ac19727ce6f3814acc4fdd65d249d9672000b2a1b7a9f510ff16eca1a86eb0fbe5dd22a609510952ce20a69db23b6fe6ec8cc58d5697a15bb69617b5ed1e6f773d5", "040000003d8e463b9e23605948b9d6c7cd995da699a24b083e275fd405f68a3d77efe40d6fb904ad0bbd4bb54fa55ec870cfb1497187947fac23d67e00bebdb110dfdb18fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493ebd3538640f0f0f200200a98712b32a2e2354a603ef139abe8fefa3f197b6fe4b058e44f4a4360000fd40050028a64fc00214793e5dd43e2030c5755a8ed8297c0ec35d3f9184849f3f1f42bc44eeee1552446bb38f1124668d00d33f0fd39f0692ac3aab26ed8fde49ec172e793b9be228cd582a61c1533f6488f7d61c4c3905786268eb96752b12a12495b3f9187e9b691ef0da08f156a267cb18947a5fc69d1ff7ecc9dff4b294070677764e5f934b635fcec3e2662358b51d5dd3ae8237001ffdd20fc75f55837c61d1e383435833fb1982015be9e5510f4ed6acc824812def2d4a7b80ff0dc6239124a8b592d755bc1a0869d5ebe67673ee7bceca14c26b353f07865b19f31348efd48af0f6afb522d423e11a8a96dcaae749ab23355bf3ddcd103f35651e0417105d9d03cd2ae211187257d05de2386d33b8dd140b97218f231053eacb5278882915f4dbe6be0e1f04ef0be5da49080cff2aa414c5c1ffb6633db3992a0fba1bfa27b32af7d6aba18636a778196913fc67a50178558b1846d8243867a4a42c3c16b138257939cd2d2d6194d35ebd6d66f0c6152f43f469be409ac1b91f7457eeb4e3e489db89b7fb225bd5a2440636157e2fa886daad2197692155391648e8f31ae9a599cb69050a745ab2ec299be8bf73d0aec9e31692fedaa0e32a7fe6f01e1660559c1845fea3c40306055c5b2aec1b04114a73913a2bdddcd3c315449d8a0b98397cd021fd01f6cdfba551fff6c2bf54dd59630adcd913e707d78fceb1c71cb729fff4a429502c013232b38e2a095d54e4019d7bc5d19832284feace68d19a1d45a712106124dc0cdd39a1a861780aa0d634fc53df83f51b112c4fa3226c0fd25cc30b91b5e454eab3fc90710a088ab8bf06bc9c99fe02a72aee899cd952d741d03d636a2da15c333f40a334cbcf565b0553cd756a790bee9d84b81f81511637b0c8ac365cd90e1cde7fb9215d43dafd55041dcbcf54db6c545a1e801a7ba3eb00d5f98861eab7f9becce64d1c4fc1e638b81a2a820aae7594998301752c3ef39053221ab1608dfa95bf024dc9116c08d8d2b87db4379a2bbd291794f7798b1bb7e8e95f4a203a7e3ae46eda2bfa3a1fb6bbcaf60d9a64a4c94775e5399df1407675d3da6a127852491b9246cacc6a88f7cddfb27d67a66321d15279f6ba322f5d1b9adf34e16e81badc03f3bb6edfcefeb71d579702f130561c972cbdf886b4fe5bdec0979b325500f2a349388a8063a9e950e6126c2e70b88870892e3f062b92255576cfcfdca69bea483abf55af7c80630d21426e2408ec31b120c234f2c1dec5db1c6f004440ced2e174a4266f65bbd50b852ca45f0dd53eee6801b35a2e4285efd26070a16e8ede05145fdeac70720b3147b310300639d3c46173c7af108dd4c639bb281d88afea6534290fb3d6a2b4c0df7f45094dfae05d47872f2fb395b4298cae4519d0afbfa97b15ebffa501bc8af9bab59a8fc26cd6bc4fce6a434fefdd81011e10df40e88819a593bcc4ec163e467a0b7e38b8e10848b73cc14405aef56db113a44fe633efb9ffd850128c0ca754ada56d9c0a5860b8e7b0ca37ab57a266061b4f3a0bdb4619ee78c1049f54e490c80f57719430959e7b44ac17ebf364d744054265f6076d1b783b0d9d9c1c4a0b3e9d3728023f72d27f84e2343dc13c41001c9afff3b633d97794e836c14ac65af25d3d4603e3a6c40045586b3c4582c29bc3ecc8f20472bbd10ce3c973160c69a2ef7f6248bf187315d861d9a4410fe0295fe009adfafe7337501a73135e1bcdd2f10340e96ac781e7f8bd8872b43a5d77499d8d49746a410684019ad38506f09526b502043e5396832c58eebd2839e46744b54015d82c963bff6fbb77143379da950f1d25e06418e465d9129394e7a2ce6e5a8e73bb4311dad975fe5bd0f1a42551eaf3a6262216c17ae02b", "04000000184f38a11d0cd053fbc4f19c78d9091159bb253216fb3d2e1f191a8450aa07086e6a9ee380a689e496bcf8abad3ec0ce470062f11b12befa4e27be88e4bdc28bfbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493ebf3538640f0f0f2001004bc00f4a51ec7b5ccc6a0026b73a2abc353e3be5fdb330dbdf5ac32e0000fd400500739045579f5155c92b95249b42757a33b01765e86f4f7d2fa0662b4b8f6b38d9e4def19b0f96d8e9f118e7e577c80dd22683cbe598f5fc4c5657c1b300c42c14e47a5ad66eb50c93e3bc2e21e5bdf1fe9bff2b00f07c92c01b664b475c5313bd3f2a2e4bdcd6e3a208354befff126372ef6125d7355b78b593b078a6de0660c94197aaaf15c3c968e1b850cb0eca6e79f26c1f7322bb712cb913dc14b20707b5fa9d3acbac9713010631d0be0c83f9e2bfe266f89d740cab0de923bb0279a1639905b9810c67150d5858ea6e101cf395f319effdafc31b9cdf6952e579efcdfbd56068fd7c9527c953f2aa343045e80402e0f999f1560d467cb0720d59019efe8dafea796501443d51355ddb835c00461307fad13999bdff4673f3777e3521d8fb71bcc4cc1ef9e2f852cbc8ee978224d13854533a964517357326eeede63217f734dcd7431ffd1f850d3d8b5b972d00a4aaee4195b574aedc7970a8f25dc353a15ca895116aa1aa804b4cab5289f2d0d56aa2f794e4df68b401fd214206c74a63c343908a8fe876006b3e97e8481b0ee561004ca65d6f6d42e70d334a1226465c0f6c02681f6814c15c7c4398b626c3e541da6eab344dfd02c72f092b4468f6cbdce411a14cdc6e943e95caa71569cf0ac2f4521baeb1536308e41b3cfbd62f60de27aacbb6475a357576e0f45b10afa6cadd5d99f92f027b8152a25894f1796bc043ece4477963627261c9200421bc10156979193be571f7de9075e69575e0b304a3d958f7c28eb7098bc4a2567947c146f28d2ed906284384fbcdce2bf59c991399ca245aca7afd5dd71363ce996721cdfbf4df716c449dc3aa212459be612db22a87b82668af96a15471bfa5c6091f059527a0222b4cb5494f5e8f247064b602db7012e0a157f388336c5480ea2b36bf746fa3f1bbad2405dc86756afd0172574400d9cb97e3ce859d6b7a9d918e22be0782093b231791478608788ec47c7d6a450698983f645812349d4f01ef19ebd9efb2d68b2d670fc16a3e20d72a96cf21d0e366c938a212dd34e6f315d9bf99198b0abe59d0e64772c50b57a8ee50d9a8572a669eee94244b0bfae1782b5bfc10caf256e8904320c8fc2c41262053d0ff8eace49d2e031d72e05d11361f8e9234602e0b559b98592725797c04d4ee65b314715df555054662cc39a7b64d6176910a8fdf305476e8444a0e09755a16bc25fb1b63fc679575d90829e9dfdb032b08b6d06b8b54aa38e433218ad76c5b38dff95ef0b91c181c3a44df124d682031e9bfb40479dff2126bc91f31e5c6d5248b5542a646ca3779bc51c9161d09a63c43ef0eb96005d33d66448382371b369afa38da38205d1291ca8ebe848f0fb79564fb637e030e1be4b127fcc15e78e44e87a182d52de1558542a7cab79e050198a50e1a99f4e99798b0651bc6f4a2ecec9e27ab06e8169928ca5948f9b841fc04516ed9b420d5fb7f02469b13c3df3aa799e010a15a58c5007edc341af93cb5bcbfd9e052bf08c048baddf18762623178345f1e4704da8fda793d317212cf0da217acde3acd993d2ee816d1ce10c0a1c58d94f65b67b06ea624375b932491a1c862265f2d65ae7250a96c1d6d320696ffc02f3e2fb82568516b6dc51488835cd1c196b9d7825c1562239a1ee1e63be84fc1c34cc249c31dae3a35842e8a2c13739cd8c3993a43d403e25aee24f87279b2236b9f2a4f90c1d146e39507ec3604368170bc01972a9902899ce4755bfdf804235fc890b53fc69174791922a286cec8031e961a044924714f417f3a36c96f26ca15e4b45baf8924c054b97b3158127cd85cd550239f767b99cd70e4ee9db503c33cf33f434ddb18cc67fd8db17f9cf1112a3ec7e17ff57a9e41c737a768", "040000003bce560649476775215711164ccf0b8c7a4cc6e8728eaaaa91ff84ac6eebb3067e8399f831facfaf6995df9c1d04d2acc3b78665ed01eccc82d6839e500b27f8fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493ec13538640f0f0f200200a5a291883ef58866f48a504d25ad46d40c241746310e15a2bb6e08230000fd40050010834fff74ccf1ae9570104c099dc0d24f78707f2a861de74255911b483ba731d8f69eb323b37e072c0f32b8dc567639dfcd4046a69c655c9e8cbbf767e617a85ada5052bbf1ff525409584a97827bf63f89bf04fc0748ccc3b4c998e4d3f614b3ae46f1c4bd31060e548dad3f89d34d0b62322144c61cbca1eedeab4906c16c19fc28d9f16b8918e2f8f74a5645d25a47c21cc3cdd0ebccf780f3aa0321839999818c6231609c006f80f0c9c83dfab7d3b1e8d5c67f44ec1832b9211aab4e7a68d2c5f50f04d86e5e5334ff24969cf4e2051aa5adc312d9a3f2cd9442ecc3cd72aa173664e60b3e24ea5e845543ec9f5581de4d940d70180f53fa06b620cffb0323782e07a12a6cb2c2d503bb162c5d460cbdb851a59783d1f30891667dd0c66ff37ee90b0f8f4ca306630789ab01e40eecc20d02f2dabe17fb48ba36635198e337f65a35040c79d425c253341de202524a744661e157589021a16dd449a0d7311229e00d5e19f1df0a34327dd4a12d2d63a529007c5c3c1b1a5246df916446df3c152316c7f400c4d6ce97ee6527c39fc1d5a1adc9f91682f92be6d0bd79c9f3eddd07d9cb7533ea3445f9c3a5a3085133264c0ab8a2b3174c64ee8f65657bbdcb94a8abd2f90ebf933c07ec22fc9380485b91fbbbf936047a449e531c94faabb6268433f07f60e5ffbb16b30ace6dcc614827f864e00573b9709314f1f77d03f19e0ea148b0bbf415e28d37c9571d095b8381b23b43b6287cd1b5ad3cd8ae9a17cd2ea0968f4a3776ef4451f5f01aed75175ad50b1b3bd1451f1e08f1a4d3b39516f90e1d2e254ab51508741c46d444ec3eb897142035bfc9e9b4f26f422c1dde6e1155659f57fa27355d3eeed82b5f75fc3d94166e69ce3352c043324f95adfe7a8f8770a19e543c7e0cb6e2faebf6a78bf57811797e9142d285da660a002de19d248bb129ac43f6ecccdd206b04c69c921705ab647b419582596d17c39b9c7b16eee124df813105ab3040829716ecf20ce1bbd40e7b51a0a20e5f053fc2b32c116b4e8b94837447852ca79e9e5878978803fa647dc02dac25f343d319fba94bbcfd5d3f90b229bc06bee5268bc9524e33839aee4eaef888dc0f9a066f4e0a7891e791ecdd13dfb46ff2298d1cf9f15e519624fb60f46019bf63691f8d599dfb03b71b2cbe00b2f3d7eed3c4e929bd103c7363fc4c13469719244af037daa8d59f17d58a78dab368e6a2432cde08de1365ada10b660963371b042d09343df331a23db3d82755b5e8724d4560925574a430a6764193e15de384047e5a7431ff1bfff92ba94ff1eaa76ad4f4fdb6b7141ae354383959ede373e654edeeabd5ba741d447e3193f2e2370f292efb3a3356b12281a2158a355826488432a5bc686cbdfc4877de69defbbaeb9ef87b16029366544516521d1cd3703eece8c705db307eb65217e78416819b8ac7cfb259e3baf2914b7b9d7bf4a4065d40dc0b668793eebcc29da2bbd2214c88bcaa36207fa2d2b7249843b702f2376b1772520e3dd4aa2004b4572b7476e841fec5505b2616408273303d52aa1476acf215945b016dd892589569121e5e16b587d61e7196f0000b0e98d22dd2c01534ad31bea6f3b8ec21ee69d7389ea71183d1251661da9572a60abdda7f02f672ca95983cf3a57b051f4caebb495bc370a38b055c3412074a0b986a67335eca20e0be1a35564c46190cea01002b6fd7e4e5f304133c4d4ac30c7cdd9e1c241671b06189add44084980a6c90e5643f1c0fbd0b243eb2c4d73eeef3e0f1167290895234db7ed0c71684d40291d96ae6eb56355ba2d585ad80083528d50e6ea76114609d797d66170ccef7097e1930dfd9cf1bcc675db699feadfb920599f27a1b1697581fd15a", "040000004d3d4422255000b02bdf3899919925494fc4e594d43ba9443556b5aa528c880ef79fbf12e7997e374a5075fbbfcf3726a379fd931ff2369360d7d0655ef8b5d7fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e7f3738640f0f0f201200918af984a0fe32cc5ba71701bd955ad808ad545381a731590d6f1efb0000fd40050267fc1d4dd224d38b07e0fe66d42b144894e56eba147d9690aa8a258f2201d545e1ec7255cc833e1fc225e65fd7d22c64aba3cb83acfd4c6095b3cf4e91322cd42f43e78d97819ff048a558d9e692be47db8fd90bbc4d075d07ab67c6ead77ef13fd5eb5ded7f22c71f7ee4abf9545774aacb33223caf7f9a1f44dd6f901a9107c98da06681cb6a34b673bcd7e95fa798a2aa375b567c30559a810276751685f6eb36019453018f060cf12b2f1a3c7afc938a3665522b62d521b6f5bb2aa94a11376edb0fbf7e349f93e93c093ceb6d16582d16c3582b180953eaed79282a5ab69af7845fa5e947a8ea4e22a3f81d7ba245ee3c7cba124c1793ed5a094c26a457d9bf73503950dc83f0f1709c0de8b7063da8ee951cd3b9b6fdce44be0a47e6ab3f0d9e8ed1148ccd31bb94386b822791ab791952c55ba4773c1717b623201f0b0f7488b9f515f3d324fd7d005a29b705f0adbdf499d57db76204c5c6c998cf0f1efe790b29410cb6a99ce43151373512a1f20a09a0a05836a713374fbae0c57852af3d85dca9bfe75efa897cc7fd462a43da6fb09a8fe753461f3e4f2d7a86b87b976909630a3008e66d734f297780af6dccc215c85e1ec90979f69e89df6477adb915b2ede1216271369c68ff25a7ac1dcba046250dba554764b48b839e12bcf35c337f3ec950151ca50e60764886e0fcc5f349fd07be09139c1704904841205f06df54dfb31f24409b790b3f946b48f79a6e6bd1ed24399164bfcf3372fdbc781e2e1d92b097eb0ff1ce88ea4a7b486711be7ab2952c309a2135af414b8fa38740436a0f92b54e96c65d10824f168e5c3aa1262e75f0eac7676e3d32fe328b21537c7d150a88e7de40c26e8998374f255a3cbe772c4f0a46101149b7b46044602b666a55c85790b0e942d67b7ed619e9b549c5b58fb9bffe7a4c899f150c041948dedc19aba54e92111dee56271d7aa314cb9c1774eab3c6c852aac3bc192457ebcd5692949b18b027ab9c764f587bd10599d344fdb48a7ea7e3fd4afe3d83acfed819cf9d45072ac5c9e8897304b83a438e0f5615ae9d9dd45b13ce7560b55440b95becd1813f29e1639d9c789e87fb48331c65df113628e613839d184392afee5b6285d288326ba94d7b72aa7ada6d2b26f84ed9541342ff9245e4cb59d4739970568f65ea04439b59f68bde2d22be120be77807ba0fd4d2b78e0718e731621c71234b8c151968e968b9dd83fe9fe904af7185b7c53439980ec0c48adab0e5a840f6c5720daeef36f444f5d45390e2fc71562181ea4a598d350472be43a01cdf2d241a23499757b5557f70d1c11b114cbff670479c0f72a845d00c73098da3751932e61a7f56b11bd5993bf94db34505cc4b2126820ebdb837560c201fa0f7b77378c6b905bc352aed51bd047504618130dc207a8d3f1f33a5b072402b3897be4dc51c9c6f4c3e69dbc5ec6e47cf7bdd8d6a7917faf7850a4b1b3c9d2f0425c56a82359ae411211dc23f55c01e3dd33e90881308f4aa22b0823857b5669cd727591a0f7af2aa4b33d72061855986337c9f3f0adff96556de63a94b9be9e3907cda605ae373db67881c8a183dbadf0f0c17a49153a3ba0e94d6091aae2a1b3b5643401d6947d6f967a8871c10ca62c157820f1d04d304b2f2db7d1020908651a236cb9455a3db4b3f5d4b0b8f9bf083fa0715efc052cb5ad910b65ce0dc8d7305f90bda32548e053bb875c2c850133b363dfaf8a92dda8d743716a96500ebf35f8a6acd9e20f13539ea0d4114a0e2a73dfd808cc1c73a608a7c93db30175514567b60dfcb7dedf0d464d75958ca4d667dbe54ba1411cddeaa53026efe0bf21acb5048210aa5fd29bc166b69cdbdc8d3256f8615d551d096a1c53b932497", "040000003328f9649137cc4d480bd7034873db7e1cbdb09c17258cf1113df0bed93e1a00c7758178385f529f44d3c45bee9e83c2ea3b90991ff3bfb18542c74af6479f50fbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e8c3738640f0f0f2001005f3387f273f0bb8d3fc47309bba012b74de419910a4b4aa9734956200000fd40050008ad3b36005a54f860417ed124c5b0ac58ec907e01d0d407fc59b58730dc911dadd711c970625858a72b501976b264c633b24a1414d8da48a516655dff6c4086678ef31af356f44b3874f8f1cb0e6279548abe004c0e2c0216fdb2e6dbf3cf454b7e9e134019ec9c24b235d37bda91699acb541056f2dc25febef06cdf0bd930f9a8c8001df01276ab89564cc2cec5dc0f3f1d8face7fa652db7a090885f366d942a1aedde580100f911b3e51f325100229109ae508a90af5c5b5d1a0ce48932a1c8f28c9ce1249ca4c2c48a125518f54f074b78b27248faf7e42ce1a079cf53310bec3b2e5c230b45076ba47d775b8d2bc72a5fec2f0fedbc1812105ca2d8406516abdb4464406aa316ddb098dccc14163ca4da655a6149b71c624874a6a7eca8df191ba0119b134c9907db5b1ef144038229a63724593b2c1625aa675fdaf15603b168d49e5b3bea7dedfa0fe7c8019ef18c6843305e568c10b7aa3349315b618c1c362000aaecf84b0b4ef5e0644fdeb2a20a73d03babff13434b6f01adc2a1961ff3fddfb87d76cea1f735e6490402bb05d3342becede5e7657a482ed47bb796980e656f764290343adf54d5cadcbee2d9b989bc290c1410eb514b22c2f533f5f2916898a3610a78589035123221ac2421fb4fadbbc250852256b17d31df712a1f9a81b8c068677bd7f3a736667573a30956be983901e3b4ec732aa28d9de8848880da26f58358b4c0cf232e4ed7599c012f82ae7a596ce7342773117f8a2d0366f707a7188b9dc74806ba22ce1b92554cbceb21093b41fa9918ea4feea1b1be857219a976d057690602df8d0da0c6467febf151ce683b1db156b492a12c10fa8fb84b2ced8fa54448388345c2f7248aff5e5378a086d8175e2e23b4b858b4f75a6e26ddea9712b78532e7f25ab3b57df01148ea3ee07616ceb97ed00700d0a7dd8a48256c6f996227c85d634dd4ca108d3f0d47c984a59cc3010267b49cedd721e9567032fcbf17327499e5c8c5bf39a626a5785728624ba1b7a7eb272c7e73c1201209a2e9830bc3355330f5d7113ccf024e2ac221e975719c6631756864f5f493022838a14904cbf4bf59baad0b6108d400ec926af00d9d6a800a32591cf5943368bfb7a70291d0a436191331640d4790a36d881d9bc36f65b47ed42ae6e26eb27987430680ebb32d648d13b6d6e5898750971d7120979aac220bf715d70b0c1b3dbb52ab149a7f10c86fe9d0650cd43583620c9989fe958cfefeef58fb9db2df59cb192f56aac6abdb316a7d07e215da4c022af4b1f32607eabdfa82853967634562e0a138d5c94b517607e86268f5ae44ac1c2bf55be90b43e7e3fa7d7616cb4a105a7d395d4a343665049aa1e0717b6bc47ddf27a310d4a9668aa12105fe78c1f9983fe7c48f53cb941e03149d1e57d836c6e67874dfd7701a4f5409db0db20f8f5501079f1badc01d0986fdecab027a165c7a580b3dea655aab7781da3d810f93d5d1ccf6b3315d7633c17eb85d5a0766ddb9251b42c6d6d29db874f39c0851d568ebcc4f58a7f9297279ed0e9ac4e059a352197b254407dc0127c4f782e247779625bdf27f4f5414506b1e82a3098d2d51e29e2a95d35e358ab64d1f28c4e53e40ce2cbac0fcb6199837001a5eeb9f277508670649b0999183679c340b2b72aa41cafb5caa4a54dd63f1a1dc344d133c77802ff8559e945ebcc67b144c17f5e60d828d4a79015e2f0f03ff343c9d226e68b5534bc7e9f5937c07c694ce66b81f3de79f3e8a126f99a14c25a9c5a48e25868ac14c2a625bb53eef3178fd77885da2afbd42c40186c46981ba8d97e8a61324527fdf7182f5f95fe5e599f425eb31141dc698224c15274909a44aa9266238d5597fcead463b53bf", "04000000a5a2a5f7dd25f88ceaf05d539299694e682530b429cdfb1d9a82a4c0f0afbc01ebf9eebc6a81e844ec6f2ae70b980a84f689038d24e5823b4ec00922a87fe93dfbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e8e3738640f0f0f200200f2f66db195e06cb5196b7c29033a9e695c16ca0ed417e50a329f952e0000fd40050001b4f1c720b89d876621f1ac284545ebdebb9a8a03c5f54909a801176157b1fd3f215c96297651bcad1435c64a985e4b373bae02a94ad9cd598afdf409e71e293ceca108c899826c54ddc36a6def04dd7f5af204af805a73ca84a1e4b1e42e0db076d20cfe768ded0821aff586129c3aa5c593cbffb2dedb2d88bbae0126442b90fe2362a3839a65a0216b719e38e292f2e72bd2a67d1bcda244ca30a671da397d2dec7cbea73e0079d96115416daccbd043bd40ad5b6ebff41aff3c1b3cf562c8a04ad3b5a9f2fcda1b61bd1df2fe2dde0f7d2f49ec70ddefde16e122f2af70412fbc186ce31dd363d3c4abd0575fbdd2048d598d294db9bdff190f232f95f5d3701ffab861b10097679b05031e4e7f1da0b4c4c24bc5faec184314efe2ab255a3cffa5a923ba96a06e67f3579f2c654b74ce81e981929566e24236422d2b18de3d5cd8a7f38be10f6e751319c77300d33bf37829243dfd3a714ec71e1a06b65d5a728b2c072b7f1b9ddd99dba2735add4c08052b24fd15b50a8ef186b4388217dd86c16e266054f55df9d4bbdb0aec80afc7f331f9fe92b8dc7b63ca4701807c431f0218e92737ed05919e0fc2fa48a11958ca7af0a09605c9534873db905de142a0b472d19a35335db34d7c13540251f6cdc498fc7d956d966068beaf95dae8502d99d7fa90e10235dd0334b0445b3bd9408119f7eb0838b5a1caa087bf05e690bbf994302971399de4ae0f1bfae91c221a99a7e7c360e2f8689f0f8eff0bf7094fe0b64714ec01d75c76164f4181a2808bdbf34b2e254cf3b326e567e935f3af2958910ced045714422045dba4fcd5dda788a9f666e756b63dab98f928bb4c5eedb3a01a9fe9b4bb353a472b1e820314dcf15332768777761b14e762d345c8ed578856647c17db0758d17c2d9bfb1983fa4c45b3cefb3625d86e9ce9eb0018e7af3d4c7bc6e9f20ad6d8f8bc0f1faf3ea6d913315f5ecf48fd2253a8137aeec99366f338fd78b430411fb38b99da5da93129bbe177d98b096998a54d3f7316c06f2cd161bcd5e51dc56d6a9ae4cef7627d0fa900f2ec88bb676c3e0423fbd2154a1c0118fe5420eb396aded10d4ad1aaf448aeeef635c699cf0ee81d998649389a15497e55620f0d2720ed809d119457241ad7e6da97f77b3b6a3886706579fe43e5d62c6612561b4e7320175bc23a21b8e9965f1e119cded4811bf43d2e9c27241584eff2c3b4f536a8dbca2d2e411976e9915b8ac23d86f04408bec3fed128628d220647a7271925d353373b35969ab66b5fc70815dfa0021a9b6e8ca85745199683621acb259b9f36707fcdf04c516b22d418de6b988727ca0365369a97581a77d61e4d93d52c51e5a9b107f4970d74b10a70a1f9a0aa38273f2cc0101a1b6f2715437ec57ce669ff9cfb31019ceea92a527d8eafe432a081e913eec8925c912c101dc45d0ddcbba141b8d4da15feae45ce1bfa7d5e1151fa93d4ed879599e2322164de1f0de3e936194162463cf884f1edefe71de689f4776ef5d2c7d4d6340abcdddd10888c97293c2633f55ed20f42de7a38611663ad7b8ce57a173db1b342154c79b1cadd14fbc80aeac646580b9739fb2491624b140abdb21d7f0349226835e56d335c17f110e52119f2370ef1e03fb3b3030fab659b12d75d8e77f42549af8dc5f8f3df45ba1f3d5af53312aa97bb2d420d05e2c5fcd0afb89d3f08eda146e84258d468baf17bd21bc611a62b7aec96222854c1aa4dda357de3e2ed521ebb5e8d603989320d84cc0b0c0d315be57fe15a589230c873126fda17268abd33e1afd785eea5f59a86ec7e3e36bb37c0350db7f6f2d883f8408542779bff4e84ca0e2c7f834f0f022408f789a9f0e50bd149bbf1c4f892779d9dab", "040000004b108db770d33e7102052947b4e79d6c059b8b8829009a1ce52fffc9b988cf0c5825051d93f855d0d5b796da37fe6ceb53a59b5044e53295182584b59bfd643ffbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e903738640f0f0f20020064afd1f8b82d60b3b1fd1adf4e6f510a3efb693fb251e64067c09e0a0000fd4005003faf29eb03dd828f4370668756e5891f215f5b24100fbf18b8a70279ce05810323d81bdf748efeecd10b20c7a9b26bff3ba98764e53db4334d49e42edae812f388b99d4cfd14c78445f53fdfe20eaa52b7a7d2101974c964cf51f4a45c51f5e4b0a3ca95b879a32623ff53ae762f1c47fc36a77535fca43362b87f88c012b259d06cd05197ea6ec4baa8f2d2016bc8f0e721489e2ae1d217aa819556048df3f5243d9894d9add1030e9949ad52d8934d3e43ce8fc1eb819588dc0a8e0d6d0d2358dc0b5cf826a3b8cd630561598c39deee191a06aaf010b45768bb55b17bfd5469e04a377d721ae9ada2d74929b9b85cf5e1e9cfa63f63addf090d0c12ab9f2d056c71ae378308a9ebdf013d4dd0576921ec0b8b5f965e3cc8f28542dfd4e1e1efcad1c6c41ad51bdf65543650d642e2ef2de6cf8155825d023d1e63e11306a57abdbba6f2381412b4123d82b60e39018ccf741bdf679942e6f774f9e9431b2058fcaf9b18f9c9218a47ad88b84a43879a294e6b3bdebd42721800ef9e4760688bd2aed57800d60fae016978f6121ad703ca1af012d9ea7bd6005933084221f717460e033af6ba854231119303d67429ddfc06922b1a982a1bef57949ab79b99ff5f120da1bdd35a683b5f638306e348df3843667ab59460a94b2d9334774a5de00c289cfb91d71575d19e61067d4be7595a7c7b9b994c03ff57871d4168715292d5e7ce6737d2f4301865050bd14e64c896d593c294a2a773e84902a9e6f7be200725636365d4defb883f7075cbe59ea07f2fdc9cf30aa8befb78dd5f71aa8ee285be7f7ac4f393745a7408adb8d6ad8de031be53449ce852d5018dcf6fa9d111de0add7a84b392a4d4391c05fafa6ea1ccf73ee22042352102a4f121a7ebd39f38cb392971ca6d53b827ffce908db16ea38ff963c1efc6ceccfa627576e70219ae2f4526d30de0e103dce97878359d435c48951b54f2d483e2e4e56a30a2672b35ba35bafedfa5b1136a57b3828a3d09b59e06b537c0b7d9e4323ad4c4150f1a3fe5a2edb3bdce51520cec0bc16ed94cfb721445af21bf89a2d6b933e4fb31b72516526b95a26329480eefbcdade55deef52ea3e6a7add9fcc2e5f9e2c330ddf51e98b336cfb83753a4a73056669fc01b92dac134c16e50081c313137592e3edb928f7d66b2f03fea2bb4c6ba225ac65317b5854a422a1815bbd8818244feb9e58c29f93e8863b4ac663619553cffae11a3e44d57753b15ecf7551d32ac2acc5d554d7ea453a826d9cd119c42afc8a08d0c1d9c83f80c49f631d049c1d4bd789916af81e76cd2345803b21f17a500f0d1717a9eb4eecbab2ece25a32f917a677d775a6a919665684298963db3f257375d6b892d6178313310a2ea994d840de6d49817337804d5795ee6a455dfc42028678d8651bc3076c9a725412de1a5cb97dd98a0d0f2d3c05378a5659642195b65bee7cc6baae77ca2d1244543938acbc2d83cee1d5ead0aa8e6fd73b55a8257db26cdbfa9ffbd8f6646aa66b81570f60fc5f12081c199d240a8bbaafdb40ed4754595226ff14e19b465c2a80426005bb004b47c27ef7881a1afff3b0ac09d4dfd84876a025d1db04d07a28bf5d48694c019b23b4ec3f3824dc91c0c2d3983251ae51dc9cda469103547e1d3f019d8f086c659e783885a64ea95e921514471556ab59a2d7248a471b5262058b2b17bbac650b5296e9f48785a9bb69c58f29b574a9db6fef3e0e0db95c9f00db471d5daa6746a1dee8567ce69aa4e708fa114c2f44d14a8ea0a93c06f651e2e7ca1cc1be1e3c43cd247108dddab9225b51aa8aa5d665faf02a1648c74b5ee5fd55dd1e62b58fca62c55a05d47a8517dea650861160eb1ebe453a0151ccd1a52d175aa3"] \ No newline at end of file diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index 246cadcbdb..00410001e3 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -15,7 +15,6 @@ use common::PagingOptionsEnum; use crypto::privkey::key_pair_from_seed; use itertools::Itertools; use mm2_test_helpers::for_tests::mm_ctx_with_custom_db; -use std::convert::TryFrom; use std::num::NonZeroUsize; use std::time::Duration; @@ -205,16 +204,19 @@ pub(super) async fn test_electrum_display_balances(rpc_client: &ElectrumClient) let expected: Vec<(Address, BigDecimal)> = vec![ ( "RG278CfeNPFtNztFZQir8cgdWexVhViYVy".into(), - BigDecimal::try_from(5.77699).unwrap(), + BigDecimal::from_str("5.77699").unwrap(), + ), + ( + "RYPz6Lr4muj4gcFzpMdv3ks1NCGn3mkDPN".into(), + BigDecimal::from_str("3.33").unwrap(), ), - ("RYPz6Lr4muj4gcFzpMdv3ks1NCGn3mkDPN".into(), BigDecimal::from(0)), ( "RJeDDtDRtKUoL8BCKdH7TNCHqUKr7kQRsi".into(), - BigDecimal::try_from(0.77699).unwrap(), + BigDecimal::from_str("0.77699").unwrap(), ), ( "RQHn9VPHBqNjYwyKfJbZCiaxVrWPKGQjeF".into(), - BigDecimal::try_from(16.55398).unwrap(), + BigDecimal::from_str("16.55398").unwrap(), ), ]; assert_eq!(actual, expected); diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index c9d823898f..265b1d94d2 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -42,8 +42,9 @@ use futures::future::join_all; use futures::TryFutureExt; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_number::bigdecimal::{BigDecimal, Signed}; +use mm2_test_helpers::electrums::doc_electrums; use mm2_test_helpers::for_tests::{electrum_servers_rpc, mm_ctx_with_custom_db, DOC_ELECTRUM_ADDRS, - MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS, T_BCH_ELECTRUMS}; + MARTY_ELECTRUM_ADDRS, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS, T_BCH_ELECTRUMS}; use mocktopus::mocking::*; use rpc::v1::types::H256 as H256Json; use serialization::{deserialize, CoinVariant}; @@ -145,11 +146,11 @@ where #[test] fn test_extract_secret() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(MARTY_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(client.into(), None, false); - let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); - let expected_secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); + let tx_hex = hex::decode("0400008085202f890125236f423b7f585e6a86d8a6c45c6805bbd5823851a57a00f6dcd3a41dc7487500000000d8483045022100ce7246314170b7c84df41a9d987dad5b572cfca5c27ee738d2682ce147c460a402206fa477fc27bec62600b13ea8a3f81fbad1fa9adad28bc1fa5c212a12ecdccd7f01205c62072b57b6473aeee6d35270c8b56d86975e6d6d4245b25425d771239fae32004c6b630476ac3765b1752103242d9cb2168968d785f6914c494c303ff1c27ba0ad882dbc3c15cfa773ea953cac6782012088a914f95ae6f5fb6a4c4e69b00b4c1dbc0698746c0f0288210210e0f210673a2024d4021270bb711664a637bb542317ed9be5ad592475320c0cac68ffffffff0128230000000000001976a9142c445a7af3da3feb2ba7d5f2a32002c772acc1e188ac76ac3765000000000000000000000000000000").unwrap(); + let expected_secret = hex::decode("5c62072b57b6473aeee6d35270c8b56d86975e6d6d4245b25425d771239fae32").unwrap(); let secret_hash = &*dhash160(&expected_secret); let secret = block_on(coin.extract_secret(secret_hash, &tx_hex, false)).unwrap(); assert_eq!(secret, expected_secret); @@ -187,7 +188,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { #[test] fn test_generate_transaction() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(client.into(), None, false); let unspents = vec![UnspentInfo { value: 10000000000, @@ -278,7 +279,7 @@ fn test_generate_transaction() { #[test] fn test_addresses_from_script() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(client.into(), None, false); // P2PKH let script: Script = "76a91405aab5342166f8594baf17a7d9bef5d56744332788ac".into(); @@ -972,20 +973,20 @@ fn test_utxo_lock() { #[test] fn test_spv_proof() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); - // https://rick.explorer.dexstats.info/tx/78ea7839f6d1b0dafda2ba7e34c1d8218676a58bd1b33f03a5f76391f61b72b0 - let tx_str = "0400008085202f8902bf17bf7d1daace52e08f732a6b8771743ca4b1cb765a187e72fd091a0aabfd52000000006a47304402203eaaa3c4da101240f80f9c5e9de716a22b1ec6d66080de6a0cca32011cd77223022040d9082b6242d6acf9a1a8e658779e1c655d708379862f235e8ba7b8ca4e69c6012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffffff023ca13c0e9e085dd13f481f193e8a3e8fd609020936e98b5587342d994f4d020000006b483045022100c0ba56adb8de923975052312467347d83238bd8d480ce66e8b709a7997373994022048507bcac921fdb2302fa5224ce86e41b7efc1a2e20ae63aa738dfa99b7be826012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0300e1f5050000000017a9141ee6d4c38a3c078eab87ad1a5e4b00f21259b10d870000000000000000166a1400000000000000000000000000000000000000001b94d736000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac2d08e35e000000000000000000000000000000"; + // https://doc.explorer.dexstats.info/tx/a3ebedbe20f82e43708f276152cf7dfb03a6050921c8f266e48c00ab66e891fb + let tx_str = "0400008085202f8901e15182af2c252bcfbd58884f3bdbd4d85ed036e53cfe2fd1f904ecfea10cb9f2010000006b483045022100d2435e0c9211114271ac452dc47fd08d3d2dc4bdd484d5750ee6bbda41056d520220408bfb236b7028b6fde0e59a1b6522949131a611584cce36c3df1e934c1748630121022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846ffffffff02a09ba104000000001976a914054407d1a2224268037cfc7ca3bc438d082bedf488acdd28ce9157ba11001976a914046922483fab8ca76b23e55e9d338605e2dbab6088ac03d63665000000000000000000000000000000"; let tx: UtxoTx = tx_str.into(); let header: BlockHeader = deserialize( - block_on(client.blockchain_block_header(452248).compat()) + block_on(client.blockchain_block_header(263240).compat()) .unwrap() .as_slice(), ) .unwrap(); let mut headers = HashMap::new(); - headers.insert(452248, header); + headers.insert(263240, header); let storage = client.block_headers_storage(); block_on(storage.add_block_headers_to_storage(headers)).unwrap(); @@ -1004,14 +1005,10 @@ fn list_since_block_btc_serde() { // https://github.com/KomodoPlatform/atomicDEX-API/issues/587 fn get_tx_details_coinbase_transaction() { /// Hash of coinbase transaction - /// https://morty.explorer.dexstats.info/tx/b59b093ed97c1798f2a88ee3375a0c11d0822b6e4468478777f899891abd34a5 - const TX_HASH: &str = "b59b093ed97c1798f2a88ee3375a0c11d0822b6e4468478777f899891abd34a5"; + /// https://marty.explorer.dexstats.info/tx/ae3220b868c677c77f8c9bdbc49b42da512260b45af695e672b1c5090815566c + const TX_HASH: &str = "ae3220b868c677c77f8c9bdbc49b42da512260b45af695e672b1c5090815566c"; - let client = electrum_client_for_test(&[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ]); + let client = electrum_client_for_test(MARTY_ELECTRUM_ADDRS); let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), @@ -1499,7 +1496,7 @@ fn test_unavailable_electrum_proto_version() { let conf = json!({"coin":"RICK","asset":"RICK","rpcport":8923}); let req = json!({ "method": "electrum", - "servers": [{"url":"electrum1.cipig.net:10017"}], + "servers": [{"url":"electrum1.cipig.net:10020"}], }); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -1837,9 +1834,9 @@ fn test_get_mature_unspent_ordered_map_from_cache_impl( expected_height: Option, expected_confs: u32, ) { - const TX_HASH: &str = "0a0fda88364b960000f445351fe7678317a1e0c80584de0413377ede00ba696f"; + const TX_HASH: &str = "b43f9ed47f7b97d4766b6f1614136fa0c55b9a52c97342428333521fa13ad714"; let tx_hash: H256Json = hex::decode(TX_HASH).unwrap().as_slice().into(); - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let mut verbose = client.get_verbose_transaction(&tx_hash).wait().unwrap(); verbose.confirmations = cached_confs; verbose.height = cached_height; @@ -2513,16 +2510,12 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { #[test] fn test_validate_fee_wrong_sender() { - let rpc_client = electrum_client_for_test(&[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ]); + let rpc_client = electrum_client_for_test(MARTY_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None, false); - // https://morty.explorer.dexstats.info/tx/fe4b0e1c4537e22f2956b5b74513fc936ebd87ada21513e850899cb07a45d475 - let tx_bytes = hex::decode("0400008085202f890199cc492c24cc617731d13cff0ef22e7b0c277a64e7368a615b46214424a1c894020000006a473044022071edae37cf518e98db3f7637b9073a7a980b957b0c7b871415dbb4898ec3ebdc022031b402a6b98e64ffdf752266449ca979a9f70144dba77ed7a6a25bfab11648f6012103ad6f89abc2e5beaa8a3ac28e22170659b3209fe2ddf439681b4b8f31508c36faffffffff0202290200000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac8a96e70b000000001976a914d55f0df6cb82630ad21a4e6049522a6f2b6c9d4588ac8afb2c60000000000000000000000000000000").unwrap(); + // https://marty.explorer.dexstats.info/tx/99349d1c72ef396ecb39ab2989b888b02e22382249271c79cda8139825adc468 + let tx_bytes = hex::decode("0400008085202f8901033aedb3c3c02fc76c15b393c7b1f638cfa6b4a1d502e00d57ad5b5305f12221000000006a473044022074879aabf38ef943eba7e4ce54c444d2d6aa93ac3e60ea1d7d288d7f17231c5002205e1671a62d8c031ac15e0e8456357e54865b7acbf49c7ebcba78058fd886b4bd012103242d9cb2168968d785f6914c494c303ff1c27ba0ad882dbc3c15cfa773ea953cffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac4802d913000000001976a914902053231ef0541a7628c11acac40d30f2a127bd88ac008e3765000000000000000000000000000000").unwrap(); let taker_fee_tx = coin.tx_enum_from_bytes(&tx_bytes).unwrap(); - let amount: BigDecimal = "0.0014157".parse().unwrap(); + let amount: BigDecimal = "0.0001".parse().unwrap(); let validate_fee_args = ValidateFeeArgs { fee_tx: &taker_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -2541,23 +2534,19 @@ fn test_validate_fee_wrong_sender() { #[test] fn test_validate_fee_min_block() { - let rpc_client = electrum_client_for_test(&[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ]); + let rpc_client = electrum_client_for_test(MARTY_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(UtxoRpcClientEnum::Electrum(rpc_client), None, false); - // https://morty.explorer.dexstats.info/tx/fe4b0e1c4537e22f2956b5b74513fc936ebd87ada21513e850899cb07a45d475 - let tx_bytes = hex::decode("0400008085202f890199cc492c24cc617731d13cff0ef22e7b0c277a64e7368a615b46214424a1c894020000006a473044022071edae37cf518e98db3f7637b9073a7a980b957b0c7b871415dbb4898ec3ebdc022031b402a6b98e64ffdf752266449ca979a9f70144dba77ed7a6a25bfab11648f6012103ad6f89abc2e5beaa8a3ac28e22170659b3209fe2ddf439681b4b8f31508c36faffffffff0202290200000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac8a96e70b000000001976a914d55f0df6cb82630ad21a4e6049522a6f2b6c9d4588ac8afb2c60000000000000000000000000000000").unwrap(); + // https://marty.explorer.dexstats.info/tx/99349d1c72ef396ecb39ab2989b888b02e22382249271c79cda8139825adc468 + let tx_bytes = hex::decode("0400008085202f8901033aedb3c3c02fc76c15b393c7b1f638cfa6b4a1d502e00d57ad5b5305f12221000000006a473044022074879aabf38ef943eba7e4ce54c444d2d6aa93ac3e60ea1d7d288d7f17231c5002205e1671a62d8c031ac15e0e8456357e54865b7acbf49c7ebcba78058fd886b4bd012103242d9cb2168968d785f6914c494c303ff1c27ba0ad882dbc3c15cfa773ea953cffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac4802d913000000001976a914902053231ef0541a7628c11acac40d30f2a127bd88ac008e3765000000000000000000000000000000").unwrap(); let taker_fee_tx = coin.tx_enum_from_bytes(&tx_bytes).unwrap(); - let amount: BigDecimal = "0.0014157".parse().unwrap(); - let sender_pub = hex::decode("03ad6f89abc2e5beaa8a3ac28e22170659b3209fe2ddf439681b4b8f31508c36fa").unwrap(); + let amount: BigDecimal = "0.0001".parse().unwrap(); + let sender_pub = hex::decode("03242d9cb2168968d785f6914c494c303ff1c27ba0ad882dbc3c15cfa773ea953c").unwrap(); let validate_fee_args = ValidateFeeArgs { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, - min_block_number: 810329, + min_block_number: 278455, uuid: &[], }; let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); @@ -3204,12 +3193,8 @@ fn test_utxo_standard_with_check_utxo_maturity_true() { let conf = json!({"coin":"RICK","asset":"RICK","rpcport":25435,"txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"UTXO"}}); let req = json!({ "method": "electrum", - "servers": [ - {"url":"electrum1.cipig.net:10017"}, - {"url":"electrum2.cipig.net:10017"}, - {"url":"electrum3.cipig.net:10017"}, - ], - "check_utxo_maturity": true, + "servers": doc_electrums(), + "check_utxo_maturity": true, }); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -3245,11 +3230,7 @@ fn test_utxo_standard_without_check_utxo_maturity() { let conf = json!({"coin":"RICK","asset":"RICK","rpcport":25435,"txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"UTXO"}}); let req = json!({ "method": "electrum", - "servers": [ - {"url":"electrum1.cipig.net:10017"}, - {"url":"electrum2.cipig.net:10017"}, - {"url":"electrum3.cipig.net:10017"}, - ] + "servers": doc_electrums() }); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -4102,7 +4083,7 @@ fn test_electrum_balance_deserializing() { #[test] fn test_electrum_display_balances() { - let rpc_client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let rpc_client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); block_on(utxo_common_tests::test_electrum_display_balances(&rpc_client)); } @@ -4110,7 +4091,7 @@ fn test_electrum_display_balances() { fn test_for_non_existent_tx_hex_utxo_electrum() { // This test shouldn't wait till timeout! let timeout = wait_until_sec(120); - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), @@ -4193,7 +4174,7 @@ fn test_native_display_balances() { #[test] fn test_message_hash() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), @@ -4207,7 +4188,7 @@ fn test_message_hash() { #[test] fn test_sign_verify_message() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), @@ -4228,7 +4209,7 @@ fn test_sign_verify_message() { #[test] fn test_sign_verify_message_segwit() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test( client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), @@ -4255,7 +4236,7 @@ fn test_sign_verify_message_segwit() { #[test] fn test_tx_enum_from_bytes() { - let client = electrum_client_for_test(RICK_ELECTRUM_ADDRS); + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); let coin = utxo_coin_for_test(client.into(), None, false); let tx_hex = hex::decode("01000000017b1eabe0209b1fe794124575ef807057c77ada2138ae4fa8d6c4de0398a14f3f00000000494830450221008949f0cb400094ad2b5eb399d59d01c14d73d8fe6e96df1a7150deb388ab8935022079656090d7f6bac4c9a94e0aad311a4268e082a725f8aeae0573fb12ff866a5f01ffffffff01f0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000").unwrap(); @@ -4285,11 +4266,7 @@ fn test_utxo_validate_valid_and_invalid_pubkey() { let conf = json!({"coin":"RICK","asset":"RICK","rpcport":25435,"txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"UTXO"}}); let req = json!({ "method": "electrum", - "servers": [ - {"url":"electrum1.cipig.net:10017"}, - {"url":"electrum2.cipig.net:10017"}, - {"url":"electrum3.cipig.net:10017"}, - ], + "servers": doc_electrums(), "check_utxo_maturity": true, }); @@ -4319,7 +4296,7 @@ fn test_block_header_utxo_loop() { static mut CURRENT_BLOCK_COUNT: u64 = 13; ElectrumClient::get_servers_with_latest_block_count.mock_safe(move |_| { - let servers = RICK_ELECTRUM_ADDRS.iter().map(|url| url.to_string()).collect(); + let servers = DOC_ELECTRUM_ADDRS.iter().map(|url| url.to_string()).collect(); MockResult::Return(Box::new(futures01::future::ok((servers, unsafe { CURRENT_BLOCK_COUNT })))) @@ -4346,7 +4323,7 @@ fn test_block_header_utxo_loop() { let ctx = mm_ctx_with_custom_db(); let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(H256Json::from([1u8; 32])); - let servers: Vec<_> = RICK_ELECTRUM_ADDRS + let servers: Vec<_> = DOC_ELECTRUM_ADDRS .iter() .map(|server| json!({ "url": server })) .collect(); @@ -4366,8 +4343,8 @@ fn test_block_header_utxo_loop() { let spv_conf = json::from_value(json!({ "starting_block_header": { "height": 1, - "hash": "0c714ba4f8d5f2d5c014a08c4e21a5387156e23bcc819c0f9bc536437586cdf5", - "time": 1564482125, + "hash": "0918169860eda78df99319a4d073d325017fbda08dd10375a6de8b6214cef3f5", + "time": 1681404988, "bits": 537857807 }, "max_stored_block_headers": 15 @@ -4516,7 +4493,7 @@ fn test_block_header_utxo_loop_with_reorg() { } ElectrumClient::get_servers_with_latest_block_count.mock_safe(move |_| { - let servers = RICK_ELECTRUM_ADDRS.iter().map(|url| url.to_string()).collect(); + let servers = DOC_ELECTRUM_ADDRS.iter().map(|url| url.to_string()).collect(); MockResult::Return(Box::new(futures01::future::ok((servers, unsafe { CURRENT_BLOCK_COUNT })))) @@ -4565,7 +4542,7 @@ fn test_block_header_utxo_loop_with_reorg() { let ctx = mm_ctx_with_custom_db(); let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(H256Json::from([1u8; 32])); - let servers: Vec<_> = RICK_ELECTRUM_ADDRS + let servers: Vec<_> = DOC_ELECTRUM_ADDRS .iter() .map(|server| json!({ "url": server })) .collect(); @@ -4585,8 +4562,8 @@ fn test_block_header_utxo_loop_with_reorg() { let spv_conf = json::from_value(json!({ "starting_block_header": { "height": 1, - "hash": "0c714ba4f8d5f2d5c014a08c4e21a5387156e23bcc819c0f9bc536437586cdf5", - "time": 1564482125, + "hash": "0918169860eda78df99319a4d073d325017fbda08dd10375a6de8b6214cef3f5", + "time": 1681404988, "bits": 537857807 }, "max_stored_block_headers": 100 diff --git a/mm2src/coins/utxo/utxo_wasm_tests.rs b/mm2src/coins/utxo/utxo_wasm_tests.rs index 65bd2fe20a..35902f4ff0 100644 --- a/mm2src/coins/utxo/utxo_wasm_tests.rs +++ b/mm2src/coins/utxo/utxo_wasm_tests.rs @@ -5,6 +5,7 @@ use super::*; use crate::utxo::utxo_common_tests; use crate::{IguanaPrivKey, PrivKeyBuildPolicy}; use mm2_core::mm_ctx::MmCtxBuilder; +use mm2_test_helpers::for_tests::DOC_ELECTRUM_ADDRS; use serialization::deserialize; use wasm_bindgen_test::*; @@ -45,9 +46,9 @@ pub async fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { #[wasm_bindgen_test] async fn test_electrum_rpc_client() { - let client = electrum_client_for_test(&["electrum1.cipig.net:30017", "electrum2.cipig.net:30017"]).await; + let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS).await; - let tx_hash: H256Json = hex::decode("0a0fda88364b960000f445351fe7678317a1e0c80584de0413377ede00ba696f") + let tx_hash: H256Json = hex::decode("a3ebedbe20f82e43708f276152cf7dfb03a6050921c8f266e48c00ab66e891fb") .unwrap() .as_slice() .into(); @@ -57,13 +58,13 @@ async fn test_electrum_rpc_client() { .await .expect("!get_verbose_transaction"); let actual: UtxoTx = deserialize(verbose_tx.hex.as_slice()).unwrap(); - let expected = UtxoTx::from("0400008085202f8902358549fe3cf9a66bf61fb57bca1b3b49434a148a4dc29450b5eefe583f2f9ecf000000006a4730440220112aa3737672f8aa16a58426f5e7656ad13d21a219390c7a0b2e266ee6b216a8022008e9f9e94db91f069f831b0d40b7f75938122cddceaa25197146dfb00fe82599012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff358549fe3cf9a66bf61fb57bca1b3b49434a148a4dc29450b5eefe583f2f9ecf010000006b483045022100d054464799246254b09f96333bf52537938abe31c24bacf41c9ef600b28155950220527ec33c4a5bef79dcabf97e38aa240fecdd14c96f698560b2f10ec2abc2e992012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0240420f00000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac66418f00000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac0e2aa85f000000000000000000000000000000"); + let expected = UtxoTx::from("0400008085202f8901e15182af2c252bcfbd58884f3bdbd4d85ed036e53cfe2fd1f904ecfea10cb9f2010000006b483045022100d2435e0c9211114271ac452dc47fd08d3d2dc4bdd484d5750ee6bbda41056d520220408bfb236b7028b6fde0e59a1b6522949131a611584cce36c3df1e934c1748630121022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846ffffffff02a09ba104000000001976a914054407d1a2224268037cfc7ca3bc438d082bedf488acdd28ce9157ba11001976a914046922483fab8ca76b23e55e9d338605e2dbab6088ac03d63665000000000000000000000000000000"); assert_eq!(actual, expected); } #[wasm_bindgen_test] async fn test_electrum_display_balances() { - let rpc_client = electrum_client_for_test(&["electrum1.cipig.net:30017", "electrum2.cipig.net:30017"]).await; + let rpc_client = electrum_client_for_test(DOC_ELECTRUM_ADDRS).await; utxo_common_tests::test_electrum_display_balances(&rpc_client).await; } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index d9607adcb3..a53f1136f7 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -62,7 +62,7 @@ mm2_gui_storage = { path = "../mm2_gui_storage" } mm2_io = { path = "../mm2_io" } mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } mm2_metrics = { path = "../mm2_metrics" } -mm2_net = { path = "../mm2_net" } +mm2_net = { path = "../mm2_net", features = ["event-stream", "p2p"] } mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"]} mm2_state_machine = { path = "../mm2_state_machine" } diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index f5025e85d3..a2178aad65 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -4,7 +4,7 @@ use common::log::wasm_log::register_wasm_log; use crypto::StandardHDCoinAddress; use mm2_core::mm_ctx::MmArc; use mm2_rpc::data::legacy::OrderbookResponse; -use mm2_test_helpers::electrums::{morty_electrums, rick_electrums}; +use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{check_recent_swaps, enable_electrum_json, morty_conf, rick_conf, start_swaps, test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, MORTY, RICK}; @@ -48,17 +48,17 @@ async fn test_mm2_stops_impl( Timer::sleep(2.).await; // Enable coins on Bob side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums(), None).await; + let rc = enable_electrum_json(&mm_bob, RICK, true, doc_electrums(), None).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_bob, MORTY, true, morty_electrums(), None).await; + let rc = enable_electrum_json(&mm_bob, MORTY, true, marty_electrums(), None).await; log!("enable MORTY (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_alice, RICK, true, rick_electrums(), None).await; + let rc = enable_electrum_json(&mm_alice, RICK, true, doc_electrums(), None).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_alice, MORTY, true, morty_electrums(), None).await; + let rc = enable_electrum_json(&mm_alice, MORTY, true, marty_electrums(), None).await; log!("enable MORTY (bob): {:?}", rc); start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; @@ -112,17 +112,17 @@ async fn trade_base_rel_electrum( let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); // Enable coins on Bob side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_bob, RICK, true, rick_electrums(), bob_path_to_address.clone()).await; + let rc = enable_electrum_json(&mm_bob, RICK, true, doc_electrums(), bob_path_to_address.clone()).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_bob, MORTY, true, morty_electrums(), bob_path_to_address).await; + let rc = enable_electrum_json(&mm_bob, MORTY, true, marty_electrums(), bob_path_to_address).await; log!("enable MORTY (bob): {:?}", rc); // Enable coins on Alice side. Print the replies in case we need the address. - let rc = enable_electrum_json(&mm_alice, RICK, true, rick_electrums(), alice_path_to_address.clone()).await; + let rc = enable_electrum_json(&mm_alice, RICK, true, doc_electrums(), alice_path_to_address.clone()).await; log!("enable RICK (bob): {:?}", rc); - let rc = enable_electrum_json(&mm_alice, MORTY, true, morty_electrums(), alice_path_to_address).await; + let rc = enable_electrum_json(&mm_alice, MORTY, true, marty_electrums(), alice_path_to_address).await; log!("enable MORTY (bob): {:?}", rc); let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; diff --git a/mm2src/mm2_main/tests/integration_tests_common/mod.rs b/mm2src/mm2_main/tests/integration_tests_common/mod.rs index 12075d2974..be7e8bcb46 100644 --- a/mm2src/mm2_main/tests/integration_tests_common/mod.rs +++ b/mm2src/mm2_main/tests/integration_tests_common/mod.rs @@ -5,7 +5,7 @@ use crypto::privkey::key_pair_from_seed; use crypto::StandardHDCoinAddress; use mm2_main::mm2::{lp_main, LpMainParams}; use mm2_rpc::data::legacy::CoinInitResponse; -use mm2_test_helpers::electrums::{morty_electrums, rick_electrums}; +use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{enable_native as enable_native_impl, init_utxo_electrum, init_utxo_status, init_z_coin_light, init_z_coin_status, MarketMakerIt}; use mm2_test_helpers::structs::{InitTaskResult, InitUtxoStatus, InitZcoinStatus, RpcV2Response, @@ -74,11 +74,11 @@ pub async fn enable_coins_rick_morty_electrum(mm: &MarketMakerIt) -> HashMap<&'s let mut replies = HashMap::new(); replies.insert( "RICK", - enable_electrum_json(mm, "RICK", false, rick_electrums(), None).await, + enable_electrum_json(mm, "RICK", false, doc_electrums(), None).await, ); replies.insert( "MORTY", - enable_electrum_json(mm, "MORTY", false, morty_electrums(), None).await, + enable_electrum_json(mm, "MORTY", false, marty_electrums(), None).await, ); replies } @@ -145,11 +145,11 @@ pub async fn enable_coins_eth_electrum( let mut replies = HashMap::new(); replies.insert( "RICK", - enable_electrum_json(mm, "RICK", false, rick_electrums(), path_to_address.clone()).await, + enable_electrum_json(mm, "RICK", false, doc_electrums(), path_to_address.clone()).await, ); replies.insert( "MORTY", - enable_electrum_json(mm, "MORTY", false, morty_electrums(), path_to_address.clone()).await, + enable_electrum_json(mm, "MORTY", false, marty_electrums(), path_to_address.clone()).await, ); replies.insert("ETH", enable_native(mm, "ETH", eth_urls, path_to_address.clone()).await); replies.insert("JST", enable_native(mm, "JST", eth_urls, path_to_address).await); diff --git a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs index 9be563b420..c540f2f328 100644 --- a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs @@ -3,10 +3,9 @@ use common::{block_on, log}; use http::StatusCode; use mm2_number::BigDecimal; use mm2_rpc::data::legacy::CoinInitResponse; -use mm2_test_helpers::for_tests::ETH_DEV_NODES; use mm2_test_helpers::for_tests::{best_orders_v2, best_orders_v2_by_number, eth_jst_testnet_conf, eth_testnet_conf, get_passphrase, morty_conf, rick_conf, tbtc_conf, tbtc_segwit_conf, MarketMakerIt, - Mm2TestConf, RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS}; + Mm2TestConf, DOC_ELECTRUM_ADDRS, ETH_DEV_NODES, TBTC_ELECTRUMS}; use mm2_test_helpers::structs::{BestOrdersResponse, SetPriceResponse}; use serde_json::{self as json, json}; use std::collections::BTreeSet; @@ -837,22 +836,8 @@ fn test_best_orders_address_and_confirmations() { let enable_tbtc_res: CoinInitResponse = json::from_str(&electrum.1).unwrap(); let tbtc_segwit_address = enable_tbtc_res.address; - let electrum = block_on(mm_bob.rpc(&json!({ - "userpass": "pass", - "method": "electrum", - "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], - "mm2": 1, - }))).unwrap(); - assert_eq!( - electrum.0, - StatusCode::OK, - "RPC «electrum» failed with {} {}", - electrum.0, - electrum.1 - ); - log!("enable RICK: {:?}", electrum); - let enable_rick_res: CoinInitResponse = json::from_str(&electrum.1).unwrap(); + let enable_rick_res = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS, None)); + log!("enable RICK: {:?}", enable_rick_res); let rick_address = enable_rick_res.address; // issue sell request on Bob side by setting base/rel price @@ -999,7 +984,7 @@ fn best_orders_must_return_duplicate_for_orderbook_tickers() { let t_btc_bob = block_on(enable_electrum(&mm_bob, "tBTC", false, TBTC_ELECTRUMS, None)); log!("Bob enable tBTC: {:?}", t_btc_bob); - let rick_bob = block_on(enable_electrum(&mm_bob, "RICK", false, RICK_ELECTRUM_ADDRS, None)); + let rick_bob = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS, None)); log!("Bob enable RICK: {:?}", rick_bob); // issue sell request on Bob side by setting base/rel price @@ -1116,10 +1101,9 @@ fn best_orders_must_return_duplicate_for_orderbook_tickers() { #[cfg(feature = "zhtlc-native-tests")] fn zhtlc_best_orders() { use super::enable_z_coin; + use mm2_test_helpers::electrums::doc_electrums; use mm2_test_helpers::for_tests::zombie_conf; - use mm2_test_helpers::electrums::rick_electrums; - let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); let alice_passphrase = get_passphrase(&".env.client", "ALICE_PASSPHRASE").unwrap(); @@ -1150,7 +1134,7 @@ fn zhtlc_best_orders() { log!("bob_zombie_cache_path {}", bob_zombie_cache_path.display()); std::fs::copy("./mm2src/coins/for_tests/ZOMBIE_CACHE.db", bob_zombie_cache_path).unwrap(); - block_on(enable_electrum_json(&mm_bob, "RICK", false, rick_electrums(), None)); + block_on(enable_electrum_json(&mm_bob, "RICK", false, doc_electrums(), None)); block_on(enable_z_coin(&mm_bob, "ZOMBIE")); let set_price_json = json!({ diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index e5869e2896..30151e5962 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -4,6 +4,7 @@ use crate::integration_tests_common::*; use common::executor::Timer; use common::{cfg_native, cfg_wasm32, get_utc_timestamp, log, new_uuid}; use crypto::privkey::key_pair_from_seed; +use crypto::StandardHDCoinAddress; use http::{HeaderMap, StatusCode}; use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; use mm2_metrics::{MetricType, MetricsJson}; @@ -20,11 +21,9 @@ use mm2_test_helpers::for_tests::{btc_segwit_conf, btc_with_spv_conf, btc_with_s wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, wait_for_swaps_finish_and_check_status, wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, - ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, ETH_MAINNET_NODE, - ETH_MAINNET_SWAP_CONTRACT, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, - TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; - -use crypto::StandardHDCoinAddress; + DOC_ELECTRUM_ADDRS, ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, + ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, MARTY_ELECTRUM_ADDRS, MORTY, + QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -232,17 +231,7 @@ fn test_my_balance() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); // Enable RICK. - let json = block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum1.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum3.cipig.net:10017", - ], - None, - )); + let json = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); assert_eq!(json.balance, "7.777".parse().unwrap()); let my_balance = block_on(mm.rpc(&json! ({ @@ -519,9 +508,10 @@ fn test_rpc_password_from_json() { "userpass": "password1", "method": "electrum", "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], + "servers": doc_electrums(), "mm2": 1, - }))).unwrap(); + }))) + .unwrap(); // electrum call must fail if invalid password is provided assert!( @@ -535,9 +525,10 @@ fn test_rpc_password_from_json() { "userpass": mm.userpass, "method": "electrum", "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], + "servers": doc_electrums(), "mm2": 1, - }))).unwrap(); + }))) + .unwrap(); // electrum call must be successful with RPC password from config assert_eq!( @@ -552,9 +543,10 @@ fn test_rpc_password_from_json() { "userpass": mm.userpass, "method": "electrum", "coin": "MORTY", - "servers": [{"url":"electrum1.cipig.net:10018"},{"url":"electrum2.cipig.net:10018"},{"url":"electrum3.cipig.net:10018"}], + "servers": marty_electrums(), "mm2": 1, - }))).unwrap(); + }))) + .unwrap(); // electrum call must be successful with RPC password from config assert_eq!( @@ -607,17 +599,7 @@ fn test_mmrpc_v2() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); + let _electrum = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); // no `userpass` let withdraw = block_on(mm.rpc(&json! ({ @@ -1037,11 +1019,7 @@ fn test_withdraw_and_send() { &mm_alice, "MORTY_SEGWIT", false, - &[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ], + MARTY_ELECTRUM_ADDRS, None, )), ); @@ -1367,11 +1345,7 @@ fn test_withdraw_legacy() { &mm_alice, "MORTY_SEGWIT", false, - &[ - "electrum1.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum3.cipig.net:10018", - ], + MARTY_ELECTRUM_ADDRS, None, )), ); @@ -1603,17 +1577,7 @@ fn test_order_errors_when_base_equal_rel() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); + block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); let rc = block_on(mm.rpc(&json! ({ "userpass": mm.userpass, @@ -2038,11 +2002,11 @@ fn test_electrum_enable_conn_errors() { "RICK", false, &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - "electrum1.cipig.net:60017", - "electrum1.cipig.net:60018", + "electrum3.cipig.net:10020", + "electrum2.cipig.net:10020", + "electrum1.cipig.net:10020", + "electrum1.cipig.net:60020", + "electrum1.cipig.net:60021", ], None, )); @@ -2052,11 +2016,11 @@ fn test_electrum_enable_conn_errors() { "MORTY", false, &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - "random-electrum-domain-name1.net:60017", - "random-electrum-domain-name2.net:60017", + "electrum3.cipig.net:10021", + "electrum2.cipig.net:10021", + "electrum1.cipig.net:10021", + "random-electrum-domain-name1.net:60020", + "random-electrum-domain-name2.net:60020", ], None, )); @@ -2091,30 +2055,10 @@ fn test_order_should_not_be_displayed_when_node_is_down() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - let electrum_rick = block_on(enable_electrum( - &mm_bob, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); + let electrum_rick = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS, None)); log!("Bob enable RICK {:?}", electrum_rick); - let electrum_morty = block_on(enable_electrum( - &mm_bob, - "MORTY", - false, - &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ], - None, - )); + let electrum_morty = block_on(enable_electrum(&mm_bob, "MORTY", false, MARTY_ELECTRUM_ADDRS, None)); log!("Bob enable MORTY {:?}", electrum_morty); let mm_alice = MarketMakerIt::start( @@ -2137,30 +2081,10 @@ fn test_order_should_not_be_displayed_when_node_is_down() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - let electrum_rick = block_on(enable_electrum( - &mm_alice, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); + let electrum_rick = block_on(enable_electrum(&mm_alice, "RICK", false, DOC_ELECTRUM_ADDRS, None)); log!("Alice enable RICK {:?}", electrum_rick); - let electrum_morty = block_on(enable_electrum( - &mm_alice, - "MORTY", - false, - &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ], - None, - )); + let electrum_morty = block_on(enable_electrum(&mm_alice, "MORTY", false, MARTY_ELECTRUM_ADDRS, None)); log!("Alice enable MORTY {:?}", electrum_morty); // issue sell request on Bob side by setting base/rel price @@ -2243,30 +2167,10 @@ fn test_own_orders_should_not_be_removed_from_orderbook() { let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - let electrum_rick = block_on(enable_electrum( - &mm_bob, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); + let electrum_rick = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS, None)); log!("Bob enable RICK {:?}", electrum_rick); - let electrum_morty = block_on(enable_electrum( - &mm_bob, - "MORTY", - false, - &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ], - None, - )); + let electrum_morty = block_on(enable_electrum(&mm_bob, "MORTY", false, MARTY_ELECTRUM_ADDRS, None)); log!("Bob enable MORTY {:?}", electrum_morty); // issue sell request on Bob side by setting base/rel price @@ -2384,11 +2288,12 @@ fn test_electrum_and_enable_response() { "userpass": mm.userpass, "method": "electrum", "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], + "servers": doc_electrums(), "mm2": 1, "required_confirmations": 10, "requires_notarization": true - }))).unwrap(); + }))) + .unwrap(); assert_eq!( electrum_rick.0, StatusCode::OK, @@ -2943,21 +2848,21 @@ fn test_batch_requests() { "userpass": mm_bob.userpass, "method": "electrum", "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], + "servers": doc_electrums(), "mm2": 1, }, { "userpass": mm_bob.userpass, "method": "electrum", "coin": "MORTY", - "servers": [{"url":"electrum1.cipig.net:10018"},{"url":"electrum2.cipig.net:10018"},{"url":"electrum3.cipig.net:10018"}], + "servers": doc_electrums(), "mm2": 1, }, { "userpass": "error", "method": "electrum", "coin": "MORTY", - "servers": [{"url":"electrum1.cipig.net:10018"},{"url":"electrum2.cipig.net:10018"},{"url":"electrum3.cipig.net:10018"}], + "servers": marty_electrums(), "mm2": 1, }, ]); @@ -3013,17 +2918,7 @@ fn test_metrics_method() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); - let _electrum = block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum1.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum3.cipig.net:10017", - ], - None, - )); + let _electrum = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); let metrics = request_metrics(&mm); assert!(!metrics.metrics.is_empty()); @@ -4378,24 +4273,14 @@ fn test_get_raw_transaction() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); // RICK - let _electrum = block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); + let _electrum = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); let raw = block_on(mm.rpc(&json! ({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", "params": { "coin": "RICK", - "tx_hash": "989360b0225b4e05fa13643e2e306c8eb5c52fa611615dfd30195089010b1c7b", + "tx_hash": "a3ebedbe20f82e43708f276152cf7dfb03a6050921c8f266e48c00ab66e891fb", }, "id": 0, }))) @@ -4403,7 +4288,7 @@ fn test_get_raw_transaction() { assert!(raw.0.is_success(), "get_raw_transaction for coin RICK: {}", raw.1); let res: RpcSuccessResponse = json::from_str(&raw.1).expect("Expected 'RpcSuccessResponse'"); - let expected_hex = "0400008085202f89025655b6fec358091a4a6b34107e69b10bd7660056d8f2a1e5f8eef0db6aec960100000000494830450221008c89db5e2d93d7674fe152e37344dfd24a0b1d4d382a7e0bcfc5d8190a141d72022050ce4ef929429e7e1a6c4ebd3f72a1a2aa25da1e0df65553a2c657658077ed1d01feffffff79cc137b70c39c9c7c2b9230c818ec684ffe731bf1ae821f91ba9d3e526f55f00000000049483045022100868c71f4a8e1452a3bc8b1d053a846959ab7df63fb0d147e9173f69818bbb1f3022060c7e045a34cf6af61bc3a74dc2db7b8bfa4949bc5919acceed40fc07d8706d201feffffff0240043a0000000000232102afdbba3e3c90db5f0f4064118f79cf308f926c68afd64ea7afc930975663e4c4ac201efc01000000001976a914347f2aedf63bac168c2cc4f075a2850435e20ac188ac96d3c96036dd0e000000000000000000000000"; + let expected_hex = "0400008085202f8901e15182af2c252bcfbd58884f3bdbd4d85ed036e53cfe2fd1f904ecfea10cb9f2010000006b483045022100d2435e0c9211114271ac452dc47fd08d3d2dc4bdd484d5750ee6bbda41056d520220408bfb236b7028b6fde0e59a1b6522949131a611584cce36c3df1e934c1748630121022d7424c741213a2b9b49aebdaa10e84419e642a8db0a09e359a3d4c850834846ffffffff02a09ba104000000001976a914054407d1a2224268037cfc7ca3bc438d082bedf488acdd28ce9157ba11001976a914046922483fab8ca76b23e55e9d338605e2dbab6088ac03d63665000000000000000000000000000000"; assert_eq!(res.result.tx_hex, expected_hex); // ETH @@ -7078,8 +6963,8 @@ fn test_no_login() { let (_dump_log, _dump_dashboard) = no_login_node.mm_dump(); log!("log path: {}", no_login_node.log_path.display()); - block_on(enable_electrum_json(&seednode, RICK, false, rick_electrums(), None)); - block_on(enable_electrum_json(&seednode, MORTY, false, morty_electrums(), None)); + block_on(enable_electrum_json(&seednode, RICK, false, doc_electrums(), None)); + block_on(enable_electrum_json(&seednode, MORTY, false, marty_electrums(), None)); let orders = [ // (base, rel, price, volume, min_volume) @@ -7595,7 +7480,7 @@ fn test_enable_coins_with_enable_hd() { &mm_hd_0, "RICK", TX_HISTORY, - RICK_ELECTRUM_ADDRS, + DOC_ELECTRUM_ADDRS, Some(path_to_address.clone()), )); assert_eq!(rick.address, "RXNtAyDSsY3DS3VxTpJegzoHU9bUX54j56"); @@ -7611,7 +7496,7 @@ fn test_enable_coins_with_enable_hd() { &mm_hd_0, "BTC-segwit", TX_HISTORY, - RICK_ELECTRUM_ADDRS, + TBTC_ELECTRUMS, Some(path_to_address), )); assert_eq!(btc_segwit.address, "bc1q6vyur5hjul2m0979aadd6u7ptuj9ac4gt0ha0c"); @@ -7644,7 +7529,7 @@ fn test_enable_coins_with_enable_hd() { &mm_hd_1, "RICK", TX_HISTORY, - RICK_ELECTRUM_ADDRS, + DOC_ELECTRUM_ADDRS, Some(path_to_address.clone()), )); assert_eq!(rick.address, "RVyndZp3ZrhGKSwHryyM3Kcz9aq2EJrW1z"); @@ -7660,7 +7545,7 @@ fn test_enable_coins_with_enable_hd() { &mm_hd_1, "BTC-segwit", TX_HISTORY, - RICK_ELECTRUM_ADDRS, + TBTC_ELECTRUMS, Some(path_to_address), )); assert_eq!(btc_segwit.address, "bc1q6kxcwcrsm5z8pe940xxu294q7588mqvarttxcx"); @@ -7693,7 +7578,7 @@ fn test_enable_coins_with_enable_hd() { &mm_hd_1, "RICK", TX_HISTORY, - RICK_ELECTRUM_ADDRS, + DOC_ELECTRUM_ADDRS, Some(path_to_address.clone()), )); assert_eq!(rick.address, "RLNu8gszQ8ENUrY3VSyBS2714CNVwn1f7P"); @@ -7709,7 +7594,7 @@ fn test_enable_coins_with_enable_hd() { &mm_hd_1, "BTC-segwit", TX_HISTORY, - RICK_ELECTRUM_ADDRS, + TBTC_ELECTRUMS, Some(path_to_address), )); assert_eq!(btc_segwit.address, "bc1q0dxnd7afj997a40j86a8a6dq3xs3dwm7rkzams"); diff --git a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs index 1a5c0c8d4c..05710fa5c4 100644 --- a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs @@ -5,10 +5,11 @@ use http::StatusCode; use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_rpc::data::legacy::{AggregatedOrderbookEntry, CoinInitResponse, OrderbookResponse}; -use mm2_test_helpers::electrums::rick_electrums; +use mm2_test_helpers::electrums::doc_electrums; use mm2_test_helpers::for_tests::{eth_jst_testnet_conf, eth_testnet_conf, get_passphrase, morty_conf, orderbook_v2, - rick_conf, zombie_conf, MarketMakerIt, Mm2TestConf, ETH_DEV_NODES, RICK, - ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, ZOMBIE_TICKER}; + rick_conf, zombie_conf, MarketMakerIt, Mm2TestConf, DOC_ELECTRUM_ADDRS, + ETH_DEV_NODES, MARTY_ELECTRUM_ADDRS, RICK, ZOMBIE_ELECTRUMS, + ZOMBIE_LIGHTWALLETD_URLS, ZOMBIE_TICKER}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::{GetPublicKeyResult, OrderbookV2Response, RpcV2Response, SetPriceResponse}; use serde_json::{self as json, json, Value as Json}; @@ -283,23 +284,9 @@ fn alice_can_see_the_active_order_after_orderbook_sync_segwit() { let enable_tbtc_res: CoinInitResponse = json::from_str(&electrum.1).unwrap(); let tbtc_segwit_address = enable_tbtc_res.address; - let electrum = block_on(mm_bob.rpc(&json!({ - "userpass": "pass", - "method": "electrum", - "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], - "mm2": 1, - }))).unwrap(); - assert_eq!( - electrum.0, - StatusCode::OK, - "RPC «electrum» failed with {} {}", - electrum.0, - electrum.1 - ); - log!("enable RICK: {:?}", electrum); - let enable_rick_res: Json = json::from_str(&electrum.1).unwrap(); - let rick_address = enable_rick_res["address"].as_str().unwrap(); + let enable_rick_res = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS, None)); + log!("enable RICK: {:?}", enable_rick_res); + let rick_address = enable_rick_res.address; // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -376,20 +363,7 @@ fn alice_can_see_the_active_order_after_orderbook_sync_segwit() { ); log!("enable Alice tBTC: {:?}", electrum); - let electrum = block_on(mm_alice.rpc(&json!({ - "userpass": "pass", - "method": "electrum", - "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], - "mm2": 1, - }))).unwrap(); - assert_eq!( - electrum.0, - StatusCode::OK, - "RPC «electrum» failed with {} {}", - electrum.0, - electrum.1 - ); + let electrum = block_on(enable_electrum(&mm_alice, "RICK", false, DOC_ELECTRUM_ADDRS, None)); log!("enable Alice RICK: {:?}", electrum); // setting the price will trigger Alice's subscription to the orderbook topic @@ -483,23 +457,9 @@ fn test_orderbook_segwit() { let enable_tbtc_res: CoinInitResponse = json::from_str(&electrum.1).unwrap(); let tbtc_segwit_address = enable_tbtc_res.address; - let electrum = block_on(mm_bob.rpc(&json!({ - "userpass": "pass", - "method": "electrum", - "coin": "RICK", - "servers": [{"url":"electrum1.cipig.net:10017"},{"url":"electrum2.cipig.net:10017"},{"url":"electrum3.cipig.net:10017"}], - "mm2": 1, - }))).unwrap(); - assert_eq!( - electrum.0, - StatusCode::OK, - "RPC «electrum» failed with {} {}", - electrum.0, - electrum.1 - ); - log!("enable RICK: {:?}", electrum); - let enable_rick_res: Json = json::from_str(&electrum.1).unwrap(); - let rick_address = enable_rick_res["address"].as_str().unwrap(); + let enable_rick_res = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS, None)); + log!("enable RICK: {:?}", enable_rick_res); + let rick_address = enable_rick_res.address; // issue sell request on Bob side by setting base/rel price log!("Issue bob sell requests"); @@ -897,28 +857,8 @@ fn orderbook_extended_data() { .unwrap(); let (_dump_log, _dump_dashboard) = &mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); - block_on(enable_electrum( - &mm, - "MORTY", - false, - &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ], - None, - )); + block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); + block_on(enable_electrum(&mm, "MORTY", false, MARTY_ELECTRUM_ADDRS, None)); let bob_orders = &[ // (base, rel, price, volume) @@ -1029,28 +969,8 @@ fn orderbook_should_display_base_rel_volumes() { .unwrap(); let (_dump_log, _dump_dashboard) = &mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); - block_on(enable_electrum( - &mm, - "MORTY", - false, - &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ], - None, - )); + block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); + block_on(enable_electrum(&mm, "MORTY", false, MARTY_ELECTRUM_ADDRS, None)); let price = BigRational::new(2.into(), 1.into()); let volume = BigRational::new(1.into(), 1.into()); @@ -1228,28 +1148,8 @@ fn test_all_orders_per_pair_per_node_must_be_displayed_in_orderbook() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - block_on(enable_electrum( - &mm, - "RICK", - false, - &[ - "electrum3.cipig.net:10017", - "electrum2.cipig.net:10017", - "electrum1.cipig.net:10017", - ], - None, - )); - block_on(enable_electrum( - &mm, - "MORTY", - false, - &[ - "electrum3.cipig.net:10018", - "electrum2.cipig.net:10018", - "electrum1.cipig.net:10018", - ], - None, - )); + block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS, None)); + block_on(enable_electrum(&mm, "MORTY", false, MARTY_ELECTRUM_ADDRS, None)); // set 2 orders with different prices let rc = block_on(mm.rpc(&json!({ @@ -1430,7 +1330,7 @@ fn zhtlc_orders_sync_alice_connected_before_creation() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - block_on(enable_electrum_json(&mm_bob, RICK, false, rick_electrums(), None)); + block_on(enable_electrum_json(&mm_bob, RICK, false, doc_electrums(), None)); block_on(enable_z_coin_light( &mm_bob, ZOMBIE_TICKER, @@ -1494,7 +1394,7 @@ fn zhtlc_orders_sync_alice_connected_after_creation() { let (_dump_log, _dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); - block_on(enable_electrum_json(&mm_bob, "RICK", false, rick_electrums(), None)); + block_on(enable_electrum_json(&mm_bob, "RICK", false, doc_electrums(), None)); block_on(enable_z_coin_light( &mm_bob, ZOMBIE_TICKER, @@ -1524,7 +1424,7 @@ fn zhtlc_orders_sync_alice_connected_after_creation() { let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); - block_on(enable_electrum_json(&mm_alice, RICK, false, rick_electrums(), None)); + block_on(enable_electrum_json(&mm_alice, RICK, false, doc_electrums(), None)); block_on(enable_z_coin_light( &mm_alice, ZOMBIE_TICKER, diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 3892e85c23..60a3ad3ee0 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -649,7 +649,7 @@ mod swap { use mm2_rpc::data::legacy::OrderbookResponse; use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, check_stats_swap_status, enable_eth_coin, rick_conf, tbnb_conf, usdc_ibc_iris_testnet_conf, - RICK_ELECTRUM_ADDRS}; + DOC_ELECTRUM_ADDRS}; use std::convert::TryFrom; use std::{env, thread}; @@ -806,7 +806,7 @@ mod swap { &mm_bob, "RICK", false, - RICK_ELECTRUM_ADDRS, + DOC_ELECTRUM_ADDRS, None ))); @@ -814,7 +814,7 @@ mod swap { &mm_alice, "RICK", false, - RICK_ELECTRUM_ADDRS, + DOC_ELECTRUM_ADDRS, None ))); diff --git a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs index 7d9d1e45b8..7fa96a4b03 100644 --- a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs @@ -2,7 +2,7 @@ use crate::integration_tests_common::*; use common::executor::Timer; use common::{block_on, log, now_ms, now_sec, wait_until_ms}; use mm2_number::BigDecimal; -use mm2_test_helpers::electrums::rick_electrums; +use mm2_test_helpers::electrums::doc_electrums; use mm2_test_helpers::for_tests::{disable_coin, init_withdraw, pirate_conf, rick_conf, send_raw_transaction, withdraw_status, z_coin_tx_history, zombie_conf, MarketMakerIt, Mm2TestConf, ARRR, PIRATE_ELECTRUMS, PIRATE_LIGHTWALLETD_URLS, RICK, ZOMBIE_ELECTRUMS, @@ -465,7 +465,7 @@ fn trade_rick_zombie_light() { println!("Bob ZOMBIE activation {:?}", zombie_activation); - let rick_activation = block_on(enable_electrum_json(&mm_bob, RICK, false, rick_electrums(), None)); + let rick_activation = block_on(enable_electrum_json(&mm_bob, RICK, false, doc_electrums(), None)); println!("Bob RICK activation {:?}", rick_activation); @@ -499,7 +499,7 @@ fn trade_rick_zombie_light() { println!("Alice ZOMBIE activation {:?}", zombie_activation); - let rick_activation = block_on(enable_electrum_json(&mm_alice, RICK, false, rick_electrums(), None)); + let rick_activation = block_on(enable_electrum_json(&mm_alice, RICK, false, doc_electrums(), None)); println!("Alice RICK activation {:?}", rick_activation); diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index ac114f28a3..f9b67767e5 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -6,8 +6,12 @@ edition = "2018" [lib] doctest = false +[features] +event-stream = ["mm2_event_stream", "async-stream" , "p2p"] +p2p = ["mm2-libp2p", "parking_lot"] + [dependencies] -async-stream = "0.3" +async-stream = { version = "0.3", optional = true } async-trait = "0.1" bytes = "1.1" cfg-if = "1.0" @@ -19,10 +23,9 @@ http = "0.2" lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } -mm2_event_stream = { path = "../mm2_event_stream"} -mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } -mm2_state_machine = { path = "../mm2_state_machine" } -parking_lot = { version = "0.12.0", features = ["nightly"] } +mm2_event_stream = { path = "../mm2_event_stream", optional = true } +mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p", optional = true } +parking_lot = { version = "0.12.0", features = ["nightly"], optional = true } prost = "0.10" rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } serde = "1" @@ -30,6 +33,7 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } [target.'cfg(target_arch = "wasm32")'.dependencies] gstuff = { version = "0.7", features = ["nightly"] } +mm2_state_machine = { path = "../mm2_state_machine"} wasm-bindgen = "0.2.86" wasm-bindgen-test = { version = "0.3.2" } wasm-bindgen-futures = "0.4.21" diff --git a/mm2src/mm2_net/src/lib.rs b/mm2src/mm2_net/src/lib.rs index ed70e093fa..379f0f928a 100644 --- a/mm2src/mm2_net/src/lib.rs +++ b/mm2src/mm2_net/src/lib.rs @@ -1,11 +1,13 @@ pub mod grpc_web; -pub mod p2p; +#[cfg(feature = "p2p")] pub mod p2p; pub mod transport; #[cfg(not(target_arch = "wasm32"))] pub mod ip_addr; #[cfg(not(target_arch = "wasm32"))] pub mod native_http; #[cfg(not(target_arch = "wasm32"))] pub mod native_tls; -#[cfg(not(target_arch = "wasm32"))] pub mod network_event; -#[cfg(not(target_arch = "wasm32"))] pub mod sse_handler; +#[cfg(all(feature = "event-stream", not(target_arch = "wasm32")))] +pub mod network_event; +#[cfg(all(feature = "event-stream", not(target_arch = "wasm32")))] +pub mod sse_handler; #[cfg(target_arch = "wasm32")] pub mod wasm_http; #[cfg(target_arch = "wasm32")] pub mod wasm_ws; diff --git a/mm2src/mm2_net/src/wasm_ws.rs b/mm2src/mm2_net/src/wasm_ws.rs index 900e08b53a..661babef3c 100644 --- a/mm2src/mm2_net/src/wasm_ws.rs +++ b/mm2src/mm2_net/src/wasm_ws.rs @@ -684,7 +684,7 @@ mod tests { let (mut outgoing_tx, mut incoming_rx) = spawn_ws_transport( conn_idx, - "wss://electrum1.cipig.net:30017", + "wss://electrum1.cipig.net:30020", &abortable_system.weak_spawner(), ) .expect("!spawn_ws_transport"); diff --git a/mm2src/mm2_test_helpers/src/electrums.rs b/mm2src/mm2_test_helpers/src/electrums.rs index c1cdbfe963..d1673b2f53 100644 --- a/mm2src/mm2_test_helpers/src/electrums.rs +++ b/mm2src/mm2_test_helpers/src/electrums.rs @@ -1,7 +1,7 @@ use serde_json::{json, Value as Json}; #[cfg(target_arch = "wasm32")] -pub fn rick_electrums() -> Vec { +pub fn doc_electrums() -> Vec { vec![ json!({ "url": "electrum1.cipig.net:30020", "protocol": "WSS" }), json!({ "url": "electrum2.cipig.net:30020", "protocol": "WSS" }), @@ -10,7 +10,7 @@ pub fn rick_electrums() -> Vec { } #[cfg(not(target_arch = "wasm32"))] -pub fn rick_electrums() -> Vec { +pub fn doc_electrums() -> Vec { vec![ json!({ "url": "electrum1.cipig.net:10020" }), json!({ "url": "electrum2.cipig.net:10020" }), @@ -18,9 +18,8 @@ pub fn rick_electrums() -> Vec { ] } -#[allow(dead_code)] #[cfg(target_arch = "wasm32")] -pub fn morty_electrums() -> Vec { +pub fn marty_electrums() -> Vec { vec![ json!({ "url": "electrum1.cipig.net:30021", "protocol": "WSS" }), json!({ "url": "electrum2.cipig.net:30021", "protocol": "WSS" }), @@ -28,9 +27,8 @@ pub fn morty_electrums() -> Vec { ] } -#[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] -pub fn morty_electrums() -> Vec { +pub fn marty_electrums() -> Vec { vec![ json!({ "url": "electrum1.cipig.net:10021" }), json!({ "url": "electrum2.cipig.net:10021" }), @@ -38,7 +36,6 @@ pub fn morty_electrums() -> Vec { ] } -#[allow(dead_code)] #[cfg(target_arch = "wasm32")] pub fn btc_electrums() -> Vec { vec![ @@ -57,7 +54,6 @@ pub fn btc_electrums() -> Vec { ] } -#[allow(dead_code)] #[cfg(target_arch = "wasm32")] pub fn tbtc_electrums() -> Vec { vec![ @@ -67,7 +63,6 @@ pub fn tbtc_electrums() -> Vec { ] } -#[allow(dead_code)] #[cfg(not(target_arch = "wasm32"))] pub fn tbtc_electrums() -> Vec { vec![ diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 95e27ad771..d12aad490d 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -139,11 +139,24 @@ pub const MORTY_ELECTRUM_ADDRS: &[&str] = &[ "electrum3.cipig.net:10018", ]; pub const DOC: &str = "DOC"; +#[cfg(not(target_arch = "wasm32"))] pub const DOC_ELECTRUM_ADDRS: &[&str] = &[ "electrum1.cipig.net:10020", "electrum2.cipig.net:10020", "electrum3.cipig.net:10020", ]; +#[cfg(target_arch = "wasm32")] +pub const DOC_ELECTRUM_ADDRS: &[&str] = &[ + "electrum1.cipig.net:30020", + "electrum2.cipig.net:30020", + "electrum3.cipig.net:30020", +]; +pub const MARTY: &str = "MARTY"; +pub const MARTY_ELECTRUM_ADDRS: &[&str] = &[ + "electrum1.cipig.net:10021", + "electrum2.cipig.net:10021", + "electrum3.cipig.net:10021", +]; pub const ZOMBIE_TICKER: &str = "ZOMBIE"; pub const ARRR: &str = "ARRR"; pub const ZOMBIE_ELECTRUMS: &[&str] = &[ From 8276b7c5f7b95a8dedf2cf86a832e491d444d3b3 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Fri, 27 Oct 2023 10:54:02 +0200 Subject: [PATCH 18/40] fix(web3): try next web3 node on response error (#1998) --- .../eth/web3_transport/http_transport.rs | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index 75c747bc82..fb408f44c3 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -211,7 +211,7 @@ async fn send_request( event_handlers.on_outgoing_request(serialized_request.as_bytes()); - let mut req = http::Request::new(serialized_request.clone().into_bytes()); + let mut req = http::Request::new(serialized_request.into_bytes()); *req.method_mut() = http::Method::POST; *req.uri_mut() = node.uri.clone(); req.headers_mut() @@ -249,17 +249,28 @@ async fn send_request( if !status.is_success() { errors.push(Web3RpcError::Transport(format!( - "Server '{:?}' response !200: {}, {}", - node, + "Server: '{}', response !200: {}, {}", + node.uri, status, binprint(&body, b'.') ))); continue; } + let res = match single_response(body, &node.uri.to_string()) { + Ok(r) => r, + Err(err) => { + errors.push(Web3RpcError::InvalidResponse(format!( + "Server: '{}', error: {}", + node.uri, err + ))); + continue; + }, + }; + client_impl.nodes.rotate_left(i); - return single_response(body, &node.uri.to_string()); + return Ok(res); } Err(request_failed_error(&request, &errors)) @@ -274,7 +285,7 @@ async fn send_request( ) -> Result { let serialized_request = to_string(&request); - let mut transport_errors = Vec::new(); + let mut errors = Vec::new(); let mut client_impl = client.0.lock().await; for (i, node) in client_impl.nodes.clone().iter().enumerate() { @@ -283,24 +294,27 @@ async fn send_request( Ok(Some(r)) => r, Ok(None) => serialized_request.clone(), Err(e) => { - transport_errors.push(e); + errors.push(e); continue; }, }; - match send_request_once(serialized_request.clone(), &node.uri, &event_handlers).await { + match send_request_once(serialized_request, &node.uri, &event_handlers).await { Ok(response_json) => { client_impl.nodes.rotate_left(i); return Ok(response_json); }, Err(Error::Transport(e)) => { - transport_errors.push(Web3RpcError::Transport(e.to_string())); + errors.push(Web3RpcError::Transport(format!("Server: '{}', error: {}", node.uri, e))) }, - Err(e) => return Err(e), + Err(e) => errors.push(Web3RpcError::InvalidResponse(format!( + "Server: '{}', error: {}", + node.uri, e + ))), } } - Err(request_failed_error(&request, &transport_errors)) + Err(request_failed_error(&request, &errors)) } #[cfg(target_arch = "wasm32")] @@ -334,8 +348,8 @@ async fn send_request_once( let response: Response = serde_json::from_str(&response_str).map_err(|e| { Error::InvalidResponse(format!( - "url: {}, Error deserializing response: {}, raw response: {:?}", - uri, e, response_str + "Error deserializing response: {}, raw response: {:?}", + e, response_str )) })?; match response { From cfd27994e75f88ce75d4dbfbd1d74b00144b933b Mon Sep 17 00:00:00 2001 From: caglarkaya Date: Fri, 27 Oct 2023 16:52:24 +0300 Subject: [PATCH 19/40] fix(watchtower): fix watchtower taker-side restart bug (#1908) This commit fixes #1887, which causes the swaps to appear failed on the taker side if the watcher already completed it and the taker is restarted. The issue is fixed by checking if the watcher has spent the maker payment or refunded the taker payment before kick-starting an unfinished saved swap. If the watcher has not completed the swap, taker continues the swap itself. If the watcher has completed the swap, the saved swap is also completed by using new events MakerPaymentSpentByWatcher or TakerPaymentRefundedByWatcher. --- mm2src/coins/eth.rs | 243 ++- .../eth/web3_transport/http_transport.rs | 6 +- mm2src/coins/lightning.rs | 11 +- mm2src/coins/lp_coins.rs | 20 + mm2src/coins/qrc20.rs | 8 +- mm2src/coins/solana.rs | 6 +- mm2src/coins/solana/spl.rs | 10 +- mm2src/coins/tendermint/tendermint_coin.rs | 12 +- mm2src/coins/tendermint/tendermint_token.rs | 6 +- mm2src/coins/test_coin.rs | 5 + mm2src/coins/utxo/bch.rs | 13 +- mm2src/coins/utxo/qtum.rs | 13 +- mm2src/coins/utxo/slp.rs | 12 +- mm2src/coins/utxo/utxo_common.rs | 29 +- mm2src/coins/utxo/utxo_standard.rs | 14 +- mm2src/coins/z_coin.rs | 11 +- mm2src/mm2_core/src/mm_ctx.rs | 5 +- ...yment_wait_confirm_failed_taker_saved.json | 2 +- .../recreate_maker_swap_taker_saved.json | 2 +- .../recreate_taker_swap_taker_expected.json | 2 +- ...nt_wait_confirm_failed_taker_expected.json | 2 +- mm2src/mm2_main/src/lp_ordermatch.rs | 8 + mm2src/mm2_main/src/lp_swap.rs | 20 +- .../src/lp_swap/recreate_swap_data.rs | 2 + mm2src/mm2_main/src/lp_swap/saved_swap.rs | 9 +- mm2src/mm2_main/src/lp_swap/swap_watcher.rs | 18 +- mm2src/mm2_main/src/lp_swap/taker_restart.rs | 264 +++ mm2src/mm2_main/src/lp_swap/taker_swap.rs | 235 ++- .../tests/docker_tests/docker_tests_common.rs | 14 +- .../tests/docker_tests/swap_watcher_tests.rs | 1490 ++++++++++++++++- mm2src/mm2_test_helpers/src/for_tests.rs | 49 +- 31 files changed, 2377 insertions(+), 164 deletions(-) create mode 100644 mm2src/mm2_main/src/lp_swap/taker_restart.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 8514af4ab6..3085d0eaa7 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -23,8 +23,9 @@ use super::eth::Action::{Call, Create}; use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_structs::{ContractType, ConvertChain, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; +use crate::{ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; -use bitcrypto::{keccak256, ripemd160, sha256}; +use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; use common::custom_futures::timeout::FutureTimerExt; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError, Timer}; @@ -1472,6 +1473,246 @@ impl WatcherOps for EthCoin { // 1.Validate if taker fee is old } + fn taker_validates_payment_spend_or_refund(&self, input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + let watcher_reward = try_f!(input + .watcher_reward + .clone() + .ok_or_else(|| ValidatePaymentError::WatcherRewardError("Watcher reward not found".to_string()))); + let expected_reward_amount = try_f!(wei_from_big_decimal(&watcher_reward.amount, self.decimals)); + + let expected_swap_contract_address = try_f!(input + .swap_contract_address + .try_to_address() + .map_to_mm(ValidatePaymentError::InvalidParameter)); + + let unsigned: UnverifiedTransaction = try_f!(rlp::decode(&input.payment_tx)); + let tx = + try_f!(SignedEthTx::new(unsigned) + .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); + + let selfi = self.clone(); + let time_lock = try_f!(input + .time_lock + .try_into() + .map_to_mm(ValidatePaymentError::TimelockOverflow)); + let swap_id = selfi.etomic_swap_id(time_lock, &input.secret_hash); + let decimals = self.decimals; + let secret_hash = if input.secret_hash.len() == 32 { + ripemd160(&input.secret_hash).to_vec() + } else { + input.secret_hash.to_vec() + }; + let maker_addr = + try_f!(addr_from_raw_pubkey(&input.maker_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); + + let trade_amount = try_f!(wei_from_big_decimal(&(input.amount), decimals)); + let fut = async move { + match tx.action { + Call(contract_address) => { + if contract_address != expected_swap_contract_address { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction {:?} was sent to wrong address, expected {:?}", + contract_address, expected_swap_contract_address, + ))); + } + }, + Create => { + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Tx action must be Call, found Create instead".to_string(), + )); + }, + }; + + let actual_status = selfi + .payment_status(expected_swap_contract_address, Token::FixedBytes(swap_id.clone())) + .compat() + .await + .map_to_mm(ValidatePaymentError::Transport)?; + let expected_status = match input.spend_type { + WatcherSpendType::MakerPaymentSpend => U256::from(PaymentState::Spent as u8), + WatcherSpendType::TakerPaymentRefund => U256::from(PaymentState::Refunded as u8), + }; + if actual_status != expected_status { + return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( + "Payment state is not {}, got {}", + expected_status, actual_status + ))); + } + + let function_name = match input.spend_type { + WatcherSpendType::MakerPaymentSpend => get_function_name("receiverSpend", true), + WatcherSpendType::TakerPaymentRefund => get_function_name("senderRefund", true), + }; + let function = SWAP_CONTRACT + .function(&function_name) + .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; + + let decoded = decode_contract_call(function, &tx.data) + .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + + let swap_id_input = get_function_input_data(&decoded, function, 0) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if swap_id_input != Token::FixedBytes(swap_id.clone()) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction invalid swap_id arg {:?}, expected {:?}", + swap_id_input, + Token::FixedBytes(swap_id.clone()) + ))); + } + + let hash_input = match input.spend_type { + WatcherSpendType::MakerPaymentSpend => { + let secret_input = get_function_input_data(&decoded, function, 2) + .map_to_mm(ValidatePaymentError::TxDeserializationError)? + .into_fixed_bytes() + .ok_or_else(|| { + ValidatePaymentError::WrongPaymentTx("Invalid type for secret hash argument".to_string()) + })?; + dhash160(&secret_input).to_vec() + }, + WatcherSpendType::TakerPaymentRefund => get_function_input_data(&decoded, function, 2) + .map_to_mm(ValidatePaymentError::TxDeserializationError)? + .into_fixed_bytes() + .ok_or_else(|| { + ValidatePaymentError::WrongPaymentTx("Invalid type for secret argument".to_string()) + })?, + }; + if hash_input != secret_hash { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction secret or secret_hash arg {:?} is invalid, expected {:?}", + hash_input, + Token::FixedBytes(secret_hash), + ))); + } + + let sender_input = get_function_input_data(&decoded, function, 4) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + let expected_sender = match input.spend_type { + WatcherSpendType::MakerPaymentSpend => maker_addr, + WatcherSpendType::TakerPaymentRefund => selfi.my_address, + }; + if sender_input != Token::Address(expected_sender) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction sender arg {:?} is invalid, expected {:?}", + sender_input, + Token::Address(expected_sender) + ))); + } + + let receiver_input = get_function_input_data(&decoded, function, 5) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + let expected_receiver = match input.spend_type { + WatcherSpendType::MakerPaymentSpend => selfi.my_address, + WatcherSpendType::TakerPaymentRefund => maker_addr, + }; + if receiver_input != Token::Address(expected_receiver) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction receiver arg {:?} is invalid, expected {:?}", + receiver_input, + Token::Address(expected_receiver) + ))); + } + + let reward_target_input = get_function_input_data(&decoded, function, 6) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if reward_target_input != Token::Uint(U256::from(watcher_reward.reward_target as u8)) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction reward target arg {:?} is invalid, expected {:?}", + reward_target_input, + Token::Uint(U256::from(watcher_reward.reward_target as u8)) + ))); + } + + let contract_reward_input = get_function_input_data(&decoded, function, 7) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if contract_reward_input != Token::Bool(watcher_reward.send_contract_reward_on_spend) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction sends contract reward on spend arg {:?} is invalid, expected {:?}", + contract_reward_input, + Token::Bool(watcher_reward.send_contract_reward_on_spend) + ))); + } + + let reward_amount_input = get_function_input_data(&decoded, function, 8) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if reward_amount_input != Token::Uint(expected_reward_amount) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction watcher reward amount arg {:?} is invalid, expected {:?}", + reward_amount_input, + Token::Uint(expected_reward_amount) + ))); + } + + if tx.value != U256::zero() { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction value arg {:?} is invalid, expected 0", + tx.value + ))); + } + + match &selfi.coin_type { + EthCoinType::Eth => { + let amount_input = get_function_input_data(&decoded, function, 1) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + let total_amount = match input.spend_type { + WatcherSpendType::MakerPaymentSpend => { + if let RewardTarget::None = watcher_reward.reward_target { + trade_amount + } else { + trade_amount + expected_reward_amount + } + }, + WatcherSpendType::TakerPaymentRefund => trade_amount + expected_reward_amount, + }; + if amount_input != Token::Uint(total_amount) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction amount arg {:?} is invalid, expected {:?}", + amount_input, + Token::Uint(total_amount), + ))); + } + + let token_address_input = get_function_input_data(&decoded, function, 3) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if token_address_input != Token::Address(Address::default()) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction token address arg {:?} is invalid, expected {:?}", + token_address_input, + Token::Address(Address::default()), + ))); + } + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + let amount_input = get_function_input_data(&decoded, function, 1) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if amount_input != Token::Uint(trade_amount) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction amount arg {:?} is invalid, expected {:?}", + amount_input, + Token::Uint(trade_amount), + ))); + } + + let token_address_input = get_function_input_data(&decoded, function, 3) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + if token_address_input != Token::Address(*token_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Transaction token address arg {:?} is invalid, expected {:?}", + token_address_input, + Token::Address(*token_addr), + ))); + } + }, + } + + Ok(()) + }; + Box::new(fut.boxed().compat()) + } + fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { let unsigned: UnverifiedTransaction = try_f!(rlp::decode(&input.payment_tx)); let tx = diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index fb408f44c3..997ff034d7 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -29,8 +29,10 @@ where { let response = serde_json::from_slice(&response).map_err(|e| { Error::InvalidResponse(format!( - "url: {}, Error deserializing response: {}, raw response: {:?}", - rpc_url, e, response + "url: {}, Error deserializing response: {}, raw response: {}", + rpc_url, + e, + String::from_utf8_lossy(&response) )) })?; diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 59b026121b..74d5619c5d 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -24,9 +24,10 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, + VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::bech32::ToBase32; use bitcoin::hashes::Hash; @@ -995,6 +996,10 @@ impl WatcherOps for LightningCoin { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, _input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index eb15ae1ca9..ac063c946c 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -640,6 +640,24 @@ pub struct WatcherValidatePaymentInput { pub maker_coin: MmCoinEnum, } +#[derive(Clone)] +pub enum WatcherSpendType { + TakerPaymentRefund, + MakerPaymentSpend, +} + +#[derive(Clone)] +pub struct ValidateWatcherSpendInput { + pub payment_tx: Vec, + pub maker_pub: Vec, + pub swap_contract_address: Option, + pub time_lock: u64, + pub secret_hash: Vec, + pub amount: BigDecimal, + pub watcher_reward: Option, + pub spend_type: WatcherSpendType, +} + /// Helper struct wrapping arguments for [SwapOps::validate_taker_payment] and [SwapOps::validate_maker_payment]. #[derive(Clone, Debug)] pub struct ValidatePaymentInput { @@ -1039,6 +1057,8 @@ pub trait WatcherOps { fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()>; + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()>; + async fn watcher_search_for_swap_tx_spend( &self, input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 36f8086e70..f556ba5032 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -25,8 +25,8 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; @@ -1147,6 +1147,10 @@ impl WatcherOps for Qrc20Coin { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, _input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 5f7bb9e44d..56178efa7a 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -11,7 +11,7 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; @@ -647,6 +647,10 @@ impl WatcherOps for SolanaCoin { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 134d8204ba..9270302b0b 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -10,9 +10,9 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPayment TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bincode::serialize; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; @@ -468,6 +468,10 @@ impl WatcherOps for SplToken { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 1920c38709..cc569cb1d4 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -25,10 +25,10 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFrom, - WithdrawFut, WithdrawRequest}; + ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFrom, WithdrawFut, WithdrawRequest}; use async_std::prelude::FutureExt as AsyncStdFutureExt; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; @@ -2739,6 +2739,10 @@ impl WatcherOps for TendermintCoin { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + async fn watcher_search_for_swap_tx_spend( &self, _input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index d753a0e61d..061478aa09 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -20,7 +20,7 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFrom, WithdrawFut, WithdrawRequest}; -use crate::{MmCoinEnum, PaymentInstructionArgs, WatcherReward, WatcherRewardError}; +use crate::{MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -498,6 +498,10 @@ impl WatcherOps for TendermintToken { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!(); + } + async fn watcher_search_for_swap_tx_spend( &self, _input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 24408cf506..3991d73e17 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -2,6 +2,7 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; +use crate::ValidateWatcherSpendInput; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, @@ -279,6 +280,10 @@ impl WatcherOps for TestCoin { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index c94179e131..f584ccb6bf 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -17,9 +17,9 @@ use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBal SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use derive_more::Display; @@ -1014,7 +1014,7 @@ impl SwapOps for BchCoin { MmError::err(ValidateInstructionsErr::UnsupportedCoin(self.ticker().to_string())) } - fn is_supported_by_watchers(&self) -> bool { std::env::var("USE_WATCHERS").is_ok() } + fn is_supported_by_watchers(&self) -> bool { true } } #[async_trait] @@ -1096,6 +1096,11 @@ impl WatcherOps for BchCoin { utxo_common::watcher_validate_taker_payment(self, input) } + #[inline] + fn taker_validates_payment_spend_or_refund(&self, input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + utxo_common::validate_payment_spend_or_refund(self, input) + } + async fn watcher_search_for_swap_tx_spend( &self, input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 2f4c57ac6e..e901a53e94 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -32,9 +32,9 @@ use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithD SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawSenderAddress}; + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use ethereum_types::H160; @@ -700,7 +700,7 @@ impl SwapOps for QtumCoin { MmError::err(ValidateInstructionsErr::UnsupportedCoin(self.ticker().to_string())) } - fn is_supported_by_watchers(&self) -> bool { std::env::var("USE_WATCHERS").is_ok() } + fn is_supported_by_watchers(&self) -> bool { true } } #[async_trait] @@ -778,6 +778,11 @@ impl WatcherOps for QtumCoin { utxo_common::watcher_validate_taker_payment(self, input) } + #[inline] + fn taker_validates_payment_spend_or_refund(&self, input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + utxo_common::validate_payment_spend_or_refund(self, input) + } + async fn watcher_search_for_swap_tx_spend( &self, input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 07379fcbbf..e400b3e128 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -22,10 +22,10 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentInput, VerificationError, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest}; + ValidateOtherPubKeyErr, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; @@ -1515,6 +1515,10 @@ impl WatcherOps for SlpToken { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, _input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 4f5970afc2..9f657d02d2 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -23,7 +23,7 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPayment TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentError, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageError, - ValidateTakerPaymentSpendPreimageResult, VerificationError, VerificationResult, + ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; @@ -2299,6 +2299,33 @@ pub fn validate_taker_payment( ) } +pub fn validate_payment_spend_or_refund( + coin: &T, + input: ValidateWatcherSpendInput, +) -> ValidatePaymentFut<()> { + let mut payment_spend_tx: UtxoTx = try_f!(deserialize(input.payment_tx.as_slice())); + payment_spend_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; + + let my_address = try_f!(coin.as_ref().derivation_method.single_addr_or_err()); + let expected_script_pubkey = &output_script(my_address, ScriptType::P2PKH).to_bytes(); + let output = try_f!(payment_spend_tx + .outputs + .get(DEFAULT_SWAP_VOUT) + .ok_or_else(|| ValidatePaymentError::WrongPaymentTx("Payment tx has no outputs".to_string(),))); + + if expected_script_pubkey != &output.script_pubkey { + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx(format!( + "Provided payment tx script pubkey doesn't match expected {:?} {:?}", + output.script_pubkey, expected_script_pubkey + )) + .into(), + )); + } + + Box::new(futures01::future::ok(())) +} + pub fn check_if_my_payment_sent( coin: T, time_lock: u32, diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 47582f8524..a9b5e3aa32 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -31,9 +31,10 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, - ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; + ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, + WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; @@ -467,7 +468,7 @@ impl SwapOps for UtxoStandardCoin { MmError::err(ValidateInstructionsErr::UnsupportedCoin(self.ticker().to_string())) } - fn is_supported_by_watchers(&self) -> bool { std::env::var("USE_WATCHERS").is_ok() } + fn is_supported_by_watchers(&self) -> bool { true } } #[async_trait] @@ -545,6 +546,11 @@ impl WatcherOps for UtxoStandardCoin { utxo_common::watcher_validate_taker_payment(self, input) } + #[inline] + fn taker_validates_payment_spend_or_refund(&self, input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + utxo_common::validate_payment_spend_or_refund(self, input) + } + #[inline] async fn watcher_search_for_swap_tx_spend( &self, diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 047a1cf1fd..af7d816435 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -22,9 +22,10 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionEnum, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, + VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -1608,6 +1609,10 @@ impl WatcherOps for ZCoin { unimplemented!(); } + fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { + unimplemented!() + } + async fn watcher_search_for_swap_tx_spend( &self, _input: WatcherSearchForSwapTxSpendInput<'_>, diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index fc7fc5f0d9..d2537425c1 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -279,10 +279,7 @@ impl MmCtx { pub fn is_watcher(&self) -> bool { self.conf["is_watcher"].as_bool().unwrap_or_default() } - pub fn use_watchers(&self) -> bool { - std::env::var("USE_WATCHERS").is_ok() - //self.conf["use_watchers"].as_bool().unwrap_or(true) - } + pub fn use_watchers(&self) -> bool { self.conf["use_watchers"].as_bool().unwrap_or(true) } pub fn netid(&self) -> u16 { let netid = self.conf["netid"].as_u64().unwrap_or(0); diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json index 4be55eef01..07ebe0d6b9 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json @@ -50,5 +50,5 @@ "gui":"atomicDEX 0.5.1 iOS", "mm_version":"1b065636a", "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"] + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json index 21760c177e..8fe8622f74 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json @@ -62,5 +62,5 @@ "gui":"atomicDEX 0.5.1 iOS", "mm_version":"1b065636a", "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"] + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json index 6fb8382594..cfb9ab66f8 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json @@ -58,5 +58,5 @@ "gui":null, "mm_version":"", "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json index 30c4b256aa..cb3146b2a3 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json @@ -54,5 +54,5 @@ "gui":null, "mm_version":"", "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 2ad35b86e4..d40ac478f2 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -81,6 +81,9 @@ use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, chec CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SecretHashAlgo, SwapConfirmationsSettings, TakerSwap}; +#[cfg(any(test, feature = "run-docker-tests"))] +use crate::mm2::lp_swap::taker_swap::FailAt; + pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; pub use orderbook_rpc::{orderbook_rpc, orderbook_rpc_v2}; @@ -3119,6 +3122,9 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat _ => todo!("implement fallback to the old protocol here"), } } else { + #[cfg(any(test, feature = "run-docker-tests"))] + let fail_at = std::env::var("TAKER_FAIL_AT").map(FailAt::from).ok(); + let taker_swap = TakerSwap::new( ctx.clone(), maker, @@ -3132,6 +3138,8 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat taker_coin, locktime, taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + #[cfg(any(test, feature = "run-docker-tests"))] + fail_at, ); run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx).await } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 3fa86a7bd2..c2620814a6 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -103,7 +103,9 @@ use std::sync::atomic::{AtomicU64, Ordering}; #[rustfmt::skip] mod swap_v2_pb; #[path = "lp_swap/swap_watcher.rs"] pub(crate) mod swap_watcher; -#[path = "lp_swap/taker_swap.rs"] mod taker_swap; +#[path = "lp_swap/taker_restart.rs"] +pub(crate) mod taker_restart; +#[path = "lp_swap/taker_swap.rs"] pub(crate) mod taker_swap; #[path = "lp_swap/taker_swap_v2.rs"] pub mod taker_swap_v2; #[path = "lp_swap/trade_preimage.rs"] mod trade_preimage; @@ -131,13 +133,13 @@ pub use swap_watcher::{process_watcher_msg, watcher_topic, TakerSwapWatcherData, use taker_swap::TakerSwapEvent; pub use taker_swap::{calc_max_taker_vol, check_balance_for_taker_swap, max_taker_vol, max_taker_vol_from_available, run_taker_swap, taker_swap_trade_preimage, RunTakerSwapInput, TakerSavedSwap, TakerSwap, - TakerSwapData, TakerSwapPreparedParams, TakerTradePreimage, WATCHER_MESSAGE_SENT_LOG}; + TakerSwapData, TakerSwapPreparedParams, TakerTradePreimage, MAKER_PAYMENT_SPENT_BY_WATCHER_LOG, + REFUND_TEST_FAILURE_LOG, WATCHER_MESSAGE_SENT_LOG}; pub use trade_preimage::trade_preimage_rpc; pub const SWAP_PREFIX: TopicPrefix = "swap"; - pub const SWAP_V2_PREFIX: TopicPrefix = "swapv2"; - +pub const SWAP_FINISHED_LOG: &str = "Swap finished: "; pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; const NEGOTIATE_SEND_INTERVAL: f64 = 30.; @@ -1213,6 +1215,7 @@ pub async fn swap_kick_starts(ctx: MmArc) -> Result, String> { let swaps = try_s!(SavedSwap::load_all_my_swaps_from_db(&ctx).await); for swap in swaps { if swap.is_finished() { + info!("{} {}", SWAP_FINISHED_LOG, swap.uuid()); continue; } @@ -2082,7 +2085,10 @@ mod lp_swap_tests { maker_swap.fail_at = maker_fail_at; - let mut taker_swap = TakerSwap::new( + #[cfg(any(test, feature = "run-docker-tests"))] + let fail_at = std::env::var("TAKER_FAIL_AT").map(taker_swap::FailAt::from).ok(); + + let taker_swap = TakerSwap::new( taker_ctx.clone(), maker_key_pair.public().compressed_unprefixed().unwrap().into(), maker_amount.into(), @@ -2095,10 +2101,10 @@ mod lp_swap_tests { morty_taker.into(), lock_duration, None, + #[cfg(any(test, feature = "run-docker-tests"))] + fail_at, ); - taker_swap.fail_at = taker_fail_at; - block_on(futures::future::join( run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), maker_ctx.clone()), run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), taker_ctx.clone()), diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index a816695d84..56e7877b70 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -264,6 +264,7 @@ fn convert_taker_to_maker_events( | TakerSwapEvent::MakerPaymentWaitConfirmStarted | TakerSwapEvent::MakerPaymentValidatedAndConfirmed | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::MakerPaymentSpendFailed(_) // We don't know the reason at the moment, so we rely on the errors handling above. | TakerSwapEvent::WatcherMessageSent(_,_) @@ -272,6 +273,7 @@ fn convert_taker_to_maker_events( | TakerSwapEvent::TakerPaymentRefunded(_) | TakerSwapEvent::TakerPaymentRefundFailed(_) | TakerSwapEvent::TakerPaymentRefundFinished + | TakerSwapEvent::TakerPaymentRefundedByWatcher(_) | TakerSwapEvent::Finished => {} } } diff --git a/mm2src/mm2_main/src/lp_swap/saved_swap.rs b/mm2src/mm2_main/src/lp_swap/saved_swap.rs index 3519d45596..265c1a5142 100644 --- a/mm2src/mm2_main/src/lp_swap/saved_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/saved_swap.rs @@ -56,6 +56,13 @@ impl SavedSwap { } } + pub fn watcher_message_sent(&self) -> bool { + match &self { + SavedSwap::Taker(taker_swap) => taker_swap.watcher_message_sent(), + _ => false, + } + } + pub fn uuid(&self) -> &Uuid { match self { SavedSwap::Maker(swap) => &swap.uuid, @@ -104,7 +111,7 @@ impl SavedSwap { Ok(try_s!(maker_swap.recover_funds().await)) }, SavedSwap::Taker(saved) => { - let (taker_swap, _) = try_s!(TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, saved)); + let (taker_swap, _) = try_s!(TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, saved).await); Ok(try_s!(taker_swap.recover_funds().await)) }, } diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index bd0d0f564f..80eb41f4e3 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -1,6 +1,7 @@ use super::{broadcast_p2p_tx_msg, get_payment_locktime, lp_coinfind, taker_payment_spend_deadline, tx_helper_topic, H256Json, SwapsContext, WAIT_CONFIRM_INTERVAL_SEC}; use crate::mm2::lp_network::{P2PRequestError, P2PRequestResult}; + use crate::mm2::MmError; use async_trait::async_trait; use coins::{CanRefundHtlc, ConfirmPaymentInput, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, @@ -65,9 +66,9 @@ impl WatcherStateMachine { pub struct WatcherConf { #[serde(default = "common::sixty_f64")] wait_taker_payment: f64, - #[serde(default = "common::one_f64")] + #[serde(default = "default_watcher_maker_payment_spend_factor")] wait_maker_payment_spend_factor: f64, - #[serde(default = "common::one_and_half_f64")] + #[serde(default = "default_watcher_refund_factor")] refund_start_factor: f64, #[serde(default = "common::three_hundred_f64")] search_interval: f64, @@ -77,13 +78,17 @@ impl Default for WatcherConf { fn default() -> Self { WatcherConf { wait_taker_payment: common::sixty_f64(), - wait_maker_payment_spend_factor: common::one_f64(), - refund_start_factor: common::one_and_half_f64(), + wait_maker_payment_spend_factor: default_watcher_maker_payment_spend_factor(), + refund_start_factor: default_watcher_refund_factor(), search_interval: common::three_hundred_f64(), } } } +pub fn default_watcher_maker_payment_spend_factor() -> f64 { common::one_f64() } + +pub fn default_watcher_refund_factor() -> f64 { common::one_and_half_f64() } + #[derive(Clone, Debug, Deserialize, Serialize)] pub enum SwapWatcherMsg { TakerSwapWatcherMsg(TakerSwapWatcherData), @@ -179,6 +184,7 @@ impl State for ValidateTakerFee { type StateMachine = WatcherStateMachine; async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { + debug!("Watcher validate taker fee"); let validated_f = watcher_ctx .taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { @@ -206,6 +212,7 @@ impl State for ValidateTakerPayment { type StateMachine = WatcherStateMachine; async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { + debug!("Watcher validate taker payment"); let taker_payment_spend_deadline = taker_payment_spend_deadline(watcher_ctx.data.swap_started_at, watcher_ctx.data.lock_duration); @@ -279,6 +286,7 @@ impl State for WaitForTakerPaymentSpend { type StateMachine = WatcherStateMachine; async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { + debug!("Watcher wait for taker payment spend"); let payment_search_interval = watcher_ctx.conf.search_interval; let wait_until = watcher_ctx.refund_start_time(); let search_input = WatcherSearchForSwapTxSpendInput { @@ -383,6 +391,7 @@ impl State for SpendMakerPayment { type StateMachine = WatcherStateMachine; async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { + debug!("Watcher spend maker payment"); let spend_fut = watcher_ctx .maker_coin .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { @@ -434,6 +443,7 @@ impl State for RefundTakerPayment { type StateMachine = WatcherStateMachine; async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { + debug!("Watcher refund taker payment"); if std::env::var("USE_TEST_LOCKTIME").is_err() { loop { match watcher_ctx diff --git a/mm2src/mm2_main/src/lp_swap/taker_restart.rs b/mm2src/mm2_main/src/lp_swap/taker_restart.rs new file mode 100644 index 0000000000..431d1a7c32 --- /dev/null +++ b/mm2src/mm2_main/src/lp_swap/taker_restart.rs @@ -0,0 +1,264 @@ +use super::taker_swap::TakerSwapCommand; +use super::{AtomicSwap, TakerSavedSwap, TakerSwap}; +use crate::mm2::lp_swap::taker_swap::{TakerPaymentSpentData, TakerSavedEvent, TakerSwapEvent}; +use crate::mm2::lp_swap::{SavedSwap, SavedSwapIo, TransactionIdentifier, MAKER_PAYMENT_SPENT_BY_WATCHER_LOG}; +use coins::{FoundSwapTxSpend, SearchForSwapTxSpendInput, TransactionEnum, ValidateWatcherSpendInput, WatcherSpendType}; +use common::log::info; +use common::{now_ms, Future01CompatExt}; +use mm2_core::mm_ctx::MmArc; +use rpc::v1::types::{Bytes, H256}; +use std::sync::atomic::Ordering; + +#[cfg(not(any(test, feature = "run-docker-tests")))] +use super::swap_watcher::{default_watcher_maker_payment_spend_factor, default_watcher_refund_factor}; + +#[cfg(not(any(test, feature = "run-docker-tests")))] +use common::now_sec; + +pub async fn get_command_based_on_watcher_activity( + ctx: &MmArc, + swap: &TakerSwap, + mut saved: TakerSavedSwap, + command: TakerSwapCommand, +) -> Result { + #[cfg(not(any(test, feature = "run-docker-tests")))] + { + let watcher_refund_time = swap.r().data.started_at + + (default_watcher_maker_payment_spend_factor() * swap.r().data.lock_duration as f64) as u64; + if now_sec() < watcher_refund_time { + return Ok(command); + } + } + + match command { + TakerSwapCommand::Start => Ok(command), + TakerSwapCommand::Negotiate => Ok(command), + TakerSwapCommand::SendTakerFee => Ok(command), + TakerSwapCommand::WaitForMakerPayment => Ok(command), + TakerSwapCommand::ValidateMakerPayment => Ok(command), + TakerSwapCommand::SendTakerPayment => Ok(command), + TakerSwapCommand::WaitForTakerPaymentSpend => match check_taker_payment_spend(swap).await { + Ok(Some(FoundSwapTxSpend::Spent(taker_payment_spend_tx))) => { + add_taker_payment_spent_event(swap, &mut saved, &taker_payment_spend_tx).await?; + check_maker_payment_spend_and_add_event(ctx, swap, saved).await + }, + Ok(Some(FoundSwapTxSpend::Refunded(taker_payment_refund_tx))) => { + add_taker_payment_refunded_by_watcher_event(ctx, swap, saved, taker_payment_refund_tx).await + }, + Ok(None) => Ok(command), + Err(e) => ERR!("Error {} when trying to find taker payment spend", e), + }, + TakerSwapCommand::SpendMakerPayment => check_maker_payment_spend_and_add_event(ctx, swap, saved).await, + TakerSwapCommand::PrepareForTakerPaymentRefund | TakerSwapCommand::RefundTakerPayment => { + #[cfg(not(any(test, feature = "run-docker-tests")))] + { + let watcher_refund_time = swap.r().data.started_at + + (default_watcher_refund_factor() * swap.r().data.lock_duration as f64) as u64; + if now_sec() < watcher_refund_time { + return Ok(command); + } + } + + match check_taker_payment_spend(swap).await { + Ok(Some(FoundSwapTxSpend::Spent(_))) => ERR!("Taker payment is not expected to be spent at this point"), + Ok(Some(FoundSwapTxSpend::Refunded(taker_payment_refund_tx))) => { + add_taker_payment_refunded_by_watcher_event(ctx, swap, saved, taker_payment_refund_tx).await + }, + Ok(None) => Ok(command), + Err(e) => ERR!("Error {} when trying to find taker payment spend", e), + } + }, + TakerSwapCommand::FinalizeTakerPaymentRefund => Ok(command), + TakerSwapCommand::Finish => Ok(command), + } +} + +pub async fn check_maker_payment_spend_and_add_event( + ctx: &MmArc, + swap: &TakerSwap, + mut saved: TakerSavedSwap, +) -> Result { + let other_maker_coin_htlc_pub = swap.r().other_maker_coin_htlc_pub; + let secret_hash = swap.r().secret_hash.0.clone(); + let maker_coin_start_block = swap.r().data.maker_coin_start_block; + let maker_coin_swap_contract_address = swap.r().data.maker_coin_swap_contract_address.clone(); + + let maker_payment = match &swap.r().maker_payment { + Some(tx) => tx.tx_hex.0.clone(), + None => return ERR!("No info about maker payment, swap is not recoverable"), + }; + let unique_data = swap.unique_swap_data(); + let watcher_reward = swap.r().watcher_reward; + + let maker_payment_spend_tx = match swap.maker_coin + .search_for_swap_tx_spend_other(SearchForSwapTxSpendInput { + time_lock: swap.maker_payment_lock.load(Ordering::Relaxed), + other_pub: other_maker_coin_htlc_pub.as_slice(), + secret_hash: &secret_hash, + tx: &maker_payment, + search_from_block: maker_coin_start_block, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + watcher_reward, + }) + .await { + Ok(Some(FoundSwapTxSpend::Spent(maker_payment_spend_tx))) => maker_payment_spend_tx, + Ok(Some(FoundSwapTxSpend::Refunded(maker_payment_refund_tx))) => return ERR!("Maker has cheated by both spending the taker payment, and refunding the maker payment with transaction {:#?}", maker_payment_refund_tx.tx_hash()), + Ok(None) => return Ok(TakerSwapCommand::SpendMakerPayment), + Err(e) => return ERR!("Error {} when trying to find maker payment spend", e) + }; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend_tx.tx_hex(), + maker_pub: other_maker_coin_htlc_pub.to_vec(), + swap_contract_address: maker_coin_swap_contract_address, + time_lock: swap.maker_payment_lock.load(Ordering::Relaxed), + secret_hash: secret_hash.clone(), + amount: swap.maker_amount.to_decimal(), + watcher_reward: None, + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + swap.maker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .compat() + .await + .map_err(|e| e.to_string())?; + + let tx_hash = maker_payment_spend_tx.tx_hash(); + info!("Watcher maker payment spend tx {:02x}", tx_hash); + let tx_ident = TransactionIdentifier { + tx_hex: Bytes::from(maker_payment_spend_tx.tx_hex()), + tx_hash, + }; + + let event = TakerSwapEvent::MakerPaymentSpentByWatcher(tx_ident); + let to_save = TakerSavedEvent { + timestamp: now_ms(), + event, + }; + saved.events.push(to_save); + let new_swap = SavedSwap::Taker(saved); + try_s!(new_swap.save_to_db(ctx).await); + info!("{}", MAKER_PAYMENT_SPENT_BY_WATCHER_LOG); + Ok(TakerSwapCommand::Finish) +} + +pub async fn check_taker_payment_spend(swap: &TakerSwap) -> Result, String> { + let taker_payment = match &swap.r().taker_payment { + Some(tx) => tx.tx_hex.0.clone(), + None => return ERR!("No info about taker payment, swap is not recoverable"), + }; + + let other_taker_coin_htlc_pub = swap.r().other_taker_coin_htlc_pub; + let taker_coin_start_block = swap.r().data.taker_coin_start_block; + let taker_coin_swap_contract_address = swap.r().data.taker_coin_swap_contract_address.clone(); + + let taker_payment_lock = match std::env::var("USE_TEST_LOCKTIME") { + Ok(_) => swap.r().data.started_at, + Err(_) => swap.r().data.taker_payment_lock, + }; + let secret_hash = swap.r().secret_hash.0.clone(); + let unique_data = swap.unique_swap_data(); + let watcher_reward = swap.r().watcher_reward; + + swap.taker_coin + .search_for_swap_tx_spend_my(SearchForSwapTxSpendInput { + time_lock: taker_payment_lock, + other_pub: other_taker_coin_htlc_pub.as_slice(), + secret_hash: &secret_hash, + tx: &taker_payment, + search_from_block: taker_coin_start_block, + swap_contract_address: &taker_coin_swap_contract_address, + swap_unique_data: &unique_data, + watcher_reward, + }) + .await +} + +pub async fn add_taker_payment_spent_event( + swap: &TakerSwap, + saved: &mut TakerSavedSwap, + taker_payment_spend_tx: &TransactionEnum, +) -> Result<(), String> { + let secret_hash = swap.r().secret_hash.0.clone(); + let watcher_reward = swap.r().watcher_reward; + + let tx_hash = taker_payment_spend_tx.tx_hash(); + info!("Taker payment spend tx {:02x}", tx_hash); + let tx_ident = TransactionIdentifier { + tx_hex: Bytes::from(taker_payment_spend_tx.tx_hex()), + tx_hash, + }; + let secret = match swap + .taker_coin + .extract_secret(&secret_hash, &tx_ident.tx_hex, watcher_reward) + .await + { + Ok(bytes) => H256::from(bytes.as_slice()), + Err(_) => { + return ERR!("Could not extract secret from taker payment spend transaction"); + }, + }; + + let event = TakerSwapEvent::TakerPaymentSpent(TakerPaymentSpentData { + transaction: tx_ident, + secret, + }); + let to_save = TakerSavedEvent { + timestamp: now_ms(), + event, + }; + saved.events.push(to_save); + Ok(()) +} + +pub async fn add_taker_payment_refunded_by_watcher_event( + ctx: &MmArc, + swap: &TakerSwap, + mut saved: TakerSavedSwap, + taker_payment_refund_tx: TransactionEnum, +) -> Result { + let other_maker_coin_htlc_pub = swap.r().other_maker_coin_htlc_pub; + let taker_coin_swap_contract_address = swap.r().data.taker_coin_swap_contract_address.clone(); + let taker_payment_lock = match std::env::var("USE_TEST_LOCKTIME") { + Ok(_) => swap.r().data.started_at, + Err(_) => swap.r().data.taker_payment_lock, + }; + let secret_hash = swap.r().secret_hash.0.clone(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund_tx.tx_hex(), + maker_pub: other_maker_coin_htlc_pub.to_vec(), + swap_contract_address: taker_coin_swap_contract_address, + time_lock: taker_payment_lock, + secret_hash: secret_hash.clone(), + amount: swap.taker_amount.to_decimal(), + watcher_reward: None, + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + swap.taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .compat() + .await + .map_err(|e| e.to_string())?; + + let tx_hash = taker_payment_refund_tx.tx_hash(); + info!("Taker refund tx hash {:02x}", tx_hash); + let tx_ident = TransactionIdentifier { + tx_hex: Bytes::from(taker_payment_refund_tx.tx_hex()), + tx_hash, + }; + + let event = TakerSwapEvent::TakerPaymentRefundedByWatcher(Some(tx_ident)); + let to_save = TakerSavedEvent { + timestamp: now_ms(), + event, + }; + saved.events.push(to_save); + + let new_swap = SavedSwap::Taker(saved); + try_s!(new_swap.save_to_db(ctx).await); + info!("Taker payment is refunded by the watcher"); + Ok(TakerSwapCommand::Finish) +} diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 168d8fc1ac..1afdcbb5f8 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -12,6 +12,7 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_msg SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL_SEC}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::TakerOrderBuilder; +use crate::mm2::lp_swap::taker_restart::get_command_based_on_watcher_activity; use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, wait_for_maker_payment_conf_duration, TakerSwapWatcherData}; use coins::lp_price::fetch_swap_coins_price; @@ -56,7 +57,7 @@ pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ "Finished", ]; -pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 12] = [ +pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ "Started", "Negotiated", "TakerFeeSent", @@ -68,10 +69,11 @@ pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 12] = [ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpentByWatcher", "Finished", ]; -pub const TAKER_ERROR_EVENTS: [&str; 15] = [ +pub const TAKER_ERROR_EVENTS: [&str; 16] = [ "StartFailed", "NegotiateFailed", "TakerFeeSendFailed", @@ -85,11 +87,14 @@ pub const TAKER_ERROR_EVENTS: [&str; 15] = [ "TakerPaymentWaitRefundStarted", "TakerPaymentRefundStarted", "TakerPaymentRefunded", + "TakerPaymentRefundedByWatcher", "TakerPaymentRefundFailed", "TakerPaymentRefundFinished", ]; +pub const REFUND_TEST_FAILURE_LOG: &str = "Explicit refund test failure..."; pub const WATCHER_MESSAGE_SENT_LOG: &str = "Watcher message sent..."; +pub const MAKER_PAYMENT_SPENT_BY_WATCHER_LOG: &str = "Maker payment is spent by the watcher..."; #[cfg(not(target_arch = "wasm32"))] pub fn stats_taker_swap_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("SWAPS").join("STATS").join("TAKER") } @@ -173,6 +178,7 @@ impl TakerSavedEvent { TakerSwapEvent::TakerPaymentWaitForSpendFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::TakerPaymentWaitConfirmFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::MakerPaymentSpent(_) => Some(TakerSwapCommand::Finish), + TakerSwapEvent::MakerPaymentSpentByWatcher(_) => Some(TakerSwapCommand::Finish), TakerSwapEvent::MakerPaymentSpendFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } => { Some(TakerSwapCommand::PrepareForTakerPaymentRefund) @@ -181,6 +187,7 @@ impl TakerSavedEvent { TakerSwapEvent::TakerPaymentRefunded(_) => Some(TakerSwapCommand::FinalizeTakerPaymentRefund), TakerSwapEvent::TakerPaymentRefundFailed(_) => Some(TakerSwapCommand::Finish), TakerSwapEvent::TakerPaymentRefundFinished => Some(TakerSwapCommand::Finish), + TakerSwapEvent::TakerPaymentRefundedByWatcher(_) => Some(TakerSwapCommand::Finish), TakerSwapEvent::Finished => None, } } @@ -258,7 +265,9 @@ impl TakerSavedSwap { | TakerSwapEvent::TakerFeeSendFailed(_) | TakerSwapEvent::MakerPaymentValidateFailed(_) | TakerSwapEvent::TakerPaymentRefunded(_) + | TakerSwapEvent::TakerPaymentRefundedByWatcher(_) | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::MakerPaymentWaitConfirmFailed(_) => { return false; }, @@ -292,16 +301,20 @@ impl TakerSavedSwap { if !self.is_finished() { return ERR!("Can not determine is_success state for not finished swap"); } - for event in self.events.iter() { if event.event.is_error() { return Ok(false); } } - Ok(true) } + pub fn watcher_message_sent(&self) -> bool { + self.events + .iter() + .any(|e| matches!(e.event, TakerSwapEvent::WatcherMessageSent(_, _))) + } + pub async fn fetch_and_set_usd_prices(&mut self) { if let Some(rates) = fetch_swap_coins_price(self.maker_coin.clone(), self.taker_coin.clone()).await { self.maker_coin_usd_price = Some(rates.base); @@ -530,39 +543,45 @@ pub struct TakerSwapData { } pub struct TakerSwapMut { - data: TakerSwapData, - other_maker_coin_htlc_pub: H264, - other_taker_coin_htlc_pub: H264, + pub data: TakerSwapData, + pub other_maker_coin_htlc_pub: H264, + pub other_taker_coin_htlc_pub: H264, taker_fee: Option, - maker_payment: Option, - taker_payment: Option, + pub maker_payment: Option, + pub taker_payment: Option, maker_payment_spend: Option, taker_payment_spend: Option, maker_payment_spend_preimage: Option>, taker_payment_refund_preimage: Option>, taker_payment_refund: Option, - secret_hash: BytesJson, + pub secret_hash: BytesJson, secret: H256Json, - watcher_reward: bool, + pub watcher_reward: bool, reward_amount: Option, payment_instructions: Option, } -#[cfg(test)] -#[derive(Eq, PartialEq)] -pub(super) enum FailAt { +#[cfg(any(test, feature = "run-docker-tests"))] +#[derive(Eq, PartialEq, Debug)] +pub enum FailAt { TakerPayment, + WaitForTakerPaymentSpendPanic, MakerPaymentSpend, + MakerPaymentSpendPanic, TakerPaymentRefund, + TakerPaymentRefundPanic, } -#[cfg(test)] +#[cfg(any(test, feature = "run-docker-tests"))] impl From for FailAt { fn from(str: String) -> Self { match str.as_str() { "taker_payment" => FailAt::TakerPayment, + "wait_for_taker_payment_spend_panic" => FailAt::WaitForTakerPaymentSpendPanic, "maker_payment_spend" => FailAt::MakerPaymentSpend, + "maker_payment_spend_panic" => FailAt::MakerPaymentSpendPanic, "taker_payment_refund" => FailAt::TakerPaymentRefund, + "taker_payment_refund_panic" => FailAt::TakerPaymentRefundPanic, _ => panic!("Invalid TAKER_FAIL_AT value"), } } @@ -570,15 +589,15 @@ impl From for FailAt { pub struct TakerSwap { ctx: MmArc, - maker_coin: MmCoinEnum, - taker_coin: MmCoinEnum, - maker_amount: MmNumber, - taker_amount: MmNumber, + pub maker_coin: MmCoinEnum, + pub taker_coin: MmCoinEnum, + pub maker_amount: MmNumber, + pub taker_amount: MmNumber, my_persistent_pub: H264, maker: bits256, uuid: Uuid, my_order_uuid: Option, - maker_payment_lock: AtomicU64, + pub maker_payment_lock: AtomicU64, maker_payment_confirmed: AtomicBool, errors: PaMutex>, finished_at: AtomicU64, @@ -586,7 +605,7 @@ pub struct TakerSwap { conf_settings: SwapConfirmationsSettings, payment_locktime: u64, p2p_privkey: Option, - #[cfg(test)] + #[cfg(any(test, feature = "run-docker-tests"))] pub(super) fail_at: Option, } @@ -637,12 +656,14 @@ pub enum TakerSwapEvent { TakerPaymentSpent(TakerPaymentSpentData), TakerPaymentWaitForSpendFailed(SwapError), MakerPaymentSpent(TransactionIdentifier), + MakerPaymentSpentByWatcher(TransactionIdentifier), MakerPaymentSpendFailed(SwapError), TakerPaymentWaitRefundStarted { wait_until: u64 }, TakerPaymentRefundStarted, TakerPaymentRefunded(Option), TakerPaymentRefundFailed(SwapError), TakerPaymentRefundFinished, + TakerPaymentRefundedByWatcher(Option), Finished, } @@ -673,6 +694,7 @@ impl TakerSwapEvent { TakerSwapEvent::TakerPaymentSpent(_) => "Taker payment spent...".to_owned(), TakerSwapEvent::TakerPaymentWaitForSpendFailed(_) => "Taker payment wait for spend failed...".to_owned(), TakerSwapEvent::MakerPaymentSpent(_) => "Maker payment spent...".to_owned(), + TakerSwapEvent::MakerPaymentSpentByWatcher(_) => "Maker payment spent by watcher...".to_owned(), TakerSwapEvent::MakerPaymentSpendFailed(_) => "Maker payment spend failed...".to_owned(), TakerSwapEvent::TakerPaymentWaitRefundStarted { wait_until } => { format!("Taker payment wait refund till {} started...", wait_until) @@ -681,6 +703,7 @@ impl TakerSwapEvent { TakerSwapEvent::TakerPaymentRefunded(_) => "Taker payment refunded...".to_owned(), TakerSwapEvent::TakerPaymentRefundFailed(_) => "Taker payment refund failed...".to_owned(), TakerSwapEvent::TakerPaymentRefundFinished => "Taker payment refund finished...".to_owned(), + TakerSwapEvent::TakerPaymentRefundedByWatcher(_) => "Taker payment refunded by watcher...".to_owned(), TakerSwapEvent::Finished => "Finished".to_owned(), } } @@ -706,6 +729,7 @@ impl TakerSwapEvent { | TakerSwapEvent::TakerPaymentSent(_) | TakerSwapEvent::TakerPaymentSpent(_) | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::Finished ) } @@ -734,7 +758,7 @@ impl TakerSwap { fn w(&self) -> RwLockWriteGuard { self.mutable.write().unwrap() } #[inline] - fn r(&self) -> RwLockReadGuard { self.mutable.read().unwrap() } + pub fn r(&self) -> RwLockReadGuard { self.mutable.read().unwrap() } #[inline] fn my_maker_coin_htlc_pub(&self) -> H264Json { @@ -809,12 +833,14 @@ impl TakerSwap { }, TakerSwapEvent::TakerPaymentWaitForSpendFailed(err) => self.errors.lock().push(err), TakerSwapEvent::MakerPaymentSpent(tx) => self.w().maker_payment_spend = Some(tx), + TakerSwapEvent::MakerPaymentSpentByWatcher(tx) => self.w().maker_payment_spend = Some(tx), TakerSwapEvent::MakerPaymentSpendFailed(err) => self.errors.lock().push(err), TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } => (), TakerSwapEvent::TakerPaymentRefundStarted => (), TakerSwapEvent::TakerPaymentRefunded(tx) => self.w().taker_payment_refund = tx, TakerSwapEvent::TakerPaymentRefundFailed(err) => self.errors.lock().push(err), TakerSwapEvent::TakerPaymentRefundFinished => (), + TakerSwapEvent::TakerPaymentRefundedByWatcher(tx) => self.w().taker_payment_refund = tx, TakerSwapEvent::Finished => self.finished_at.store(now_sec(), Ordering::Relaxed), } } @@ -853,6 +879,7 @@ impl TakerSwap { taker_coin: MmCoinEnum, payment_locktime: u64, p2p_privkey: Option, + #[cfg(any(test, feature = "run-docker-tests"))] fail_at: Option, ) -> Self { TakerSwap { maker_coin, @@ -889,8 +916,8 @@ impl TakerSwap { payment_instructions: None, }), ctx, - #[cfg(test)] - fail_at: None, + #[cfg(any(test, feature = "run-docker-tests"))] + fail_at, } } @@ -1600,6 +1627,7 @@ impl TakerSwap { Some(maker_payment_spend.tx_hex()), Some(taker_payment_refund.tx_hex()), )); + info!("{}", WATCHER_MESSAGE_SENT_LOG); }, Err(e) => error!( "The watcher message could not be sent, error creating at least one of the preimages: {}", @@ -1675,11 +1703,22 @@ impl TakerSwap { ])); } + #[cfg(any(test, feature = "run-docker-tests"))] + if self.fail_at == Some(FailAt::WaitForTakerPaymentSpendPanic) { + panic!("Taker panicked unexpectedly at wait for taker payment spend"); + } + info!("Taker payment confirmed"); + + let wait_until = match std::env::var("USE_TEST_LOCKTIME") { + Ok(_) => self.r().data.started_at, + Err(_) => self.r().data.taker_payment_lock, + }; + let f = self.taker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { tx_bytes: &self.r().taker_payment.clone().unwrap().tx_hex, secret_hash: &self.r().secret_hash.0, - wait_until: self.r().data.taker_payment_lock, + wait_until, from_block: self.r().data.taker_coin_start_block, swap_contract_address: &self.r().data.taker_coin_swap_contract_address, check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, @@ -1729,12 +1768,15 @@ impl TakerSwap { } async fn spend_maker_payment(&self) -> Result<(Option, Vec), String> { - #[cfg(test)] + #[cfg(any(test, feature = "run-docker-tests"))] if self.fail_at == Some(FailAt::MakerPaymentSpend) { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::MakerPaymentSpendFailed("Explicit test failure".into()), ])); + } else if self.fail_at == Some(FailAt::MakerPaymentSpendPanic) { + panic!("Taker panicked unexpectedly at maker payment spend"); } + let spend_fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { other_payment_tx: &self.r().maker_payment.clone().unwrap().tx_hex, time_lock: self.maker_payment_lock.load(Ordering::Relaxed), @@ -1796,11 +1838,13 @@ impl TakerSwap { } async fn refund_taker_payment(&self) -> Result<(Option, Vec), String> { - #[cfg(test)] + #[cfg(any(test, feature = "run-docker-tests"))] if self.fail_at == Some(FailAt::TakerPaymentRefund) { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::TakerPaymentRefundFailed("Explicit test failure".into()), ])); + } else if self.fail_at == Some(FailAt::TakerPaymentRefundPanic) { + panic!("{}", REFUND_TEST_FAILURE_LOG); } let taker_payment = self.r().taker_payment.clone().unwrap().tx_hex; @@ -1909,10 +1953,10 @@ impl TakerSwap { SavedSwap::Taker(swap) => swap, SavedSwap::Maker(_) => return ERR!("Can not load TakerSwap from SavedSwap::Maker uuid: {}", swap_uuid), }; - Self::load_from_saved(ctx, maker_coin, taker_coin, saved) + Self::load_from_saved(ctx, maker_coin, taker_coin, saved).await } - pub fn load_from_saved( + pub async fn load_from_saved( ctx: MmArc, maker_coin: MmCoinEnum, taker_coin: MmCoinEnum, @@ -1951,8 +1995,11 @@ impl TakerSwap { .unwrap_or_else(|| taker_coin.requires_notarization()), }; + #[cfg(any(test, feature = "run-docker-tests"))] + let fail_at = std::env::var("TAKER_FAIL_AT").map(FailAt::from).ok(); + let swap = TakerSwap::new( - ctx, + ctx.clone(), maker, data.maker_amount.clone().into(), data.taker_amount.clone().into(), @@ -1960,16 +2007,32 @@ impl TakerSwap { saved.uuid, Some(saved.uuid), conf_settings, - maker_coin, - taker_coin, + maker_coin.clone(), + taker_coin.clone(), data.lock_duration, data.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + #[cfg(any(test, feature = "run-docker-tests"))] + fail_at, ); - let command = saved.events.last().unwrap().get_command(); - for saved_event in saved.events { - swap.apply_event(saved_event.event); + + for saved_event in &saved.events { + swap.apply_event(saved_event.event.clone()); } - Ok((swap, command)) + + let mut command = match saved.events.last().unwrap().get_command() { + Some(command) => command, + None => return Ok((swap, None)), + }; + + if taker_coin.is_supported_by_watchers() + && maker_coin.is_supported_by_watchers() + && saved.watcher_message_sent() + { + command = get_command_based_on_watcher_activity(&ctx, &swap, saved, command).await?; + } + drop_mutability!(command); + + Ok((swap, Some(command))) } pub async fn recover_funds(&self) -> Result { @@ -2622,7 +2685,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_maker_payment_spend_errored() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2637,7 +2700,13 @@ mod taker_swap_tests { .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let actual = block_on(taker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::SpentOtherPayment, @@ -2652,7 +2721,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_not_spent() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2679,7 +2748,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let actual = block_on(taker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::RefundedMyPayment, @@ -2696,7 +2771,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_and_spent_by_maker() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2726,7 +2801,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let actual = block_on(taker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::SpentOtherPayment, @@ -2743,7 +2824,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2764,7 +2845,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let actual = block_on(taker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::RefundedMyPayment, @@ -2780,7 +2867,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent_too_early_to_refund() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2795,7 +2882,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let error = block_on(taker_swap.recover_funds()).unwrap_err(); assert!(error.contains("Too early to refund")); assert!(unsafe { SEARCH_TX_SPEND_CALLED }); @@ -2805,7 +2898,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_spent_by_maker() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2829,7 +2922,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let actual = block_on(taker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::SpentOtherPayment, @@ -2846,14 +2945,20 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // the json doesn't have Finished event at the end - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); assert!(block_on(taker_swap.recover_funds()).is_err()); } @@ -2877,7 +2982,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // swap file contains neither maker_coin_swap_contract_address nor taker_coin_swap_contract_address - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2888,7 +2993,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); assert_eq!(unsafe { SWAP_CONTRACT_ADDRESS_CALLED }, 2); assert_eq!( @@ -2906,7 +3017,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // swap file contains only maker_coin_swap_contract_address - let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"83965c539899cc0f918552e5a26915de40ee8852"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; + let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"83965c539899cc0f918552e5a26915de40ee8852"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"]}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2917,7 +3028,13 @@ mod taker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (taker_swap, _) = TakerSwap::load_from_saved(ctx, maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (taker_swap, _) = block_on(TakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); assert_eq!(unsafe { SWAP_CONTRACT_ADDRESS_CALLED }, 1); let expected_addr = addr_from_str(ETH_DEV_SWAP_CONTRACT).unwrap(); @@ -2934,7 +3051,7 @@ mod taker_swap_tests { fn test_recoverable() { // Swap ended with MakerPaymentWaitConfirmFailed event. // MM2 did not attempt to send the payment in this case so swap is not recoverable. - let swap: TakerSavedSwap = json::from_str(r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"data":{"error":"error"},"type":"MakerPaymentWaitConfirmFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#).unwrap(); + let swap: TakerSavedSwap = json::from_str(r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"data":{"error":"error"},"type":"MakerPaymentWaitConfirmFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#).unwrap(); assert!(!swap.is_recoverable()); } @@ -3080,7 +3197,13 @@ mod taker_swap_tests { TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::min_tx_amount.mock_safe(|_| MockResult::Return(BigDecimal::from(0))); - let (swap, _) = TakerSwap::load_from_saved(ctx.clone(), maker_coin, taker_coin, taker_saved_swap).unwrap(); + let (swap, _) = block_on(TakerSwap::load_from_saved( + ctx.clone(), + maker_coin, + taker_coin, + taker_saved_swap, + )) + .unwrap(); let swaps_ctx = SwapsContext::from_ctx(&ctx).unwrap(); let arc = Arc::new(swap); let weak_ref = Arc::downgrade(&arc); diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 3ddddf6801..ad8e874819 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -7,10 +7,6 @@ pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, ETH_DEV_SWAP_CONTRACT, ETH_DEV_TOKEN_CONTRACT, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; -pub use secp256k1::{PublicKey, SecretKey}; -pub use std::env; -pub use std::thread; - use bitcrypto::{dhash160, ChecksumType}; use chain::TransactionOutput; use coins::eth::{eth_coin_from_conf_and_request, EthCoin}; @@ -33,14 +29,18 @@ use http::StatusCode; use keys::{Address, AddressHashEnum, KeyPair, NetworkPrefix as CashAddrPrefix}; use mm2_core::mm_ctx::{MmArc, MmCtxBuilder}; use mm2_number::BigDecimal; +use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::TransactionDetails; use primitives::hash::{H160, H256}; use script::Builder; use secp256k1::Secp256k1; +pub use secp256k1::{PublicKey, SecretKey}; use serde_json::{self as json, Value as Json}; +pub use std::env; use std::path::PathBuf; use std::process::Command; use std::sync::Mutex; +pub use std::thread; use std::time::Duration; use testcontainers::clients::Cli; use testcontainers::images::generic::{GenericImage, WaitFor}; @@ -148,8 +148,8 @@ pub fn eth_distributor() -> EthCoin { "urls": ETH_DEV_NODES, "swap_contract_address": ETH_DEV_SWAP_CONTRACT, }); - let keypair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid").unwrap(); + let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); + let keypair = key_pair_from_seed(&alice_passphrase).unwrap(); let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(keypair.private().secret); block_on(eth_coin_from_conf_and_request( &MM_CTX, @@ -171,7 +171,7 @@ pub fn _fill_eth(to_addr: &str) { } // Generates an ethereum coin in the sepolia network with the given seed -pub fn _generate_eth_coin_with_seed(seed: &str) -> EthCoin { +pub fn generate_eth_coin_with_seed(seed: &str) -> EthCoin { let req = json!({ "method": "enable", "coin": "ETH", diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 55ef6dc435..3fce082f0f 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -4,7 +4,8 @@ use crate::{generate_utxo_coin_with_privkey, generate_utxo_coin_with_random_priv use coins::coin_errors::ValidatePaymentError; use coins::utxo::{dhash160, UtxoCommonOps}; use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoinEnum, RefundPaymentArgs, RewardTarget, - SearchForSwapTxSpendInput, SendPaymentArgs, SwapOps, WatcherOps, WatcherValidatePaymentInput, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SwapOps, + ValidateWatcherSpendInput, WatcherOps, WatcherSpendType, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; @@ -13,21 +14,26 @@ use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; use futures01::Future; use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, get_payment_locktime, MakerSwap, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, - TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; + REFUND_TEST_FAILURE_LOG, SWAP_FINISHED_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, + WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{enable_eth_coin, eth_jst_testnet_conf, eth_testnet_conf, mm_dump, my_balance, - mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, Mm2TestConf, + my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, + wait_for_swaps_finish_and_check_status, MarketMakerIt, Mm2TestConf, DEFAULT_RPC_PASSWORD, ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::WatcherConf; use num_traits::{One, Zero}; use primitives::hash::H256; +use serde_json::Value; use std::str::FromStr; use std::thread; use std::time::Duration; use uuid::Uuid; +use super::docker_tests_common::generate_eth_coin_with_seed; + #[derive(Debug, Clone)] struct BalanceResult { alice_acoin_balance_before: BigDecimal, @@ -254,6 +260,388 @@ fn start_swaps_and_get_balances( } } +fn check_actual_events(mm_alice: &MarketMakerIt, uuid: &str, expected_events: &[&'static str]) -> Value { + let status_response = block_on(my_swap_status(mm_alice, uuid)); + let events_array = status_response["result"]["events"].as_array().unwrap(); + let actual_events = events_array.iter().map(|item| item["event"]["type"].as_str().unwrap()); + let actual_events: Vec<&str> = actual_events.collect(); + assert_eq!(expected_events, actual_events.as_slice()); + status_response +} + +fn run_taker_node(coins: &Value, envs: &[(&str, &str)]) -> (MarketMakerIt, Mm2TestConf) { + let privkey = hex::encode(random_secp256k1_secret()); + let conf = Mm2TestConf::seednode(&format!("0x{}", privkey), coins); + let mm = block_on(MarketMakerIt::start_with_envs( + conf.conf.clone(), + conf.rpc_password.clone(), + None, + envs, + )) + .unwrap(); + let (_dump_log, _dump_dashboard) = mm.mm_dump(); + log!("Log path: {}", mm.log_path.display()); + + generate_utxo_coin_with_privkey("MYCOIN", 100.into(), H256::from_str(&privkey).unwrap()); + generate_utxo_coin_with_privkey("MYCOIN1", 100.into(), H256::from_str(&privkey).unwrap()); + enable_coin(&mm, "MYCOIN"); + enable_coin(&mm, "MYCOIN1"); + + (mm, conf) +} + +fn restart_taker_and_wait_until(conf: &Mm2TestConf, envs: &[(&str, &str)], wait_until: &str) -> MarketMakerIt { + let mut mm_alice = block_on(MarketMakerIt::start_with_envs( + conf.conf.clone(), + conf.rpc_password.clone(), + None, + envs, + )) + .unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + enable_coin(&mm_alice, "MYCOIN"); + enable_coin(&mm_alice, "MYCOIN1"); + + block_on(mm_alice.wait_for_log(120., |log| log.contains(wait_until))).unwrap(); + mm_alice +} + +fn run_maker_node(coins: &Value, envs: &[(&str, &str)], seednodes: &[&str]) -> MarketMakerIt { + let privkey = hex::encode(random_secp256k1_secret()); + let conf = Mm2TestConf::light_node(&format!("0x{}", privkey), coins, seednodes); + let mm = block_on(MarketMakerIt::start_with_envs( + conf.conf.clone(), + conf.rpc_password, + None, + envs, + )) + .unwrap(); + let (_dump_log, _dump_dashboard) = mm.mm_dump(); + log!("Log path: {}", mm.log_path.display()); + + generate_utxo_coin_with_privkey("MYCOIN", 100.into(), H256::from_str(&privkey).unwrap()); + generate_utxo_coin_with_privkey("MYCOIN1", 100.into(), H256::from_str(&privkey).unwrap()); + enable_coin(&mm, "MYCOIN"); + enable_coin(&mm, "MYCOIN1"); + + mm +} + +fn run_watcher_node( + coins: &Value, + envs: &[(&str, &str)], + seednodes: &[&str], + watcher_conf: WatcherConf, +) -> MarketMakerIt { + let privkey = hex::encode(random_secp256k1_secret()); + let conf = Mm2TestConf::watcher_light_node(&format!("0x{}", privkey), coins, seednodes, watcher_conf).conf; + let mm = block_on(MarketMakerIt::start_with_envs( + conf, + DEFAULT_RPC_PASSWORD.to_string(), + None, + envs, + )) + .unwrap(); + let (_dump_log, _dump_dashboard) = mm.mm_dump(); + log!("Log path: {}", mm.log_path.display()); + + generate_utxo_coin_with_privkey("MYCOIN", 100.into(), H256::from_str(&privkey).unwrap()); + generate_utxo_coin_with_privkey("MYCOIN1", 100.into(), H256::from_str(&privkey).unwrap()); + enable_coin(&mm, "MYCOIN"); + enable_coin(&mm, "MYCOIN1"); + + mm +} + +#[test] +fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_wait_for_taker_payment_spend() { + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let (mut mm_alice, mut alice_conf) = + run_taker_node(&coins, &[("TAKER_FAIL_AT", "wait_for_taker_payment_spend_panic")]); + let mut mm_bob = run_maker_node(&coins, &[], &[&mm_alice.ip.to_string()]); + + let watcher_conf = WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 0., + refund_start_factor: 1.5, + search_interval: 1.0, + }; + let mut mm_watcher = run_watcher_node(&coins, &[], &[&mm_alice.ip.to_string()], watcher_conf); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("MYCOIN1", "MYCOIN")], + 25., + 25., + 2., + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); + + block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); + block_on(mm_bob.wait_for_log(120., |log| log.contains(&format!("[swap uuid={}] Finished", &uuids[0])))).unwrap(); + block_on(mm_watcher.wait_for_log(120., |log| log.contains(MAKER_PAYMENT_SPEND_SENT_LOG))).unwrap(); + + restart_taker_and_wait_until(&alice_conf, &[], &format!("[swap uuid={}] Finished", &uuids[0])); + block_on(mm_alice.stop()).unwrap(); + + let mm_alice = restart_taker_and_wait_until(&alice_conf, &[], &format!("{} {}", SWAP_FINISHED_LOG, uuids[0])); + + let expected_events = [ + "Started", + "Negotiated", + "TakerFeeSent", + "TakerPaymentInstructionsReceived", + "MakerPaymentReceived", + "MakerPaymentWaitConfirmStarted", + "MakerPaymentValidatedAndConfirmed", + "TakerPaymentSent", + "WatcherMessageSent", + "TakerPaymentSpent", + "MakerPaymentSpentByWatcher", + "Finished", + ]; + check_actual_events(&mm_alice, &uuids[0], &expected_events); +} + +#[test] +fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_maker_payment_spend() { + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let (mut mm_alice, mut alice_conf) = run_taker_node(&coins, &[("TAKER_FAIL_AT", "maker_payment_spend_panic")]); + let mut mm_bob = run_maker_node(&coins, &[], &[&mm_alice.ip.to_string()]); + + let watcher_conf = WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 0., + refund_start_factor: 1.5, + search_interval: 1.0, + }; + let mut mm_watcher = run_watcher_node(&coins, &[], &[&mm_alice.ip.to_string()], watcher_conf); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("MYCOIN1", "MYCOIN")], + 25., + 25., + 2., + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); + + block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); + block_on(mm_bob.wait_for_log(120., |log| log.contains(&format!("[swap uuid={}] Finished", &uuids[0])))).unwrap(); + block_on(mm_watcher.wait_for_log(120., |log| log.contains(MAKER_PAYMENT_SPEND_SENT_LOG))).unwrap(); + + restart_taker_and_wait_until(&alice_conf, &[], &format!("[swap uuid={}] Finished", &uuids[0])); + block_on(mm_alice.stop()).unwrap(); + + let mm_alice = restart_taker_and_wait_until(&alice_conf, &[], &format!("{} {}", SWAP_FINISHED_LOG, uuids[0])); + + let expected_events = [ + "Started", + "Negotiated", + "TakerFeeSent", + "TakerPaymentInstructionsReceived", + "MakerPaymentReceived", + "MakerPaymentWaitConfirmStarted", + "MakerPaymentValidatedAndConfirmed", + "TakerPaymentSent", + "WatcherMessageSent", + "TakerPaymentSpent", + "MakerPaymentSpentByWatcher", + "Finished", + ]; + check_actual_events(&mm_alice, &uuids[0], &expected_events); +} + +#[test] +fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_panic_at_wait_for_taker_payment_spend() { + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let (mut mm_alice, mut alice_conf) = run_taker_node(&coins, &[ + ("USE_TEST_LOCKTIME", ""), + ("TAKER_FAIL_AT", "wait_for_taker_payment_spend_panic"), + ]); + let mut mm_bob = run_maker_node(&coins, &[("USE_TEST_LOCKTIME", "")], &[&mm_alice.ip.to_string()]); + + let watcher_conf = WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 1., + refund_start_factor: 0., + search_interval: 1., + }; + let mut mm_watcher = run_watcher_node( + &coins, + &[("USE_TEST_LOCKTIME", "")], + &[&mm_alice.ip.to_string()], + watcher_conf, + ); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("MYCOIN1", "MYCOIN")], + 25., + 25., + 2., + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); + + block_on(mm_bob.wait_for_log(120., |log| log.contains(MAKER_PAYMENT_SENT_LOG))).unwrap(); + block_on(mm_bob.stop()).unwrap(); + + block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); + block_on(mm_watcher.wait_for_log(120., |log| log.contains(TAKER_PAYMENT_REFUND_SENT_LOG))).unwrap(); + + restart_taker_and_wait_until( + &alice_conf, + &[("USE_TEST_LOCKTIME", "")], + &format!("[swap uuid={}] Finished", &uuids[0]), + ); + block_on(mm_alice.stop()).unwrap(); + + let mm_alice = restart_taker_and_wait_until( + &alice_conf, + &[("USE_TEST_LOCKTIME", "")], + &format!("{} {}", SWAP_FINISHED_LOG, uuids[0]), + ); + + let expected_events = [ + "Started", + "Negotiated", + "TakerFeeSent", + "TakerPaymentInstructionsReceived", + "MakerPaymentReceived", + "MakerPaymentWaitConfirmStarted", + "MakerPaymentValidatedAndConfirmed", + "TakerPaymentSent", + "WatcherMessageSent", + "TakerPaymentRefundedByWatcher", + "Finished", + ]; + check_actual_events(&mm_alice, &uuids[0], &expected_events); +} + +#[test] +fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_panic_at_taker_payment_refund() { + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let (mut mm_alice, mut alice_conf) = run_taker_node(&coins, &[ + ("USE_TEST_LOCKTIME", ""), + ("TAKER_FAIL_AT", "taker_payment_refund_panic"), + ]); + let mut mm_bob = run_maker_node(&coins, &[("USE_TEST_LOCKTIME", "")], &[&mm_alice.ip.to_string()]); + + let watcher_conf = WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 1., + refund_start_factor: 0., + search_interval: 1., + }; + let mut mm_watcher = run_watcher_node( + &coins, + &[("USE_TEST_LOCKTIME", "")], + &[&mm_alice.ip.to_string()], + watcher_conf, + ); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("MYCOIN1", "MYCOIN")], + 25., + 25., + 2., + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); + + block_on(mm_bob.wait_for_log(120., |log| log.contains(MAKER_PAYMENT_SENT_LOG))).unwrap(); + block_on(mm_bob.stop()).unwrap(); + + block_on(mm_alice.wait_for_log(120., |log| log.contains(REFUND_TEST_FAILURE_LOG))).unwrap(); + block_on(mm_watcher.wait_for_log(120., |log| log.contains(TAKER_PAYMENT_REFUND_SENT_LOG))).unwrap(); + + restart_taker_and_wait_until( + &alice_conf, + &[("USE_TEST_LOCKTIME", "")], + &format!("[swap uuid={}] Finished", &uuids[0]), + ); + block_on(mm_alice.stop()).unwrap(); + + let mm_alice = restart_taker_and_wait_until( + &alice_conf, + &[("USE_TEST_LOCKTIME", "")], + &format!("{} {}", SWAP_FINISHED_LOG, uuids[0]), + ); + + let expected_events = [ + "Started", + "Negotiated", + "TakerFeeSent", + "TakerPaymentInstructionsReceived", + "MakerPaymentReceived", + "MakerPaymentWaitConfirmStarted", + "MakerPaymentValidatedAndConfirmed", + "TakerPaymentSent", + "WatcherMessageSent", + "TakerPaymentWaitForSpendFailed", + "TakerPaymentWaitRefundStarted", + "TakerPaymentRefundStarted", + "TakerPaymentRefundedByWatcher", + "Finished", + ]; + check_actual_events(&mm_alice, &uuids[0], &expected_events); +} + +#[test] +fn test_taker_completes_swap_after_restart() { + let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let (mut mm_alice, mut alice_conf) = run_taker_node(&coins, &[]); + let mut mm_bob = run_maker_node(&coins, &[], &[&mm_alice.ip.to_string()]); + + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("MYCOIN1", "MYCOIN")], + 25., + 25., + 2., + )); + + block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); + alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); + block_on(mm_alice.stop()).unwrap(); + + let mut mm_alice = block_on(MarketMakerIt::start_with_envs( + alice_conf.conf, + alice_conf.rpc_password.clone(), + None, + &[], + )) + .unwrap(); + + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + enable_coin(&mm_alice, "MYCOIN"); + enable_coin(&mm_alice, "MYCOIN1"); + + block_on(wait_for_swaps_finish_and_check_status( + &mut mm_bob, + &mut mm_alice, + &uuids, + 2., + 25., + )); +} + #[test] fn test_watcher_spends_maker_payment_utxo_utxo() { let alice_privkey = hex::encode(random_secp256k1_secret()); @@ -266,7 +654,7 @@ fn test_watcher_spends_maker_payment_utxo_utxo() { 25., 25., 2., - &[("USE_WATCHERS", "")], + &[], SwapFlow::WatcherSpendsMakerPayment, &alice_privkey, &bob_privkey, @@ -306,7 +694,7 @@ fn test_watcher_spends_maker_payment_utxo_eth() { 0.01, 0.01, 1., - &[("USE_WATCHERS", ""), ("USE_WATCHER_REWARD", "")], + &[("USE_WATCHER_REWARD", "")], SwapFlow::WatcherSpendsMakerPayment, alice_privkey, bob_privkey, @@ -339,11 +727,7 @@ fn test_watcher_spends_maker_payment_eth_utxo() { 100., 100., 0.01, - &[ - ("USE_WATCHERS", ""), - ("TEST_COIN_PRICE", "0.01"), - ("USE_WATCHER_REWARD", ""), - ], + &[("TEST_COIN_PRICE", "0.01"), ("USE_WATCHER_REWARD", "")], SwapFlow::WatcherSpendsMakerPayment, alice_privkey, bob_privkey, @@ -384,11 +768,7 @@ fn test_watcher_spends_maker_payment_eth_erc20() { 100., 100., 0.01, - &[ - ("USE_WATCHERS", ""), - ("TEST_COIN_PRICE", "0.01"), - ("USE_WATCHER_REWARD", ""), - ], + &[("TEST_COIN_PRICE", "0.01"), ("USE_WATCHER_REWARD", "")], SwapFlow::WatcherSpendsMakerPayment, alice_privkey, bob_privkey, @@ -421,7 +801,7 @@ fn test_watcher_spends_maker_payment_erc20_eth() { 0.01, 0.01, 1., - &[("USE_WATCHERS", ""), ("USE_WATCHER_REWARD", "")], + &[("USE_WATCHER_REWARD", "")], SwapFlow::WatcherSpendsMakerPayment, alice_privkey, bob_privkey, @@ -454,11 +834,7 @@ fn test_watcher_spends_maker_payment_utxo_erc20() { 1., 1., 1., - &[ - ("USE_WATCHERS", ""), - ("TEST_COIN_PRICE", "0.01"), - ("USE_WATCHER_REWARD", ""), - ], + &[("TEST_COIN_PRICE", "0.01"), ("USE_WATCHER_REWARD", "")], SwapFlow::WatcherSpendsMakerPayment, alice_privkey, bob_privkey, @@ -491,11 +867,7 @@ fn test_watcher_spends_maker_payment_erc20_utxo() { 1., 1., 1., - &[ - ("USE_WATCHERS", ""), - ("TEST_COIN_PRICE", "0.01"), - ("USE_WATCHER_REWARD", ""), - ], + &[("TEST_COIN_PRICE", "0.01"), ("USE_WATCHER_REWARD", "")], SwapFlow::WatcherSpendsMakerPayment, alice_privkey, bob_privkey, @@ -542,7 +914,7 @@ fn test_watcher_refunds_taker_payment_utxo() { 25., 25., 2., - &[("USE_WATCHERS", ""), ("USE_TEST_LOCKTIME", "")], + &[("USE_TEST_LOCKTIME", "")], SwapFlow::WatcherRefundsTakerPayment, alice_privkey, bob_privkey, @@ -568,11 +940,7 @@ fn test_watcher_refunds_taker_payment_eth() { 0.01, 0.01, 1., - &[ - ("USE_WATCHERS", ""), - ("USE_TEST_LOCKTIME", ""), - ("USE_WATCHER_REWARD", ""), - ], + &[("USE_TEST_LOCKTIME", ""), ("USE_WATCHER_REWARD", "")], SwapFlow::WatcherRefundsTakerPayment, alice_privkey, bob_privkey, @@ -599,7 +967,6 @@ fn test_watcher_refunds_taker_payment_erc20() { 100., 0.01, &[ - ("USE_WATCHERS", ""), ("USE_TEST_LOCKTIME", ""), ("TEST_COIN_PRICE", "0.01"), ("USE_WATCHER_REWARD", ""), @@ -651,11 +1018,7 @@ fn test_watcher_waits_for_taker_eth() { 100., 100., 0.01, - &[ - ("USE_WATCHERS", ""), - ("TEST_COIN_PRICE", "0.01"), - ("USE_WATCHER_REWARD", ""), - ], + &[("TEST_COIN_PRICE", "0.01"), ("USE_WATCHER_REWARD", "")], SwapFlow::TakerSpendsMakerPayment, alice_privkey, bob_privkey, @@ -664,6 +1027,7 @@ fn test_watcher_waits_for_taker_eth() { } #[test] +#[ignore] fn test_two_watchers_spend_maker_payment_eth_erc20() { let coins = json!([eth_testnet_conf(), eth_jst_testnet_conf()]); @@ -1781,6 +2145,1056 @@ fn test_watcher_validate_taker_payment_erc20() { } } +#[test] +fn test_taker_validates_taker_payment_refund_utxo() { + let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run + let time_lock_duration = get_payment_locktime(); + let wait_for_confirmation_until = wait_until_sec(time_lock_duration); + let time_lock = now_sec() - 10; + + let (_ctx, taker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let maker_pubkey = maker_coin.my_public_key().unwrap(); + + let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + + let taker_payment = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + }) + .wait() + .unwrap(); + + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + + let taker_payment_refund_preimage = taker_coin + .create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &None, + &[], + ) + .wait() + .unwrap(); + + let taker_payment_refund = taker_coin + .send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + time_lock, + swap_contract_address: &None, + swap_unique_data: &[], + watcher_reward: false, + }) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pubkey.to_vec(), + swap_contract_address: None, + time_lock, + secret_hash: secret_hash.to_vec(), + amount: BigDecimal::from(10), + watcher_reward: None, + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let validate_watcher_refund = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait(); + assert!(validate_watcher_refund.is_ok()); +} + +#[test] +fn test_taker_validates_taker_payment_refund_eth() { + let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run + + let taker_coin = eth_distributor(); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let maker_keypair = key_pair_from_seed(&maker_seed).unwrap(); + let maker_pub = maker_keypair.public(); + let maker_coin = generate_eth_coin_with_seed(&maker_seed); + + let time_lock_duration = get_payment_locktime(); + let wait_for_confirmation_until = wait_until_sec(time_lock_duration); + let time_lock = now_sec() - 10; + let taker_amount = BigDecimal::from_str("0.001").unwrap(); + let maker_amount = BigDecimal::from_str("0.001").unwrap(); + let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + + let watcher_reward = block_on(taker_coin.get_taker_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + Some(taker_amount.clone()), + Some(maker_amount), + None, + wait_for_confirmation_until, + )) + .unwrap(); + + let taker_payment = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: Some(watcher_reward.clone()), + wait_for_confirmation_until, + }) + .wait() + .unwrap(); + + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + + let taker_payment_refund_preimage = taker_coin + .create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + taker_pub, + secret_hash.as_slice(), + &taker_coin.swap_contract_address(), + &[], + ) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund_preimage.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains("Payment state is not")) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` {}, found {:?}", + "Payment state is not 3", error + ), + } + + let taker_payment_refund = taker_coin + .send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + time_lock, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + watcher_reward: true, + }) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let validate_watcher_refund = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait(); + assert!(validate_watcher_refund.is_ok()); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("was sent to wrong address")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid contract address", error + ), + } + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = maker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction sender arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx sender arg", error + ), + } + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: taker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction receiver arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx receiver arg", error + ), + } + + let mut wrong_watcher_reward = watcher_reward.clone(); + wrong_watcher_reward.reward_target = RewardTarget::PaymentReceiver; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(wrong_watcher_reward), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction reward target arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx reward target arg", error + ), + } + + let mut wrong_watcher_reward = watcher_reward.clone(); + wrong_watcher_reward.send_contract_reward_on_spend = true; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount.clone(), + watcher_reward: Some(wrong_watcher_reward), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction sends contract reward on spend arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx sends contract reward on spend arg", error + ), + } + + let mut wrong_watcher_reward = watcher_reward.clone(); + wrong_watcher_reward.amount = BigDecimal::one(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount, + watcher_reward: Some(wrong_watcher_reward), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction watcher reward amount arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx watcher reward amount arg", error + ), + } + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: BigDecimal::one(), + watcher_reward: Some(watcher_reward), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction amount arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx amount arg", error + ), + } +} + +#[test] +fn test_taker_validates_taker_payment_refund_erc20() { + let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run + + let seed = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); + let taker_coin = generate_jst_with_seed(&seed); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let maker_keypair = key_pair_from_seed(&maker_seed).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let wait_for_confirmation_until = wait_until_sec(time_lock_duration); + let time_lock = now_sec() - 10; + + let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + + let taker_amount = BigDecimal::from_str("0.001").unwrap(); + let maker_amount = BigDecimal::from_str("0.001").unwrap(); + + let watcher_reward = Some( + block_on(taker_coin.get_taker_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + Some(taker_amount.clone()), + Some(maker_amount), + None, + wait_for_confirmation_until, + )) + .unwrap(), + ); + + let taker_payment = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + }) + .wait() + .unwrap(); + + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + + let taker_payment_refund_preimage = taker_coin + .create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + taker_pub, + secret_hash.as_slice(), + &taker_coin.swap_contract_address(), + &[], + ) + .wait() + .unwrap(); + + let taker_payment_refund = taker_coin + .send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + time_lock, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + watcher_reward: true, + }) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: taker_amount, + watcher_reward: watcher_reward.clone(), + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let validate_watcher_refund = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait(); + assert!(validate_watcher_refund.is_ok()); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: taker_payment_refund.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: taker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: BigDecimal::one(), + watcher_reward, + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction amount arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid refund tx amount arg", error + ), + } +} + +#[test] +fn test_taker_validates_maker_payment_spend_utxo() { + let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run + let time_lock_duration = get_payment_locktime(); + let wait_for_confirmation_until = wait_until_sec(time_lock_duration); + let time_lock = wait_for_confirmation_until; + + let (_ctx, taker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let taker_pubkey = taker_coin.my_public_key().unwrap(); + let maker_pubkey = maker_coin.my_public_key().unwrap(); + + let secret = MakerSwap::generate_secret().unwrap(); + let secret_hash = dhash160(&secret); + + let maker_payment = maker_coin + .send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + }) + .wait() + .unwrap(); + + maker_coin + .wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }) + .wait() + .unwrap(); + + let maker_payment_spend_preimage = taker_coin + .create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &[], + ) + .wait() + .unwrap(); + + let maker_payment_spend = taker_coin + .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + preimage: &maker_payment_spend_preimage.tx_hex(), + secret_hash: secret_hash.as_slice(), + secret: secret.as_slice(), + taker_pub: taker_pubkey, + watcher_reward: false, + }) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pubkey.to_vec(), + swap_contract_address: None, + time_lock, + secret_hash: secret_hash.to_vec(), + amount: BigDecimal::from(10), + watcher_reward: None, + spend_type: WatcherSpendType::TakerPaymentRefund, + }; + + let validate_watcher_spend = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait(); + assert!(validate_watcher_spend.is_ok()); +} + +#[test] +fn test_taker_validates_maker_payment_spend_eth() { + let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run + + let taker_coin = eth_distributor(); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let maker_coin = generate_eth_coin_with_seed(&maker_seed); + let maker_keypair = key_pair_from_seed(&maker_seed).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let wait_for_confirmation_until = wait_until_sec(time_lock_duration); + let time_lock = wait_for_confirmation_until; + let maker_amount = BigDecimal::from_str("0.001").unwrap(); + + let secret = MakerSwap::generate_secret().unwrap(); + let secret_hash = dhash160(&secret); + + let watcher_reward = block_on(maker_coin.get_maker_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + None, + wait_for_confirmation_until, + )) + .unwrap() + .unwrap(); + + let maker_payment = maker_coin + .send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + amount: maker_amount.clone(), + swap_contract_address: &maker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: Some(watcher_reward.clone()), + wait_for_confirmation_until, + }) + .wait() + .unwrap(); + + maker_coin + .wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }) + .wait() + .unwrap(); + + let maker_payment_spend_preimage = taker_coin + .create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pub, + secret_hash.as_slice(), + &[], + ) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend_preimage.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains("Payment state is not")) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` {}, found {:?}", + "invalid payment state", error + ), + } + + let maker_payment_spend = taker_coin + .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + preimage: &maker_payment_spend_preimage.tx_hex(), + secret_hash: secret_hash.as_slice(), + secret: secret.as_slice(), + taker_pub, + watcher_reward: true, + }) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let validate_watcher_spend = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait(); + assert!(validate_watcher_spend.is_ok()); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("was sent to wrong address")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid contract address", error + ), + }; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: taker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction sender arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx sender arg", error + ), + }; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(watcher_reward.clone()), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = maker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction receiver arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx receiver arg", error + ), + }; + + let mut wrong_watcher_reward = watcher_reward.clone(); + wrong_watcher_reward.reward_target = RewardTarget::Contract; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(wrong_watcher_reward), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction reward target arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx reward target arg", error + ), + }; + + let mut wrong_watcher_reward = watcher_reward.clone(); + wrong_watcher_reward.send_contract_reward_on_spend = false; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount.clone(), + watcher_reward: Some(wrong_watcher_reward), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction sends contract reward on spend arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx sends contract reward on spend arg", error + ), + }; + + let mut wrong_watcher_reward = watcher_reward.clone(); + wrong_watcher_reward.amount = BigDecimal::one(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount, + watcher_reward: Some(wrong_watcher_reward), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction watcher reward amount arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx watcher reward amount arg", error + ), + }; + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: BigDecimal::one(), + watcher_reward: Some(watcher_reward), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction amount arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx amount arg", error + ), + }; +} + +#[test] +fn test_taker_validates_maker_payment_spend_erc20() { + let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run + + let taker_seed = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); + let taker_coin = generate_jst_with_seed(&taker_seed); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_seed = get_passphrase!(".env.client", "BOB_PASSPHRASE").unwrap(); + let maker_coin = generate_jst_with_seed(&maker_seed); + let maker_keypair = key_pair_from_seed(&maker_seed).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let wait_for_confirmation_until = wait_until_sec(time_lock_duration); + let time_lock = wait_for_confirmation_until; + let maker_amount = BigDecimal::from_str("0.001").unwrap(); + + let secret = MakerSwap::generate_secret().unwrap(); + let secret_hash = dhash160(&secret); + + let watcher_reward = block_on(maker_coin.get_maker_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + None, + wait_for_confirmation_until, + )) + .unwrap(); + + let maker_payment = maker_coin + .send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + amount: maker_amount.clone(), + swap_contract_address: &maker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + }) + .wait() + .unwrap(); + + maker_coin + .wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }) + .wait() + .unwrap(); + + let maker_payment_spend_preimage = taker_coin + .create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pub, + secret_hash.as_slice(), + &[], + ) + .wait() + .unwrap(); + + let maker_payment_spend = taker_coin + .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + preimage: &maker_payment_spend_preimage.tx_hex(), + secret_hash: secret_hash.as_slice(), + secret: secret.as_slice(), + taker_pub, + watcher_reward: true, + }) + .wait() + .unwrap(); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: maker_amount, + watcher_reward: watcher_reward.clone(), + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let validate_watcher_spend = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait(); + assert!(validate_watcher_spend.is_ok()); + + let validate_input = ValidateWatcherSpendInput { + payment_tx: maker_payment_spend.tx_hex(), + maker_pub: maker_pub.to_vec(), + swap_contract_address: maker_coin.swap_contract_address(), + time_lock, + secret_hash: secret_hash.to_vec(), + amount: BigDecimal::one(), + watcher_reward, + spend_type: WatcherSpendType::MakerPaymentSpend, + }; + + let error = taker_coin + .taker_validates_payment_spend_or_refund(validate_input) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("Transaction amount arg")) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + "invalid payment spend tx amount arg", error + ), + }; +} + #[test] fn test_send_taker_payment_refund_preimage_utxo() { let timeout = wait_until_sec(120); // timeout if test takes more than 120 seconds to run diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index d12aad490d..0f33a332e2 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -93,7 +93,7 @@ pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ "Finished", ]; -pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 12] = [ +pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ "Started", "Negotiated", "TakerFeeSent", @@ -105,10 +105,43 @@ pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 12] = [ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpentByWatcher", "Finished", ]; -pub const TAKER_ERROR_EVENTS: [&str; 15] = [ +// Taker using watchers and watcher spends maker payment +pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ + "Started", + "Negotiated", + "TakerFeeSent", + "TakerPaymentInstructionsReceived", + "MakerPaymentReceived", + "MakerPaymentWaitConfirmStarted", + "MakerPaymentValidatedAndConfirmed", + "TakerPaymentSent", + "WatcherMessageSent", + "TakerPaymentSpent", + "MakerPaymentSpentByWatcher", + "Finished", +]; + +// Taker using watchers and spends maker payment instead of watcher +pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ + "Started", + "Negotiated", + "TakerFeeSent", + "TakerPaymentInstructionsReceived", + "MakerPaymentReceived", + "MakerPaymentWaitConfirmStarted", + "MakerPaymentValidatedAndConfirmed", + "TakerPaymentSent", + "WatcherMessageSent", + "TakerPaymentSpent", + "MakerPaymentSpent", + "Finished", +]; + +pub const TAKER_ERROR_EVENTS: [&str; 16] = [ "StartFailed", "NegotiateFailed", "TakerFeeSendFailed", @@ -122,6 +155,7 @@ pub const TAKER_ERROR_EVENTS: [&str; 15] = [ "TakerPaymentWaitRefundStarted", "TakerPaymentRefundStarted", "TakerPaymentRefunded", + "TakerPaymentRefundedByWatcher", "TakerPaymentRefundFailed", "TakerPaymentRefundFinished", ]; @@ -2082,9 +2116,11 @@ pub async fn check_my_swap_status(mm: &MarketMakerIt, uuid: &str, maker_amount: assert_eq!(maker_amount, actual_maker_amount); let actual_taker_amount = json::from_value(events_array[0]["event"]["data"]["taker_amount"].clone()).unwrap(); assert_eq!(taker_amount, actual_taker_amount); - let actual_events = events_array.iter().map(|item| item["event"]["type"].as_str().unwrap()); - let actual_events: Vec<&str> = actual_events.collect(); - assert_eq!(success_events, actual_events.as_slice()); + let actual_events = events_array + .iter() + .map(|item| item["event"]["type"].as_str().unwrap().to_string()) + .collect::>(); + assert!(actual_events.iter().all(|item| success_events.contains(item))); } pub async fn check_my_swap_status_amounts( @@ -2128,7 +2164,8 @@ pub async fn check_stats_swap_status(mm: &MarketMakerIt, uuid: &str) { assert_eq!(maker_actual_events.as_slice(), MAKER_SUCCESS_EVENTS); assert!( taker_actual_events.as_slice() == TAKER_SUCCESS_EVENTS - || taker_actual_events.as_slice() == TAKER_USING_WATCHERS_SUCCESS_EVENTS + || taker_actual_events.as_slice() == TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT + || taker_actual_events.as_slice() == TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT ); } From 1224c03496a1d0181530c295787b3a0c565a1129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Fri, 27 Oct 2023 17:56:18 +0300 Subject: [PATCH 20/40] feat(channels): wasm, coin balance and socket impl (#1978) This commit adds wasm event streaming using workers and COIN_BALANCE events for Tendermint Protocol. --- Cargo.lock | 88 +++++++++ mm2src/adex_cli/Cargo.lock | 1 + mm2src/coins/Cargo.toml | 3 + mm2src/coins/tendermint/mod.rs | 1 + .../tendermint/rpc/tendermint_native_rpc.rs | 10 + .../tendermint/rpc/tendermint_wasm_rpc.rs | 3 + .../tendermint/tendermint_balance_events.rs | 180 ++++++++++++++++++ mm2src/coins/tendermint/tendermint_coin.rs | 29 ++- mm2src/coins_activation/Cargo.toml | 1 + .../src/bch_with_tokens_activation.rs | 8 + .../src/eth_with_token_activation.rs | 8 + .../src/platform_coin_with_tokens.rs | 10 + .../src/solana_with_tokens_activation.rs | 8 + .../src/tendermint_with_assets_activation.rs | 24 ++- mm2src/common/common.rs | 37 ++++ mm2src/mm2_event_stream/Cargo.toml | 1 + mm2src/mm2_event_stream/src/behaviour.rs | 12 +- mm2src/mm2_event_stream/src/lib.rs | 3 + mm2src/mm2_libp2p/src/atomicdex_behaviour.rs | 18 +- mm2src/mm2_main/src/lp_native_dex.rs | 39 ++-- mm2src/mm2_net/Cargo.toml | 2 +- mm2src/mm2_net/src/lib.rs | 5 +- mm2src/mm2_net/src/network_event.rs | 32 +++- mm2src/mm2_net/src/wasm_event_stream.rs | 26 +++ 24 files changed, 509 insertions(+), 40 deletions(-) create mode 100644 mm2src/coins/tendermint/tendermint_balance_events.rs create mode 100644 mm2src/mm2_net/src/wasm_event_stream.rs diff --git a/Cargo.lock b/Cargo.lock index c7d9b6952f..1911e05d20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1020,6 +1020,7 @@ dependencies = [ "ethkey", "futures 0.1.29", "futures 0.3.28", + "futures-util", "group 0.8.0", "gstuff", "hex 0.4.3", @@ -1039,6 +1040,7 @@ dependencies = [ "mm2_core", "mm2_db", "mm2_err_handle", + "mm2_event_stream", "mm2_git", "mm2_io", "mm2_metamask", @@ -1086,6 +1088,7 @@ dependencies = [ "tiny-bip39", "tokio", "tokio-rustls", + "tokio-tungstenite-wasm", "tonic", "tonic-build", "url", @@ -1122,6 +1125,7 @@ dependencies = [ "lightning-invoice", "mm2_core", "mm2_err_handle", + "mm2_event_stream", "mm2_metamask", "mm2_metrics", "mm2_number", @@ -4333,6 +4337,7 @@ dependencies = [ "async-trait", "cfg-if 1.0.0", "common", + "futures 0.3.28", "parking_lot 0.12.0", "serde", "tokio", @@ -4989,6 +4994,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "ordered-float" version = "3.7.0" @@ -6316,6 +6327,18 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.2", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "0.2.1" @@ -6399,6 +6422,15 @@ dependencies = [ "syn 1.0.95", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scoped-tls" version = "1.0.0" @@ -6511,6 +6543,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -8285,6 +8340,39 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e80b39df6afcc12cdf752398ade96a6b9e99c903dfdc36e53ad10b9c366bca72" +dependencies = [ + "futures-util", + "log", + "rustls 0.20.4", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki 0.22.0", +] + +[[package]] +name = "tokio-tungstenite-wasm" +version = "0.1.1-alpha.0" +source = "git+https://github.com/KomodoPlatform/tokio-tungstenite-wasm?rev=d20abdb#d20abdbbb2f03e302e3a8d11a1736ec8b50d0f58" +dependencies = [ + "futures-channel", + "futures-util", + "http 0.2.7", + "httparse", + "js-sys", + "thiserror", + "tokio", + "tokio-tungstenite", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "tokio-util" version = "0.7.2" diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index ab1f4548d9..7d513ab8d2 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -1781,6 +1781,7 @@ dependencies = [ "async-trait", "cfg-if 1.0.0", "common", + "futures 0.3.28", "parking_lot", "serde", "tokio", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 1e45499ae6..b2ccc8c227 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -49,7 +49,9 @@ ethereum-types = { version = "0.13", default-features = false, features = ["std" ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } # Waiting for https://github.com/rust-lang/rust/issues/54725 to use on Stable. #enum_dispatch = "0.1" +tokio-tungstenite-wasm = { git = "https://github.com/KomodoPlatform/tokio-tungstenite-wasm", rev = "d20abdb", features = ["rustls-tls-native-roots"]} futures01 = { version = "0.1", package = "futures" } +futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } # using select macro requires the crate to be named futures, compilation failed with futures03 name futures = { version = "0.3", package = "futures", features = ["compat", "async-await"] } group = "0.8.0" @@ -63,6 +65,7 @@ lazy_static = "1.4" libc = "0.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } +mm2_event_stream = { path = "../mm2_event_stream" } mm2_git = { path = "../mm2_git" } mm2_io = { path = "../mm2_io" } mm2_metrics = { path = "../mm2_metrics" } diff --git a/mm2src/coins/tendermint/mod.rs b/mm2src/coins/tendermint/mod.rs index d480a4964e..60a4c61ec1 100644 --- a/mm2src/coins/tendermint/mod.rs +++ b/mm2src/coins/tendermint/mod.rs @@ -5,6 +5,7 @@ mod ibc; mod iris; mod rpc; +mod tendermint_balance_events; mod tendermint_coin; mod tendermint_token; pub mod tendermint_tx_history_v2; diff --git a/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs b/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs index dde181b3e3..4904a2ed30 100644 --- a/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs +++ b/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs @@ -309,6 +309,9 @@ impl HttpClient { }, }) } + + #[inline] + pub fn uri(&self) -> http::Uri { self.inner.uri() } } #[async_trait] @@ -481,6 +484,13 @@ mod sealed { HttpClient::Https(c) => c.perform(request).await, } } + + pub fn uri(&self) -> Uri { + match self { + HttpClient::Http(client) => client.uri.clone(), + HttpClient::Https(client) => client.uri.clone(), + } + } } async fn response_to_string(response: hyper::Response) -> Result { diff --git a/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs b/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs index 036815a25f..bcbc07c874 100644 --- a/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs +++ b/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs @@ -58,6 +58,9 @@ impl HttpClient { Ok(HttpClient { uri: url.to_owned() }) } + #[inline] + pub fn uri(&self) -> http::Uri { Uri::from_str(&self.uri).expect("This should never happen.") } + pub(crate) async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/mm2src/coins/tendermint/tendermint_balance_events.rs b/mm2src/coins/tendermint/tendermint_balance_events.rs new file mode 100644 index 0000000000..122262eb51 --- /dev/null +++ b/mm2src/coins/tendermint/tendermint_balance_events.rs @@ -0,0 +1,180 @@ +use async_trait::async_trait; +use common::{executor::{AbortSettings, SpawnAbortable}, + http_uri_to_ws_address, log}; +use futures::channel::oneshot::{self, Receiver, Sender}; +use futures_util::{SinkExt, StreamExt}; +use jsonrpc_core::MethodCall; +use jsonrpc_core::{Id as RpcId, Params as RpcParams, Value as RpcValue, Version as RpcVersion}; +use mm2_core::mm_ctx::MmArc; +use mm2_event_stream::{behaviour::{EventBehaviour, EventInitStatus}, + Event, EventStreamConfiguration}; +use mm2_number::BigDecimal; +use std::collections::{HashMap, HashSet}; + +use super::TendermintCoin; +use crate::{tendermint::TendermintCommons, utxo::utxo_common::big_decimal_from_sat_unsigned, MarketCoinOps, MmCoin}; + +#[async_trait] +impl EventBehaviour for TendermintCoin { + const EVENT_NAME: &'static str = "COIN_BALANCE"; + + async fn handle(self, _interval: f64, tx: oneshot::Sender) { + fn generate_subscription_query(query_filter: String) -> String { + let mut params = serde_json::Map::with_capacity(1); + params.insert("query".to_owned(), RpcValue::String(query_filter)); + + let q = MethodCall { + id: RpcId::Num(0), + jsonrpc: Some(RpcVersion::V2), + method: "subscribe".to_owned(), + params: RpcParams::Map(params), + }; + + serde_json::to_string(&q).expect("This should never happen") + } + + let ctx = match MmArc::from_weak(&self.ctx) { + Some(ctx) => ctx, + None => { + let msg = "MM context must have been initialized already."; + tx.send(EventInitStatus::Failed(msg.to_owned())) + .expect("Receiver is dropped, which should never happen."); + panic!("{}", msg); + }, + }; + + let account_id = self.account_id.to_string(); + let mut current_balances: HashMap = HashMap::new(); + + let receiver_q = generate_subscription_query(format!("coin_received.receiver = '{}'", account_id)); + let receiver_q = tokio_tungstenite_wasm::Message::Text(receiver_q); + + let spender_q = generate_subscription_query(format!("coin_spent.spender = '{}'", account_id)); + let spender_q = tokio_tungstenite_wasm::Message::Text(spender_q); + + tx.send(EventInitStatus::Success) + .expect("Receiver is dropped, which should never happen."); + + loop { + let node_uri = match self.rpc_client().await { + Ok(client) => client.uri(), + Err(e) => { + log::error!("{e}"); + continue; + }, + }; + + let socket_address = format!("{}/{}", http_uri_to_ws_address(node_uri), "websocket"); + + let mut wsocket = match tokio_tungstenite_wasm::connect(socket_address).await { + Ok(ws) => ws, + Err(e) => { + log::error!("{e}"); + continue; + }, + }; + + // Filter received TX events + if let Err(e) = wsocket.send(receiver_q.clone()).await { + log::error!("{e}"); + continue; + } + + // Filter spent TX events + if let Err(e) = wsocket.send(spender_q.clone()).await { + log::error!("{e}"); + continue; + } + + while let Some(message) = wsocket.next().await { + let msg = match message { + Ok(tokio_tungstenite_wasm::Message::Text(data)) => data.clone(), + Ok(tokio_tungstenite_wasm::Message::Close(_)) => break, + Err(err) => { + log::error!("Server returned an unknown message type - {err}"); + break; + }, + _ => continue, + }; + + // Here, we receive raw data from the socket. + // To examine this data, you can use tools like wscat/websocat or visit + // https://pastebin.pl/view/499cbf2c for sample data. + if let Ok(json_val) = serde_json::from_str::(&msg) { + let transfers: Vec = + serde_json::from_value(json_val["result"]["events"]["transfer.amount"].clone()) + .unwrap_or_default(); + + let denoms: HashSet = transfers + .iter() + .map(|t| { + let amount: String = t.chars().take_while(|c| c.is_numeric()).collect(); + let denom = &t[amount.len()..]; + denom.to_owned() + }) + .collect(); + + for denom in denoms { + if let Some((ticker, decimals)) = self.active_ticker_and_decimals_from_denom(&denom) { + let balance_denom = match self.account_balance_for_denom(&self.account_id, denom).await { + Ok(balance_denom) => balance_denom, + Err(e) => { + log::error!("{e}"); + continue; + }, + }; + + let balance_decimal = big_decimal_from_sat_unsigned(balance_denom, decimals); + + // Only broadcast when balance is changed + let mut broadcast = false; + if let Some(balance) = current_balances.get_mut(&ticker) { + if *balance != balance_decimal { + *balance = balance_decimal.clone(); + broadcast = true; + } + } else { + current_balances.insert(ticker.clone(), balance_decimal.clone()); + broadcast = true; + } + + if broadcast { + let payload = json!({ + "ticker": ticker, + "balance": { "spendable": balance_decimal, "unspendable": BigDecimal::default() } + }); + + ctx.stream_channel_controller + .broadcast(Event::new(Self::EVENT_NAME.to_string(), payload.to_string())) + .await; + } + } + } + } + } + } + } + + async fn spawn_if_active(self, config: &EventStreamConfiguration) -> EventInitStatus { + if let Some(event) = config.get_event(Self::EVENT_NAME) { + log::info!( + "{} event is activated for {}. `stream_interval_seconds`({}) has no effect on this.", + Self::EVENT_NAME, + self.ticker(), + event.stream_interval_seconds + ); + + let (tx, rx): (Sender, Receiver) = oneshot::channel(); + let fut = self.clone().handle(event.stream_interval_seconds, tx); + let settings = + AbortSettings::info_on_abort(format!("{} event is stopped for {}.", Self::EVENT_NAME, self.ticker())); + self.spawner().spawn_with_settings(fut, settings); + + rx.await.unwrap_or_else(|e| { + EventInitStatus::Failed(format!("Event initialization status must be received: {}", e)) + }) + } else { + EventInitStatus::Inactive + } + } +} diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index cc569cb1d4..6d0432d703 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -60,7 +60,7 @@ use futures01::Future; use hex::FromHexError; use itertools::Itertools; use keys::KeyPair; -use mm2_core::mm_ctx::MmArc; +use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_git::{FileMetadata, GitController, GithubClient, RepositoryOperations, GITHUB_API_URI}; use mm2_number::MmNumber; @@ -142,7 +142,7 @@ pub struct TendermintProtocolInfo { #[derive(Clone)] pub struct ActivatedTokenInfo { pub(crate) decimals: u8, - pub(crate) denom: Denom, + pub ticker: String, } pub struct TendermintConf { @@ -237,6 +237,7 @@ pub struct TendermintCoinImpl { pub(crate) history_sync_state: Mutex, client: TendermintRpcClient, chain_registry_name: Option, + pub(crate) ctx: MmWeak, } #[derive(Clone)] @@ -279,6 +280,7 @@ pub enum TendermintInitErrorKind { AvgBlockTimeMissing, #[display(fmt = "avg_blocktime must be in-between '0' and '255'.")] AvgBlockTimeInvalid, + BalanceStreamInitError(String), } #[derive(Display, Debug)] @@ -441,14 +443,14 @@ impl TendermintCommons for TendermintCoin { let ibc_assets_info = self.tokens_info.lock().clone(); let mut requests = Vec::new(); - for (ticker, info) in ibc_assets_info { + for (denom, info) in ibc_assets_info { let fut = async move { let balance_denom = self - .account_balance_for_denom(&self.account_id, info.denom.to_string()) + .account_balance_for_denom(&self.account_id, denom) .await .map_err(|e| e.into_inner())?; let balance_decimal = big_decimal_from_sat_unsigned(balance_denom, info.decimals); - Ok::<_, TendermintCoinRpcError>((ticker.clone(), balance_decimal)) + Ok::<_, TendermintCoinRpcError>((info.ticker, balance_decimal)) }; requests.push(fut); } @@ -544,6 +546,7 @@ impl TendermintCoin { history_sync_state: Mutex::new(history_sync_state), client: TendermintRpcClient(AsyncMutex::new(client_impl)), chain_registry_name: protocol_info.chain_registry_name, + ctx: ctx.weak(), }))) } @@ -1178,7 +1181,7 @@ impl TendermintCoin { pub fn add_activated_token_info(&self, ticker: String, decimals: u8, denom: Denom) { self.tokens_info .lock() - .insert(ticker, ActivatedTokenInfo { decimals, denom }); + .insert(denom.to_string(), ActivatedTokenInfo { decimals, ticker }); } fn estimate_blocks_from_duration(&self, duration: u64) -> i64 { @@ -1821,6 +1824,20 @@ impl TendermintCoin { _ => (self.gas_price(), fallback_gas_limit), } } + + pub(crate) fn active_ticker_and_decimals_from_denom(&self, denom: &str) -> Option<(String, u8)> { + if self.denom.as_ref() == denom { + return Some((self.ticker.clone(), self.decimals)); + } + + let tokens = self.tokens_info.lock(); + + if let Some(token_info) = tokens.get(denom) { + return Some((token_info.ticker.to_owned(), token_info.decimals)); + } + + None + } } fn clients_from_urls(rpc_urls: &[String]) -> MmResult, TendermintInitErrorKind> { diff --git a/mm2src/coins_activation/Cargo.toml b/mm2src/coins_activation/Cargo.toml index e6ae6401a0..09fd4adf8e 100644 --- a/mm2src/coins_activation/Cargo.toml +++ b/mm2src/coins_activation/Cargo.toml @@ -21,6 +21,7 @@ futures = { version = "0.3", package = "futures", features = ["compat", "async-a hex = "0.4.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } +mm2_event_stream = { path = "../mm2_event_stream" } mm2_metrics = { path = "../mm2_metrics" } mm2_number = { path = "../mm2_number" } parking_lot = { version = "0.12.0", features = ["nightly"] } diff --git a/mm2src/coins_activation/src/bch_with_tokens_activation.rs b/mm2src/coins_activation/src/bch_with_tokens_activation.rs index a6d4c54df8..b99b49235a 100644 --- a/mm2src/coins_activation/src/bch_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/bch_with_tokens_activation.rs @@ -17,6 +17,7 @@ use common::{drop_mutability, true_f}; use crypto::CryptoCtxError; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_event_stream::EventStreamConfiguration; use mm2_number::BigDecimal; use serde_derive::{Deserialize, Serialize}; use serde_json::Value as Json; @@ -330,4 +331,11 @@ impl PlatformWithTokensActivationOps for BchCoin { let settings = AbortSettings::info_on_abort(format!("bch_and_slp_history_loop stopped for {}", self.ticker())); self.spawner().spawn_with_settings(fut, settings); } + + async fn handle_balance_streaming( + &self, + _config: &EventStreamConfiguration, + ) -> Result<(), MmError> { + Ok(()) + } } diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index 0f7c8d8455..3a93f3ad07 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -15,6 +15,7 @@ use common::Future01CompatExt; use common::{drop_mutability, true_f}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_event_stream::EventStreamConfiguration; #[cfg(target_arch = "wasm32")] use mm2_metamask::MetamaskRpcError; use mm2_number::BigDecimal; @@ -277,6 +278,13 @@ impl PlatformWithTokensActivationOps for EthCoin { _initial_balance: Option, ) { } + + async fn handle_balance_streaming( + &self, + _config: &EventStreamConfiguration, + ) -> Result<(), MmError> { + Ok(()) + } } fn eth_priv_key_build_policy( diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 94b9a16fc9..bd12c99a22 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -8,6 +8,7 @@ use crypto::CryptoCtxError; use derive_more::Display; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_event_stream::EventStreamConfiguration; use mm2_number::BigDecimal; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; @@ -165,6 +166,11 @@ pub trait PlatformWithTokensActivationOps: Into { storage: impl TxHistoryStorage, initial_balance: Option, ); + + async fn handle_balance_streaming( + &self, + config: &EventStreamConfiguration, + ) -> Result<(), MmError>; } #[derive(Debug, Deserialize)] @@ -364,6 +370,10 @@ where ); } + if let Some(config) = &ctx.event_stream_configuration { + platform_coin.handle_balance_streaming(config).await?; + } + let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); coins_ctx .add_platform_with_tokens(platform_coin.into(), mm_tokens) diff --git a/mm2src/coins_activation/src/solana_with_tokens_activation.rs b/mm2src/coins_activation/src/solana_with_tokens_activation.rs index b6bd7b123d..aa049d0867 100644 --- a/mm2src/coins_activation/src/solana_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/solana_with_tokens_activation.rs @@ -18,6 +18,7 @@ use crypto::CryptoCtxError; use futures::future::try_join_all; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_event_stream::EventStreamConfiguration; use mm2_number::BigDecimal; use serde_derive::{Deserialize, Serialize}; use serde_json::Value as Json; @@ -288,4 +289,11 @@ impl PlatformWithTokensActivationOps for SolanaCoin { _initial_balance: Option, ) { } + + async fn handle_balance_streaming( + &self, + _config: &EventStreamConfiguration, + ) -> Result<(), MmError> { + Ok(()) + } } diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index 006f7993d3..e2e8fda8f0 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -15,6 +15,8 @@ use common::{true_f, Future01CompatExt}; use crypto::StandardHDCoinAddress; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; +use mm2_event_stream::EventStreamConfiguration; use mm2_number::BigDecimal; use serde::{Deserialize, Serialize}; use serde_json::Value as Json; @@ -231,7 +233,14 @@ impl PlatformWithTokensActivationOps for TendermintCoin { current_block, balance: None, tokens_balances: None, - tokens_tickers: Some(self.tokens_info.lock().clone().into_keys().collect()), + tokens_tickers: Some( + self.tokens_info + .lock() + .clone() + .into_values() + .map(|t| t.ticker) + .collect(), + ), }); } @@ -275,4 +284,17 @@ impl PlatformWithTokensActivationOps for TendermintCoin { let settings = AbortSettings::info_on_abort(format!("tendermint_history_loop stopped for {}", self.ticker())); self.spawner().spawn_with_settings(fut, settings); } + + async fn handle_balance_streaming( + &self, + config: &EventStreamConfiguration, + ) -> Result<(), MmError> { + if let EventInitStatus::Failed(err) = EventBehaviour::spawn_if_active(self.clone(), config).await { + return MmError::err(TendermintInitError { + ticker: self.ticker().to_owned(), + kind: TendermintInitErrorKind::BalanceStreamInitError(err), + }); + } + Ok(()) + } } diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 058661f42c..6af8724e64 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -1035,3 +1035,40 @@ pub fn parse_rfc3339_to_timestamp(date_str: &str) -> Result bool { old_version == 0 && new_version == 1 } + +/// Takes `http:Uri` and converts it into `String` of websocket address +/// +/// Panics if the given URI doesn't contain a host value. +pub fn http_uri_to_ws_address(uri: http::Uri) -> String { + let address_prefix = match uri.scheme_str() { + Some("https") => "wss://", + _ => "ws://", + }; + + let host_address = uri.host().expect("Host can't be empty."); + let port = uri.port_u16().map(|p| format!(":{}", p)).unwrap_or_default(); + + format!("{}{}{}", address_prefix, host_address, port) +} + +#[test] +fn test_http_uri_to_ws_address() { + let uri = "https://cosmos-rpc.polkachu.com".parse::().unwrap(); + let ws_connection = http_uri_to_ws_address(uri); + assert_eq!(ws_connection, "wss://cosmos-rpc.polkachu.com"); + + let uri = "http://cosmos-rpc.polkachu.com".parse::().unwrap(); + let ws_connection = http_uri_to_ws_address(uri); + assert_eq!(ws_connection, "ws://cosmos-rpc.polkachu.com"); + + let uri = "http://34.82.96.8:26657".parse::().unwrap(); + let ws_connection = http_uri_to_ws_address(uri); + assert_eq!(ws_connection, "ws://34.82.96.8:26657"); +} + +#[test] +#[should_panic(expected = "Host can't be empty.")] +fn test_http_uri_to_ws_address_panic() { + let uri = "/demo/value".parse::().unwrap(); + http_uri_to_ws_address(uri); +} diff --git a/mm2src/mm2_event_stream/Cargo.toml b/mm2src/mm2_event_stream/Cargo.toml index 2865e0a01f..adf20e7ee2 100644 --- a/mm2src/mm2_event_stream/Cargo.toml +++ b/mm2src/mm2_event_stream/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" async-trait = "0.1" cfg-if = "1.0" common = { path = "../common" } +futures = { version = "0.3", default-features = false } parking_lot = "0.12" serde = { version = "1", features = ["derive", "rc"] } tokio = { version = "1", features = ["sync"] } diff --git a/mm2src/mm2_event_stream/src/behaviour.rs b/mm2src/mm2_event_stream/src/behaviour.rs index bb905af3fc..8539754061 100644 --- a/mm2src/mm2_event_stream/src/behaviour.rs +++ b/mm2src/mm2_event_stream/src/behaviour.rs @@ -1,5 +1,13 @@ use crate::EventStreamConfiguration; use async_trait::async_trait; +use futures::channel::oneshot; + +#[derive(Clone, Debug)] +pub enum EventInitStatus { + Inactive, + Success, + Failed(String), +} #[async_trait] pub trait EventBehaviour { @@ -7,9 +15,9 @@ pub trait EventBehaviour { const EVENT_NAME: &'static str; /// Event handler that is responsible for broadcasting event data to the streaming channels. - async fn handle(self, interval: f64); + async fn handle(self, interval: f64, tx: oneshot::Sender); /// Spawns the `Self::handle` in a separate thread if the event is active according to the mm2 configuration. /// Does nothing if the event is not active. - fn spawn_if_active(self, config: &EventStreamConfiguration); + async fn spawn_if_active(self, config: &EventStreamConfiguration) -> EventInitStatus; } diff --git a/mm2src/mm2_event_stream/src/lib.rs b/mm2src/mm2_event_stream/src/lib.rs index afa5c7e1be..cc3b86f7d5 100644 --- a/mm2src/mm2_event_stream/src/lib.rs +++ b/mm2src/mm2_event_stream/src/lib.rs @@ -40,9 +40,12 @@ pub struct EventStreamConfiguration { #[derive(Clone, Default, Deserialize)] pub struct EventConfig { /// The interval in seconds at which the event should be streamed. + #[serde(default = "default_stream_interval")] pub stream_interval_seconds: f64, } +const fn default_stream_interval() -> f64 { 5. } + impl Default for EventStreamConfiguration { fn default() -> Self { Self { diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs index 8b1930c9e0..482251fb93 100644 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs +++ b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs @@ -26,7 +26,7 @@ use libp2p_floodsub::{Floodsub, FloodsubEvent, Topic as FloodsubTopic}; use log::{debug, error, info}; use rand::seq::SliceRandom; use rand::Rng; -use std::{collections::hash_map::{DefaultHasher, HashMap}, +use std::{collections::{hash_map::DefaultHasher, BTreeMap}, hash::{Hash, Hasher}, iter, net::IpAddr, @@ -47,7 +47,7 @@ const ANNOUNCE_INITIAL_DELAY: Duration = Duration::from_secs(60); const CHANNEL_BUF_SIZE: usize = 1024 * 8; /// Returns info about connected peers -pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> HashMap> { +pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> BTreeMap> { let (result_tx, rx) = oneshot::channel(); let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; cmd_tx.send(cmd).await.expect("Rx should be present"); @@ -55,21 +55,21 @@ pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> HashMap HashMap> { +pub async fn get_gossip_mesh(mut cmd_tx: AdexCmdTx) -> BTreeMap> { let (result_tx, rx) = oneshot::channel(); let cmd = AdexBehaviourCmd::GetGossipMesh { result_tx }; cmd_tx.send(cmd).await.expect("Rx should be present"); rx.await.expect("Tx should be present") } -pub async fn get_gossip_peer_topics(mut cmd_tx: AdexCmdTx) -> HashMap> { +pub async fn get_gossip_peer_topics(mut cmd_tx: AdexCmdTx) -> BTreeMap> { let (result_tx, rx) = oneshot::channel(); let cmd = AdexBehaviourCmd::GetGossipPeerTopics { result_tx }; cmd_tx.send(cmd).await.expect("Rx should be present"); rx.await.expect("Tx should be present") } -pub async fn get_gossip_topic_peers(mut cmd_tx: AdexCmdTx) -> HashMap> { +pub async fn get_gossip_topic_peers(mut cmd_tx: AdexCmdTx) -> BTreeMap> { let (result_tx, rx) = oneshot::channel(); let cmd = AdexBehaviourCmd::GetGossipTopicPeers { result_tx }; cmd_tx.send(cmd).await.expect("Rx should be present"); @@ -133,16 +133,16 @@ pub enum AdexBehaviourCmd { response_channel: AdexResponseChannel, }, GetPeersInfo { - result_tx: oneshot::Sender>>, + result_tx: oneshot::Sender>>, }, GetGossipMesh { - result_tx: oneshot::Sender>>, + result_tx: oneshot::Sender>>, }, GetGossipPeerTopics { - result_tx: oneshot::Sender>>, + result_tx: oneshot::Sender>>, }, GetGossipTopicPeers { - result_tx: oneshot::Sender>>, + result_tx: oneshot::Sender>>, }, GetRelayMesh { result_tx: oneshot::Sender>, diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index db0600c633..cfe63525da 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -28,9 +28,11 @@ use enum_from::EnumFromTrait; use mm2_core::mm_ctx::{MmArc, MmCtx}; use mm2_err_handle::common_errors::InternalError; use mm2_err_handle::prelude::*; +use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; use mm2_libp2p::{spawn_gossipsub, AdexBehaviourError, NodeType, RelayAddress, RelayAddressError, SwarmRuntime, WssCerts}; use mm2_metrics::mm_gauge; +use mm2_net::network_event::NetworkEvent; use mm2_net::p2p::P2PContext; use rpc_task::RpcTaskError; use serde_json::{self as json}; @@ -52,17 +54,19 @@ use crate::mm2::rpc::spawn_rpc; cfg_native! { use db_common::sqlite::rusqlite::Error as SqlError; - use mm2_event_stream::behaviour::EventBehaviour; use mm2_io::fs::{ensure_dir_is_writable, ensure_file_is_writable}; use mm2_net::ip_addr::myipaddr; - use mm2_net::network_event::NetworkEvent; } #[path = "lp_init/init_context.rs"] mod init_context; #[path = "lp_init/init_hw.rs"] pub mod init_hw; -#[cfg(target_arch = "wasm32")] -#[path = "lp_init/init_metamask.rs"] -pub mod init_metamask; + +cfg_wasm32! { + use mm2_net::wasm_event_stream::handle_worker_stream; + + #[path = "lp_init/init_metamask.rs"] + pub mod init_metamask; +} const NETID_8762_SEEDNODES: [&str; 3] = [ "streamseed1.komodo.earth", @@ -161,6 +165,8 @@ pub enum MmInitError { EmptyPassphrase, #[display(fmt = "Invalid passphrase: {}", _0)] InvalidPassphrase(String), + #[display(fmt = "NETWORK event initialization failed: {}", _0)] + NetworkEventInitFailed(String), #[from_trait(WithHwRpcError::hw_rpc_error)] #[display(fmt = "{}", _0)] HwError(HwRpcError), @@ -384,12 +390,21 @@ fn migrate_db(ctx: &MmArc) -> MmInitResult<()> { #[cfg(not(target_arch = "wasm32"))] fn migration_1(_ctx: &MmArc) {} -#[cfg(not(target_arch = "wasm32"))] -fn init_event_streaming(ctx: &MmArc) { +async fn init_event_streaming(ctx: &MmArc) -> MmInitResult<()> { // This condition only executed if events were enabled in mm2 configuration. if let Some(config) = &ctx.event_stream_configuration { - // Network event handling - NetworkEvent::new(ctx.clone()).spawn_if_active(config); + if let EventInitStatus::Failed(err) = NetworkEvent::new(ctx.clone()).spawn_if_active(config).await { + return MmError::err(MmInitError::NetworkEventInitFailed(err)); + } + } + + Ok(()) +} + +#[cfg(target_arch = "wasm32")] +fn init_wasm_event_streaming(ctx: &MmArc) { + if ctx.event_stream_configuration.is_some() { + ctx.spawner().spawn(handle_worker_stream(ctx.clone())); } } @@ -423,13 +438,15 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { // an order and start new swap that might get started 2 times because of kick-start kick_start(ctx.clone()).await?; - #[cfg(not(target_arch = "wasm32"))] - init_event_streaming(&ctx); + init_event_streaming(&ctx).await?; ctx.spawner().spawn(lp_ordermatch_loop(ctx.clone())); ctx.spawner().spawn(broadcast_maker_orders_keep_alive_loop(ctx.clone())); + #[cfg(target_arch = "wasm32")] + init_wasm_event_streaming(&ctx); + ctx.spawner().spawn(clean_memory_loop(ctx.weak())); Ok(()) diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index f9b67767e5..9c42cd3d38 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -37,7 +37,7 @@ mm2_state_machine = { path = "../mm2_state_machine"} wasm-bindgen = "0.2.86" wasm-bindgen-test = { version = "0.3.2" } wasm-bindgen-futures = "0.4.21" -web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomException", "ErrorEvent", "IdbDatabase", "IdbCursor", "IdbCursorWithValue", "IdbFactory", "IdbIndex", "IdbIndexParameters", "IdbObjectStore", "IdbObjectStoreParameters", "IdbOpenDbRequest", "IdbKeyRange", "IdbTransaction", "IdbTransactionMode", "IdbVersionChangeEvent", "MessageEvent", "WebSocket"] } +web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomException", "ErrorEvent", "IdbDatabase", "IdbCursor", "IdbCursorWithValue", "IdbFactory", "IdbIndex", "IdbIndexParameters", "IdbObjectStore", "IdbObjectStoreParameters", "IdbOpenDbRequest", "IdbKeyRange", "IdbTransaction", "IdbTransactionMode", "IdbVersionChangeEvent", "MessageEvent", "WebSocket", "Worker"] } js-sys = "0.3.27" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/mm2src/mm2_net/src/lib.rs b/mm2src/mm2_net/src/lib.rs index 379f0f928a..edd13738b9 100644 --- a/mm2src/mm2_net/src/lib.rs +++ b/mm2src/mm2_net/src/lib.rs @@ -1,4 +1,5 @@ pub mod grpc_web; +#[cfg(feature = "event-stream")] pub mod network_event; #[cfg(feature = "p2p")] pub mod p2p; pub mod transport; @@ -6,8 +7,8 @@ pub mod transport; #[cfg(not(target_arch = "wasm32"))] pub mod native_http; #[cfg(not(target_arch = "wasm32"))] pub mod native_tls; #[cfg(all(feature = "event-stream", not(target_arch = "wasm32")))] -pub mod network_event; -#[cfg(all(feature = "event-stream", not(target_arch = "wasm32")))] pub mod sse_handler; +#[cfg(all(feature = "event-stream", target_arch = "wasm32"))] +pub mod wasm_event_stream; #[cfg(target_arch = "wasm32")] pub mod wasm_http; #[cfg(target_arch = "wasm32")] pub mod wasm_ws; diff --git a/mm2src/mm2_net/src/network_event.rs b/mm2src/mm2_net/src/network_event.rs index 35ce5de40d..beee72e36f 100644 --- a/mm2src/mm2_net/src/network_event.rs +++ b/mm2src/mm2_net/src/network_event.rs @@ -2,9 +2,10 @@ use crate::p2p::P2PContext; use async_trait::async_trait; use common::{executor::{SpawnFuture, Timer}, log::info}; +use futures::channel::oneshot::{self, Receiver, Sender}; use mm2_core::mm_ctx::MmArc; pub use mm2_event_stream::behaviour::EventBehaviour; -use mm2_event_stream::{Event, EventStreamConfiguration}; +use mm2_event_stream::{behaviour::EventInitStatus, Event, EventStreamConfiguration}; use mm2_libp2p::behaviours::atomicdex; use serde_json::json; @@ -20,8 +21,11 @@ impl NetworkEvent { impl EventBehaviour for NetworkEvent { const EVENT_NAME: &'static str = "NETWORK"; - async fn handle(self, interval: f64) { + async fn handle(self, interval: f64, tx: oneshot::Sender) { let p2p_ctx = P2PContext::fetch_from_mm_arc(&self.ctx); + let mut previously_sent = json!({}); + + tx.send(EventInitStatus::Success).unwrap(); loop { let p2p_cmd_tx = p2p_ctx.cmd_tx.lock().clone(); @@ -40,22 +44,34 @@ impl EventBehaviour for NetworkEvent { "relay_mesh": relay_mesh, }); - self.ctx - .stream_channel_controller - .broadcast(Event::new(Self::EVENT_NAME.to_string(), event_data.to_string())) - .await; + if previously_sent != event_data { + self.ctx + .stream_channel_controller + .broadcast(Event::new(Self::EVENT_NAME.to_string(), event_data.to_string())) + .await; + + previously_sent = event_data; + } Timer::sleep(interval).await; } } - fn spawn_if_active(self, config: &EventStreamConfiguration) { + async fn spawn_if_active(self, config: &EventStreamConfiguration) -> EventInitStatus { if let Some(event) = config.get_event(Self::EVENT_NAME) { info!( "NETWORK event is activated with {} seconds interval.", event.stream_interval_seconds ); - self.ctx.spawner().spawn(self.handle(event.stream_interval_seconds)); + + let (tx, rx): (Sender, Receiver) = oneshot::channel(); + self.ctx.spawner().spawn(self.handle(event.stream_interval_seconds, tx)); + + rx.await.unwrap_or_else(|e| { + EventInitStatus::Failed(format!("Event initialization status must be received: {}", e)) + }) + } else { + EventInitStatus::Inactive } } } diff --git a/mm2src/mm2_net/src/wasm_event_stream.rs b/mm2src/mm2_net/src/wasm_event_stream.rs new file mode 100644 index 0000000000..1c6f8182fc --- /dev/null +++ b/mm2src/mm2_net/src/wasm_event_stream.rs @@ -0,0 +1,26 @@ +use mm2_core::mm_ctx::MmArc; +use serde_json::json; + +/// Handles broadcasted messages from `mm2_event_stream` continuously for WASM. +pub async fn handle_worker_stream(ctx: MmArc) { + let config = ctx + .event_stream_configuration + .as_ref() + .expect("Event stream configuration couldn't be found. This should never happen."); + + let mut channel_controller = ctx.stream_channel_controller.clone(); + let mut rx = channel_controller.create_channel(config.total_active_events()); + + while let Some(event) = rx.recv().await { + let data = json!({ + "_type": event.event_type(), + "message": event.message(), + }); + + let worker = web_sys::Worker::new("worker.js").expect("Missing worker.js"); + let message_js = wasm_bindgen::JsValue::from_str(&data.to_string()); + + worker.post_message(&message_js) + .expect("Incompatible browser!\nSee https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage#browser_compatibility for details."); + } +} From 8d0a58356a4806f88e695e5541ff870daffddba8 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 30 Oct 2023 13:31:25 +0700 Subject: [PATCH 21/40] feat(trading-proto-upgrade): sql storage wip + protocol enhancement (#1980) This commit: - Removes access by index for UTXO transactions handling - Changes OP_CHECKMULTISIG to OP_CHECKSIGVERIFY OP_CHECKSIG - Stores upgraded swaps data to SQLite DB (still WIP) - Implements protocol enhancement for UTXO coins by adding one more funding tx for taker, which can be reclaimed immediately if maker back-outs. - Adds CoinAssocTypes trait representing coin associated types. #1895 --- mm2src/coins/lp_coins.rs | 230 +++-- mm2src/coins/test_coin.rs | 123 ++- mm2src/coins/utxo.rs | 44 +- mm2src/coins/utxo/swap_proto_v2_scripts.rs | 70 +- mm2src/coins/utxo/utxo_common.rs | 612 +++++++++--- mm2src/coins/utxo/utxo_standard.rs | 90 +- mm2src/mm2_bitcoin/chain/src/transaction.rs | 15 + mm2src/mm2_main/src/database.rs | 5 + mm2src/mm2_main/src/database/my_swaps.rs | 177 +++- mm2src/mm2_main/src/lp_ordermatch.rs | 153 +-- mm2src/mm2_main/src/lp_swap.rs | 69 +- .../src/lp_swap/komodefi.swap_v2.pb.rs | 32 +- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 10 +- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 618 ++++++++---- mm2src/mm2_main/src/lp_swap/swap_v2.proto | 27 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 4 +- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 887 +++++++++++++----- .../tests/docker_tests/swap_proto_v2_tests.rs | 202 ++-- .../tests/docker_tests/swap_watcher_tests.rs | 26 +- mm2src/mm2_number/src/mm_number.rs | 21 +- mm2src/mm2_state_machine/src/state_machine.rs | 2 +- 21 files changed, 2585 insertions(+), 832 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index ac063c946c..5eb13daf6a 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -313,10 +313,12 @@ pub type RawTransactionResult = Result = Box> + Send + 'a>; pub type RefundResult = Result>; -/// Helper type used for taker payment's spend preimage generation result -pub type GenTakerPaymentSpendResult = MmResult; -/// Helper type used for taker payment's validation result -pub type ValidateTakerPaymentResult = MmResult<(), ValidateTakerPaymentError>; +/// Helper type used for swap transactions' spend preimage generation result +pub type GenPreimageResult = MmResult, TxGenError>; +/// Helper type used for taker funding's validation result +pub type ValidateTakerFundingResult = MmResult<(), ValidateTakerFundingError>; +/// Helper type used for taker funding's spend preimage validation result +pub type ValidateTakerFundingSpendPreimageResult = MmResult<(), ValidateTakerFundingSpendPreimageError>; /// Helper type used for taker payment's spend preimage validation result pub type ValidateTakerPaymentSpendPreimageResult = MmResult<(), ValidateTakerPaymentSpendPreimageError>; @@ -1081,14 +1083,14 @@ pub trait WatcherOps { ) -> Result, MmError>; } -/// Helper struct wrapping arguments for [SwapOpsV2::send_combined_taker_payment] -pub struct SendCombinedTakerPaymentArgs<'a> { +/// Helper struct wrapping arguments for [SwapOpsV2::send_taker_funding] +pub struct SendTakerFundingArgs<'a> { /// Taker will be able to refund the payment after this timestamp pub time_lock: u64, - /// The hash of the secret generated by maker - pub secret_hash: &'a [u8], + /// The hash of the secret generated by taker, this needs to be revealed for immediate refund + pub taker_secret_hash: &'a [u8], /// Maker's pubkey - pub other_pub: &'a [u8], + pub maker_pub: &'a [u8], /// DEX fee amount pub dex_fee_amount: BigDecimal, /// Additional reward for maker (premium) @@ -1099,16 +1101,46 @@ pub struct SendCombinedTakerPaymentArgs<'a> { pub swap_unique_data: &'a [u8], } -/// Helper struct wrapping arguments for [SwapOpsV2::validate_combined_taker_payment] -pub struct ValidateTakerPaymentArgs<'a> { +/// Helper struct wrapping arguments for [SwapOpsV2::refund_taker_funding_secret] +pub struct RefundFundingSecretArgs<'a, Coin: CoinAssocTypes + ?Sized> { + pub funding_tx: &'a Coin::Tx, + pub time_lock: u64, + pub maker_pubkey: &'a Coin::Pubkey, + pub taker_secret: &'a [u8], + pub taker_secret_hash: &'a [u8], + pub swap_contract_address: &'a Option, + pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, +} + +/// Helper struct wrapping arguments for [SwapOpsV2::gen_taker_funding_spend_preimage] +pub struct GenTakerFundingSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a [u8], + pub funding_tx: &'a Coin::Tx, + /// Maker's pubkey + pub maker_pub: &'a Coin::Pubkey, + /// Taker's pubkey + pub taker_pub: &'a Coin::Pubkey, + /// Timelock of the funding tx + pub funding_time_lock: u64, + /// The hash of the secret generated by taker + pub taker_secret_hash: &'a [u8], + /// Timelock of the taker payment + pub taker_payment_time_lock: u64, + /// The hash of the secret generated by maker + pub maker_secret_hash: &'a [u8], +} + +/// Helper struct wrapping arguments for [SwapOpsV2::validate_taker_funding] +pub struct ValidateTakerFundingArgs<'a, Coin: CoinAssocTypes + ?Sized> { + /// Taker funding transaction + pub funding_tx: &'a Coin::Tx, /// Taker will be able to refund the payment after this timestamp pub time_lock: u64, - /// The hash of the secret generated by maker - pub secret_hash: &'a [u8], + /// The hash of the secret generated by taker + pub taker_secret_hash: &'a [u8], /// Taker's pubkey - pub other_pub: &'a [u8], + pub other_pub: &'a Coin::Pubkey, /// DEX fee amount pub dex_fee_amount: BigDecimal, /// Additional reward for maker (premium) @@ -1122,17 +1154,17 @@ pub struct ValidateTakerPaymentArgs<'a> { /// Helper struct wrapping arguments for taker payment's spend generation, used in /// [SwapOpsV2::gen_taker_payment_spend_preimage], [SwapOpsV2::validate_taker_payment_spend_preimage] and /// [SwapOpsV2::sign_and_broadcast_taker_payment_spend] -pub struct GenTakerPaymentSpendArgs<'a> { +pub struct GenTakerPaymentSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a [u8], + pub taker_tx: &'a Coin::Tx, /// Taker will be able to refund the payment after this timestamp pub time_lock: u64, /// The hash of the secret generated by maker pub secret_hash: &'a [u8], /// Maker's pubkey - pub maker_pub: &'a [u8], + pub maker_pub: &'a Coin::Pubkey, /// Taker's pubkey - pub taker_pub: &'a [u8], + pub taker_pub: &'a Coin::Pubkey, /// Pubkey of address, receiving DEX fees pub dex_fee_pub: &'a [u8], /// DEX fee amount @@ -1144,14 +1176,14 @@ pub struct GenTakerPaymentSpendArgs<'a> { } /// Taker payment spend preimage with taker's signature -pub struct TxPreimageWithSig { - /// The preimage tx serialized to raw bytes, might be empty for certain coin protocols - pub preimage: Vec, +pub struct TxPreimageWithSig { + /// The preimage, might be () for certain coin types (only signature might be used) + pub preimage: Coin::Preimage, /// Taker's signature - pub signature: Vec, + pub signature: Coin::Sig, } -/// Enum covering error cases that can happen during taker payment spend preimage generation. +/// Enum covering error cases that can happen during transaction preimage generation. #[derive(Debug, Display)] pub enum TxGenError { /// RPC error @@ -1160,16 +1192,16 @@ pub enum TxGenError { NumConversion(String), /// Address derivation error. AddressDerivation(String), - /// Error during transaction raw bytes deserialization. - TxDeserialization(String), - /// Error during pubkey deserialization. - InvalidPubkey(String), /// Problem with tx preimage signing. Signing(String), /// Legacy error produced by usage of try_s/try_fus and other similar macros. Legacy(String), /// Input payment timelock overflows the type used by specific coin. LocktimeOverflow(String), + /// Transaction fee is too high + TxFeeTooHigh(String), + /// Previous tx is not valid + PrevTxIsNotValid(String), } impl From for TxGenError { @@ -1184,48 +1216,76 @@ impl From for TxGenError { fn from(err: UtxoSignWithKeyPairError) -> Self { TxGenError::Signing(err.to_string()) } } -/// Enum covering error cases that can happen during taker payment validation. -#[derive(Debug)] -pub enum ValidateTakerPaymentError { +/// Enum covering error cases that can happen during taker funding validation. +#[derive(Debug, Display)] +pub enum ValidateTakerFundingError { /// Payment sent to wrong address or has invalid amount. InvalidDestinationOrAmount(String), - /// Error during pubkey deserialization. - InvalidPubkey(String), /// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.). NumConversion(String), /// RPC error. Rpc(String), - /// Serialized tx bytes doesn't match ones received from coin's RPC. + /// Serialized tx bytes don't match ones received from coin's RPC. + #[display(fmt = "Tx bytes {:02x} don't match ones received from rpc {:02x}", actual, from_rpc)] TxBytesMismatch { from_rpc: BytesJson, actual: BytesJson }, - /// Error during transaction raw bytes deserialization. - TxDeserialization(String), /// Provided transaction doesn't have output with specific index TxLacksOfOutputs, /// Input payment timelock overflows the type used by specific coin. LocktimeOverflow(String), } -impl From for ValidateTakerPaymentError { - fn from(err: NumConversError) -> Self { ValidateTakerPaymentError::NumConversion(err.to_string()) } +impl From for ValidateTakerFundingError { + fn from(err: NumConversError) -> Self { ValidateTakerFundingError::NumConversion(err.to_string()) } +} + +impl From for ValidateTakerFundingError { + fn from(err: UtxoRpcError) -> Self { ValidateTakerFundingError::Rpc(err.to_string()) } +} + +/// Enum covering error cases that can happen during taker funding spend preimage validation. +#[derive(Debug, Display)] +pub enum ValidateTakerFundingSpendPreimageError { + /// Funding tx has no outputs + FundingTxNoOutputs, + /// Actual preimage fee is either too high or too small + UnexpectedPreimageFee(String), + /// Error during signature deserialization. + InvalidMakerSignature, + /// Error during preimage comparison to an expected one. + InvalidPreimage(String), + /// Error during taker's signature check. + SignatureVerificationFailure(String), + /// Error during generation of an expected preimage. + TxGenError(String), + /// Input payment timelock overflows the type used by specific coin. + LocktimeOverflow(String), + /// Coin's RPC error + Rpc(String), +} + +impl From for ValidateTakerFundingSpendPreimageError { + fn from(err: UtxoSignWithKeyPairError) -> Self { + ValidateTakerFundingSpendPreimageError::SignatureVerificationFailure(err.to_string()) + } +} + +impl From for ValidateTakerFundingSpendPreimageError { + fn from(err: TxGenError) -> Self { ValidateTakerFundingSpendPreimageError::TxGenError(format!("{:?}", err)) } } -impl From for ValidateTakerPaymentError { - fn from(err: UtxoRpcError) -> Self { ValidateTakerPaymentError::Rpc(err.to_string()) } +impl From for ValidateTakerFundingSpendPreimageError { + fn from(err: UtxoRpcError) -> Self { ValidateTakerFundingSpendPreimageError::Rpc(err.to_string()) } } /// Enum covering error cases that can happen during taker payment spend preimage validation. #[derive(Debug, Display)] pub enum ValidateTakerPaymentSpendPreimageError { - /// Error during pubkey deserialization. - InvalidPubkey(String), /// Error during signature deserialization. InvalidTakerSignature, /// Error during preimage comparison to an expected one. InvalidPreimage(String), /// Error during taker's signature check. SignatureVerificationFailure(String), - /// Error during preimage raw bytes deserialization. - TxDeserialization(String), /// Error during generation of an expected preimage. TxGenError(String), /// Input payment timelock overflows the type used by specific coin. @@ -1242,14 +1302,71 @@ impl From for ValidateTakerPaymentSpendPreimageError { fn from(err: TxGenError) -> Self { ValidateTakerPaymentSpendPreimageError::TxGenError(format!("{:?}", err)) } } +/// Helper trait used for various types serialization to bytes +pub trait ToBytes { + fn to_bytes(&self) -> Vec; +} + +/// Defines associated types specific to each coin (Pubkey, Address, etc.) +pub trait CoinAssocTypes { + type Pubkey: ToBytes + Send + Sync; + type PubkeyParseError: Send + std::fmt::Display; + type Tx: Transaction + Send + Sync; + type TxParseError: Send + std::fmt::Display; + type Preimage: ToBytes + Send + Sync; + type PreimageParseError: Send + std::fmt::Display; + type Sig: ToBytes + Send + Sync; + type SigParseError: Send + std::fmt::Display; + + fn parse_pubkey(&self, pubkey: &[u8]) -> Result; + + fn parse_tx(&self, tx: &[u8]) -> Result; + + fn parse_preimage(&self, tx: &[u8]) -> Result; + + fn parse_signature(&self, sig: &[u8]) -> Result; +} + /// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait SwapOpsV2: Send + Sync + 'static { - /// Generate and broadcast taker payment transaction that includes dex fee, maker premium and actual trading volume. - async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult; +pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { + /// Generate and broadcast taker funding transaction that includes dex fee, maker premium and actual trading volume. + /// Funding tx can be reclaimed immediately if maker back-outs (doesn't send maker payment) + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result; + + /// Validates taker funding transaction. + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult; + + /// Refunds taker funding transaction using time-locked path without secret reveal. + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; + + /// Reclaims taker funding transaction using immediate refund path with secret reveal. + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result; + + /// Generates and signs a preimage spending funding tx to the combined taker payment + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + swap_unique_data: &[u8], + ) -> GenPreimageResult; + + /// Validates taker funding spend preimage generated and signed by maker + async fn validate_taker_funding_spend_preimage( + &self, + gen_args: &GenTakerFundingSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult; - /// Validates taker payment transaction. - async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult; + /// Generates and signs a preimage spending funding tx to the combined taker payment + async fn sign_and_send_taker_funding_spend( + &self, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, + swap_unique_data: &[u8], + ) -> Result; /// Refunds taker payment transaction. async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; @@ -1258,25 +1375,28 @@ pub trait SwapOpsV2: Send + Sync + 'static { /// shared with maker to proceed with protocol execution. async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenTakerPaymentSpendResult; + ) -> GenPreimageResult; /// Validate taker payment spend preimage on maker's side. async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult; /// Sign and broadcast taker payment spend on maker's side. async fn sign_and_broadcast_taker_payment_spend( &self, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult; + + /// Derives an HTLC key-pair and returns a public key corresponding to that key. + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey; } /// Operations that coins have independently from the MarketMaker. diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 3991d73e17..d21b438f58 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -2,20 +2,21 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; -use crate::ValidateWatcherSpendInput; -use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, - ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenTakerPaymentSpendArgs, - GenTakerPaymentSpendResult, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionResult, TxMarshalingErr, TxPreimageWithSig, +use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, + CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, + NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, + SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateTakerPaymentArgs, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawRequest}; + ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; +use crate::{ToBytes, ValidateWatcherSpendInput}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -387,42 +388,122 @@ impl MmCoin for TestCoin { fn on_token_deactivated(&self, _ticker: &str) { () } } +pub struct TestPubkey {} + +impl ToBytes for TestPubkey { + fn to_bytes(&self) -> Vec { vec![] } +} + +#[derive(Debug)] +pub struct TestTx {} + +impl Transaction for TestTx { + fn tx_hex(&self) -> Vec { todo!() } + + fn tx_hash(&self) -> BytesJson { todo!() } +} + +pub struct TestPreimage {} + +impl ToBytes for TestPreimage { + fn to_bytes(&self) -> Vec { vec![] } +} + +pub struct TestSig {} + +impl ToBytes for TestSig { + fn to_bytes(&self) -> Vec { vec![] } +} + +impl CoinAssocTypes for TestCoin { + type Pubkey = TestPubkey; + type PubkeyParseError = String; + type Tx = TestTx; + type TxParseError = String; + type Preimage = TestPreimage; + type PreimageParseError = String; + type Sig = TestSig; + type SigParseError = String; + + fn parse_pubkey(&self, pubkey: &[u8]) -> Result { unimplemented!() } + + fn parse_tx(&self, tx: &[u8]) -> Result { unimplemented!() } + + fn parse_preimage(&self, preimage: &[u8]) -> Result { todo!() } + + fn parse_signature(&self, sig: &[u8]) -> Result { todo!() } +} + #[async_trait] #[mockable] impl SwapOpsV2 for TestCoin { - async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { todo!() } + + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { unimplemented!() } - async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult { - unimplemented!() + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { todo!() } + + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result { + todo!() + } + + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + swap_unique_data: &[u8], + ) -> GenPreimageResult { + todo!() + } + + async fn validate_taker_funding_spend_preimage( + &self, + gen_args: &GenTakerFundingSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult { + todo!() + } + + async fn sign_and_send_taker_funding_spend( + &self, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, + swap_unique_data: &[u8], + ) -> Result { + todo!() } async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { unimplemented!() } async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenTakerPaymentSpendResult { + ) -> GenPreimageResult { unimplemented!() } async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { unimplemented!() } async fn sign_and_broadcast_taker_payment_spend( &self, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { unimplemented!() } + + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index ab1fee5bde..19a9d9cd7d 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -60,6 +60,7 @@ use futures::compat::Future01CompatExt; use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use futures01::Future; use keys::bytes::Bytes; +use keys::Signature; pub use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHashEnum, KeyPair, Private, Public, Secret, Type as ScriptType}; #[cfg(not(target_arch = "wasm32"))] @@ -74,8 +75,9 @@ use num_traits::ToPrimitive; use primitives::hash::{H160, H256, H264}; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use script::{Builder, Script, SignatureVersion, TransactionInputSigner}; +use secp256k1::Signature as SecpSignature; use serde_json::{self as json, Value as Json}; -use serialization::{serialize, serialize_with_flags, Error as SerError, SERIALIZE_TRANSACTION_WITNESS}; +use serialization::{deserialize, serialize, serialize_with_flags, Error as SerError, SERIALIZE_TRANSACTION_WITNESS}; use spv_validation::conf::SPVConf; use spv_validation::helpers_validation::SPVError; use spv_validation::storage::BlockHeaderStorageError; @@ -110,6 +112,7 @@ use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDAddressId, HD InvalidBip44ChainError}; use crate::hd_wallet_storage::{HDAccountStorageItem, HDWalletCoinStorage, HDWalletStorageError, HDWalletStorageResult}; use crate::utxo::tx_cache::UtxoVerboseCacheShared; +use crate::{CoinAssocTypes, ToBytes}; pub mod tx_cache; @@ -1011,6 +1014,45 @@ pub trait UtxoCommonOps: } } +impl ToBytes for UtxoTx { + fn to_bytes(&self) -> Vec { self.tx_hex() } +} + +impl ToBytes for Signature { + fn to_bytes(&self) -> Vec { self.to_vec() } +} + +impl CoinAssocTypes for T { + type Pubkey = Public; + type PubkeyParseError = MmError; + type Tx = UtxoTx; + type TxParseError = MmError; + type Preimage = UtxoTx; + type PreimageParseError = MmError; + type Sig = Signature; + type SigParseError = MmError; + + #[inline] + fn parse_pubkey(&self, pubkey: &[u8]) -> Result { + Ok(Public::from_slice(pubkey)?) + } + + #[inline] + fn parse_tx(&self, tx: &[u8]) -> Result { + let mut tx: UtxoTx = deserialize(tx)?; + tx.tx_hash_algo = self.as_ref().tx_hash_algo; + Ok(tx) + } + + #[inline] + fn parse_preimage(&self, tx: &[u8]) -> Result { self.parse_tx(tx) } + + fn parse_signature(&self, sig: &[u8]) -> Result { + SecpSignature::from_der(sig)?; + Ok(sig.into()) + } +} + #[async_trait] #[cfg_attr(test, mockable)] pub trait GetUtxoListOps { diff --git a/mm2src/coins/utxo/swap_proto_v2_scripts.rs b/mm2src/coins/utxo/swap_proto_v2_scripts.rs index 153f0bc4bb..f0b5231e04 100644 --- a/mm2src/coins/utxo/swap_proto_v2_scripts.rs +++ b/mm2src/coins/utxo/swap_proto_v2_scripts.rs @@ -4,37 +4,79 @@ use bitcrypto::ripemd160; use keys::Public; use script::{Builder, Opcode, Script}; -/// Builds a script for refundable dex_fee + premium taker transaction -pub fn taker_payment_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: &Public) -> Script { +/// Builds a script for taker funding transaction +pub fn taker_funding_script( + time_lock: u32, + taker_secret_hash: &[u8], + taker_pub: &Public, + maker_pub: &Public, +) -> Script { let mut builder = Builder::default() - // Dex fee refund path, same lock time as for taker payment .push_opcode(Opcode::OP_IF) .push_bytes(&time_lock.to_le_bytes()) .push_opcode(Opcode::OP_CHECKLOCKTIMEVERIFY) .push_opcode(Opcode::OP_DROP) - .push_bytes(pub_0) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIG) + .push_opcode(Opcode::OP_ELSE) + .push_opcode(Opcode::OP_IF) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIGVERIFY) + .push_bytes(maker_pub) .push_opcode(Opcode::OP_CHECKSIG) - // Dex fee redeem path, Maker needs to reveal the secret to prevent case of getting - // the premium but not proceeding with spending the taker payment .push_opcode(Opcode::OP_ELSE) .push_opcode(Opcode::OP_SIZE) .push_bytes(&[32]) .push_opcode(Opcode::OP_EQUALVERIFY) .push_opcode(Opcode::OP_HASH160); - if secret_hash.len() == 32 { - builder = builder.push_bytes(ripemd160(secret_hash).as_slice()); + if taker_secret_hash.len() == 32 { + builder = builder.push_bytes(ripemd160(taker_secret_hash).as_slice()); } else { - builder = builder.push_bytes(secret_hash); + builder = builder.push_bytes(taker_secret_hash); } builder .push_opcode(Opcode::OP_EQUALVERIFY) - .push_opcode(Opcode::OP_2) - .push_bytes(pub_0) - .push_bytes(pub_1) - .push_opcode(Opcode::OP_2) - .push_opcode(Opcode::OP_CHECKMULTISIG) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIG) + .push_opcode(Opcode::OP_ENDIF) + .push_opcode(Opcode::OP_ENDIF) + .into_script() +} + +/// Builds a script for combined trading_volume + dex_fee + premium taker transaction +pub fn taker_payment_script( + time_lock: u32, + maker_secret_hash: &[u8], + taker_pub: &Public, + maker_pub: &Public, +) -> Script { + let mut builder = Builder::default() + .push_opcode(Opcode::OP_IF) + .push_bytes(&time_lock.to_le_bytes()) + .push_opcode(Opcode::OP_CHECKLOCKTIMEVERIFY) + .push_opcode(Opcode::OP_DROP) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIG) + .push_opcode(Opcode::OP_ELSE) + .push_opcode(Opcode::OP_SIZE) + .push_bytes(&[32]) + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_opcode(Opcode::OP_HASH160); + + if maker_secret_hash.len() == 32 { + builder = builder.push_bytes(ripemd160(maker_secret_hash).as_slice()); + } else { + builder = builder.push_bytes(maker_secret_hash); + } + + builder + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIGVERIFY) + .push_bytes(maker_pub) + .push_opcode(Opcode::OP_CHECKSIG) .push_opcode(Opcode::OP_ENDIF) .into_script() } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 9f657d02d2..82e33e4b36 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,18 +15,20 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::watcher_common::validate_watcher_reward; -use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenTakerPaymentSpendArgs, - GenTakerPaymentSpendResult, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, - RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, - SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, - SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TransactionResult, - TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, - ValidateTakerPaymentError, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageError, - ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationError, VerificationResult, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, - WithdrawResult, WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, - INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, + RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, + RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, + TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, + ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + ValidateTakerFundingArgs, ValidateTakerFundingError, ValidateTakerFundingResult, + ValidateTakerFundingSpendPreimageError, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageError, ValidateTakerPaymentSpendPreimageResult, + ValidateWatcherSpendInput, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, + WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, + INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; @@ -50,7 +52,7 @@ use mm2_number::{BigDecimal, MmNumber}; use primitives::hash::H512; use rpc::v1::types::{Bytes as BytesJson, ToTxHash, TransactionInputEnum, H256 as H256Json}; use script::{Builder, Opcode, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput}; -use secp256k1::{PublicKey, Signature}; +use secp256k1::{PublicKey, Signature as SecpSignature}; use serde_json::{self as json}; use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Serializable, Stream, SERIALIZE_TRANSACTION_WITNESS}; @@ -1114,22 +1116,34 @@ enum LocktimeSetting { UseExact(u32), } +enum NTimeSetting { + UseNow, + UseValue(Option), +} + +enum FundingSpendFeeSetting { + GetFromCoin, + UseExact(u64), +} + async fn p2sh_spending_tx_preimage( coin: &T, prev_tx: &UtxoTx, lock_time: LocktimeSetting, + set_n_time: NTimeSetting, sequence: u32, outputs: Vec, ) -> Result { - if prev_tx.outputs.is_empty() { - return ERR!("Previous transaction doesn't have any output"); - } + let amount = try_s!(prev_tx.first_output()).value; let lock_time = match lock_time { LocktimeSetting::CalcByHtlcLocktime(lock) => try_s!(coin.p2sh_tx_locktime(lock).await), LocktimeSetting::UseExact(lock) => lock, }; let n_time = if coin.as_ref().conf.is_pos { - Some(now_sec_u32()) + match set_n_time { + NTimeSetting::UseNow => Some(now_sec_u32()), + NTimeSetting::UseValue(value) => value, + } } else { None }; @@ -1150,7 +1164,7 @@ async fn p2sh_spending_tx_preimage( hash: prev_tx.hash(), index: DEFAULT_SWAP_VOUT as u32, }, - amount: prev_tx.outputs[0].value, + amount, witness: Vec::new(), }], outputs, @@ -1174,6 +1188,7 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI coin, &input.prev_transaction, LocktimeSetting::CalcByHtlcLocktime(input.lock_time), + NTimeSetting::UseNow, input.sequence, input.outputs ) @@ -1211,17 +1226,267 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI }) } -pub type GenDexFeeSpendResult = MmResult; +type GenPreimageResInner = MmResult; -async fn gen_taker_payment_spend_preimage( +async fn gen_taker_funding_spend_preimage( coin: &T, - args: &GenTakerPaymentSpendArgs<'_>, - lock_time: LocktimeSetting, -) -> GenDexFeeSpendResult { - let mut prev_tx: UtxoTx = deserialize(args.taker_tx).map_to_mm(|e| TxGenError::TxDeserialization(e.to_string()))?; - prev_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - drop_mutability!(prev_tx); + args: &GenTakerFundingSpendArgs<'_, T>, + n_time: NTimeSetting, + fee: FundingSpendFeeSetting, +) -> GenPreimageResInner { + let payment_time_lock = args + .taker_payment_time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; + + let payment_redeem_script = swap_proto_v2_scripts::taker_payment_script( + payment_time_lock, + args.maker_secret_hash, + args.taker_pub, + args.maker_pub, + ); + + let funding_amount = args + .funding_tx + .first_output() + .map_to_mm(|_| TxGenError::PrevTxIsNotValid("Funding tx has no outputs".into()))? + .value; + + let fee = match fee { + FundingSpendFeeSetting::GetFromCoin => { + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await? + }, + FundingSpendFeeSetting::UseExact(f) => f, + }; + + let fee_plus_dust = fee + coin.as_ref().dust_amount; + if funding_amount < fee_plus_dust { + return MmError::err(TxGenError::TxFeeTooHigh(format!( + "Fee + dust {} is larger than funding amount {}", + fee_plus_dust, funding_amount + ))); + } + + let payment_output = TransactionOutput { + value: funding_amount - fee, + script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&payment_redeem_script))).to_bytes(), + }; + + p2sh_spending_tx_preimage( + coin, + args.funding_tx, + LocktimeSetting::UseExact(0), + n_time, + SEQUENCE_FINAL, + vec![payment_output], + ) + .await + .map_to_mm(TxGenError::Legacy) +} + +pub async fn gen_and_sign_taker_funding_spend_preimage( + coin: &T, + args: &GenTakerFundingSpendArgs<'_, T>, + htlc_keypair: &KeyPair, +) -> GenPreimageResult { + let funding_time_lock = args + .funding_time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; + + let preimage = + gen_taker_funding_spend_preimage(coin, args, NTimeSetting::UseNow, FundingSpendFeeSetting::GetFromCoin).await?; + + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + funding_time_lock, + args.taker_secret_hash, + args.taker_pub, + args.maker_pub, + ); + let signature = calc_and_sign_sighash( + &preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id, + )?; + Ok(TxPreimageWithSig { + preimage: preimage.into(), + signature: signature.take().into(), + }) +} + +/// Common implementation of taker funding spend preimage validation for UTXO coins. +/// Checks maker's signature and compares received preimage with the expected tx. +pub async fn validate_taker_funding_spend_preimage( + coin: &T, + gen_args: &GenTakerFundingSpendArgs<'_, T>, + preimage: &TxPreimageWithSig, +) -> ValidateTakerFundingSpendPreimageResult { + let funding_amount = gen_args + .funding_tx + .first_output() + .map_to_mm(|_| ValidateTakerFundingSpendPreimageError::FundingTxNoOutputs)? + .value; + + let payment_amount = preimage + .preimage + .first_output() + .map_to_mm(|_| ValidateTakerFundingSpendPreimageError::InvalidPreimage("Preimage has no outputs".into()))? + .value; + + if payment_amount > funding_amount { + return MmError::err(ValidateTakerFundingSpendPreimageError::InvalidPreimage(format!( + "Preimage output {} larger than funding input {}", + payment_amount, funding_amount + ))); + } + + let expected_fee = coin + .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await?; + + let actual_fee = funding_amount - payment_amount; + + let fee_div = expected_fee as f64 / actual_fee as f64; + + if !(0.9..=1.1).contains(&fee_div) { + return MmError::err(ValidateTakerFundingSpendPreimageError::UnexpectedPreimageFee(format!( + "Too large difference between expected {} and actual {} fees", + expected_fee, actual_fee + ))); + } + + let expected_preimage = gen_taker_funding_spend_preimage( + coin, + gen_args, + NTimeSetting::UseValue(preimage.preimage.n_time), + FundingSpendFeeSetting::UseExact(actual_fee), + ) + .await?; + + let funding_time_lock = gen_args + .funding_time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| ValidateTakerFundingSpendPreimageError::LocktimeOverflow(e.to_string()))?; + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + funding_time_lock, + gen_args.taker_secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); + let sig_hash = signature_hash_to_sign( + &expected_preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id, + )?; + + if !gen_args + .maker_pub + .verify(&sig_hash, &preimage.signature) + .map_to_mm(|e| ValidateTakerFundingSpendPreimageError::SignatureVerificationFailure(e.to_string()))? + { + return MmError::err(ValidateTakerFundingSpendPreimageError::InvalidMakerSignature); + }; + let expected_preimage_tx: UtxoTx = expected_preimage.into(); + if expected_preimage_tx != preimage.preimage { + return MmError::err(ValidateTakerFundingSpendPreimageError::InvalidPreimage( + "Preimage is not equal to expected".into(), + )); + } + Ok(()) +} + +/// Common implementation of taker funding spend finalization and broadcast for UTXO coins. +pub async fn sign_and_send_taker_funding_spend( + coin: &T, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerFundingSpendArgs<'_, T>, + htlc_keypair: &KeyPair, +) -> Result { + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + try_tx_s!(gen_args.funding_time_lock.try_into()), + gen_args.taker_secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); + + let mut signer: TransactionInputSigner = preimage.preimage.clone().into(); + let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); + let funding_output = try_tx_s!(gen_args.funding_tx.first_output()); + payment_input.amount = funding_output.value; + signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; + + let taker_signature = try_tx_s!(calc_and_sign_sighash( + &signer, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id + )); + let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; + + let mut maker_signature_with_sighash = preimage.signature.to_vec(); + maker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(maker_signature_with_sighash); + + let mut taker_signature_with_sighash: Vec = taker_signature.take(); + taker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(taker_signature_with_sighash); + + let script_sig = Builder::default() + .push_data(&maker_signature_with_sighash) + .push_data(&taker_signature_with_sighash) + .push_opcode(Opcode::OP_1) + .push_opcode(Opcode::OP_0) + .push_data(&redeem_script) + .into_bytes(); + let mut final_tx: UtxoTx = signer.into(); + let final_tx_input = try_tx_s!(final_tx.inputs.first_mut().ok_or("Final tx doesn't have inputs")); + final_tx_input.script_sig = script_sig; + drop_mutability!(final_tx); + if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { + let payment_redeem_script = swap_proto_v2_scripts::taker_payment_script( + try_tx_s!(gen_args.taker_payment_time_lock.try_into()), + gen_args.maker_secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); + let payment_address = Address { + checksum_type: coin.as_ref().conf.checksum_type, + hash: AddressHashEnum::AddressHash(dhash160(&payment_redeem_script)), + prefix: coin.as_ref().conf.p2sh_addr_prefix, + t_addr_prefix: coin.as_ref().conf.p2sh_t_addr_prefix, + hrp: coin.as_ref().conf.bech32_hrp.clone(), + addr_format: UtxoAddressFormat::Standard, + }; + let payment_address_str = payment_address.to_string(); + try_tx_s!( + client + .import_address(&payment_address_str, &payment_address_str, false) + .compat() + .await + ); + } + + try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); + Ok(final_tx) +} + +async fn gen_taker_payment_spend_preimage( + coin: &T, + args: &GenTakerPaymentSpendArgs<'_, T>, + n_time: NTimeSetting, +) -> GenPreimageResInner { let dex_fee_sat = sat_from_big_decimal(&args.dex_fee_amount, coin.as_ref().decimals)?; let dex_fee_address = address_from_raw_pubkey( @@ -1238,27 +1503,32 @@ async fn gen_taker_payment_spend_preimage( script_pubkey: Builder::build_p2pkh(&dex_fee_address.hash).to_bytes(), }; - p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, SEQUENCE_FINAL, vec![dex_fee_output]) - .await - .map_to_mm(TxGenError::Legacy) + p2sh_spending_tx_preimage( + coin, + args.taker_tx, + LocktimeSetting::UseExact(0), + n_time, + SEQUENCE_FINAL, + vec![dex_fee_output], + ) + .await + .map_to_mm(TxGenError::Legacy) } pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, T>, htlc_keypair: &KeyPair, -) -> GenTakerPaymentSpendResult { - let maker_pub = Public::from_slice(args.maker_pub).map_to_mm(|e| TxGenError::InvalidPubkey(e.to_string()))?; - let taker_pub = Public::from_slice(args.taker_pub).map_to_mm(|e| TxGenError::InvalidPubkey(e.to_string()))?; +) -> GenPreimageResult { let time_lock = args .time_lock .try_into() .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; - let preimage = gen_taker_payment_spend_preimage(coin, args, LocktimeSetting::CalcByHtlcLocktime(time_lock)).await?; + let preimage = gen_taker_payment_spend_preimage(coin, args, NTimeSetting::UseNow).await?; let redeem_script = - swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, &taker_pub, &maker_pub); + swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, args.taker_pub, args.maker_pub); let signature = calc_and_sign_sighash( &preimage, DEFAULT_SWAP_VOUT, @@ -1268,10 +1538,9 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( SIGHASH_SINGLE, coin.as_ref().conf.fork_id, )?; - let preimage_tx: UtxoTx = preimage.into(); Ok(TxPreimageWithSig { - preimage: serialize(&preimage_tx).take(), - signature: signature.take(), + preimage: preimage.into(), + signature: signature.take().into(), }) } @@ -1279,33 +1548,24 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( /// Checks taker's signature and compares received preimage with the expected tx. pub async fn validate_taker_payment_spend_preimage( coin: &T, - gen_args: &GenTakerPaymentSpendArgs<'_>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, T>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { - // TODO validate that preimage has exactly 2 outputs - let actual_preimage_tx: UtxoTx = deserialize(preimage.preimage.as_slice()) - .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::TxDeserialization(e.to_string()))?; - - let maker_pub = Public::from_slice(gen_args.maker_pub) - .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::InvalidPubkey(e.to_string()))?; - let taker_pub = Public::from_slice(gen_args.taker_pub) - .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::InvalidPubkey(e.to_string()))?; - - // TODO validate premium amount. Might be a bit tricky in the case of dynamic miner fee - // TODO validate that output amounts are larger than dust - // Here, we have to use the exact lock time from the preimage because maker // can get different values (e.g. if MTP advances during preimage exchange/fee rate changes) let expected_preimage = - gen_taker_payment_spend_preimage(coin, gen_args, LocktimeSetting::UseExact(actual_preimage_tx.lock_time)) - .await?; + gen_taker_payment_spend_preimage(coin, gen_args, NTimeSetting::UseValue(preimage.preimage.n_time)).await?; let time_lock = gen_args .time_lock .try_into() .map_to_mm(|e: TryFromIntError| ValidateTakerPaymentSpendPreimageError::LocktimeOverflow(e.to_string()))?; - let redeem_script = - swap_proto_v2_scripts::taker_payment_script(time_lock, gen_args.secret_hash, &taker_pub, &maker_pub); + let redeem_script = swap_proto_v2_scripts::taker_payment_script( + time_lock, + gen_args.secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); let sig_hash = signature_hash_to_sign( &expected_preimage, DEFAULT_SWAP_VOUT, @@ -1315,14 +1575,15 @@ pub async fn validate_taker_payment_spend_preimage( coin.as_ref().conf.fork_id, )?; - if !taker_pub - .verify(&sig_hash, &preimage.signature.clone().into()) + if !gen_args + .taker_pub + .verify(&sig_hash, &preimage.signature) .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::SignatureVerificationFailure(e.to_string()))? { return MmError::err(ValidateTakerPaymentSpendPreimageError::InvalidTakerSignature); }; let expected_preimage_tx: UtxoTx = expected_preimage.into(); - if expected_preimage_tx != actual_preimage_tx { + if expected_preimage_tx != preimage.preimage { return MmError::err(ValidateTakerPaymentSpendPreimageError::InvalidPreimage( "Preimage is not equal to expected".into(), )); @@ -1334,31 +1595,23 @@ pub async fn validate_taker_payment_spend_preimage( /// Appends maker output to the preimage, signs it with SIGHASH_ALL and submits the resulting tx to coin's RPC. pub async fn sign_and_broadcast_taker_payment_spend( coin: &T, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, T>, secret: &[u8], htlc_keypair: &KeyPair, ) -> TransactionResult { - let taker_pub = try_tx_s!(Public::from_slice(gen_args.taker_pub)); - - let mut taker_tx: UtxoTx = try_tx_s!(deserialize(gen_args.taker_tx)); - taker_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - drop_mutability!(taker_tx); - - let mut preimage_tx: UtxoTx = try_tx_s!(deserialize(preimage.preimage.as_slice())); - preimage_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - drop_mutability!(preimage_tx); - let secret_hash = dhash160(secret); let redeem_script = swap_proto_v2_scripts::taker_payment_script( try_tx_s!(gen_args.time_lock.try_into()), secret_hash.as_slice(), - &taker_pub, + gen_args.taker_pub, htlc_keypair.public(), ); - let mut signer: TransactionInputSigner = preimage_tx.clone().into(); - signer.inputs[0].amount = taker_tx.outputs[0].value; + let mut signer: TransactionInputSigner = preimage.preimage.clone().into(); + let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); + let payment_output = try_tx_s!(gen_args.taker_tx.first_output()); + payment_input.amount = payment_output.value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; let miner_fee = try_tx_s!( @@ -1369,7 +1622,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( let maker_amount = &gen_args.trading_amount + &gen_args.premium_amount; let maker_sat = try_tx_s!(sat_from_big_decimal(&maker_amount, coin.as_ref().decimals)); if miner_fee + coin.as_ref().dust_amount > maker_sat { - return TX_PLAIN_ERR!("Maker amount is too small to cover miner fee"); + return TX_PLAIN_ERR!("Maker amount is too small to cover miner fee + dust"); } let maker_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()); @@ -1390,7 +1643,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( coin.as_ref().conf.fork_id )); let sig_hash_single_fork_id = (SIGHASH_SINGLE | coin.as_ref().conf.fork_id) as u8; - let mut taker_signature_with_sighash = preimage.signature.clone(); + let mut taker_signature_with_sighash = preimage.signature.to_vec(); taker_signature_with_sighash.push(sig_hash_single_fork_id); drop_mutability!(taker_signature_with_sighash); @@ -1400,15 +1653,15 @@ pub async fn sign_and_broadcast_taker_payment_spend( drop_mutability!(maker_signature_with_sighash); let script_sig = Builder::default() - .push_opcode(Opcode::OP_0) - .push_data(&taker_signature_with_sighash) .push_data(&maker_signature_with_sighash) + .push_data(&taker_signature_with_sighash) .push_data(secret) .push_opcode(Opcode::OP_0) .push_data(&redeem_script) .into_bytes(); let mut final_tx: UtxoTx = signer.into(); - final_tx.inputs[0].script_sig = script_sig; + let final_tx_input = try_tx_s!(final_tx.inputs.first_mut().ok_or("Final tx doesn't have inputs")); + final_tx_input.script_sig = script_sig; drop_mutability!(final_tx); try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); @@ -1510,9 +1763,8 @@ pub fn send_maker_spends_taker_payment(coin: T, args let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() @@ -1533,16 +1785,16 @@ pub fn send_maker_spends_taker_payment(coin: T, args coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1620,9 +1872,7 @@ pub fn create_maker_payment_spend_preimage( let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(swap_unique_data); @@ -1641,16 +1891,16 @@ pub fn create_maker_payment_spend_preimage( .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1684,9 +1934,7 @@ pub fn create_taker_payment_refund_preimage( try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); @@ -1702,16 +1950,16 @@ pub fn create_taker_payment_refund_preimage( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WatcherPreimage) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1736,9 +1984,7 @@ pub fn send_taker_spends_maker_payment(coin: T, args let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); @@ -1761,16 +2007,16 @@ pub fn send_taker_spends_maker_payment(coin: T, args coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1803,9 +2049,7 @@ async fn refund_htlc_payment( try_tx_s!(deserialize(args.payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_s!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_s!(prev_transaction.first_output()).value; let other_public = try_tx_s!(Public::from_slice(args.other_pubkey)); let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); @@ -1816,6 +2060,10 @@ async fn refund_htlc_payment( SwapPaymentType::TakerOrMakerPayment => { payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public).into() }, + SwapPaymentType::TakerFunding => { + swap_proto_v2_scripts::taker_funding_script(time_lock, args.secret_hash, key_pair.public(), &other_public) + .into() + }, SwapPaymentType::TakerPaymentV2 => { swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public) .into() @@ -1825,16 +2073,16 @@ async fn refund_htlc_payment( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1895,7 +2143,7 @@ fn pubkey_from_script_sig(script: &Script) -> Result { match script.get_instruction(0) { Some(Ok(instruction)) => match instruction.opcode { Opcode::OP_PUSHBYTES_70 | Opcode::OP_PUSHBYTES_71 | Opcode::OP_PUSHBYTES_72 => match instruction.data { - Some(bytes) => try_s!(Signature::from_der(&bytes[..bytes.len() - 1])), + Some(bytes) => try_s!(SecpSignature::from_der(&bytes[..bytes.len() - 1])), None => return ERR!("No data at instruction 0 of script {:?}", script), }, _ => return ERR!("Unexpected opcode {:?}", instruction.opcode), @@ -1932,7 +2180,7 @@ fn pubkey_from_witness_script(witness_script: &[Bytes]) -> Result if signature.is_empty() { return ERR!("Empty signature data in witness script"); } - try_s!(Signature::from_der(&signature[..signature.len() - 1])); + try_s!(SecpSignature::from_der(&signature[..signature.len() - 1])); let pubkey = try_s!(PublicKey::from_slice(&witness_script[1])); @@ -2630,10 +2878,16 @@ pub fn wait_for_output_spend( let tx_hash_algo = coin.tx_hash_algo; let fut = async move { loop { + let script_pubkey = &try_tx_s!(tx + .outputs + .get(output_index) + .ok_or(ERRL!("No output with index {}", output_index))) + .script_pubkey; + match client .find_output_spend( tx.hash(), - &tx.outputs[output_index].script_pubkey, + script_pubkey, output_index, BlockHashOrHeight::Height(from_block as i64), ) @@ -4146,10 +4400,17 @@ async fn search_for_swap_output_spend( } let script = payment_script(time_lock, secret_hash, first_pub, second_pub); let expected_script_pubkey = Builder::build_p2sh(&dhash160(&script).into()).to_bytes(); - if tx.outputs[0].script_pubkey != expected_script_pubkey { + let script_pubkey = &tx + .outputs + .get(output_index) + .ok_or(ERRL!("No output with index {}", output_index))? + .script_pubkey; + + if *script_pubkey != expected_script_pubkey { return ERR!( - "Transaction {:?} output 0 script_pubkey doesn't match expected {:?}", + "Transaction {:?} output {} script_pubkey doesn't match expected {:?}", tx, + output_index, expected_script_pubkey ); } @@ -4158,7 +4419,7 @@ async fn search_for_swap_output_spend( coin.rpc_client .find_output_spend( tx.hash(), - &tx.outputs[output_index].script_pubkey, + script_pubkey, output_index, BlockHashOrHeight::Height(search_from_block as i64) ) @@ -4198,6 +4459,7 @@ struct SwapPaymentOutputsResult { enum SwapPaymentType { TakerOrMakerPayment, + TakerFunding, TakerPaymentV2, } @@ -4217,6 +4479,9 @@ where let other_public = try_s!(Public::from_slice(other_pub)); let redeem_script = match payment_type { SwapPaymentType::TakerOrMakerPayment => payment_script(time_lock, secret_hash, &my_public, &other_public), + SwapPaymentType::TakerFunding => { + swap_proto_v2_scripts::taker_funding_script(time_lock, secret_hash, &my_public, &other_public) + }, SwapPaymentType::TakerPaymentV2 => { swap_proto_v2_scripts::taker_payment_script(time_lock, secret_hash, &my_public, &other_public) }, @@ -4574,8 +4839,8 @@ where .collect() } -/// Common implementation of combined taker payment generation and broadcast for UTXO coins. -pub async fn send_combined_taker_payment(coin: T, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult +/// Common implementation of taker funding generation and broadcast for UTXO coins. +pub async fn send_taker_funding(coin: T, args: SendTakerFundingArgs<'_>) -> Result where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { @@ -4589,10 +4854,10 @@ where &coin, try_tx_s!(args.time_lock.try_into()), taker_htlc_key_pair.public_slice(), - args.other_pub, - args.secret_hash, + args.maker_pub, + args.taker_secret_hash, total_amount, - SwapPaymentType::TakerPaymentV2, + SwapPaymentType::TakerFunding, )); if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { let addr_string = try_tx_s!(payment_address.display_address()); @@ -4602,25 +4867,82 @@ where .compat() .await?; } - send_outputs_from_my_address(coin, outputs).compat().await + send_outputs_from_my_address_impl(coin, outputs).await } -/// Common implementation of combined taker payment validation for UTXO coins. -pub async fn validate_combined_taker_payment( - coin: &T, - args: ValidateTakerPaymentArgs<'_>, -) -> ValidateTakerPaymentResult +/// Common implementation of taker funding reclaim for UTXO coins using time-locked path. +pub async fn refund_taker_funding_timelock(coin: T, args: RefundPaymentArgs<'_>) -> TransactionResult where - T: UtxoCommonOps + SwapOps, + T: UtxoCommonOps + GetUtxoListOps + SwapOps, +{ + refund_htlc_payment(coin, args, SwapPaymentType::TakerFunding).await +} + +/// Common implementation of taker funding reclaim for UTXO coins using immediate refund path with secret reveal. +pub async fn refund_taker_funding_secret( + coin: T, + args: RefundFundingSecretArgs<'_, T>, +) -> Result +where + T: UtxoCommonOps + GetUtxoListOps + SwapOps, { - let dex_fee_tx: UtxoTx = - deserialize(args.taker_tx).map_to_mm(|e| ValidateTakerPaymentError::TxDeserialization(e.to_string()))?; - if dex_fee_tx.outputs.len() < 2 { - return MmError::err(ValidateTakerPaymentError::TxLacksOfOutputs); + let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); + let payment_value = try_tx_s!(args.funding_tx.first_output()).value; + + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let script_data = Builder::default() + .push_data(args.taker_secret) + .push_opcode(Opcode::OP_0) + .push_opcode(Opcode::OP_0) + .into_script(); + let time_lock = try_tx_s!(args.time_lock.try_into()); + + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + time_lock, + args.taker_secret_hash, + key_pair.public(), + args.maker_pubkey, + ) + .into(); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); + if fee >= payment_value { + return TX_PLAIN_ERR!( + "HTLC spend fee {} is greater than transaction output {}", + fee, + payment_value + ); } + let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); + let output = TransactionOutput { + value: payment_value - fee, + script_pubkey, + }; + + let input = P2SHSpendingTxInput { + prev_transaction: args.funding_tx.clone(), + redeem_script, + outputs: vec![output], + script_data, + sequence: SEQUENCE_FINAL, + lock_time: time_lock, + keypair: &key_pair, + }; + let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); + + let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); + try_tx_s!(tx_fut.await, transaction); + + Ok(transaction) +} - let taker_pub = - Public::from_slice(args.other_pub).map_to_mm(|e| ValidateTakerPaymentError::InvalidPubkey(e.to_string()))?; +/// Common implementation of taker funding validation for UTXO coins. +pub async fn validate_taker_funding(coin: &T, args: ValidateTakerFundingArgs<'_, T>) -> ValidateTakerFundingResult +where + T: UtxoCommonOps + SwapOps, +{ let maker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let total_expected_amount = &args.dex_fee_amount + &args.premium_amount + &args.trading_amount; @@ -4629,12 +4951,12 @@ where let time_lock = args .time_lock .try_into() - .map_to_mm(|e: TryFromIntError| ValidateTakerPaymentError::LocktimeOverflow(e.to_string()))?; + .map_to_mm(|e: TryFromIntError| ValidateTakerFundingError::LocktimeOverflow(e.to_string()))?; - let redeem_script = swap_proto_v2_scripts::taker_payment_script( + let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, - args.secret_hash, - &taker_pub, + args.taker_secret_hash, + args.other_pub, maker_htlc_key_pair.public(), ); let expected_output = TransactionOutput { @@ -4642,23 +4964,25 @@ where script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&redeem_script))).into(), }; - if dex_fee_tx.outputs[0] != expected_output { - return MmError::err(ValidateTakerPaymentError::InvalidDestinationOrAmount(format!( + if args.funding_tx.outputs.get(0) != Some(&expected_output) { + return MmError::err(ValidateTakerFundingError::InvalidDestinationOrAmount(format!( "Expected {:?}, got {:?}", - expected_output, dex_fee_tx.outputs[0] + expected_output, + args.funding_tx.outputs.get(0) ))); } let tx_bytes_from_rpc = coin .as_ref() .rpc_client - .get_transaction_bytes(&dex_fee_tx.hash().reversed().into()) + .get_transaction_bytes(&args.funding_tx.hash().reversed().into()) .compat() .await?; - if tx_bytes_from_rpc.0 != args.taker_tx { - return MmError::err(ValidateTakerPaymentError::TxBytesMismatch { + let actual_tx_bytes = serialize(args.funding_tx).take(); + if tx_bytes_from_rpc.0 != actual_tx_bytes { + return MmError::err(ValidateTakerFundingError::TxBytesMismatch { from_rpc: tx_bytes_from_rpc, - actual: args.taker_tx.into(), + actual: actual_tx_bytes.into(), }); } Ok(()) diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index a9b5e3aa32..26bc6bdc88 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,18 +23,18 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, IguanaPrivKey, - MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, - TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, - ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawSenderAddress}; + GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, + IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SendTakerFundingArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, + TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, + TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; @@ -588,14 +588,56 @@ impl WatcherOps for UtxoStandardCoin { } } +impl ToBytes for Public { + fn to_bytes(&self) -> Vec { self.to_vec() } +} + #[async_trait] impl SwapOpsV2 for UtxoStandardCoin { - async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { - utxo_common::send_combined_taker_payment(self.clone(), args).await + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { + utxo_common::send_taker_funding(self.clone(), args).await + } + + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { + utxo_common::validate_taker_funding(self, args).await + } + + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::refund_taker_funding_timelock(self.clone(), args).await + } + + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result { + utxo_common::refund_taker_funding_secret(self.clone(), args).await + } + + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + swap_unique_data: &[u8], + ) -> GenPreimageResult { + let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); + utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await } - async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult { - utxo_common::validate_combined_taker_payment(self, args).await + async fn validate_taker_funding_spend_preimage( + &self, + gen_args: &GenTakerFundingSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult { + utxo_common::validate_taker_funding_spend_preimage(self, gen_args, preimage).await + } + + async fn sign_and_send_taker_funding_spend( + &self, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, + swap_unique_data: &[u8], + ) -> Result { + let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); + utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await } async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { @@ -604,31 +646,35 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenTakerPaymentSpendResult { + ) -> GenPreimageResult { let key_pair = self.derive_htlc_key_pair(swap_unique_data); utxo_common::gen_and_sign_taker_payment_spend_preimage(self, args, &key_pair).await } async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { utxo_common::validate_taker_payment_spend_preimage(self, gen_args, preimage).await } async fn sign_and_broadcast_taker_payment_spend( &self, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); utxo_common::sign_and_broadcast_taker_payment_spend(self, preimage, gen_args, secret, &htlc_keypair).await } + + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { + *self.derive_htlc_key_pair(swap_unique_data).public() + } } impl MarketCoinOps for UtxoStandardCoin { diff --git a/mm2src/mm2_bitcoin/chain/src/transaction.rs b/mm2src/mm2_bitcoin/chain/src/transaction.rs index ca75eaa1ad..0490447d29 100644 --- a/mm2src/mm2_bitcoin/chain/src/transaction.rs +++ b/mm2src/mm2_bitcoin/chain/src/transaction.rs @@ -14,6 +14,7 @@ use hash::{CipherText, EncCipherText, OutCipherText, ZkProof, ZkProofSapling, H2 use hex::FromHex; use ser::{deserialize, serialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; use ser::{CompactInteger, Deserializable, Error, Reader, Serializable, Stream}; +use std::fmt::Formatter; use std::io; use std::io::Read; @@ -257,6 +258,14 @@ impl Default for TxHashAlgo { fn default() -> Self { TxHashAlgo::DSHA256 } } +/// Represents the error returned when transaction has no outputs +#[derive(Debug)] +pub struct TxHasNoOutputs {} + +impl std::fmt::Display for TxHasNoOutputs { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("Tx has no outputs") } +} + impl Transaction { pub fn hash(&self) -> H256 { let serialized = &serialize(self); @@ -318,6 +327,12 @@ impl Transaction { } result } + + /// Returns reference to first output of the transaction or error if outputs are empty + #[inline] + pub fn first_output(&self) -> Result<&TransactionOutput, TxHasNoOutputs> { + self.outputs.first().ok_or(TxHasNoOutputs {}) + } } impl Serializable for TransactionInput { diff --git a/mm2src/mm2_main/src/database.rs b/mm2src/mm2_main/src/database.rs index 47b1241cb3..8ce87db0e1 100644 --- a/mm2src/mm2_main/src/database.rs +++ b/mm2src/mm2_main/src/database.rs @@ -101,6 +101,10 @@ fn migration_8() -> Vec<(&'static str, Vec)> { db_common::sqlite::execute_batch(stats_swaps::ADD_MAKER_TAKER_PUBKEYS) } +fn migration_9() -> Vec<(&'static str, Vec)> { + db_common::sqlite::execute_batch(my_swaps::TRADING_PROTO_UPGRADE_MIGRATION) +} + async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option)>> { match current_migration { 1 => Some(migration_1(ctx).await), @@ -111,6 +115,7 @@ async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option 6 => Some(migration_6()), 7 => Some(migration_7()), 8 => Some(migration_8()), + 9 => Some(migration_9()), _ => None, } } diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index ab5a08b84b..4b852db835 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -5,7 +5,7 @@ use crate::mm2::lp_swap::{MyRecentSwapsUuids, MySwapsFilter, SavedSwap, SavedSwa use common::log::debug; use common::PagingOptions; use db_common::sqlite::offset_by_uuid; -use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, ToSql}; +use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, ToSql}; use db_common::sqlite::sql_builder::SqlBuilder; use mm2_core::mm_ctx::MmArc; use std::convert::TryInto; @@ -27,6 +27,31 @@ macro_rules! CREATE_MY_SWAPS_TABLE { );" }; } + +/// Adds new fields required for trading protocol upgrade implementation (swap v2) +pub const TRADING_PROTO_UPGRADE_MIGRATION: &[&str] = &[ + "ALTER TABLE my_swaps ADD COLUMN is_finished BOOLEAN NOT NULL DEFAULT 0;", + "ALTER TABLE my_swaps ADD COLUMN events_json TEXT NOT NULL DEFAULT '[]';", + "ALTER TABLE my_swaps ADD COLUMN swap_type INTEGER NOT NULL DEFAULT 0;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN maker_volume TEXT;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN taker_volume TEXT;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN premium TEXT;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN dex_fee TEXT;", + "ALTER TABLE my_swaps ADD COLUMN secret BLOB;", + "ALTER TABLE my_swaps ADD COLUMN secret_hash BLOB;", + "ALTER TABLE my_swaps ADD COLUMN secret_hash_algo INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN p2p_privkey BLOB;", + "ALTER TABLE my_swaps ADD COLUMN lock_duration INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN maker_coin_confs INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN maker_coin_nota BOOLEAN;", + "ALTER TABLE my_swaps ADD COLUMN taker_coin_confs INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN taker_coin_nota BOOLEAN;", +]; + const INSERT_MY_SWAP: &str = "INSERT INTO my_swaps (my_coin, other_coin, uuid, started_at) VALUES (?1, ?2, ?3, ?4)"; pub fn insert_new_swap(ctx: &MmArc, my_coin: &str, other_coin: &str, uuid: &str, started_at: &str) -> SqlResult<()> { @@ -36,6 +61,51 @@ pub fn insert_new_swap(ctx: &MmArc, my_coin: &str, other_coin: &str, uuid: &str, conn.execute(INSERT_MY_SWAP, params).map(|_| ()) } +const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( + my_coin, + other_coin, + uuid, + started_at, + swap_type, + maker_volume, + taker_volume, + premium, + dex_fee, + secret, + secret_hash, + secret_hash_algo, + p2p_privkey, + lock_duration, + maker_coin_confs, + maker_coin_nota, + taker_coin_confs, + taker_coin_nota +) VALUES ( + :my_coin, + :other_coin, + :uuid, + :started_at, + :swap_type, + :maker_volume, + :taker_volume, + :premium, + :dex_fee, + :secret, + :secret_hash, + :secret_hash_algo, + :p2p_privkey, + :lock_duration, + :maker_coin_confs, + :maker_coin_nota, + :taker_coin_confs, + :taker_coin_nota +);"#; + +pub fn insert_new_swap_v2(ctx: &MmArc, params: &[(&str, &dyn ToSql)]) -> SqlResult<()> { + let conn = ctx.sqlite_connection(); + conn.execute(INSERT_MY_SWAP_V2, params).map(|_| ()) +} + /// Returns SQL statements to initially fill my_swaps table using existing DB with JSON files pub async fn fill_my_swaps_from_json_statements(ctx: &MmArc) -> Vec<(&'static str, Vec)> { let swaps = SavedSwap::load_all_my_swaps_from_db(ctx).await.unwrap_or_default(); @@ -154,3 +224,108 @@ pub fn select_uuids_by_my_swaps_filter( skipped, }) } + +/// Queries swap type by uuid +pub fn get_swap_type(conn: &Connection, uuid: &str) -> SqlResult { + const SELECT_SWAP_TYPE_BY_UUID: &str = "SELECT swap_type FROM my_swaps WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(SELECT_SWAP_TYPE_BY_UUID)?; + let swap_type = stmt.query_row(&[(":uuid", uuid)], |row| row.get(0))?; + Ok(swap_type) +} + +/// Queries swap events by uuid +pub fn get_swap_events(conn: &Connection, uuid: &str) -> SqlResult { + const SELECT_SWAP_EVENTS_BY_UUID: &str = "SELECT events_json FROM my_swaps WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(SELECT_SWAP_EVENTS_BY_UUID)?; + let swap_type = stmt.query_row(&[(":uuid", uuid)], |row| row.get(0))?; + Ok(swap_type) +} + +/// Updates swap events by uuid +pub fn update_swap_events(conn: &Connection, uuid: &str, events_json: &str) -> SqlResult<()> { + const UPDATE_SWAP_EVENTS_BY_UUID: &str = "UPDATE my_swaps SET events_json = :events_json WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(UPDATE_SWAP_EVENTS_BY_UUID)?; + stmt.execute(&[(":uuid", uuid), (":events_json", events_json)]) + .map(|_| ()) +} + +pub fn set_swap_is_finished(conn: &Connection, uuid: &str) -> SqlResult<()> { + const UPDATE_SWAP_IS_FINISHED_BY_UUID: &str = "UPDATE my_swaps SET is_finished = 1 WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(UPDATE_SWAP_IS_FINISHED_BY_UUID)?; + stmt.execute(&[(":uuid", uuid)]).map(|_| ()) +} + +const SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID: &str = r#"SELECT + my_coin, + other_coin, + uuid, + started_at, + is_finished, + events_json, + maker_volume, + taker_volume, + premium, + dex_fee, + secret_hash, + secret_hash_algo, + lock_duration, + maker_coin_confs, + maker_coin_nota, + taker_coin_confs, + taker_coin_nota +FROM my_swaps +WHERE uuid = :uuid; +"#; + +/// Represents data of the swap used for RPC, omits fields that should be kept in secret +#[derive(Debug, Serialize)] +pub struct MySwapForRpc { + my_coin: String, + other_coin: String, + uuid: String, + started_at: i64, + is_finished: bool, + events_json: String, + maker_volume: String, + taker_volume: String, + premium: String, + dex_fee: String, + secret_hash: Vec, + secret_hash_algo: i64, + lock_duration: i64, + maker_coin_confs: i64, + maker_coin_nota: bool, + taker_coin_confs: i64, + taker_coin_nota: bool, +} + +impl MySwapForRpc { + fn from_row(row: &Row) -> SqlResult { + Ok(Self { + my_coin: row.get(0)?, + other_coin: row.get(1)?, + uuid: row.get(2)?, + started_at: row.get(3)?, + is_finished: row.get(4)?, + events_json: row.get(5)?, + maker_volume: row.get(6)?, + taker_volume: row.get(7)?, + premium: row.get(8)?, + dex_fee: row.get(9)?, + secret_hash: row.get(10)?, + secret_hash_algo: row.get(11)?, + lock_duration: row.get(12)?, + maker_coin_confs: row.get(13)?, + maker_coin_nota: row.get(14)?, + taker_coin_confs: row.get(15)?, + taker_coin_nota: row.get(16)?, + }) + } +} + +/// Queries `MySwapForRpc` by uuid +pub fn get_swap_data_for_rpc(conn: &Connection, uuid: &str) -> SqlResult { + let mut stmt = conn.prepare(SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID)?; + let swap_data = stmt.query_row(&[(":uuid", uuid)], MySwapForRpc::from_row)?; + Ok(swap_data) +} diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index d40ac478f2..2456a2f38b 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -71,15 +71,19 @@ use uuid::Uuid; use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest, P2PRequestError}; +#[cfg(not(target_arch = "wasm32"))] +use crate::mm2::lp_swap::detect_secret_hash_algo; +#[cfg(not(target_arch = "wasm32"))] use crate::mm2::lp_swap::maker_swap_v2::{self, DummyMakerSwapStorage, MakerSwapStateMachine}; +#[cfg(not(target_arch = "wasm32"))] use crate::mm2::lp_swap::taker_swap_v2::{self, DummyTakerSwapStorage, TakerSwapStateMachine}; use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, - check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, get_max_maker_vol, - insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, + check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, generate_secret, + get_max_maker_vol, insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast, p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, swap_v2_topic, AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, - CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SecretHashAlgo, - SwapConfirmationsSettings, TakerSwap}; + CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, + TakerSwap}; #[cfg(any(test, feature = "run-docker-tests"))] use crate::mm2::lp_swap::taker_swap::FailAt; @@ -2947,11 +2951,8 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO ); let now = now_sec(); - if let Err(e) = insert_new_swap_to_db(ctx.clone(), maker_coin.ticker(), taker_coin.ticker(), uuid, now).await { - error!("Error {} on new swap insertion", e); - } - let secret = match MakerSwap::generate_secret() { + let secret = match generate_secret() { Ok(s) => s.into(), Err(e) => { error!("Error {} on secret generation", e); @@ -2960,35 +2961,44 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }; if ctx.use_trading_proto_v2() { - match (maker_coin, taker_coin) { - (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { - let mut maker_swap_state_machine = MakerSwapStateMachine { - ctx, - storage: DummyMakerSwapStorage::default(), - started_at: now_sec(), - maker_coin: m.clone(), - maker_volume: maker_amount, - secret, - taker_coin: t.clone(), - dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount), - taker_volume: taker_amount, - taker_premium: Default::default(), - conf_settings: my_conf_settings, - p2p_topic: swap_v2_topic(&uuid), - uuid, - p2p_keypair: maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - secret_hash_algo: SecretHashAlgo::DHASH160, - lock_duration: lock_time, - }; - #[allow(clippy::box_default)] - maker_swap_state_machine - .run(Box::new(maker_swap_v2::Initialize::default())) - .await - .error_log(); - }, - _ => todo!("implement fallback to the old protocol here"), + #[cfg(not(target_arch = "wasm32"))] + { + let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); + match (maker_coin, taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + let mut maker_swap_state_machine = MakerSwapStateMachine { + storage: DummyMakerSwapStorage::new(ctx.clone()), + ctx, + started_at: now_sec(), + maker_coin: m.clone(), + maker_volume: maker_amount, + secret, + taker_coin: t.clone(), + dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount), + taker_volume: taker_amount, + taker_premium: Default::default(), + conf_settings: my_conf_settings, + p2p_topic: swap_v2_topic(&uuid), + uuid, + p2p_keypair: maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + secret_hash_algo, + lock_duration: lock_time, + }; + #[allow(clippy::box_default)] + maker_swap_state_machine + .run(Box::new(maker_swap_v2::Initialize::default())) + .await + .error_log(); + }, + _ => todo!("implement fallback to the old protocol here"), + } } } else { + if let Err(e) = + insert_new_swap_to_db(ctx.clone(), maker_coin.ticker(), taker_coin.ticker(), uuid, now).await + { + error!("Error {} on new swap insertion", e); + } let maker_swap = MakerSwap::new( ctx.clone(), alice, @@ -3090,41 +3100,56 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat ); let now = now_sec(); - if let Err(e) = insert_new_swap_to_db(ctx.clone(), taker_coin.ticker(), maker_coin.ticker(), uuid, now).await { - error!("Error {} on new swap insertion", e); - } - if ctx.use_trading_proto_v2() { - match (maker_coin, taker_coin) { - (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { - let mut taker_swap_state_machine = TakerSwapStateMachine { - ctx, - storage: DummyTakerSwapStorage::default(), - started_at: now_sec(), - lock_duration: locktime, - maker_coin: m.clone(), - maker_volume: maker_amount, - taker_coin: t.clone(), - dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), - taker_volume: taker_amount, - taker_premium: Default::default(), - conf_settings: my_conf_settings, - p2p_topic: swap_v2_topic(&uuid), - uuid, - p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - }; - #[allow(clippy::box_default)] - taker_swap_state_machine - .run(Box::new(taker_swap_v2::Initialize::default())) - .await - .error_log(); - }, - _ => todo!("implement fallback to the old protocol here"), + #[cfg(not(target_arch = "wasm32"))] + { + let taker_secret = match generate_secret() { + Ok(s) => s.into(), + Err(e) => { + error!("Error {} on secret generation", e); + return; + }, + }; + let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); + match (maker_coin, taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + let mut taker_swap_state_machine = TakerSwapStateMachine { + storage: DummyTakerSwapStorage::new(ctx.clone()), + ctx, + started_at: now, + lock_duration: locktime, + maker_coin: m.clone(), + maker_volume: maker_amount, + taker_coin: t.clone(), + dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), + taker_volume: taker_amount, + taker_premium: Default::default(), + secret_hash_algo, + conf_settings: my_conf_settings, + p2p_topic: swap_v2_topic(&uuid), + uuid, + p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + taker_secret, + }; + #[allow(clippy::box_default)] + taker_swap_state_machine + .run(Box::new(taker_swap_v2::Initialize::default())) + .await + .error_log(); + }, + _ => todo!("implement fallback to the old protocol here"), + } } } else { #[cfg(any(test, feature = "run-docker-tests"))] let fail_at = std::env::var("TAKER_FAIL_AT").map(FailAt::from).ok(); + if let Err(e) = + insert_new_swap_to_db(ctx.clone(), taker_coin.ticker(), maker_coin.ticker(), uuid, now).await + { + error!("Error {} on new swap insertion", e); + } + let taker_swap = TakerSwap::new( ctx.clone(), maker, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index c2620814a6..5cacc9458a 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -92,7 +92,9 @@ use std::sync::atomic::{AtomicU64, Ordering}; #[path = "lp_swap/check_balance.rs"] mod check_balance; #[path = "lp_swap/maker_swap.rs"] mod maker_swap; -#[path = "lp_swap/maker_swap_v2.rs"] pub mod maker_swap_v2; +#[cfg(not(target_arch = "wasm32"))] +#[path = "lp_swap/maker_swap_v2.rs"] +pub mod maker_swap_v2; #[path = "lp_swap/max_maker_vol_rpc.rs"] mod max_maker_vol_rpc; #[path = "lp_swap/my_swaps_storage.rs"] mod my_swaps_storage; #[path = "lp_swap/pubkey_banning.rs"] mod pubkey_banning; @@ -106,13 +108,17 @@ mod swap_v2_pb; #[path = "lp_swap/taker_restart.rs"] pub(crate) mod taker_restart; #[path = "lp_swap/taker_swap.rs"] pub(crate) mod taker_swap; -#[path = "lp_swap/taker_swap_v2.rs"] pub mod taker_swap_v2; +#[cfg(not(target_arch = "wasm32"))] +#[path = "lp_swap/taker_swap_v2.rs"] +pub mod taker_swap_v2; #[path = "lp_swap/trade_preimage.rs"] mod trade_preimage; #[cfg(target_arch = "wasm32")] #[path = "lp_swap/swap_wasm_db.rs"] mod swap_wasm_db; +#[cfg(not(target_arch = "wasm32"))] +use crate::mm2::database::my_swaps::{get_swap_data_for_rpc, get_swap_type}; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use crypto::CryptoCtx; use keys::{KeyPair, SECP_SIGN, SECP_VERIFY}; @@ -142,6 +148,11 @@ pub const SWAP_V2_PREFIX: TopicPrefix = "swapv2"; pub const SWAP_FINISHED_LOG: &str = "Swap finished: "; pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; +const LEGACY_SWAP_TYPE: u8 = 0; +const MAKER_SWAP_V2_TYPE: u8 = 1; +const TAKER_SWAP_V2_TYPE: u8 = 2; +const MAX_STARTED_AT_DIFF: u64 = 60; + const NEGOTIATE_SEND_INTERVAL: f64 = 30.; /// If a certain P2P message is not received, swap will be aborted after this time expires. @@ -190,8 +201,9 @@ pub struct SwapV2MsgStore { maker_negotiation: Option, taker_negotiation: Option, maker_negotiated: Option, - taker_payment: Option, + taker_funding: Option, maker_payment: Option, + taker_payment: Option, taker_payment_spend_preimage: Option, #[allow(dead_code)] accept_only_from: bits256, @@ -1000,6 +1012,35 @@ impl From for MySwapStatusResponse { } /// Returns the status of swap performed on `my` node +#[cfg(not(target_arch = "wasm32"))] +pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { + let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); + let uuid_str = uuid.to_string(); + let swap_type = try_s!(get_swap_type(&ctx.sqlite_connection(), &uuid_str)); + + match swap_type { + LEGACY_SWAP_TYPE => { + let status = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { + Ok(Some(status)) => status, + Ok(None) => return Err("swap data is not found".to_owned()), + Err(e) => return ERR!("{}", e), + }; + + let res_js = json!({ "result": MySwapStatusResponse::from(status) }); + let res = try_s!(json::to_vec(&res_js)); + Ok(try_s!(Response::builder().body(res))) + }, + MAKER_SWAP_V2_TYPE | TAKER_SWAP_V2_TYPE => { + let swap_data = try_s!(get_swap_data_for_rpc(&ctx.sqlite_connection(), &uuid_str)); + let res_js = json!({ "result": swap_data }); + let res = try_s!(json::to_vec(&res_js)); + Ok(try_s!(Response::builder().body(res))) + }, + unsupported_type => ERR!("Got unsupported swap type from DB: {}", unsupported_type), + } +} + +#[cfg(target_arch = "wasm32")] pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); let status = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { @@ -1406,11 +1447,12 @@ pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result> } /// Algorithm used to hash swap secret. +#[derive(Clone, Copy)] pub enum SecretHashAlgo { /// ripemd160(sha256(secret)) - DHASH160, + DHASH160 = 1, /// sha256(secret) - SHA256, + SHA256 = 2, } impl Default for SecretHashAlgo { @@ -1427,8 +1469,9 @@ impl SecretHashAlgo { } // Todo: Maybe add a secret_hash_algo method to the SwapOps trait instead +/// Selects secret hash algorithm depending on types of coins being swapped #[cfg(not(target_arch = "wasm32"))] -fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { +pub fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { match (maker_coin, taker_coin) { (MmCoinEnum::Tendermint(_) | MmCoinEnum::TendermintToken(_) | MmCoinEnum::LightningCoin(_), _) => { SecretHashAlgo::SHA256 @@ -1439,6 +1482,7 @@ fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> } } +/// Selects secret hash algorithm depending on types of coins being swapped #[cfg(target_arch = "wasm32")] fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { match (maker_coin, taker_coin) { @@ -1535,12 +1579,15 @@ pub fn process_swap_v2_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PProcessRes Some(swap_v2_pb::swap_message::Inner::MakerNegotiated(maker_negotiated)) => { msg_store.maker_negotiated = Some(maker_negotiated) }, - Some(swap_v2_pb::swap_message::Inner::TakerPaymentInfo(taker_payment)) => { - msg_store.taker_payment = Some(taker_payment) + Some(swap_v2_pb::swap_message::Inner::TakerFundingInfo(taker_funding)) => { + msg_store.taker_funding = Some(taker_funding) }, Some(swap_v2_pb::swap_message::Inner::MakerPaymentInfo(maker_payment)) => { msg_store.maker_payment = Some(maker_payment) }, + Some(swap_v2_pb::swap_message::Inner::TakerPaymentInfo(taker_payment)) => { + msg_store.taker_payment = Some(taker_payment) + }, Some(swap_v2_pb::swap_message::Inner::TakerPaymentSpendPreimage(preimage)) => { msg_store.taker_payment_spend_preimage = Some(preimage) }, @@ -1575,6 +1622,12 @@ async fn recv_swap_v2_msg( } } +pub fn generate_secret() -> Result<[u8; 32], rand::Error> { + let mut sec = [0u8; 32]; + common::os_rng(&mut sec)?; + Ok(sec) +} + #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; diff --git a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs index d677f903a0..761fabd0e3 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -34,15 +34,18 @@ pub struct TakerNegotiationData { #[prost(uint64, tag="1")] pub started_at: u64, #[prost(uint64, tag="2")] + pub funding_locktime: u64, + #[prost(uint64, tag="3")] pub payment_locktime: u64, - /// add bytes secret_hash = 3 if required #[prost(bytes="vec", tag="4")] - pub maker_coin_htlc_pub: ::prost::alloc::vec::Vec, + pub taker_secret_hash: ::prost::alloc::vec::Vec, #[prost(bytes="vec", tag="5")] + pub maker_coin_htlc_pub: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] pub taker_coin_htlc_pub: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", optional, tag="6")] - pub maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(bytes="vec", optional, tag="7")] + pub maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", optional, tag="8")] pub taker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] @@ -69,6 +72,13 @@ pub struct MakerNegotiated { pub reason: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct TakerFundingInfo { + #[prost(bytes="vec", tag="1")] + pub tx_bytes: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", optional, tag="2")] + pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TakerPaymentInfo { #[prost(bytes="vec", tag="1")] pub tx_bytes: ::prost::alloc::vec::Vec, @@ -81,17 +91,21 @@ pub struct MakerPaymentInfo { pub tx_bytes: ::prost::alloc::vec::Vec, #[prost(bytes="vec", optional, tag="2")] pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", tag="3")] + pub funding_preimage_sig: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="4")] + pub funding_preimage_tx: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct TakerPaymentSpendPreimage { #[prost(bytes="vec", tag="1")] pub signature: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", optional, tag="2")] - pub tx_preimage: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", tag="2")] + pub tx_preimage: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct SwapMessage { - #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6")] + #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6, 7")] pub inner: ::core::option::Option, } /// Nested message and enum types in `SwapMessage`. @@ -105,10 +119,12 @@ pub mod swap_message { #[prost(message, tag="3")] MakerNegotiated(super::MakerNegotiated), #[prost(message, tag="4")] - TakerPaymentInfo(super::TakerPaymentInfo), + TakerFundingInfo(super::TakerFundingInfo), #[prost(message, tag="5")] MakerPaymentInfo(super::MakerPaymentInfo), #[prost(message, tag="6")] + TakerPaymentInfo(super::TakerPaymentInfo), + #[prost(message, tag="7")] TakerPaymentSpendPreimage(super::TakerPaymentSpendPreimage), } } diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 7c51fe0c30..67f1b6285a 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -13,7 +13,7 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_msg_e use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::MakerOrderBuilder; -use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration}; +use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, @@ -239,12 +239,6 @@ impl MakerSwap { #[inline] fn r(&self) -> RwLockReadGuard { self.mutable.read().unwrap() } - pub fn generate_secret() -> Result<[u8; 32], rand::Error> { - let mut sec = [0u8; 32]; - common::os_rng(&mut sec)?; - Ok(sec) - } - #[inline] fn secret_hash(&self) -> Vec { self.r() @@ -613,7 +607,7 @@ impl MakerSwap { }; drop(send_abort_handle); let time_dif = self.r().data.started_at.abs_diff(taker_data.started_at()); - if time_dif > 60 { + if time_dif > MAX_STARTED_AT_DIFF { self.broadcast_negotiated_false(); return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::NegotiateFailed( ERRL!("The time difference between you and the taker cannot be longer than 60 seconds. Current difference: {}. Please make sure that your system clock is synced to the correct time before starting another swap!", time_dif).into(), diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index eb601df2e2..097e71d9c3 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -1,58 +1,93 @@ use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; +use crate::mm2::database::my_swaps::{get_swap_events, insert_new_swap_v2, set_swap_is_finished, update_swap_events}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_swap::swap_v2_pb::*; use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, - SwapConfirmationsSettings, SwapsContext, TransactionIdentifier}; + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, MAKER_SWAP_V2_TYPE, + MAX_STARTED_AT_DIFF}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, SendPaymentArgs, - SwapOpsV2, TxPreimageWithSig}; +use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MarketCoinOps, MmCoin, SendPaymentArgs, SwapOpsV2, ToBytes, Transaction, TxPreimageWithSig, + ValidateTakerFundingArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; +use db_common::sqlite::rusqlite::named_params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; use mm2_number::MmNumber; use mm2_state_machine::prelude::*; use mm2_state_machine::storable_state_machine::*; use primitives::hash::H256; -use std::collections::HashMap; +use rpc::v1::types::Bytes as BytesJson; use std::marker::PhantomData; use uuid::Uuid; // This is needed to have Debug on messages #[allow(unused_imports)] use prost::Message; +/// Negotiation data representation to be stored in DB. +#[derive(Debug, Deserialize, Serialize)] +pub struct StoredNegotiationData { + taker_payment_locktime: u64, + maker_coin_htlc_pub_from_taker: BytesJson, + taker_coin_htlc_pub_from_taker: BytesJson, + maker_coin_swap_contract: Option, + taker_coin_swap_contract: Option, + taker_secret_hash: BytesJson, +} + /// Represents events produced by maker swap states. -#[derive(Debug, PartialEq)] +#[derive(Debug, Deserialize, Serialize)] pub enum MakerSwapEvent { /// Swap has been successfully initialized. Initialized { maker_coin_start_block: u64, taker_coin_start_block: u64, }, - /// Started waiting for taker payment. - WaitingForTakerPayment { + /// Started waiting for taker funding tx. + WaitingForTakerFunding { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, }, - /// Received taker payment info. - TakerPaymentReceived { + /// Received taker funding info. + TakerFundingReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment: TransactionIdentifier, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, }, /// Sent maker payment. MakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, maker_payment: TransactionIdentifier, }, + /// Received funding spend preimage. + TakerFundingSpendReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + maker_payment: TransactionIdentifier, + taker_funding_preimage: BytesJson, + taker_funding_spend_signature: BytesJson, + }, /// Something went wrong, so maker payment refund is required. - MakerPaymentRefundRequired { maker_payment: TransactionIdentifier }, + MakerPaymentRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + maker_payment: TransactionIdentifier, + }, /// Taker payment has been confirmed on-chain. TakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, }, @@ -65,37 +100,54 @@ pub enum MakerSwapEvent { taker_payment_spend: TransactionIdentifier, }, /// Swap has been aborted before maker payment was sent. - Aborted { reason: String }, + Aborted { reason: AbortReason }, /// Swap completed successfully. Completed, } /// Represents errors that can be produced by [`MakerSwapStateMachine`] run. #[derive(Debug, Display)] -pub enum MakerSwapStateMachineError {} +pub enum MakerSwapStateMachineError { + StorageError(String), + SerdeError(String), +} /// Dummy storage for maker swap events (used temporary). -#[derive(Default)] pub struct DummyMakerSwapStorage { - events: HashMap>, + ctx: MmArc, +} + +impl DummyMakerSwapStorage { + pub fn new(ctx: MmArc) -> Self { DummyMakerSwapStorage { ctx } } } #[async_trait] impl StateMachineStorage for DummyMakerSwapStorage { type MachineId = Uuid; type Event = MakerSwapEvent; - type Error = MakerSwapStateMachineError; + type Error = MmError; async fn store_event(&mut self, id: Self::MachineId, event: Self::Event) -> Result<(), Self::Error> { - self.events.entry(id).or_insert_with(Vec::new).push(event); + let id_str = id.to_string(); + let events_json = get_swap_events(&self.ctx.sqlite_connection(), &id_str) + .map_to_mm(|e| MakerSwapStateMachineError::StorageError(e.to_string()))?; + let mut events: Vec = + serde_json::from_str(&events_json).map_to_mm(|e| MakerSwapStateMachineError::SerdeError(e.to_string()))?; + events.push(event); + drop_mutability!(events); + let serialized_events = + serde_json::to_string(&events).map_to_mm(|e| MakerSwapStateMachineError::SerdeError(e.to_string()))?; + update_swap_events(&self.ctx.sqlite_connection(), &id_str, &serialized_events) + .map_to_mm(|e| MakerSwapStateMachineError::StorageError(e.to_string()))?; Ok(()) } - async fn get_unfinished(&self) -> Result, Self::Error> { - Ok(self.events.keys().copied().collect()) - } + async fn get_unfinished(&self) -> Result, Self::Error> { todo!() } - async fn mark_finished(&mut self, _id: Self::MachineId) -> Result<(), Self::Error> { Ok(()) } + async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { + set_swap_is_finished(&self.ctx.sqlite_connection(), &id.to_string()) + .map_to_mm(|e| MakerSwapStateMachineError::StorageError(e.to_string())) + } } /// Represents the state machine for maker's side of the Trading Protocol Upgrade swap (v2). @@ -194,24 +246,52 @@ impl InitialState for Init } #[async_trait] -impl State - for Initialize -{ +impl State for Initialize { type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + { + let sql_params = named_params! { + ":my_coin": state_machine.maker_coin.ticker(), + ":other_coin": state_machine.taker_coin.ticker(), + ":uuid": state_machine.uuid.to_string(), + ":started_at": state_machine.started_at, + ":swap_type": MAKER_SWAP_V2_TYPE, + ":maker_volume": state_machine.maker_volume.to_fraction_string(), + ":taker_volume": state_machine.taker_volume.to_fraction_string(), + ":premium": state_machine.taker_premium.to_fraction_string(), + ":dex_fee": state_machine.dex_fee_amount.to_fraction_string(), + ":secret": state_machine.secret.take(), + ":secret_hash": state_machine.secret_hash(), + ":secret_hash_algo": state_machine.secret_hash_algo as u8, + ":p2p_privkey": state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), + ":lock_duration": state_machine.lock_duration, + ":maker_coin_confs": state_machine.conf_settings.maker_coin_confs, + ":maker_coin_nota": state_machine.conf_settings.maker_coin_nota, + ":taker_coin_confs": state_machine.conf_settings.taker_coin_confs, + ":taker_coin_nota": state_machine.conf_settings.taker_coin_nota + }; + insert_new_swap_v2(&state_machine.ctx, sql_params).unwrap(); + } + subscribe_to_topic(&state_machine.ctx, state_machine.p2p_topic.clone()); let swap_ctx = SwapsContext::from_ctx(&state_machine.ctx).expect("SwapsContext::from_ctx should not fail"); swap_ctx.init_msg_v2_store(state_machine.uuid, bits256::default()); let maker_coin_start_block = match state_machine.maker_coin.current_block().compat().await { Ok(b) => b, - Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + Err(e) => { + let reason = AbortReason::FailedToGetMakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, }; let taker_coin_start_block = match state_machine.taker_coin.current_block().compat().await { Ok(b) => b, - Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + Err(e) => { + let reason = AbortReason::FailedToGetTakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, }; if let Err(e) = check_balance_for_maker_swap( @@ -225,7 +305,8 @@ impl StorableState for Ini } #[async_trait] -impl State for Initialized { +impl State for Initialized { type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -296,8 +377,8 @@ impl State for Initialized d, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive TakerNegotiation: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveTakerNegotiation(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; drop(abort_handle); @@ -306,49 +387,106 @@ impl State for Initialized data, Some(taker_negotiation::Action::Abort(abort)) => { - let next_state = Aborted::new(abort.reason); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::TakerAbortedNegotiation(abort.reason); + return Self::change_state(Aborted::new(reason), state_machine).await; }, None => { - let next_state = Aborted::new("received invalid negotiation message from taker".into()); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::ReceivedInvalidTakerNegotiation; + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; - let next_state = WaitingForTakerPayment { - maker_coin: Default::default(), - taker_coin: Default::default(), + let started_at_diff = state_machine.started_at.abs_diff(taker_data.started_at); + if started_at_diff > MAX_STARTED_AT_DIFF { + let reason = AbortReason::TooLargeStartedAtDiff(started_at_diff); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let expected_taker_funding_locktime = taker_data.started_at + 3 * state_machine.lock_duration; + if taker_data.funding_locktime != expected_taker_funding_locktime { + let reason = AbortReason::TakerProvidedInvalidFundingLocktime(taker_data.funding_locktime); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let expected_taker_payment_locktime = taker_data.started_at + state_machine.lock_duration; + if taker_data.payment_locktime != expected_taker_payment_locktime { + let reason = AbortReason::TakerProvidedInvalidPaymentLocktime(taker_data.payment_locktime); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let taker_coin_htlc_pub_from_taker = + match state_machine.taker_coin.parse_pubkey(&taker_data.taker_coin_htlc_pub) { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + let maker_coin_htlc_pub_from_taker = + match state_machine.maker_coin.parse_pubkey(&taker_data.maker_coin_htlc_pub) { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + let next_state = WaitingForTakerFunding { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment_locktime: taker_data.payment_locktime, - maker_coin_htlc_pub_from_taker: taker_data.maker_coin_htlc_pub, - taker_coin_htlc_pub_from_taker: taker_data.taker_coin_htlc_pub, - maker_coin_swap_contract: taker_data.maker_coin_swap_contract, - taker_coin_swap_contract: taker_data.taker_coin_swap_contract, + negotiation_data: NegotiationData { + taker_payment_locktime: expected_taker_payment_locktime, + taker_funding_locktime: expected_taker_funding_locktime, + maker_coin_htlc_pub_from_taker, + taker_coin_htlc_pub_from_taker, + maker_coin_swap_contract: taker_data.maker_coin_swap_contract, + taker_coin_swap_contract: taker_data.taker_coin_swap_contract, + taker_secret_hash: taker_data.taker_secret_hash, + }, }; Self::change_state(next_state, state_machine).await } } -struct WaitingForTakerPayment { - maker_coin: PhantomData, - taker_coin: PhantomData, - maker_coin_start_block: u64, - taker_coin_start_block: u64, +struct NegotiationData { taker_payment_locktime: u64, - maker_coin_htlc_pub_from_taker: Vec, - taker_coin_htlc_pub_from_taker: Vec, + taker_funding_locktime: u64, + maker_coin_htlc_pub_from_taker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, + taker_secret_hash: Vec, +} + +impl NegotiationData { + fn to_stored_data(&self) -> StoredNegotiationData { + StoredNegotiationData { + taker_payment_locktime: self.taker_payment_locktime, + maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker.to_bytes().into(), + taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker.to_bytes().into(), + maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), + taker_coin_swap_contract: self.taker_coin_swap_contract.clone().map(|b| b.into()), + taker_secret_hash: self.taker_secret_hash.clone().into(), + } + } +} + +struct WaitingForTakerFunding { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, } -impl TransitionFrom> - for WaitingForTakerPayment +impl TransitionFrom> + for WaitingForTakerFunding { } #[async_trait] -impl State for WaitingForTakerPayment { +impl State + for WaitingForTakerFunding +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -370,83 +508,117 @@ impl State for WaitingForTaker let recv_fut = recv_swap_v2_msg( state_machine.ctx.clone(), - |store| store.taker_payment.take(), + |store| store.taker_funding.take(), &state_machine.uuid, NEGOTIATION_TIMEOUT_SEC, ); - let taker_payment = match recv_fut.await { + let taker_funding_info = match recv_fut.await { Ok(p) => p, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive TakerPaymentInfo: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveTakerFundingInfo(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; drop(abort_handle); - debug!("Received taker payment info message {:?}", taker_payment); - let next_state = TakerPaymentReceived { - maker_coin: Default::default(), - taker_coin: Default::default(), + debug!("Received taker funding info message {:?}", taker_funding_info); + let taker_funding = match state_machine.taker_coin.parse_tx(&taker_funding_info.tx_bytes) { + Ok(tx) => tx, + Err(e) => { + let reason = AbortReason::FailedToParseTakerFunding(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + let next_state = TakerFundingReceived { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment_locktime: self.taker_payment_locktime, - maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker, - taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker, - maker_coin_swap_contract: self.maker_coin_swap_contract, - taker_coin_swap_contract: self.taker_coin_swap_contract, - taker_payment: TransactionIdentifier { - tx_hex: taker_payment.tx_bytes.into(), - tx_hash: Default::default(), - }, + negotiation_data: self.negotiation_data, + taker_funding, }; Self::change_state(next_state, state_machine).await } } -impl StorableState - for WaitingForTakerPayment +impl StorableState + for WaitingForTakerFunding { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { - MakerSwapEvent::WaitingForTakerPayment { + MakerSwapEvent::WaitingForTakerFunding { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct TakerPaymentReceived { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerFundingReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment_locktime: u64, - maker_coin_htlc_pub_from_taker: Vec, - taker_coin_htlc_pub_from_taker: Vec, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, - taker_payment: TransactionIdentifier, + negotiation_data: NegotiationData, + taker_funding: TakerCoin::Tx, } -impl TransitionFrom> - for TakerPaymentReceived +impl TransitionFrom> + for TakerFundingReceived { } #[async_trait] -impl State for TakerPaymentReceived { +impl State + for TakerFundingReceived +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let unique_data = state_machine.unique_data(); + + let validation_args = ValidateTakerFundingArgs { + funding_tx: &self.taker_funding, + time_lock: self.negotiation_data.taker_funding_locktime, + taker_secret_hash: &self.negotiation_data.taker_secret_hash, + other_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker, + dex_fee_amount: state_machine.dex_fee_amount.to_decimal(), + premium_amount: state_machine.taker_premium.to_decimal(), + trading_amount: state_machine.taker_volume.to_decimal(), + swap_unique_data: &unique_data, + }; + + if let Err(e) = state_machine.taker_coin.validate_taker_funding(validation_args).await { + let reason = AbortReason::TakerFundingValidationFailed(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let args = GenTakerFundingSpendArgs { + funding_tx: &self.taker_funding, + maker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), + taker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker, + funding_time_lock: self.negotiation_data.taker_funding_locktime, + taker_secret_hash: &self.negotiation_data.taker_secret_hash, + taker_payment_time_lock: self.negotiation_data.taker_payment_locktime, + maker_secret_hash: &state_machine.secret_hash(), + }; + let funding_spend_preimage = match state_machine + .taker_coin + .gen_taker_funding_spend_preimage(&args, &unique_data) + .await + { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToGenerateFundingSpend(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + let args = SendPaymentArgs { time_lock_duration: state_machine.lock_duration, time_lock: state_machine.maker_payment_locktime(), - other_pubkey: &self.maker_coin_htlc_pub_from_taker, + other_pubkey: &self.negotiation_data.maker_coin_htlc_pub_from_taker.to_bytes(), secret_hash: &state_machine.secret_hash(), amount: state_machine.maker_volume.to_decimal(), swap_contract_address: &None, - swap_unique_data: &state_machine.unique_data(), + swap_unique_data: &unique_data, payment_instructions: &None, watcher_reward: None, wait_for_confirmation_until: 0, @@ -454,8 +626,8 @@ impl State for TakerPaymentRec let maker_payment = match state_machine.maker_coin.send_maker_payment(args).compat().await { Ok(tx) => tx, Err(e) => { - let next_state = Aborted::new(format!("Failed to send maker payment {:?}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::FailedToSendMakerPayment(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; info!( @@ -464,17 +636,11 @@ impl State for TakerPaymentRec maker_payment.tx_hash(), state_machine.uuid ); - let next_state = MakerPaymentSent { - maker_coin: Default::default(), - taker_coin: Default::default(), + let next_state = MakerPaymentSentFundingSpendGenerated { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment_locktime: self.taker_payment_locktime, - maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker, - taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker, - maker_coin_swap_contract: self.maker_coin_swap_contract, - taker_coin_swap_contract: self.taker_coin_swap_contract, - taker_payment: self.taker_payment, + negotiation_data: self.negotiation_data, + funding_spend_preimage, maker_payment: TransactionIdentifier { tx_hex: maker_payment.tx_hex().into(), tx_hash: maker_payment.tx_hash(), @@ -485,62 +651,100 @@ impl State for TakerPaymentRec } } -impl StorableState - for TakerPaymentReceived +impl StorableState + for TakerFundingReceived { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { - MakerSwapEvent::TakerPaymentReceived { + MakerSwapEvent::TakerFundingReceived { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment: self.taker_payment.clone(), + negotiation_data: self.negotiation_data.to_stored_data(), + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, } } } -struct MakerPaymentSent { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct MakerPaymentSentFundingSpendGenerated { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment_locktime: u64, - maker_coin_htlc_pub_from_taker: Vec, - taker_coin_htlc_pub_from_taker: Vec, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, - taker_payment: TransactionIdentifier, + negotiation_data: NegotiationData, + funding_spend_preimage: TxPreimageWithSig, maker_payment: TransactionIdentifier, } -impl TransitionFrom> - for MakerPaymentSent +impl TransitionFrom> + for MakerPaymentSentFundingSpendGenerated { } #[async_trait] -impl State for MakerPaymentSent { +impl State + for MakerPaymentSentFundingSpendGenerated +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { let maker_payment_info = MakerPaymentInfo { tx_bytes: self.maker_payment.tx_hex.0.clone(), next_step_instructions: None, + funding_preimage_sig: self.funding_spend_preimage.signature.to_bytes(), + funding_preimage_tx: self.funding_spend_preimage.preimage.to_bytes(), }; let swap_msg = SwapMessage { inner: Some(swap_message::Inner::MakerPaymentInfo(maker_payment_info)), }; debug!("Sending maker payment info message {:?}", swap_msg); - let _abort_handle = broadcast_swap_v2_msg_every( + let abort_handle = broadcast_swap_v2_msg_every( state_machine.ctx.clone(), state_machine.p2p_topic.clone(), swap_msg, 600., state_machine.p2p_keypair, ); + + let recv_fut = recv_swap_v2_msg( + state_machine.ctx.clone(), + |store| store.taker_payment.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + let taker_payment_info = match recv_fut.await { + Ok(p) => p, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::DidNotGetTakerPayment(e), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + + let taker_payment = match state_machine.taker_coin.parse_tx(&taker_payment_info.tx_bytes) { + Ok(tx) => tx, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::FailedToParseTakerPayment(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + let input = ConfirmPaymentInput { - payment_tx: self.taker_payment.tx_hex.0.clone(), + payment_tx: taker_payment.tx_hex(), confirmations: state_machine.conf_settings.taker_coin_confs, requires_nota: state_machine.conf_settings.taker_coin_nota, wait_until: state_machine.taker_payment_conf_timeout(), @@ -548,8 +752,9 @@ impl State for MakerPaymentSen }; if let Err(e) = state_machine.taker_coin.wait_for_confirmations(input).compat().await { let next_state = MakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::TakerPaymentNotConfirmedInTime(e), }; @@ -557,29 +762,26 @@ impl State for MakerPaymentSen } let next_state = TakerPaymentConfirmed { - maker_coin: Default::default(), - taker_coin: Default::default(), maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment, - taker_payment: self.taker_payment, - taker_payment_locktime: self.taker_payment_locktime, - maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker, - taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker, - maker_coin_swap_contract: self.maker_coin_swap_contract, - taker_coin_swap_contract: self.taker_coin_swap_contract, + taker_payment, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } } -impl StorableState for MakerPaymentSent { +impl StorableState + for MakerPaymentSentFundingSpendGenerated +{ type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { MakerSwapEvent::MakerPaymentSent { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), maker_payment: self.maker_payment.clone(), } } @@ -587,31 +789,39 @@ impl StorableState for Mak #[derive(Debug)] enum MakerPaymentRefundReason { + DidNotGetTakerPayment(String), + FailedToParseTakerPayment(String), TakerPaymentNotConfirmedInTime(String), DidNotGetTakerPaymentSpendPreimage(String), TakerPaymentSpendPreimageIsNotValid(String), + FailedToParseTakerPreimage(String), + FailedToParseTakerSignature(String), TakerPaymentSpendBroadcastFailed(String), } -struct MakerPaymentRefundRequired { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct MakerPaymentRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, maker_payment: TransactionIdentifier, reason: MakerPaymentRefundReason, } -impl TransitionFrom> +impl + TransitionFrom> for MakerPaymentRefundRequired { } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentRefundRequired { } #[async_trait] -impl State - for MakerPaymentRefundRequired +impl< + MakerCoin: CoinAssocTypes + Send + Sync + 'static, + TakerCoin: MarketCoinOps + CoinAssocTypes + Send + Sync + 'static, + > State for MakerPaymentRefundRequired { type StateMachine = MakerSwapStateMachine; @@ -624,40 +834,40 @@ impl StorableState +impl StorableState for MakerPaymentRefundRequired { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { MakerSwapEvent::MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), maker_payment: self.maker_payment.clone(), } } } #[allow(dead_code)] -struct TakerPaymentConfirmed { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, - taker_payment: TransactionIdentifier, - taker_payment_locktime: u64, - maker_coin_htlc_pub_from_taker: Vec, - taker_coin_htlc_pub_from_taker: Vec, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + taker_payment: TakerCoin::Tx, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl + TransitionFrom> for TakerPaymentConfirmed { } #[async_trait] -impl State for TakerPaymentConfirmed { +impl State + for TakerPaymentConfirmed +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -667,45 +877,72 @@ impl State for TakerPaymentCon &state_machine.uuid, state_machine.taker_payment_conf_timeout(), ); - let preimage = match recv_fut.await { + let preimage_data = match recv_fut.await { Ok(preimage) => preimage, Err(e) => { let next_state = MakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::DidNotGetTakerPaymentSpendPreimage(e), }; return Self::change_state(next_state, state_machine).await; }, }; - debug!("Received taker payment spend preimage message {:?}", preimage); + debug!("Received taker payment spend preimage message {:?}", preimage_data); let unique_data = state_machine.unique_data(); let gen_args = GenTakerPaymentSpendArgs { - taker_tx: &self.taker_payment.tx_hex.0, - time_lock: self.taker_payment_locktime, + taker_tx: &self.taker_payment, + time_lock: self.negotiation_data.taker_payment_locktime, secret_hash: &state_machine.secret_hash(), - maker_pub: &state_machine.maker_coin.derive_htlc_pubkey(&unique_data), - taker_pub: &self.taker_coin_htlc_pub_from_taker, + maker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), + taker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker, dex_fee_amount: state_machine.dex_fee_amount.to_decimal(), premium_amount: Default::default(), trading_amount: state_machine.taker_volume.to_decimal(), dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, }; - let tx_preimage = TxPreimageWithSig { - preimage: preimage.tx_preimage.unwrap_or_default(), - signature: preimage.signature, + + let preimage = match state_machine.taker_coin.parse_preimage(&preimage_data.tx_preimage) { + Ok(p) => p, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::FailedToParseTakerPreimage(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + let signature = match state_machine.taker_coin.parse_signature(&preimage_data.signature) { + Ok(s) => s, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::FailedToParseTakerSignature(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, }; + + let tx_preimage = TxPreimageWithSig { preimage, signature }; if let Err(e) = state_machine .taker_coin .validate_taker_payment_spend_preimage(&gen_args, &tx_preimage) .await { let next_state = MakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::TakerPaymentSpendPreimageIsNotValid(e.to_string()), }; @@ -725,8 +962,9 @@ impl State for TakerPaymentCon Ok(tx) => tx, Err(e) => { let next_state = MakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::TakerPaymentSpendBroadcastFailed(format!("{:?}", e)), }; @@ -741,7 +979,6 @@ impl State for TakerPaymentCon ); let next_state = TakerPaymentSpent { maker_coin: Default::default(), - taker_coin: Default::default(), maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment, @@ -755,7 +992,7 @@ impl State for TakerPaymentCon } } -impl StorableState +impl StorableState for TakerPaymentConfirmed { type StateMachine = MakerSwapStateMachine; @@ -764,29 +1001,32 @@ impl StorableState MakerSwapEvent::TakerPaymentConfirmed { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), maker_payment: self.maker_payment.clone(), - taker_payment: self.taker_payment.clone(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, } } } -struct TakerPaymentSpent { +struct TakerPaymentSpent { maker_coin: PhantomData, - taker_coin: PhantomData, maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, - taker_payment: TransactionIdentifier, + taker_payment: TakerCoin::Tx, taker_payment_spend: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSpent { } #[async_trait] -impl State +impl State for TakerPaymentSpent { type StateMachine = MakerSwapStateMachine; @@ -796,7 +1036,9 @@ impl State } } -impl StorableState for TakerPaymentSpent { +impl StorableState + for TakerPaymentSpent +{ type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -804,20 +1046,43 @@ impl StorableState for Tak maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment.clone(), - taker_payment: self.taker_payment.clone(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, taker_payment_spend: self.taker_payment_spend.clone(), } } } +/// Represents possible reasons of maker swap being aborted +#[derive(Clone, Debug, Deserialize, Display, Serialize)] +pub enum AbortReason { + FailedToGetMakerCoinBlock(String), + FailedToGetTakerCoinBlock(String), + BalanceCheckFailure(String), + DidNotReceiveTakerNegotiation(String), + TakerAbortedNegotiation(String), + ReceivedInvalidTakerNegotiation, + DidNotReceiveTakerFundingInfo(String), + FailedToParseTakerFunding(String), + TakerFundingValidationFailed(String), + FailedToGenerateFundingSpend(String), + FailedToSendMakerPayment(String), + TooLargeStartedAtDiff(u64), + TakerProvidedInvalidFundingLocktime(u64), + TakerProvidedInvalidPaymentLocktime(u64), + FailedToParsePubkey(String), +} + struct Aborted { maker_coin: PhantomData, taker_coin: PhantomData, - reason: String, + reason: AbortReason, } impl Aborted { - fn new(reason: String) -> Aborted { + fn new(reason: AbortReason) -> Aborted { Aborted { maker_coin: Default::default(), taker_coin: Default::default(), @@ -850,11 +1115,11 @@ impl StorableState for Abo impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> +impl TransitionFrom> for Aborted { } -impl TransitionFrom> +impl TransitionFrom> for Aborted { } @@ -893,4 +1158,7 @@ impl LastSta } } -impl TransitionFrom> for Completed {} +impl TransitionFrom> + for Completed +{ +} diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2.proto b/mm2src/mm2_main/src/lp_swap/swap_v2.proto index 5ef75bc241..9bbaa87e5d 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2.proto +++ b/mm2src/mm2_main/src/lp_swap/swap_v2.proto @@ -24,12 +24,13 @@ message Abort { message TakerNegotiationData { uint64 started_at = 1; - uint64 payment_locktime = 2; - // add bytes secret_hash = 3 if required - bytes maker_coin_htlc_pub = 4; - bytes taker_coin_htlc_pub = 5; - optional bytes maker_coin_swap_contract = 6; - optional bytes taker_coin_swap_contract = 7; + uint64 funding_locktime = 2; + uint64 payment_locktime = 3; + bytes taker_secret_hash = 4; + bytes maker_coin_htlc_pub = 5; + bytes taker_coin_htlc_pub = 6; + optional bytes maker_coin_swap_contract = 7; + optional bytes taker_coin_swap_contract = 8; } message TakerNegotiation { @@ -45,6 +46,11 @@ message MakerNegotiated { optional string reason = 2; } +message TakerFundingInfo { + bytes tx_bytes = 1; + optional bytes next_step_instructions = 2; +} + message TakerPaymentInfo { bytes tx_bytes = 1; optional bytes next_step_instructions = 2; @@ -53,11 +59,13 @@ message TakerPaymentInfo { message MakerPaymentInfo { bytes tx_bytes = 1; optional bytes next_step_instructions = 2; + bytes funding_preimage_sig = 3; + bytes funding_preimage_tx = 4; } message TakerPaymentSpendPreimage { bytes signature = 1; - optional bytes tx_preimage = 2; + bytes tx_preimage = 2; } message SwapMessage { @@ -65,8 +73,9 @@ message SwapMessage { MakerNegotiation maker_negotiation = 1; TakerNegotiation taker_negotiation = 2; MakerNegotiated maker_negotiated = 3; - TakerPaymentInfo taker_payment_info = 4; + TakerFundingInfo taker_funding_info = 4; MakerPaymentInfo maker_payment_info = 5; - TakerPaymentSpendPreimage taker_payment_spend_preimage = 6; + TakerPaymentInfo taker_payment_info = 6; + TakerPaymentSpendPreimage taker_payment_spend_preimage = 7; } } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 1afdcbb5f8..f16bdd129d 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -14,7 +14,7 @@ use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::TakerOrderBuilder; use crate::mm2::lp_swap::taker_restart::get_command_based_on_watcher_activity; use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, - wait_for_maker_payment_conf_duration, TakerSwapWatcherData}; + wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -1128,7 +1128,7 @@ impl TakerSwap { debug!("Received maker negotiation data {:?}", maker_data); let time_dif = self.r().data.started_at.abs_diff(maker_data.started_at()); - if time_dif > 60 { + if time_dif > MAX_STARTED_AT_DIFF { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( ERRL!("The time difference between you and the maker cannot be longer than 60 seconds. Current difference: {}. Please make sure that your system clock is synced to the correct time before starting another swap!", time_dif).into(), )])); diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index e4ae68b89b..0e825d5e9c 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -1,28 +1,45 @@ use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; +use crate::mm2::database::my_swaps::{get_swap_events, insert_new_swap_v2, set_swap_is_finished, update_swap_events}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_swap::swap_v2_pb::*; -use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, - SwapConfirmationsSettings, SwapsContext, TransactionIdentifier}; +use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, SecretHashAlgo, + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, MAX_STARTED_AT_DIFF, + TAKER_SWAP_V2_TYPE}; use async_trait::async_trait; -use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, SendCombinedTakerPaymentArgs, - SpendPaymentArgs, SwapOpsV2, WaitForHTLCTxSpendArgs}; +use bitcrypto::{dhash160, sha256}; +use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MmCoin, SendTakerFundingArgs, SpendPaymentArgs, SwapOps, SwapOpsV2, ToBytes, Transaction, + TxPreimageWithSig, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; +use db_common::sqlite::rusqlite::named_params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; -use mm2_number::{BigDecimal, MmNumber}; +use mm2_err_handle::prelude::*; +use mm2_number::MmNumber; use mm2_state_machine::prelude::*; use mm2_state_machine::storable_state_machine::*; +use primitives::hash::H256; use rpc::v1::types::Bytes as BytesJson; -use std::collections::HashMap; use std::marker::PhantomData; use uuid::Uuid; // This is needed to have Debug on messages #[allow(unused_imports)] use prost::Message; +/// Negotiation data representation to be stored in DB. +#[derive(Debug, Deserialize, Serialize)] +pub struct StoredNegotiationData { + maker_payment_locktime: u64, + maker_secret_hash: BytesJson, + maker_coin_htlc_pub_from_maker: BytesJson, + taker_coin_htlc_pub_from_maker: BytesJson, + maker_coin_swap_contract: Option, + taker_coin_swap_contract: Option, +} + /// Represents events produced by taker swap states. -#[derive(Debug, PartialEq)] +#[derive(Debug, Deserialize, Serialize)] pub enum TakerSwapEvent { /// Swap has been successfully initialized. Initialized { @@ -33,27 +50,50 @@ pub enum TakerSwapEvent { Negotiated { maker_coin_start_block: u64, taker_coin_start_block: u64, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, + }, + /// Sent taker funding tx. + TakerFundingSent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + }, + /// Taker funding tx refund is required. + TakerFundingRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + reason: TakerFundingRefundReason, + }, + /// Received maker payment + MakerPaymentReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + maker_payment: TransactionIdentifier, }, /// Sent taker payment. TakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, taker_payment: TransactionIdentifier, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Something went wrong, so taker payment refund is required. TakerPaymentRefundRequired { taker_payment: TransactionIdentifier, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, - /// Both payments are confirmed on-chain - BothPaymentsSentAndConfirmed { + /// Maker payment is confirmed on-chain + MakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Maker spent taker's payment and taker discovered the tx on-chain. TakerPaymentSpent { @@ -62,7 +102,7 @@ pub enum TakerSwapEvent { maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, taker_payment_spend: TransactionIdentifier, - secret: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Taker spent maker's payment. MakerPaymentSpent { @@ -74,37 +114,54 @@ pub enum TakerSwapEvent { maker_payment_spend: TransactionIdentifier, }, /// Swap has been aborted before taker payment was sent. - Aborted { reason: String }, + Aborted { reason: AbortReason }, /// Swap completed successfully. Completed, } /// Represents errors that can be produced by [`TakerSwapStateMachine`] run. #[derive(Debug, Display)] -pub enum TakerSwapStateMachineError {} +pub enum TakerSwapStateMachineError { + StorageError(String), + SerdeError(String), +} /// Dummy storage for taker swap events (used temporary). -#[derive(Default)] pub struct DummyTakerSwapStorage { - events: HashMap>, + ctx: MmArc, +} + +impl DummyTakerSwapStorage { + pub fn new(ctx: MmArc) -> Self { DummyTakerSwapStorage { ctx } } } #[async_trait] impl StateMachineStorage for DummyTakerSwapStorage { type MachineId = Uuid; type Event = TakerSwapEvent; - type Error = TakerSwapStateMachineError; + type Error = MmError; async fn store_event(&mut self, id: Self::MachineId, event: Self::Event) -> Result<(), Self::Error> { - self.events.entry(id).or_insert_with(Vec::new).push(event); + let id_str = id.to_string(); + let events_json = get_swap_events(&self.ctx.sqlite_connection(), &id_str) + .map_to_mm(|e| TakerSwapStateMachineError::StorageError(e.to_string()))?; + let mut events: Vec = + serde_json::from_str(&events_json).map_to_mm(|e| TakerSwapStateMachineError::SerdeError(e.to_string()))?; + events.push(event); + drop_mutability!(events); + let serialized_events = + serde_json::to_string(&events).map_to_mm(|e| TakerSwapStateMachineError::SerdeError(e.to_string()))?; + update_swap_events(&self.ctx.sqlite_connection(), &id_str, &serialized_events) + .map_to_mm(|e| TakerSwapStateMachineError::StorageError(e.to_string()))?; Ok(()) } - async fn get_unfinished(&self) -> Result, Self::Error> { - Ok(self.events.keys().copied().collect()) - } + async fn get_unfinished(&self) -> Result, Self::Error> { todo!() } - async fn mark_finished(&mut self, _id: Self::MachineId) -> Result<(), Self::Error> { Ok(()) } + async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { + set_swap_is_finished(&self.ctx.sqlite_connection(), &id.to_string()) + .map_to_mm(|e| TakerSwapStateMachineError::StorageError(e.to_string())) + } } /// Represents the state machine for taker's side of the Trading Protocol Upgrade swap (v2). @@ -129,6 +186,8 @@ pub struct TakerSwapStateMachine { pub dex_fee: MmNumber, /// Premium amount, which might be paid to maker as additional reward. pub taker_premium: MmNumber, + /// Algorithm used to hash swap secrets. + pub secret_hash_algo: SecretHashAlgo, /// Swap transactions' confirmations settings. pub conf_settings: SwapConfirmationsSettings, /// UUID of the swap. @@ -137,14 +196,26 @@ pub struct TakerSwapStateMachine { pub p2p_topic: String, /// If Some, used to sign P2P messages of this swap. pub p2p_keypair: Option, + /// The secret used for immediate taker funding tx reclaim if maker back-outs + pub taker_secret: H256, } impl TakerSwapStateMachine { fn maker_payment_conf_timeout(&self) -> u64 { self.started_at + self.lock_duration * 2 / 3 } + fn taker_funding_locktime(&self) -> u64 { self.started_at + self.lock_duration * 3 } + fn taker_payment_locktime(&self) -> u64 { self.started_at + self.lock_duration } fn unique_data(&self) -> Vec { self.uuid.as_bytes().to_vec() } + + /// Returns secret hash generated using selected [SecretHashAlgo]. + fn taker_secret_hash(&self) -> Vec { + match self.secret_hash_algo { + SecretHashAlgo::DHASH160 => dhash160(self.taker_secret.as_slice()).take().into(), + SecretHashAlgo::SHA256 => sha256(self.taker_secret.as_slice()).take().into(), + } + } } impl StorableStateMachine @@ -191,18 +262,48 @@ impl; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + { + let sql_params = named_params! { + ":my_coin": state_machine.taker_coin.ticker(), + ":other_coin": state_machine.maker_coin.ticker(), + ":uuid": state_machine.uuid.to_string(), + ":started_at": state_machine.started_at, + ":swap_type": TAKER_SWAP_V2_TYPE, + ":maker_volume": state_machine.maker_volume.to_fraction_string(), + ":taker_volume": state_machine.taker_volume.to_fraction_string(), + ":premium": state_machine.taker_premium.to_fraction_string(), + ":dex_fee": state_machine.dex_fee.to_fraction_string(), + ":secret": state_machine.taker_secret.take(), + ":secret_hash": state_machine.taker_secret_hash(), + ":secret_hash_algo": state_machine.secret_hash_algo as u8, + ":p2p_privkey": state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), + ":lock_duration": state_machine.lock_duration, + ":maker_coin_confs": state_machine.conf_settings.maker_coin_confs, + ":maker_coin_nota": state_machine.conf_settings.maker_coin_nota, + ":taker_coin_confs": state_machine.conf_settings.taker_coin_confs, + ":taker_coin_nota": state_machine.conf_settings.taker_coin_nota + }; + insert_new_swap_v2(&state_machine.ctx, sql_params).unwrap(); + } + subscribe_to_topic(&state_machine.ctx, state_machine.p2p_topic.clone()); let swap_ctx = SwapsContext::from_ctx(&state_machine.ctx).expect("SwapsContext::from_ctx should not fail"); swap_ctx.init_msg_v2_store(state_machine.uuid, bits256::default()); let maker_coin_start_block = match state_machine.maker_coin.current_block().compat().await { Ok(b) => b, - Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + Err(e) => { + let reason = AbortReason::FailedToGetMakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, }; let taker_coin_start_block = match state_machine.taker_coin.current_block().compat().await { Ok(b) => b, - Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + Err(e) => { + let reason = AbortReason::FailedToGetTakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, }; if let Err(e) = check_balance_for_taker_swap( @@ -216,7 +317,8 @@ impl StorableState for Ini } #[async_trait] -impl State - for Initialized -{ +impl State for Initialized { type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -267,18 +367,59 @@ impl d, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive MakerNegotiation: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveMakerNegotiation(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; debug!("Received maker negotiation message {:?}", maker_negotiation); + let started_at_diff = state_machine.started_at.abs_diff(maker_negotiation.started_at); + if started_at_diff > MAX_STARTED_AT_DIFF { + let reason = AbortReason::TooLargeStartedAtDiff(started_at_diff); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + if !(maker_negotiation.secret_hash.len() == 20 || maker_negotiation.secret_hash.len() == 32) { + let reason = AbortReason::SecretHashUnexpectedLen(maker_negotiation.secret_hash.len()); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let expected_maker_payment_locktime = maker_negotiation.started_at + 2 * state_machine.lock_duration; + if maker_negotiation.payment_locktime != expected_maker_payment_locktime { + let reason = AbortReason::MakerProvidedInvalidLocktime(maker_negotiation.payment_locktime); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let maker_coin_htlc_pub_from_maker = match state_machine + .maker_coin + .parse_pubkey(&maker_negotiation.maker_coin_htlc_pub) + { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + let taker_coin_htlc_pub_from_maker = match state_machine + .taker_coin + .parse_pubkey(&maker_negotiation.taker_coin_htlc_pub) + { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + let unique_data = state_machine.unique_data(); let taker_negotiation = TakerNegotiation { action: Some(taker_negotiation::Action::Continue(TakerNegotiationData { started_at: state_machine.started_at, + funding_locktime: state_machine.taker_funding_locktime(), payment_locktime: state_machine.taker_payment_locktime(), + taker_secret_hash: state_machine.taker_secret_hash(), maker_coin_htlc_pub: state_machine.maker_coin.derive_htlc_pubkey(&unique_data), taker_coin_htlc_pub: state_machine.taker_coin.derive_htlc_pubkey(&unique_data), maker_coin_swap_contract: state_machine.maker_coin.swap_contract_address().map(|bytes| bytes.0), @@ -307,140 +448,142 @@ impl d, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive MakerNegotiated: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveMakerNegotiated(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; drop(abort_handle); debug!("Received maker negotiated message {:?}", maker_negotiated); if !maker_negotiated.negotiated { - let next_state = Aborted::new(format!( - "Maker did not negotiate with the reason: {}", - maker_negotiated.reason.unwrap_or_default() - )); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::MakerDidNotNegotiate(maker_negotiated.reason.unwrap_or_default()); + return Self::change_state(Aborted::new(reason), state_machine).await; } let next_state = Negotiated { - maker_coin: Default::default(), - taker_coin: Default::default(), maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - secret_hash: maker_negotiation.secret_hash, - maker_payment_locktime: maker_negotiation.payment_locktime, - maker_coin_htlc_pub_from_maker: maker_negotiation.maker_coin_htlc_pub, - taker_coin_htlc_pub_from_maker: maker_negotiation.taker_coin_htlc_pub, - maker_coin_swap_contract: maker_negotiation.maker_coin_swap_contract, - taker_coin_swap_contract: maker_negotiation.taker_coin_swap_contract, + negotiation_data: NegotiationData { + maker_secret_hash: maker_negotiation.secret_hash, + maker_payment_locktime: expected_maker_payment_locktime, + maker_coin_htlc_pub_from_maker, + taker_coin_htlc_pub_from_maker, + maker_coin_swap_contract: maker_negotiation.maker_coin_swap_contract, + taker_coin_swap_contract: maker_negotiation.taker_coin_swap_contract, + }, }; Self::change_state(next_state, state_machine).await } } -struct Negotiated { - maker_coin: PhantomData, - taker_coin: PhantomData, - maker_coin_start_block: u64, - taker_coin_start_block: u64, - secret_hash: Vec, +struct NegotiationData { + maker_secret_hash: Vec, maker_payment_locktime: u64, - maker_coin_htlc_pub_from_maker: Vec, - taker_coin_htlc_pub_from_maker: Vec, + maker_coin_htlc_pub_from_maker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> for Negotiated {} +impl NegotiationData { + fn to_stored_data(&self) -> StoredNegotiationData { + StoredNegotiationData { + maker_payment_locktime: self.maker_payment_locktime, + maker_secret_hash: self.maker_secret_hash.clone().into(), + maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker.to_bytes().into(), + taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker.to_bytes().into(), + maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), + taker_coin_swap_contract: self.taker_coin_swap_contract.clone().map(|b| b.into()), + } + } +} + +struct Negotiated { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, +} + +impl TransitionFrom> + for Negotiated +{ +} #[async_trait] -impl State for Negotiated { +impl State for Negotiated { type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { - let args = SendCombinedTakerPaymentArgs { - time_lock: state_machine.taker_payment_locktime(), - secret_hash: &self.secret_hash, - other_pub: &self.taker_coin_htlc_pub_from_maker, + let args = SendTakerFundingArgs { + time_lock: state_machine.taker_funding_locktime(), + taker_secret_hash: &state_machine.taker_secret_hash(), + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee_amount: state_machine.dex_fee.to_decimal(), - premium_amount: BigDecimal::from(0), + premium_amount: state_machine.taker_premium.to_decimal(), trading_amount: state_machine.taker_volume.to_decimal(), swap_unique_data: &state_machine.unique_data(), }; - let taker_payment = match state_machine.taker_coin.send_combined_taker_payment(args).await { + let taker_funding = match state_machine.taker_coin.send_taker_funding(args).await { Ok(tx) => tx, Err(e) => { - let next_state = Aborted::new(format!("Failed to send taker payment {:?}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::FailedToSendTakerFunding(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; + info!( - "Sent combined taker payment {} tx {:02x} during swap {}", + "Sent taker funding {} tx {:02x} during swap {}", state_machine.taker_coin.ticker(), - taker_payment.tx_hash(), + taker_funding.tx_hash(), state_machine.uuid ); - let next_state = TakerPaymentSent { - maker_coin: Default::default(), - taker_coin: Default::default(), + let next_state = TakerFundingSent { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment: TransactionIdentifier { - tx_hex: taker_payment.tx_hex().into(), - tx_hash: taker_payment.tx_hash(), - }, - secret_hash: self.secret_hash, - maker_payment_locktime: self.maker_payment_locktime, - maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker, - taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker, - maker_coin_swap_contract: self.maker_coin_swap_contract, - taker_coin_swap_contract: self.taker_coin_swap_contract, + taker_funding, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } } -impl StorableState for Negotiated { +impl StorableState + for Negotiated +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { TakerSwapEvent::Negotiated { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - secret_hash: Default::default(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct TakerPaymentSent { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerFundingSent { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment: TransactionIdentifier, - secret_hash: Vec, - maker_payment_locktime: u64, - maker_coin_htlc_pub_from_maker: Vec, - taker_coin_htlc_pub_from_maker: Vec, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + taker_funding: TakerCoin::Tx, + negotiation_data: NegotiationData, } -impl TransitionFrom> for TakerPaymentSent {} - #[async_trait] -impl State for TakerPaymentSent { +impl State + for TakerFundingSent +{ type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { - let taker_payment_info = TakerPaymentInfo { - tx_bytes: self.taker_payment.tx_hex.clone().0, + let taker_funding_info = TakerFundingInfo { + tx_bytes: self.taker_funding.tx_hex(), next_step_instructions: None, }; + let swap_msg = SwapMessage { - inner: Some(swap_message::Inner::TakerPaymentInfo(taker_payment_info)), + inner: Some(swap_message::Inner::TakerFundingInfo(taker_funding_info)), }; let abort_handle = broadcast_swap_v2_msg_every( state_machine.ctx.clone(), @@ -460,21 +603,260 @@ impl State for TakerPaymentSen let maker_payment_info = match recv_fut.await { Ok(p) => p, Err(e) => { - let next_state = TakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), - taker_payment: self.taker_payment, - secret_hash: self.secret_hash, - reason: TakerPaymentRefundReason::DidNotReceiveMakerPayment(e), + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::DidNotReceiveMakerPayment(e), }; return Self::change_state(next_state, state_machine).await; }, }; drop(abort_handle); + debug!("Received maker payment info message {:?}", maker_payment_info); + let preimage_tx = match state_machine + .taker_coin + .parse_preimage(&maker_payment_info.funding_preimage_tx) + { + Ok(p) => p, + Err(e) => { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FailedToParseFundingSpendPreimg(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + let preimage_sig = match state_machine + .taker_coin + .parse_signature(&maker_payment_info.funding_preimage_sig) + { + Ok(p) => p, + Err(e) => { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FailedToParseFundingSpendSig(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + let next_state = MakerPaymentAndFundingSpendPreimgReceived { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + taker_funding: self.taker_funding, + funding_spend_preimage: TxPreimageWithSig { + preimage: preimage_tx, + signature: preimage_sig, + }, + maker_payment: TransactionIdentifier { + tx_hex: maker_payment_info.tx_bytes.into(), + tx_hash: Default::default(), + }, + }; + Self::change_state(next_state, state_machine).await + } +} + +impl TransitionFrom> + for TakerFundingSent +{ +} + +impl StorableState + for TakerFundingSent +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerFundingSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), + } + } +} + +struct MakerPaymentAndFundingSpendPreimgReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, + taker_funding: TakerCoin::Tx, + funding_spend_preimage: TxPreimageWithSig, + maker_payment: TransactionIdentifier, +} + +impl TransitionFrom> + for MakerPaymentAndFundingSpendPreimgReceived +{ +} + +impl StorableState + for MakerPaymentAndFundingSpendPreimgReceived +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::MakerPaymentReceived { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, + maker_payment: self.maker_payment.clone(), + } + } +} + +#[async_trait] +impl State + for MakerPaymentAndFundingSpendPreimgReceived +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let unique_data = state_machine.unique_data(); + + let input = ValidatePaymentInput { + payment_tx: self.maker_payment.tx_hex.0.clone(), + time_lock_duration: state_machine.lock_duration, + time_lock: self.negotiation_data.maker_payment_locktime, + other_pub: self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), + secret_hash: self.negotiation_data.maker_secret_hash.clone(), + amount: state_machine.maker_volume.to_decimal(), + swap_contract_address: None, + try_spv_proof_until: state_machine.maker_payment_conf_timeout(), + confirmations: state_machine.conf_settings.maker_coin_confs, + unique_swap_data: unique_data.clone(), + watcher_reward: None, + }; + if let Err(e) = state_machine.maker_coin.validate_maker_payment(input).compat().await { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::MakerPaymentValidationFailed(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }; + + let args = GenTakerFundingSpendArgs { + funding_tx: &self.taker_funding, + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker, + taker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), + funding_time_lock: state_machine.taker_funding_locktime(), + taker_secret_hash: &state_machine.taker_secret_hash(), + taker_payment_time_lock: state_machine.taker_payment_locktime(), + maker_secret_hash: &self.negotiation_data.maker_secret_hash, + }; + + if let Err(e) = state_machine + .taker_coin + .validate_taker_funding_spend_preimage(&args, &self.funding_spend_preimage) + .await + { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FundingSpendPreimageValidationFailed(format!("{:?}", e)), + }; + return Self::change_state(next_state, state_machine).await; + } + + let taker_payment = match state_machine + .taker_coin + .sign_and_send_taker_funding_spend(&self.funding_spend_preimage, &args, &unique_data) + .await + { + Ok(tx) => tx, + Err(e) => { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FailedToSendTakerPayment(format!("{:?}", e)), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + info!( + "Sent taker payment {} tx {:02x} during swap {}", + state_machine.taker_coin.ticker(), + taker_payment.tx_hash(), + state_machine.uuid + ); + + let next_state = TakerPaymentSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment, + maker_payment: self.maker_payment, + negotiation_data: self.negotiation_data, + }; + Self::change_state(next_state, state_machine).await + } +} + +struct TakerPaymentSent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment: TakerCoin::Tx, + maker_payment: TransactionIdentifier, + negotiation_data: NegotiationData, +} + +impl + TransitionFrom> + for TakerPaymentSent +{ +} + +#[async_trait] +impl State + for TakerPaymentSent +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let taker_payment_info = TakerPaymentInfo { + tx_bytes: self.taker_payment.tx_hex(), + next_step_instructions: None, + }; + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::TakerPaymentInfo(taker_payment_info)), + }; + let _abort_handle = broadcast_swap_v2_msg_every( + state_machine.ctx.clone(), + state_machine.p2p_topic.clone(), + swap_msg, + 600., + state_machine.p2p_keypair, + ); + let input = ConfirmPaymentInput { - payment_tx: maker_payment_info.tx_bytes.clone(), + payment_tx: self.maker_payment.tx_hex.0.clone(), confirmations: state_machine.conf_settings.taker_coin_confs, requires_nota: state_machine.conf_settings.taker_coin_nota, wait_until: state_machine.maker_payment_conf_timeout(), @@ -483,76 +865,125 @@ impl State for TakerPaymentSen if let Err(e) = state_machine.maker_coin.wait_for_confirmations(input).compat().await { let next_state = TakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), taker_payment: self.taker_payment, - secret_hash: self.secret_hash, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::MakerPaymentNotConfirmedInTime(e), }; return Self::change_state(next_state, state_machine).await; } let next_state = MakerPaymentConfirmed { - maker_coin: Default::default(), - taker_coin: Default::default(), maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - maker_payment: TransactionIdentifier { - tx_hex: maker_payment_info.tx_bytes.into(), - tx_hash: Default::default(), - }, + maker_payment: self.maker_payment, taker_payment: self.taker_payment, - secret_hash: self.secret_hash, - maker_payment_locktime: self.maker_payment_locktime, - maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker, - taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker, - maker_coin_swap_contract: self.maker_coin_swap_contract, - taker_coin_swap_contract: self.taker_coin_swap_contract, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } } -impl StorableState for TakerPaymentSent { +impl StorableState + for TakerPaymentSent +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { TakerSwapEvent::TakerPaymentSent { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment: self.taker_payment.clone(), - secret_hash: self.secret_hash.clone().into(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), + } + } +} + +/// Represents the reason taker funding refund +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum TakerFundingRefundReason { + DidNotReceiveMakerPayment(String), + FailedToParseFundingSpendPreimg(String), + FailedToParseFundingSpendSig(String), + FailedToSendTakerPayment(String), + MakerPaymentValidationFailed(String), + FundingSpendPreimageValidationFailed(String), +} + +struct TakerFundingRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_funding: TakerCoin::Tx, + negotiation_data: NegotiationData, + reason: TakerFundingRefundReason, +} + +impl TransitionFrom> + for TakerFundingRefundRequired +{ +} +impl + TransitionFrom> + for TakerFundingRefundRequired +{ +} + +#[async_trait] +impl State + for TakerFundingRefundRequired +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, _state_machine: &mut Self::StateMachine) -> StateResult { + todo!() + } +} + +impl StorableState + for TakerFundingRefundRequired +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), + reason: self.reason.clone(), } } } #[derive(Debug)] enum TakerPaymentRefundReason { - DidNotReceiveMakerPayment(String), MakerPaymentNotConfirmedInTime(String), FailedToGenerateSpendPreimage(String), MakerDidNotSpendInTime(String), } -struct TakerPaymentRefundRequired { - maker_coin: PhantomData, - taker_coin: PhantomData, - taker_payment: TransactionIdentifier, - secret_hash: Vec, +struct TakerPaymentRefundRequired { + taker_payment: TakerCoin::Tx, + negotiation_data: NegotiationData, reason: TakerPaymentRefundReason, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentRefundRequired { } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentRefundRequired { } #[async_trait] -impl State +impl State for TakerPaymentRefundRequired { type StateMachine = TakerSwapStateMachine; @@ -566,52 +997,50 @@ impl State } } -impl StorableState +impl StorableState for TakerPaymentRefundRequired { type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { TakerSwapEvent::TakerPaymentRefundRequired { - taker_payment: self.taker_payment.clone(), - secret_hash: self.secret_hash.clone().into(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct MakerPaymentConfirmed { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct MakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, - taker_payment: TransactionIdentifier, - secret_hash: Vec, - maker_payment_locktime: u64, - maker_coin_htlc_pub_from_maker: Vec, - taker_coin_htlc_pub_from_maker: Vec, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + taker_payment: TakerCoin::Tx, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentConfirmed { } #[async_trait] -impl State for MakerPaymentConfirmed { +impl State + for MakerPaymentConfirmed +{ type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { let unique_data = state_machine.unique_data(); let args = GenTakerPaymentSpendArgs { - taker_tx: &self.taker_payment.tx_hex.0, + taker_tx: &self.taker_payment, time_lock: state_machine.taker_payment_locktime(), - secret_hash: &self.secret_hash, - maker_pub: &self.maker_coin_htlc_pub_from_maker, - taker_pub: &state_machine.taker_coin.derive_htlc_pubkey(&unique_data), + secret_hash: &self.negotiation_data.maker_secret_hash, + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker, + taker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount: state_machine.dex_fee.to_decimal(), premium_amount: Default::default(), @@ -626,10 +1055,8 @@ impl State for MakerPaymentCon Ok(p) => p, Err(e) => { let next_state = TakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), taker_payment: self.taker_payment, - secret_hash: self.secret_hash, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::FailedToGenerateSpendPreimage(e.to_string()), }; return Self::change_state(next_state, state_machine).await; @@ -637,12 +1064,8 @@ impl State for MakerPaymentCon }; let preimage_msg = TakerPaymentSpendPreimage { - signature: preimage.signature, - tx_preimage: if !preimage.preimage.is_empty() { - Some(preimage.preimage) - } else { - None - }, + signature: preimage.signature.to_bytes(), + tx_preimage: preimage.preimage.to_bytes(), }; let swap_msg = SwapMessage { inner: Some(swap_message::Inner::TakerPaymentSpendPreimage(preimage_msg)), @@ -657,11 +1080,15 @@ impl State for MakerPaymentCon ); let wait_args = WaitForHTLCTxSpendArgs { - tx_bytes: &self.taker_payment.tx_hex.0, - secret_hash: &self.secret_hash, + tx_bytes: &self.taker_payment.tx_hex(), + secret_hash: &self.negotiation_data.maker_secret_hash, wait_until: state_machine.taker_payment_locktime(), from_block: self.taker_coin_start_block, - swap_contract_address: &self.taker_coin_swap_contract.clone().map(|bytes| bytes.into()), + swap_contract_address: &self + .negotiation_data + .taker_coin_swap_contract + .clone() + .map(|bytes| bytes.into()), check_every: 10.0, watcher_reward: false, }; @@ -674,10 +1101,8 @@ impl State for MakerPaymentCon Ok(tx) => tx, Err(e) => { let next_state = TakerPaymentRefundRequired { - maker_coin: Default::default(), - taker_coin: Default::default(), taker_payment: self.taker_payment, - secret_hash: self.secret_hash, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::MakerDidNotSpendInTime(format!("{:?}", e)), }; return Self::change_state(next_state, state_machine).await; @@ -691,8 +1116,6 @@ impl State for MakerPaymentCon ); let next_state = TakerPaymentSpent { - maker_coin: Default::default(), - taker_coin: Default::default(), maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment, @@ -701,57 +1124,48 @@ impl State for MakerPaymentCon tx_hex: taker_payment_spend.tx_hex().into(), tx_hash: taker_payment_spend.tx_hash(), }, - secret_hash: self.secret_hash, - maker_payment_locktime: self.maker_payment_locktime, - maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker, - taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker, - maker_coin_swap_contract: self.maker_coin_swap_contract, - taker_coin_swap_contract: self.taker_coin_swap_contract, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } } -impl StorableState +impl StorableState for MakerPaymentConfirmed { type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { - TakerSwapEvent::BothPaymentsSentAndConfirmed { + TakerSwapEvent::MakerPaymentConfirmed { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment.clone(), - taker_payment: self.taker_payment.clone(), - secret_hash: self.secret_hash.clone().into(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), } } } #[allow(dead_code)] -struct TakerPaymentSpent { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentSpent { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, - taker_payment: TransactionIdentifier, + taker_payment: TakerCoin::Tx, taker_payment_spend: TransactionIdentifier, - secret_hash: Vec, - maker_payment_locktime: u64, - maker_coin_htlc_pub_from_maker: Vec, - taker_coin_htlc_pub_from_maker: Vec, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSpent { } #[async_trait] -impl State +impl State for TakerPaymentSpent { type StateMachine = TakerSwapStateMachine; @@ -759,23 +1173,31 @@ impl S async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { let secret = match state_machine .taker_coin - .extract_secret(&self.secret_hash, &self.taker_payment_spend.tx_hex.0, false) + .extract_secret( + &self.negotiation_data.maker_secret_hash, + &self.taker_payment_spend.tx_hex.0, + false, + ) .await { Ok(s) => s, Err(e) => { - let next_state = Aborted::new(format!("Couldn't extract secret from taker payment spend {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::CouldNotExtractSecret(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; let args = SpendPaymentArgs { other_payment_tx: &self.maker_payment.tx_hex.0, - time_lock: self.maker_payment_locktime, - other_pubkey: &self.maker_coin_htlc_pub_from_maker, + time_lock: self.negotiation_data.maker_payment_locktime, + other_pubkey: &self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), secret: &secret, - secret_hash: &self.secret_hash, - swap_contract_address: &self.maker_coin_swap_contract.clone().map(|bytes| bytes.into()), + secret_hash: &self.negotiation_data.maker_secret_hash, + swap_contract_address: &self + .negotiation_data + .maker_coin_swap_contract + .clone() + .map(|bytes| bytes.into()), swap_unique_data: &state_machine.unique_data(), watcher_reward: false, }; @@ -787,8 +1209,8 @@ impl S { Ok(tx) => tx, Err(e) => { - let next_state = Aborted::new(format!("Failed to spend maker payment {:?}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::FailedToSpendMakerPayment(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; info!( @@ -799,7 +1221,6 @@ impl S ); let next_state = MakerPaymentSpent { maker_coin: Default::default(), - taker_coin: Default::default(), maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment, @@ -814,7 +1235,9 @@ impl S } } -impl StorableState for TakerPaymentSpent { +impl StorableState + for TakerPaymentSpent +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -822,30 +1245,34 @@ impl StorableState for Tak maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment.clone(), - taker_payment: self.taker_payment.clone(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, taker_payment_spend: self.taker_payment_spend.clone(), - secret: Vec::new().into(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct MakerPaymentSpent { +struct MakerPaymentSpent { maker_coin: PhantomData, - taker_coin: PhantomData, maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, - taker_payment: TransactionIdentifier, + taker_payment: TakerCoin::Tx, taker_payment_spend: TransactionIdentifier, maker_payment_spend: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentSpent { } -impl StorableState for MakerPaymentSpent { +impl StorableState + for MakerPaymentSpent +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -853,7 +1280,10 @@ impl StorableState for Mak maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment.clone(), - taker_payment: self.taker_payment.clone(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, taker_payment_spend: self.taker_payment_spend.clone(), maker_payment_spend: self.maker_payment_spend.clone(), } @@ -861,7 +1291,7 @@ impl StorableState for Mak } #[async_trait] -impl State +impl State for MakerPaymentSpent { type StateMachine = TakerSwapStateMachine; @@ -871,14 +1301,32 @@ impl State } } +/// Represents possible reasons of taker swap being aborted +#[derive(Clone, Debug, Deserialize, Display, Serialize)] +pub enum AbortReason { + FailedToGetMakerCoinBlock(String), + FailedToGetTakerCoinBlock(String), + BalanceCheckFailure(String), + DidNotReceiveMakerNegotiation(String), + TooLargeStartedAtDiff(u64), + FailedToParsePubkey(String), + MakerProvidedInvalidLocktime(u64), + SecretHashUnexpectedLen(usize), + DidNotReceiveMakerNegotiated(String), + MakerDidNotNegotiate(String), + FailedToSendTakerFunding(String), + CouldNotExtractSecret(String), + FailedToSpendMakerPayment(String), +} + struct Aborted { maker_coin: PhantomData, taker_coin: PhantomData, - reason: String, + reason: AbortReason, } impl Aborted { - fn new(reason: String) -> Aborted { + fn new(reason: AbortReason) -> Aborted { Aborted { maker_coin: Default::default(), taker_coin: Default::default(), @@ -911,8 +1359,14 @@ impl StorableState for Abo impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> for Aborted {} -impl TransitionFrom> for Aborted {} +impl TransitionFrom> + for Aborted +{ +} +impl TransitionFrom> + for Aborted +{ +} struct Completed { maker_coin: PhantomData, @@ -948,4 +1402,7 @@ impl LastSta } } -impl TransitionFrom> for Completed {} +impl TransitionFrom> + for Completed +{ +} diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index 331b5b918b..7276fd1847 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -1,65 +1,60 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; -use coins::{GenTakerPaymentSpendArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, SwapOpsV2, Transaction, - TransactionEnum, ValidateTakerPaymentArgs}; -use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; -use mm2_test_helpers::for_tests::{enable_native, mm_dump, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, - Mm2TestConf}; +use coins::{GenTakerFundingSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, SendTakerFundingArgs, SwapOpsV2, + Transaction, ValidateTakerFundingArgs}; +use common::{block_on, now_sec}; +use mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, + MarketMakerIt, Mm2TestConf}; use script::{Builder, Opcode}; +use serialization::serialize; #[test] -fn send_and_refund_taker_payment() { +fn send_and_refund_taker_funding_timelock() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let time_lock = now_sec() - 1000; - let secret_hash = &[0; 20]; - let other_pub = coin.my_public_key().unwrap(); + let taker_secret_hash = &[0; 20]; + let maker_pub = coin.my_public_key().unwrap(); - let send_args = SendCombinedTakerPaymentArgs { + let send_args = SendTakerFundingArgs { time_lock, - secret_hash, - other_pub, + taker_secret_hash, + maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), swap_unique_data: &[], }; - let taker_payment_tx = block_on(coin.send_combined_taker_payment(send_args)).unwrap(); - println!("{:02x}", taker_payment_tx.tx_hash()); - let taker_payment_utxo_tx = match taker_payment_tx { - TransactionEnum::UtxoTx(tx) => tx, - unexpected => panic!("Unexpected tx {:?}", unexpected), - }; - // tx must have 3 outputs: actual payment, OP_RETURN containing the secret hash and change - assert_eq!(3, taker_payment_utxo_tx.outputs.len()); + let taker_funding_utxo_tx = block_on(coin.send_taker_funding(send_args)).unwrap(); + println!("{:02x}", taker_funding_utxo_tx.tx_hash()); + // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change + assert_eq!(3, taker_funding_utxo_tx.outputs.len()); // dex_fee_amount + premium_amount + trading_amount let expected_amount = 111000000u64; - assert_eq!(expected_amount, taker_payment_utxo_tx.outputs[0].value); + assert_eq!(expected_amount, taker_funding_utxo_tx.outputs[0].value); let expected_op_return = Builder::default() .push_opcode(Opcode::OP_RETURN) .push_data(&[0; 20]) .into_bytes(); - assert_eq!(expected_op_return, taker_payment_utxo_tx.outputs[1].script_pubkey); - - let taker_payment_bytes = taker_payment_utxo_tx.tx_hex(); + assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_bytes, + let validate_args = ValidateTakerFundingArgs { + funding_tx: &taker_funding_utxo_tx, time_lock, - secret_hash, - other_pub, + taker_secret_hash, + other_pub: maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), swap_unique_data: &[], }; - block_on(coin.validate_combined_taker_payment(validate_args)).unwrap(); + block_on(coin.validate_taker_funding(validate_args)).unwrap(); let refund_args = RefundPaymentArgs { - payment_tx: &taker_payment_bytes, + payment_tx: &serialize(&taker_funding_utxo_tx).take(), time_lock, other_pubkey: coin.my_public_key().unwrap(), secret_hash: &[0; 20], @@ -68,71 +63,130 @@ fn send_and_refund_taker_payment() { watcher_reward: false, }; - let refund_tx = block_on(coin.refund_combined_taker_payment(refund_args)).unwrap(); + let refund_tx = block_on(coin.refund_taker_funding_timelock(refund_args)).unwrap(); println!("{:02x}", refund_tx.tx_hash()); } #[test] -fn send_and_spend_taker_payment() { - let (_, taker_coin, _) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let (_, maker_coin, _) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); +fn send_and_refund_taker_funding_secret() { + let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let time_lock = now_sec() - 1000; - let secret = [1; 32]; - let secret_hash = dhash160(&secret); - let send_args = SendCombinedTakerPaymentArgs { + let taker_secret = [0; 32]; + let taker_secret_hash = dhash160(&taker_secret); + let maker_pub = coin.my_public_key().unwrap(); + + let send_args = SendTakerFundingArgs { time_lock, - secret_hash: secret_hash.as_slice(), - other_pub: maker_coin.my_public_key().unwrap(), + taker_secret_hash: taker_secret_hash.as_slice(), + maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), swap_unique_data: &[], }; - let taker_payment_tx = block_on(taker_coin.send_combined_taker_payment(send_args)).unwrap(); - println!("taker_payment_tx hash {:02x}", taker_payment_tx.tx_hash()); - let taker_payment_utxo_tx = match taker_payment_tx { - TransactionEnum::UtxoTx(tx) => tx, - unexpected => panic!("Unexpected tx {:?}", unexpected), - }; + let taker_funding_utxo_tx = block_on(coin.send_taker_funding(send_args)).unwrap(); + println!("{:02x}", taker_funding_utxo_tx.tx_hash()); + // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change + assert_eq!(3, taker_funding_utxo_tx.outputs.len()); + + // dex_fee_amount + premium_amount + trading_amount + let expected_amount = 111000000u64; + assert_eq!(expected_amount, taker_funding_utxo_tx.outputs[0].value); + + let expected_op_return = Builder::default() + .push_opcode(Opcode::OP_RETURN) + .push_data(taker_secret_hash.as_slice()) + .into_bytes(); + assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); - let taker_payment_bytes = taker_payment_utxo_tx.tx_hex(); - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_bytes, + let validate_args = ValidateTakerFundingArgs { + funding_tx: &taker_funding_utxo_tx, time_lock, - secret_hash: secret_hash.as_slice(), - other_pub: taker_coin.my_public_key().unwrap(), + taker_secret_hash: taker_secret_hash.as_slice(), + other_pub: maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), swap_unique_data: &[], }; - block_on(maker_coin.validate_combined_taker_payment(validate_args)).unwrap(); + block_on(coin.validate_taker_funding(validate_args)).unwrap(); - let gen_preimage_args = GenTakerPaymentSpendArgs { - taker_tx: &taker_payment_utxo_tx.tx_hex(), + let refund_args = RefundFundingSecretArgs { + funding_tx: &taker_funding_utxo_tx, time_lock, - secret_hash: secret_hash.as_slice(), - maker_pub: maker_coin.my_public_key().unwrap(), - taker_pub: taker_coin.my_public_key().unwrap(), - dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, + maker_pubkey: maker_pub, + taker_secret: &taker_secret, + taker_secret_hash: taker_secret_hash.as_slice(), + swap_unique_data: &[], + swap_contract_address: &None, + watcher_reward: false, + }; + + let refund_tx = block_on(coin.refund_taker_funding_secret(refund_args)).unwrap(); + println!("{:02x}", refund_tx.tx_hash()); +} + +#[test] +fn send_and_spend_taker_funding() { + let (_mm_arc, taker_coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + let (_mm_arc, maker_coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + + let funding_time_lock = now_sec() - 1000; + let taker_secret_hash = &[0; 20]; + + let taker_pub = taker_coin.my_public_key().unwrap(); + let maker_pub = maker_coin.my_public_key().unwrap(); + + let send_args = SendTakerFundingArgs { + time_lock: funding_time_lock, + taker_secret_hash, + maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), + swap_unique_data: &[], }; - let preimage_with_taker_sig = - block_on(taker_coin.gen_taker_payment_spend_preimage(&gen_preimage_args, &[])).unwrap(); - - block_on(maker_coin.validate_taker_payment_spend_preimage(&gen_preimage_args, &preimage_with_taker_sig)).unwrap(); - - let taker_payment_spend = block_on(maker_coin.sign_and_broadcast_taker_payment_spend( - &preimage_with_taker_sig, - &gen_preimage_args, - &secret, - &[], - )) - .unwrap(); - println!("taker_payment_spend hash {:02x}", taker_payment_spend.tx_hash()); + let taker_funding_utxo_tx = block_on(taker_coin.send_taker_funding(send_args)).unwrap(); + println!("Funding tx {:02x}", taker_funding_utxo_tx.tx_hash()); + // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change + assert_eq!(3, taker_funding_utxo_tx.outputs.len()); + + // dex_fee_amount + premium_amount + trading_amount + let expected_amount = 111000000u64; + assert_eq!(expected_amount, taker_funding_utxo_tx.outputs[0].value); + + let expected_op_return = Builder::default() + .push_opcode(Opcode::OP_RETURN) + .push_data(&[0; 20]) + .into_bytes(); + assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); + + let validate_args = ValidateTakerFundingArgs { + funding_tx: &taker_funding_utxo_tx, + time_lock: funding_time_lock, + taker_secret_hash, + other_pub: taker_pub, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), + swap_unique_data: &[], + }; + block_on(maker_coin.validate_taker_funding(validate_args)).unwrap(); + + let preimage_args = GenTakerFundingSpendArgs { + funding_tx: &taker_funding_utxo_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash, + taker_payment_time_lock: 0, + maker_secret_hash: &[0; 20], + }; + let preimage = block_on(maker_coin.gen_taker_funding_spend_preimage(&preimage_args, &[])).unwrap(); + + let payment_tx = block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &preimage_args, &[])).unwrap(); + println!("Taker payment tx {:02x}", payment_tx.tx_hash()); } #[test] @@ -144,6 +198,7 @@ fn test_v2_swap_utxo_utxo() { let bob_conf = Mm2TestConf::seednode_trade_v2(&format!("0x{}", hex::encode(bob_priv_key)), &coins); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); + log!("Bob log path: {}", mm_bob.log_path.display()); let alice_conf = Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob @@ -151,6 +206,7 @@ fn test_v2_swap_utxo_utxo() { .to_string()]); let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); + log!("Alice log path: {}", mm_alice.log_path.display()); log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); @@ -170,6 +226,12 @@ fn test_v2_swap_utxo_utxo() { for uuid in uuids { let expected_msg = format!("Swap {} has been completed", uuid); block_on(mm_bob.wait_for_log(60., |log| log.contains(&expected_msg))).unwrap(); - block_on(mm_alice.wait_for_log(60., |log| log.contains(&expected_msg))).unwrap(); + block_on(mm_alice.wait_for_log(30., |log| log.contains(&expected_msg))).unwrap(); + + let maker_swap_status = block_on(my_swap_status(&mm_bob, &uuid)); + println!("{:?}", maker_swap_status); + + let taker_swap_status = block_on(my_swap_status(&mm_alice, &uuid)); + println!("{:?}", taker_swap_status); } } diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 3fce082f0f..4f3a10593f 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -12,7 +12,7 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin use common::{block_on, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; use futures01::Future; -use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, get_payment_locktime, MakerSwap, +use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, generate_secret, get_payment_locktime, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, REFUND_TEST_FAILURE_LOG, SWAP_FINISHED_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; @@ -1457,7 +1457,7 @@ fn test_watcher_validate_taker_payment_utxo() { let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let maker_pubkey = maker_coin.my_public_key().unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { @@ -1535,7 +1535,7 @@ fn test_watcher_validate_taker_payment_utxo() { } // Used to get wrong swap id - let wrong_secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let wrong_secret_hash = dhash160(&generate_secret().unwrap()); let error = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1672,7 +1672,7 @@ fn test_watcher_validate_taker_payment_eth() { let time_lock = wait_for_confirmation_until; let taker_amount = BigDecimal::from_str("0.01").unwrap(); let maker_amount = BigDecimal::from_str("0.01").unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let watcher_reward = Some( block_on(taker_coin.get_taker_watcher_reward( &MmCoinEnum::from(taker_coin.clone()), @@ -1793,7 +1793,7 @@ fn test_watcher_validate_taker_payment_eth() { } // Used to get wrong swap id - let wrong_secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let wrong_secret_hash = dhash160(&generate_secret().unwrap()); let error = taker_coin .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1915,7 +1915,7 @@ fn test_watcher_validate_taker_payment_erc20() { let wait_for_confirmation_until = wait_until_sec(time_lock_duration); let time_lock = wait_for_confirmation_until; - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_amount = BigDecimal::from_str("0.01").unwrap(); let maker_amount = BigDecimal::from_str("0.01").unwrap(); @@ -2040,7 +2040,7 @@ fn test_watcher_validate_taker_payment_erc20() { } // Used to get wrong swap id - let wrong_secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let wrong_secret_hash = dhash160(&generate_secret().unwrap()); let error = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2156,7 +2156,7 @@ fn test_taker_validates_taker_payment_refund_utxo() { let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let maker_pubkey = maker_coin.my_public_key().unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { @@ -2243,7 +2243,7 @@ fn test_taker_validates_taker_payment_refund_eth() { let time_lock = now_sec() - 10; let taker_amount = BigDecimal::from_str("0.001").unwrap(); let maker_amount = BigDecimal::from_str("0.001").unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let watcher_reward = block_on(taker_coin.get_taker_watcher_reward( &MmCoinEnum::from(taker_coin.clone()), @@ -2562,7 +2562,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { let wait_for_confirmation_until = wait_until_sec(time_lock_duration); let time_lock = now_sec() - 10; - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_amount = BigDecimal::from_str("0.001").unwrap(); let maker_amount = BigDecimal::from_str("0.001").unwrap(); @@ -2684,7 +2684,7 @@ fn test_taker_validates_maker_payment_spend_utxo() { let taker_pubkey = taker_coin.my_public_key().unwrap(); let maker_pubkey = maker_coin.my_public_key().unwrap(); - let secret = MakerSwap::generate_secret().unwrap(); + let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); let maker_payment = maker_coin @@ -2771,7 +2771,7 @@ fn test_taker_validates_maker_payment_spend_eth() { let time_lock = wait_for_confirmation_until; let maker_amount = BigDecimal::from_str("0.001").unwrap(); - let secret = MakerSwap::generate_secret().unwrap(); + let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); let watcher_reward = block_on(maker_coin.get_maker_watcher_reward( @@ -3092,7 +3092,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { let time_lock = wait_for_confirmation_until; let maker_amount = BigDecimal::from_str("0.001").unwrap(); - let secret = MakerSwap::generate_secret().unwrap(); + let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); let watcher_reward = block_on(maker_coin.get_maker_watcher_reward( diff --git a/mm2src/mm2_number/src/mm_number.rs b/mm2src/mm2_number/src/mm_number.rs index 3934f02391..6e3c5896a6 100644 --- a/mm2src/mm2_number/src/mm_number.rs +++ b/mm2src/mm2_number/src/mm_number.rs @@ -3,7 +3,7 @@ use crate::{from_dec_to_ratio, from_ratio_to_dec}; use bigdecimal::BigDecimal; use core::ops::{Add, AddAssign, Div, Mul, Sub}; use num_bigint::BigInt; -use num_rational::BigRational; +use num_rational::{BigRational, ParseRatioError}; use num_traits::CheckedDiv; use num_traits::Zero; use serde::Serialize; @@ -228,11 +228,20 @@ impl MmNumber { /// Get BigDecimal representation pub fn to_decimal(&self) -> BigDecimal { from_ratio_to_dec(&self.0) } + /// Returns the numerator of the internal BigRational pub fn numer(&self) -> &BigInt { self.0.numer() } + /// Returns the denominator of the internal BigRational pub fn denom(&self) -> &BigInt { self.0.denom() } + /// Returns whether the number is zero pub fn is_zero(&self) -> bool { self.0.is_zero() } + + /// Returns the stringified representation of a number in a format like "1/3". + pub fn to_fraction_string(&self) -> String { self.0.to_string() } + + /// Attempts to parse a number from string, expects input to have fraction format like "1/3". + pub fn from_fraction_string(input: &str) -> Result { Ok(MmNumber(input.parse()?)) } } impl From for MmNumber { @@ -399,4 +408,14 @@ mod tests { assert_eq!(actual.num, expected); } + + #[test] + fn test_from_to_fraction_string() { + let input = "1000/999"; + let mm_num = MmNumber::from_fraction_string(input).unwrap(); + assert_eq!(*mm_num.numer(), BigInt::from(1000)); + assert_eq!(*mm_num.denom(), BigInt::from(999)); + + assert_eq!(input, mm_num.to_fraction_string()); + } } diff --git a/mm2src/mm2_state_machine/src/state_machine.rs b/mm2src/mm2_state_machine/src/state_machine.rs index 63ce6a96a3..9deae81120 100644 --- a/mm2src/mm2_state_machine/src/state_machine.rs +++ b/mm2src/mm2_state_machine/src/state_machine.rs @@ -63,7 +63,7 @@ pub trait State: Send + Sync + 'static { /// ```rust /// return Self::change_state(next_state); /// ``` - async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult; + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult; } /// A trait for transitioning between states in the state machine. From bc77f3934d4c795ad2489b45ff5c3ca514041bb8 Mon Sep 17 00:00:00 2001 From: smk762 <35845239+smk762@users.noreply.github.com> Date: Wed, 1 Nov 2023 18:41:49 +0800 Subject: [PATCH 22/40] fix(rpc): add 'version' to PUBLIC_METHODS --- mm2src/mm2_main/src/rpc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index 001f8bea28..ed883d15f0 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -70,6 +70,7 @@ const PUBLIC_METHODS: &[Option<&str>] = &[ Some("stats_swap_status"), Some("tradesarray"), Some("ticker"), + Some("version"), None, ]; From 30c58694c6a903337573598e2145c14232730f66 Mon Sep 17 00:00:00 2001 From: Alina Sharon <52405288+laruh@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:22:41 +0700 Subject: [PATCH 23/40] feat(nft): exclude spam and phishing, refactor db (#1959) In this commit `exclude_spam` and `exclude_phishing` params were added for `get_nft_list` and `get_nft_transfers` RPCs. Db in native target was refactored, It doesn't use details_json with all rust struct fields anymore. --- .../coins/hd_wallet_storage/wasm_storage.rs | 3 +- mm2src/coins/nft.rs | 847 +++++++++++++----- mm2src/coins/nft/nft_errors.rs | 103 ++- mm2src/coins/nft/nft_structs.rs | 208 ++++- mm2src/coins/nft/nft_tests.rs | 834 +++++++++++++---- mm2src/coins/nft/storage/db_test_helpers.rs | 388 +------- mm2src/coins/nft/storage/mod.rs | 108 ++- mm2src/coins/nft/storage/sql_storage.rs | 819 ++++++++++++----- mm2src/coins/nft/storage/wasm/nft_idb.rs | 17 +- mm2src/coins/nft/storage/wasm/wasm_storage.rs | 448 +++++++-- .../tx_history_storage/wasm/tx_history_db.rs | 3 +- .../wasm/indexeddb_block_header_storage.rs | 3 +- .../coins/z_coin/storage/blockdb/block_idb.rs | 3 +- .../z_coin/storage/walletdb/wallet_idb.rs | 3 +- mm2src/common/common.rs | 13 + mm2src/mm2_db/src/indexed_db/indexed_db.rs | 9 +- mm2src/mm2_event_stream/src/controller.rs | 13 +- .../src/account/storage/wasm_storage.rs | 3 +- .../src/lp_ordermatch/ordermatch_wasm_db.rs | 3 +- mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs | 3 +- mm2src/mm2_net/src/native_http.rs | 25 +- mm2src/mm2_net/src/transport.rs | 50 ++ mm2src/mm2_net/src/wasm_http.rs | 38 +- 23 files changed, 2818 insertions(+), 1126 deletions(-) diff --git a/mm2src/coins/hd_wallet_storage/wasm_storage.rs b/mm2src/coins/hd_wallet_storage/wasm_storage.rs index a9e11143e2..76ad67494f 100644 --- a/mm2src/coins/hd_wallet_storage/wasm_storage.rs +++ b/mm2src/coins/hd_wallet_storage/wasm_storage.rs @@ -10,7 +10,6 @@ use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, DbTable, DbTransact TableSignature, WeakDb}; use mm2_err_handle::prelude::*; -const DB_NAME: &str = "hd_wallet"; const DB_VERSION: u32 = 1; /// An index of the `HDAccountTable` table that consists of the following properties: /// * coin - coin ticker @@ -142,7 +141,7 @@ pub struct HDWalletDb { #[async_trait] impl DbInstance for HDWalletDb { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "hd_wallet"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index c84d0cbe24..0f0cb942e0 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -9,27 +9,35 @@ pub(crate) mod storage; #[cfg(any(test, target_arch = "wasm32"))] mod nft_tests; use crate::{coin_conf, get_my_address, MyAddressReq, WithdrawError}; -use nft_errors::{GetInfoFromUriError, GetNftInfoError, UpdateNftError}; +use nft_errors::{GetNftInfoError, UpdateNftError}; use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList, TransactionNftDetails, UpdateNftReq, WithdrawNftReq}; use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721}; -use crate::nft::nft_errors::ProtectFromSpamError; -use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetadataReq, TransferMeta, TransferStatus, - UriMeta}; +use crate::nft::nft_errors::{MetaFromUrlError, ProtectFromSpamError, UpdateSpamPhishingError}; +use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, NftCommon, NftCtx, NftTransferCommon, + PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq, SpamContractReq, + SpamContractRes, TransferMeta, TransferStatus, UriMeta}; use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; -use common::{parse_rfc3339_to_timestamp, APPLICATION_JSON}; +use common::parse_rfc3339_to_timestamp; use crypto::StandardHDCoinAddress; use ethereum_types::Address; -use http::header::ACCEPT; use mm2_err_handle::map_to_mm::MapToMmResult; +use mm2_net::transport::send_post_request_to_uri; use mm2_number::BigDecimal; use regex::Regex; use serde_json::Value as Json; use std::cmp::Ordering; +use std::collections::HashSet; use std::str::FromStr; +#[cfg(not(target_arch = "wasm32"))] +use mm2_net::native_http::send_request_to_uri; + +#[cfg(target_arch = "wasm32")] +use mm2_net::wasm_http::send_request_to_uri; + const MORALIS_API_ENDPOINT: &str = "api/v2"; /// query parameters for moralis request: The format of the token ID const MORALIS_FORMAT_QUERY_NAME: &str = "format"; @@ -37,9 +45,33 @@ const MORALIS_FORMAT_QUERY_VALUE: &str = "decimal"; /// The minimum block number from which to get the transfers const MORALIS_FROM_BLOCK_QUERY_NAME: &str = "from_block"; +const BLOCKLIST_ENDPOINT: &str = "api/blocklist"; +const BLOCKLIST_CONTRACT: &str = "contract"; +const BLOCKLIST_DOMAIN: &str = "domain"; +const BLOCKLIST_SCAN: &str = "scan"; + +/// `WithdrawNftResult` type represents the result of an NFT withdrawal operation. On success, it provides the details +/// of the generated transaction meant for transferring the NFT. On failure, it details the encountered error. pub type WithdrawNftResult = Result>; -/// `get_nft_list` function returns list of NFTs on requested chains owned by user. +/// Fetches a list of user-owned NFTs across specified chains. +/// +/// The function aggregates NFTs based on provided chains, supports pagination, and +/// allows for result limits and filters. If the `protect_from_spam` flag is true, +/// NFTs are checked and redacted for potential spam. +/// +/// # Parameters +/// +/// * `ctx`: Shared context with configurations/resources. +/// * `req`: Request specifying chains, pagination, and filters. +/// +/// # Returns +/// +/// On success, returns a detailed `NftList` containing NFTs, total count, and skipped count. +/// # Errors +/// +/// Returns `GetNftInfoError` variants for issues like invalid requests, transport failures, +/// database errors, and spam protection errors. pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; let _lock = nft_ctx.guard.lock().await; @@ -51,18 +83,35 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; let _lock = nft_ctx.guard.lock().await; @@ -79,13 +128,32 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; let _lock = nft_ctx.guard.lock().await; @@ -101,14 +169,27 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult`: A result indicating success or an error. pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; let _lock = nft_ctx.guard.lock().await; @@ -125,29 +206,27 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft None }; let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url).await?; - storage.add_transfers_to_history(chain, nft_transfers).await?; + storage.add_transfers_to_history(*chain, nft_transfers).await?; let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { Ok(Some(block)) => block, Ok(None) => { - // if there are no rows in NFT LIST table we can try to get all info from moralis. - let nfts = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url).await?; - update_meta_in_transfers(&storage, chain, nfts).await?; - update_transfers_with_empty_meta(&storage, chain, &req.url).await?; + // if there are no rows in NFT LIST table we can try to get nft list from moralis. + let nft_list = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url, &req.url_antispam).await?; + update_meta_in_transfers(&storage, chain, nft_list).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url, &req.url_antispam).await?; + update_spam(&storage, *chain, &req.url_antispam).await?; + update_phishing(&storage, chain, &req.url_antispam).await?; continue; }, Err(_) => { - // if there is an error, then NFT LIST table doesnt exist, so we need to cache from mroalis. + // if there is an error, then NFT LIST table doesnt exist, so we need to cache nft list from moralis. NftListStorageOps::init(&storage, chain).await?; - let nft_list = get_moralis_nft_list(&ctx, chain, &req.url).await?; - let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain) - .await? - .unwrap_or(0); - storage - .add_nfts_to_list(chain, nft_list.clone(), last_scanned_block) - .await?; + let nft_list = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url, &req.url_antispam).await?; update_meta_in_transfers(&storage, chain, nft_list).await?; - update_transfers_with_empty_meta(&storage, chain, &req.url).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url, &req.url_antispam).await?; + update_spam(&storage, *chain, &req.url_antispam).await?; + update_phishing(&storage, chain, &req.url_antispam).await?; continue; }, }; @@ -166,53 +245,291 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft last_nft_block: nft_block.to_string(), }); } - update_nft_list(ctx.clone(), &storage, chain, scanned_block + 1, &req.url).await?; - update_transfers_with_empty_meta(&storage, chain, &req.url).await?; + update_nft_list( + ctx.clone(), + &storage, + chain, + scanned_block + 1, + &req.url, + &req.url_antispam, + ) + .await?; + update_transfers_with_empty_meta(&storage, chain, &req.url, &req.url_antispam).await?; + update_spam(&storage, *chain, &req.url_antispam).await?; + update_phishing(&storage, chain, &req.url_antispam).await?; + } + Ok(()) +} + +/// `update_spam` function updates spam contracts info in NFT list and NFT transfers. +async fn update_spam(storage: &T, chain: Chain, url_antispam: &Url) -> MmResult<(), UpdateSpamPhishingError> +where + T: NftListStorageOps + NftTransferHistoryStorageOps, +{ + let token_addresses = storage.get_token_addresses(chain).await?; + if !token_addresses.is_empty() { + let addresses = token_addresses + .iter() + .map(eth_addr_to_hex) + .collect::>() + .join(","); + let spam_res = send_spam_request(&chain, url_antispam, addresses).await?; + for (address, is_spam) in spam_res.result.into_iter() { + if is_spam { + let address_hex = eth_addr_to_hex(&address); + storage + .update_nft_spam_by_token_address(&chain, address_hex.clone(), is_spam) + .await?; + storage + .update_transfer_spam_by_token_address(&chain, address_hex, is_spam) + .await?; + } + } + } + Ok(()) +} + +async fn update_phishing(storage: &T, chain: &Chain, url_antispam: &Url) -> MmResult<(), UpdateSpamPhishingError> +where + T: NftListStorageOps + NftTransferHistoryStorageOps, +{ + let transfer_domains = storage.get_domains(chain).await?; + let nft_domains = storage.get_animation_external_domains(chain).await?; + let domains: HashSet = transfer_domains.union(&nft_domains).cloned().collect(); + if !domains.is_empty() { + let domains = domains.into_iter().collect::>().join(","); + let domain_res = send_phishing_request(url_antispam, domains).await?; + for (domain, is_phishing) in domain_res.result.into_iter() { + if is_phishing { + storage + .update_nft_phishing_by_domain(chain, domain.clone(), is_phishing) + .await?; + storage + .update_transfer_phishing_by_domain(chain, domain, is_phishing) + .await?; + } + } } Ok(()) } +/// `send_spam_request` function sends request to antispam api to scan contract addresses for spam. +async fn send_spam_request( + chain: &Chain, + url_antispam: &Url, + addresses: String, +) -> MmResult { + let scan_contract_uri = prepare_uri_for_blocklist_endpoint(url_antispam, BLOCKLIST_CONTRACT, BLOCKLIST_SCAN)?; + let req_spam = SpamContractReq { + network: *chain, + addresses, + }; + let req_spam_json = serde_json::to_string(&req_spam)?; + let scan_contract_res = send_post_request_to_uri(scan_contract_uri.as_str(), req_spam_json).await?; + let spam_res: SpamContractRes = serde_json::from_slice(&scan_contract_res)?; + Ok(spam_res) +} + +/// `send_spam_request` function sends request to antispam api to scan domains for phishing. +async fn send_phishing_request( + url_antispam: &Url, + domains: String, +) -> MmResult { + let scan_contract_uri = prepare_uri_for_blocklist_endpoint(url_antispam, BLOCKLIST_DOMAIN, BLOCKLIST_SCAN)?; + let req_phishing = PhishingDomainReq { domains }; + let req_phishing_json = serde_json::to_string(&req_phishing)?; + let scan_domains_res = send_post_request_to_uri(scan_contract_uri.as_str(), req_phishing_json).await?; + let phishing_res: PhishingDomainRes = serde_json::from_slice(&scan_domains_res)?; + Ok(phishing_res) +} + +/// `prepare_uri_for_blocklist_endpoint` function constructs the URI required for the antispam API request. +/// It appends the required path segments to the given base URL and returns the completed URI. +fn prepare_uri_for_blocklist_endpoint( + url_antispam: &Url, + blocklist_type: &str, + blocklist_action_or_network: &str, +) -> MmResult { + let mut uri = url_antispam.clone(); + uri.set_path(BLOCKLIST_ENDPOINT); + uri.path_segments_mut() + .map_to_mm(|_| UpdateSpamPhishingError::Internal("Invalid URI".to_string()))? + .push(blocklist_type) + .push(blocklist_action_or_network); + Ok(uri) +} + +/// Refreshes and updates metadata associated with a specific NFT. +/// +/// The function obtains updated metadata for an NFT using its token address and token id. +/// It fetches the metadata from the provided `url` and validates it against possible spam and +/// phishing domains using the provided `url_antispam`. If the fetched metadata or its domain +/// is identified as spam or matches with any phishing domains, the NFT's `possible_spam` and/or +/// `possible_phishing` flags are set to true. +/// +/// # Arguments +/// +/// * `ctx`: Context required for handling internal operations. +/// * `req`: A request containing details about the NFT whose metadata needs to be refreshed. +/// +/// # Returns +/// +/// * `MmResult<(), UpdateNftError>`: A result indicating success or an error. pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; let _lock = nft_ctx.guard.lock().await; let storage = NftStorageBuilder::new(&ctx).build()?; - let moralis_meta = get_moralis_metadata( - format!("{:#02x}", req.token_address), + let token_address_str = eth_addr_to_hex(&req.token_address); + let moralis_meta = match get_moralis_metadata( + token_address_str.clone(), req.token_id.clone(), &req.chain, &req.url, + &req.url_antispam, ) - .await?; - let req = NftMetadataReq { - token_address: req.token_address, - token_id: req.token_id, - chain: req.chain, - protect_from_spam: false, + .await + { + Ok(moralis_meta) => moralis_meta, + Err(_) => { + storage + .update_nft_spam_by_token_address(&req.chain, token_address_str.clone(), true) + .await?; + storage + .update_transfer_spam_by_token_address(&req.chain, token_address_str.clone(), true) + .await?; + return Ok(()); + }, }; - let mut nft_db = get_nft_metadata(ctx.clone(), req).await?; + let mut nft_db = storage + .get_nft(&req.chain, token_address_str.clone(), req.token_id.clone()) + .await? + .ok_or_else(|| GetNftInfoError::TokenNotFoundInWallet { + token_address: token_address_str, + token_id: req.token_id.to_string(), + })?; let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref()); - let uri_meta = get_uri_meta(token_uri.as_deref(), moralis_meta.common.metadata.as_deref()).await; + let token_domain = get_domain_from_url(token_uri.as_deref()); + let uri_meta = get_uri_meta( + token_uri.as_deref(), + moralis_meta.common.metadata.as_deref(), + &req.url_antispam, + ) + .await; + // Gather domains for phishing checks + let domains = gather_domains(&token_domain, &uri_meta); nft_db.common.collection_name = moralis_meta.common.collection_name; nft_db.common.symbol = moralis_meta.common.symbol; nft_db.common.token_uri = token_uri; + nft_db.common.token_domain = token_domain; nft_db.common.metadata = moralis_meta.common.metadata; nft_db.common.last_token_uri_sync = moralis_meta.common.last_token_uri_sync; nft_db.common.last_metadata_sync = moralis_meta.common.last_metadata_sync; nft_db.common.possible_spam = moralis_meta.common.possible_spam; nft_db.uri_meta = uri_meta; - drop_mutability!(nft_db); + if !nft_db.common.possible_spam { + refresh_possible_spam(&storage, &req.chain, &mut nft_db, &req.url_antispam).await?; + }; + if !nft_db.possible_phishing { + refresh_possible_phishing(&storage, &req.chain, domains, &mut nft_db, &req.url_antispam).await?; + }; storage .refresh_nft_metadata(&moralis_meta.chain, nft_db.clone()) .await?; - let transfer_meta = TransferMeta::from(nft_db.clone()); + update_transfer_meta_using_nft(&storage, &req.chain, &mut nft_db).await?; + Ok(()) +} + +/// The `update_transfer_meta_using_nft` function updates the transfer metadata associated with the given NFT. +/// If metadata info contains potential spam links, function sets `possible_spam` true. +async fn update_transfer_meta_using_nft(storage: &T, chain: &Chain, nft: &mut Nft) -> MmResult<(), UpdateNftError> +where + T: NftListStorageOps + NftTransferHistoryStorageOps, +{ + let transfer_meta = TransferMeta::from(nft.clone()); storage - .update_transfers_meta_by_token_addr_id(&nft_db.chain, transfer_meta) + .update_transfers_meta_by_token_addr_id(chain, transfer_meta, nft.common.possible_spam) .await?; Ok(()) } -async fn get_moralis_nft_list(ctx: &MmArc, chain: &Chain, url: &Url) -> MmResult, GetNftInfoError> { +/// Extracts domains from uri_meta and token_domain. +fn gather_domains(token_domain: &Option, uri_meta: &UriMeta) -> HashSet { + let mut domains = HashSet::new(); + if let Some(domain) = token_domain { + domains.insert(domain.clone()); + } + if let Some(domain) = &uri_meta.image_domain { + domains.insert(domain.clone()); + } + if let Some(domain) = &uri_meta.animation_domain { + domains.insert(domain.clone()); + } + if let Some(domain) = &uri_meta.external_domain { + domains.insert(domain.clone()); + } + domains +} + +/// Refreshes the `possible_spam` flag based on spam results. +async fn refresh_possible_spam( + storage: &T, + chain: &Chain, + nft_db: &mut Nft, + url_antispam: &Url, +) -> MmResult<(), UpdateNftError> +where + T: NftListStorageOps + NftTransferHistoryStorageOps, +{ + let address_hex = eth_addr_to_hex(&nft_db.common.token_address); + let spam_res = send_spam_request(chain, url_antispam, address_hex.clone()).await?; + if let Some(true) = spam_res.result.get(&nft_db.common.token_address) { + nft_db.common.possible_spam = true; + storage + .update_nft_spam_by_token_address(chain, address_hex.clone(), true) + .await?; + storage + .update_transfer_spam_by_token_address(chain, address_hex, true) + .await?; + } + Ok(()) +} + +/// Refreshes the `possible_phishing` flag based on phishing results. +async fn refresh_possible_phishing( + storage: &T, + chain: &Chain, + domains: HashSet, + nft_db: &mut Nft, + url_antispam: &Url, +) -> MmResult<(), UpdateNftError> +where + T: NftListStorageOps + NftTransferHistoryStorageOps, +{ + if !domains.is_empty() { + let domain_list = domains.into_iter().collect::>().join(","); + let domain_res = send_phishing_request(url_antispam, domain_list).await?; + for (domain, is_phishing) in domain_res.result.into_iter() { + if is_phishing { + nft_db.possible_phishing = true; + storage + .update_transfer_phishing_by_domain(chain, domain.clone(), is_phishing) + .await?; + storage + .update_nft_phishing_by_domain(chain, domain, is_phishing) + .await?; + } + } + } + Ok(()) +} + +async fn get_moralis_nft_list( + ctx: &MmArc, + chain: &Chain, + url: &Url, + url_antispam: &Url, +) -> MmResult, GetNftInfoError> { let mut res_list = Vec::new(); let ticker = chain.to_ticker(); let conf = coin_conf(ctx, &ticker); @@ -243,7 +560,8 @@ async fn get_moralis_nft_list(ctx: &MmArc, chain: &Chain, url: &Url) -> MmResult Some(contract_type) => contract_type, None => continue, }; - let nft = build_nft_from_moralis(chain, nft_moralis, contract_type).await; + let mut nft = build_nft_from_moralis(*chain, nft_moralis, contract_type, url_antispam).await; + protect_from_nft_spam_links(&mut nft, false)?; // collect NFTs from the page res_list.push(nft); } @@ -329,10 +647,13 @@ async fn get_moralis_nft_transfers( block_timestamp, contract_type, token_uri: None, + token_domain: None, collection_name: None, image_url: None, + image_domain: None, token_name: None, status, + possible_phishing: false, }; // collect NFTs transfers from the page res_list.push(transfer_history); @@ -351,7 +672,13 @@ async fn get_moralis_nft_transfers( Ok(res_list) } -/// **Caution:** ERC-1155 token can have a total supply more than 1, which means there could be several owners +/// Implements request to the Moralis "Get NFT metadata" endpoint. +/// +/// [Moralis Documentation Link](https://docs.moralis.io/web3-data-api/evm/reference/get-nft-metadata) +/// +/// **Caution:** +/// +/// ERC-1155 token can have a total supply more than 1, which means there could be several owners /// of the same token. `get_nft_metadata` returns NFTs info with the most recent owner. /// **Dont** use this function to get specific info about owner address, amount etc, you will get info not related to my_address. async fn get_moralis_metadata( @@ -359,6 +686,7 @@ async fn get_moralis_metadata( token_id: BigDecimal, chain: &Chain, url: &Url, + url_antispam: &Url, ) -> MmResult { let mut uri = url.clone(); uri.set_path(MORALIS_API_ENDPOINT); @@ -378,7 +706,8 @@ async fn get_moralis_metadata( Some(contract_type) => contract_type, None => return MmError::err(GetNftInfoError::ContractTypeIsNull), }; - let nft_metadata = build_nft_from_moralis(chain, nft_moralis, contract_type).await; + let mut nft_metadata = build_nft_from_moralis(*chain, nft_moralis, contract_type, url_antispam).await; + protect_from_nft_spam_links(&mut nft_metadata, false)?; Ok(nft_metadata) } @@ -392,57 +721,6 @@ pub async fn withdraw_nft(ctx: MmArc, req: WithdrawNftReq) -> WithdrawNftResult } } -#[cfg(not(target_arch = "wasm32"))] -async fn send_request_to_uri(uri: &str) -> MmResult { - use http::header::HeaderValue; - use mm2_net::transport::slurp_req_body; - - let request = http::Request::builder() - .method("GET") - .uri(uri) - .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) - .body(hyper::Body::from(""))?; - - let (status, _header, body) = slurp_req_body(request).await?; - if !status.is_success() { - return Err(MmError::new(GetInfoFromUriError::Transport(format!( - "Response !200 from {}: {}, {}", - uri, status, body - )))); - } - Ok(body) -} - -#[cfg(target_arch = "wasm32")] -async fn send_request_to_uri(uri: &str) -> MmResult { - use mm2_net::wasm_http::FetchRequest; - - macro_rules! try_or { - ($exp:expr, $errtype:ident) => { - match $exp { - Ok(x) => x, - Err(e) => return Err(MmError::new(GetInfoFromUriError::$errtype(ERRL!("{:?}", e)))), - } - }; - } - - let result = FetchRequest::get(uri) - .header(ACCEPT.as_str(), APPLICATION_JSON) - .request_str() - .await; - let (status_code, response_str) = try_or!(result, Transport); - if !status_code.is_success() { - return Err(MmError::new(GetInfoFromUriError::Transport(ERRL!( - "!200: {}, {}", - status_code, - response_str - )))); - } - - let response: Json = try_or!(serde_json::from_str(&response_str), InvalidResponse); - Ok(response) -} - /// `check_moralis_ipfs_bafy` inspects a given token URI and modifies it if certain conditions are met. /// /// It checks if the URI points to the Moralis IPFS domain `"ipfs.moralis.io"` and starts with a specific path prefix `"/ipfs/bafy"`. @@ -465,26 +743,46 @@ fn check_moralis_ipfs_bafy(token_uri: Option<&str>) -> Option { }) } -async fn get_uri_meta(token_uri: Option<&str>, metadata: Option<&str>) -> UriMeta { +async fn get_uri_meta(token_uri: Option<&str>, metadata: Option<&str>, url_antispam: &Url) -> UriMeta { let mut uri_meta = UriMeta::default(); + // Fetching data from the URL if token_uri is provided if let Some(token_uri) = token_uri { - if let Ok(response_meta) = send_request_to_uri(token_uri).await { - if let Ok(token_uri_meta) = serde_json::from_value(response_meta) { - uri_meta = token_uri_meta; - } + if let Some(url) = construct_camo_url_with_token(token_uri, url_antispam) { + uri_meta = fetch_meta_from_url(url).await.unwrap_or_default(); } } + // Filling fields from metadata if provided if let Some(metadata) = metadata { if let Ok(meta_from_meta) = serde_json::from_str::(metadata) { - uri_meta.try_to_fill_missing_fields_from(meta_from_meta) + uri_meta.try_to_fill_missing_fields_from(meta_from_meta); } } - uri_meta.image_url = check_moralis_ipfs_bafy(uri_meta.image_url.as_deref()); - uri_meta.animation_url = check_moralis_ipfs_bafy(uri_meta.animation_url.as_deref()); + update_uri_moralis_ipfs_fields(&mut uri_meta); drop_mutability!(uri_meta); uri_meta } +fn construct_camo_url_with_token(token_uri: &str, url_antispam: &Url) -> Option { + let mut url = url_antispam.clone(); + url.set_path("url/decode"); + url.path_segments_mut().ok()?.push(hex::encode(token_uri).as_str()); + Some(url) +} + +async fn fetch_meta_from_url(url: Url) -> MmResult { + let response_meta = send_request_to_uri(url.as_str()).await?; + serde_json::from_value(response_meta).map_err(|e| e.into()) +} + +fn update_uri_moralis_ipfs_fields(uri_meta: &mut UriMeta) { + uri_meta.image_url = check_moralis_ipfs_bafy(uri_meta.image_url.as_deref()); + uri_meta.image_domain = get_domain_from_url(uri_meta.image_url.as_deref()); + uri_meta.animation_url = check_moralis_ipfs_bafy(uri_meta.animation_url.as_deref()); + uri_meta.animation_domain = get_domain_from_url(uri_meta.animation_url.as_deref()); + uri_meta.external_url = check_moralis_ipfs_bafy(uri_meta.external_url.as_deref()); + uri_meta.external_domain = get_domain_from_url(uri_meta.external_url.as_deref()); +} + fn get_transfer_status(my_wallet: &str, to_address: &str) -> TransferStatus { // if my_wallet == from_address && my_wallet == to_address it is incoming transfer, so we can check just to_address. if my_wallet.to_lowercase() == to_address.to_lowercase() { @@ -502,15 +800,16 @@ async fn update_nft_list( chain: &Chain, scan_from_block: u64, url: &Url, + url_antispam: &Url, ) -> MmResult<(), UpdateNftError> { - let transfers = storage.get_transfers_from_block(chain, scan_from_block).await?; + let transfers = storage.get_transfers_from_block(*chain, scan_from_block).await?; let req = MyAddressReq { coin: chain.to_ticker(), path_to_address: StandardHDCoinAddress::default(), }; let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase(); for transfer in transfers.into_iter() { - handle_nft_transfer(storage, chain, url, transfer, &my_address).await?; + handle_nft_transfer(storage, chain, url, url_antispam, transfer, &my_address).await?; } Ok(()) } @@ -519,17 +818,18 @@ async fn handle_nft_transfer MmResult<(), UpdateNftError> { match (transfer.status, transfer.contract_type) { (TransferStatus::Send, ContractType::Erc721) => handle_send_erc721(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc721) => { - handle_receive_erc721(storage, chain, transfer, url, my_address).await + handle_receive_erc721(storage, chain, transfer, url, url_antispam, my_address).await }, (TransferStatus::Send, ContractType::Erc1155) => handle_send_erc1155(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc1155) => { - handle_receive_erc1155(storage, chain, transfer, url, my_address).await + handle_receive_erc1155(storage, chain, transfer, url, url_antispam, my_address).await }, } } @@ -539,7 +839,7 @@ async fn handle_send_erc721 chain: &Chain, transfer: NftTransferHistory, ) -> MmResult<(), UpdateNftError> { - let nft_db = storage + storage .get_nft( chain, eth_addr_to_hex(&transfer.common.token_address), @@ -550,10 +850,6 @@ async fn handle_send_erc721 token_address: eth_addr_to_hex(&transfer.common.token_address), token_id: transfer.common.token_id.to_string(), })?; - let transfer_meta = TransferMeta::from(nft_db); - storage - .update_transfers_meta_by_token_addr_id(chain, transfer_meta) - .await?; storage .remove_nft_from_list( chain, @@ -570,14 +866,12 @@ async fn handle_receive_erc721 MmResult<(), UpdateNftError> { - let nft = match storage - .get_nft( - chain, - eth_addr_to_hex(&transfer.common.token_address), - transfer.common.token_id.clone(), - ) + let token_address_str = eth_addr_to_hex(&transfer.common.token_address); + match storage + .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone()) .await? { Some(mut nft_db) => { @@ -589,37 +883,39 @@ async fn handle_receive_erc721 { - let mut nft = get_moralis_metadata( - eth_addr_to_hex(&transfer.common.token_address), - transfer.common.token_id, + let mut nft = match get_moralis_metadata( + token_address_str.clone(), + transfer.common.token_id.clone(), chain, url, + url_antispam, ) - .await?; - // sometimes moralis updates Get All NFTs (which also affects Get Metadata) later - // than History by Wallet update - nft.common.owner_of = - Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?; - nft.block_number = transfer.block_number; - drop_mutability!(nft); + .await + { + Ok(mut moralis_meta) => { + // sometimes moralis updates Get All NFTs (which also affects Get Metadata) later + // than History by Wallet update + moralis_meta.common.owner_of = + Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?; + moralis_meta.block_number = transfer.block_number; + moralis_meta + }, + Err(_) => { + mark_as_spam_and_build_empty_meta(storage, chain, token_address_str, &transfer, my_address).await? + }, + }; storage - .add_nfts_to_list(chain, vec![nft.clone()], transfer.block_number) + .add_nfts_to_list(*chain, vec![nft.clone()], transfer.block_number) .await?; - nft + update_transfer_meta_using_nft(storage, chain, &mut nft).await?; }, - }; - let transfer_meta = TransferMeta::from(nft); - storage - .update_transfers_meta_by_token_addr_id(chain, transfer_meta) - .await?; + } Ok(()) } @@ -628,15 +924,12 @@ async fn handle_send_erc1155 MmResult<(), UpdateNftError> { + let token_address_str = eth_addr_to_hex(&transfer.common.token_address); let mut nft_db = storage - .get_nft( - chain, - eth_addr_to_hex(&transfer.common.token_address), - transfer.common.token_id.clone(), - ) + .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone()) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { - token_address: eth_addr_to_hex(&transfer.common.token_address), + token_address: token_address_str.clone(), token_id: transfer.common.token_id.to_string(), })?; match nft_db.common.amount.cmp(&transfer.common.amount) { @@ -644,7 +937,7 @@ async fn handle_send_erc1155 MmResult<(), UpdateNftError> { - let nft = match storage - .get_nft( - chain, - eth_addr_to_hex(&transfer.common.token_address), - transfer.common.token_id.clone(), - ) + let token_address_str = eth_addr_to_hex(&transfer.common.token_address); + let mut nft = match storage + .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone()) .await? { Some(mut nft_db) => { @@ -700,49 +987,98 @@ async fn handle_receive_erc1155 { - let moralis_meta = get_moralis_metadata( - eth_addr_to_hex(&transfer.common.token_address), + let nft = match get_moralis_metadata( + token_address_str.clone(), transfer.common.token_id.clone(), chain, url, + url_antispam, ) - .await?; - let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref()); - let uri_meta = get_uri_meta(token_uri.as_deref(), moralis_meta.common.metadata.as_deref()).await; - let nft = Nft { - common: NftCommon { - token_address: moralis_meta.common.token_address, - token_id: moralis_meta.common.token_id, - amount: transfer.common.amount, - owner_of: Address::from_str(my_address) - .map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?, - token_hash: moralis_meta.common.token_hash, - collection_name: moralis_meta.common.collection_name, - symbol: moralis_meta.common.symbol, - token_uri, - metadata: moralis_meta.common.metadata, - last_token_uri_sync: moralis_meta.common.last_token_uri_sync, - last_metadata_sync: moralis_meta.common.last_metadata_sync, - minter_address: moralis_meta.common.minter_address, - possible_spam: moralis_meta.common.possible_spam, + .await + { + Ok(moralis_meta) => { + create_nft_from_moralis_metadata(moralis_meta, &transfer, my_address, chain, url_antispam).await? + }, + Err(_) => { + mark_as_spam_and_build_empty_meta(storage, chain, token_address_str, &transfer, my_address).await? }, - chain: *chain, - block_number_minted: moralis_meta.block_number_minted, - block_number: transfer.block_number, - contract_type: moralis_meta.contract_type, - uri_meta, }; storage - .add_nfts_to_list(chain, [nft.clone()], transfer.block_number) + .add_nfts_to_list(*chain, [nft.clone()], transfer.block_number) .await?; nft }, }; - let transfer_meta = TransferMeta::from(nft); + update_transfer_meta_using_nft(storage, chain, &mut nft).await?; + Ok(()) +} + +async fn create_nft_from_moralis_metadata( + moralis_meta: Nft, + transfer: &NftTransferHistory, + my_address: &str, + chain: &Chain, + url_antispam: &Url, +) -> MmResult { + let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref()); + let token_domain = get_domain_from_url(token_uri.as_deref()); + let uri_meta = get_uri_meta( + token_uri.as_deref(), + moralis_meta.common.metadata.as_deref(), + url_antispam, + ) + .await; + let nft = Nft { + common: NftCommon { + token_address: moralis_meta.common.token_address, + token_id: moralis_meta.common.token_id, + amount: transfer.common.amount.clone(), + owner_of: Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?, + token_hash: moralis_meta.common.token_hash, + collection_name: moralis_meta.common.collection_name, + symbol: moralis_meta.common.symbol, + token_uri, + token_domain, + metadata: moralis_meta.common.metadata, + last_token_uri_sync: moralis_meta.common.last_token_uri_sync, + last_metadata_sync: moralis_meta.common.last_metadata_sync, + minter_address: moralis_meta.common.minter_address, + possible_spam: moralis_meta.common.possible_spam, + }, + chain: *chain, + block_number_minted: moralis_meta.block_number_minted, + block_number: transfer.block_number, + contract_type: moralis_meta.contract_type, + possible_phishing: false, + uri_meta, + }; + Ok(nft) +} + +async fn mark_as_spam_and_build_empty_meta( + storage: &T, + chain: &Chain, + token_address_str: String, + transfer: &NftTransferHistory, + my_address: &str, +) -> MmResult { storage - .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .update_nft_spam_by_token_address(chain, token_address_str.clone(), true) .await?; - Ok(()) + storage + .update_transfer_spam_by_token_address(chain, token_address_str, true) + .await?; + + Ok(build_nft_with_empty_meta(BuildNftFields { + token_address: transfer.common.token_address, + token_id: transfer.common.token_id.clone(), + amount: transfer.common.amount.clone(), + owner_of: Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?, + contract_type: transfer.contract_type, + possible_spam: true, + chain: transfer.chain, + block_number: transfer.block_number, + })) } /// `find_wallet_nft_amount` function returns NFT amount of cached NFT. @@ -775,13 +1111,14 @@ async fn cache_nfts_from_moralis MmResult, UpdateNftError> { - let nft_list = get_moralis_nft_list(ctx, chain, url).await?; + let nft_list = get_moralis_nft_list(ctx, chain, url, url_antispam).await?; let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(storage, chain) .await? .unwrap_or(0); storage - .add_nfts_to_list(chain, nft_list.clone(), last_scanned_block) + .add_nfts_to_list(*chain, nft_list.clone(), last_scanned_block) .await?; Ok(nft_list) } @@ -791,27 +1128,45 @@ async fn update_meta_in_transfers(storage: &T, chain: &Chain, nfts: Vec) where T: NftListStorageOps + NftTransferHistoryStorageOps, { - for nft in nfts.into_iter() { - let transfer_meta = TransferMeta::from(nft); - storage - .update_transfers_meta_by_token_addr_id(chain, transfer_meta) - .await?; + for mut nft in nfts.into_iter() { + update_transfer_meta_using_nft(storage, chain, &mut nft).await?; } Ok(()) } /// `update_transfers_with_empty_meta` function updates empty metadata in transfers. -async fn update_transfers_with_empty_meta(storage: &T, chain: &Chain, url: &Url) -> MmResult<(), UpdateNftError> +async fn update_transfers_with_empty_meta( + storage: &T, + chain: &Chain, + url: &Url, + url_antispam: &Url, +) -> MmResult<(), UpdateNftError> where T: NftListStorageOps + NftTransferHistoryStorageOps, { - let nft_token_addr_id = storage.get_transfers_with_empty_meta(chain).await?; + let nft_token_addr_id = storage.get_transfers_with_empty_meta(*chain).await?; for addr_id_pair in nft_token_addr_id.into_iter() { - let nft_meta = get_moralis_metadata(addr_id_pair.token_address, addr_id_pair.token_id, chain, url).await?; - let transfer_meta = TransferMeta::from(nft_meta); - storage - .update_transfers_meta_by_token_addr_id(chain, transfer_meta) - .await?; + let mut nft_meta = match get_moralis_metadata( + addr_id_pair.token_address.clone(), + addr_id_pair.token_id, + chain, + url, + url_antispam, + ) + .await + { + Ok(nft_meta) => nft_meta, + Err(_) => { + storage + .update_nft_spam_by_token_address(chain, addr_id_pair.token_address.clone(), true) + .await?; + storage + .update_transfer_spam_by_token_address(chain, addr_id_pair.token_address, true) + .await?; + continue; + }, + }; + update_transfer_meta_using_nft(storage, chain, &mut nft_meta).await?; } Ok(()) } @@ -824,26 +1179,31 @@ fn contains_disallowed_url(text: &str) -> Result { Ok(url_regex.is_match(text)) } -/// `check_and_redact_if_spam` checks if the text contains any links. +/// `process_text_for_spam_link` checks if the text contains any links and optionally redacts it. /// It doesn't matter if the link is valid or not, as this is a spam check. -/// If text contains some link, then it is a spam. -fn check_and_redact_if_spam(text: &mut Option) -> Result { +/// If text contains some link, then function returns `true`. +fn process_text_for_spam_link(text: &mut Option, redact: bool) -> Result { match text { Some(s) if contains_disallowed_url(s)? => { - *text = Some("URL redacted for user protection".to_string()); + if redact { + *text = Some("URL redacted for user protection".to_string()); + } Ok(true) }, _ => Ok(false), } } -/// `protect_from_history_spam` function checks and redact spam in `NftTransferHistory`. +/// `protect_from_history_spam_links` function checks and redact spam in `NftTransferHistory`. /// /// `collection_name` and `token_name` in `NftTransferHistory` shouldn't contain any links, /// they must be just an arbitrary text, which represents NFT names. -fn protect_from_history_spam(transfer: &mut NftTransferHistory) -> MmResult<(), ProtectFromSpamError> { - let collection_name_spam = check_and_redact_if_spam(&mut transfer.collection_name)?; - let token_name_spam = check_and_redact_if_spam(&mut transfer.token_name)?; +fn protect_from_history_spam_links( + transfer: &mut NftTransferHistory, + redact: bool, +) -> MmResult<(), ProtectFromSpamError> { + let collection_name_spam = process_text_for_spam_link(&mut transfer.collection_name, redact)?; + let token_name_spam = process_text_for_spam_link(&mut transfer.token_name, redact)?; if collection_name_spam || token_name_spam { transfer.common.possible_spam = true; @@ -851,65 +1211,82 @@ fn protect_from_history_spam(transfer: &mut NftTransferHistory) -> MmResult<(), Ok(()) } -/// `protect_from_nft_spam` function checks and redact spam in `Nft`. +/// `protect_from_nft_spam_links` function checks and optionally redacts spam links in `Nft`. /// /// `collection_name` and `token_name` in `Nft` shouldn't contain any links, /// they must be just an arbitrary text, which represents NFT names. /// `symbol` also must be a text or sign that represents a symbol. -fn protect_from_nft_spam(nft: &mut Nft) -> MmResult<(), ProtectFromSpamError> { - let collection_name_spam = check_and_redact_if_spam(&mut nft.common.collection_name)?; - let symbol_spam = check_and_redact_if_spam(&mut nft.common.symbol)?; - let token_name_spam = check_and_redact_if_spam(&mut nft.uri_meta.token_name)?; - let meta_spam = check_nft_metadata_for_spam(nft)?; +/// This function also checks `metadata` field for spam. +fn protect_from_nft_spam_links(nft: &mut Nft, redact: bool) -> MmResult<(), ProtectFromSpamError> { + let collection_name_spam = process_text_for_spam_link(&mut nft.common.collection_name, redact)?; + let symbol_spam = process_text_for_spam_link(&mut nft.common.symbol, redact)?; + let token_name_spam = process_text_for_spam_link(&mut nft.uri_meta.token_name, redact)?; + let meta_spam = process_metadata_for_spam_link(nft, redact)?; if collection_name_spam || symbol_spam || token_name_spam || meta_spam { nft.common.possible_spam = true; } Ok(()) } -/// `check_nft_metadata_for_spam` function checks and redact spam in `metadata` field from `Nft`. + +/// The `process_metadata_for_spam_link` function checks and optionally redacts spam link in the `metadata` field of `Nft`. /// /// **note:** `token_name` is usually called `name` in `metadata`. -fn check_nft_metadata_for_spam(nft: &mut Nft) -> MmResult { +fn process_metadata_for_spam_link(nft: &mut Nft, redact: bool) -> MmResult { if let Some(Ok(mut metadata)) = nft .common .metadata .as_ref() .map(|t| serde_json::from_str::>(t)) { - if check_spam_and_redact_metadata_field(&mut metadata, "name")? { + let spam_detected = process_metadata_field(&mut metadata, "name", redact)?; + if redact && spam_detected { nft.common.metadata = Some(serde_json::to_string(&metadata)?); - return Ok(true); } + return Ok(spam_detected); } Ok(false) } -/// The `check_spam_and_redact_metadata_field` function scans a specified field in a JSON metadata object for potential spam. +/// The `process_metadata_field` function scans a specified field in a JSON metadata object for potential spam. /// /// This function checks the provided `metadata` map for a field matching the `field` parameter. /// If this field is found and its value contains some link, it's considered to contain spam. -/// To protect users, function redacts field containing spam link. -/// The function returns `true` if it detected spam link, or `false` otherwise. -fn check_spam_and_redact_metadata_field( +/// Depending on the `redact` flag, it will either redact the spam link or leave it as it is. +/// The function returns `true` if it detected a spam link, or `false` otherwise. +fn process_metadata_field( metadata: &mut serde_json::Map, field: &str, + redact: bool, ) -> MmResult { match metadata.get(field).and_then(|v| v.as_str()) { Some(text) if contains_disallowed_url(text)? => { - metadata.insert( - field.to_string(), - serde_json::Value::String("URL redacted for user protection".to_string()), - ); + if redact { + metadata.insert( + field.to_string(), + serde_json::Value::String("URL redacted for user protection".to_string()), + ); + } Ok(true) }, _ => Ok(false), } } -async fn build_nft_from_moralis(chain: &Chain, nft_moralis: NftFromMoralis, contract_type: ContractType) -> Nft { +async fn build_nft_from_moralis( + chain: Chain, + nft_moralis: NftFromMoralis, + contract_type: ContractType, + url_antispam: &Url, +) -> Nft { let token_uri = check_moralis_ipfs_bafy(nft_moralis.common.token_uri.as_deref()); - let uri_meta = get_uri_meta(token_uri.as_deref(), nft_moralis.common.metadata.as_deref()).await; + let uri_meta = get_uri_meta( + token_uri.as_deref(), + nft_moralis.common.metadata.as_deref(), + url_antispam, + ) + .await; + let token_domain = get_domain_from_url(token_uri.as_deref()); Nft { common: NftCommon { token_address: nft_moralis.common.token_address, @@ -920,16 +1297,24 @@ async fn build_nft_from_moralis(chain: &Chain, nft_moralis: NftFromMoralis, cont collection_name: nft_moralis.common.collection_name, symbol: nft_moralis.common.symbol, token_uri, + token_domain, metadata: nft_moralis.common.metadata, last_token_uri_sync: nft_moralis.common.last_token_uri_sync, last_metadata_sync: nft_moralis.common.last_metadata_sync, minter_address: nft_moralis.common.minter_address, possible_spam: nft_moralis.common.possible_spam, }, - chain: *chain, + chain, block_number_minted: nft_moralis.block_number_minted.map(|v| v.0), block_number: *nft_moralis.block_number, contract_type, + possible_phishing: false, uri_meta, } } + +#[inline(always)] +pub(crate) fn get_domain_from_url(url: Option<&str>) -> Option { + url.and_then(|uri| Url::parse(uri).ok()) + .and_then(|url| url.domain().map(String::from)) +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 719c481dfc..941348db67 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -5,10 +5,11 @@ use common::{HttpStatusCode, ParseRfc3339Err}; use derive_more::Display; use enum_from::EnumFromStringify; use http::StatusCode; -use mm2_net::transport::SlurpError; +use mm2_net::transport::{GetInfoFromUriError, SlurpError}; use serde::{Deserialize, Serialize}; use web3::Error; +/// Enumerates potential errors that can arise when fetching NFT information. #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetNftInfoError { @@ -119,6 +120,16 @@ impl HttpStatusCode for GetNftInfoError { } } +/// Enumerates possible errors that can occur while updating NFT details in the database. +/// +/// The errors capture various issues that can arise during: +/// - Metadata refresh +/// - NFT transfer history updating +/// - NFT list updating +/// +/// The issues addressed include database errors, invalid hex strings, +/// inconsistencies in block numbers, and problems related to fetching or interpreting +/// fetched metadata. #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum UpdateNftError { @@ -168,6 +179,11 @@ pub enum UpdateNftError { }, #[display(fmt = "Invalid hex string: {}", _0)] InvalidHexString(String), + UpdateSpamPhishingError(UpdateSpamPhishingError), + GetInfoFromUriError(GetInfoFromUriError), + #[from_stringify("serde_json::Error")] + SerdeError(String), + ProtectFromSpamError(ProtectFromSpamError), } impl From for UpdateNftError { @@ -190,6 +206,18 @@ impl From for UpdateNftError { fn from(err: T) -> Self { UpdateNftError::DbError(format!("{:?}", err)) } } +impl From for UpdateNftError { + fn from(e: UpdateSpamPhishingError) -> Self { UpdateNftError::UpdateSpamPhishingError(e) } +} + +impl From for UpdateNftError { + fn from(e: GetInfoFromUriError) -> Self { UpdateNftError::GetInfoFromUriError(e) } +} + +impl From for UpdateNftError { + fn from(e: ProtectFromSpamError) -> Self { UpdateNftError::ProtectFromSpamError(e) } +} + impl HttpStatusCode for UpdateNftError { fn status_code(&self) -> StatusCode { match self { @@ -202,15 +230,35 @@ impl HttpStatusCode for UpdateNftError { | UpdateNftError::InvalidBlockOrder { .. } | UpdateNftError::LastScannedBlockNotFound { .. } | UpdateNftError::AttemptToReceiveAlreadyOwnedErc721 { .. } - | UpdateNftError::InvalidHexString(_) => StatusCode::INTERNAL_SERVER_ERROR, + | UpdateNftError::InvalidHexString(_) + | UpdateNftError::UpdateSpamPhishingError(_) + | UpdateNftError::GetInfoFromUriError(_) + | UpdateNftError::SerdeError(_) + | UpdateNftError::ProtectFromSpamError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } +/// Enumerates the errors that can occur during spam protection operations. +/// +/// This includes issues such as regex failures during text validation and +/// serialization/deserialization problems. #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)] -pub(crate) enum GetInfoFromUriError { - /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. - #[from_stringify("http::Error")] +pub enum ProtectFromSpamError { + #[from_stringify("regex::Error")] + RegexError(String), + #[from_stringify("serde_json::Error")] + SerdeError(String), +} + +/// An enumeration representing the potential errors encountered +/// during the process of updating spam or phishing-related information. +/// +/// This error set captures various failures, from request malformation +/// to database interaction errors, providing a comprehensive view of +/// possible issues during the spam/phishing update operations. +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)] +pub enum UpdateSpamPhishingError { #[display(fmt = "Invalid request: {}", _0)] InvalidRequest(String), #[display(fmt = "Transport: {}", _0)] @@ -220,24 +268,45 @@ pub(crate) enum GetInfoFromUriError { InvalidResponse(String), #[display(fmt = "Internal: {}", _0)] Internal(String), + #[display(fmt = "DB error {}", _0)] + DbError(String), + GetMyAddressError(GetMyAddressError), } -impl From for GetInfoFromUriError { - fn from(e: SlurpError) -> Self { - let error_str = e.to_string(); +impl From for UpdateSpamPhishingError { + fn from(e: GetMyAddressError) -> Self { UpdateSpamPhishingError::GetMyAddressError(e) } +} + +impl From for UpdateSpamPhishingError { + fn from(e: GetInfoFromUriError) -> Self { match e { - SlurpError::ErrorDeserializing { .. } => GetInfoFromUriError::InvalidResponse(error_str), - SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetInfoFromUriError::Transport(error_str), - SlurpError::InvalidRequest(_) => GetInfoFromUriError::InvalidRequest(error_str), - SlurpError::Internal(_) => GetInfoFromUriError::Internal(error_str), + GetInfoFromUriError::InvalidRequest(e) => UpdateSpamPhishingError::InvalidRequest(e), + GetInfoFromUriError::Transport(e) => UpdateSpamPhishingError::Transport(e), + GetInfoFromUriError::InvalidResponse(e) => UpdateSpamPhishingError::InvalidResponse(e), + GetInfoFromUriError::Internal(e) => UpdateSpamPhishingError::Internal(e), } } } -#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)] -pub enum ProtectFromSpamError { - #[from_stringify("regex::Error")] - RegexError(String), +impl From for UpdateSpamPhishingError { + fn from(err: T) -> Self { UpdateSpamPhishingError::DbError(format!("{:?}", err)) } +} + +/// Errors encountered when parsing a `Chain` from a string. +#[derive(Debug, Display)] +pub enum ParseChainTypeError { + /// The provided string does not correspond to any of the supported blockchain types. + UnsupportedChainType, +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum MetaFromUrlError { #[from_stringify("serde_json::Error")] - SerdeError(String), + #[display(fmt = "Invalid response: {}", _0)] + InvalidResponse(String), + GetInfoFromUriError(GetInfoFromUriError), +} + +impl From for MetaFromUrlError { + fn from(e: GetInfoFromUriError) -> Self { MetaFromUrlError::GetInfoFromUriError(e) } } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 9d7a07b89a..165e7cbd93 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -6,32 +6,65 @@ use futures::lock::Mutex as AsyncMutex; use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; +use serde::de::{self, Deserializer}; use serde::Deserialize; use serde_json::Value as Json; +use std::collections::HashMap; use std::fmt; use std::num::NonZeroUsize; use std::str::FromStr; use std::sync::Arc; use url::Url; +use crate::nft::nft_errors::ParseChainTypeError; #[cfg(target_arch = "wasm32")] use mm2_db::indexed_db::{ConstructibleDb, SharedDb}; #[cfg(target_arch = "wasm32")] use crate::nft::storage::wasm::nft_idb::NftCacheIDB; +/// Represents a request to list NFTs owned by the user across specified chains. +/// +/// The request provides options such as pagination, limiting the number of results, +/// and applying specific filters to the list. #[derive(Debug, Deserialize)] pub struct NftListReq { + /// List of chains to fetch the NFTs from. pub(crate) chains: Vec, + /// Parameter indicating if the maximum number of NFTs should be fetched. + /// If true, then `limit` will be ignored. #[serde(default)] pub(crate) max: bool, + /// Limit to the number of NFTs returned in a single request. #[serde(default = "ten")] pub(crate) limit: usize, + /// Page number for pagination. pub(crate) page_number: Option, + /// Flag indicating if the returned list should be protected from potential spam. #[serde(default)] pub(crate) protect_from_spam: bool, + /// Optional filters to apply when listing the NFTs. + pub(crate) filters: Option, +} + +/// Filters that can be applied when listing NFTs to exclude potential threats or nuisances. +#[derive(Copy, Clone, Debug, Deserialize)] +pub struct NftListFilters { + /// Exclude NFTs that are flagged as possible spam. + #[serde(default)] + pub(crate) exclude_spam: bool, + /// Exclude NFTs that are flagged as phishing attempts. + #[serde(default)] + pub(crate) exclude_phishing: bool, } +/// Contains parameters required to fetch metadata for a specified NFT. +/// # Fields +/// * `token_address`: The address of the NFT token. +/// * `token_id`: The ID of the NFT token. +/// * `chain`: The blockchain where the NFT exists. +/// * `protect_from_spam`: Indicates whether to check and redact potential spam. If set to true, +/// the internal function `protect_from_nft_spam` is utilized. #[derive(Debug, Deserialize)] pub struct NftMetadataReq { pub(crate) token_address: Address, @@ -41,20 +74,26 @@ pub struct NftMetadataReq { pub(crate) protect_from_spam: bool, } +/// Contains parameters required to refresh metadata for a specified NFT. +/// # Fields +/// * `token_address`: The address of the NFT token whose metadata needs to be refreshed. +/// * `token_id`: The ID of the NFT token. +/// * `chain`: The blockchain where the NFT exists. +/// * `url`: URL to fetch the metadata. +/// * `url_antispam`: URL used to validate if the fetched contract addresses are associated +/// with spam contracts or if domain fields in the fetched metadata match known phishing domains. #[derive(Debug, Deserialize)] pub struct RefreshMetadataReq { pub(crate) token_address: Address, pub(crate) token_id: BigDecimal, pub(crate) chain: Chain, pub(crate) url: Url, + pub(crate) url_antispam: Url, } -#[derive(Debug, Display)] -pub enum ParseChainTypeError { - UnsupportedChainType, -} - -#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +/// Represents blockchains which are supported by NFT feature. +/// Currently there are only EVM based chains. +#[derive(Clone, Copy, Debug, PartialEq, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum Chain { Avalanche, @@ -99,15 +138,31 @@ impl FromStr for Chain { fn from_str(s: &str) -> Result { match s { "AVALANCHE" => Ok(Chain::Avalanche), + "avalanche" => Ok(Chain::Avalanche), "BSC" => Ok(Chain::Bsc), + "bsc" => Ok(Chain::Bsc), "ETH" => Ok(Chain::Eth), + "eth" => Ok(Chain::Eth), "FANTOM" => Ok(Chain::Fantom), + "fantom" => Ok(Chain::Fantom), "POLYGON" => Ok(Chain::Polygon), + "polygon" => Ok(Chain::Polygon), _ => Err(ParseChainTypeError::UnsupportedChainType), } } } +/// This implementation will use `FromStr` to deserialize `Chain`. +impl<'de> Deserialize<'de> for Chain { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(de::Error::custom) + } +} + #[derive(Debug, Display)] pub(crate) enum ParseContractTypeError { UnsupportedContractType, @@ -156,12 +211,15 @@ pub(crate) struct UriMeta { #[serde(rename = "image")] pub(crate) raw_image_url: Option, pub(crate) image_url: Option, + pub(crate) image_domain: Option, #[serde(rename = "name")] pub(crate) token_name: Option, pub(crate) description: Option, pub(crate) attributes: Option, pub(crate) animation_url: Option, + pub(crate) animation_domain: Option, pub(crate) external_url: Option, + pub(crate) external_domain: Option, pub(crate) image_details: Option, } @@ -196,6 +254,7 @@ impl UriMeta { } /// [`NftCommon`] structure contains common fields from [`Nft`] and [`NftFromMoralis`] +/// The `possible_spam` field indicates if any potential spam has been detected. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NftCommon { pub(crate) token_address: Address, @@ -207,6 +266,7 @@ pub struct NftCommon { pub(crate) collection_name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, + pub(crate) token_domain: Option, pub(crate) metadata: Option, pub(crate) last_token_uri_sync: Option, pub(crate) last_metadata_sync: Option, @@ -215,6 +275,9 @@ pub struct NftCommon { pub(crate) possible_spam: bool, } +/// Represents an NFT with specific chain details, contract type, and other relevant attributes. +/// This structure captures detailed information about an NFT. The `possible_phishing` +/// field indicates if any domains associated with the NFT have been marked as phishing. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Nft { #[serde(flatten)] @@ -223,10 +286,52 @@ pub struct Nft { pub(crate) block_number_minted: Option, pub(crate) block_number: u64, pub(crate) contract_type: ContractType, + #[serde(default)] + pub(crate) possible_phishing: bool, pub(crate) uri_meta: UriMeta, } -/// This structure is for deserializing moralis NFT json to struct. +pub(crate) struct BuildNftFields { + pub(crate) token_address: Address, + pub(crate) token_id: BigDecimal, + pub(crate) amount: BigDecimal, + pub(crate) owner_of: Address, + pub(crate) contract_type: ContractType, + pub(crate) possible_spam: bool, + pub(crate) chain: Chain, + pub(crate) block_number: u64, +} + +pub(crate) fn build_nft_with_empty_meta(nft_fields: BuildNftFields) -> Nft { + Nft { + common: NftCommon { + token_address: nft_fields.token_address, + token_id: nft_fields.token_id, + amount: nft_fields.amount, + owner_of: nft_fields.owner_of, + token_hash: None, + collection_name: None, + symbol: None, + token_uri: None, + token_domain: None, + metadata: None, + last_token_uri_sync: None, + last_metadata_sync: None, + minter_address: None, + possible_spam: nft_fields.possible_spam, + }, + chain: nft_fields.chain, + block_number_minted: None, + block_number: nft_fields.block_number, + contract_type: nft_fields.contract_type, + possible_phishing: false, + uri_meta: Default::default(), + } +} + +/// Represents an NFT structure specifically for deserialization from Moralis's JSON response. +/// +/// This structure is adapted to the specific format provided by Moralis's API. #[derive(Debug, Deserialize)] pub(crate) struct NftFromMoralis { #[serde(flatten)] @@ -259,6 +364,8 @@ impl std::ops::Deref for SerdeStringWrap { fn deref(&self) -> &T { &self.0 } } +/// Represents a detailed list of NFTs, including the total number of NFTs and the number of skipped NFTs. +/// It is used as response of `get_nft_list` if it is successful. #[derive(Debug, Serialize)] pub struct NftList { pub(crate) nfts: Vec, @@ -322,15 +429,26 @@ pub struct TransactionNftDetails { pub(crate) transaction_type: TransactionType, } +/// Represents a request to fetch the transfer history of NFTs owned by the user across specified chains. +/// +/// The request provides options such as pagination, limiting the number of results, +/// and applying specific filters to the history. #[derive(Debug, Deserialize)] pub struct NftTransfersReq { + /// List of chains to fetch the NFT transfer history from. pub(crate) chains: Vec, + /// Optional filters to apply when fetching the NFT transfer history. pub(crate) filters: Option, + /// Parameter indicating if the maximum number of transfer records should be fetched. + /// If true, then `limit` will be ignored. #[serde(default)] pub(crate) max: bool, + /// Limit to the number of transfer records returned in a single request. #[serde(default = "ten")] pub(crate) limit: usize, + /// Page number for pagination. pub(crate) page_number: Option, + /// Flag indicating if the returned transfer history should be protected from potential spam. #[serde(default)] pub(crate) protect_from_spam: bool, } @@ -340,7 +458,7 @@ pub(crate) enum ParseTransferStatusError { UnsupportedTransferStatus, } -#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] pub(crate) enum TransferStatus { Receive, Send, @@ -369,12 +487,13 @@ impl fmt::Display for TransferStatus { } /// [`NftTransferCommon`] structure contains common fields from [`NftTransferHistory`] and [`NftTransferHistoryFromMoralis`] +/// The `possible_spam` field indicates if any potential spam has been detected. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NftTransferCommon { pub(crate) block_hash: Option, /// Transaction hash in hexadecimal format pub(crate) transaction_hash: String, - pub(crate) transaction_index: Option, + pub(crate) transaction_index: Option, pub(crate) log_index: u32, pub(crate) value: Option, pub(crate) transaction_type: Option, @@ -383,12 +502,18 @@ pub struct NftTransferCommon { pub(crate) from_address: Address, pub(crate) to_address: Address, pub(crate) amount: BigDecimal, - pub(crate) verified: Option, + pub(crate) verified: Option, pub(crate) operator: Option, #[serde(default)] pub(crate) possible_spam: bool, } +/// Represents the historical transfer details of an NFT. +/// +/// Contains relevant information about the NFT transfer such as the chain, block details, +/// and contract type. Additionally, fields like `collection_name`, `token_name`, and +/// urls to metadata provide insight into the NFT's identity. The `possible_phishing` +/// field indicates if any domains associated with the NFT have been marked as phishing. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NftTransferHistory { #[serde(flatten)] @@ -398,13 +523,19 @@ pub struct NftTransferHistory { pub(crate) block_timestamp: u64, pub(crate) contract_type: ContractType, pub(crate) token_uri: Option, + pub(crate) token_domain: Option, pub(crate) collection_name: Option, pub(crate) image_url: Option, + pub(crate) image_domain: Option, pub(crate) token_name: Option, pub(crate) status: TransferStatus, + #[serde(default)] + pub(crate) possible_phishing: bool, } -/// This structure is for deserializing moralis NFT transfer json to struct. +/// Represents an NFT transfer structure specifically for deserialization from Moralis's JSON response. +/// +/// This structure is adapted to the specific format provided by Moralis's API. #[derive(Debug, Deserialize)] pub(crate) struct NftTransferHistoryFromMoralis { #[serde(flatten)] @@ -414,6 +545,9 @@ pub(crate) struct NftTransferHistoryFromMoralis { pub(crate) contract_type: Option, } +/// Represents the detailed transfer history of NFTs, including the total number of transfers +/// and the number of skipped transfers. +/// It is used as a response of `get_nft_transfers` if it is successful. #[derive(Debug, Serialize)] pub struct NftsTransferHistoryList { pub(crate) transfer_history: Vec, @@ -421,20 +555,35 @@ pub struct NftsTransferHistoryList { pub(crate) total: usize, } +/// Filters that can be applied to the NFT transfer history. +/// +/// Allows filtering based on transaction type (send/receive), date range, +/// and whether to exclude spam or phishing-related transfers. #[derive(Copy, Clone, Debug, Deserialize)] pub struct NftTransferHistoryFilters { #[serde(default)] - pub receive: bool, + pub(crate) receive: bool, #[serde(default)] pub(crate) send: bool, pub(crate) from_date: Option, pub(crate) to_date: Option, + #[serde(default)] + pub(crate) exclude_spam: bool, + #[serde(default)] + pub(crate) exclude_phishing: bool, } +/// Contains parameters required to update NFT transfer history and NFT list. +/// # Fields +/// * `chains`: A list of blockchains for which the NFTs need to be updated. +/// * `url`: URL to fetch the NFT data. +/// * `url_antispam`: URL used to validate if the fetched contract addresses are associated +/// with spam contracts or if domain fields in the fetched metadata match known phishing domains. #[derive(Debug, Deserialize)] pub struct UpdateNftReq { pub(crate) chains: Vec, pub(crate) url: Url, + pub(crate) url_antispam: Url, } #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] @@ -448,8 +597,10 @@ pub struct TransferMeta { pub(crate) token_address: String, pub(crate) token_id: BigDecimal, pub(crate) token_uri: Option, + pub(crate) token_domain: Option, pub(crate) collection_name: Option, pub(crate) image_url: Option, + pub(crate) image_domain: Option, pub(crate) token_name: Option, } @@ -459,20 +610,32 @@ impl From for TransferMeta { token_address: eth_addr_to_hex(&nft_db.common.token_address), token_id: nft_db.common.token_id, token_uri: nft_db.common.token_uri, + token_domain: nft_db.common.token_domain, collection_name: nft_db.common.collection_name, image_url: nft_db.uri_meta.image_url, + image_domain: nft_db.uri_meta.image_domain, token_name: nft_db.uri_meta.token_name, } } } +/// The primary context for NFT operations within the MM environment. +/// +/// This struct provides an interface for interacting with the underlying data structures +/// required for NFT operations, including guarding against concurrent accesses and +/// dealing with platform-specific storage mechanisms. pub(crate) struct NftCtx { + /// An asynchronous mutex to guard against concurrent NFT operations, ensuring data consistency. pub(crate) guard: Arc>, #[cfg(target_arch = "wasm32")] + /// Platform-specific database for caching NFT data. pub(crate) nft_cache_db: SharedDb, } impl NftCtx { + /// Create a new `NftCtx` from the given MM context. + /// + /// If an `NftCtx` instance doesn't already exist in the MM context, it gets created and cached for subsequent use. pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { Ok(try_s!(from_ctx(&ctx.nft_ctx, move || { Ok(NftCtx { @@ -483,3 +646,24 @@ impl NftCtx { }))) } } + +#[derive(Debug, Serialize)] +pub(crate) struct SpamContractReq { + pub(crate) network: Chain, + pub(crate) addresses: String, +} + +#[derive(Debug, Serialize)] +pub(crate) struct PhishingDomainReq { + pub(crate) domains: String, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct SpamContractRes { + pub(crate) result: HashMap, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct PhishingDomainRes { + pub(crate) result: HashMap, +} diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index 02710e0ac4..ae10513987 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -1,194 +1,656 @@ -const NFT_LIST_URL_TEST: &str = "https://moralis-proxy.komodo.earth/api/v2/0x394d86994f954ed931b86791b62fe64f4c5dac37/nft?chain=POLYGON&format=decimal"; -const NFT_HISTORY_URL_TEST: &str = "https://moralis-proxy.komodo.earth/api/v2/0x394d86994f954ed931b86791b62fe64f4c5dac37/nft/transfers?chain=POLYGON&format=decimal"; -const NFT_METADATA_URL_TEST: &str = "https://moralis-proxy.komodo.earth/api/v2/nft/0xed55e4477b795eaa9bb4bca24df42214e1a05c18/1111777?chain=POLYGON&format=decimal"; +use crate::eth::eth_addr_to_hex; +use crate::nft::nft_structs::{Chain, NftFromMoralis, NftListFilters, NftTransferHistoryFilters, + NftTransferHistoryFromMoralis, PhishingDomainReq, PhishingDomainRes, SpamContractReq, + SpamContractRes, TransferMeta, UriMeta}; +use crate::nft::storage::db_test_helpers::{init_nft_history_storage, init_nft_list_storage, nft, nft_list, + nft_transfer_history}; +use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps, RemoveNftResult}; +use crate::nft::{check_moralis_ipfs_bafy, get_domain_from_url, process_metadata_for_spam_link, + process_text_for_spam_link}; +use common::cross_test; +use ethereum_types::Address; +use mm2_net::transport::send_post_request_to_uri; +use mm2_number::BigDecimal; +use std::num::NonZeroUsize; +use std::str::FromStr; + +const MORALIS_API_ENDPOINT_TEST: &str = "https://moralis-proxy.komodo.earth/api/v2"; const TEST_WALLET_ADDR_EVM: &str = "0x394d86994f954ed931b86791b62fe64f4c5dac37"; +const BLOCKLIST_API_ENDPOINT: &str = "https://nft.antispam.dragonhound.info"; +const TOKEN_ADD: &str = "0xfd913a305d70a60aac4faac70c739563738e1f81"; +const TOKEN_ID: &str = "214300044414"; +const TX_HASH: &str = "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe"; +const LOG_INDEX: u32 = 495; -#[cfg(all(test, not(target_arch = "wasm32")))] -mod native_tests { - use crate::eth::eth_addr_to_hex; - use crate::nft::nft_structs::{NftFromMoralis, NftTransferHistoryFromMoralis, UriMeta}; - use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; - use crate::nft::storage::db_test_helpers::*; - use crate::nft::{check_and_redact_if_spam, check_moralis_ipfs_bafy, check_nft_metadata_for_spam, - send_request_to_uri}; - use common::block_on; - - #[test] - fn test_moralis_ipfs_bafy() { - let uri = - "https://ipfs.moralis.io:2053/ipfs/bafybeifnek24coy5xj5qabdwh24dlp5omq34nzgvazkfyxgnqms4eidsiq/1.json"; - let res_uri = check_moralis_ipfs_bafy(Some(uri)); - let expected = "https://ipfs.io/ipfs/bafybeifnek24coy5xj5qabdwh24dlp5omq34nzgvazkfyxgnqms4eidsiq/1.json"; - assert_eq!(expected, res_uri.unwrap()); - } +#[cfg(not(target_arch = "wasm32"))] +use mm2_net::native_http::send_request_to_uri; - #[test] - fn test_invalid_moralis_ipfs_link() { - let uri = "example.com/bafy?1=ipfs.moralis.io&e=https://"; - let res_uri = check_moralis_ipfs_bafy(Some(uri)); - assert_eq!(uri, res_uri.unwrap()); - } +common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + use mm2_net::wasm_http::send_request_to_uri; +} - #[test] - fn test_check_for_spam() { - let mut spam_text = Some("https://arweave.net".to_string()); - assert!(check_and_redact_if_spam(&mut spam_text).unwrap()); - let url_redacted = "URL redacted for user protection"; - assert_eq!(url_redacted, spam_text.unwrap()); - - let mut spam_text = Some("ftp://123path ".to_string()); - assert!(check_and_redact_if_spam(&mut spam_text).unwrap()); - let url_redacted = "URL redacted for user protection"; - assert_eq!(url_redacted, spam_text.unwrap()); - - let mut spam_text = Some("/192.168.1.1/some.example.org?type=A".to_string()); - assert!(check_and_redact_if_spam(&mut spam_text).unwrap()); - let url_redacted = "URL redacted for user protection"; - assert_eq!(url_redacted, spam_text.unwrap()); - - let mut spam_text = Some(r"C:\Users\path\".to_string()); - assert!(check_and_redact_if_spam(&mut spam_text).unwrap()); - let url_redacted = "URL redacted for user protection"; - assert_eq!(url_redacted, spam_text.unwrap()); - - let mut valid_text = Some("Hello my name is NFT (The best ever!)".to_string()); - assert!(!check_and_redact_if_spam(&mut valid_text).unwrap()); - assert_eq!("Hello my name is NFT (The best ever!)", valid_text.unwrap()); - - let mut nft = nft(); - assert!(check_nft_metadata_for_spam(&mut nft).unwrap()); - let meta_redacted = "{\"name\":\"URL redacted for user protection\",\"image\":\"https://tikimetadata.s3.amazonaws.com/tiki_box.png\"}"; - assert_eq!(meta_redacted, nft.common.metadata.unwrap()) +cross_test!(test_moralis_ipfs_bafy, { + let uri = "https://ipfs.moralis.io:2053/ipfs/bafybeifnek24coy5xj5qabdwh24dlp5omq34nzgvazkfyxgnqms4eidsiq/1.json"; + let res_uri = check_moralis_ipfs_bafy(Some(uri)); + let expected = "https://ipfs.io/ipfs/bafybeifnek24coy5xj5qabdwh24dlp5omq34nzgvazkfyxgnqms4eidsiq/1.json"; + assert_eq!(expected, res_uri.unwrap()); +}); + +cross_test!(test_get_domain_from_url, { + let image_url = "https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png"; + let res_domain = get_domain_from_url(Some(image_url)); + let expected = "public.nftstatic.com"; + assert_eq!(expected, res_domain.unwrap()); +}); + +cross_test!(test_invalid_moralis_ipfs_link, { + let uri = "example.com/bafy?1=ipfs.moralis.io&e=https://"; + let res_uri = check_moralis_ipfs_bafy(Some(uri)); + assert_eq!(uri, res_uri.unwrap()); +}); + +cross_test!(test_check_for_spam_links, { + let mut spam_text = Some("https://arweave.net".to_string()); + assert!(process_text_for_spam_link(&mut spam_text, true).unwrap()); + let url_redacted = "URL redacted for user protection"; + assert_eq!(url_redacted, spam_text.unwrap()); + + let mut spam_text = Some("ftp://123path ".to_string()); + assert!(process_text_for_spam_link(&mut spam_text, true).unwrap()); + let url_redacted = "URL redacted for user protection"; + assert_eq!(url_redacted, spam_text.unwrap()); + + let mut spam_text = Some("/192.168.1.1/some.example.org?type=A".to_string()); + assert!(process_text_for_spam_link(&mut spam_text, true).unwrap()); + let url_redacted = "URL redacted for user protection"; + assert_eq!(url_redacted, spam_text.unwrap()); + + let mut spam_text = Some(r"C:\Users\path\".to_string()); + assert!(process_text_for_spam_link(&mut spam_text, true).unwrap()); + let url_redacted = "URL redacted for user protection"; + assert_eq!(url_redacted, spam_text.unwrap()); + + let mut valid_text = Some("Hello my name is NFT (The best ever!)".to_string()); + assert!(!process_text_for_spam_link(&mut valid_text, true).unwrap()); + assert_eq!("Hello my name is NFT (The best ever!)", valid_text.unwrap()); + + let mut nft = nft(); + assert!(process_metadata_for_spam_link(&mut nft, true).unwrap()); + let meta_redacted = "{\"name\":\"URL redacted for user protection\",\"image\":\"https://tikimetadata.s3.amazonaws.com/tiki_box.png\"}"; + assert_eq!(meta_redacted, nft.common.metadata.unwrap()) +}); + +cross_test!(test_moralis_requests, { + let uri_nft_list = format!( + "{}/{}/nft?chain=POLYGON&format=decimal", + MORALIS_API_ENDPOINT_TEST, TEST_WALLET_ADDR_EVM + ); + let response_nft_list = send_request_to_uri(uri_nft_list.as_str()).await.unwrap(); + let nfts_list = response_nft_list["result"].as_array().unwrap(); + for nft_json in nfts_list { + let nft_moralis: NftFromMoralis = serde_json::from_str(&nft_json.to_string()).unwrap(); + assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); } - #[test] - fn test_moralis_requests() { - let response_nft_list = block_on(send_request_to_uri(NFT_LIST_URL_TEST)).unwrap(); - let nfts_list = response_nft_list["result"].as_array().unwrap(); - for nft_json in nfts_list { - let nft_moralis: NftFromMoralis = serde_json::from_str(&nft_json.to_string()).unwrap(); - assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); - } - - let response_transfer_history = block_on(send_request_to_uri(NFT_HISTORY_URL_TEST)).unwrap(); - let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); - assert!(!transfer_list.is_empty()); - let first_transfer = transfer_list.remove(transfer_list.len() - 1); - let transfer_moralis: NftTransferHistoryFromMoralis = - serde_json::from_str(&first_transfer.to_string()).unwrap(); - assert_eq!( - TEST_WALLET_ADDR_EVM, - eth_addr_to_hex(&transfer_moralis.common.to_address) - ); - - let response_meta = block_on(send_request_to_uri(NFT_METADATA_URL_TEST)).unwrap(); - let nft_moralis: NftFromMoralis = serde_json::from_str(&response_meta.to_string()).unwrap(); - assert_eq!(41237364, *nft_moralis.block_number_minted.unwrap()); - let token_uri = nft_moralis.common.token_uri.unwrap(); - let uri_response = block_on(send_request_to_uri(token_uri.as_str())).unwrap(); - serde_json::from_str::(&uri_response.to_string()).unwrap(); + let uri_history = format!( + "{}/{}/nft/transfers?chain=POLYGON&format=decimal", + MORALIS_API_ENDPOINT_TEST, TEST_WALLET_ADDR_EVM + ); + let response_transfer_history = send_request_to_uri(uri_history.as_str()).await.unwrap(); + let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); + assert!(!transfer_list.is_empty()); + let first_transfer = transfer_list.remove(transfer_list.len() - 1); + let transfer_moralis: NftTransferHistoryFromMoralis = serde_json::from_str(&first_transfer.to_string()).unwrap(); + assert_eq!( + TEST_WALLET_ADDR_EVM, + eth_addr_to_hex(&transfer_moralis.common.to_address) + ); + + let uri_meta = format!( + "{}/nft/0xed55e4477b795eaa9bb4bca24df42214e1a05c18/1111777?chain=POLYGON&format=decimal", + MORALIS_API_ENDPOINT_TEST + ); + let response_meta = send_request_to_uri(uri_meta.as_str()).await.unwrap(); + let nft_moralis: NftFromMoralis = serde_json::from_str(&response_meta.to_string()).unwrap(); + assert_eq!(41237364, *nft_moralis.block_number_minted.unwrap()); +}); + +cross_test!(test_antispam_scan_endpoints, { + let req_spam = SpamContractReq { + network: Chain::Eth, + addresses: "0x0ded8542fc8b2b4e781b96e99fee6406550c9b7c,0x8d1355b65da254f2cc4611453adfa8b7a13f60ee".to_string(), + }; + let uri_contract = format!("{}/api/blocklist/contract/scan", BLOCKLIST_API_ENDPOINT); + let req_json = serde_json::to_string(&req_spam).unwrap(); + let contract_scan_res = send_post_request_to_uri(uri_contract.as_str(), req_json).await.unwrap(); + let spam_res: SpamContractRes = serde_json::from_slice(&contract_scan_res).unwrap(); + assert!(spam_res + .result + .get(&Address::from_str("0x0ded8542fc8b2b4e781b96e99fee6406550c9b7c").unwrap()) + .unwrap()); + assert!(spam_res + .result + .get(&Address::from_str("0x8d1355b65da254f2cc4611453adfa8b7a13f60ee").unwrap()) + .unwrap()); + + let req_phishing = PhishingDomainReq { + domains: "disposal-account-case-1f677.web.app,defi8090.vip".to_string(), + }; + let req_json = serde_json::to_string(&req_phishing).unwrap(); + let uri_domain = format!("{}/api/blocklist/domain/scan", BLOCKLIST_API_ENDPOINT); + let domain_scan_res = send_post_request_to_uri(uri_domain.as_str(), req_json).await.unwrap(); + let phishing_res: PhishingDomainRes = serde_json::from_slice(&domain_scan_res).unwrap(); + assert!(phishing_res.result.get("disposal-account-case-1f677.web.app").unwrap()); +}); + +cross_test!(test_camo, { + let hex_token_uri = hex::encode("https://tikimetadata.s3.amazonaws.com/tiki_box.json"); + let uri_decode = format!("{}/url/decode/{}", BLOCKLIST_API_ENDPOINT, hex_token_uri); + let decode_res = send_request_to_uri(&uri_decode).await.unwrap(); + let uri_meta: UriMeta = serde_json::from_value(decode_res).unwrap(); + assert_eq!( + uri_meta.raw_image_url.unwrap(), + "https://tikimetadata.s3.amazonaws.com/tiki_box.png" + ); +}); + +cross_test!(test_add_get_nfts, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); + let nft = storage + .get_nft(&chain, TOKEN_ADD.to_string(), token_id) + .await + .unwrap() + .unwrap(); + assert_eq!(nft.block_number, 28056721); +}); + +cross_test!(test_last_nft_block, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let last_block = NftListStorageOps::get_last_block_number(&storage, &chain) + .await + .unwrap() + .unwrap(); + assert_eq!(last_block, 28056726); +}); + +cross_test!(test_nft_list, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let nft_list = storage + .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap()), None) + .await + .unwrap(); + assert_eq!(nft_list.nfts.len(), 1); + let nft = nft_list.nfts.get(0).unwrap(); + assert_eq!(nft.block_number, 28056721); + assert_eq!(nft_list.skipped, 2); + assert_eq!(nft_list.total, 4); +}); + +cross_test!(test_remove_nft, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); + let remove_rslt = storage + .remove_nft_from_list(&chain, TOKEN_ADD.to_string(), token_id, 28056800) + .await + .unwrap(); + assert_eq!(remove_rslt, RemoveNftResult::NftRemoved); + let list_len = storage + .get_nft_list(vec![chain], true, 1, None, None) + .await + .unwrap() + .nfts + .len(); + assert_eq!(list_len, 3); + let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); + assert_eq!(last_scanned_block, 28056800); +}); + +cross_test!(test_nft_amount, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let mut nft = nft(); + storage + .add_nfts_to_list(chain, vec![nft.clone()], 25919780) + .await + .unwrap(); + + nft.common.amount -= BigDecimal::from(1); + storage.update_nft_amount(&chain, nft.clone(), 25919800).await.unwrap(); + let amount = storage + .get_nft_amount( + &chain, + eth_addr_to_hex(&nft.common.token_address), + nft.common.token_id.clone(), + ) + .await + .unwrap() + .unwrap(); + assert_eq!(amount, "1"); + let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); + assert_eq!(last_scanned_block, 25919800); + + nft.common.amount += BigDecimal::from(1); + nft.block_number = 25919900; + storage + .update_nft_amount_and_block_number(&chain, nft.clone()) + .await + .unwrap(); + let amount = storage + .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.common.token_id) + .await + .unwrap() + .unwrap(); + assert_eq!(amount, "2"); + let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); + assert_eq!(last_scanned_block, 25919900); +}); + +cross_test!(test_refresh_metadata, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let new_symbol = "NEW_SYMBOL"; + let mut nft = nft(); + storage + .add_nfts_to_list(chain, vec![nft.clone()], 25919780) + .await + .unwrap(); + nft.common.symbol = Some(new_symbol.to_string()); + drop_mutability!(nft); + let token_add = eth_addr_to_hex(&nft.common.token_address); + let token_id = nft.common.token_id.clone(); + storage.refresh_nft_metadata(&chain, nft).await.unwrap(); + let nft_upd = storage.get_nft(&chain, token_add, token_id).await.unwrap().unwrap(); + assert_eq!(new_symbol.to_string(), nft_upd.common.symbol.unwrap()); +}); + +cross_test!(test_update_nft_spam_by_token_address, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + storage + .update_nft_spam_by_token_address(&chain, TOKEN_ADD.to_string(), true) + .await + .unwrap(); + let nfts = storage + .get_nfts_by_token_address(chain, TOKEN_ADD.to_string()) + .await + .unwrap(); + for nft in nfts { + assert!(nft.common.possible_spam); } - - #[test] - fn test_add_get_nfts() { block_on(test_add_get_nfts_impl()) } - - #[test] - fn test_last_nft_blocks() { block_on(test_last_nft_blocks_impl()) } - - #[test] - fn test_nft_list() { block_on(test_nft_list_impl()) } - - #[test] - fn test_remove_nft() { block_on(test_remove_nft_impl()) } - - #[test] - fn test_refresh_metadata() { block_on(test_refresh_metadata_impl()) } - - #[test] - fn test_nft_amount() { block_on(test_nft_amount_impl()) } - - #[test] - fn test_add_get_transfers() { block_on(test_add_get_transfers_impl()) } - - #[test] - fn test_last_transfer_block() { block_on(test_last_transfer_block_impl()) } - - #[test] - fn test_transfer_history() { block_on(test_transfer_history_impl()) } - - #[test] - fn test_transfer_history_filters() { block_on(test_transfer_history_filters_impl()) } - - #[test] - fn test_get_update_transfer_meta() { block_on(test_get_update_transfer_meta_impl()) } -} - -#[cfg(target_arch = "wasm32")] -mod wasm_tests { - use crate::eth::eth_addr_to_hex; - use crate::nft::nft_structs::{NftFromMoralis, NftTransferHistoryFromMoralis}; - use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; - use crate::nft::send_request_to_uri; - use crate::nft::storage::db_test_helpers::*; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - #[wasm_bindgen_test] - async fn test_moralis_requests() { - let response_nft_list = send_request_to_uri(NFT_LIST_URL_TEST).await.unwrap(); - let nfts_list = response_nft_list["result"].as_array().unwrap(); - for nft_json in nfts_list { - let nft_moralis: NftFromMoralis = serde_json::from_str(&nft_json.to_string()).unwrap(); - assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); - } - - let response_transfer_history = send_request_to_uri(NFT_HISTORY_URL_TEST).await.unwrap(); - let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); - assert!(!transfer_list.is_empty()); - let first_transfer = transfer_list.remove(transfer_list.len() - 1); - let transfer_moralis: NftTransferHistoryFromMoralis = - serde_json::from_str(&first_transfer.to_string()).unwrap(); - assert_eq!( - TEST_WALLET_ADDR_EVM, - eth_addr_to_hex(&transfer_moralis.common.to_address) - ); - - let response_meta = send_request_to_uri(NFT_METADATA_URL_TEST).await.unwrap(); - let nft_moralis: NftFromMoralis = serde_json::from_str(&response_meta.to_string()).unwrap(); - assert_eq!(41237364, *nft_moralis.block_number_minted.unwrap()); +}); + +cross_test!(test_exclude_nft_spam, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let filters = NftListFilters { + exclude_spam: true, + exclude_phishing: false, + }; + let nft_list = storage + .get_nft_list(vec![chain], true, 1, None, Some(filters)) + .await + .unwrap(); + assert_eq!(nft_list.nfts.len(), 3); +}); + +cross_test!(test_get_animation_external_domains, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let domains = storage.get_animation_external_domains(&chain).await.unwrap(); + assert_eq!(2, domains.len()); + assert!(domains.contains("tikimetadata.s3.amazonaws.com")); + assert!(domains.contains("public.nftstatic.com")); +}); + +cross_test!(test_update_nft_phishing_by_domain, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + let domains = vec![ + "tikimetadata.s3.amazonaws.com".to_string(), + "public.nftstatic.com".to_string(), + ]; + for domain in domains.into_iter() { + storage + .update_nft_phishing_by_domain(&chain, domain, true) + .await + .unwrap(); } - - #[wasm_bindgen_test] - async fn test_add_get_nfts() { test_add_get_nfts_impl().await } - - #[wasm_bindgen_test] - async fn test_last_nft_blocks() { test_last_nft_blocks_impl().await } - - #[wasm_bindgen_test] - async fn test_nft_list() { test_nft_list_impl().await } - - #[wasm_bindgen_test] - async fn test_remove_nft() { test_remove_nft_impl().await } - - #[wasm_bindgen_test] - async fn test_nft_amount() { test_nft_amount_impl().await } - - #[wasm_bindgen_test] - async fn test_refresh_metadata() { test_refresh_metadata_impl().await } - - #[wasm_bindgen_test] - async fn test_add_get_transfers() { test_add_get_transfers_impl().await } - - #[wasm_bindgen_test] - async fn test_last_transfer_block() { test_last_transfer_block_impl().await } - - #[wasm_bindgen_test] - async fn test_transfer_history() { test_transfer_history_impl().await } - - #[wasm_bindgen_test] - async fn test_transfer_history_filters() { test_transfer_history_filters_impl().await } - - #[wasm_bindgen_test] - async fn test_get_update_transfer_meta() { test_get_update_transfer_meta_impl().await } -} + let nfts = storage + .get_nft_list(vec![chain], true, 1, None, None) + .await + .unwrap() + .nfts; + for nft in nfts.into_iter() { + assert!(nft.possible_phishing); + } +}); + +cross_test!(test_exclude_nft_phishing_spam, { + let chain = Chain::Bsc; + let storage = init_nft_list_storage(&chain).await; + let nft_list = nft_list(); + storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + + storage + .update_nft_phishing_by_domain(&chain, "tikimetadata.s3.amazonaws.com".to_string(), true) + .await + .unwrap(); + let filters = NftListFilters { + exclude_spam: true, + exclude_phishing: true, + }; + let nfts = storage + .get_nft_list(vec![chain], true, 1, None, Some(filters)) + .await + .unwrap() + .nfts; + assert_eq!(nfts.len(), 2); +}); + +cross_test!(test_add_get_transfers, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); + let transfer1 = storage + .get_transfers_by_token_addr_id(chain, TOKEN_ADD.to_string(), token_id) + .await + .unwrap() + .get(0) + .unwrap() + .clone(); + assert_eq!(transfer1.block_number, 28056721); + let transfer2 = storage + .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) + .await + .unwrap() + .unwrap(); + assert_eq!(transfer2.block_number, 28056726); + let transfer_from = storage.get_transfers_from_block(chain, 28056721).await.unwrap(); + assert_eq!(transfer_from.len(), 3); +}); + +cross_test!(test_last_transfer_block, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let last_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, &chain) + .await + .unwrap() + .unwrap(); + assert_eq!(last_block, 28056726); +}); + +cross_test!(test_transfer_history, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let transfer_history = storage + .get_transfer_history(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap()), None) + .await + .unwrap(); + assert_eq!(transfer_history.transfer_history.len(), 1); + let transfer = transfer_history.transfer_history.get(0).unwrap(); + assert_eq!(transfer.block_number, 28056721); + assert_eq!(transfer_history.skipped, 2); + assert_eq!(transfer_history.total, 4); +}); + +cross_test!(test_transfer_history_filters, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let filters = NftTransferHistoryFilters { + receive: true, + send: false, + from_date: None, + to_date: None, + exclude_spam: false, + exclude_phishing: false, + }; + + let filters1 = NftTransferHistoryFilters { + receive: false, + send: false, + from_date: None, + to_date: Some(1677166110), + exclude_spam: false, + exclude_phishing: false, + }; + + let filters2 = NftTransferHistoryFilters { + receive: false, + send: false, + from_date: Some(1677166110), + to_date: Some(1683627417), + exclude_spam: false, + exclude_phishing: false, + }; + + let transfer_history = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters)) + .await + .unwrap(); + assert_eq!(transfer_history.transfer_history.len(), 4); + let transfer = transfer_history.transfer_history.get(0).unwrap(); + assert_eq!(transfer.block_number, 28056726); + + let transfer_history1 = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters1)) + .await + .unwrap(); + assert_eq!(transfer_history1.transfer_history.len(), 1); + let transfer1 = transfer_history1.transfer_history.get(0).unwrap(); + assert_eq!(transfer1.block_number, 25919780); + + let transfer_history2 = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters2)) + .await + .unwrap(); + assert_eq!(transfer_history2.transfer_history.len(), 2); + let transfer_0 = transfer_history2.transfer_history.get(0).unwrap(); + assert_eq!(transfer_0.block_number, 28056721); + let transfer_1 = transfer_history2.transfer_history.get(1).unwrap(); + assert_eq!(transfer_1.block_number, 25919780); +}); + +cross_test!(test_get_update_transfer_meta, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let vec_token_add_id = storage.get_transfers_with_empty_meta(chain).await.unwrap(); + assert_eq!(vec_token_add_id.len(), 3); + + let token_add = "0x5c7d6712dfaf0cb079d48981781c8705e8417ca0".to_string(); + let transfer_meta = TransferMeta { + token_address: token_add.clone(), + token_id: Default::default(), + token_uri: None, + token_domain: None, + collection_name: None, + image_url: None, + image_domain: None, + token_name: Some("Tiki box".to_string()), + }; + storage + .update_transfers_meta_by_token_addr_id(&chain, transfer_meta, true) + .await + .unwrap(); + let transfer_upd = storage + .get_transfers_by_token_addr_id(chain, token_add, Default::default()) + .await + .unwrap(); + let transfer_upd = transfer_upd.get(0).unwrap(); + assert_eq!(transfer_upd.token_name, Some("Tiki box".to_string())); + assert!(transfer_upd.common.possible_spam); +}); + +cross_test!(test_update_transfer_spam_by_token_address, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + storage + .update_transfer_spam_by_token_address(&chain, TOKEN_ADD.to_string(), true) + .await + .unwrap(); + let transfers = storage + .get_transfers_by_token_address(chain, TOKEN_ADD.to_string()) + .await + .unwrap(); + for transfers in transfers { + assert!(transfers.common.possible_spam); + } +}); + +cross_test!(test_get_token_addresses, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let token_addresses = storage.get_token_addresses(chain).await.unwrap(); + assert_eq!(token_addresses.len(), 2); +}); + +cross_test!(test_exclude_transfer_spam, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let filters = NftTransferHistoryFilters { + receive: true, + send: true, + from_date: None, + to_date: None, + exclude_spam: true, + exclude_phishing: false, + }; + let transfer_history = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters)) + .await + .unwrap(); + assert_eq!(transfer_history.transfer_history.len(), 3); +}); + +cross_test!(test_get_domains, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let domains = storage.get_domains(&chain).await.unwrap(); + assert_eq!(2, domains.len()); + assert!(domains.contains("tikimetadata.s3.amazonaws.com")); + assert!(domains.contains("public.nftstatic.com")); +}); + +cross_test!(test_update_transfer_phishing_by_domain, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + let domains = vec![ + "tikimetadata.s3.amazonaws.com".to_string(), + "public.nftstatic.com".to_string(), + ]; + for domain in domains.into_iter() { + storage + .update_transfer_phishing_by_domain(&chain, domain, true) + .await + .unwrap(); + } + let transfers = storage + .get_transfer_history(vec![chain], true, 1, None, None) + .await + .unwrap() + .transfer_history; + for transfer in transfers.into_iter() { + assert!(transfer.possible_phishing); + } +}); + +cross_test!(test_exclude_transfer_phishing_spam, { + let chain = Chain::Bsc; + let storage = init_nft_history_storage(&chain).await; + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(chain, transfers).await.unwrap(); + + storage + .update_transfer_phishing_by_domain(&chain, "tikimetadata.s3.amazonaws.com".to_string(), true) + .await + .unwrap(); + let filters = NftTransferHistoryFilters { + receive: true, + send: true, + from_date: None, + to_date: None, + exclude_spam: false, + exclude_phishing: true, + }; + let transfers = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters)) + .await + .unwrap() + .transfer_history; + assert_eq!(transfers.len(), 2); + + let filters1 = NftTransferHistoryFilters { + receive: true, + send: true, + from_date: None, + to_date: None, + exclude_spam: true, + exclude_phishing: true, + }; + let transfers = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters1)) + .await + .unwrap() + .transfer_history; + assert_eq!(transfers.len(), 1); +}); diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs index 7961d4c1ea..9d4e8bfe14 100644 --- a/mm2src/coins/nft/storage/db_test_helpers.rs +++ b/mm2src/coins/nft/storage/db_test_helpers.rs @@ -1,24 +1,11 @@ -use crate::eth::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftTransferCommon, NftTransferHistory, - NftTransferHistoryFilters, TransferMeta, TransferStatus, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps, RemoveNftResult}; + TransferStatus, UriMeta}; +use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; use ethereum_types::Address; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::mm_ctx_with_custom_db; -use std::num::NonZeroUsize; use std::str::FromStr; -cfg_wasm32! { - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); -} - -const TOKEN_ADD: &str = "0xfd913a305d70a60aac4faac70c739563738e1f81"; -const TOKEN_ID: &str = "214300044414"; -const TX_HASH: &str = "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe"; -const LOG_INDEX: u32 = 495; - pub(crate) fn nft() -> Nft { Nft { common: NftCommon { @@ -30,6 +17,7 @@ pub(crate) fn nft() -> Nft { collection_name: None, symbol: None, token_uri: Some("https://tikimetadata.s3.amazonaws.com/tiki_box.json".to_string()), + token_domain: None, metadata: Some( "{\"name\":\"https://arweave.net\",\"image\":\"https://tikimetadata.s3.amazonaws.com/tiki_box.png\"}" .to_string(), @@ -37,13 +25,13 @@ pub(crate) fn nft() -> Nft { last_token_uri_sync: Some("2023-02-07T17:10:08.402Z".to_string()), last_metadata_sync: Some("2023-02-07T17:10:16.858Z".to_string()), minter_address: Some("ERC1155 tokens don't have a single minter".to_string()), - possible_spam: false, + possible_spam: true, }, chain: Chain::Bsc, block_number_minted: Some(25465916), block_number: 25919780, contract_type: ContractType::Erc1155, - + possible_phishing: false, uri_meta: UriMeta { image_url: Some("https://tikimetadata.s3.amazonaws.com/tiki_box.png".to_string()), raw_image_url: Some("https://tikimetadata.s3.amazonaws.com/tiki_box.png".to_string()), @@ -51,43 +39,16 @@ pub(crate) fn nft() -> Nft { description: Some("Born to usher in Bull markets.".to_string()), attributes: None, animation_url: None, + animation_domain: None, external_url: None, + external_domain: None, image_details: None, + image_domain: Some("tikimetadata.s3.amazonaws.com".to_string()), }, } } -fn transfer() -> NftTransferHistory { - NftTransferHistory { - common: NftTransferCommon { - block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), - transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), - transaction_index: Some(198), - log_index: 495, - value: Default::default(), - transaction_type: Some("Single".to_string()), - token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300047252").unwrap(), - from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), - to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), - amount: BigDecimal::from_str("1").unwrap(), - verified: Some(1), - operator: None, - possible_spam: false, - }, - chain: Chain::Bsc, - block_number: 28056726, - block_timestamp: 1683627432, - contract_type: ContractType::Erc721, - token_uri: None, - collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), - image_url: Some("https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string()), - token_name: Some("Nebula Nodes".to_string()), - status: TransferStatus::Receive, - } -} - -fn nft_list() -> Vec { +pub(crate) fn nft_list() -> Vec { let nft = Nft { common: NftCommon { token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(), @@ -98,6 +59,7 @@ fn nft_list() -> Vec { collection_name: None, symbol: None, token_uri: Some("https://tikimetadata.s3.amazonaws.com/tiki_box.json".to_string()), + token_domain: None, metadata: Some("{\"name\":\"Tiki box\"}".to_string()), last_token_uri_sync: Some("2023-02-07T17:10:08.402Z".to_string()), last_metadata_sync: Some("2023-02-07T17:10:16.858Z".to_string()), @@ -108,6 +70,7 @@ fn nft_list() -> Vec { block_number_minted: Some(25465916), block_number: 25919780, contract_type: ContractType::Erc1155, + possible_phishing: false, uri_meta: UriMeta { image_url: Some("https://tikimetadata.s3.amazonaws.com/tiki_box.png".to_string()), raw_image_url: None, @@ -115,8 +78,11 @@ fn nft_list() -> Vec { description: Some("Born to usher in Bull markets.".to_string()), attributes: None, animation_url: None, + animation_domain: Some("tikimetadata.s3.amazonaws.com".to_string()), external_url: None, + external_domain: None, image_details: None, + image_domain: None, }, }; @@ -130,6 +96,7 @@ fn nft_list() -> Vec { collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), symbol: Some("BMBBBF".to_string()), token_uri: Some("https://public.nftstatic.com/static/nft/BSC/BMBBBF/214300047252".to_string()), + token_domain: Some("public.nftstatic.com".to_string()), metadata: Some( "{\"image\":\"https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png\"}" .to_string(), @@ -137,13 +104,13 @@ fn nft_list() -> Vec { last_token_uri_sync: Some("2023-02-16T16:35:52.392Z".to_string()), last_metadata_sync: Some("2023-02-16T16:36:04.283Z".to_string()), minter_address: Some("0xdbdeb0895f3681b87fb3654b5cf3e05546ba24a9".to_string()), - possible_spam: false, + possible_spam: true, }, chain: Chain::Bsc, - block_number_minted: Some(25721963), block_number: 28056726, contract_type: ContractType::Erc721, + possible_phishing: false, uri_meta: UriMeta { image_url: Some( "https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string(), @@ -153,8 +120,11 @@ fn nft_list() -> Vec { description: Some("Interchain nodes".to_string()), attributes: None, animation_url: None, + animation_domain: None, external_url: None, + external_domain: None, image_details: None, + image_domain: None, }, }; @@ -168,6 +138,7 @@ fn nft_list() -> Vec { collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), symbol: Some("BMBBBF".to_string()), token_uri: Some("https://public.nftstatic.com/static/nft/BSC/BMBBBF/214300047252".to_string()), + token_domain: None, metadata: Some( "{\"image\":\"https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png\"}" .to_string(), @@ -178,10 +149,10 @@ fn nft_list() -> Vec { possible_spam: false, }, chain: Chain::Bsc, - block_number_minted: Some(25721963), block_number: 28056726, contract_type: ContractType::Erc721, + possible_phishing: false, uri_meta: UriMeta { image_url: Some( "https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string(), @@ -191,8 +162,11 @@ fn nft_list() -> Vec { description: Some("Interchain nodes".to_string()), attributes: None, animation_url: None, + animation_domain: None, external_url: None, + external_domain: None, image_details: None, + image_domain: Some("public.nftstatic.com".to_string()), }, }; @@ -206,6 +180,7 @@ fn nft_list() -> Vec { collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), symbol: Some("BMBBBF".to_string()), token_uri: Some("https://public.nftstatic.com/static/nft/BSC/BMBBBF/214300044414".to_string()), + token_domain: None, metadata: Some( "{\"image\":\"https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png\"}" .to_string(), @@ -216,10 +191,10 @@ fn nft_list() -> Vec { possible_spam: false, }, chain: Chain::Bsc, - block_number_minted: Some(25810308), block_number: 28056721, contract_type: ContractType::Erc721, + possible_phishing: false, uri_meta: UriMeta { image_url: Some( "https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string(), @@ -229,14 +204,17 @@ fn nft_list() -> Vec { description: Some("Interchain nodes".to_string()), attributes: None, animation_url: None, + animation_domain: None, external_url: None, + external_domain: Some("public.nftstatic.com".to_string()), image_details: None, + image_domain: None, }, }; vec![nft, nft1, nft2, nft3] } -fn nft_transfer_history() -> Vec { +pub(crate) fn nft_transfer_history() -> Vec { let transfer = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0xcb41654fc5cf2bf5d7fd3f061693405c74d419def80993caded0551ecfaeaae5".to_string()), @@ -259,10 +237,13 @@ fn nft_transfer_history() -> Vec { block_timestamp: 1677166110, contract_type: ContractType::Erc1155, token_uri: None, + token_domain: Some("tikimetadata.s3.amazonaws.com".to_string()), collection_name: None, image_url: None, + image_domain: None, token_name: None, status: TransferStatus::Receive, + possible_phishing: false, }; let transfer1 = NftTransferHistory { @@ -280,19 +261,20 @@ fn nft_transfer_history() -> Vec { amount: BigDecimal::from_str("1").unwrap(), verified: Some(1), operator: None, - possible_spam: false, + possible_spam: true, }, chain: Chain::Bsc, block_number: 28056726, block_timestamp: 1683627432, contract_type: ContractType::Erc721, - token_uri: None, + token_domain: Some("public.nftstatic.com".to_string()), collection_name: None, image_url: None, + image_domain: None, token_name: None, - status: TransferStatus::Receive, + possible_phishing: false, }; // Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction @@ -317,13 +299,14 @@ fn nft_transfer_history() -> Vec { block_number: 28056726, block_timestamp: 1683627432, contract_type: ContractType::Erc721, - token_uri: None, + token_domain: None, collection_name: None, image_url: None, + image_domain: Some("public.nftstatic.com".to_string()), token_name: None, - status: TransferStatus::Receive, + possible_phishing: false, }; let transfer3 = NftTransferHistory { @@ -346,20 +329,20 @@ fn nft_transfer_history() -> Vec { chain: Chain::Bsc, block_number: 28056721, block_timestamp: 1683627417, - contract_type: ContractType::Erc721, - token_uri: None, + token_domain: None, collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), image_url: Some("https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string()), + image_domain: Some("tikimetadata.s3.amazonaws.com".to_string()), token_name: Some("Nebula Nodes".to_string()), - status: TransferStatus::Receive, + possible_phishing: false, }; vec![transfer, transfer1, transfer2, transfer3] } -async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { +pub(crate) async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { let ctx = mm_ctx_with_custom_db(); let storage = NftStorageBuilder::new(&ctx).build().unwrap(); NftListStorageOps::init(&storage, chain).await.unwrap(); @@ -368,7 +351,7 @@ async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTra storage } -async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { +pub(crate) async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { let ctx = mm_ctx_with_custom_db(); let storage = NftStorageBuilder::new(&ctx).build().unwrap(); NftTransferHistoryStorageOps::init(&storage, chain).await.unwrap(); @@ -378,282 +361,3 @@ async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + Nft assert!(is_initialized); storage } - -pub(crate) async fn test_add_get_nfts_impl() { - let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; - let nft_list = nft_list(); - storage.add_nfts_to_list(&chain, nft_list, 28056726).await.unwrap(); - - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); - let nft = storage - .get_nft(&chain, TOKEN_ADD.to_string(), token_id) - .await - .unwrap() - .unwrap(); - assert_eq!(nft.block_number, 28056721); -} - -pub(crate) async fn test_last_nft_blocks_impl() { - let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; - let nft_list = nft_list(); - storage.add_nfts_to_list(&chain, nft_list, 28056726).await.unwrap(); - - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); - let nft = storage - .get_nft(&chain, TOKEN_ADD.to_string(), token_id) - .await - .unwrap() - .unwrap(); - assert_eq!(nft.block_number, 28056721); -} - -pub(crate) async fn test_nft_list_impl() { - let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; - let nft_list = nft_list(); - storage.add_nfts_to_list(&chain, nft_list, 28056726).await.unwrap(); - - let nft_list = storage - .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap())) - .await - .unwrap(); - assert_eq!(nft_list.nfts.len(), 1); - let nft = nft_list.nfts.get(0).unwrap(); - assert_eq!(nft.block_number, 28056721); - assert_eq!(nft_list.skipped, 2); - assert_eq!(nft_list.total, 4); -} - -pub(crate) async fn test_remove_nft_impl() { - let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; - let nft_list = nft_list(); - storage.add_nfts_to_list(&chain, nft_list, 28056726).await.unwrap(); - - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); - let remove_rslt = storage - .remove_nft_from_list(&chain, TOKEN_ADD.to_string(), token_id, 28056800) - .await - .unwrap(); - assert_eq!(remove_rslt, RemoveNftResult::NftRemoved); - let list_len = storage - .get_nft_list(vec![chain], true, 1, None) - .await - .unwrap() - .nfts - .len(); - assert_eq!(list_len, 3); - let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); - assert_eq!(last_scanned_block, 28056800); -} - -pub(crate) async fn test_nft_amount_impl() { - let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; - let mut nft = nft(); - storage - .add_nfts_to_list(&chain, vec![nft.clone()], 25919780) - .await - .unwrap(); - - nft.common.amount -= BigDecimal::from(1); - storage.update_nft_amount(&chain, nft.clone(), 25919800).await.unwrap(); - let amount = storage - .get_nft_amount( - &chain, - eth_addr_to_hex(&nft.common.token_address), - nft.common.token_id.clone(), - ) - .await - .unwrap() - .unwrap(); - assert_eq!(amount, "1"); - let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); - assert_eq!(last_scanned_block, 25919800); - - nft.common.amount += BigDecimal::from(1); - nft.block_number = 25919900; - storage - .update_nft_amount_and_block_number(&chain, nft.clone()) - .await - .unwrap(); - let amount = storage - .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.common.token_id) - .await - .unwrap() - .unwrap(); - assert_eq!(amount, "2"); - let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); - assert_eq!(last_scanned_block, 25919900); -} - -pub(crate) async fn test_refresh_metadata_impl() { - let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; - let new_symbol = "NEW_SYMBOL"; - let mut nft = nft(); - storage - .add_nfts_to_list(&chain, vec![nft.clone()], 25919780) - .await - .unwrap(); - nft.common.symbol = Some(new_symbol.to_string()); - drop_mutability!(nft); - let token_add = eth_addr_to_hex(&nft.common.token_address); - let token_id = nft.common.token_id.clone(); - storage.refresh_nft_metadata(&chain, nft).await.unwrap(); - let nft_upd = storage.get_nft(&chain, token_add, token_id).await.unwrap().unwrap(); - assert_eq!(new_symbol.to_string(), nft_upd.common.symbol.unwrap()); -} - -pub(crate) async fn test_add_get_transfers_impl() { - let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; - let transfers = nft_transfer_history(); - storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); - let transfer1 = storage - .get_transfers_by_token_addr_id(&chain, TOKEN_ADD.to_string(), token_id) - .await - .unwrap() - .get(0) - .unwrap() - .clone(); - assert_eq!(transfer1.block_number, 28056721); - let transfer2 = storage - .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) - .await - .unwrap() - .unwrap(); - assert_eq!(transfer2.block_number, 28056726); - let transfer_from = storage.get_transfers_from_block(&chain, 28056721).await.unwrap(); - assert_eq!(transfer_from.len(), 3); -} - -pub(crate) async fn test_last_transfer_block_impl() { - let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; - let transfers = nft_transfer_history(); - storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - - let last_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, &chain) - .await - .unwrap() - .unwrap(); - assert_eq!(last_block, 28056726); -} - -pub(crate) async fn test_transfer_history_impl() { - let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; - let transfers = nft_transfer_history(); - storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - - let transfer_history = storage - .get_transfer_history(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap()), None) - .await - .unwrap(); - assert_eq!(transfer_history.transfer_history.len(), 1); - let transfer = transfer_history.transfer_history.get(0).unwrap(); - assert_eq!(transfer.block_number, 28056721); - assert_eq!(transfer_history.skipped, 2); - assert_eq!(transfer_history.total, 4); -} - -pub(crate) async fn test_transfer_history_filters_impl() { - let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; - let transfers = nft_transfer_history(); - storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - - let filters = NftTransferHistoryFilters { - receive: true, - send: false, - from_date: None, - to_date: None, - }; - - let filters1 = NftTransferHistoryFilters { - receive: false, - send: false, - from_date: None, - to_date: Some(1677166110), - }; - - let filters2 = NftTransferHistoryFilters { - receive: false, - send: false, - from_date: Some(1677166110), - to_date: Some(1683627417), - }; - - let transfer_history = storage - .get_transfer_history(vec![chain], true, 1, None, Some(filters)) - .await - .unwrap(); - assert_eq!(transfer_history.transfer_history.len(), 4); - let transfer = transfer_history.transfer_history.get(0).unwrap(); - assert_eq!(transfer.block_number, 28056726); - - let transfer_history1 = storage - .get_transfer_history(vec![chain], true, 1, None, Some(filters1)) - .await - .unwrap(); - assert_eq!(transfer_history1.transfer_history.len(), 1); - let transfer1 = transfer_history1.transfer_history.get(0).unwrap(); - assert_eq!(transfer1.block_number, 25919780); - - let transfer_history2 = storage - .get_transfer_history(vec![chain], true, 1, None, Some(filters2)) - .await - .unwrap(); - assert_eq!(transfer_history2.transfer_history.len(), 2); - let transfer_0 = transfer_history2.transfer_history.get(0).unwrap(); - assert_eq!(transfer_0.block_number, 28056721); - let transfer_1 = transfer_history2.transfer_history.get(1).unwrap(); - assert_eq!(transfer_1.block_number, 25919780); -} - -pub(crate) async fn test_get_update_transfer_meta_impl() { - let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; - let transfers = nft_transfer_history(); - storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - - let vec_token_add_id = storage.get_transfers_with_empty_meta(&chain).await.unwrap(); - assert_eq!(vec_token_add_id.len(), 3); - - let token_add = "0x5c7d6712dfaf0cb079d48981781c8705e8417ca0".to_string(); - let transfer_meta = TransferMeta { - token_address: token_add.clone(), - token_id: Default::default(), - token_uri: None, - collection_name: None, - image_url: None, - token_name: Some("Tiki box".to_string()), - }; - storage - .update_transfers_meta_by_token_addr_id(&chain, transfer_meta) - .await - .unwrap(); - let transfer_upd = storage - .get_transfers_by_token_addr_id(&chain, token_add, Default::default()) - .await - .unwrap(); - let transfer_upd = transfer_upd.get(0).unwrap(); - assert_eq!(transfer_upd.token_name, Some("Tiki box".to_string())); - - let transfer_meta = transfer(); - storage - .update_transfer_meta_by_hash_and_log_index(&chain, transfer_meta) - .await - .unwrap(); - let transfer_by_hash = storage - .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) - .await - .unwrap() - .unwrap(); - assert_eq!(transfer_by_hash.token_name, Some("Nebula Nodes".to_string())) -} diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs index 0a2e906ccc..14cc9243f0 100644 --- a/mm2src/coins/nft/storage/mod.rs +++ b/mm2src/coins/nft/storage/mod.rs @@ -1,13 +1,15 @@ -use crate::nft::nft_structs::{Chain, Nft, NftList, NftTokenAddrId, NftTransferHistory, NftTransferHistoryFilters, - NftsTransferHistoryList, TransferMeta}; +use crate::nft::nft_structs::{Chain, Nft, NftList, NftListFilters, NftTokenAddrId, NftTransferHistory, + NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta}; use crate::WithdrawError; use async_trait::async_trait; use derive_more::Display; +use ethereum_types::Address; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::mm_error::MmResult; use mm2_err_handle::mm_error::{NotEqual, NotMmError}; use mm2_number::BigDecimal; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; use std::num::NonZeroUsize; #[cfg(any(test, target_arch = "wasm32"))] @@ -15,23 +17,28 @@ pub(crate) mod db_test_helpers; #[cfg(not(target_arch = "wasm32"))] pub(crate) mod sql_storage; #[cfg(target_arch = "wasm32")] pub(crate) mod wasm; +/// Represents the outcome of an attempt to remove an NFT. #[derive(Debug, PartialEq)] pub enum RemoveNftResult { + /// Indicates that the NFT was successfully removed. NftRemoved, + /// Indicates that the NFT did not exist in the storage. NftDidNotExist, } +/// Defines the standard errors that can occur in NFT storage operations pub trait NftStorageError: std::fmt::Debug + NotMmError + NotEqual + Send {} impl From for WithdrawError { fn from(err: T) -> Self { WithdrawError::DbError(format!("{:?}", err)) } } +/// Provides asynchronous operations for handling and querying NFT listings. #[async_trait] pub trait NftListStorageOps { type Error: NftStorageError; - /// Initializes tables in storage for the specified chain type. + /// Prepares the storage by initializing required tables for a specified chain type. async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error>; /// Whether tables are initialized for the specified chain. @@ -43,9 +50,10 @@ pub trait NftListStorageOps { max: bool, limit: usize, page_number: Option, + filters: Option, ) -> MmResult; - async fn add_nfts_to_list(&self, chain: &Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> + async fn add_nfts_to_list(&self, chain: Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send; @@ -85,13 +93,34 @@ pub trait NftListStorageOps { async fn update_nft_amount(&self, chain: &Chain, nft: Nft, scanned_block: u64) -> MmResult<(), Self::Error>; async fn update_nft_amount_and_block_number(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error>; + + /// `get_nfts_by_token_address` function returns list of NFTs which have specified token address. + async fn get_nfts_by_token_address(&self, chain: Chain, token_address: String) -> MmResult, Self::Error>; + + /// `update_nft_spam_by_token_address` function updates `possible_spam` field in NFTs which have specified token address. + async fn update_nft_spam_by_token_address( + &self, + chain: &Chain, + token_address: String, + possible_spam: bool, + ) -> MmResult<(), Self::Error>; + + async fn get_animation_external_domains(&self, chain: &Chain) -> MmResult, Self::Error>; + + async fn update_nft_phishing_by_domain( + &self, + chain: &Chain, + domain: String, + possible_phishing: bool, + ) -> MmResult<(), Self::Error>; } +/// Provides asynchronous operations related to the history of NFT transfers. #[async_trait] pub trait NftTransferHistoryStorageOps { type Error: NftStorageError; - /// Initializes tables in storage for the specified chain type. + /// Prepares the storage by initializing required tables for a specified chain type. async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error>; /// Whether tables are initialized for the specified chain. @@ -106,7 +135,7 @@ pub trait NftTransferHistoryStorageOps { filters: Option, ) -> MmResult; - async fn add_transfers_to_history(&self, chain: &Chain, transfers: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, chain: Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send; @@ -117,13 +146,13 @@ pub trait NftTransferHistoryStorageOps { /// block_number in ascending order. It is needed to update the NFT LIST table correctly. async fn get_transfers_from_block( &self, - chain: &Chain, + chain: Chain, from_block: u64, ) -> MmResult, Self::Error>; async fn get_transfers_by_token_addr_id( &self, - chain: &Chain, + chain: Chain, token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error>; @@ -135,21 +164,47 @@ pub trait NftTransferHistoryStorageOps { log_index: u32, ) -> MmResult, Self::Error>; - async fn update_transfer_meta_by_hash_and_log_index( + /// Updates the metadata for NFT transfers identified by their token address and ID. + /// Flags the transfers as `possible_spam` if `set_spam` is true. + async fn update_transfers_meta_by_token_addr_id( &self, chain: &Chain, - transfer: NftTransferHistory, + transfer_meta: TransferMeta, + set_spam: bool, ) -> MmResult<(), Self::Error>; - async fn update_transfers_meta_by_token_addr_id( + async fn get_transfers_with_empty_meta(&self, chain: Chain) -> MmResult, Self::Error>; + + /// `get_transfers_by_token_address` function returns list of NFT transfers which have specified token address. + async fn get_transfers_by_token_address( + &self, + chain: Chain, + token_address: String, + ) -> MmResult, Self::Error>; + + /// `update_transfer_spam_by_token_address` function updates `possible_spam` field in NFT transfers which have specified token address. + async fn update_transfer_spam_by_token_address( &self, chain: &Chain, - transfer_meta: TransferMeta, + token_address: String, + possible_spam: bool, ) -> MmResult<(), Self::Error>; - async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error>; + /// `get_token_addresses` return all unique token addresses. + async fn get_token_addresses(&self, chain: Chain) -> MmResult, Self::Error>; + + /// `get_domains` return all unique token domain fields. + async fn get_domains(&self, chain: &Chain) -> MmResult, Self::Error>; + + async fn update_transfer_phishing_by_domain( + &self, + chain: &Chain, + domain: String, + possible_phishing: bool, + ) -> MmResult<(), Self::Error>; } +/// Represents potential errors that can occur when creating an NFT storage. #[derive(Debug, Deserialize, Display, Serialize)] pub enum CreateNftStorageError { Internal(String), @@ -164,12 +219,13 @@ impl From for WithdrawError { } /// `NftStorageBuilder` is used to create an instance that implements the [`NftListStorageOps`] -/// and [`NftTransferHistoryStorageOps`] traits.Also has guard to lock write operations. +/// and [`NftTransferHistoryStorageOps`] traits. pub struct NftStorageBuilder<'a> { ctx: &'a MmArc, } impl<'a> NftStorageBuilder<'a> { + /// Creates a new `NftStorageBuilder` instance with the provided context. #[inline] pub fn new(ctx: &MmArc) -> NftStorageBuilder<'_> { NftStorageBuilder { ctx } } @@ -193,3 +249,27 @@ fn get_offset_limit(max: bool, limit: usize, page_number: Option, None => (0, limit), } } + +/// `NftDetailsJson` structure contains immutable parameters that are not needed for queries. +/// This is what `details_json` string contains in db table. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub(crate) struct NftDetailsJson { + pub(crate) owner_of: Address, + pub(crate) token_hash: Option, + pub(crate) minter_address: Option, + pub(crate) block_number_minted: Option, +} + +/// `TransferDetailsJson` structure contains immutable parameters that are not needed for queries. +/// This is what `details_json` string contains in db table. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub(crate) struct TransferDetailsJson { + pub(crate) block_hash: Option, + pub(crate) transaction_index: Option, + pub(crate) value: Option, + pub(crate) transaction_type: Option, + pub(crate) verified: Option, + pub(crate) operator: Option, + pub(crate) from_address: Address, + pub(crate) to_address: Address, +} diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index 9179704467..4e76b93249 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -1,34 +1,51 @@ use crate::nft::eth_addr_to_hex; -use crate::nft::nft_structs::{Chain, ConvertChain, Nft, NftList, NftTokenAddrId, NftTransferHistory, - NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta}; -use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftStorageError, - NftTransferHistoryStorageOps, RemoveNftResult}; +use crate::nft::nft_structs::{Chain, ContractType, ConvertChain, Nft, NftCommon, NftList, NftListFilters, + NftTokenAddrId, NftTransferCommon, NftTransferHistory, NftTransferHistoryFilters, + NftsTransferHistoryList, TransferMeta, UriMeta}; +use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftDetailsJson, NftListStorageOps, NftStorageError, + NftTransferHistoryStorageOps, RemoveNftResult, TransferDetailsJson}; use async_trait::async_trait; use common::async_blocking; use db_common::sql_build::{SqlCondition, SqlQuery}; use db_common::sqlite::rusqlite::types::{FromSqlError, Type}; -use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Row, Statement}; +use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, Statement}; use db_common::sqlite::sql_builder::SqlBuilder; use db_common::sqlite::{query_single_row, string_from_row, validate_table_name, CHECK_TABLE_EXISTS_SQL}; +use ethereum_types::Address; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::map_to_mm::MapToMmResult; use mm2_err_handle::mm_error::{MmError, MmResult}; use mm2_number::BigDecimal; +use serde_json::Value as Json; use serde_json::{self as json}; +use std::collections::HashSet; use std::convert::TryInto; use std::num::NonZeroUsize; use std::str::FromStr; use std::sync::{Arc, Mutex}; -fn nft_list_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_list" } +impl Chain { + fn nft_list_table_name(&self) -> SqlResult { + let name = self.to_ticker() + "_nft_list"; + validate_table_name(&name)?; + Ok(name) + } -fn nft_transfer_history_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_transfer_history" } + fn transfer_history_table_name(&self) -> SqlResult { + let name = self.to_ticker() + "_nft_transfer_history"; + validate_table_name(&name)?; + Ok(name) + } +} -fn scanned_nft_blocks_table_name() -> String { "scanned_nft_blocks".to_string() } +fn scanned_nft_blocks_table_name() -> SqlResult { + let name = "scanned_nft_blocks".to_string(); + validate_table_name(&name)?; + Ok(name) +} fn create_nft_list_table_sql(chain: &Chain) -> MmResult { - let table_name = nft_list_table_name(chain); - validate_table_name(&table_name)?; + let table_name = chain.nft_list_table_name()?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( token_address VARCHAR(256) NOT NULL, @@ -37,6 +54,26 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { amount VARCHAR(256) NOT NULL, block_number INTEGER NOT NULL, contract_type TEXT NOT NULL, + possible_spam INTEGER DEFAULT 0 NOT NULL, + possible_phishing INTEGER DEFAULT 0 NOT NULL, + collection_name TEXT, + symbol TEXT, + token_uri TEXT, + token_domain TEXT, + metadata TEXT, + last_token_uri_sync TEXT, + last_metadata_sync TEXT, + raw_image_url TEXT, + image_url TEXT, + image_domain TEXT, + token_name TEXT, + description TEXT, + attributes TEXT, + animation_url TEXT, + animation_domain TEXT, + external_url TEXT, + external_domain TEXT, + image_details TEXT, details_json TEXT, PRIMARY KEY (token_address, token_id) );", @@ -46,8 +83,7 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { } fn create_transfer_history_table_sql(chain: &Chain) -> MmResult { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(&table_name)?; + let table_name = chain.transfer_history_table_name()?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( transaction_hash VARCHAR(256) NOT NULL, @@ -60,9 +96,13 @@ fn create_transfer_history_table_sql(chain: &Chain) -> MmResult MmResult MmResult { - let table_name = scanned_nft_blocks_table_name(); - validate_table_name(&table_name)?; + let table_name = scanned_nft_blocks_table_name()?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( chain TEXT PRIMARY KEY, @@ -101,13 +140,15 @@ impl SqliteNftStorage { } } -fn get_nft_list_builder_preimage(chains: Vec) -> MmResult { +fn get_nft_list_builder_preimage( + chains: Vec, + filters: Option, +) -> MmResult { let union_sql_strings = chains .iter() .map(|chain| { - let table_name = nft_list_table_name(chain); - validate_table_name(&table_name)?; - let sql_builder = SqlBuilder::select_from(table_name.as_str()); + let table_name = chain.nft_list_table_name()?; + let sql_builder = nft_list_builder_preimage(table_name.as_str(), filters)?; let sql_string = sql_builder .sql() .map_err(|e| SqlError::ToSqlConversionFailure(e.into()))? @@ -123,6 +164,20 @@ fn get_nft_list_builder_preimage(chains: Vec) -> MmResult) -> Result { + let mut sql_builder = SqlBuilder::select_from(table_name); + if let Some(filters) = filters { + if filters.exclude_spam { + sql_builder.and_where("possible_spam == 0"); + } + if filters.exclude_phishing { + sql_builder.and_where("possible_phishing == 0"); + } + } + drop_mutability!(sql_builder); + Ok(sql_builder) +} + fn get_nft_transfer_builder_preimage( chains: Vec, filters: Option, @@ -130,8 +185,7 @@ fn get_nft_transfer_builder_preimage( let union_sql_strings = chains .into_iter() .map(|chain| { - let table_name = nft_transfer_history_table_name(&chain); - validate_table_name(&table_name)?; + let table_name = chain.transfer_history_table_name()?; let sql_builder = nft_history_table_builder_preimage(table_name.as_str(), filters)?; let sql_string = sql_builder .sql() @@ -165,18 +219,20 @@ fn nft_history_table_builder_preimage( if let Some(date) = filters.to_date { sql_builder.and_where(format!("block_timestamp <= {}", date)); } + if filters.exclude_spam { + sql_builder.and_where("possible_spam == 0"); + } + if filters.exclude_phishing { + sql_builder.and_where("possible_phishing == 0"); + } } drop_mutability!(sql_builder); Ok(sql_builder) } -fn finalize_nft_list_sql_builder( - mut sql_builder: SqlBuilder, - offset: usize, - limit: usize, -) -> MmResult { +fn finalize_sql_builder(mut sql_builder: SqlBuilder, offset: usize, limit: usize) -> MmResult { let sql = sql_builder - .field("nft_list.details_json") + .field("*") .offset(offset) .limit(limit) .sql() @@ -184,28 +240,158 @@ fn finalize_nft_list_sql_builder( Ok(sql) } -fn finalize_nft_history_sql_builder( - mut sql_builder: SqlBuilder, - offset: usize, - limit: usize, -) -> MmResult { - let sql = sql_builder - .field("nft_history.details_json") - .offset(offset) - .limit(limit) - .sql() - .map_err(|e| SqlError::ToSqlConversionFailure(e.into()))?; - Ok(sql) +fn get_and_parse(row: &Row<'_>, column: &str) -> Result { + let value_str: String = row.get(column)?; + value_str.parse().map_err(|_| SqlError::from(FromSqlError::InvalidType)) } fn nft_from_row(row: &Row<'_>) -> Result { - let json_string: String = row.get(0)?; - json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) + let token_address = get_and_parse(row, "token_address")?; + let token_id = get_and_parse(row, "token_id")?; + let chain = get_and_parse(row, "chain")?; + let amount = get_and_parse(row, "amount")?; + let block_number: u64 = row.get("block_number")?; + let contract_type = get_and_parse(row, "contract_type")?; + let possible_spam: i32 = row.get("possible_spam")?; + let possible_phishing: i32 = row.get("possible_phishing")?; + let collection_name: Option = row.get("collection_name")?; + let symbol: Option = row.get("symbol")?; + let token_uri: Option = row.get("token_uri")?; + let token_domain: Option = row.get("token_domain")?; + let metadata: Option = row.get("metadata")?; + let last_token_uri_sync: Option = row.get("last_token_uri_sync")?; + let last_metadata_sync: Option = row.get("last_metadata_sync")?; + let raw_image_url: Option = row.get("raw_image_url")?; + let image_url: Option = row.get("image_url")?; + let image_domain: Option = row.get("image_domain")?; + let token_name: Option = row.get("token_name")?; + let description: Option = row.get("description")?; + let attributes_str: Option = row.get("attributes")?; + let attributes: Option = attributes_str + .as_deref() + .map(json::from_str) + .transpose() + .map_err(|e| SqlError::FromSqlConversionFailure(21, Type::Text, Box::new(e)))?; + let animation_url: Option = row.get("animation_url")?; + let animation_domain: Option = row.get("animation_domain")?; + let external_url: Option = row.get("external_url")?; + let external_domain: Option = row.get("external_domain")?; + let image_details_str: Option = row.get("image_details")?; + let image_details: Option = image_details_str + .as_deref() + .map(json::from_str) + .transpose() + .map_err(|e| SqlError::FromSqlConversionFailure(26, Type::Text, Box::new(e)))?; + let details_json: String = row.get("details_json")?; + let nft_details: NftDetailsJson = + json::from_str(&details_json).map_err(|e| SqlError::FromSqlConversionFailure(27, Type::Text, Box::new(e)))?; + + let uri_meta = UriMeta { + raw_image_url, + image_url, + image_domain, + token_name, + description, + attributes, + animation_url, + animation_domain, + external_url, + external_domain, + image_details, + }; + + let common = NftCommon { + token_address, + token_id, + amount, + owner_of: nft_details.owner_of, + token_hash: nft_details.token_hash, + collection_name, + symbol, + token_uri, + token_domain, + metadata, + last_token_uri_sync, + last_metadata_sync, + minter_address: nft_details.minter_address, + possible_spam: possible_spam != 0, + }; + let nft = Nft { + common, + chain, + block_number_minted: nft_details.block_number_minted, + block_number, + contract_type, + possible_phishing: possible_phishing != 0, + uri_meta, + }; + Ok(nft) } fn transfer_history_from_row(row: &Row<'_>) -> Result { - let json_string: String = row.get(0)?; - json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) + let transaction_hash: String = row.get("transaction_hash")?; + let log_index: u32 = row.get("log_index")?; + let chain: Chain = get_and_parse(row, "chain")?; + let block_number: u64 = row.get("block_number")?; + let block_timestamp: u64 = row.get("block_timestamp")?; + let contract_type: ContractType = get_and_parse(row, "contract_type")?; + let token_address: Address = get_and_parse(row, "token_address")?; + let token_id: BigDecimal = get_and_parse(row, "token_id")?; + let status = get_and_parse(row, "status")?; + let amount: BigDecimal = get_and_parse(row, "amount")?; + let token_uri: Option = row.get("token_uri")?; + let token_domain: Option = row.get("token_domain")?; + let collection_name: Option = row.get("collection_name")?; + let image_url: Option = row.get("image_url")?; + let image_domain: Option = row.get("image_domain")?; + let token_name: Option = row.get("token_name")?; + let possible_spam: i32 = row.get("possible_spam")?; + let possible_phishing: i32 = row.get("possible_phishing")?; + let details_json: String = row.get("details_json")?; + let details: TransferDetailsJson = + json::from_str(&details_json).map_err(|e| SqlError::FromSqlConversionFailure(19, Type::Text, Box::new(e)))?; + + let common = NftTransferCommon { + block_hash: details.block_hash, + transaction_hash, + transaction_index: details.transaction_index, + log_index, + value: details.value, + transaction_type: details.transaction_type, + token_address, + token_id, + from_address: details.from_address, + to_address: details.to_address, + amount, + verified: details.verified, + operator: details.operator, + possible_spam: possible_spam != 0, + }; + + let transfer_history = NftTransferHistory { + common, + chain, + block_number, + block_timestamp, + contract_type, + token_uri, + token_domain, + collection_name, + image_url, + image_domain, + token_name, + status, + possible_phishing: possible_phishing != 0, + }; + + Ok(transfer_history) +} + +fn address_from_row(row: &Row<'_>) -> Result { + let address: String = row.get(0)?; + address + .parse() + .map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) } fn token_address_id_from_row(row: &Row<'_>) -> Result { @@ -219,14 +405,17 @@ fn token_address_id_from_row(row: &Row<'_>) -> Result } fn insert_nft_in_list_sql(chain: &Chain) -> MmResult { - let table_name = nft_list_table_name(chain); - validate_table_name(&table_name)?; - + let table_name = chain.nft_list_table_name()?; let sql = format!( "INSERT INTO {} ( - token_address, token_id, chain, amount, block_number, contract_type, details_json + token_address, token_id, chain, amount, block_number, contract_type, possible_spam, + possible_phishing, collection_name, symbol, token_uri, token_domain, metadata, + last_token_uri_sync, last_metadata_sync, raw_image_url, image_url, image_domain, + token_name, description, attributes, animation_url, animation_domain, external_url, + external_domain, image_details, details_json ) VALUES ( - ?1, ?2, ?3, ?4, ?5, ?6, ?7 + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, + ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27 );", table_name ); @@ -234,15 +423,14 @@ fn insert_nft_in_list_sql(chain: &Chain) -> MmResult { } fn insert_transfer_in_history_sql(chain: &Chain) -> MmResult { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(&table_name)?; - + let table_name = chain.transfer_history_table_name()?; let sql = format!( "INSERT INTO {} ( transaction_hash, log_index, chain, block_number, block_timestamp, contract_type, - token_address, token_id, status, amount, collection_name, image_url, token_name, details_json + token_address, token_id, status, amount, token_uri, token_domain, collection_name, image_url, image_domain, + token_name, possible_spam, possible_phishing, details_json ) VALUES ( - ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14 + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19 );", table_name ); @@ -250,8 +438,7 @@ fn insert_transfer_in_history_sql(chain: &Chain) -> MmResult { } fn upsert_last_scanned_block_sql() -> MmResult { - let table_name = scanned_nft_blocks_table_name(); - validate_table_name(&table_name)?; + let table_name = scanned_nft_blocks_table_name()?; let sql = format!( "INSERT OR REPLACE INTO {} (chain, last_scanned_block) VALUES (?1, ?2);", table_name @@ -259,75 +446,37 @@ fn upsert_last_scanned_block_sql() -> MmResult { Ok(sql) } -fn update_details_json_by_token_add_id_sql(chain: &Chain, table_name_creator: F) -> MmResult -where - F: FnOnce(&Chain) -> String, -{ - let table_name = table_name_creator(chain); - - validate_table_name(&table_name)?; +fn refresh_nft_metadata_sql(chain: &Chain) -> MmResult { + let table_name = chain.nft_list_table_name()?; let sql = format!( - "UPDATE {} SET details_json = ?1 WHERE token_address = ?2 AND token_id = ?3;", + "UPDATE {} SET possible_spam = ?1, possible_phishing = ?2, collection_name = ?3, symbol = ?4, token_uri = ?5, token_domain = ?6, metadata = ?7, \ + last_token_uri_sync = ?8, last_metadata_sync = ?9, raw_image_url = ?10, image_url = ?11, image_domain = ?12, token_name = ?13, description = ?14, \ + attributes = ?15, animation_url = ?16, animation_domain = ?17, external_url = ?18, external_domain = ?19, image_details = ?20 WHERE token_address = ?21 AND token_id = ?22;", table_name ); Ok(sql) } -fn update_meta_by_tx_hash_and_log_index_sql(chain: &Chain) -> MmResult { - let table_name = nft_transfer_history_table_name(chain); - - validate_table_name(&table_name)?; +fn update_transfers_meta_by_token_addr_id_sql(chain: &Chain) -> MmResult { + let table_name = chain.transfer_history_table_name()?; let sql = format!( - "UPDATE {} SET token_uri = ?1, collection_name = ?2, image_url = ?3, token_name = ?4, details_json = ?5 WHERE transaction_hash = ?6 AND log_index = ?7;", + "UPDATE {} SET token_uri = ?1, token_domain = ?2, collection_name = ?3, image_url = ?4, image_domain = ?5, \ + token_name = ?6 WHERE token_address = ?7 AND token_id = ?8;", table_name ); Ok(sql) } -fn update_nft_amount_sql(chain: &Chain, table_name_creator: F) -> MmResult -where - F: FnOnce(&Chain) -> String, -{ - let table_name = table_name_creator(chain); - - validate_table_name(&table_name)?; - let sql = format!( - "UPDATE {} SET amount = ?1, details_json = ?2 WHERE token_address = ?3 AND token_id = ?4;", - table_name - ); - Ok(sql) -} - -fn update_nft_amount_and_block_number_sql(chain: &Chain, table_name_creator: F) -> MmResult -where - F: FnOnce(&Chain) -> String, -{ - let table_name = table_name_creator(chain); - - validate_table_name(&table_name)?; +fn update_transfer_spam_by_token_addr_id(chain: &Chain) -> MmResult { + let table_name = chain.transfer_history_table_name()?; let sql = format!( - "UPDATE {} SET amount = ?1, block_number = ?2, details_json = ?3 WHERE token_address = ?4 AND token_id = ?5;", + "UPDATE {} SET possible_spam = ?1 WHERE token_address = ?2 AND token_id = ?3;", table_name ); Ok(sql) } -fn get_nft_metadata_sql(chain: &Chain) -> MmResult { - let table_name = nft_list_table_name(chain); - validate_table_name(&table_name)?; - let sql = format!( - "SELECT details_json FROM {} WHERE token_address=?1 AND token_id=?2", - table_name - ); - Ok(sql) -} - -fn select_last_block_number_sql(chain: &Chain, table_name_creator: F) -> MmResult -where - F: FnOnce(&Chain) -> String, -{ - let table_name = table_name_creator(chain); - validate_table_name(&table_name)?; +fn select_last_block_number_sql(table_name: String) -> MmResult { let sql = format!( "SELECT block_number FROM {} ORDER BY block_number DESC LIMIT 1", table_name @@ -336,31 +485,12 @@ where } fn select_last_scanned_block_sql() -> MmResult { - let table_name = scanned_nft_blocks_table_name(); - validate_table_name(&table_name)?; + let table_name = scanned_nft_blocks_table_name()?; let sql = format!("SELECT last_scanned_block FROM {} WHERE chain=?1", table_name,); Ok(sql) } -fn get_nft_amount_sql(chain: &Chain, table_name_creator: F) -> MmResult -where - F: FnOnce(&Chain) -> String, -{ - let table_name = table_name_creator(chain); - validate_table_name(&table_name)?; - let sql = format!( - "SELECT amount FROM {} WHERE token_address=?1 AND token_id=?2", - table_name - ); - Ok(sql) -} - -fn delete_nft_sql(chain: &Chain, table_name_creator: F) -> Result> -where - F: FnOnce(&Chain) -> String, -{ - let table_name = table_name_creator(chain); - validate_table_name(&table_name)?; +fn delete_nft_sql(table_name: String) -> Result> { let sql = format!("DELETE FROM {} WHERE token_address=?1 AND token_id=?2", table_name); Ok(sql) } @@ -369,43 +499,40 @@ fn block_number_from_row(row: &Row<'_>) -> Result { row.get::<_, fn nft_amount_from_row(row: &Row<'_>) -> Result { row.get(0) } -fn get_transfers_from_block_builder<'a>( - conn: &'a Connection, - chain: &'a Chain, - from_block: u64, -) -> MmResult, SqlError> { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(table_name.as_str())?; - let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; - sql_builder - .sql_builder() - .and_where(format!("block_number >= '{}'", from_block)) - .order_asc("block_number") - .field("details_json"); - drop_mutability!(sql_builder); - Ok(sql_builder) +fn get_nfts_by_token_address_statement(conn: &Connection, table_name: String) -> MmResult { + let sql_query = format!("SELECT * FROM {} WHERE token_address = ?", table_name); + let stmt = conn.prepare(&sql_query)?; + Ok(stmt) } -fn get_transfers_by_token_addr_id_statement<'a>( - conn: &'a Connection, - chain: &'a Chain, -) -> MmResult, SqlError> { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(table_name.as_str())?; +fn get_token_addresses_statement(conn: &Connection, table_name: String) -> MmResult { + let sql_query = format!("SELECT DISTINCT token_address FROM {}", table_name); + let stmt = conn.prepare(&sql_query)?; + Ok(stmt) +} + +fn get_transfers_from_block_statement<'a>(conn: &'a Connection, chain: &'a Chain) -> MmResult, SqlError> { + let table_name = chain.transfer_history_table_name()?; let sql_query = format!( - "SELECT details_json FROM {} WHERE token_address = ? AND token_id = ?", + "SELECT * FROM {} WHERE block_number >= ? ORDER BY block_number ASC", table_name ); let stmt = conn.prepare(&sql_query)?; Ok(stmt) } +fn get_transfers_by_token_addr_id_statement(conn: &Connection, chain: Chain) -> MmResult { + let table_name = chain.transfer_history_table_name()?; + let sql_query = format!("SELECT * FROM {} WHERE token_address = ? AND token_id = ?", table_name); + let stmt = conn.prepare(&sql_query)?; + Ok(stmt) +} + fn get_transfers_with_empty_meta_builder<'a>( conn: &'a Connection, chain: &'a Chain, ) -> MmResult, SqlError> { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(table_name.as_str())?; + let table_name = chain.transfer_history_table_name()?; let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; sql_builder .sql_builder() @@ -420,16 +547,6 @@ fn get_transfers_with_empty_meta_builder<'a>( Ok(sql_builder) } -fn get_transfer_by_tx_hash_and_log_index_sql(chain: &Chain) -> MmResult { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(&table_name)?; - let sql = format!( - "SELECT details_json FROM {} WHERE transaction_hash=?1 AND log_index = ?2", - table_name - ); - Ok(sql) -} - #[async_trait] impl NftListStorageOps for SqliteNftStorage { type Error = SqlError; @@ -447,8 +564,7 @@ impl NftListStorageOps for SqliteNftStorage { } async fn is_initialized(&self, chain: &Chain) -> MmResult { - let table_name = nft_list_table_name(chain); - validate_table_name(&table_name)?; + let table_name = chain.nft_list_table_name()?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -456,7 +572,7 @@ impl NftListStorageOps for SqliteNftStorage { let scanned_nft_blocks_initialized = query_single_row( &conn, CHECK_TABLE_EXISTS_SQL, - [scanned_nft_blocks_table_name()], + [scanned_nft_blocks_table_name()?], string_from_row, )?; Ok(nft_list_initialized.is_some() && scanned_nft_blocks_initialized.is_some()) @@ -470,11 +586,12 @@ impl NftListStorageOps for SqliteNftStorage { max: bool, limit: usize, page_number: Option, + filters: Option, ) -> MmResult { let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_nft_list_builder_preimage(chains)?; + let sql_builder = get_nft_list_builder_preimage(chains, filters)?; let total_count_builder_sql = sql_builder .clone() .count("*") @@ -486,7 +603,7 @@ impl NftListStorageOps for SqliteNftStorage { let count_total = total.try_into().expect("count should not be failed"); let (offset, limit) = get_offset_limit(max, limit, page_number, count_total); - let sql = finalize_nft_list_sql_builder(sql_builder, offset, limit)?; + let sql = finalize_sql_builder(sql_builder, offset, limit)?; let nfts = conn .prepare(&sql)? .query_map([], nft_from_row)? @@ -501,19 +618,24 @@ impl NftListStorageOps for SqliteNftStorage { .await } - async fn add_nfts_to_list(&self, chain: &Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> + async fn add_nfts_to_list(&self, chain: Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, { let selfi = self.clone(); - let chain = *chain; async_blocking(move || { let mut conn = selfi.0.lock().unwrap(); let sql_transaction = conn.transaction()?; for nft in nfts { - let nft_json = json::to_string(&nft).expect("serialization should not fail"); + let details_json = NftDetailsJson { + owner_of: nft.common.owner_of, + token_hash: nft.common.token_hash, + minter_address: nft.common.minter_address, + block_number_minted: nft.block_number_minted, + }; + let details_json = json::to_string(&details_json).expect("serialization should not fail"); let params = [ Some(eth_addr_to_hex(&nft.common.token_address)), Some(nft.common.token_id.to_string()), @@ -521,7 +643,27 @@ impl NftListStorageOps for SqliteNftStorage { Some(nft.common.amount.to_string()), Some(nft.block_number.to_string()), Some(nft.contract_type.to_string()), - Some(nft_json), + Some(i32::from(nft.common.possible_spam).to_string()), + Some(i32::from(nft.possible_phishing).to_string()), + nft.common.collection_name, + nft.common.symbol, + nft.common.token_uri, + nft.common.token_domain, + nft.common.metadata, + nft.common.last_token_uri_sync, + nft.common.last_metadata_sync, + nft.uri_meta.raw_image_url, + nft.uri_meta.image_url, + nft.uri_meta.image_domain, + nft.uri_meta.token_name, + nft.uri_meta.description, + nft.uri_meta.attributes.map(|v| v.to_string()), + nft.uri_meta.animation_url, + nft.uri_meta.animation_domain, + nft.uri_meta.external_url, + nft.uri_meta.external_domain, + nft.uri_meta.image_details.map(|v| v.to_string()), + Some(details_json), ]; sql_transaction.execute(&insert_nft_in_list_sql(&chain)?, params)?; } @@ -539,7 +681,8 @@ impl NftListStorageOps for SqliteNftStorage { token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error> { - let sql = get_nft_metadata_sql(chain)?; + let table_name = chain.nft_list_table_name()?; + let sql = format!("SELECT * FROM {} WHERE token_address=?1 AND token_id=?2", table_name); let params = [token_address, token_id.to_string()]; let selfi = self.clone(); async_blocking(move || { @@ -556,7 +699,8 @@ impl NftListStorageOps for SqliteNftStorage { token_id: BigDecimal, scanned_block: u64, ) -> MmResult { - let sql = delete_nft_sql(chain, nft_list_table_name)?; + let table_name = chain.nft_list_table_name()?; + let sql = delete_nft_sql(table_name)?; let params = [token_address, token_id.to_string()]; let scanned_block_params = [chain.to_ticker(), scanned_block.to_string()]; let selfi = self.clone(); @@ -583,7 +727,11 @@ impl NftListStorageOps for SqliteNftStorage { token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error> { - let sql = get_nft_amount_sql(chain, nft_list_table_name)?; + let table_name = chain.nft_list_table_name()?; + let sql = format!( + "SELECT amount FROM {} WHERE token_address=?1 AND token_id=?2", + table_name + ); let params = [token_address, token_id.to_string()]; let selfi = self.clone(); async_blocking(move || { @@ -594,16 +742,34 @@ impl NftListStorageOps for SqliteNftStorage { } async fn refresh_nft_metadata(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error> { - let sql = update_details_json_by_token_add_id_sql(chain, nft_list_table_name)?; - let nft_json = json::to_string(&nft).expect("serialization should not fail"); + let sql = refresh_nft_metadata_sql(chain)?; let selfi = self.clone(); async_blocking(move || { let mut conn = selfi.0.lock().unwrap(); let sql_transaction = conn.transaction()?; let params = [ - nft_json, - eth_addr_to_hex(&nft.common.token_address), - nft.common.token_id.to_string(), + Some(i32::from(nft.common.possible_spam).to_string()), + Some(i32::from(nft.possible_phishing).to_string()), + nft.common.collection_name, + nft.common.symbol, + nft.common.token_uri, + nft.common.token_domain, + nft.common.metadata, + nft.common.last_token_uri_sync, + nft.common.last_metadata_sync, + nft.uri_meta.raw_image_url, + nft.uri_meta.image_url, + nft.uri_meta.image_domain, + nft.uri_meta.token_name, + nft.uri_meta.description, + nft.uri_meta.attributes.map(|v| v.to_string()), + nft.uri_meta.animation_url, + nft.uri_meta.animation_domain, + nft.uri_meta.external_url, + nft.uri_meta.external_domain, + nft.uri_meta.image_details.map(|v| v.to_string()), + Some(eth_addr_to_hex(&nft.common.token_address)), + Some(nft.common.token_id.to_string()), ]; sql_transaction.execute(&sql, params)?; sql_transaction.commit()?; @@ -613,7 +779,8 @@ impl NftListStorageOps for SqliteNftStorage { } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { - let sql = select_last_block_number_sql(chain, nft_list_table_name)?; + let table_name = chain.nft_list_table_name()?; + let sql = select_last_block_number_sql(table_name)?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -640,8 +807,11 @@ impl NftListStorageOps for SqliteNftStorage { } async fn update_nft_amount(&self, chain: &Chain, nft: Nft, scanned_block: u64) -> MmResult<(), Self::Error> { - let sql = update_nft_amount_sql(chain, nft_list_table_name)?; - let nft_json = json::to_string(&nft).expect("serialization should not fail"); + let table_name = chain.nft_list_table_name()?; + let sql = format!( + "UPDATE {} SET amount = ?1 WHERE token_address = ?2 AND token_id = ?3;", + table_name + ); let scanned_block_params = [chain.to_ticker(), scanned_block.to_string()]; let selfi = self.clone(); async_blocking(move || { @@ -649,7 +819,6 @@ impl NftListStorageOps for SqliteNftStorage { let sql_transaction = conn.transaction()?; let params = [ Some(nft.common.amount.to_string()), - Some(nft_json), Some(eth_addr_to_hex(&nft.common.token_address)), Some(nft.common.token_id.to_string()), ]; @@ -662,8 +831,11 @@ impl NftListStorageOps for SqliteNftStorage { } async fn update_nft_amount_and_block_number(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error> { - let sql = update_nft_amount_and_block_number_sql(chain, nft_list_table_name)?; - let nft_json = json::to_string(&nft).expect("serialization should not fail"); + let table_name = chain.nft_list_table_name()?; + let sql = format!( + "UPDATE {} SET amount = ?1, block_number = ?2 WHERE token_address = ?3 AND token_id = ?4;", + table_name + ); let scanned_block_params = [chain.to_ticker(), nft.block_number.to_string()]; let selfi = self.clone(); async_blocking(move || { @@ -672,7 +844,6 @@ impl NftListStorageOps for SqliteNftStorage { let params = [ Some(nft.common.amount.to_string()), Some(nft.block_number.to_string()), - Some(nft_json), Some(eth_addr_to_hex(&nft.common.token_address)), Some(nft.common.token_id.to_string()), ]; @@ -683,6 +854,87 @@ impl NftListStorageOps for SqliteNftStorage { }) .await } + + async fn get_nfts_by_token_address(&self, chain: Chain, token_address: String) -> MmResult, Self::Error> { + let selfi = self.clone(); + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + let table_name = chain.nft_list_table_name()?; + let mut stmt = get_nfts_by_token_address_statement(&conn, table_name)?; + let nfts = stmt + .query_map([token_address], nft_from_row)? + .collect::, _>>()?; + Ok(nfts) + }) + .await + } + + async fn update_nft_spam_by_token_address( + &self, + chain: &Chain, + token_address: String, + possible_spam: bool, + ) -> MmResult<(), Self::Error> { + let selfi = self.clone(); + let table_name = chain.nft_list_table_name()?; + let sql = format!("UPDATE {} SET possible_spam = ?1 WHERE token_address = ?2;", table_name); + async_blocking(move || { + let mut conn = selfi.0.lock().unwrap(); + let sql_transaction = conn.transaction()?; + let params = [Some(i32::from(possible_spam).to_string()), Some(token_address.clone())]; + sql_transaction.execute(&sql, params)?; + sql_transaction.commit()?; + Ok(()) + }) + .await + } + + async fn get_animation_external_domains(&self, chain: &Chain) -> MmResult, Self::Error> { + let selfi = self.clone(); + let table_name = chain.nft_list_table_name()?; + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + let sql_query = format!( + "SELECT DISTINCT animation_domain FROM {} + UNION + SELECT DISTINCT external_domain FROM {}", + table_name, table_name + ); + let mut stmt = conn.prepare(&sql_query)?; + let domains = stmt + .query_map([], |row| row.get::<_, Option>(0))? + .collect::, _>>()?; + let domains = domains.into_iter().flatten().collect(); + Ok(domains) + }) + .await + } + + async fn update_nft_phishing_by_domain( + &self, + chain: &Chain, + domain: String, + possible_phishing: bool, + ) -> MmResult<(), Self::Error> { + let selfi = self.clone(); + + let table_name = chain.nft_list_table_name()?; + let sql = format!( + "UPDATE {} SET possible_phishing = ?1 WHERE token_domain = ?2 + OR image_domain = ?2 OR animation_domain = ?2 OR external_domain = ?2;", + table_name + ); + + async_blocking(move || { + let mut conn = selfi.0.lock().unwrap(); + let sql_transaction = conn.transaction()?; + let params = [Some(i32::from(possible_phishing).to_string()), Some(domain)]; + sql_transaction.execute(&sql, params)?; + sql_transaction.commit()?; + Ok(()) + }) + .await + } } #[async_trait] @@ -701,8 +953,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { } async fn is_initialized(&self, chain: &Chain) -> MmResult { - let table_name = nft_transfer_history_table_name(chain); - validate_table_name(&table_name)?; + let table_name = chain.transfer_history_table_name()?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -735,7 +986,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { let count_total = total.try_into().expect("count should not be failed"); let (offset, limit) = get_offset_limit(max, limit, page_number, count_total); - let sql = finalize_nft_history_sql_builder(sql_builder, offset, limit)?; + let sql = finalize_sql_builder(sql_builder, offset, limit)?; let transfers = conn .prepare(&sql)? .query_map([], transfer_history_from_row)? @@ -750,19 +1001,28 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { .await } - async fn add_transfers_to_history(&self, chain: &Chain, transfers: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, chain: Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, { let selfi = self.clone(); - let chain = *chain; async_blocking(move || { let mut conn = selfi.0.lock().unwrap(); let sql_transaction = conn.transaction()?; for transfer in transfers { - let transfer_json = json::to_string(&transfer).expect("serialization should not fail"); + let details_json = TransferDetailsJson { + block_hash: transfer.common.block_hash, + transaction_index: transfer.common.transaction_index, + value: transfer.common.value, + transaction_type: transfer.common.transaction_type, + verified: transfer.common.verified, + operator: transfer.common.operator, + from_address: transfer.common.from_address, + to_address: transfer.common.from_address, + }; + let transfer_json = json::to_string(&details_json).expect("serialization should not fail"); let params = [ Some(transfer.common.transaction_hash), Some(transfer.common.log_index.to_string()), @@ -774,9 +1034,14 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Some(transfer.common.token_id.to_string()), Some(transfer.status.to_string()), Some(transfer.common.amount.to_string()), + transfer.token_uri, + transfer.token_domain, transfer.collection_name, transfer.image_url, + transfer.image_domain, transfer.token_name, + Some(i32::from(transfer.common.possible_spam).to_string()), + Some(i32::from(transfer.possible_phishing).to_string()), Some(transfer_json), ]; sql_transaction.execute(&insert_transfer_in_history_sql(&chain)?, params)?; @@ -788,7 +1053,8 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { - let sql = select_last_block_number_sql(chain, nft_transfer_history_table_name)?; + let table_name = chain.transfer_history_table_name()?; + let sql = select_last_block_number_sql(table_name)?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -802,15 +1068,16 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { async fn get_transfers_from_block( &self, - chain: &Chain, + chain: Chain, from_block: u64, ) -> MmResult, Self::Error> { let selfi = self.clone(); - let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_transfers_from_block_builder(&conn, &chain, from_block)?; - let transfers = sql_builder.query(transfer_history_from_row)?; + let mut stmt = get_transfers_from_block_statement(&conn, &chain)?; + let transfers = stmt + .query_map([from_block], transfer_history_from_row)? + .collect::, _>>()?; Ok(transfers) }) .await @@ -818,15 +1085,14 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { async fn get_transfers_by_token_addr_id( &self, - chain: &Chain, + chain: Chain, token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error> { let selfi = self.clone(); - let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let mut stmt = get_transfers_by_token_addr_id_statement(&conn, &chain)?; + let mut stmt = get_transfers_by_token_addr_id_statement(&conn, chain)?; let transfers = stmt .query_map([token_address, token_id.to_string()], transfer_history_from_row)? .collect::, _>>()?; @@ -841,7 +1107,11 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { transaction_hash: String, log_index: u32, ) -> MmResult, Self::Error> { - let sql = get_transfer_by_tx_hash_and_log_index_sql(chain)?; + let table_name = chain.transfer_history_table_name()?; + let sql = format!( + "SELECT * FROM {} WHERE transaction_hash=?1 AND log_index = ?2", + table_name + ); let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -856,63 +1126,150 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { .await } - async fn update_transfer_meta_by_hash_and_log_index( + async fn update_transfers_meta_by_token_addr_id( &self, chain: &Chain, - transfer: NftTransferHistory, + transfer_meta: TransferMeta, + set_spam: bool, ) -> MmResult<(), Self::Error> { - let sql = update_meta_by_tx_hash_and_log_index_sql(chain)?; - let transfer_json = json::to_string(&transfer).expect("serialization should not fail"); + let sql = update_transfers_meta_by_token_addr_id_sql(chain)?; let params = [ - transfer.token_uri, - transfer.collection_name, - transfer.image_url, - transfer.token_name, - Some(transfer_json), - Some(transfer.common.transaction_hash), - Some(transfer.common.log_index.to_string()), + transfer_meta.token_uri, + transfer_meta.token_domain, + transfer_meta.collection_name, + transfer_meta.image_url, + transfer_meta.image_domain, + transfer_meta.token_name, + Some(transfer_meta.token_address.clone()), + Some(transfer_meta.token_id.to_string()), + ]; + let sql_spam = update_transfer_spam_by_token_addr_id(chain)?; + let params_spam = [ + Some(i32::from(true).to_string()), + Some(transfer_meta.token_address), + Some(transfer_meta.token_id.to_string()), ]; let selfi = self.clone(); async_blocking(move || { let mut conn = selfi.0.lock().unwrap(); let sql_transaction = conn.transaction()?; sql_transaction.execute(&sql, params)?; + if set_spam { + sql_transaction.execute(&sql_spam, params_spam)?; + } sql_transaction.commit()?; Ok(()) }) .await } - async fn update_transfers_meta_by_token_addr_id( + async fn get_transfers_with_empty_meta(&self, chain: Chain) -> MmResult, Self::Error> { + let selfi = self.clone(); + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + let sql_builder = get_transfers_with_empty_meta_builder(&conn, &chain)?; + let token_addr_id_pair = sql_builder.query(token_address_id_from_row)?; + Ok(token_addr_id_pair) + }) + .await + } + + async fn get_transfers_by_token_address( + &self, + chain: Chain, + token_address: String, + ) -> MmResult, Self::Error> { + let selfi = self.clone(); + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + let table_name = chain.transfer_history_table_name()?; + let mut stmt = get_nfts_by_token_address_statement(&conn, table_name)?; + let nfts = stmt + .query_map([token_address], transfer_history_from_row)? + .collect::, _>>()?; + Ok(nfts) + }) + .await + } + + async fn update_transfer_spam_by_token_address( &self, chain: &Chain, - transfer_meta: TransferMeta, + token_address: String, + possible_spam: bool, ) -> MmResult<(), Self::Error> { let selfi = self.clone(); - let transfers = selfi - .get_transfers_by_token_addr_id(chain, transfer_meta.token_address, transfer_meta.token_id) - .await?; - for mut transfer in transfers.into_iter() { - transfer.token_uri = transfer_meta.token_uri.clone(); - transfer.collection_name = transfer_meta.collection_name.clone(); - transfer.image_url = transfer_meta.image_url.clone(); - transfer.token_name = transfer_meta.token_name.clone(); - drop_mutability!(transfer); - selfi - .update_transfer_meta_by_hash_and_log_index(chain, transfer) - .await?; - } - Ok(()) + + let table_name = chain.transfer_history_table_name()?; + let sql = format!("UPDATE {} SET possible_spam = ?1 WHERE token_address = ?2;", table_name); + + async_blocking(move || { + let mut conn = selfi.0.lock().unwrap(); + let sql_transaction = conn.transaction()?; + let params = [Some(i32::from(possible_spam).to_string()), Some(token_address.clone())]; + sql_transaction.execute(&sql, params)?; + sql_transaction.commit()?; + Ok(()) + }) + .await } - async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { + async fn get_token_addresses(&self, chain: Chain) -> MmResult, Self::Error> { let selfi = self.clone(); - let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_transfers_with_empty_meta_builder(&conn, &chain)?; - let token_addr_id_pair = sql_builder.query(token_address_id_from_row)?; - Ok(token_addr_id_pair) + let table_name = chain.transfer_history_table_name()?; + let mut stmt = get_token_addresses_statement(&conn, table_name)?; + let addresses = stmt + .query_map([], address_from_row)? + .collect::, _>>()?; + Ok(addresses) + }) + .await + } + + async fn get_domains(&self, chain: &Chain) -> MmResult, Self::Error> { + let selfi = self.clone(); + let table_name = chain.transfer_history_table_name()?; + async_blocking(move || { + let conn = selfi.0.lock().unwrap(); + let sql_query = format!( + "SELECT DISTINCT token_domain FROM {} + UNION + SELECT DISTINCT image_domain FROM {}", + table_name, table_name + ); + let mut stmt = conn.prepare(&sql_query)?; + let domains = stmt + .query_map([], |row| row.get::<_, Option>(0))? + .collect::, _>>()?; + let domains = domains.into_iter().flatten().collect(); + Ok(domains) + }) + .await + } + + async fn update_transfer_phishing_by_domain( + &self, + chain: &Chain, + domain: String, + possible_phishing: bool, + ) -> MmResult<(), Self::Error> { + let selfi = self.clone(); + + let table_name = chain.transfer_history_table_name()?; + let sql = format!( + "UPDATE {} SET possible_phishing = ?1 WHERE token_domain = ?2 OR image_domain = ?2;", + table_name + ); + + async_blocking(move || { + let mut conn = selfi.0.lock().unwrap(); + let sql_transaction = conn.transaction()?; + let params = [Some(i32::from(possible_phishing).to_string()), Some(domain)]; + sql_transaction.execute(&sql, params)?; + sql_transaction.commit()?; + Ok(()) }) .await } diff --git a/mm2src/coins/nft/storage/wasm/nft_idb.rs b/mm2src/coins/nft/storage/wasm/nft_idb.rs index 0d7758d61a..054f1c058e 100644 --- a/mm2src/coins/nft/storage/wasm/nft_idb.rs +++ b/mm2src/coins/nft/storage/wasm/nft_idb.rs @@ -3,17 +3,27 @@ use async_trait::async_trait; use mm2_db::indexed_db::InitDbResult; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder}; -const DB_NAME: &str = "nft_cache"; const DB_VERSION: u32 = 1; + +/// Represents a locked instance of the `NftCacheIDB` database. +/// +/// This type ensures that while the database is being accessed or modified, +/// no other operations can interfere, maintaining data integrity. pub type NftCacheIDBLocked<'a> = DbLocked<'a, NftCacheIDB>; +/// Represents the IndexedDB instance specifically designed for caching NFT data. +/// +/// This struct provides an abstraction over the raw IndexedDB, offering methods +/// to interact with the database and ensuring that the database is initialized with the +/// required tables and configurations. pub struct NftCacheIDB { + /// The underlying raw IndexedDb instance. inner: IndexedDb, } #[async_trait] impl DbInstance for NftCacheIDB { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "nft_cache"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) @@ -28,5 +38,8 @@ impl DbInstance for NftCacheIDB { } impl NftCacheIDB { + /// Get a reference to the underlying `IndexedDb` instance. + /// + /// This method allows for direct interaction with the raw database, bypassing any abstractions. pub(crate) fn get_inner(&self) -> &IndexedDb { &self.inner } } diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index 058b6cdffd..789ec069da 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -1,12 +1,13 @@ use crate::eth::eth_addr_to_hex; -use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCtx, NftList, NftTransferHistory, NftsTransferHistoryList, - TransferMeta, TransferStatus}; +use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCtx, NftList, NftListFilters, NftTransferHistory, + NftsTransferHistoryList, TransferMeta, TransferStatus}; use crate::nft::storage::wasm::nft_idb::{NftCacheIDB, NftCacheIDBLocked}; use crate::nft::storage::wasm::{WasmNftCacheError, WasmNftCacheResult}; use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftTokenAddrId, NftTransferHistoryFilters, NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; use common::is_initial_upgrade; +use ethereum_types::Address; use mm2_core::mm_ctx::MmArc; use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeResult, SharedDb, TableSignature}; use mm2_err_handle::map_mm_error::MapMmError; @@ -19,12 +20,27 @@ use std::collections::HashSet; use std::num::NonZeroUsize; use std::str::FromStr; +const CHAIN_TOKEN_ADD_TOKEN_ID_INDEX: &str = "chain_token_add_token_id_index"; +const CHAIN_BLOCK_NUMBER_INDEX: &str = "chain_block_number_index"; +const CHAIN_TOKEN_ADD_INDEX: &str = "chain_token_add_index"; +const CHAIN_TOKEN_DOMAIN_INDEX: &str = "chain_token_domain_index"; +const CHAIN_IMAGE_DOMAIN_INDEX: &str = "chain_image_domain_index"; + +/// Provides methods for interacting with the IndexedDB storage specifically designed for NFT data. +/// +/// This struct abstracts the intricacies of fetching and storing NFT data in the IndexedDB, +/// ensuring optimal performance and data integrity. #[derive(Clone)] pub struct IndexedDbNftStorage { + /// The underlying shared database instance for caching NFT data. db: SharedDb, } impl IndexedDbNftStorage { + /// Construct a new `IndexedDbNftStorage` using the given MM context. + /// + /// This method ensures that a proper NFT context (`NftCtx`) exists within the MM context + /// and initializes the underlying storage as required. pub fn new(ctx: &MmArc) -> MmResult { let nft_ctx = NftCtx::from_ctx(ctx).map_to_mm(CreateNftStorageError::Internal)?; Ok(IndexedDbNftStorage { @@ -32,6 +48,7 @@ impl IndexedDbNftStorage { }) } + /// Lock the underlying database to ensure exclusive access, maintaining data consistency during operations. async fn lock_db(&self) -> WasmNftCacheResult> { self.db.get_or_initialize().await.mm_err(WasmNftCacheError::from) } @@ -52,6 +69,24 @@ impl IndexedDbNftStorage { }) } + fn filter_nfts(nfts: I, filters: Option) -> WasmNftCacheResult> + where + I: Iterator, + { + let mut filtered_nfts = Vec::new(); + for nft_table in nfts { + let nft = nft_details_from_item(nft_table)?; + if let Some(filters) = &filters { + if filters.passes_spam_filter(&nft) && filters.passes_phishing_filter(&nft) { + filtered_nfts.push(nft); + } + } else { + filtered_nfts.push(nft); + } + } + Ok(filtered_nfts) + } + fn take_transfers_according_to_paging_opts( mut transfers: Vec, max: bool, @@ -68,7 +103,7 @@ impl IndexedDbNftStorage { }) } - fn take_transfers_according_to_filters( + fn filter_transfers( transfers: I, filters: Option, ) -> WasmNftCacheResult> @@ -79,7 +114,11 @@ impl IndexedDbNftStorage { for transfers_table in transfers { let transfer = transfer_details_from_item(transfers_table)?; if let Some(filters) = &filters { - if filters.is_status_match(&transfer) && filters.is_date_match(&transfer) { + if filters.is_status_match(&transfer) + && filters.is_date_match(&transfer) + && filters.passes_spam_filter(&transfer) + && filters.passes_phishing_filter(&transfer) + { filtered_transfers.push(transfer); } } else { @@ -90,6 +129,12 @@ impl IndexedDbNftStorage { } } +impl NftListFilters { + fn passes_spam_filter(&self, nft: &Nft) -> bool { !self.exclude_spam || !nft.common.possible_spam } + + fn passes_phishing_filter(&self, nft: &Nft) -> bool { !self.exclude_phishing || !nft.possible_phishing } +} + impl NftTransferHistoryFilters { fn is_status_match(&self, transfer: &NftTransferHistory) -> bool { (!self.receive && !self.send) @@ -101,6 +146,14 @@ impl NftTransferHistoryFilters { self.from_date.map_or(true, |from| transfer.block_timestamp >= from) && self.to_date.map_or(true, |to| transfer.block_timestamp <= to) } + + fn passes_spam_filter(&self, transfer: &NftTransferHistory) -> bool { + !self.exclude_spam || !transfer.common.possible_spam + } + + fn passes_phishing_filter(&self, transfer: &NftTransferHistory) -> bool { + !self.exclude_phishing || !transfer.possible_phishing + } } #[async_trait] @@ -117,22 +170,25 @@ impl NftListStorageOps for IndexedDbNftStorage { max: bool, limit: usize, page_number: Option, + filters: Option, ) -> MmResult { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; let mut nfts = Vec::new(); for chain in chains { - let items = table.get_items("chain", chain.to_string()).await?; - for (_item_id, item) in items.into_iter() { - let nft_detail = nft_details_from_item(item)?; - nfts.push(nft_detail); - } + let nft_tables = table + .get_items("chain", chain.to_string()) + .await? + .into_iter() + .map(|(_item_id, nft)| nft); + let filtered = Self::filter_nfts(nft_tables, filters)?; + nfts.extend(filtered); } Self::take_nft_according_to_paging_opts(nfts, max, limit, page_number) } - async fn add_nfts_to_list(&self, chain: &Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> + async fn add_nfts_to_list(&self, chain: Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, @@ -164,7 +220,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftListTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? .with_value(token_id.to_string())?; @@ -188,7 +244,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftListTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? .with_value(token_id.to_string())?; @@ -218,7 +274,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftListTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? .with_value(token_id.to_string())?; @@ -234,7 +290,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftListTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(eth_addr_to_hex(&nft.common.token_address))? .with_value(nft.common.token_id.to_string())?; @@ -248,7 +304,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - get_last_block_from_table(chain, table, NftListTable::CHAIN_BLOCK_NUMBER_INDEX).await + get_last_block_from_table(chain, table, CHAIN_BLOCK_NUMBER_INDEX).await } async fn get_last_scanned_block(&self, chain: &Chain) -> MmResult, Self::Error> { @@ -272,7 +328,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftListTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(eth_addr_to_hex(&nft.common.token_address))? .with_value(nft.common.token_id.to_string())?; @@ -297,7 +353,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftListTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(eth_addr_to_hex(&nft.common.token_address))? .with_value(nft.common.token_id.to_string())?; @@ -315,6 +371,99 @@ impl NftListStorageOps for IndexedDbNftStorage { .await?; Ok(()) } + + async fn get_nfts_by_token_address(&self, chain: Chain, token_address: String) -> MmResult, Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_INDEX) + .with_value(chain.to_string())? + .with_value(&token_address)?; + + table + .get_items_by_multi_index(index_keys) + .await? + .into_iter() + .map(|(_item_id, item)| nft_details_from_item(item)) + .collect() + } + + async fn update_nft_spam_by_token_address( + &self, + chain: &Chain, + token_address: String, + possible_spam: bool, + ) -> MmResult<(), Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let chain_str = chain.to_string(); + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_INDEX) + .with_value(&chain_str)? + .with_value(&token_address)?; + + let nfts: Result, _> = table + .get_items_by_multi_index(index_keys) + .await? + .into_iter() + .map(|(_item_id, item)| nft_details_from_item(item)) + .collect(); + let nfts = nfts?; + + for mut nft in nfts { + nft.common.possible_spam = possible_spam; + drop_mutability!(nft); + + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + .with_value(&chain_str)? + .with_value(eth_addr_to_hex(&nft.common.token_address))? + .with_value(nft.common.token_id.to_string())?; + + let item = NftListTable::from_nft(&nft)?; + table.replace_item_by_unique_multi_index(index_keys, &item).await?; + } + Ok(()) + } + + async fn get_animation_external_domains(&self, chain: &Chain) -> MmResult, Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let mut domains = HashSet::new(); + let nft_tables = table.get_items("chain", chain.to_string()).await?; + for (_item_id, nft) in nft_tables.into_iter() { + if let Some(domain) = nft.animation_domain { + domains.insert(domain); + } + if let Some(domain) = nft.external_domain { + domains.insert(domain); + } + } + Ok(domains) + } + + async fn update_nft_phishing_by_domain( + &self, + chain: &Chain, + domain: String, + possible_phishing: bool, + ) -> MmResult<(), Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let chain_str = chain.to_string(); + update_nft_phishing_for_index(&table, &chain_str, CHAIN_TOKEN_DOMAIN_INDEX, &domain, possible_phishing).await?; + update_nft_phishing_for_index(&table, &chain_str, CHAIN_IMAGE_DOMAIN_INDEX, &domain, possible_phishing).await?; + let animation_index = NftListTable::CHAIN_ANIMATION_DOMAIN_INDEX; + update_nft_phishing_for_index(&table, &chain_str, animation_index, &domain, possible_phishing).await?; + let external_index = NftListTable::CHAIN_EXTERNAL_DOMAIN_INDEX; + update_nft_phishing_for_index(&table, &chain_str, external_index, &domain, possible_phishing).await?; + Ok(()) + } } #[async_trait] @@ -343,13 +492,13 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { .await? .into_iter() .map(|(_item_id, transfer)| transfer); - let filtered = Self::take_transfers_according_to_filters(transfer_tables, filters)?; + let filtered = Self::filter_transfers(transfer_tables, filters)?; transfers.extend(filtered); } Self::take_transfers_according_to_paging_opts(transfers, max, limit, page_number) } - async fn add_transfers_to_history(&self, _chain: &Chain, transfers: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, _chain: Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, @@ -368,12 +517,12 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - get_last_block_from_table(chain, table, NftTransferHistoryTable::CHAIN_BLOCK_NUMBER_INDEX).await + get_last_block_from_table(chain, table, CHAIN_BLOCK_NUMBER_INDEX).await } async fn get_transfers_from_block( &self, - chain: &Chain, + chain: Chain, from_block: u64, ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; @@ -384,7 +533,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { .only("chain", chain.to_string()) .map_err(|e| WasmNftCacheError::GetLastNftBlockError(e.to_string()))? .bound("block_number", BeBigUint::from(from_block), BeBigUint::from(u64::MAX)) - .open_cursor(NftTransferHistoryTable::CHAIN_BLOCK_NUMBER_INDEX) + .open_cursor(CHAIN_BLOCK_NUMBER_INDEX) .await .map_err(|e| WasmNftCacheError::GetLastNftBlockError(e.to_string()))? .collect() @@ -401,7 +550,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { async fn get_transfers_by_token_addr_id( &self, - chain: &Chain, + chain: Chain, token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error> { @@ -409,7 +558,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? .with_value(token_id.to_string())?; @@ -443,45 +592,44 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { } } - async fn update_transfer_meta_by_hash_and_log_index( + async fn update_transfers_meta_by_token_addr_id( &self, chain: &Chain, - transfer: NftTransferHistory, + transfer_meta: TransferMeta, + set_spam: bool, ) -> MmResult<(), Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) - .with_value(chain.to_string())? - .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + let chain_str = chain.to_string(); + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + .with_value(&chain_str)? + .with_value(&transfer_meta.token_address)? + .with_value(transfer_meta.token_id.to_string())?; - let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; - table.replace_item_by_unique_multi_index(index_keys, &item).await?; - Ok(()) - } + let transfers: Result, _> = table + .get_items_by_multi_index(index_keys) + .await? + .into_iter() + .map(|(_item_id, item)| transfer_details_from_item(item)) + .collect(); + let transfers = transfers?; - async fn update_transfers_meta_by_token_addr_id( - &self, - chain: &Chain, - transfer_meta: TransferMeta, - ) -> MmResult<(), Self::Error> { - let transfers: Vec = self - .get_transfers_by_token_addr_id(chain, transfer_meta.token_address, transfer_meta.token_id) - .await?; - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; for mut transfer in transfers { transfer.token_uri = transfer_meta.token_uri.clone(); + transfer.token_domain = transfer_meta.token_domain.clone(); transfer.collection_name = transfer_meta.collection_name.clone(); transfer.image_url = transfer_meta.image_url.clone(); + transfer.image_domain = transfer_meta.image_domain.clone(); transfer.token_name = transfer_meta.token_name.clone(); + if set_spam { + transfer.common.possible_spam = true; + } drop_mutability!(transfer); let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) - .with_value(chain.to_string())? + .with_value(&chain_str)? .with_value(&transfer.common.transaction_hash)? .with_value(transfer.common.log_index)?; @@ -491,7 +639,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { Ok(()) } - async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { + async fn get_transfers_with_empty_meta(&self, chain: Chain) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let table = db_transaction.table::().await?; @@ -521,6 +669,163 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { } Ok(res.into_iter().collect()) } + + async fn get_transfers_by_token_address( + &self, + chain: Chain, + token_address: String, + ) -> MmResult, Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_INDEX) + .with_value(chain.to_string())? + .with_value(&token_address)?; + + table + .get_items_by_multi_index(index_keys) + .await? + .into_iter() + .map(|(_item_id, item)| transfer_details_from_item(item)) + .collect() + } + + async fn update_transfer_spam_by_token_address( + &self, + chain: &Chain, + token_address: String, + possible_spam: bool, + ) -> MmResult<(), Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let chain_str = chain.to_string(); + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_INDEX) + .with_value(&chain_str)? + .with_value(&token_address)?; + + let transfers: Result, _> = table + .get_items_by_multi_index(index_keys) + .await? + .into_iter() + .map(|(_item_id, item)| transfer_details_from_item(item)) + .collect(); + let transfers = transfers?; + + for mut transfer in transfers { + transfer.common.possible_spam = possible_spam; + drop_mutability!(transfer); + + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + .with_value(&chain_str)? + .with_value(&transfer.common.transaction_hash)? + .with_value(transfer.common.log_index)?; + + let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; + table.replace_item_by_unique_multi_index(index_keys, &item).await?; + } + Ok(()) + } + + async fn get_token_addresses(&self, chain: Chain) -> MmResult, Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let items = table.get_items("chain", chain.to_string()).await?; + let mut token_addresses = HashSet::new(); + for (_item_id, item) in items.into_iter() { + let transfer = transfer_details_from_item(item)?; + token_addresses.insert(transfer.common.token_address); + } + Ok(token_addresses) + } + + async fn get_domains(&self, chain: &Chain) -> MmResult, Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let mut domains = HashSet::new(); + let transfer_tables = table.get_items("chain", chain.to_string()).await?; + for (_item_id, transfer) in transfer_tables.into_iter() { + if let Some(domain) = transfer.token_domain { + domains.insert(domain); + } + if let Some(domain) = transfer.image_domain { + domains.insert(domain); + } + } + Ok(domains) + } + + async fn update_transfer_phishing_by_domain( + &self, + chain: &Chain, + domain: String, + possible_phishing: bool, + ) -> MmResult<(), Self::Error> { + let locked_db = self.lock_db().await?; + let db_transaction = locked_db.get_inner().transaction().await?; + let table = db_transaction.table::().await?; + + let chain_str = chain.to_string(); + update_transfer_phishing_for_index(&table, &chain_str, CHAIN_TOKEN_DOMAIN_INDEX, &domain, possible_phishing) + .await?; + update_transfer_phishing_for_index(&table, &chain_str, CHAIN_IMAGE_DOMAIN_INDEX, &domain, possible_phishing) + .await?; + Ok(()) + } +} + +async fn update_transfer_phishing_for_index( + table: &DbTable<'_, NftTransferHistoryTable>, + chain: &str, + index: &str, + domain: &str, + possible_phishing: bool, +) -> MmResult<(), WasmNftCacheError> { + let index_keys = MultiIndex::new(index).with_value(chain)?.with_value(domain)?; + let transfers_table = table.get_items_by_multi_index(index_keys).await?; + for (_item_id, item) in transfers_table.into_iter() { + let mut transfer = transfer_details_from_item(item)?; + transfer.possible_phishing = possible_phishing; + drop_mutability!(transfer); + let transfer_item = NftTransferHistoryTable::from_transfer_history(&transfer)?; + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + .with_value(chain)? + .with_value(&transfer.common.transaction_hash)? + .with_value(transfer.common.log_index)?; + table + .replace_item_by_unique_multi_index(index_keys, &transfer_item) + .await?; + } + Ok(()) +} + +async fn update_nft_phishing_for_index( + table: &DbTable<'_, NftListTable>, + chain: &str, + index: &str, + domain: &str, + possible_phishing: bool, +) -> MmResult<(), WasmNftCacheError> { + let index_keys = MultiIndex::new(index).with_value(chain)?.with_value(domain)?; + let nfts_table = table.get_items_by_multi_index(index_keys).await?; + for (_item_id, item) in nfts_table.into_iter() { + let mut nft = nft_details_from_item(item)?; + nft.possible_phishing = possible_phishing; + drop_mutability!(nft); + let nft_item = NftListTable::from_nft(&nft)?; + let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + .with_value(chain)? + .with_value(eth_addr_to_hex(&nft.common.token_address))? + .with_value(nft.common.token_id.to_string())?; + table.replace_item_by_unique_multi_index(index_keys, &nft_item).await?; + } + Ok(()) } /// `get_last_block_from_table` function returns the highest block in the table related to certain blockchain type. @@ -577,13 +882,18 @@ pub(crate) struct NftListTable { amount: String, block_number: BeBigUint, contract_type: ContractType, + possible_spam: bool, + possible_phishing: bool, + token_domain: Option, + image_domain: Option, + animation_domain: Option, + external_domain: Option, details_json: Json, } impl NftListTable { - const CHAIN_TOKEN_ADD_TOKEN_ID_INDEX: &str = "chain_token_add_token_id_index"; - - const CHAIN_BLOCK_NUMBER_INDEX: &str = "chain_block_number_index"; + const CHAIN_ANIMATION_DOMAIN_INDEX: &str = "chain_animation_domain_index"; + const CHAIN_EXTERNAL_DOMAIN_INDEX: &str = "chain_external_domain_index"; fn from_nft(nft: &Nft) -> WasmNftCacheResult { let details_json = json::to_value(nft).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; @@ -594,6 +904,12 @@ impl NftListTable { amount: nft.common.amount.to_string(), block_number: BeBigUint::from(nft.block_number), contract_type: nft.contract_type, + possible_spam: nft.common.possible_spam, + possible_phishing: nft.possible_phishing, + token_domain: nft.common.token_domain.clone(), + image_domain: nft.uri_meta.image_domain.clone(), + animation_domain: nft.uri_meta.animation_domain.clone(), + external_domain: nft.uri_meta.external_domain.clone(), details_json, }) } @@ -606,11 +922,20 @@ impl TableSignature for NftListTable { if is_initial_upgrade(old_version, new_version) { let table = upgrader.create_table(Self::table_name())?; table.create_multi_index( - Self::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, + CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, &["chain", "token_address", "token_id"], true, )?; - table.create_multi_index(Self::CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; + table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; + table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; + table.create_multi_index( + Self::CHAIN_ANIMATION_DOMAIN_INDEX, + &["chain", "animation_domain"], + false, + )?; + table.create_multi_index(Self::CHAIN_EXTERNAL_DOMAIN_INDEX, &["chain", "external_domain"], false)?; table.create_index("chain", false)?; table.create_index("block_number", false)?; } @@ -631,19 +956,19 @@ pub(crate) struct NftTransferHistoryTable { status: TransferStatus, amount: String, token_uri: Option, + token_domain: Option, collection_name: Option, image_url: Option, + image_domain: Option, token_name: Option, + possible_spam: bool, + possible_phishing: bool, details_json: Json, } impl NftTransferHistoryTable { - const CHAIN_TOKEN_ADD_TOKEN_ID_INDEX: &str = "chain_token_add_token_id_index"; - const CHAIN_TX_HASH_LOG_INDEX_INDEX: &str = "chain_tx_hash_log_index_index"; - const CHAIN_BLOCK_NUMBER_INDEX: &str = "chain_block_number_index"; - fn from_transfer_history(transfer: &NftTransferHistory) -> WasmNftCacheResult { let details_json = json::to_value(transfer).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; @@ -659,9 +984,13 @@ impl NftTransferHistoryTable { status: transfer.status, amount: transfer.common.amount.to_string(), token_uri: transfer.token_uri.clone(), + token_domain: transfer.token_domain.clone(), collection_name: transfer.collection_name.clone(), image_url: transfer.image_url.clone(), + image_domain: transfer.image_domain.clone(), token_name: transfer.token_name.clone(), + possible_spam: transfer.common.possible_spam, + possible_phishing: transfer.possible_phishing, details_json, }) } @@ -674,7 +1003,7 @@ impl TableSignature for NftTransferHistoryTable { if is_initial_upgrade(old_version, new_version) { let table = upgrader.create_table(Self::table_name())?; table.create_multi_index( - Self::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, + CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, &["chain", "token_address", "token_id"], false, )?; @@ -683,7 +1012,10 @@ impl TableSignature for NftTransferHistoryTable { &["chain", "transaction_hash", "log_index"], true, )?; - table.create_multi_index(Self::CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; + table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; + table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; table.create_index("block_number", false)?; table.create_index("chain", false)?; } diff --git a/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs b/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs index c88fd3defc..b646e7cefc 100644 --- a/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs +++ b/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs @@ -3,7 +3,6 @@ use crate::tx_history_storage::wasm::tx_history_storage_v2::{TxCacheTableV2, TxH use async_trait::async_trait; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder, InitDbResult}; -const DB_NAME: &str = "tx_history"; const DB_VERSION: u32 = 1; pub type TxHistoryDbLocked<'a> = DbLocked<'a, TxHistoryDb>; @@ -14,7 +13,7 @@ pub struct TxHistoryDb { #[async_trait] impl DbInstance for TxHistoryDb { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "tx_history"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs b/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs index 13abdd4ed5..e958300e47 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs @@ -12,7 +12,6 @@ use serialization::Reader; use spv_validation::storage::{BlockHeaderStorageError, BlockHeaderStorageOps}; use std::collections::HashMap; -const DB_NAME: &str = "block_headers_cache"; const DB_VERSION: u32 = 1; pub type IDBBlockHeadersStorageRes = MmResult; @@ -24,7 +23,7 @@ pub struct IDBBlockHeadersInner { #[async_trait] impl DbInstance for IDBBlockHeadersInner { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "block_headers_cache"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/coins/z_coin/storage/blockdb/block_idb.rs b/mm2src/coins/z_coin/storage/blockdb/block_idb.rs index e70cfce122..a1d4eda6d9 100644 --- a/mm2src/coins/z_coin/storage/blockdb/block_idb.rs +++ b/mm2src/coins/z_coin/storage/blockdb/block_idb.rs @@ -2,7 +2,6 @@ use async_trait::async_trait; use mm2_db::indexed_db::{BeBigUint, DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, OnUpgradeResult, TableSignature}; -const DB_NAME: &str = "z_compactblocks_cache"; const DB_VERSION: u32 = 1; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -39,7 +38,7 @@ impl BlockDbInner { #[async_trait] impl DbInstance for BlockDbInner { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "z_compactblocks_cache"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs b/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs index 129097dbe6..e3abf591ce 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wallet_idb.rs @@ -2,7 +2,6 @@ use async_trait::async_trait; use mm2_db::indexed_db::{BeBigUint, DbIdentifier, DbInstance, DbUpgrader, IndexedDb, IndexedDbBuilder, InitDbResult, OnUpgradeResult, TableSignature}; -const DB_NAME: &str = "wallet_db_cache"; const DB_VERSION: u32 = 1; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -224,7 +223,7 @@ impl WalletDbInner { #[async_trait] impl DbInstance for WalletDbInner { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "wallet_db_cache"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 6af8724e64..9bcc277724 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -103,6 +103,19 @@ macro_rules! some_or_return_ok_none { }; } +#[macro_export] +macro_rules! cross_test { + ($test_name:ident, $test_code:block) => { + #[cfg(not(target_arch = "wasm32"))] + #[tokio::test(flavor = "multi_thread")] + async fn $test_name() { $test_code } + + #[cfg(target_arch = "wasm32")] + #[wasm_bindgen_test] + async fn $test_name() { $test_code } + }; +} + #[macro_use] pub mod jsonrpc_client; #[macro_use] diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index 3bad052869..c1d56cd20d 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -65,10 +65,15 @@ pub trait TableSignature: DeserializeOwned + Serialize + 'static { fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()>; } +/// Essential operations for initializing an IndexedDb instance. #[async_trait] pub trait DbInstance: Sized { - fn db_name() -> &'static str; + /// Returns the static name of the database. + const DB_NAME: &'static str; + /// Initialize the database with the provided identifier. + /// This method ensures that the database is properly set up with the correct version + /// and has the required tables. async fn init(db_id: DbIdentifier) -> InitDbResult; } @@ -89,7 +94,7 @@ impl DbIdentifier { DbIdentifier { namespace_id, wallet_rmd160, - db_name: Db::db_name(), + db_name: Db::DB_NAME, } } diff --git a/mm2src/mm2_event_stream/src/controller.rs b/mm2src/mm2_event_stream/src/controller.rs index 098c6e4bb7..72870308b4 100644 --- a/mm2src/mm2_event_stream/src/controller.rs +++ b/mm2src/mm2_event_stream/src/controller.rs @@ -103,24 +103,13 @@ impl GuardedReceiver { #[cfg(any(test, target_arch = "wasm32"))] mod tests { use super::*; + use common::cross_test; common::cfg_wasm32! { use wasm_bindgen_test::*; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); } - macro_rules! cross_test { - ($test_name:ident, $test_code:block) => { - #[cfg(not(target_arch = "wasm32"))] - #[tokio::test(flavor = "multi_thread")] - async fn $test_name() { $test_code } - - #[cfg(target_arch = "wasm32")] - #[wasm_bindgen_test] - async fn $test_name() { $test_code } - }; - } - cross_test!(test_create_channel_and_broadcast, { let mut controller = Controller::new(); let mut guard_receiver = controller.create_channel(1); diff --git a/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs b/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs index 8392294c78..cbf9967d42 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs @@ -11,7 +11,6 @@ use mm2_number::BigDecimal; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; -const DB_NAME: &str = "gui_account_storage"; const DB_VERSION: u32 = 1; type AccountDbLocked<'a> = DbLocked<'a, AccountDb>; @@ -342,7 +341,7 @@ struct AccountDb { #[async_trait] impl DbInstance for AccountDb { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "gui_account_storage"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs index 812a802999..c96c2ef024 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs @@ -9,7 +9,6 @@ pub use mm2_db::indexed_db::{cursor_prelude, DbTransactionError, DbTransactionRe pub use tables::{MyActiveMakerOrdersTable, MyActiveTakerOrdersTable, MyFilteringHistoryOrdersTable, MyHistoryOrdersTable}; -const DB_NAME: &str = "ordermatch"; const DB_VERSION: u32 = 1; pub struct OrdermatchDb { @@ -18,7 +17,7 @@ pub struct OrdermatchDb { #[async_trait] impl DbInstance for OrdermatchDb { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "ordermatch"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs b/mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs index d680ba4eb8..4b1b16aaf1 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs @@ -8,7 +8,6 @@ pub use mm2_db::indexed_db::{cursor_prelude, DbTransactionError, DbTransactionRe ItemId}; pub use tables::{MySwapsFiltersTable, SavedSwapTable, SwapLockTable}; -const DB_NAME: &str = "swap"; const DB_VERSION: u32 = 1; pub struct SwapDb { @@ -17,7 +16,7 @@ pub struct SwapDb { #[async_trait] impl DbInstance for SwapDb { - fn db_name() -> &'static str { DB_NAME } + const DB_NAME: &'static str = "swap"; async fn init(db_id: DbIdentifier) -> InitDbResult { let inner = IndexedDbBuilder::new(db_id) diff --git a/mm2src/mm2_net/src/native_http.rs b/mm2src/mm2_net/src/native_http.rs index 9a79611835..924b4e8448 100644 --- a/mm2src/mm2_net/src/native_http.rs +++ b/mm2src/mm2_net/src/native_http.rs @@ -13,6 +13,7 @@ use async_trait::async_trait; use futures::channel::oneshot::Canceled; +use http::header::ACCEPT; use http::{header, HeaderValue, Request}; use hyper::client::connect::Connect; use hyper::client::ResponseFuture; @@ -23,7 +24,7 @@ use common::wio::{drive03, HYPER}; use common::APPLICATION_JSON; use mm2_err_handle::prelude::*; -use super::transport::{SlurpError, SlurpResult, SlurpResultJson}; +use super::transport::{GetInfoFromUriError, SlurpError, SlurpResult, SlurpResultJson}; /// Provides requesting http through it /// @@ -231,6 +232,28 @@ impl From for SlurpError { fn from(e: http::Error) -> Self { SlurpError::InvalidRequest(e.to_string()) } } +/// Sends a GET request to the given URI and expects a 2xx status code in response. +/// +/// # Errors +/// +/// Returns an error if the HTTP status code of the response is not in the 2xx range. +pub async fn send_request_to_uri(uri: &str) -> MmResult { + let request = http::Request::builder() + .method("GET") + .uri(uri) + .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) + .body(hyper::Body::from(""))?; + + let (status, _header, body) = slurp_req_body(request).await?; + if !status.is_success() { + return Err(MmError::new(GetInfoFromUriError::Transport(format!( + "Status code not in 2xx range from {}: {}, {}", + uri, status, body + )))); + } + Ok(body) +} + #[cfg(test)] mod tests { use crate::native_http::slurp_url; diff --git a/mm2src/mm2_net/src/transport.rs b/mm2src/mm2_net/src/transport.rs index 2ba04b65e1..27c039d556 100644 --- a/mm2src/mm2_net/src/transport.rs +++ b/mm2src/mm2_net/src/transport.rs @@ -84,3 +84,53 @@ pub struct GuiAuthValidation { pub timestamp_message: i64, pub signature: String, } + +/// Errors encountered when making HTTP requests to fetch information from a URI. +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] +pub enum GetInfoFromUriError { + #[display(fmt = "Invalid request: {}", _0)] + InvalidRequest(String), + #[display(fmt = "Transport: {}", _0)] + Transport(String), + #[display(fmt = "Invalid response: {}", _0)] + InvalidResponse(String), + #[display(fmt = "Internal: {}", _0)] + Internal(String), +} + +/// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. +impl From for GetInfoFromUriError { + fn from(e: http::Error) -> Self { GetInfoFromUriError::InvalidRequest(e.to_string()) } +} + +impl From for GetInfoFromUriError { + fn from(e: serde_json::Error) -> Self { GetInfoFromUriError::InvalidRequest(e.to_string()) } +} + +impl From for GetInfoFromUriError { + fn from(e: SlurpError) -> Self { + let error_str = e.to_string(); + match e { + SlurpError::ErrorDeserializing { .. } => GetInfoFromUriError::InvalidResponse(error_str), + SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetInfoFromUriError::Transport(error_str), + SlurpError::InvalidRequest(_) => GetInfoFromUriError::InvalidRequest(error_str), + SlurpError::Internal(_) => GetInfoFromUriError::Internal(error_str), + } + } +} + +/// Sends a POST request to the given URI and expects a 2xx status code in response. +/// +/// # Errors +/// +/// Returns an error if the HTTP status code of the response is not in the 2xx range. +pub async fn send_post_request_to_uri(uri: &str, body: String) -> MmResult, GetInfoFromUriError> { + let (status, _header, body) = slurp_post_json(uri, body).await?; + if !status.is_success() { + return Err(MmError::new(GetInfoFromUriError::Transport(format!( + "Status code not in 2xx range from {}: {}", + uri, status, + )))); + } + Ok(body) +} diff --git a/mm2src/mm2_net/src/wasm_http.rs b/mm2src/mm2_net/src/wasm_http.rs index 3767c2f40c..66362d60e3 100644 --- a/mm2src/mm2_net/src/wasm_http.rs +++ b/mm2src/mm2_net/src/wasm_http.rs @@ -1,11 +1,13 @@ -use crate::transport::{SlurpError, SlurpResult}; +use crate::transport::{GetInfoFromUriError, SlurpError, SlurpResult}; use common::executor::spawn_local; use common::{stringify_js_error, APPLICATION_JSON}; use futures::channel::oneshot; -use http::header::CONTENT_TYPE; +use gstuff::ERRL; +use http::header::{ACCEPT, CONTENT_TYPE}; use http::{HeaderMap, StatusCode}; use js_sys::Uint8Array; use mm2_err_handle::prelude::*; +use serde_json::Value as Json; use std::collections::HashMap; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -282,6 +284,38 @@ impl RequestBody { } } +/// Sends a GET request to the given URI and expects a 2xx status code in response. +/// +/// # Errors +/// +/// Returns an error if the HTTP status code of the response is not in the 2xx range. +pub async fn send_request_to_uri(uri: &str) -> MmResult { + macro_rules! try_or { + ($exp:expr, $errtype:ident) => { + match $exp { + Ok(x) => x, + Err(e) => return Err(MmError::new(GetInfoFromUriError::$errtype(ERRL!("{:?}", e)))), + } + }; + } + + let result = FetchRequest::get(uri) + .header(ACCEPT.as_str(), APPLICATION_JSON) + .request_str() + .await; + let (status_code, response_str) = try_or!(result, Transport); + if !status_code.is_success() { + return Err(MmError::new(GetInfoFromUriError::Transport(ERRL!( + "Status code not in 2xx range from: {}, {}", + status_code, + response_str + )))); + } + + let response: Json = try_or!(serde_json::from_str(&response_str), InvalidResponse); + Ok(response) +} + mod tests { use super::*; use wasm_bindgen_test::*; From c58edbc8f214ca9dc05b6e39569556e25285703b Mon Sep 17 00:00:00 2001 From: Kadan Stadelmann Date: Mon, 6 Nov 2023 16:35:42 +0100 Subject: [PATCH 24/40] chore(license): update copyright / license (#2002) --- LEGAL/CONTRIBUTOR-LICENSE-AGREEMENT | 10 +++--- LEGAL/LICENSE-COPYRIGHT-NOTICE | 35 +++++++++---------- examples/README.md | 4 +-- examples/wasm/README.md | 2 +- mm2src/coins/eth.rs | 6 ++-- mm2src/coins/lp_coins.rs | 4 +-- mm2src/coins/utxo.rs | 6 ++-- mm2src/crypto/src/privkey.rs | 4 +-- mm2src/mm2_main/src/lp_native_dex.rs | 4 +-- mm2src/mm2_main/src/lp_network.rs | 4 +-- mm2src/mm2_main/src/lp_ordermatch.rs | 4 +-- mm2src/mm2_main/src/lp_swap.rs | 4 +-- mm2src/mm2_main/src/mm2.rs | 6 ++-- mm2src/mm2_main/src/rpc.rs | 6 ++-- .../src/rpc/lp_commands/lp_commands_legacy.rs | 4 +-- 15 files changed, 51 insertions(+), 52 deletions(-) diff --git a/LEGAL/CONTRIBUTOR-LICENSE-AGREEMENT b/LEGAL/CONTRIBUTOR-LICENSE-AGREEMENT index 4c48d95b9b..04cdaa830c 100644 --- a/LEGAL/CONTRIBUTOR-LICENSE-AGREEMENT +++ b/LEGAL/CONTRIBUTOR-LICENSE-AGREEMENT @@ -1,6 +1,6 @@ Software Grant and Contributor License Agreement (CLA) -In order to clarify the intellectual property license granted with Contributions from any person or entity, we must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of " Atomic Private Limited" (the "Company, Us and Our) users; +In order to clarify the intellectual property license granted with Contributions from any person or entity, we must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of "Pampex LTD and TillyHK LTD" (collectively referred to as the "Companies," "Us," "Our," and "We") users; This version of the Agreement allows Contributors to submit Contributions to us, to authorize Contributions and to grant copyright and patent licenses thereto. @@ -38,7 +38,7 @@ References to "employer" in this Agreement include Your employer or anyone else If You change employers in the future and desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement and secure permission from the new employer before Submitting those Submissions. -You agree to notify " Atomic Private Limited" in writing of any facts or circumstances of which You later become aware that would make Your representations in this Agreement inaccurate in any respect. +You agree to notify "Pampex LTD and TillyHK LTD" in writing of any facts or circumstances of which You later become aware that would make Your representations in this Agreement inaccurate in any respect. You agree that contributions to Project and information about contributions may be maintained indefinitely and disclosed publicly, including Your name and other information that You submit with Your Submission. @@ -50,13 +50,13 @@ Any arbitration must be commenced by filing a demand for arbitration within 6 mo Any arbitration hearing ("Hearing") may be conducted through online means including but not limited to videoconference, upon request from either party. The Hearing will be conducted in English, and the Arbitrator may, at his or her discretion, also select a secondary language upon request by either party. -You may be required, at Company's sole discretion, to give up any and all rights you may have to seek legal action to resolve any disputes arising from these terms through any other means, including but not limited to any court of law. +You may be required, at the Companies sole discretion, to give up any and all rights you may have to seek legal action to resolve any disputes arising from these terms through any other means, including but not limited to any court of law. The statute of limitations and any filing fee deadlines shall be tolled while the parties engage in the informal dispute resolution process required by this section. You give up your right to participate in a class action or other class proceeding. -This Agreement is the entire agreement between the parties, and supersedes any and all prior agreements, understandings or communications, written or oral, between the parties relating to the subject matter hereof. This Agreement may be assigned by " Atomic Private Limited". +This Agreement is the entire agreement between the parties, and supersedes any and all prior agreements, understandings or communications, written or oral, between the parties relating to the subject matter hereof. This Agreement may be assigned by Pampex LTD and TillyHK LTD. -By signing, you accept and agree to the terms of this Contribution License Agreement for Your present and future Submissions to " Atomic Private Limited". +By signing, you accept and agree to the terms of this Contribution License Agreement for Your present and future Submissions to "Pampex LTD and TillyHK LTD". Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software with a limited restrictions as envisaged under the Copyright notice, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. \ No newline at end of file diff --git a/LEGAL/LICENSE-COPYRIGHT-NOTICE b/LEGAL/LICENSE-COPYRIGHT-NOTICE index 6af58751a5..6abe2c2d01 100755 --- a/LEGAL/LICENSE-COPYRIGHT-NOTICE +++ b/LEGAL/LICENSE-COPYRIGHT-NOTICE @@ -1,41 +1,41 @@ -Copyright © 2022 Atomic Private Limited and its contributors +Copyright © 2023 Pampex LTD and TillyHK LTD, along with its contributors. Permission to include in application software or to make digital or hard copies of part or all of this work is subject to the following Agreement. -All software, both binary and source published by Atomic Private Limited (hereinafter, Software) is copyrighted by the Atomic Private Limited (hereinafter, "Atomic Private Limited", "Company","We" or "Us") and ownership of all right, title and interest in and to the Software remains with Atomic Private Limited. By using or copying the Software, User agrees to abide by the terms of this Agreement. +All software, both binary and source code (hereinafter, 'Software'), published under the Komodo DeFi Framework is jointly copyrighted by Pampex LTD and TillyHK LTD (collectively, 'the Companies'). Ownership of all rights, title, and interest in and to the Software remains with the Companies. By using or copying the Software, you (the 'User') agree to abide by the terms of this Agreement. -The Company grants to you (hereinafter, "User" or "You") a royalty-free, non-exclusive right to execute, copy, modify and distribute both the binary and source code solely for the uses, subject to the following conditions: +The Companies grant to you (hereinafter, 'User' or 'You') a royalty-free, non-exclusive right to execute, copy, modify, and distribute both the binary and source code, solely for the uses, subject to the following conditions: -You acknowledge that the Software is being supplied "as is," without any support services from the Company. We don't make any representations or warranties, express or implied, including, without limitation, any representations or warranties of the merchantability or fitness for any particular purpose, or that the application of the software, will not infringe on any patents or other proprietary rights of others. +You acknowledge that the Software is being supplied 'as is,' without any support services from the Companies. We don't make any representations or warranties, express or implied, including, without limitation, any representations or warranties of merchantability or fitness for any particular purpose, or that the application of the software will not infringe on any patents or other proprietary rights of others. -Company shall not be held liable for direct, indirect, incidental or consequential damages arising from any claim by User or any third party with respect to uses allowed under this Agreement, or from any use of the Software. +The Companies shall not be held liable for direct, indirect, incidental, or consequential damages arising from any claim by User or any third party with respect to uses allowed under this Agreement, or from any use of the Software. -User agrees to fully indemnify and hold harmless Company from and against any and all claims, demands, suits, losses, damages, costs and expenses arising out of the User's use of the Software, including, without limitation, arising out of the User's modification of the Software. +User agrees to fully indemnify and hold harmless the Companies from and against any and all claims, demands, suits, losses, damages, costs, and expenses arising out of the User's use of the Software, including, without limitation, arising out of the User's modification of the Software. User may modify the Software and distribute that modified work to third parties under open-source license GPL Version 3.0 terms provided that: -(a) if posted separately, it clearly acknowledges that it contains material copyrighted by "Atomic Private Limited" +(a) if posted separately, it clearly acknowledges that it contains material copyrighted by Pampex LTD and TillyHK LTD (b) no charge is associated with such copies, -(c) User agrees to notify "Atomic Private Limited" of the distribution. +(c) User agrees to notify Pampex LTD and TillyHK LTD of the distribution. (d) User clearly notifies secondary users that such modified work is not the original Software. -(e) Code sections starting with a "license protected" comment and/or "Dex_Fee" code prefix and code-sections / software-logic related to these aforementioned sections shall not be modified by the Users/third parties and are subject of the copyright limitation covered under the foregoing Agreement. +(e) Code sections starting with a "license protected" comment and/or "Dex_Fee" (or "DEX_FEE" and other notations such as lower case, etc.) code prefix and code-sections / software-logic related to these aforementioned sections shall not be modified by the Users/third parties and are subject of the copyright limitation covered under the foregoing Agreement. (f) User Agrees that he/she will not modify the following code sections and/or cryptographic keys: -https://github.com/KomodoPlatform/atomicDEX-API/blob/2fe5be95e166744667bc1fdd75fa5bb1eb5c5903/mm2src/common/common.rs#L164 -https://github.com/KomodoPlatform/atomicDEX-API/blob/2fe5be95e166744667bc1fdd75fa5bb1eb5c5903/mm2src/common/common.rs#L166 -https://github.com/KomodoPlatform/atomicDEX-API/blob/6a634c09198ff18a3825164d2ca1e597cd8ebb51/mm2src/coins/z_coin.rs#L97 -https://github.com/KomodoPlatform/atomicDEX-API/blob/2fe5be95e166744667bc1fdd75fa5bb1eb5c5903/mm2src/lp_swap.rs#L504 +https://github.com/KomodoPlatform/komodo-defi-framework/blob/2fe5be95e166744667bc1fdd75fa5bb1eb5c5903/mm2src/common/common.rs#L164 +https://github.com/KomodoPlatform/komodo-defi-framework/blob/2fe5be95e166744667bc1fdd75fa5bb1eb5c5903/mm2src/common/common.rs#L166 +https://github.com/KomodoPlatform/komodo-defi-framework/blob/6a634c09198ff18a3825164d2ca1e597cd8ebb51/mm2src/coins/z_coin.rs#L97 +https://github.com/KomodoPlatform/komodo-defi-framework/blob/2fe5be95e166744667bc1fdd75fa5bb1eb5c5903/mm2src/lp_swap.rs#L504 -User agrees that "Atomic Private Limited", the authors of the original work and others may enjoy a royalty-free, non-exclusive license to use, copy, modify and redistribute these modifications to the Software made by the User and distributed to third parties as a derivative work under this agreement. +User agrees that Pampex LTD and TillyHK LTD, the authors of the original work and others may enjoy a royalty-free, non-exclusive license to use, copy, modify and redistribute these modifications to the Software made by the User and distributed to third parties as a derivative work under this agreement. This agreement will terminate immediately upon User's breach of, or non-compliance with, any of its terms. User may be held liable for any copyright infringement or the infringement of any other proprietary rights in the Software that is caused or facilitated by the User's failure to abide by the terms of this agreement. -This agreement will be construed and enforced in accordance with the international private laws applicable to contracts performed entirely within the designated country of the Company. +This agreement will be construed and enforced in accordance with the laws applicable to contracts performed entirely within Hong Kong, the domicile of the Companies. Parties agree to resolve disputes in a prompt, low-cost and mutually beneficial way. Before taking legal action or requesting legal proceedings, User will personally participate in an alternative dispute resolution procedure. Mediation and/or arbitral forum will be mutually selected and parties of the alternative dispute resolution will choose the procedure. @@ -45,14 +45,13 @@ Any arbitration must be commenced by filing a demand for arbitration within 6 mo Any arbitration hearing ("Hearing") may be conducted through online means including but not limited to videoconference, upon request from either party. The Hearing will be conducted in English, and the Arbitrator may, at his or her discretion, also select a secondary language upon request by either party. -You may be required, at Company's sole discretion, to give up any and all rights you may have to seek legal action to resolve any disputes arising from these terms through any other means, including but not limited to any court of law. +You may be required, at Companies sole discretion, to give up any and all rights you may have to seek legal action to resolve any disputes arising from these terms through any other means, including but not limited to any court of law. The statute of limitations and any filing fee deadlines shall be tolled while the parties engage in the informal dispute resolution process required by this section. You give up your right to participate in a class action or other class proceeding. -This Agreement is the entire agreement between the parties, and supersedes any and all prior agreements, understandings or communications, written or oral, between the parties relating to the subject matter hereof. This Agreement may be assigned by "Atomic Private Limited" and hence you accept and agree to the terms of this Agreement. - +This Agreement is the entire agreement between the User and the Companies, and supersedes any and all prior agreements, understandings, or communications, written or oral, between the User and the Companies relating to the subject matter hereof. This Agreement may be assigned by the Companies, and by accepting the terms herein, you acknowledge and consent to such assignment. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software with a limited restrictions as envisaged under the Copyright notice, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. The above copyright notice/Agreement and this permission notice shall be included in all copies or substantial portions of the Software. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index e33942a767..6bd770efa8 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,3 @@ -# AtomicDEX-API Examples +# Komodo DeFi Framework Examples -This directory contains example implementation/use cases of AtomicDEX-API \ No newline at end of file +This directory contains example implementation/use cases of the Komodo DeFi Framework \ No newline at end of file diff --git a/examples/wasm/README.md b/examples/wasm/README.md index 849ff63773..809764a753 100644 --- a/examples/wasm/README.md +++ b/examples/wasm/README.md @@ -1,4 +1,4 @@ -# AtomicDEX-API WASM example +# Komodo DeFi Framework WASM example **wasm_build** is an example of using **MarketMaker2** in webpages via [WebAssembly](https://developer.mozilla.org/en-US/docs/WebAssembly) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 3085d0eaa7..c69ce6981f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * @@ -18,7 +18,7 @@ // eth.rs // marketmaker // -// Copyright © 2022 AtomicDEX. All rights reserved. +// Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // use super::eth::Action::{Call, Create}; use crate::lp_price::get_base_price_in_rel; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 5eb13daf6a..74303ba226 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 19a9d9cd7d..622f2eebb5 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * @@ -18,7 +18,7 @@ // utxo.rs // marketmaker // -// Copyright © 2022 AtomicDEX. All rights reserved. +// Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // pub mod bch; diff --git a/mm2src/crypto/src/privkey.rs b/mm2src/crypto/src/privkey.rs index 48ca098c20..98e18e4df6 100644 --- a/mm2src/crypto/src/privkey.rs +++ b/mm2src/crypto/src/privkey.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index cfe63525da..c00ad9916f 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the LICENSE file * * * * Removal or modification of this copyright notice is prohibited. * diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index bba7ca0ed8..356da9e05c 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -1,7 +1,7 @@ // TODO: a lof of these implementations should be handled in `mm2_net` /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -9,7 +9,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 2456a2f38b..8dfe4b7e30 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 5cacc9458a..4f0a704f16 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -37,7 +37,7 @@ //! /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -45,7 +45,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index e343f24ba9..f57c6dd711 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * @@ -18,7 +18,7 @@ // mm2.rs // marketmaker // -// Copyright © 2022 AtomicDEX. All rights reserved. +// Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // #![cfg_attr(target_arch = "wasm32", allow(dead_code))] diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index ed883d15f0..fc1912e7f1 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * @@ -17,7 +17,7 @@ // // rpc.rs // -// Copyright © 2022 AtomicDEX. All rights reserved. +// Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // use crate::mm2::rpc::rate_limiter::RateLimitError; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs index d5f3c41a50..1ac3bb619d 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2022 Atomic Private Limited and its contributors * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -7,7 +7,7 @@ * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * - * AtomicDEX software, including this file may be copied, modified, propagated* + * Komodo DeFi Framework software, including this file may be copied, modified, propagated* * or distributed except according to the terms contained in the * * LICENSE-COPYRIGHT-NOTICE file. * * * From 024b6f37aeed78c8eaa2eb5e756cf67bc5a8274e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Tue, 7 Nov 2023 13:46:36 +0300 Subject: [PATCH 25/40] chore(containers and docs): update docs and container images (#2003) --- .docker/Dockerfile | 112 +++++++++++++++----------------- .docker/Dockerfile.ci-container | 6 +- .docker/Dockerfile.ubuntu.ci | 5 -- README.md | 26 ++++++-- docs/RASPBERRY_PI4_CROSS.md | 13 ++-- 5 files changed, 80 insertions(+), 82 deletions(-) diff --git a/.docker/Dockerfile b/.docker/Dockerfile index eb53aa84ec..57683d5a17 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,60 +1,52 @@ -# Happening in a well-defined setting the Docker builds should be somewhat -# more reproducible than builds relying on the local workstation environment. -# Hence we're going to use the Docker build as the reference one. -# CI and local builds might be considered a second tier build optimizations. -# -# docker build --tag mm2 . - -# NB: The version here was picked to match the one tested in our CI. The latest Travis has (as of 2018-11) is Xenial. -FROM docker.io/ubuntu:xenial - -RUN \ - apt-get update &&\ - apt-get install -y git build-essential libssl-dev wget &&\ - apt-get install -y cmake &&\ - # https://github.com/rust-lang/rust-bindgen/blob/master/book/src/requirements.md#debian-based-linuxes - apt-get install -y llvm-3.9-dev libclang-3.9-dev clang-3.9 lld &&\ - # openssl-sys requirements, cf. https://crates.io/crates/openssl-sys - apt-get install -y pkg-config libssl-dev &&\ - apt-get clean - -RUN \ - wget -O- https://sh.rustup.rs > /tmp/rustup-init.sh &&\ - sh /tmp/rustup-init.sh -y --default-toolchain none &&\ - . /root/.cargo/env &&\ - rustup set profile minimal &&\ - rustup install nightly-2020-02-01 &&\ - rustup default nightly-2020-02-01 &&\ - # It seems that bindgen won't prettify without it: - rustup component add rustfmt-preview &&\ - rm -f /tmp/rustup-init.sh - -ENV PATH="/root/.cargo/bin:${PATH}" - -# First 7 characters of the commit ID. -ENV MM_VERSION="f236ad1" - -RUN cd /tmp &&\ - wget https://api.github.com/repos/KomodoPlatform/atomicDEX-API/tarball/$MM_VERSION &&\ - tar -xzf $MM_VERSION &&\ - ls &&\ - mv KomodoPlatform-atomicDEX-API-$MM_VERSION /mm2 &&\ - rm $MM_VERSION &&\ - echo $MM_VERSION > /mm2/MM_VERSION - -RUN cd /mm2 && cargo fetch - -# This will overwrite the Git version with the local one. -# Only needed when we're developing or changing something locally. -#COPY . /mm2 - -# Build MM1 and MM2. -# Increased verbosity here allows us to see the MM1 CMake logs. -RUN cd /mm2 &&\ - cargo build -vv &&\ - mv target/debug/mm2 /usr/local/bin/marketmaker-mainnet &&\ - # We currently need BOB_PASSPHRASE, BOB_USERPASS, ALICE_PASSPHRASE and ALICE_USERPASS for the tests… - #cargo test &&\ - cargo clean - -CMD marketmaker-testnet +FROM docker.io/debian:buster-slim + +MAINTAINER Onur Özkan + +RUN apt-get update -y + +RUN apt-get install -y \ + build-essential \ + cmake \ + ca-certificates \ + curl \ + wget \ + gnupg + +RUN ln -s /usr/bin/python3 /bin/python + +RUN apt install -y \ + software-properties-common \ + lsb-release + +RUN curl --output llvm.sh https://apt.llvm.org/llvm.sh + +RUN chmod +x llvm.sh + +RUN ./llvm.sh 16 + +RUN rm ./llvm.sh + +RUN ln -s /usr/bin/clang-16 /usr/bin/clang + +ENV AR=/usr/bin/llvm-ar-16 +ENV CC=/usr/bin/clang-16 + +RUN mkdir -m 0755 -p /etc/apt/keyrings + +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + +RUN echo \ + "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ + "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null + +RUN apt-get update -y + +RUN apt-get install -y \ + docker-ce \ + docker-ce-cli \ + containerd.io \ + docker-buildx-plugin + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +ENV PATH="/root/.cargo/bin:$PATH" diff --git a/.docker/Dockerfile.ci-container b/.docker/Dockerfile.ci-container index 2553521750..d68cecd225 100644 --- a/.docker/Dockerfile.ci-container +++ b/.docker/Dockerfile.ci-container @@ -23,7 +23,7 @@ RUN apt install -y \ lsb-release \ gnupg -RUN wget https://apt.llvm.org/llvm.sh +RUN curl --output llvm.sh https://apt.llvm.org/llvm.sh RUN chmod +x llvm.sh @@ -52,4 +52,6 @@ RUN apt-get install -y \ containerd.io \ docker-buildx-plugin -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ No newline at end of file +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +ENV PATH="/root/.cargo/bin:$PATH" diff --git a/.docker/Dockerfile.ubuntu.ci b/.docker/Dockerfile.ubuntu.ci index fc759dbcca..5e86dac8f2 100644 --- a/.docker/Dockerfile.ubuntu.ci +++ b/.docker/Dockerfile.ubuntu.ci @@ -14,11 +14,6 @@ RUN \ wget -O- https://sh.rustup.rs > /tmp/rustup-init.sh &&\ sh /tmp/rustup-init.sh -y --default-toolchain none &&\ . /root/.cargo/env &&\ - rustup set profile minimal &&\ - rustup install nightly-2021-07-18 &&\ - rustup default nightly-2021-07-18 &&\ - # It seems that bindgen won't prettify without it: - rustup component add rustfmt-preview &&\ rm -f /tmp/rustup-init.sh &&\ chmod -R 777 /root diff --git a/README.md b/README.md index d48c24090c..1c699e7064 100755 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ For a curated list of Komodo DeFi Framework based projects and resources, check ## Building from source +### On Host System: + [Pre-built release binaries](https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/get-started-atomicdex.html) are available for OSX, Linux or Windows. If you want to build from source, the following prerequisites are required: @@ -82,17 +84,29 @@ If you want to build from source, the following prerequisites are required: - (Optional) OSX: run `LIBRARY_PATH=/usr/local/opt/openssl/lib` - (Optional) Linux: Install libudev-dev (dpkg) or libudev-devel (rpm) package. - [Download](https://github.com/protocolbuffers/protobuf/releases) or [compile](https://github.com/protocolbuffers/protobuf) `protoc 3.21.x+` and add it to your PATH env. It is also available via package managers depending on the OS. -- Additional Rust Components - ``` - rustup install nightly-2022-10-29 - rustup default nightly-2022-10-29 - rustup component add rustfmt-preview - ``` To build, run `cargo build` (or `cargo build -vv` to get verbose build output). For more detailed instructions, please refer to the [Installation Guide](https://developers.komodoplatform.com/basic-docs/atomicdex/atomicdex-setup/get-started-atomicdex.html). +### From Container: + +If you want to build from source without installing prerequisites to your host system, you can do so by binding the source code inside a container and compiling it there. + +Build the image: + +```sh +docker build -t mm2-build-container -f .docker/Dockerfile . +``` + +Bind source code into container and compile it: + +```sh +docker run -v "$(pwd)":/app -w /app mm2-build-container cargo build +``` + +Just like building it on your host system, you will now have the target directory containing the build files. + ## Building WASM binary Please refer to the [WASM Build Guide](./docs/WASM_BUILD.md). diff --git a/docs/RASPBERRY_PI4_CROSS.md b/docs/RASPBERRY_PI4_CROSS.md index 91bb6a27dd..f8a358e692 100644 --- a/docs/RASPBERRY_PI4_CROSS.md +++ b/docs/RASPBERRY_PI4_CROSS.md @@ -1,9 +1,4 @@ -1. Install latest nightly toolchain and make it default: -``` -rustup install nightly -rustup default nightly -``` -2. Install cross: `cargo install cross`. -3. Build the Docker image for cross compilation: `docker build -f .docker/Dockerfile.armv7-unknown-linux-gnueabihf -t mm2-armv7-unknown-linux-gnueabihf .` -4. Build mm2: `cross build --target armv7-unknown-linux-gnueabihf` or `cross build --target armv7-unknown-linux-gnueabihf --release` for release build. -5. The binary path will be `target/armv7-unknown-linux-gnueabihf/debug/mm2` or `target/armv7-unknown-linux-gnueabihf/release/mm2` for release build. \ No newline at end of file +1. Install cross: `cargo install cross`. +2. Build the Docker image for cross compilation: `docker build -f .docker/Dockerfile.armv7-unknown-linux-gnueabihf -t mm2-armv7-unknown-linux-gnueabihf .` +3. Build mm2: `cross build --target armv7-unknown-linux-gnueabihf` or `cross build --target armv7-unknown-linux-gnueabihf --release` for release build. +4. The binary path will be `target/armv7-unknown-linux-gnueabihf/debug/mm2` or `target/armv7-unknown-linux-gnueabihf/release/mm2` for release build. \ No newline at end of file From 3ce3de108c6241c9680576b02cdc8fa14f563a7a Mon Sep 17 00:00:00 2001 From: Rozhkov Dmitrii Date: Fri, 17 Nov 2023 21:07:54 +0500 Subject: [PATCH 26/40] refactor(cli): cli dependency updates and warn on bad config perm (#1956) This commit: - Sets the exact dependency versions of `hyper-rustls`, `rustls` and other deps. - Adds warning on insecure cli configuration file mode instead of changing it. - Moves unix fs permission import closer to the place where it's checked. --- mm2src/adex_cli/Cargo.lock | 671 ++++++++++++++--------------- mm2src/adex_cli/Cargo.toml | 12 +- mm2src/adex_cli/src/adex_config.rs | 21 +- mm2src/adex_cli/src/helpers.rs | 9 - 4 files changed, 349 insertions(+), 364 deletions(-) diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 7d513ab8d2..8aed234b0b 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.19.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ "gimli", ] @@ -28,13 +28,13 @@ dependencies = [ "hyper-rustls", "inquire", "itertools", - "log 0.4.17", + "log", "mm2_net", "mm2_number", "mm2_rpc", "passwords", "rpc", - "rustls 0.20.8", + "rustls 0.20.4", "serde", "serde_json", "sysinfo", @@ -63,27 +63,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - -[[package]] -name = "aho-corasick" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android_system_properties" @@ -111,15 +102,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] @@ -135,9 +126,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", "windows-sys 0.48.0", @@ -145,9 +136,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "arrayref" @@ -157,25 +148,25 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] @@ -184,7 +175,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.1.14", "libc", "winapi", ] @@ -206,9 +197,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.67" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", @@ -315,12 +306,12 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "0.5.11" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec 0.5.1", "constant_time_eq", ] @@ -342,9 +333,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bumpalo" -version = "3.12.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byte-slice-cast" @@ -376,9 +367,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" [[package]] name = "cfg-if" @@ -406,9 +397,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -421,9 +412,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.4" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" dependencies = [ "clap_builder", "clap_derive", @@ -432,9 +423,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.3.4" +version = "4.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" dependencies = [ "anstream", "anstyle", @@ -445,21 +436,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.2" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" dependencies = [ "heck", "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" [[package]] name = "cloudabi" @@ -518,9 +509,9 @@ dependencies = [ "lazy_static", "libc", "lightning", - "log 0.4.17", + "log", "parking_lot", - "parking_lot_core 0.6.3", + "parking_lot_core 0.6.2", "primitive-types", "rand 0.7.3", "regex", @@ -557,12 +548,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.3" @@ -581,9 +566,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] @@ -625,13 +610,13 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "autocfg 1.1.0", "cfg-if 1.0.0", "crossbeam-utils", + "lazy_static", "memoffset", "scopeguard", ] @@ -648,11 +633,12 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", + "lazy_static", ] [[package]] @@ -673,9 +659,9 @@ dependencies = [ [[package]] name = "crossterm_winapi" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] @@ -698,9 +684,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.94" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" dependencies = [ "cc", "cxxbridge-flags", @@ -710,34 +696,34 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.94" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +checksum = "8b49af8e551e84f85d6def97c42b8d93bc5bb0817cce96b56a472b1b19b5bfc2" dependencies = [ "cc", "codespan-reporting", - "once_cell", + "lazy_static", "proc-macro2", "quote 1.0.27", "scratch", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] name = "cxxbridge-flags" -version = "1.0.94" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" [[package]] name = "cxxbridge-macro" -version = "1.0.94" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] @@ -746,7 +732,7 @@ version = "0.1.0" dependencies = [ "common", "hex", - "log 0.4.17", + "log", "rusqlite", "sql-builder", "uuid", @@ -754,15 +740,13 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ - "convert_case", "proc-macro2", "quote 1.0.27", - "rustc_version 0.4.0", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -797,9 +781,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" [[package]] name = "edit-distance" @@ -809,9 +793,9 @@ checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" [[package]] name = "either" -version = "1.8.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" [[package]] name = "endian-type" @@ -827,7 +811,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.17", + "log", "regex", "termcolor", ] @@ -840,16 +824,16 @@ checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime 2.1.0", - "log 0.4.17", + "log", "regex", "termcolor", ] [[package]] name = "errno" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", @@ -896,12 +880,12 @@ dependencies = [ [[package]] name = "ethkey" version = "0.3.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#223da7972113a548531804510708f87b629a48fd" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" dependencies = [ "byteorder", "edit-distance", "ethereum-types", - "log 0.3.9", + "log", "mem", "rand 0.6.5", "rustc-hex", @@ -1026,7 +1010,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1089,12 +1073,11 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "cfg-if 1.0.0", - "js-sys", + "cfg-if 0.1.10", "libc", "wasi 0.9.0+wasi-snapshot-preview1", "wasm-bindgen", @@ -1113,9 +1096,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.2" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "groestl" @@ -1140,9 +1123,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes 1.4.0", "fnv", @@ -1165,9 +1148,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" dependencies = [ "ahash", ] @@ -1199,27 +1182,18 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" dependencies = [ "libc", ] [[package]] name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -1245,7 +1219,7 @@ checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" dependencies = [ "bytes 0.4.12", "fnv", - "itoa 0.4.8", + "itoa 0.4.6", ] [[package]] @@ -1256,7 +1230,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes 1.4.0", "fnv", - "itoa 1.0.6", + "itoa 1.0.1", ] [[package]] @@ -1324,7 +1298,7 @@ dependencies = [ "http-body 0.4.5", "httparse", "httpdate", - "itoa 1.0.6", + "itoa 1.0.1", "pin-project-lite", "socket2", "tokio", @@ -1335,14 +1309,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http 0.2.9", "hyper", - "log 0.4.17", - "rustls 0.20.8", + "log", + "rustls 0.20.4", "rustls-native-certs", "tokio", "tokio-rustls", @@ -1351,16 +1325,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "winapi", ] [[package]] @@ -1408,7 +1382,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -1455,7 +1429,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "libc", "windows-sys 0.48.0", ] @@ -1471,9 +1445,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" [[package]] name = "is-terminal" @@ -1481,7 +1455,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.3.1", + "hermit-abi 0.3.2", "io-lifetimes", "rustix", "windows-sys 0.48.0", @@ -1498,21 +1472,21 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" dependencies = [ "wasm-bindgen", ] @@ -1551,9 +1525,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.144" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libsqlite3-sys" @@ -1577,9 +1551,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.8" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" dependencies = [ "cc", ] @@ -1592,23 +1566,13 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ - "autocfg 1.1.0", "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.17", -] - [[package]] name = "log" version = "0.4.17" @@ -1636,7 +1600,7 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "mem" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#223da7972113a548531804510708f87b629a48fd" +source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git#3326a6c3c12c1655f9dec57ad28b0983d8c08997" [[package]] name = "memchr" @@ -1646,18 +1610,18 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.8.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ "autocfg 1.1.0", ] [[package]] name = "metrics" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa8ebbd1a9e57bbab77b9facae7f5136aea44c356943bf9a198f647da64285d6" +checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" dependencies = [ "ahash", "metrics-macros", @@ -1690,7 +1654,7 @@ checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -1699,10 +1663,10 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "111cb375987443c3de8d503580b536f77dc8416d32db62d9456db5d93bd7ac47" dependencies = [ - "aho-corasick 0.7.20", + "aho-corasick", "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.13.2", + "hashbrown 0.13.1", "indexmap", "metrics", "num_cpus", @@ -1714,9 +1678,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.6.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -1728,7 +1692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", - "log 0.4.17", + "log", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.45.0", ] @@ -1830,7 +1794,7 @@ dependencies = [ "mm2_state_machine", "prost", "rand 0.7.3", - "rustls 0.20.8", + "rustls 0.20.4", "serde", "serde_json", "tokio", @@ -1895,7 +1859,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "smallvec 1.10.0", + "smallvec 1.6.1", ] [[package]] @@ -1953,28 +1917,28 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi 0.1.14", "libc", ] [[package]] name = "object" -version = "0.30.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "opaque-debug" @@ -2005,11 +1969,11 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.5.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" +checksum = "e8b44461635bbb1a0300f100a841e571e7d919c81c73075ef5d152ffdb521066" dependencies = [ - "arrayvec 0.7.2", + "arrayvec 0.7.1", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", @@ -2019,52 +1983,52 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.27", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ "lock_api", - "parking_lot_core 0.9.7", + "parking_lot_core 0.9.1", ] [[package]] name = "parking_lot_core" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66b810a62be75176a80873726630147a5ca780cd33921e0b5709033e66b0a" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ "cfg-if 0.1.10", "cloudabi", "libc", - "redox_syscall 0.1.57", - "rustc_version 0.2.3", + "redox_syscall 0.1.56", + "rustc_version", "smallvec 0.6.14", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.16", - "smallvec 1.10.0", - "windows-sys 0.45.0", + "redox_syscall 0.2.10", + "smallvec 1.6.1", + "windows-sys 0.32.0", ] [[package]] @@ -2078,9 +2042,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.12" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] name = "pbkdf2" @@ -2111,9 +2075,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "portable-atomic" -version = "1.3.3" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" +checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5" [[package]] name = "ppv-lite86" @@ -2146,12 +2110,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", - "toml_edit", + "thiserror", + "toml", ] [[package]] @@ -2162,9 +2126,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" dependencies = [ "unicode-ident", ] @@ -2189,7 +2153,7 @@ dependencies = [ "itertools", "proc-macro2", "quote 1.0.27", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] @@ -2270,7 +2234,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom 0.1.16", + "getrandom 0.1.14", "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", @@ -2340,7 +2304,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom 0.1.16", + "getrandom 0.1.14", ] [[package]] @@ -2451,7 +2415,7 @@ checksum = "8b86292cf41ccfc96c5de7165c1c53d5b4ac540c5bab9d1857acbe9eba5f1a0b" dependencies = [ "proc-macro-hack", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] @@ -2505,46 +2469,45 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.57" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.9", - "redox_syscall 0.2.16", - "thiserror", + "redox_syscall 0.2.10", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ - "aho-corasick 1.0.1", + "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" [[package]] name = "ring" @@ -2588,7 +2551,7 @@ version = "0.1.0" dependencies = [ "chain", "keys", - "log 0.4.17", + "log", "primitives", "rustc-hex", "script", @@ -2609,7 +2572,7 @@ dependencies = [ "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", - "smallvec 1.10.0", + "smallvec 1.6.1", ] [[package]] @@ -2636,30 +2599,21 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", + "semver", ] [[package]] name = "rustix" -version = "0.37.20" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.45.0", ] [[package]] @@ -2669,7 +2623,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.1", - "log 0.4.17", + "log", "ring", "sct 0.6.1", "webpki 0.21.4", @@ -2677,11 +2631,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.8" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ - "log 0.4.17", + "log", "ring", "sct 0.7.0", "webpki 0.22.0", @@ -2710,9 +2664,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "schannel" @@ -2737,9 +2691,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" [[package]] name = "script" @@ -2749,7 +2703,7 @@ dependencies = [ "blake2b_simd", "chain", "keys", - "log 0.4.17", + "log", "primitives", "serde", "serialization", @@ -2815,9 +2769,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags", "core-foundation", @@ -2828,9 +2782,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -2845,12 +2799,6 @@ dependencies = [ "semver-parser", ] -[[package]] -name = "semver" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" - [[package]] name = "semver-parser" version = "0.7.0" @@ -2871,14 +2819,14 @@ dependencies = [ "proc-macro2", "quote 1.0.27", "ser_error", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] @@ -2896,36 +2844,36 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "indexmap", - "itoa 1.0.6", + "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.12" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" +checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] @@ -2990,9 +2938,9 @@ version = "0.1.0" [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", @@ -3050,9 +2998,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "socket2" @@ -3111,9 +3059,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.109" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote 1.0.27", @@ -3122,9 +3070,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" dependencies = [ "proc-macro2", "quote 1.0.27", @@ -3179,22 +3127,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] @@ -3261,19 +3209,20 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ "autocfg 1.1.0", "bytes 1.4.0", "libc", + "memchr", "mio", "num_cpus", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.42.0", ] [[package]] @@ -3288,13 +3237,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] @@ -3303,7 +3252,7 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.8", + "rustls 0.20.4", "tokio", "webpki 0.22.0", ] @@ -3323,20 +3272,12 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "0.6.1" +name = "toml" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" - -[[package]] -name = "toml_edit" -version = "0.19.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" dependencies = [ - "indexmap", - "toml_datetime", - "winnow", + "serde", ] [[package]] @@ -3347,9 +3288,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -3359,22 +3300,22 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 1.0.95", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ - "once_cell", + "lazy_static", ] [[package]] @@ -3403,9 +3344,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" @@ -3448,9 +3389,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom 0.2.9", "rand 0.8.5", @@ -3475,7 +3416,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.17", + "log", "try-lock", ] @@ -3508,19 +3449,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", - "log 0.4.17", + "log", "once_cell", "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "8e67a5806118af01f0d9045915676b22aaebecf4178ae7021bc171dab0b897ab" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -3546,7 +3487,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3559,9 +3500,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" -version = "0.3.30" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4464b3f74729a25f42b1a0cd9e6a515d2f25001f3535a6cfaf35d34a4de3bab" +checksum = "0f0dfda4d3b3f8acbc3c291b09208081c203af457fb14a229783b06e2f128aa7" dependencies = [ "console_error_panic_hook", "js-sys", @@ -3573,9 +3514,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.30" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c5a6f82cc6093a321ca5fb3dc9327fe51675d477b3799b4a9375bac3b7b4c" +checksum = "2c2e18093f11c19ca4e188c177fecc7c372304c311189f12c2f9bea5b7324ac7" dependencies = [ "proc-macro2", "quote 1.0.27", @@ -3583,9 +3524,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", @@ -3652,12 +3593,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-sys" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" dependencies = [ - "windows-targets 0.48.0", + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -3666,7 +3626,7 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.42.1", ] [[package]] @@ -3675,14 +3635,14 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.1", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", @@ -3695,9 +3655,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", @@ -3720,6 +3680,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3732,6 +3698,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3744,6 +3716,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3756,6 +3734,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3780,6 +3764,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3792,15 +3782,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "winnow" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" -dependencies = [ - "memchr", -] - [[package]] name = "wyz" version = "0.5.1" @@ -3827,5 +3808,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 2.0.15", + "syn 2.0.16", ] diff --git a/mm2src/adex_cli/Cargo.toml b/mm2src/adex_cli/Cargo.toml index ec0e0e5283..b09916344e 100644 --- a/mm2src/adex_cli/Cargo.toml +++ b/mm2src/adex_cli/Cargo.toml @@ -7,8 +7,8 @@ description = "Provides a CLI interface and facilitates interoperating to komodo # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -anyhow = { version = "1.0.71", features = ["std"] } -async-trait = "0.1.68" +anyhow = { version = "=1.0.42", features = ["std"] } +async-trait = "=0.1.52" clap = { version = "4.2", features = ["derive"] } common = { path = "../common" } derive_more = "0.99" @@ -16,7 +16,7 @@ directories = "5.0" env_logger = "0.7.1" http = "0.2" hyper = { version = "0.14.26", features = ["client", "http2", "tcp"] } -hyper-rustls = "^0.23.0" +hyper-rustls = "=0.23.0" gstuff = { version = "=0.7.4" , features = [ "nightly" ]} inquire = "0.6" itertools = "0.10" @@ -26,13 +26,13 @@ mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc"} passwords = "3.1" rpc = { path = "../mm2_bitcoin/rpc" } -rustls = { version = "^0.20.4", features = [ "dangerous_configuration" ] } +rustls = { version = "=0.20.4", features = [ "dangerous_configuration" ] } serde = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } sysinfo = "0.28" tiny-bip39 = "0.8.0" -tokio = { version = "1.20", features = [ "macros" ] } -uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } +tokio = { version = "=1.25.0", features = [ "macros" ] } +uuid = { version = "=1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.3", features = ["processthreadsapi", "winnt"] } diff --git a/mm2src/adex_cli/src/adex_config.rs b/mm2src/adex_cli/src/adex_config.rs index aee4fec2d7..d69e27353f 100644 --- a/mm2src/adex_cli/src/adex_config.rs +++ b/mm2src/adex_cli/src/adex_config.rs @@ -9,7 +9,6 @@ use std::path::{Path, PathBuf}; use crate::adex_proc::SmartFractPrecision; use crate::helpers::rewrite_json_file; -#[cfg(unix)] use crate::helpers::set_file_permissions; use crate::logging::{error_anyhow, warn_bail}; const PROJECT_QUALIFIER: &str = "com"; @@ -151,17 +150,31 @@ impl AdexConfigImpl { } fn write_to(&self, cfg_path: &Path) -> Result<()> { - let adex_path_str = cfg_path + let komodefi_path_str = cfg_path .to_str() .ok_or_else(|| error_anyhow!("Failed to get cfg_path as str"))?; - rewrite_json_file(self, adex_path_str)?; + rewrite_json_file(self, komodefi_path_str)?; #[cfg(unix)] { - set_file_permissions(adex_path_str, CFG_FILE_PERM_MODE)?; + Self::warn_on_insecure_mode(komodefi_path_str)?; } Ok(()) } + #[cfg(unix)] + fn warn_on_insecure_mode(file_path: &str) -> Result<()> { + use std::os::unix::fs::PermissionsExt; + let perms = fs::metadata(file_path)?.permissions(); + let mode = perms.mode() & 0o777; + if mode != CFG_FILE_PERM_MODE { + warn!( + "Configuration file: '{}' - does not comply to the expected mode: {:o}, the actual one is: {:o}", + file_path, CFG_FILE_PERM_MODE, mode + ); + }; + Ok(()) + } + fn set_rpc_password(&mut self, rpc_password: String) { self.rpc_password.replace(rpc_password); } fn set_rpc_uri(&mut self, rpc_uri: String) { self.rpc_uri.replace(rpc_uri); } diff --git a/mm2src/adex_cli/src/helpers.rs b/mm2src/adex_cli/src/helpers.rs index 6d6cef2a90..d5dc21ce64 100644 --- a/mm2src/adex_cli/src/helpers.rs +++ b/mm2src/adex_cli/src/helpers.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; use std::fs; use std::io::Write; use std::ops::Deref; -#[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::Path; use crate::error_anyhow; @@ -27,14 +26,6 @@ where Ok(()) } -#[cfg(unix)] -pub(crate) fn set_file_permissions(file: &str, unix_mode: u32) -> Result<()> { - let mut perms = fs::metadata(file)?.permissions(); - perms.set_mode(unix_mode); - fs::set_permissions(file, perms)?; - Ok(()) -} - pub(crate) fn rewrite_json_file(value: &T, file: &str) -> Result<()> where T: Serialize, From 1bafc7c31885037fd8cc6fd30a0708fc4be39683 Mon Sep 17 00:00:00 2001 From: uak <411046+damascene@users.noreply.github.com> Date: Fri, 17 Nov 2023 19:36:11 +0000 Subject: [PATCH 27/40] chore(docs): fix the link to simple market maker in README.md (#2011) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c699e7064..b22bdb5856 100755 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ For a curated list of Komodo DeFi Framework based projects and resources, check - Perform blockchain transactions without a local native chain (e.g. via Electrum servers) - Query orderbooks for all pairs within the [supported coins](https://github.com/KomodoPlatform/coins/blob/master/coins) - Buy/sell from the orderbook, or create maker orders -- Configure automated ["makerbot" trading](https://developers.komodoplatform.com/basic-docs/atomicdex-api-20-dev/start_simple_market_maker_bot.html) with periodic price updates and optional [telegram](https://telegram.org/) alerts +- Configure automated ["makerbot" trading](https://developers.komodoplatform.com/basic-docs/atomicdex-api-20/start_simple_market_maker_bot.html) with periodic price updates and optional [telegram](https://telegram.org/) alerts ## System Requirements From 0221505e3461fefca4145afb10a2d64672a012f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Wed, 22 Nov 2023 13:05:34 +0300 Subject: [PATCH 28/40] feat(UTXO swaps): kmd burn plan impl (#2006) For KMD, taker fee payment algorithm is updated by burning up to 25% of the dex fee amount. This is achieved by adding a new output to the DEX fee transaction that is an OP_RETURN with up to 25% of the fee burned in KMD. We also Validate the new output by the maker as part of the taker fee validation step. --------- Signed-off-by: onur-ozkan --- mm2src/coins/eth.rs | 17 ++- mm2src/coins/eth/eth_tests.rs | 30 ++-- mm2src/coins/lightning.rs | 8 +- mm2src/coins/lp_coins.rs | 72 +++++++++- mm2src/coins/qrc20.rs | 38 ++--- mm2src/coins/qrc20/qrc20_tests.rs | 25 ++-- mm2src/coins/solana.rs | 28 ++-- mm2src/coins/solana/spl.rs | 6 +- mm2src/coins/tendermint/tendermint_coin.rs | 34 +++-- mm2src/coins/tendermint/tendermint_token.rs | 17 ++- mm2src/coins/test_coin.rs | 8 +- mm2src/coins/utxo/bch.rs | 10 +- mm2src/coins/utxo/qtum.rs | 26 ++-- mm2src/coins/utxo/slp.rs | 18 +-- mm2src/coins/utxo/utxo_common.rs | 131 +++++++++++++++--- mm2src/coins/utxo/utxo_standard.rs | 10 +- mm2src/coins/utxo/utxo_tests.rs | 8 +- mm2src/coins/z_coin.rs | 42 +++--- mm2src/coins/z_coin/z_coin_native_tests.rs | 10 +- mm2src/mm2_bitcoin/script/src/builder.rs | 2 +- mm2src/mm2_main/src/lp_ordermatch.rs | 10 +- mm2src/mm2_main/src/lp_swap.rs | 118 ++++++++++++++-- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 5 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 26 ++-- .../tests/docker_tests/qrc20_tests.rs | 10 +- .../tests/docker_tests/swap_watcher_tests.rs | 16 ++- 26 files changed, 507 insertions(+), 218 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index c69ce6981f..53567b87eb 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -23,7 +23,7 @@ use super::eth::Action::{Call, Create}; use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_structs::{ContractType, ConvertChain, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; -use crate::{ValidateWatcherSpendInput, WatcherSpendType}; +use crate::{DexFee, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -1058,12 +1058,15 @@ impl Deref for EthCoin { #[async_trait] impl SwapOps for EthCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let address = try_tx_fus!(addr_from_raw_pubkey(fee_addr)); Box::new( - self.send_to_address(address, try_tx_fus!(wei_from_big_decimal(&amount, self.decimals))) - .map(TransactionEnum::from), + self.send_to_address( + address, + try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.decimals)), + ) + .map(TransactionEnum::from), ) } @@ -1118,7 +1121,7 @@ impl SwapOps for EthCoin { fee_tx_hash: &tx.hash, expected_sender: validate_fee_args.expected_sender, fee_addr: validate_fee_args.fee_addr, - amount: validate_fee_args.amount, + amount: &validate_fee_args.dex_fee.fee_amount().into(), min_block_number: validate_fee_args.min_block_number, uuid: validate_fee_args.uuid, }) @@ -4901,10 +4904,10 @@ impl MmCoin for EthCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let dex_fee_amount = wei_from_big_decimal(&dex_fee_amount, self.decimals)?; + let dex_fee_amount = wei_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.decimals)?; // pass the dummy params let to_addr = addr_from_raw_pubkey(&DEX_FEE_ADDR_RAW_PUBKEY) diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index dfeff254f9..152f68436f 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::IguanaPrivKey; +use crate::{DexFee, IguanaPrivKey}; use common::{block_on, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use ethkey::{Generator, Random}; @@ -1104,8 +1104,11 @@ fn test_get_fee_to_send_taker_fee() { let dex_fee_amount = u256_to_big_decimal(DEX_FEE_AMOUNT.into(), 18).expect("!u256_to_big_decimal"); let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount.clone(), FeeApproxStage::WithoutApprox)) - .expect("!get_fee_to_send_taker_fee"); + let actual = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount.clone())), + FeeApproxStage::WithoutApprox, + )) + .expect("!get_fee_to_send_taker_fee"); assert_eq!(actual, expected_fee); let (_ctx, coin) = eth_coin_for_test( @@ -1116,8 +1119,11 @@ fn test_get_fee_to_send_taker_fee() { &["http://dummy.dummy"], None, ); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)) - .expect("!get_fee_to_send_taker_fee"); + let actual = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount)), + FeeApproxStage::WithoutApprox, + )) + .expect("!get_fee_to_send_taker_fee"); assert_eq!(actual, expected_fee); } @@ -1143,7 +1149,11 @@ fn test_get_fee_to_send_taker_fee_insufficient_balance() { ); let dex_fee_amount = u256_to_big_decimal(DEX_FEE_AMOUNT.into(), 18).expect("!u256_to_big_decimal"); - let error = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)).unwrap_err(); + let error = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount)), + FeeApproxStage::WithoutApprox, + )) + .unwrap_err(); log!("{}", error); assert!( matches!(error.get_inner(), TradePreimageError::NotSufficientBalance { .. }), @@ -1167,7 +1177,7 @@ fn validate_dex_fee_invalid_sender_eth() { fee_tx: &tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; @@ -1201,7 +1211,7 @@ fn validate_dex_fee_invalid_sender_erc() { fee_tx: &tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; @@ -1239,7 +1249,7 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { fee_tx: &tx, expected_sender: &compressed_public, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 11784793, uuid: &[], }; @@ -1276,7 +1286,7 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { fee_tx: &tx, expected_sender: &compressed_public, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 11823975, uuid: &[], }; diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 74d5619c5d..447990e2b1 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -15,8 +15,8 @@ use crate::lightning::ln_utils::{filter_channels, pay_invoice_with_max_total_clt use crate::utxo::rpc_clients::UtxoRpcClientEnum; use crate::utxo::utxo_common::{big_decimal_from_sat, big_decimal_from_sat_unsigned}; use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, + FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, @@ -612,7 +612,7 @@ impl LightningCoin { #[async_trait] impl SwapOps for LightningCoin { // Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning - fn send_taker_fee(&self, _fee_addr: &[u8], _amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let fut = async move { Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) }; Box::new(fut.boxed().compat()) } @@ -1342,7 +1342,7 @@ impl MmCoin for LightningCoin { // Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { Ok(TradeFee { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 74303ba226..ad33246977 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -820,7 +820,7 @@ pub struct ValidateFeeArgs<'a> { pub fee_tx: &'a TransactionEnum, pub expected_sender: &'a [u8], pub fee_addr: &'a [u8], - pub amount: &'a BigDecimal, + pub dex_fee: &'a DexFee, pub min_block_number: u64, pub uuid: &'a [u8], } @@ -903,7 +903,7 @@ pub enum WatcherRewardError { /// Swap operations (mostly based on the Hash/Time locked transactions implemented by coin wallets). #[async_trait] pub trait SwapOps { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut; + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut; fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; @@ -2673,7 +2673,7 @@ pub trait MmCoin: /// Get transaction fee the Taker has to pay to send a `TakerFee` transaction and check if the wallet has sufficient balance to pay the fee. async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult; @@ -2951,6 +2951,72 @@ impl MmCoinStruct { } } +/// Represents the different types of DEX fees. +#[derive(Clone, Debug, PartialEq)] +pub enum DexFee { + /// Standard dex fee which will be sent to the dex fee address + Standard(MmNumber), + /// Dex fee with the burn amount. + /// - `fee_amount` goes to the dex fee address. + /// - `burn_amount` will be added as `OP_RETURN` output in the dex fee transaction. + WithBurn { + fee_amount: MmNumber, + burn_amount: MmNumber, + }, +} + +impl DexFee { + /// Creates a new `DexFee` with burn amounts. + pub fn with_burn(fee_amount: MmNumber, burn_amount: MmNumber) -> DexFee { + DexFee::WithBurn { + fee_amount, + burn_amount, + } + } + + /// Gets the fee amount associated with the dex fee. + pub fn fee_amount(&self) -> MmNumber { + match self { + DexFee::Standard(t) => t.clone(), + DexFee::WithBurn { fee_amount, .. } => fee_amount.clone(), + } + } + + /// Gets the burn amount associated with the dex fee, if applicable. + pub fn burn_amount(&self) -> Option { + match self { + DexFee::Standard(_) => None, + DexFee::WithBurn { burn_amount, .. } => Some(burn_amount.clone()), + } + } + + /// Calculates the total spend amount, considering both the fee and burn amounts. + pub fn total_spend_amount(&self) -> MmNumber { + match self { + DexFee::Standard(t) => t.clone(), + DexFee::WithBurn { + fee_amount, + burn_amount, + } => fee_amount + burn_amount, + } + } + + /// Converts the fee amount to micro-units based on the specified decimal places. + pub fn fee_uamount(&self, decimals: u8) -> NumConversResult { + let fee_amount = self.fee_amount(); + utxo::sat_from_big_decimal(&fee_amount.into(), decimals) + } + + /// Converts the burn amount to micro-units, if applicable, based on the specified decimal places. + pub fn burn_uamount(&self, decimals: u8) -> NumConversResult> { + if let Some(burn_amount) = self.burn_amount() { + Ok(Some(utxo::sat_from_big_decimal(&burn_amount.into(), decimals)?)) + } else { + Ok(None) + } + } +} + pub struct CoinsContext { /// A map from a currency ticker symbol to the corresponding coin. /// Similar to `LP_coins`. diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index f556ba5032..4043d299bb 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -16,18 +16,19 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, Broadca UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, UtxoFromLegacyReqErr, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom, UTXO_LOCK}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, - FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, - MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, - TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, - TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; + DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, + MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, + TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, + WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -753,9 +754,9 @@ impl UtxoCommonOps for Qrc20Coin { #[async_trait] impl SwapOps for Qrc20Coin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let to_address = try_tx_fus!(self.contract_address_from_raw_pubkey(fee_addr)); - let amount = try_tx_fus!(wei_from_big_decimal(&amount, self.utxo.decimals)); + let amount = try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals)); let transfer_output = try_tx_fus!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)); let outputs = vec![transfer_output]; @@ -874,7 +875,10 @@ impl SwapOps for Qrc20Coin { let fee_addr = try_f!(self .contract_address_from_raw_pubkey(validate_fee_args.fee_addr) .map_to_mm(ValidatePaymentError::WrongPaymentTx)); - let expected_value = try_f!(wei_from_big_decimal(validate_fee_args.amount, self.utxo.decimals)); + let expected_value = try_f!(wei_from_big_decimal( + &validate_fee_args.dex_fee.fee_amount().into(), + self.utxo.decimals + )); let selfi = self.clone(); let fut = async move { @@ -1432,10 +1436,10 @@ impl MmCoin for Qrc20Coin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let amount = wei_from_big_decimal(&dex_fee_amount, self.utxo.decimals)?; + let amount = wei_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.utxo.decimals)?; // pass the dummy params let to_addr = H160::default(); diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index f5aa39ba52..a837e18364 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::utxo::rpc_clients::UnspentInfo; -use crate::{TxFeeDetails, WaitForHTLCTxSpendArgs}; +use crate::{DexFee, TxFeeDetails, WaitForHTLCTxSpendArgs}; use chain::OutPoint; use common::{block_on, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; @@ -309,7 +309,7 @@ fn test_send_taker_fee() { let amount = BigDecimal::from_str("0.01").unwrap(); let tx = coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, amount.clone(), &[]) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, DexFee::Standard(amount.clone().into()), &[]) .wait() .unwrap(); let tx_hash: H256Json = match tx { @@ -323,7 +323,7 @@ fn test_send_taker_fee() { fee_tx: &tx, expected_sender: coin.my_public_key().unwrap(), fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }) @@ -351,7 +351,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 0, uuid: &[], }) @@ -364,7 +364,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &fee_addr_dif, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 0, uuid: &[], }) @@ -382,7 +382,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 0, uuid: &[], }) @@ -400,7 +400,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 2000000, uuid: &[], }) @@ -419,7 +419,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount_dif, + dex_fee: &DexFee::Standard(amount_dif.into()), min_block_number: 0, uuid: &[], }) @@ -442,7 +442,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }) @@ -949,8 +949,11 @@ fn test_taker_fee_tx_fee() { assert_eq!(coin.my_balance().wait().expect("!my_balance"), expected_balance); let dex_fee_amount = BigDecimal::from(5u32); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)) - .expect("!get_fee_to_send_taker_fee"); + let actual = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount)), + FeeApproxStage::WithoutApprox, + )) + .expect("!get_fee_to_send_taker_fee"); // only one contract call should be included into the expected trade fee let expected_receiver_fee = big_decimal_from_sat(CONTRACT_CALL_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals); let expected = TradeFee { diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 56178efa7a..025f4c6c7d 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -2,18 +2,18 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, Trade use crate::coin_errors::MyAddressError; use crate::solana::solana_common::{lamports_to_sol, PrepareTransferData, SufficientBalanceError}; use crate::solana::spl::SplTokenInfo; -use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, - RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, - SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; +use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, + FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -468,7 +468,7 @@ impl MarketCoinOps for SolanaCoin { #[async_trait] impl SwapOps for SolanaCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } + fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { unimplemented!() } fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } @@ -745,7 +745,7 @@ impl MmCoin for SolanaCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { unimplemented!() diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 9270302b0b..0777e278e5 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -2,7 +2,7 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, Trade use crate::coin_errors::MyAddressError; use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, SufficientBalanceError}; use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, @@ -289,7 +289,7 @@ impl MarketCoinOps for SplToken { #[async_trait] impl SwapOps for SplToken { - fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } + fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { unimplemented!() } fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } @@ -539,7 +539,7 @@ impl MmCoin for SplToken { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { unimplemented!() diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 6d0432d703..89b79573c3 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -15,8 +15,8 @@ use crate::tendermint::ibc::IBC_OUT_SOURCE_PORT; use crate::utxo::sat_from_big_decimal; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, CheckIfMyPaymentSentArgs, - CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, - MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, + HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, @@ -1612,11 +1612,11 @@ impl TendermintCoin { ticker: String, denom: Denom, decimals: u8, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, ) -> TradePreimageResult { let to_address = account_id_from_pubkey_hex(&self.account_prefix, DEX_FEE_ADDR_PUBKEY) .map_err(|e| MmError::new(TradePreimageError::InternalError(e.into_inner().to_string())))?; - let amount = sat_from_big_decimal(&dex_fee_amount, decimals)?; + let amount = sat_from_big_decimal(&dex_fee_amount.fee_amount().into(), decimals)?; let current_block = self.current_block().compat().await.map_err(|e| { MmError::new(TradePreimageError::InternalError(format!( @@ -2174,7 +2174,7 @@ impl MmCoin for TendermintCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { self.get_fee_to_send_taker_fee_for_denom(self.ticker.clone(), self.denom.clone(), self.decimals, dex_fee_amount) @@ -2421,8 +2421,14 @@ impl MarketCoinOps for TendermintCoin { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { - self.send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { + self.send_taker_fee_for_denom( + fee_addr, + dex_fee.fee_amount().into(), + self.denom.clone(), + self.decimals, + uuid, + ) } fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { @@ -2570,7 +2576,7 @@ impl SwapOps for TendermintCoin { validate_fee_args.fee_tx, validate_fee_args.expected_sender, validate_fee_args.fee_addr, - validate_fee_args.amount, + &validate_fee_args.dex_fee.fee_amount().into(), self.decimals, validate_fee_args.uuid, self.denom.to_string(), @@ -3185,13 +3191,13 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(create_htlc_tx_bytes.as_slice()).unwrap(), }); - let invalid_amount = 1.into(); + let invalid_amount: MmNumber = 1.into(); let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &create_htlc_tx, expected_sender: &[], fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &invalid_amount, + dex_fee: &DexFee::Standard(invalid_amount.clone()), min_block_number: 0, uuid: &[1; 16], }) @@ -3225,7 +3231,7 @@ pub mod tendermint_coin_tests { fee_tx: &random_transfer_tx, expected_sender: &[], fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &invalid_amount, + dex_fee: &DexFee::Standard(invalid_amount.clone()), min_block_number: 0, uuid: &[1; 16], }) @@ -3258,7 +3264,7 @@ pub mod tendermint_coin_tests { fee_tx: &dex_fee_tx, expected_sender: &[], fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &invalid_amount, + dex_fee: &DexFee::Standard(invalid_amount), min_block_number: 0, uuid: &[1; 16], }) @@ -3278,7 +3284,7 @@ pub mod tendermint_coin_tests { fee_tx: &dex_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &valid_amount, + dex_fee: &DexFee::Standard(valid_amount.clone().into()), min_block_number: 0, uuid: &[1; 16], }) @@ -3297,7 +3303,7 @@ pub mod tendermint_coin_tests { fee_tx: &dex_fee_tx, expected_sender: &pubkey, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &valid_amount, + dex_fee: &DexFee::Standard(valid_amount.into()), min_block_number: 0, uuid: &[1; 16], }) diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 061478aa09..93c5eb56f7 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -20,7 +20,7 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFrom, WithdrawFut, WithdrawRequest}; -use crate::{MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; +use crate::{DexFee, MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -268,9 +268,14 @@ impl TendermintToken { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintToken { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { - self.platform_coin - .send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { + self.platform_coin.send_taker_fee_for_denom( + fee_addr, + dex_fee.fee_amount().into(), + self.denom.clone(), + self.decimals, + uuid, + ) } fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { @@ -322,7 +327,7 @@ impl SwapOps for TendermintToken { validate_fee_args.fee_tx, validate_fee_args.expected_sender, validate_fee_args.fee_addr, - validate_fee_args.amount, + &validate_fee_args.dex_fee.fee_amount().into(), self.decimals, validate_fee_args.uuid, self.denom.to_string(), @@ -835,7 +840,7 @@ impl MmCoin for TendermintToken { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { self.platform_coin diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index d21b438f58..d4da2109bd 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -16,7 +16,7 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; -use crate::{ToBytes, ValidateWatcherSpendInput}; +use crate::{DexFee, ToBytes, ValidateWatcherSpendInput}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -97,7 +97,7 @@ impl MarketCoinOps for TestCoin { fn display_priv_key(&self) -> Result { unimplemented!() } - fn min_tx_amount(&self) -> BigDecimal { unimplemented!() } + fn min_tx_amount(&self) -> BigDecimal { Default::default() } fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } } @@ -105,7 +105,7 @@ impl MarketCoinOps for TestCoin { #[async_trait] #[mockable] impl SwapOps for TestCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { unimplemented!() } + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { unimplemented!() } fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } @@ -351,7 +351,7 @@ impl MmCoin for TestCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { unimplemented!() diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index f584ccb6bf..072719eeee 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -10,7 +10,7 @@ use crate::utxo::utxo_common::big_decimal_from_sat_unsigned; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, - CoinWithDerivationMethod, ConfirmPaymentInput, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, + CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, @@ -840,8 +840,8 @@ impl UtxoCommonOps for BchCoin { #[async_trait] impl SwapOps for BchCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { - utxo_common::send_taker_fee(self.clone(), fee_addr, amount) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { + utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) } #[inline] @@ -884,7 +884,7 @@ impl SwapOps for BchCoin { tx, utxo_common::DEFAULT_FEE_VOUT, validate_fee_args.expected_sender, - validate_fee_args.amount, + validate_fee_args.dex_fee, validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) @@ -1268,7 +1268,7 @@ impl MmCoin for BchCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { utxo_common::get_fee_to_send_taker_fee(self, dex_fee_amount, stage).await diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index e901a53e94..a66ae04331 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -25,15 +25,15 @@ use crate::utxo::utxo_builder::{MergeUtxoArcOps, UtxoCoinBuildError, UtxoCoinBui use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - DelegationError, DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, StakingInfosFut, - SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + DelegationError, DelegationFut, DexFee, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, + MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, + TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; @@ -526,8 +526,8 @@ impl UtxoStandardOps for QtumCoin { #[async_trait] impl SwapOps for QtumCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { - utxo_common::send_taker_fee(self.clone(), fee_addr, amount) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { + utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) } #[inline] @@ -570,7 +570,7 @@ impl SwapOps for QtumCoin { tx, utxo_common::DEFAULT_FEE_VOUT, validate_fee_args.expected_sender, - validate_fee_args.amount, + validate_fee_args.dex_fee, validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) @@ -939,7 +939,7 @@ impl MmCoin for QtumCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { utxo_common::get_fee_to_send_taker_fee(self, dex_fee_amount, stage).await diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index e400b3e128..da2a51224b 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -13,8 +13,8 @@ use crate::utxo::utxo_common::{self, big_decimal_from_sat_unsigned, payment_scri use crate::utxo::{generate_and_send_tx, sat_from_big_decimal, ActualTxFee, AdditionalTxData, BroadcastTxErr, FeePolicy, GenerateTxError, RecentlySpentOutPointsGuard, UtxoCoinConf, UtxoCoinFields, UtxoCommonOps, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, + FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, @@ -750,7 +750,7 @@ impl SlpToken { tx, SLP_FEE_VOUT, expected_sender, - &self.platform_dust_dec(), + &DexFee::Standard(self.platform_dust_dec().into()), min_block_number, fee_addr, ); @@ -1193,11 +1193,11 @@ impl MarketCoinOps for SlpToken { #[async_trait] impl SwapOps for SlpToken { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let coin = self.clone(); let fee_pubkey = try_tx_fus!(Public::from_slice(fee_addr)); let script_pubkey = ScriptBuilder::build_p2pkh(&fee_pubkey.address_hash().into()).into(); - let amount = try_tx_fus!(sat_from_big_decimal(&amount, self.decimals())); + let amount = try_tx_fus!(dex_fee.fee_uamount(self.decimals())); let fut = async move { let slp_out = SlpOutput { amount, script_pubkey }; @@ -1326,11 +1326,11 @@ impl SwapOps for SlpToken { let coin = self.clone(); let expected_sender = validate_fee_args.expected_sender.to_owned(); let fee_addr = validate_fee_args.fee_addr.to_owned(); - let amount = validate_fee_args.amount.to_owned(); + let amount = validate_fee_args.dex_fee.fee_amount(); let min_block_number = validate_fee_args.min_block_number; let fut = async move { - coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount, min_block_number) + coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount.into(), min_block_number) .await .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string())))?; Ok(()) @@ -1818,10 +1818,10 @@ impl MmCoin for SlpToken { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let slp_amount = sat_from_big_decimal(&dex_fee_amount, self.decimals())?; + let slp_amount = sat_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.decimals())?; // can use dummy P2PKH script_pubkey here let script_pubkey = ScriptBuilder::build_p2pkh(&H160::default().into()).into(); let slp_out = SlpOutput { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 82e33e4b36..2354d8ba44 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,7 +15,7 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::watcher_common::validate_watcher_reward; -use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenPreimageResult, +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, @@ -1668,7 +1668,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( Ok(final_tx.into()) } -pub fn send_taker_fee(coin: T, fee_pub_key: &[u8], amount: BigDecimal) -> TransactionFut +pub fn send_taker_fee(coin: T, fee_pub_key: &[u8], dex_fee: DexFee) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps, { @@ -1680,12 +1680,36 @@ where coin.as_ref().conf.bech32_hrp.clone(), coin.addr_format().clone(), )); - let amount = try_tx_fus!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); - let output = TransactionOutput { - value: amount, - script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), - }; - send_outputs_from_my_address(coin, vec![output]) + + let outputs = try_tx_fus!(generate_taker_fee_tx_outputs( + coin.as_ref().decimals, + &address.hash, + dex_fee, + )); + + send_outputs_from_my_address(coin, outputs) +} + +fn generate_taker_fee_tx_outputs( + decimals: u8, + address_hash: &AddressHashEnum, + dex_fee: DexFee, +) -> Result, MmError> { + let fee_amount = dex_fee.fee_uamount(decimals)?; + + let mut outputs = vec![TransactionOutput { + value: fee_amount, + script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), + }]; + + if let Some(burn_amount) = dex_fee.burn_uamount(decimals)? { + outputs.push(TransactionOutput { + value: burn_amount, + script_pubkey: Builder::default().push_opcode(Opcode::OP_RETURN).into_bytes(), + }); + } + + Ok(outputs) } pub fn send_maker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut @@ -2329,11 +2353,10 @@ pub fn validate_fee( tx: UtxoTx, output_index: usize, sender_pubkey: &[u8], - amount: &BigDecimal, + dex_amount: &DexFee, min_block_number: u64, fee_addr: &[u8], ) -> ValidatePaymentFut<()> { - let amount = amount.clone(); let address = try_f!(address_from_raw_pubkey( fee_addr, coin.as_ref().conf.pub_addr_prefix, @@ -2354,8 +2377,10 @@ pub fn validate_fee( )); } + let fee_amount = try_f!(dex_amount.fee_uamount(coin.as_ref().decimals)); + let burn_amount = try_f!(dex_amount.burn_uamount(coin.as_ref().decimals)); + let fut = async move { - let amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; let tx_from_rpc = coin .as_ref() .rpc_client @@ -2391,10 +2416,10 @@ pub fn validate_fee( INVALID_RECEIVER_ERR_LOG, out.script_pubkey, expected_script_pubkey ))); } - if out.value < amount { + if out.value < fee_amount { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx output value is less than expected {:?} {:?}", - out.value, amount + out.value, fee_amount ))); } }, @@ -2405,6 +2430,35 @@ pub fn validate_fee( ))) }, } + + if let Some(burn_amount) = burn_amount { + match tx.outputs.get(output_index + 1) { + Some(out) => { + let expected_script_pubkey = Builder::default().push_opcode(Opcode::OP_RETURN).into_bytes(); + + if out.script_pubkey != expected_script_pubkey { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Provided burn tx output script_pubkey doesn't match expected {:?} {:?}", + INVALID_RECEIVER_ERR_LOG, out.script_pubkey, expected_script_pubkey + ))); + } + + if out.value < burn_amount { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Provided burn tx output value is less than expected {:?} {:?}", + out.value, burn_amount + ))); + } + }, + None => { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Provided burn tx output {:?} does not have output {}", + tx, output_index + ))) + }, + } + } + Ok(()) }; Box::new(fut.boxed().compat()) @@ -3999,21 +4053,19 @@ pub fn get_receiver_trade_fee(coin: T) -> TradePreimageFut( coin: &T, - dex_fee_amount: BigDecimal, + dex_fee: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult where T: MarketCoinOps + UtxoCommonOps, { let decimals = coin.as_ref().decimals; - let value = sat_from_big_decimal(&dex_fee_amount, decimals)?; - let output = TransactionOutput { - value, - script_pubkey: Builder::build_p2pkh(&AddressHashEnum::default_address_hash()).to_bytes(), - }; + + let outputs = generate_taker_fee_tx_outputs(decimals, &AddressHashEnum::default_address_hash(), dex_fee)?; + let gas_fee = None; let fee_amount = coin - .preimage_trade_fee_required_to_send_outputs(vec![output], FeePolicy::SendExact, gas_fee, &stage) + .preimage_trade_fee_required_to_send_outputs(outputs, FeePolicy::SendExact, gas_fee, &stage) .await?; Ok(TradeFee { coin: coin.ticker().to_owned(), @@ -5057,3 +5109,42 @@ fn test_tx_v_size() { let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Segwit, &tx); assert_eq!(v_size, 209) } + +#[test] +fn test_generate_taker_fee_tx_outputs() { + let amount = BigDecimal::from(6150); + let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); + + let outputs = generate_taker_fee_tx_outputs( + 8, + &AddressHashEnum::default_address_hash(), + DexFee::Standard(amount.into()), + ) + .unwrap(); + + assert_eq!(outputs.len(), 1); + + assert_eq!(outputs[0].value, fee_amount); +} + +#[test] +fn test_generate_taker_fee_tx_outputs_with_burn() { + let fee_amount = BigDecimal::from(6150); + let burn_amount = &(&fee_amount / &BigDecimal::from_str("0.75").unwrap()) - &fee_amount; + + let fee_uamount = sat_from_big_decimal(&fee_amount, 8).unwrap(); + let burn_uamount = sat_from_big_decimal(&burn_amount, 8).unwrap(); + + let outputs = generate_taker_fee_tx_outputs( + 8, + &AddressHashEnum::default_address_hash(), + DexFee::with_burn(fee_amount.into(), burn_amount.into()), + ) + .unwrap(); + + assert_eq!(outputs.len(), 2); + + assert_eq!(outputs[0].value, fee_uamount); + + assert_eq!(outputs[1].value, burn_uamount); +} diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 26bc6bdc88..ab809ccfc5 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,7 +23,7 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, + DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, @@ -294,8 +294,8 @@ impl UtxoStandardOps for UtxoStandardCoin { #[async_trait] impl SwapOps for UtxoStandardCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { - utxo_common::send_taker_fee(self.clone(), fee_addr, amount) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { + utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) } #[inline] @@ -338,7 +338,7 @@ impl SwapOps for UtxoStandardCoin { tx, utxo_common::DEFAULT_FEE_VOUT, validate_fee_args.expected_sender, - validate_fee_args.amount, + validate_fee_args.dex_fee, validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) @@ -804,7 +804,7 @@ impl MmCoin for UtxoStandardCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { utxo_common::get_fee_to_send_taker_fee(self, dex_fee_amount, stage).await diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 265b1d94d2..88d60142bd 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -28,7 +28,7 @@ use crate::utxo::utxo_common_tests::{self, utxo_coin_fields_for_test, utxo_coin_ use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps}; #[cfg(not(target_arch = "wasm32"))] use crate::WithdrawFee; -use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, IguanaPrivKey, PrivKeyBuildPolicy, +use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, DexFee, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, WaitForHTLCTxSpendArgs, INVALID_SENDER_ERR_LOG}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; @@ -2520,7 +2520,7 @@ fn test_validate_fee_wrong_sender() { fee_tx: &taker_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; @@ -2545,7 +2545,7 @@ fn test_validate_fee_min_block() { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 278455, uuid: &[], }; @@ -2574,7 +2574,7 @@ fn test_validate_fee_bch_70_bytes_signature() { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index af7d816435..65e2729361 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -9,23 +9,23 @@ use crate::utxo::utxo_builder::UtxoCoinBuildError; use crate::utxo::utxo_builder::{UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaSecretBuilder}; use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, payment_script}; -use crate::utxo::{sat_from_big_decimal, utxo_common, ActualTxFee, AdditionalTxData, AddrFromStrError, Address, - BroadcastTxErr, FeePolicy, GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, - RecentlySpentOutPointsGuard, UtxoActivationParams, UtxoAddressFormat, UtxoArc, UtxoCoinFields, - UtxoCommonOps, UtxoRpcMode, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom}; +use crate::utxo::{utxo_common, ActualTxFee, AdditionalTxData, AddrFromStrError, Address, BroadcastTxErr, FeePolicy, + GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, RecentlySpentOutPointsGuard, + UtxoActivationParams, UtxoAddressFormat, UtxoArc, UtxoCoinFields, UtxoCommonOps, UtxoRpcMode, + UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, - FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, - SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionEnum, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, - VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, + MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, + PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionEnum, TransactionFut, TransactionResult, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -82,7 +82,7 @@ pub use z_rpc::{FirstSyncBlock, SyncStatus}; cfg_native!( use crate::{NumConversError, TransactionDetails, TxFeeDetails}; - use crate::utxo::UtxoFeeDetails; + use crate::utxo::{UtxoFeeDetails, sat_from_big_decimal}; use crate::utxo::utxo_common::{addresses_from_script, big_decimal_from_sat}; use common::{async_blocking, calc_total_pages, PagingOptionsEnum}; @@ -1180,11 +1180,11 @@ impl MarketCoinOps for ZCoin { #[async_trait] impl SwapOps for ZCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { let selfi = self.clone(); let uuid = uuid.to_owned(); let fut = async move { - let tx = try_tx_s!(z_send_dex_fee(&selfi, amount, &uuid).await); + let tx = try_tx_s!(z_send_dex_fee(&selfi, dex_fee.fee_amount().into(), &uuid).await); Ok(tx.into()) }; Box::new(fut.boxed().compat()) @@ -1354,7 +1354,7 @@ impl SwapOps for ZCoin { TransactionEnum::ZTransaction(t) => t.clone(), _ => panic!("Unexpected tx {:?}", validate_fee_args.fee_tx), }; - let amount_sat = try_f!(sat_from_big_decimal(validate_fee_args.amount, self.utxo_arc.decimals)); + let amount_sat = try_f!(validate_fee_args.dex_fee.fee_uamount(self.utxo_arc.decimals)); let expected_memo = MemoBytes::from_bytes(validate_fee_args.uuid).expect("Uuid length < 512"); let min_block_number = validate_fee_args.min_block_number; @@ -1717,7 +1717,7 @@ impl MmCoin for ZCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { Ok(TradeFee { diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 12789b2090..c2a0b66201 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -11,6 +11,8 @@ use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, Future, ZTransaction}; use crate::z_coin::{z_htlc::z_send_dex_fee, ZcoinActivationParams, ZcoinRpcMode}; use crate::CoinProtocol; +use crate::DexFee; +use mm2_number::MmNumber; #[test] fn zombie_coin_send_and_refund_maker_payment() { @@ -228,7 +230,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.001".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.001")), min_block_number: 12000, uuid: &[1; 16], }; @@ -244,7 +246,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.01".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.01")), min_block_number: 12000, uuid: &[2; 16], }; @@ -259,7 +261,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.01".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.01")), min_block_number: 14000, uuid: &[1; 16], }; @@ -274,7 +276,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.01".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.01")), min_block_number: 12000, uuid: &[1; 16], }; diff --git a/mm2src/mm2_bitcoin/script/src/builder.rs b/mm2src/mm2_bitcoin/script/src/builder.rs index 4dfc3e4d07..281cc0f185 100644 --- a/mm2src/mm2_bitcoin/script/src/builder.rs +++ b/mm2src/mm2_bitcoin/script/src/builder.rs @@ -78,7 +78,7 @@ impl Builder { pub fn push_bytes(mut self, bytes: &[u8]) -> Self { let len = bytes.len(); if !(1..=75).contains(&len) { - panic!("Canot push {} bytes", len); + panic!("Can not push {} bytes", len); } let opcode: Opcode = Opcode::from_u8(((Opcode::OP_PUSHBYTES_1 as usize) + len - 1) as u8) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 8dfe4b7e30..92055cf43d 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2974,7 +2974,10 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO maker_volume: maker_amount, secret, taker_coin: t.clone(), - dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount), + // TODO: + // Support KMD burning for v2 + dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount) + .total_spend_amount(), taker_volume: taker_amount, taker_premium: Default::default(), conf_settings: my_conf_settings, @@ -3121,7 +3124,10 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat maker_coin: m.clone(), maker_volume: maker_amount, taker_coin: t.clone(), - dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), + // TODO: + // Support KMD burning for v2 + dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount) + .total_spend_amount(), taker_volume: taker_amount, taker_premium: Default::default(), secret_hash_algo, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 4f0a704f16..8d207542fc 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -60,7 +60,7 @@ use super::lp_network::P2PRequestResult; use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; use bitcrypto::{dhash160, sha256}; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::now_sec; use common::time_cache::DuplicateCache; @@ -742,18 +742,44 @@ fn dex_fee_rate(base: &str, rel: &str) -> MmNumber { } } -pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amount: &MmNumber) -> MmNumber { +pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amount: &MmNumber) -> DexFee { let rate = dex_fee_rate(base, rel); - let fee_amount = trade_amount * &rate; - if &fee_amount < min_tx_amount { - min_tx_amount.clone() - } else { - fee_amount + let fee = trade_amount * &rate; + + if &fee <= min_tx_amount { + return DexFee::Standard(min_tx_amount.clone()); + } + + if base == "KMD" { + // Drop the fee by 25%, which will be burned during the taker fee payment. + // + // This cut will be dropped before return if the final amount is less than + // the minimum transaction amount. + + // Fee with 25% cut + let new_fee = &fee * &MmNumber::from("0.75"); + + let (fee, burn) = if &new_fee >= min_tx_amount { + // Use the max burn value, which is 25%. + let burn_amount = &fee - &new_fee; + + (new_fee, burn_amount) + } else { + // Burn only the exceed amount because fee after 25% cut is less + // than `min_tx_amount`. + let burn_amount = &fee - min_tx_amount; + + (min_tx_amount.clone(), burn_amount) + }; + + return DexFee::with_burn(fee, burn); } + + DexFee::Standard(fee) } /// Calculates DEX fee with a threshold based on min tx amount of the taker coin. -pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { +pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> DexFee { let min_tx_amount = MmNumber::from(taker_coin.min_tx_amount()); dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount) } @@ -1650,28 +1676,43 @@ mod lp_swap_tests { let rel = "ETH"; let amount = 1.into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - let expected_fee = amount / 777u64.into(); + let expected_fee = DexFee::Standard(amount / 777u64.into()); assert_eq!(expected_fee, actual_fee); let base = "KMD"; let rel = "ETH"; let amount = 1.into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - let expected_fee = amount * (9, 7770).into(); - assert_eq!(expected_fee, actual_fee); + let expected_fee = amount.clone() * (9, 7770).into() * MmNumber::from("0.75"); + let expected_burn_amount = amount * (9, 7770).into() * MmNumber::from("0.25"); + assert_eq!(DexFee::with_burn(expected_fee, expected_burn_amount), actual_fee); + + // check the case when KMD taker fee is close to dust + let base = "KMD"; + let rel = "BTC"; + let amount = (1001 * 777, 90000000).into(); + let min_tx_amount = "0.00001".into(); + let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); + assert_eq!( + DexFee::WithBurn { + fee_amount: "0.00001".into(), + burn_amount: "0.00000001".into() + }, + actual_fee + ); let base = "BTC"; let rel = "KMD"; let amount = 1.into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - let expected_fee = amount * (9, 7770).into(); + let expected_fee = DexFee::Standard(amount * (9, 7770).into()); assert_eq!(expected_fee, actual_fee); let base = "BTC"; let rel = "KMD"; let amount: MmNumber = "0.001".parse::().unwrap().into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - assert_eq!(min_tx_amount, actual_fee); + assert_eq!(DexFee::Standard(min_tx_amount), actual_fee); } #[test] @@ -2182,4 +2223,55 @@ mod lp_swap_tests { let _: SavedSwap = json::from_str(include_str!("for_tests/iris_nimda_rick_taker_swap.json")).unwrap(); let _: SavedSwap = json::from_str(include_str!("for_tests/iris_nimda_rick_maker_swap.json")).unwrap(); } + + #[test] + fn test_kmd_taker_dex_fee_calculation() { + std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); + + let kmd = coins::TestCoin::new("KMD"); + let (kmd_taker_fee, kmd_burn_amount) = match dex_fee_amount_from_taker_coin(&kmd, "", &MmNumber::from(6150)) { + DexFee::Standard(_) => panic!("Wrong variant returned for KMD from `dex_fee_amount_from_taker_coin`."), + DexFee::WithBurn { + fee_amount, + burn_amount, + } => (fee_amount, burn_amount), + }; + + let mycoin = coins::TestCoin::new("MYCOIN"); + let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)) { + DexFee::Standard(t) => t, + DexFee::WithBurn { .. } => { + panic!("Wrong variant returned for MYCOIN from `dex_fee_amount_from_taker_coin`.") + }, + }; + + let expected_mycoin_taker_fee = &kmd_taker_fee / &MmNumber::from("0.75"); + let expected_kmd_burn_amount = &mycoin_taker_fee - &kmd_taker_fee; + + assert_eq!(expected_mycoin_taker_fee, mycoin_taker_fee); + assert_eq!(expected_kmd_burn_amount, kmd_burn_amount); + } + + #[test] + fn test_dex_fee_amount_from_taker_coin_discount() { + std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); + + let mycoin = coins::TestCoin::new("MYCOIN"); + let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)) { + DexFee::Standard(t) => t, + DexFee::WithBurn { .. } => { + panic!("Wrong variant returned for MYCOIN from `dex_fee_amount_from_taker_coin`.") + }, + }; + + let testcoin = coins::TestCoin::default(); + let testcoin_taker_fee = match dex_fee_amount_from_taker_coin(&testcoin, "", &MmNumber::from(6150)) { + DexFee::Standard(t) => t, + DexFee::WithBurn { .. } => { + panic!("Wrong variant returned for TEST coin from `dex_fee_amount_from_taker_coin`.") + }, + }; + + assert_eq!(testcoin_taker_fee * MmNumber::from("0.90"), mycoin_taker_fee); + } } diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 67f1b6285a..9608213eaa 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -748,8 +748,7 @@ impl MakerSwap { info!("Taker fee tx {:02x}", hash); let taker_amount = MmNumber::from(self.taker_amount.clone()); - let fee_amount = - dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); + let dex_fee = dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; let taker_coin_start_block = self.r().data.taker_coin_start_block; @@ -761,7 +760,7 @@ impl MakerSwap { fee_tx: &taker_fee, expected_sender: &*other_taker_coin_htlc_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &fee_amount.clone().into(), + dex_fee: &dex_fee, min_block_number: taker_coin_start_block, uuid: self.uuid.as_bytes(), }) diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index f16bdd129d..a60ff2e149 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -998,7 +998,7 @@ impl TakerSwap { let fee_to_send_dex_fee_fut = self .taker_coin - .get_fee_to_send_taker_fee(dex_fee.to_decimal(), stage.clone()); + .get_fee_to_send_taker_fee(dex_fee.clone(), stage.clone()); let fee_to_send_dex_fee = match fee_to_send_dex_fee_fut.await { Ok(fee) => fee, Err(e) => { @@ -1027,7 +1027,7 @@ impl TakerSwap { }; let params = TakerSwapPreparedParams { - dex_fee: dex_fee.clone(), + dex_fee: dex_fee.total_spend_amount(), fee_to_send_dex_fee: fee_to_send_dex_fee.clone(), taker_payment_trade_fee: taker_payment_trade_fee.clone(), maker_payment_spend_trade_fee: maker_payment_spend_trade_fee.clone(), @@ -1272,7 +1272,7 @@ impl TakerSwap { dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &self.taker_amount); let fee_tx = self .taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), self.uuid.as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, self.uuid.as_bytes()) .compat() .await; let transaction = match fee_tx { @@ -2291,7 +2291,7 @@ impl AtomicSwap for TakerSwap { if self.r().taker_fee.is_none() { result.push(LockedAmount { coin: self.taker_coin.ticker().to_owned(), - amount: taker_fee_amount, + amount: taker_fee_amount.total_spend_amount(), trade_fee, }); } @@ -2356,7 +2356,7 @@ pub async fn check_balance_for_taker_swap( None => { let dex_fee = dex_fee_amount_from_taker_coin(my_coin, other_coin.ticker(), &volume); let fee_to_send_dex_fee = my_coin - .get_fee_to_send_taker_fee(dex_fee.to_decimal(), stage.clone()) + .get_fee_to_send_taker_fee(dex_fee.clone(), stage.clone()) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); @@ -2370,7 +2370,7 @@ pub async fn check_balance_for_taker_swap( .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, other_coin.ticker()))?; TakerSwapPreparedParams { - dex_fee, + dex_fee: dex_fee.total_spend_amount(), fee_to_send_dex_fee, taker_payment_trade_fee, maker_payment_spend_trade_fee, @@ -2445,12 +2445,12 @@ pub async fn taker_swap_trade_preimage( let dex_amount = dex_fee_amount_from_taker_coin(my_coin.deref(), other_coin_ticker, &my_coin_volume); let taker_fee = TradeFee { coin: my_coin_ticker.to_owned(), - amount: dex_amount.clone(), + amount: dex_amount.total_spend_amount(), paid_from_trading_vol: false, }; let fee_to_send_taker_fee = my_coin - .get_fee_to_send_taker_fee(dex_amount.to_decimal(), stage.clone()) + .get_fee_to_send_taker_fee(dex_amount.clone(), stage.clone()) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, my_coin_ticker))?; @@ -2466,7 +2466,7 @@ pub async fn taker_swap_trade_preimage( .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, other_coin_ticker))?; let prepared_params = TakerSwapPreparedParams { - dex_fee: dex_amount, + dex_fee: dex_amount.total_spend_amount(), fee_to_send_dex_fee: fee_to_send_taker_fee.clone(), taker_payment_trade_fee: my_coin_trade_fee.clone(), maker_payment_spend_trade_fee: other_coin_trade_fee.clone(), @@ -2590,7 +2590,7 @@ pub async fn calc_max_taker_vol( let max_possible_2 = &max_possible - &max_trade_fee.amount; let max_dex_fee = dex_fee_amount_from_taker_coin(coin.deref(), other_coin, &max_possible_2); let max_fee_to_send_taker_fee = coin - .get_fee_to_send_taker_fee(max_dex_fee.to_decimal(), stage) + .get_fee_to_send_taker_fee(max_dex_fee.clone(), stage) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin))?; let min_max_possible = &max_possible_2 - &max_fee_to_send_taker_fee.amount; @@ -2601,7 +2601,7 @@ pub async fn calc_max_taker_vol( balance.to_fraction(), locked.to_fraction(), max_trade_fee.amount.to_fraction(), - max_dex_fee.to_fraction(), + max_dex_fee.total_spend_amount().to_fraction(), max_fee_to_send_taker_fee.amount.to_fraction() ); max_taker_vol_from_available(min_max_possible, my_coin, other_coin, &min_tx_amount) @@ -3078,7 +3078,7 @@ mod taker_swap_tests { let max_taker_vol = max_taker_vol_from_available(available.clone(), "RICK", "MORTY", &min_tx_amount) .expect("!max_taker_vol_from_available"); - let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount); + let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount).fee_amount(); assert!(min_tx_amount < dex_fee); assert!(min_tx_amount <= max_taker_vol); assert_eq!(max_taker_vol + dex_fee, available); @@ -3098,7 +3098,7 @@ mod taker_swap_tests { let base = if is_kmd { "KMD" } else { "RICK" }; let max_taker_vol = max_taker_vol_from_available(available.clone(), base, "MORTY", &min_tx_amount) .expect("!max_taker_vol_from_available"); - let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount); + let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount).fee_amount(); println!( "available={:?} max_taker_vol={:?} dex_fee={:?}", available.to_decimal(), diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 03c484275d..109e31b6cc 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -1054,12 +1054,12 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let max_possible_2 = &qtum_balance - &max_trade_fee; // - `max_dex_fee = dex_fee(max_possible_2)` let max_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &MmNumber::from(max_possible_2), &qtum_min_tx_amount); - debug!("max_dex_fee: {:?}", max_dex_fee.to_fraction()); + debug!("max_dex_fee: {:?}", max_dex_fee.fee_amount().to_fraction()); // - `max_fee_to_send_taker_fee = fee_to_send_taker_fee(max_dex_fee)` // `taker_fee` is sent using general withdraw, and the fee get be obtained from withdraw result let max_fee_to_send_taker_fee = - block_on(coin.get_fee_to_send_taker_fee(max_dex_fee.to_decimal(), FeeApproxStage::TradePreimage)) + block_on(coin.get_fee_to_send_taker_fee(max_dex_fee, FeeApproxStage::TradePreimage)) .expect("!get_fee_to_send_taker_fee"); let max_fee_to_send_taker_fee = max_fee_to_send_taker_fee.amount.to_decimal(); debug!("max_fee_to_send_taker_fee: {}", max_fee_to_send_taker_fee); @@ -1072,7 +1072,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let expected_max_taker_vol = max_taker_vol_from_available(MmNumber::from(available), "QTUM", "MYCOIN", &min_tx_amount) .expect("max_taker_vol_from_available"); - let real_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); + let real_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount).fee_amount(); debug!("real_max_dex_fee: {:?}", real_dex_fee.to_fraction()); // check if the actual max_taker_vol equals to the expected @@ -1105,9 +1105,9 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let timelock = now_sec() - 200; let secret_hash = &[0; 20]; - let dex_fee_amount = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); + let dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); let _taker_fee_tx = coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount.to_decimal(), &[]) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee, &[]) .wait() .expect("!send_taker_fee"); let taker_payment_args = SendPaymentArgs { diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 4f3a10593f..00348f66c0 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -738,8 +738,9 @@ fn test_watcher_spends_maker_payment_eth_utxo() { let mycoin_volume = BigDecimal::from_str("1").unwrap(); let min_tx_amount = BigDecimal::from_str("0.00001").unwrap().into(); - let dex_fee: BigDecimal = - dex_fee_amount("MYCOIN", "ETH", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount).into(); + let dex_fee: BigDecimal = dex_fee_amount("MYCOIN", "ETH", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount) + .fee_amount() + .into(); let alice_mycoin_reward_sent = balances.alice_acoin_balance_before - balances.alice_acoin_balance_after.clone() - mycoin_volume.clone() @@ -878,8 +879,9 @@ fn test_watcher_spends_maker_payment_erc20_utxo() { let jst_volume = BigDecimal::from_str("1").unwrap(); let min_tx_amount = BigDecimal::from_str("0.00001").unwrap().into(); - let dex_fee: BigDecimal = - dex_fee_amount("MYCOIN", "JST", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount).into(); + let dex_fee: BigDecimal = dex_fee_amount("MYCOIN", "JST", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount) + .fee_amount() + .into(); let alice_mycoin_reward_sent = balances.alice_acoin_balance_before - balances.alice_acoin_balance_after.clone() - mycoin_volume.clone() @@ -1130,7 +1132,7 @@ fn test_watcher_validate_taker_fee_utxo() { let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, maker_coin.ticker(), &taker_amount); let taker_fee = taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, Uuid::new_v4().as_bytes()) .wait() .unwrap(); @@ -1251,7 +1253,7 @@ fn test_watcher_validate_taker_fee_eth() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); let taker_fee = taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, Uuid::new_v4().as_bytes()) .wait() .unwrap(); @@ -1354,7 +1356,7 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); let taker_fee = taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, Uuid::new_v4().as_bytes()) .wait() .unwrap(); From 8a333a42e710d8e580fdcd898b483d1093e39446 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Mon, 4 Dec 2023 18:06:41 +0200 Subject: [PATCH 29/40] chore(release): bump mm2 version to 2.0.0-beta (#2018) --- Cargo.lock | 2 +- mm2src/mm2_bin_lib/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1911e05d20..90bd846e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4226,7 +4226,7 @@ dependencies = [ [[package]] name = "mm2_bin_lib" -version = "1.1.0-beta" +version = "2.0.0-beta" dependencies = [ "chrono", "common", diff --git a/mm2src/mm2_bin_lib/Cargo.toml b/mm2src/mm2_bin_lib/Cargo.toml index 3abda9470d..4a1d5da437 100644 --- a/mm2src/mm2_bin_lib/Cargo.toml +++ b/mm2src/mm2_bin_lib/Cargo.toml @@ -5,8 +5,8 @@ [package] name = "mm2_bin_lib" -version = "1.1.0-beta" -authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann "] +version = "2.0.0-beta" +authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann ", "Dimxy"] edition = "2018" default-run = "mm2" From efa2426c86b4a46f0d64d418e2f0ce895669eb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Thu, 7 Dec 2023 01:24:11 +0300 Subject: [PATCH 30/40] feat(network): deprecate 7777 network (#2020) Signed-off-by: onur-ozkan --- mm2src/mm2_main/src/lp_native_dex.rs | 11 ++++++++--- mm2src/mm2_main/src/lp_network.rs | 2 ++ mm2src/mm2_p2p/src/behaviours/atomicdex.rs | 10 +++++++--- mm2src/mm2_p2p/src/network.rs | 8 ++++---- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index c00ad9916f..a8d4019677 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -29,6 +29,7 @@ use mm2_core::mm_ctx::{MmArc, MmCtx}; use mm2_err_handle::common_errors::InternalError; use mm2_err_handle::prelude::*; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; +use mm2_libp2p::behaviours::atomicdex::DEPRECATED_NETID_LIST; use mm2_libp2p::{spawn_gossipsub, AdexBehaviourError, NodeType, RelayAddress, RelayAddressError, SwarmRuntime, WssCerts}; use mm2_metrics::mm_gauge; @@ -68,7 +69,7 @@ cfg_wasm32! { pub mod init_metamask; } -const NETID_8762_SEEDNODES: [&str; 3] = [ +const DEFAULT_NETID_SEEDNODES: [&str; 3] = [ "streamseed1.komodo.earth", "streamseed2.komodo.earth", "streamseed3.komodo.earth", @@ -267,7 +268,7 @@ impl MmInitError { #[cfg(target_arch = "wasm32")] fn default_seednodes(netid: u16) -> Vec { if netid == 8762 { - NETID_8762_SEEDNODES + DEFAULT_NETID_SEEDNODES .iter() .map(|seed| RelayAddress::Dns(seed.to_string())) .collect() @@ -280,7 +281,7 @@ fn default_seednodes(netid: u16) -> Vec { fn default_seednodes(netid: u16) -> Vec { use crate::mm2::lp_network::addr_to_ipv4_string; if netid == 8762 { - NETID_8762_SEEDNODES + DEFAULT_NETID_SEEDNODES .iter() .filter_map(|seed| addr_to_ipv4_string(seed).ok()) .map(RelayAddress::IPv4) @@ -523,6 +524,10 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { let i_am_seed = ctx.conf["i_am_seed"].as_bool().unwrap_or(false); let netid = ctx.netid(); + if DEPRECATED_NETID_LIST.contains(&netid) { + return MmError::err(P2PInitError::InvalidNetId(NetIdError::Deprecated { netid })); + } + let seednodes = seednodes(&ctx)?; let ctx_on_poll = ctx.clone(); diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index 356da9e05c..717c76d3c1 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -464,6 +464,8 @@ pub fn addr_to_ipv4_string(address: &str) -> Result Result<(u16, u16, u16), MmError> { diff --git a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs index c895466515..63e98e21e7 100644 --- a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs +++ b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs @@ -30,7 +30,7 @@ use super::peers_exchange::{PeerAddresses, PeersExchange, PeersExchangeRequest, use super::ping::AdexPing; use super::request_response::{build_request_response_behaviour, PeerRequest, PeerResponse, RequestResponseBehaviour, RequestResponseSender}; -use crate::network::{get_all_network_seednodes, NETID_8762}; +use crate::network::{get_all_network_seednodes, DEFAULT_NETID}; use crate::relay_address::{RelayAddress, RelayAddressError}; use crate::swarm_runtime::SwarmRuntime; use crate::{NetworkInfo, NetworkPorts, RequestResponseBehaviourEvent}; @@ -51,6 +51,10 @@ const ANNOUNCE_INTERVAL: Duration = Duration::from_secs(600); const ANNOUNCE_INITIAL_DELAY: Duration = Duration::from_secs(60); const CHANNEL_BUF_SIZE: usize = 1024 * 8; +pub const DEPRECATED_NETID_LIST: &[u16] = &[ + 7777, // TODO: keep it inaccessible until Q2 of 2024. +]; + /// The structure is the same as `PeerResponse`, /// but is used to prevent `PeerResponse` from being used outside the network implementation. #[derive(Debug, Eq, PartialEq)] @@ -641,7 +645,7 @@ fn start_gossipsub( let mut gossipsub = Gossipsub::new(MessageAuthenticity::Author(local_peer_id), gossipsub_config) .map_err(|e| AdexBehaviourError::InitializationError(e.to_owned()))?; - let floodsub = Floodsub::new(local_peer_id, netid != NETID_8762); + let floodsub = Floodsub::new(local_peer_id, netid != DEFAULT_NETID); let mut peers_exchange = PeersExchange::new(network_info); if !network_info.in_memory() { @@ -735,7 +739,7 @@ fn start_gossipsub( debug!("Swarm event {:?}", event); if let SwarmEvent::Behaviour(event) = event { - if swarm.behaviour_mut().netid != NETID_8762 { + if swarm.behaviour_mut().netid != DEFAULT_NETID { if let AdexBehaviourEvent::Floodsub(FloodsubEvent::Message(message)) = &event { for topic in &message.topics { if topic == &FloodsubTopic::new(PEERS_TOPIC) { diff --git a/mm2src/mm2_p2p/src/network.rs b/mm2src/mm2_p2p/src/network.rs index 364cee72cf..c3900bbc16 100644 --- a/mm2src/mm2_p2p/src/network.rs +++ b/mm2src/mm2_p2p/src/network.rs @@ -1,10 +1,10 @@ use crate::relay_address::RelayAddress; use libp2p::PeerId; -pub const NETID_8762: u16 = 8762; +pub const DEFAULT_NETID: u16 = 8762; #[cfg_attr(target_arch = "wasm32", allow(dead_code))] -const ALL_NETID_8762_SEEDNODES: &[(&str, &str)] = &[ +const ALL_DEFAULT_NETID_SEEDNODES: &[(&str, &str)] = &[ ( "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", "168.119.236.251", @@ -52,10 +52,10 @@ pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { V pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { use std::str::FromStr; - if netid != NETID_8762 { + if netid != DEFAULT_NETID { return Vec::new(); } - ALL_NETID_8762_SEEDNODES + ALL_DEFAULT_NETID_SEEDNODES .iter() .map(|(peer_id, ipv4)| { let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); From 040df1da8bb25cf03322de909e2191e7455d347d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Thu, 7 Dec 2023 12:57:36 +0300 Subject: [PATCH 31/40] chore(network): exclude `168.119.236.249` from the seednode list (#2021) `168.119.236.249` will be used to cut the communication on the old main network by giving information about the new main network, it will be run using this branch https://github.com/KomodoPlatform/komodo-defi-framework/tree/patch-for-deprecation Signed-off-by: onur-ozkan --- mm2src/mm2_p2p/src/network.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mm2src/mm2_p2p/src/network.rs b/mm2src/mm2_p2p/src/network.rs index c3900bbc16..88794d6329 100644 --- a/mm2src/mm2_p2p/src/network.rs +++ b/mm2src/mm2_p2p/src/network.rs @@ -26,10 +26,11 @@ const ALL_DEFAULT_NETID_SEEDNODES: &[(&str, &str)] = &[ "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", "168.119.236.243", ), - ( - "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", - "168.119.236.249", - ), + // TODO: Uncomment this once re-enabled on the main network. + // ( + // "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", + // "168.119.236.249", + // ), ( "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", "168.119.236.246", From bf96f22499e80a9baad2b1df92c65da333039ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Fri, 8 Dec 2023 20:22:15 +0300 Subject: [PATCH 32/40] chore(network): add todo on peer storage behaviour (#2025) Note added about adding peer scorer Signed-off-by: onur-ozkan --- mm2src/mm2_p2p/src/behaviours/peer_store.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm2src/mm2_p2p/src/behaviours/peer_store.rs b/mm2src/mm2_p2p/src/behaviours/peer_store.rs index cd7b437f58..8d0bd44582 100644 --- a/mm2src/mm2_p2p/src/behaviours/peer_store.rs +++ b/mm2src/mm2_p2p/src/behaviours/peer_store.rs @@ -23,6 +23,9 @@ pub struct Connection { } /// A request/response protocol for some message codec. +/// +/// TODO: implement a scoring algorithm of peers depending on +/// their activities/connections. #[derive(Default)] pub struct Behaviour { /// The currently connected peers, their pending outbound and inbound responses and their known, From 2f63bd5f9716e7652ca51086183b954816ab8cbf Mon Sep 17 00:00:00 2001 From: smk762 <35845239+smk762@users.noreply.github.com> Date: Tue, 12 Dec 2023 02:04:36 +0800 Subject: [PATCH 33/40] chore(network): update seednodes for netid 8762 (#2024) --- mm2src/coins/lp_price.rs | 2 +- mm2src/coins/nft/nft_tests.rs | 2 +- mm2src/mm2_main/src/lp_native_dex.rs | 53 +++++++++-- .../src/lp_ordermatch/simple_market_maker.rs | 2 +- mm2src/mm2_p2p/src/behaviours/atomicdex.rs | 2 +- mm2src/mm2_p2p/src/behaviours/mod.rs | 8 +- mm2src/mm2_p2p/src/lib.rs | 1 + mm2src/mm2_p2p/src/network.rs | 95 ++++++++++++------- 8 files changed, 116 insertions(+), 49 deletions(-) diff --git a/mm2src/coins/lp_price.rs b/mm2src/coins/lp_price.rs index c0834c2efe..12f11e79ad 100644 --- a/mm2src/coins/lp_price.rs +++ b/mm2src/coins/lp_price.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::str::Utf8Error; const PRICE_ENDPOINTS: [&str; 2] = [ - "https://prices.komodo.earth/api/v2/tickers", + "https://prices.komodian.info/api/v2/tickers", "https://prices.cipig.net:1717/api/v2/tickers", ]; diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index ae10513987..e8f27b854c 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -14,7 +14,7 @@ use mm2_number::BigDecimal; use std::num::NonZeroUsize; use std::str::FromStr; -const MORALIS_API_ENDPOINT_TEST: &str = "https://moralis-proxy.komodo.earth/api/v2"; +const MORALIS_API_ENDPOINT_TEST: &str = "https://moralis-proxy.komodian.info/api/v2"; const TEST_WALLET_ADDR_EVM: &str = "0x394d86994f954ed931b86791b62fe64f4c5dac37"; const BLOCKLIST_API_ENDPOINT: &str = "https://nft.antispam.dragonhound.info"; const TOKEN_ADD: &str = "0xfd913a305d70a60aac4faac70c739563738e1f81"; diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index a8d4019677..80912cbb04 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -30,8 +30,8 @@ use mm2_err_handle::common_errors::InternalError; use mm2_err_handle::prelude::*; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; use mm2_libp2p::behaviours::atomicdex::DEPRECATED_NETID_LIST; -use mm2_libp2p::{spawn_gossipsub, AdexBehaviourError, NodeType, RelayAddress, RelayAddressError, SwarmRuntime, - WssCerts}; +use mm2_libp2p::{spawn_gossipsub, AdexBehaviourError, NodeType, RelayAddress, RelayAddressError, SeedNodeInfo, + SwarmRuntime, WssCerts}; use mm2_metrics::mm_gauge; use mm2_net::network_event::NetworkEvent; use mm2_net::p2p::P2PContext; @@ -69,10 +69,47 @@ cfg_wasm32! { pub mod init_metamask; } -const DEFAULT_NETID_SEEDNODES: [&str; 3] = [ - "streamseed1.komodo.earth", - "streamseed2.komodo.earth", - "streamseed3.komodo.earth", +const DEFAULT_NETID_SEEDNODES: &[SeedNodeInfo] = &[ + SeedNodeInfo::new( + "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", + "168.119.236.251", + "viserion.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", + "168.119.236.240", + "rhaegal.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWSmEi8ypaVzFA1AGde2RjxNW5Pvxw3qa2fVe48PjNs63R", + "168.119.236.239", + "drogon.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", + "168.119.237.8", + "falkor.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWEWzbYcosK2JK9XpFXzumfgsWJW1F7BZS15yLTrhfjX2Z", + "65.21.51.47", + "smaug.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", + "135.181.34.220", + "balerion.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", + "168.119.237.13", + "kalessin.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", + "46.4.78.11", + "fr1.cipig.net", + ), ]; pub type P2PResult = Result>; @@ -270,7 +307,7 @@ fn default_seednodes(netid: u16) -> Vec { if netid == 8762 { DEFAULT_NETID_SEEDNODES .iter() - .map(|seed| RelayAddress::Dns(seed.to_string())) + .map(|SeedNodeInfo { domain, .. }| RelayAddress::Dns(domain.to_string())) .collect() } else { Vec::new() @@ -283,7 +320,7 @@ fn default_seednodes(netid: u16) -> Vec { if netid == 8762 { DEFAULT_NETID_SEEDNODES .iter() - .filter_map(|seed| addr_to_ipv4_string(seed).ok()) + .filter_map(|SeedNodeInfo { domain, .. }| addr_to_ipv4_string(domain).ok()) .map(RelayAddress::IPv4) .collect() } else { diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs index 4fde30b8ee..be132a0d76 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs @@ -23,7 +23,7 @@ use std::collections::{HashMap, HashSet}; use uuid::Uuid; // !< constants -pub const KMD_PRICE_ENDPOINT: &str = "https://prices.komodo.earth/api/v2/tickers"; +pub const KMD_PRICE_ENDPOINT: &str = "https://prices.komodian.info/api/v2/tickers"; pub const BOT_DEFAULT_REFRESH_RATE: f64 = 30.0; pub const PRECISION_FOR_NOTIFICATION: u64 = 8; const LATEST_SWAPS_LIMIT: usize = 1000; diff --git a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs index 63e98e21e7..91f423c4e2 100644 --- a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs +++ b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs @@ -651,7 +651,7 @@ fn start_gossipsub( if !network_info.in_memory() { // Please note WASM nodes don't support `PeersExchange` currently, // so `get_all_network_seednodes` returns an empty list. - for (peer_id, addr) in get_all_network_seednodes(netid) { + for (peer_id, addr, _domain) in get_all_network_seednodes(netid) { let multiaddr = addr.try_to_multiaddr(network_info)?; peers_exchange.add_peer_addresses_to_known_peers(&peer_id, iter::once(multiaddr).collect()); if peer_id != local_peer_id { diff --git a/mm2src/mm2_p2p/src/behaviours/mod.rs b/mm2src/mm2_p2p/src/behaviours/mod.rs index dd78d7ed65..0af8fd71c0 100644 --- a/mm2src/mm2_p2p/src/behaviours/mod.rs +++ b/mm2src/mm2_p2p/src/behaviours/mod.rs @@ -420,18 +420,18 @@ mod tests { assert!(!behaviour.validate_get_known_peers_response(&response)); let address: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" + "/ip4/168.119.236.251/tcp/3000/p2p/12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm" .parse() .unwrap(); let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); assert!(behaviour.validate_get_known_peers_response(&response)); let address1: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" + "/ip4/168.119.236.251/tcp/3000/p2p/12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm" .parse() .unwrap(); - let address2: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); + let address2: Multiaddr = "/ip4/168.119.236.251/tcp/3000".parse().unwrap(); let response = HashMap::from_iter(vec![( PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address1, address2]), @@ -448,7 +448,7 @@ mod tests { let result = behaviour.get_random_known_peers(1); assert!(result.is_empty()); - let address: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); + let address: Multiaddr = "/ip4/168.119.236.251/tcp/3000".parse().unwrap(); behaviour.request_response.add_address(&peer_id, address.clone()); let result = behaviour.get_random_known_peers(1); diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index fd13446f6e..7aca2eebd1 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -32,6 +32,7 @@ pub use libp2p::identity::{secp256k1::PublicKey as Libp2pSecpPublic, PublicKey a pub use libp2p::{Multiaddr, PeerId}; // relay-address related re-exports +pub use network::SeedNodeInfo; pub use relay_address::RelayAddress; pub use relay_address::RelayAddressError; diff --git a/mm2src/mm2_p2p/src/network.rs b/mm2src/mm2_p2p/src/network.rs index 88794d6329..70978a3301 100644 --- a/mm2src/mm2_p2p/src/network.rs +++ b/mm2src/mm2_p2p/src/network.rs @@ -3,54 +3,82 @@ use libp2p::PeerId; pub const DEFAULT_NETID: u16 = 8762; +pub struct SeedNodeInfo { + pub id: &'static str, + pub ip: &'static str, + pub domain: &'static str, +} + +impl SeedNodeInfo { + pub const fn new(id: &'static str, ip: &'static str, domain: &'static str) -> Self { Self { id, ip, domain } } +} + #[cfg_attr(target_arch = "wasm32", allow(dead_code))] -const ALL_DEFAULT_NETID_SEEDNODES: &[(&str, &str)] = &[ - ( +const ALL_DEFAULT_NETID_SEEDNODES: &[SeedNodeInfo] = &[ + SeedNodeInfo::new( "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", "168.119.236.251", + "viserion.dragon-seed.com", ), - ( + SeedNodeInfo::new( "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", "168.119.236.240", + "rhaegal.dragon-seed.com", ), - ( + SeedNodeInfo::new( "12D3KooWSmEi8ypaVzFA1AGde2RjxNW5Pvxw3qa2fVe48PjNs63R", "168.119.236.239", + "drogon.dragon-seed.com", ), - ("12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", "135.181.34.220"), - ( - "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", - "168.119.236.241", + SeedNodeInfo::new( + "12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", + "168.119.237.8", + "falkor.dragon-seed.com", ), - ( - "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", - "168.119.236.243", + SeedNodeInfo::new( + "12D3KooWEWzbYcosK2JK9XpFXzumfgsWJW1F7BZS15yLTrhfjX2Z", + "65.21.51.47", + "smaug.dragon-seed.com", ), - // TODO: Uncomment this once re-enabled on the main network. - // ( - // "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", - // "168.119.236.249", - // ), - ( - "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", - "168.119.236.246", + SeedNodeInfo::new( + "12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", + "135.181.34.220", + "balerion.dragon-seed.com", ), - ( - "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", - "168.119.236.233", + SeedNodeInfo::new( + "12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", + "168.119.237.13", + "kalessin.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", + "65.108.90.210", + "icefyre.dragon-seed.com", + ), + SeedNodeInfo::new( + "12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", + "46.4.78.11", + "fr1.cipig.net", ), - ("12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", "168.119.237.8"), - ("12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", "168.119.237.13"), - ("12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", "65.108.90.210"), - ("12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", "46.4.78.11"), - ("12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", "46.4.87.18"), ]; +// TODO: Uncomment these once re-enabled on the main network. +// Operated by Dragonhound, still on NetID 7777. Domains will update after netid migration. +// SeedNodeInfo::new("12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", "168.119.236.241", "seed1.komodo.earth"), // tintaglia.dragon-seed.com +// SeedNodeInfo::new("12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", "168.119.236.243", "seed2.komodo.earth"), // mercor.dragon-seed.com +// SeedNodeInfo::new("12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", "168.119.236.249", "seed3.komodo.earth"), // karrigvestrit.dragon-seed.com +// SeedNodeInfo::new("12D3KooWGrUpCAbkxhPRioNs64sbUmPmpEcou6hYfrqQvxfWDEuf", "135.181.35.77", "seed4.komodo.earth"), // sintara.dragon-seed.com +// SeedNodeInfo::new("12D3KooWKu8pMTgteWacwFjN7zRWWHb3bctyTvHU3xx5x4x6qDYY", "65.21.56.210", "seed6.komodo.earth"), // heeby.dragon-seed.com +// SeedNodeInfo::new("12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", "168.119.236.246", "seed7.komodo.earth"), // kalo.dragon-seed.com +// SeedNodeInfo::new("12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", "168.119.236.233", "seed8.komodo.earth"), // relpda.dragon-seed.com +// Operated by Cipi, still on NetID 7777 +// SeedNodeInfo::new("12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", "46.4.87.18", "fr2.cipig.net"), + #[cfg(target_arch = "wasm32")] -pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { Vec::new() } +pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress, String)> { Vec::new() } #[cfg(not(target_arch = "wasm32"))] -pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { +pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress, String)> { use std::str::FromStr; if netid != DEFAULT_NETID { @@ -58,10 +86,11 @@ pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { } ALL_DEFAULT_NETID_SEEDNODES .iter() - .map(|(peer_id, ipv4)| { - let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); - let address = RelayAddress::IPv4(ipv4.to_string()); - (peer_id, address) + .map(|SeedNodeInfo { id, ip, domain }| { + let peer_id = PeerId::from_str(id).expect("valid peer id"); + let address = RelayAddress::IPv4(ip.to_string()); + let domain = domain.to_string(); + (peer_id, address, domain) }) .collect() } From 52dab22f71896a324ad93ddefc79a54918ebe708 Mon Sep 17 00:00:00 2001 From: Alina Sharon <52405288+laruh@users.noreply.github.com> Date: Tue, 12 Dec 2023 01:08:44 +0700 Subject: [PATCH 34/40] feat(nft): move db lock, add tx fee and confirmations (#1989) * `nft_cache_db` was added in `NftCtx` for non wasm targets. * `AsyncConnection` structure was created in `mm2src/db_common/src/async_sql_conn.rs`. It can be used as async wrapper for sqlite connection. * `async_sqlite_connection` field was added in `MmCtx`. * Spam transfers with empty meta no longer update. --- Cargo.lock | 3 + mm2src/coins/eth.rs | 8 +- mm2src/coins/my_tx_history_v2.rs | 2 +- mm2src/coins/nft.rs | 174 ++++++--- mm2src/coins/nft/nft_errors.rs | 96 ++++- mm2src/coins/nft/nft_structs.rs | 133 +++++-- mm2src/coins/nft/nft_tests.rs | 109 ++++-- mm2src/coins/nft/storage/db_test_helpers.rs | 57 ++- mm2src/coins/nft/storage/mod.rs | 49 +-- mm2src/coins/nft/storage/sql_storage.rs | 352 ++++++++---------- mm2src/coins/nft/storage/wasm/mod.rs | 2 - mm2src/coins/nft/storage/wasm/wasm_storage.rs | 288 ++++++-------- mm2src/db_common/Cargo.toml | 3 + mm2src/db_common/src/async_conn_tests.rs | 253 +++++++++++++ mm2src/db_common/src/async_sql_conn.rs | 292 +++++++++++++++ mm2src/db_common/src/lib.rs | 3 + mm2src/mm2_core/src/mm_ctx.rs | 34 +- mm2src/mm2_main/src/lp_native_dex.rs | 3 + .../src/rpc/lp_commands/lp_commands_legacy.rs | 14 + mm2src/mm2_test_helpers/src/for_tests.rs | 14 + 20 files changed, 1295 insertions(+), 594 deletions(-) create mode 100644 mm2src/db_common/src/async_conn_tests.rs create mode 100644 mm2src/db_common/src/async_sql_conn.rs diff --git a/Cargo.lock b/Cargo.lock index 90bd846e30..3fba39200e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1740,10 +1740,13 @@ name = "db_common" version = "0.1.0" dependencies = [ "common", + "crossbeam-channel 0.5.1", + "futures 0.3.28", "hex 0.4.3", "log", "rusqlite", "sql-builder", + "tokio", "uuid 1.2.2", ] diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 53567b87eb..90e2f90ffb 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -422,7 +422,7 @@ pub struct EthCoinImpl { swap_contract_address: Address, fallback_swap_contract: Option
, contract_supports_watchers: bool, - web3: Web3, + pub(crate) web3: Web3, /// The separate web3 instances kept to get nonce, will replace the web3 completely soon web3_instances: Vec, decimals: u8, @@ -875,7 +875,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { /// `withdraw_erc1155` function returns details of `ERC-1155` transaction including tx hex, /// which should be sent to`send_raw_transaction` RPC to broadcast the transaction. pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> WithdrawNftResult { - let coin = lp_coinfind_or_err(&ctx, &withdraw_type.chain.to_ticker()).await?; + let coin = lp_coinfind_or_err(&ctx, withdraw_type.chain.to_ticker()).await?; let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?; let my_address = eth_coin.my_address()?; @@ -977,7 +977,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit /// `withdraw_erc721` function returns details of `ERC-721` transaction including tx hex, /// which should be sent to`send_raw_transaction` RPC to broadcast the transaction. pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> WithdrawNftResult { - let coin = lp_coinfind_or_err(&ctx, &withdraw_type.chain.to_ticker()).await?; + let coin = lp_coinfind_or_err(&ctx, withdraw_type.chain.to_ticker()).await?; let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?; let my_address = eth_coin.my_address()?; @@ -4716,7 +4716,7 @@ pub struct EthTxFeeDetails { } impl EthTxFeeDetails { - fn new(gas: U256, gas_price: U256, coin: &str) -> NumConversResult { + pub(crate) fn new(gas: U256, gas_price: U256, coin: &str) -> NumConversResult { let total_fee = gas * gas_price; // Fees are always paid in ETH, can use 18 decimals by default let total_fee = u256_to_big_decimal(total_fee, ETH_DECIMALS)?; diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index 1635be9e80..97c5a5ca8f 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -491,7 +491,7 @@ where _ => {}, }; - let confirmations = if details.block_height == 0 || details.block_height > current_block { + let confirmations = if details.block_height > current_block { 0 } else { current_block + 1 - details.block_height diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 0f0cb942e0..40736a6414 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -8,29 +8,34 @@ pub(crate) mod storage; #[cfg(any(test, target_arch = "wasm32"))] mod nft_tests; -use crate::{coin_conf, get_my_address, MyAddressReq, WithdrawError}; +use crate::{coin_conf, get_my_address, lp_coinfind_or_err, MarketCoinOps, MmCoinEnum, MyAddressReq, WithdrawError}; use nft_errors::{GetNftInfoError, UpdateNftError}; use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList, TransactionNftDetails, UpdateNftReq, WithdrawNftReq}; -use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721}; -use crate::nft::nft_errors::{MetaFromUrlError, ProtectFromSpamError, UpdateSpamPhishingError}; +use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721, EthCoin, EthCoinType, + EthTxFeeDetails}; +use crate::nft::nft_errors::{MetaFromUrlError, ProtectFromSpamError, TransferConfirmationsError, + UpdateSpamPhishingError}; use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, NftCommon, NftCtx, NftTransferCommon, PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq, SpamContractReq, SpamContractRes, TransferMeta, TransferStatus, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; +use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; use common::parse_rfc3339_to_timestamp; use crypto::StandardHDCoinAddress; -use ethereum_types::Address; +use ethereum_types::{Address, H256}; +use futures::compat::Future01CompatExt; +use futures::future::try_join_all; use mm2_err_handle::map_to_mm::MapToMmResult; use mm2_net::transport::send_post_request_to_uri; -use mm2_number::BigDecimal; +use mm2_number::{BigDecimal, BigUint}; use regex::Regex; use serde_json::Value as Json; use std::cmp::Ordering; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::str::FromStr; +use web3::types::TransactionId; #[cfg(not(target_arch = "wasm32"))] use mm2_net::native_http::send_request_to_uri; @@ -74,9 +79,8 @@ pub type WithdrawNftResult = Result MmResult { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; - let _lock = nft_ctx.guard.lock().await; - let storage = NftStorageBuilder::new(&ctx).build()?; + let storage = nft_ctx.lock_db().await?; for chain in req.chains.iter() { if !NftListStorageOps::is_initialized(&storage, chain).await? { NftListStorageOps::init(&storage, chain).await?; @@ -114,9 +118,8 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; - let _lock = nft_ctx.guard.lock().await; - let storage = NftStorageBuilder::new(&ctx).build()?; + let storage = nft_ctx.lock_db().await?; if !NftListStorageOps::is_initialized(&storage, &req.chain).await? { NftListStorageOps::init(&storage, &req.chain).await?; } @@ -156,26 +159,67 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; - let _lock = nft_ctx.guard.lock().await; - let storage = NftStorageBuilder::new(&ctx).build()?; + let storage = nft_ctx.lock_db().await?; for chain in req.chains.iter() { if !NftTransferHistoryStorageOps::is_initialized(&storage, chain).await? { NftTransferHistoryStorageOps::init(&storage, chain).await?; } } let mut transfer_history_list = storage - .get_transfer_history(req.chains, req.max, req.limit, req.page_number, req.filters) + .get_transfer_history(req.chains.clone(), req.max, req.limit, req.page_number, req.filters) .await?; if req.protect_from_spam { for transfer in &mut transfer_history_list.transfer_history { protect_from_history_spam_links(transfer, true)?; } } + process_transfers_confirmations(&ctx, req.chains, &mut transfer_history_list).await?; drop_mutability!(transfer_history_list); Ok(transfer_history_list) } +async fn process_transfers_confirmations( + ctx: &MmArc, + chains: Vec, + history_list: &mut NftsTransferHistoryList, +) -> MmResult<(), TransferConfirmationsError> { + async fn current_block_impl(coin: Coin) -> MmResult { + coin.current_block() + .compat() + .await + .map_to_mm(TransferConfirmationsError::GetCurrentBlockErr) + } + + let futures = chains.into_iter().map(|chain| async move { + let ticker = chain.to_ticker(); + let coin_enum = lp_coinfind_or_err(ctx, ticker).await?; + match coin_enum { + MmCoinEnum::EthCoin(eth_coin) => { + let current_block = current_block_impl(eth_coin).await?; + Ok((ticker, current_block)) + }, + _ => MmError::err(TransferConfirmationsError::CoinDoesntSupportNft { + coin: coin_enum.ticker().to_owned(), + }), + } + }); + let blocks_map = try_join_all(futures).await?.into_iter().collect::>(); + + for transfer in history_list.transfer_history.iter_mut() { + let current_block = match blocks_map.get(transfer.chain.to_ticker()) { + Some(block) => *block, + None => 0, + }; + transfer.confirmations = if transfer.block_number > current_block { + 0 + } else { + current_block + 1 - transfer.block_number + }; + } + Ok(()) +} + /// Updates NFT transfer history and NFT list in the DB. /// /// This function refreshes the NFT transfer history and NFT list cache based on new @@ -192,9 +236,8 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult`: A result indicating success or an error. pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; - let _lock = nft_ctx.guard.lock().await; - let storage = NftStorageBuilder::new(&ctx).build()?; + let storage = nft_ctx.lock_db().await?; for chain in req.chains.iter() { let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; @@ -205,7 +248,16 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft NftTransferHistoryStorageOps::init(&storage, chain).await?; None }; - let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url).await?; + let coin_enum = lp_coinfind_or_err(&ctx, chain.to_ticker()).await?; + let eth_coin = match coin_enum { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(UpdateNftError::CoinDoesntSupportNft { + coin: coin_enum.ticker().to_owned(), + }) + }, + }; + let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url, eth_coin).await?; storage.add_transfers_to_history(*chain, nft_transfers).await?; let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { @@ -377,9 +429,8 @@ fn prepare_uri_for_blocklist_endpoint( /// * `MmResult<(), UpdateNftError>`: A result indicating success or an error. pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; - let _lock = nft_ctx.guard.lock().await; - let storage = NftStorageBuilder::new(&ctx).build()?; + let storage = nft_ctx.lock_db().await?; let token_address_str = eth_addr_to_hex(&req.token_address); let moralis_meta = match get_moralis_metadata( token_address_str.clone(), @@ -532,8 +583,8 @@ async fn get_moralis_nft_list( ) -> MmResult, GetNftInfoError> { let mut res_list = Vec::new(); let ticker = chain.to_ticker(); - let conf = coin_conf(ctx, &ticker); - let my_address = get_eth_address(ctx, &conf, &ticker, &StandardHDCoinAddress::default()).await?; + let conf = coin_conf(ctx, ticker); + let my_address = get_eth_address(ctx, &conf, ticker, &StandardHDCoinAddress::default()).await?; let mut uri_without_cursor = url.clone(); uri_without_cursor.set_path(MORALIS_API_ENDPOINT); @@ -584,11 +635,12 @@ async fn get_moralis_nft_transfers( chain: &Chain, from_block: Option, url: &Url, + eth_coin: EthCoin, ) -> MmResult, GetNftInfoError> { let mut res_list = Vec::new(); let ticker = chain.to_ticker(); - let conf = coin_conf(ctx, &ticker); - let my_address = get_eth_address(ctx, &conf, &ticker, &StandardHDCoinAddress::default()).await?; + let conf = coin_conf(ctx, ticker); + let my_address = get_eth_address(ctx, &conf, ticker, &StandardHDCoinAddress::default()).await?; let mut uri_without_cursor = url.clone(); uri_without_cursor.set_path(MORALIS_API_ENDPOINT); @@ -625,6 +677,7 @@ async fn get_moralis_nft_transfers( let status = get_transfer_status(&wallet_address, ð_addr_to_hex(&transfer_moralis.common.to_address)); let block_timestamp = parse_rfc3339_to_timestamp(&transfer_moralis.block_timestamp)?; + let fee_details = get_fee_details(ð_coin, &transfer_moralis.common.transaction_hash).await; let transfer_history = NftTransferHistory { common: NftTransferCommon { block_hash: transfer_moralis.common.block_hash, @@ -634,7 +687,6 @@ async fn get_moralis_nft_transfers( value: transfer_moralis.common.value, transaction_type: transfer_moralis.common.transaction_type, token_address: transfer_moralis.common.token_address, - token_id: transfer_moralis.common.token_id, from_address: transfer_moralis.common.from_address, to_address: transfer_moralis.common.to_address, amount: transfer_moralis.common.amount, @@ -643,6 +695,7 @@ async fn get_moralis_nft_transfers( possible_spam: transfer_moralis.common.possible_spam, }, chain: *chain, + token_id: transfer_moralis.token_id.0, block_number: *transfer_moralis.block_number, block_timestamp, contract_type, @@ -654,6 +707,8 @@ async fn get_moralis_nft_transfers( token_name: None, status, possible_phishing: false, + fee_details, + confirmations: 0, }; // collect NFTs transfers from the page res_list.push(transfer_history); @@ -672,6 +727,35 @@ async fn get_moralis_nft_transfers( Ok(res_list) } +async fn get_fee_details(eth_coin: &EthCoin, transaction_hash: &str) -> Option { + let hash = H256::from_str(transaction_hash).ok()?; + let receipt = eth_coin.web3.eth().transaction_receipt(hash).await.ok()?; + let fee_coin = match eth_coin.coin_type { + EthCoinType::Eth => eth_coin.ticker(), + EthCoinType::Erc20 { .. } => return None, + }; + + match receipt { + Some(r) => { + let gas_used = r.gas_used.unwrap_or_default(); + match r.effective_gas_price { + Some(gas_price) => EthTxFeeDetails::new(gas_used, gas_price, fee_coin).ok(), + None => { + let web3_tx = eth_coin + .web3 + .eth() + .transaction(TransactionId::Hash(hash)) + .await + .ok()??; + let gas_price = web3_tx.gas_price.unwrap_or_default(); + EthTxFeeDetails::new(gas_used, gas_price, fee_coin).ok() + }, + } + }, + None => None, + } +} + /// Implements request to the Moralis "Get NFT metadata" endpoint. /// /// [Moralis Documentation Link](https://docs.moralis.io/web3-data-api/evm/reference/get-nft-metadata) @@ -683,7 +767,7 @@ async fn get_moralis_nft_transfers( /// **Dont** use this function to get specific info about owner address, amount etc, you will get info not related to my_address. async fn get_moralis_metadata( token_address: String, - token_id: BigDecimal, + token_id: BigUint, chain: &Chain, url: &Url, url_antispam: &Url, @@ -804,7 +888,7 @@ async fn update_nft_list( ) -> MmResult<(), UpdateNftError> { let transfers = storage.get_transfers_from_block(*chain, scan_from_block).await?; let req = MyAddressReq { - coin: chain.to_ticker(), + coin: chain.to_ticker().to_string(), path_to_address: StandardHDCoinAddress::default(), }; let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase(); @@ -843,18 +927,18 @@ async fn handle_send_erc721 .get_nft( chain, eth_addr_to_hex(&transfer.common.token_address), - transfer.common.token_id.clone(), + transfer.token_id.clone(), ) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { token_address: eth_addr_to_hex(&transfer.common.token_address), - token_id: transfer.common.token_id.to_string(), + token_id: transfer.token_id.to_string(), })?; storage .remove_nft_from_list( chain, eth_addr_to_hex(&transfer.common.token_address), - transfer.common.token_id, + transfer.token_id, transfer.block_number, ) .await?; @@ -871,7 +955,7 @@ async fn handle_receive_erc721 MmResult<(), UpdateNftError> { let token_address_str = eth_addr_to_hex(&transfer.common.token_address); match storage - .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone()) + .get_nft(chain, token_address_str.clone(), transfer.token_id.clone()) .await? { Some(mut nft_db) => { @@ -891,7 +975,7 @@ async fn handle_receive_erc721 { let mut nft = match get_moralis_metadata( token_address_str.clone(), - transfer.common.token_id.clone(), + transfer.token_id.clone(), chain, url, url_antispam, @@ -926,21 +1010,16 @@ async fn handle_send_erc1155 MmResult<(), UpdateNftError> { let token_address_str = eth_addr_to_hex(&transfer.common.token_address); let mut nft_db = storage - .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone()) + .get_nft(chain, token_address_str.clone(), transfer.token_id.clone()) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { token_address: token_address_str.clone(), - token_id: transfer.common.token_id.to_string(), + token_id: transfer.token_id.to_string(), })?; match nft_db.common.amount.cmp(&transfer.common.amount) { Ordering::Equal => { storage - .remove_nft_from_list( - chain, - token_address_str, - transfer.common.token_id, - transfer.block_number, - ) + .remove_nft_from_list(chain, token_address_str, transfer.token_id, transfer.block_number) .await?; }, Ordering::Greater => { @@ -969,7 +1048,7 @@ async fn handle_receive_erc1155 MmResult<(), UpdateNftError> { let token_address_str = eth_addr_to_hex(&transfer.common.token_address); let mut nft = match storage - .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone()) + .get_nft(chain, token_address_str.clone(), transfer.token_id.clone()) .await? { Some(mut nft_db) => { @@ -989,7 +1068,7 @@ async fn handle_receive_erc1155 { let nft = match get_moralis_metadata( token_address_str.clone(), - transfer.common.token_id.clone(), + transfer.token_id.clone(), chain, url, url_antispam, @@ -1031,7 +1110,6 @@ async fn create_nft_from_moralis_metadata( let nft = Nft { common: NftCommon { token_address: moralis_meta.common.token_address, - token_id: moralis_meta.common.token_id, amount: transfer.common.amount.clone(), owner_of: Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?, token_hash: moralis_meta.common.token_hash, @@ -1046,6 +1124,7 @@ async fn create_nft_from_moralis_metadata( possible_spam: moralis_meta.common.possible_spam, }, chain: *chain, + token_id: moralis_meta.token_id, block_number_minted: moralis_meta.block_number_minted, block_number: transfer.block_number, contract_type: moralis_meta.contract_type, @@ -1071,7 +1150,7 @@ async fn mark_as_spam_and_build_empty_meta MmResult { - let nft_ctx = NftCtx::from_ctx(ctx).map_err(GetNftInfoError::Internal)?; - let _lock = nft_ctx.guard.lock().await; + let nft_ctx = NftCtx::from_ctx(ctx).map_to_mm(GetNftInfoError::Internal)?; - let storage = NftStorageBuilder::new(ctx).build()?; + let storage = nft_ctx.lock_db().await?; if !NftListStorageOps::is_initialized(&storage, chain).await? { NftListStorageOps::init(&storage, chain).await?; } @@ -1290,7 +1368,6 @@ async fn build_nft_from_moralis( Nft { common: NftCommon { token_address: nft_moralis.common.token_address, - token_id: nft_moralis.common.token_id, amount: nft_moralis.common.amount, owner_of: nft_moralis.common.owner_of, token_hash: nft_moralis.common.token_hash, @@ -1305,6 +1382,7 @@ async fn build_nft_from_moralis( possible_spam: nft_moralis.common.possible_spam, }, chain, + token_id: nft_moralis.token_id.0, block_number_minted: nft_moralis.block_number_minted.map(|v| v.0), block_number: *nft_moralis.block_number, contract_type, diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 941348db67..f5dd5adaba 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -1,7 +1,11 @@ use crate::eth::GetEthAddressError; -use crate::nft::storage::{CreateNftStorageError, NftStorageError}; -use crate::{GetMyAddressError, WithdrawError}; +#[cfg(target_arch = "wasm32")] +use crate::nft::storage::wasm::WasmNftCacheError; +use crate::nft::storage::NftStorageError; +use crate::{CoinFindError, GetMyAddressError, WithdrawError}; use common::{HttpStatusCode, ParseRfc3339Err}; +#[cfg(not(target_arch = "wasm32"))] +use db_common::sqlite::rusqlite::Error as SqlError; use derive_more::Display; use enum_from::EnumFromStringify; use http::StatusCode; @@ -38,6 +42,7 @@ pub enum GetNftInfoError { #[display(fmt = "The contract type is required and should not be null.")] ContractTypeIsNull, ProtectFromSpamError(ProtectFromSpamError), + TransferConfirmationsError(TransferConfirmationsError), } impl From for WithdrawError { @@ -73,14 +78,6 @@ impl From for GetNftInfoError { fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) } } -impl From for GetNftInfoError { - fn from(e: CreateNftStorageError) -> Self { - match e { - CreateNftStorageError::Internal(err) => GetNftInfoError::Internal(err), - } - } -} - impl From for GetNftInfoError { fn from(err: T) -> Self { GetNftInfoError::DbError(format!("{:?}", err)) } } @@ -104,6 +101,14 @@ impl From for GetNftInfoError { fn from(e: ProtectFromSpamError) -> Self { GetNftInfoError::ProtectFromSpamError(e) } } +impl From for GetNftInfoError { + fn from(e: LockDBError) -> Self { GetNftInfoError::DbError(e.to_string()) } +} + +impl From for GetNftInfoError { + fn from(e: TransferConfirmationsError) -> Self { GetNftInfoError::TransferConfirmationsError(e) } +} + impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { match self { @@ -115,7 +120,8 @@ impl HttpStatusCode for GetNftInfoError { | GetNftInfoError::GetEthAddressError(_) | GetNftInfoError::TokenNotFoundInWallet { .. } | GetNftInfoError::DbError(_) - | GetNftInfoError::ProtectFromSpamError(_) => StatusCode::INTERNAL_SERVER_ERROR, + | GetNftInfoError::ProtectFromSpamError(_) + | GetNftInfoError::TransferConfirmationsError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } @@ -184,14 +190,14 @@ pub enum UpdateNftError { #[from_stringify("serde_json::Error")] SerdeError(String), ProtectFromSpamError(ProtectFromSpamError), -} - -impl From for UpdateNftError { - fn from(e: CreateNftStorageError) -> Self { - match e { - CreateNftStorageError::Internal(err) => UpdateNftError::Internal(err), - } - } + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { + coin: String, + }, + #[display(fmt = "{} coin doesn't support NFT", coin)] + CoinDoesntSupportNft { + coin: String, + }, } impl From for UpdateNftError { @@ -218,6 +224,18 @@ impl From for UpdateNftError { fn from(e: ProtectFromSpamError) -> Self { UpdateNftError::ProtectFromSpamError(e) } } +impl From for UpdateNftError { + fn from(e: LockDBError) -> Self { UpdateNftError::DbError(e.to_string()) } +} + +impl From for UpdateNftError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => UpdateNftError::NoSuchCoin { coin }, + } + } +} + impl HttpStatusCode for UpdateNftError { fn status_code(&self) -> StatusCode { match self { @@ -234,7 +252,9 @@ impl HttpStatusCode for UpdateNftError { | UpdateNftError::UpdateSpamPhishingError(_) | UpdateNftError::GetInfoFromUriError(_) | UpdateNftError::SerdeError(_) - | UpdateNftError::ProtectFromSpamError(_) => StatusCode::INTERNAL_SERVER_ERROR, + | UpdateNftError::ProtectFromSpamError(_) + | UpdateNftError::NoSuchCoin { .. } + | UpdateNftError::CoinDoesntSupportNft { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } } @@ -310,3 +330,39 @@ pub(crate) enum MetaFromUrlError { impl From for MetaFromUrlError { fn from(e: GetInfoFromUriError) -> Self { MetaFromUrlError::GetInfoFromUriError(e) } } + +#[derive(Debug, Display)] +pub enum LockDBError { + #[cfg(target_arch = "wasm32")] + WasmNftCacheError(WasmNftCacheError), + #[cfg(not(target_arch = "wasm32"))] + SqlError(SqlError), +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for LockDBError { + fn from(e: SqlError) -> Self { LockDBError::SqlError(e) } +} + +#[cfg(target_arch = "wasm32")] +impl From for LockDBError { + fn from(e: WasmNftCacheError) -> Self { LockDBError::WasmNftCacheError(e) } +} + +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] +pub enum TransferConfirmationsError { + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, + #[display(fmt = "{} coin doesn't support NFT", coin)] + CoinDoesntSupportNft { coin: String }, + #[display(fmt = "Get current block error: {}", _0)] + GetCurrentBlockErr(String), +} + +impl From for TransferConfirmationsError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => TransferConfirmationsError::NoSuchCoin { coin }, + } + } +} diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 165e7cbd93..9173f3bd5b 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -1,13 +1,11 @@ -use crate::nft::eth_addr_to_hex; -use crate::{TransactionType, TxFeeDetails, WithdrawFee}; use common::ten; use ethereum_types::Address; -use futures::lock::Mutex as AsyncMutex; use mm2_core::mm_ctx::{from_ctx, MmArc}; -use mm2_number::BigDecimal; +use mm2_err_handle::prelude::*; +use mm2_number::{BigDecimal, BigUint}; use rpc::v1::types::Bytes as BytesJson; use serde::de::{self, Deserializer}; -use serde::Deserialize; +use serde::{Deserialize, Serializer}; use serde_json::Value as Json; use std::collections::HashMap; use std::fmt; @@ -16,12 +14,22 @@ use std::str::FromStr; use std::sync::Arc; use url::Url; -use crate::nft::nft_errors::ParseChainTypeError; -#[cfg(target_arch = "wasm32")] -use mm2_db::indexed_db::{ConstructibleDb, SharedDb}; +use crate::eth::EthTxFeeDetails; +use crate::nft::eth_addr_to_hex; +use crate::nft::nft_errors::{LockDBError, ParseChainTypeError}; +use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; +use crate::{TransactionType, TxFeeDetails, WithdrawFee}; + +cfg_native! { + use db_common::async_sql_conn::AsyncConnection; + use futures::lock::Mutex as AsyncMutex; +} -#[cfg(target_arch = "wasm32")] -use crate::nft::storage::wasm::nft_idb::NftCacheIDB; +cfg_wasm32! { + use mm2_db::indexed_db::{ConstructibleDb, SharedDb}; + use crate::nft::storage::wasm::WasmNftCacheError; + use crate::nft::storage::wasm::nft_idb::NftCacheIDB; +} /// Represents a request to list NFTs owned by the user across specified chains. /// @@ -68,7 +76,8 @@ pub struct NftListFilters { #[derive(Debug, Deserialize)] pub struct NftMetadataReq { pub(crate) token_address: Address, - pub(crate) token_id: BigDecimal, + #[serde(deserialize_with = "deserialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) chain: Chain, #[serde(default)] pub(crate) protect_from_spam: bool, @@ -85,7 +94,8 @@ pub struct NftMetadataReq { #[derive(Debug, Deserialize)] pub struct RefreshMetadataReq { pub(crate) token_address: Address, - pub(crate) token_id: BigDecimal, + #[serde(deserialize_with = "deserialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) chain: Chain, pub(crate) url: Url, pub(crate) url_antispam: Url, @@ -104,17 +114,17 @@ pub enum Chain { } pub(crate) trait ConvertChain { - fn to_ticker(&self) -> String; + fn to_ticker(&self) -> &'static str; } impl ConvertChain for Chain { - fn to_ticker(&self) -> String { + fn to_ticker(&self) -> &'static str { match self { - Chain::Avalanche => "AVAX".to_owned(), - Chain::Bsc => "BNB".to_owned(), - Chain::Eth => "ETH".to_owned(), - Chain::Fantom => "FTM".to_owned(), - Chain::Polygon => "MATIC".to_owned(), + Chain::Avalanche => "AVAX", + Chain::Bsc => "BNB", + Chain::Eth => "ETH", + Chain::Fantom => "FTM", + Chain::Polygon => "MATIC", } } } @@ -258,7 +268,6 @@ impl UriMeta { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NftCommon { pub(crate) token_address: Address, - pub(crate) token_id: BigDecimal, pub(crate) amount: BigDecimal, pub(crate) owner_of: Address, pub(crate) token_hash: Option, @@ -283,6 +292,8 @@ pub struct Nft { #[serde(flatten)] pub(crate) common: NftCommon, pub(crate) chain: Chain, + #[serde(serialize_with = "serialize_token_id", deserialize_with = "deserialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) block_number_minted: Option, pub(crate) block_number: u64, pub(crate) contract_type: ContractType, @@ -293,7 +304,7 @@ pub struct Nft { pub(crate) struct BuildNftFields { pub(crate) token_address: Address, - pub(crate) token_id: BigDecimal, + pub(crate) token_id: BigUint, pub(crate) amount: BigDecimal, pub(crate) owner_of: Address, pub(crate) contract_type: ContractType, @@ -306,7 +317,6 @@ pub(crate) fn build_nft_with_empty_meta(nft_fields: BuildNftFields) -> Nft { Nft { common: NftCommon { token_address: nft_fields.token_address, - token_id: nft_fields.token_id, amount: nft_fields.amount, owner_of: nft_fields.owner_of, token_hash: None, @@ -321,6 +331,7 @@ pub(crate) fn build_nft_with_empty_meta(nft_fields: BuildNftFields) -> Nft { possible_spam: nft_fields.possible_spam, }, chain: nft_fields.chain, + token_id: nft_fields.token_id, block_number_minted: None, block_number: nft_fields.block_number, contract_type: nft_fields.contract_type, @@ -339,6 +350,7 @@ pub(crate) struct NftFromMoralis { pub(crate) block_number_minted: Option>, pub(crate) block_number: SerdeStringWrap, pub(crate) contract_type: Option, + pub(crate) token_id: SerdeStringWrap, } #[derive(Debug)] @@ -378,7 +390,8 @@ pub struct WithdrawErc1155 { pub(crate) chain: Chain, pub(crate) to: String, pub(crate) token_address: String, - pub(crate) token_id: BigDecimal, + #[serde(deserialize_with = "deserialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) amount: Option, #[serde(default)] pub(crate) max: bool, @@ -390,7 +403,8 @@ pub struct WithdrawErc721 { pub(crate) chain: Chain, pub(crate) to: String, pub(crate) token_address: String, - pub(crate) token_id: BigDecimal, + #[serde(deserialize_with = "deserialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) fee: Option, } @@ -413,7 +427,8 @@ pub struct TransactionNftDetails { pub(crate) to: Vec, pub(crate) contract_type: ContractType, pub(crate) token_address: String, - pub(crate) token_id: BigDecimal, + #[serde(serialize_with = "serialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) amount: BigDecimal, pub(crate) fee_details: Option, /// The coin transaction belongs to @@ -498,7 +513,6 @@ pub struct NftTransferCommon { pub(crate) value: Option, pub(crate) transaction_type: Option, pub(crate) token_address: Address, - pub(crate) token_id: BigDecimal, pub(crate) from_address: Address, pub(crate) to_address: Address, pub(crate) amount: BigDecimal, @@ -519,6 +533,8 @@ pub struct NftTransferHistory { #[serde(flatten)] pub(crate) common: NftTransferCommon, pub(crate) chain: Chain, + #[serde(serialize_with = "serialize_token_id", deserialize_with = "deserialize_token_id")] + pub(crate) token_id: BigUint, pub(crate) block_number: u64, pub(crate) block_timestamp: u64, pub(crate) contract_type: ContractType, @@ -531,6 +547,8 @@ pub struct NftTransferHistory { pub(crate) status: TransferStatus, #[serde(default)] pub(crate) possible_phishing: bool, + pub(crate) fee_details: Option, + pub(crate) confirmations: u64, } /// Represents an NFT transfer structure specifically for deserialization from Moralis's JSON response. @@ -543,6 +561,7 @@ pub(crate) struct NftTransferHistoryFromMoralis { pub(crate) block_number: SerdeStringWrap, pub(crate) block_timestamp: String, pub(crate) contract_type: Option, + pub(crate) token_id: SerdeStringWrap, } /// Represents the detailed transfer history of NFTs, including the total number of transfers @@ -589,13 +608,13 @@ pub struct UpdateNftReq { #[derive(Debug, Deserialize, Eq, Hash, PartialEq)] pub struct NftTokenAddrId { pub(crate) token_address: String, - pub(crate) token_id: BigDecimal, + pub(crate) token_id: BigUint, } #[derive(Debug)] pub struct TransferMeta { pub(crate) token_address: String, - pub(crate) token_id: BigDecimal, + pub(crate) token_id: BigUint, pub(crate) token_uri: Option, pub(crate) token_domain: Option, pub(crate) collection_name: Option, @@ -608,7 +627,7 @@ impl From for TransferMeta { fn from(nft_db: Nft) -> Self { TransferMeta { token_address: eth_addr_to_hex(&nft_db.common.token_address), - token_id: nft_db.common.token_id, + token_id: nft_db.token_id, token_uri: nft_db.common.token_uri, token_domain: nft_db.common.token_domain, collection_name: nft_db.common.collection_name, @@ -625,26 +644,56 @@ impl From for TransferMeta { /// required for NFT operations, including guarding against concurrent accesses and /// dealing with platform-specific storage mechanisms. pub(crate) struct NftCtx { - /// An asynchronous mutex to guard against concurrent NFT operations, ensuring data consistency. - pub(crate) guard: Arc>, - #[cfg(target_arch = "wasm32")] /// Platform-specific database for caching NFT data. + #[cfg(target_arch = "wasm32")] pub(crate) nft_cache_db: SharedDb, + #[cfg(not(target_arch = "wasm32"))] + pub(crate) nft_cache_db: Arc>, } impl NftCtx { /// Create a new `NftCtx` from the given MM context. /// /// If an `NftCtx` instance doesn't already exist in the MM context, it gets created and cached for subsequent use. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { + Ok(try_s!(from_ctx(&ctx.nft_ctx, move || { + let async_sqlite_connection = ctx + .async_sqlite_connection + .ok_or("async_sqlite_connection is not initialized".to_owned())?; + Ok(NftCtx { + nft_cache_db: async_sqlite_connection.clone(), + }) + }))) + } + + #[cfg(target_arch = "wasm32")] pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { Ok(try_s!(from_ctx(&ctx.nft_ctx, move || { Ok(NftCtx { - guard: Arc::new(AsyncMutex::new(())), - #[cfg(target_arch = "wasm32")] nft_cache_db: ConstructibleDb::new(ctx).into_shared(), }) }))) } + + /// Lock database to guard against concurrent NFT operations, ensuring data consistency. + #[cfg(not(target_arch = "wasm32"))] + pub(crate) async fn lock_db( + &self, + ) -> MmResult { + Ok(self.nft_cache_db.lock().await) + } + + #[cfg(target_arch = "wasm32")] + pub(crate) async fn lock_db( + &self, + ) -> MmResult { + self.nft_cache_db + .get_or_initialize() + .await + .mm_err(WasmNftCacheError::from) + .mm_err(LockDBError::from) + } } #[derive(Debug, Serialize)] @@ -667,3 +716,19 @@ pub(crate) struct SpamContractRes { pub(crate) struct PhishingDomainRes { pub(crate) result: HashMap, } + +fn serialize_token_id(token_id: &BigUint, serializer: S) -> Result +where + S: Serializer, +{ + let token_id_str = token_id.to_string(); + serializer.serialize_str(&token_id_str) +} + +fn deserialize_token_id<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + BigUint::from_str(&s).map_err(serde::de::Error::custom) +} diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index e8f27b854c..99ec3925de 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -2,15 +2,14 @@ use crate::eth::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, NftFromMoralis, NftListFilters, NftTransferHistoryFilters, NftTransferHistoryFromMoralis, PhishingDomainReq, PhishingDomainRes, SpamContractReq, SpamContractRes, TransferMeta, UriMeta}; -use crate::nft::storage::db_test_helpers::{init_nft_history_storage, init_nft_list_storage, nft, nft_list, - nft_transfer_history}; +use crate::nft::storage::db_test_helpers::{get_nft_ctx, nft, nft_list, nft_transfer_history}; use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps, RemoveNftResult}; use crate::nft::{check_moralis_ipfs_bafy, get_domain_from_url, process_metadata_for_spam_link, process_text_for_spam_link}; use common::cross_test; use ethereum_types::Address; use mm2_net::transport::send_post_request_to_uri; -use mm2_number::BigDecimal; +use mm2_number::{BigDecimal, BigUint}; use std::num::NonZeroUsize; use std::str::FromStr; @@ -158,11 +157,13 @@ cross_test!(test_camo, { cross_test!(test_add_get_nfts, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); + let token_id = BigUint::from_str(TOKEN_ID).unwrap(); let nft = storage .get_nft(&chain, TOKEN_ADD.to_string(), token_id) .await @@ -173,7 +174,9 @@ cross_test!(test_add_get_nfts, { cross_test!(test_last_nft_block, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -186,7 +189,9 @@ cross_test!(test_last_nft_block, { cross_test!(test_nft_list, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -203,11 +208,13 @@ cross_test!(test_nft_list, { cross_test!(test_remove_nft, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); + let token_id = BigUint::from_str(TOKEN_ID).unwrap(); let remove_rslt = storage .remove_nft_from_list(&chain, TOKEN_ADD.to_string(), token_id, 28056800) .await @@ -226,7 +233,9 @@ cross_test!(test_remove_nft, { cross_test!(test_nft_amount, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let mut nft = nft(); storage .add_nfts_to_list(chain, vec![nft.clone()], 25919780) @@ -236,11 +245,7 @@ cross_test!(test_nft_amount, { nft.common.amount -= BigDecimal::from(1); storage.update_nft_amount(&chain, nft.clone(), 25919800).await.unwrap(); let amount = storage - .get_nft_amount( - &chain, - eth_addr_to_hex(&nft.common.token_address), - nft.common.token_id.clone(), - ) + .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.token_id.clone()) .await .unwrap() .unwrap(); @@ -255,7 +260,7 @@ cross_test!(test_nft_amount, { .await .unwrap(); let amount = storage - .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.common.token_id) + .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.token_id) .await .unwrap() .unwrap(); @@ -266,7 +271,9 @@ cross_test!(test_nft_amount, { cross_test!(test_refresh_metadata, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let new_symbol = "NEW_SYMBOL"; let mut nft = nft(); storage @@ -276,7 +283,7 @@ cross_test!(test_refresh_metadata, { nft.common.symbol = Some(new_symbol.to_string()); drop_mutability!(nft); let token_add = eth_addr_to_hex(&nft.common.token_address); - let token_id = nft.common.token_id.clone(); + let token_id = nft.token_id.clone(); storage.refresh_nft_metadata(&chain, nft).await.unwrap(); let nft_upd = storage.get_nft(&chain, token_add, token_id).await.unwrap().unwrap(); assert_eq!(new_symbol.to_string(), nft_upd.common.symbol.unwrap()); @@ -284,7 +291,9 @@ cross_test!(test_refresh_metadata, { cross_test!(test_update_nft_spam_by_token_address, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -303,7 +312,9 @@ cross_test!(test_update_nft_spam_by_token_address, { cross_test!(test_exclude_nft_spam, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -320,7 +331,9 @@ cross_test!(test_exclude_nft_spam, { cross_test!(test_get_animation_external_domains, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -332,7 +345,9 @@ cross_test!(test_get_animation_external_domains, { cross_test!(test_update_nft_phishing_by_domain, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -358,7 +373,9 @@ cross_test!(test_update_nft_phishing_by_domain, { cross_test!(test_exclude_nft_phishing_spam, { let chain = Chain::Bsc; - let storage = init_nft_list_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -380,11 +397,13 @@ cross_test!(test_exclude_nft_phishing_spam, { cross_test!(test_add_get_transfers, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); - let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); + let token_id = BigUint::from_str(TOKEN_ID).unwrap(); let transfer1 = storage .get_transfers_by_token_addr_id(chain, TOKEN_ADD.to_string(), token_id) .await @@ -405,7 +424,9 @@ cross_test!(test_add_get_transfers, { cross_test!(test_last_transfer_block, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -418,7 +439,9 @@ cross_test!(test_last_transfer_block, { cross_test!(test_transfer_history, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -435,7 +458,9 @@ cross_test!(test_transfer_history, { cross_test!(test_transfer_history_filters, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -495,7 +520,9 @@ cross_test!(test_transfer_history_filters, { cross_test!(test_get_update_transfer_meta, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -528,7 +555,9 @@ cross_test!(test_get_update_transfer_meta, { cross_test!(test_update_transfer_spam_by_token_address, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -547,7 +576,9 @@ cross_test!(test_update_transfer_spam_by_token_address, { cross_test!(test_get_token_addresses, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -557,7 +588,9 @@ cross_test!(test_get_token_addresses, { cross_test!(test_exclude_transfer_spam, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -578,7 +611,9 @@ cross_test!(test_exclude_transfer_spam, { cross_test!(test_get_domains, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -590,7 +625,9 @@ cross_test!(test_get_domains, { cross_test!(test_update_transfer_phishing_by_domain, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -616,7 +653,9 @@ cross_test!(test_update_transfer_phishing_by_domain, { cross_test!(test_exclude_transfer_phishing_spam, { let chain = Chain::Bsc; - let storage = init_nft_history_storage(&chain).await; + let nft_ctx = get_nft_ctx(&chain).await; + let storage = nft_ctx.lock_db().await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs index 9d4e8bfe14..d59b845661 100644 --- a/mm2src/coins/nft/storage/db_test_helpers.rs +++ b/mm2src/coins/nft/storage/db_test_helpers.rs @@ -1,16 +1,18 @@ -use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftTransferCommon, NftTransferHistory, +use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftCtx, NftTransferCommon, NftTransferHistory, TransferStatus, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; use ethereum_types::Address; -use mm2_number::BigDecimal; +use mm2_number::{BigDecimal, BigUint}; +#[cfg(not(target_arch = "wasm32"))] +use mm2_test_helpers::for_tests::mm_ctx_with_custom_async_db; +#[cfg(target_arch = "wasm32")] use mm2_test_helpers::for_tests::mm_ctx_with_custom_db; use std::str::FromStr; +use std::sync::Arc; pub(crate) fn nft() -> Nft { Nft { common: NftCommon { token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(), - token_id: Default::default(), amount: BigDecimal::from_str("2").unwrap(), owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), token_hash: Some("b34ddf294013d20a6d70691027625839".to_string()), @@ -28,6 +30,7 @@ pub(crate) fn nft() -> Nft { possible_spam: true, }, chain: Chain::Bsc, + token_id: Default::default(), block_number_minted: Some(25465916), block_number: 25919780, contract_type: ContractType::Erc1155, @@ -52,7 +55,6 @@ pub(crate) fn nft_list() -> Vec { let nft = Nft { common: NftCommon { token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(), - token_id: Default::default(), amount: BigDecimal::from_str("2").unwrap(), owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), token_hash: Some("b34ddf294013d20a6d70691027625839".to_string()), @@ -67,6 +69,7 @@ pub(crate) fn nft_list() -> Vec { possible_spam: false, }, chain: Chain::Bsc, + token_id: Default::default(), block_number_minted: Some(25465916), block_number: 25919780, contract_type: ContractType::Erc1155, @@ -89,7 +92,6 @@ pub(crate) fn nft_list() -> Vec { let nft1 = Nft { common: NftCommon { token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300047252").unwrap(), amount: BigDecimal::from_str("1").unwrap(), owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), token_hash: Some("c5d1cfd75a0535b0ec750c0156e6ddfe".to_string()), @@ -107,6 +109,7 @@ pub(crate) fn nft_list() -> Vec { possible_spam: true, }, chain: Chain::Bsc, + token_id: BigUint::from_str("214300047252").unwrap(), block_number_minted: Some(25721963), block_number: 28056726, contract_type: ContractType::Erc721, @@ -131,7 +134,6 @@ pub(crate) fn nft_list() -> Vec { let nft2 = Nft { common: NftCommon { token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300047253").unwrap(), amount: BigDecimal::from_str("1").unwrap(), owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), token_hash: Some("c5d1cfd75a0535b0ec750c0156e6ddfe".to_string()), @@ -149,6 +151,7 @@ pub(crate) fn nft_list() -> Vec { possible_spam: false, }, chain: Chain::Bsc, + token_id: BigUint::from_str("214300047253").unwrap(), block_number_minted: Some(25721963), block_number: 28056726, contract_type: ContractType::Erc721, @@ -173,7 +176,6 @@ pub(crate) fn nft_list() -> Vec { let nft3 = Nft { common: NftCommon { token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300044414").unwrap(), amount: BigDecimal::from_str("1").unwrap(), owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), token_hash: Some("125f8f4e952e107c257960000b4b250e".to_string()), @@ -191,6 +193,7 @@ pub(crate) fn nft_list() -> Vec { possible_spam: false, }, chain: Chain::Bsc, + token_id: BigUint::from_str("214300044414").unwrap(), block_number_minted: Some(25810308), block_number: 28056721, contract_type: ContractType::Erc721, @@ -224,7 +227,6 @@ pub(crate) fn nft_transfer_history() -> Vec { value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(), - token_id: Default::default(), from_address: Address::from_str("0x4ff0bbc9b64d635a4696d1a38554fb2529c103ff").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), amount: BigDecimal::from_str("1").unwrap(), @@ -233,6 +235,7 @@ pub(crate) fn nft_transfer_history() -> Vec { possible_spam: false, }, chain: Chain::Bsc, + token_id: Default::default(), block_number: 25919780, block_timestamp: 1677166110, contract_type: ContractType::Erc1155, @@ -244,6 +247,8 @@ pub(crate) fn nft_transfer_history() -> Vec { token_name: None, status: TransferStatus::Receive, possible_phishing: false, + fee_details: None, + confirmations: 0, }; let transfer1 = NftTransferHistory { @@ -255,7 +260,6 @@ pub(crate) fn nft_transfer_history() -> Vec { value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300047252").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), amount: BigDecimal::from_str("1").unwrap(), @@ -264,6 +268,7 @@ pub(crate) fn nft_transfer_history() -> Vec { possible_spam: true, }, chain: Chain::Bsc, + token_id: BigUint::from_str("214300047252").unwrap(), block_number: 28056726, block_timestamp: 1683627432, contract_type: ContractType::Erc721, @@ -275,6 +280,8 @@ pub(crate) fn nft_transfer_history() -> Vec { token_name: None, status: TransferStatus::Receive, possible_phishing: false, + fee_details: None, + confirmations: 0, }; // Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction @@ -287,7 +294,6 @@ pub(crate) fn nft_transfer_history() -> Vec { value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300047253").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), amount: BigDecimal::from_str("1").unwrap(), @@ -296,6 +302,7 @@ pub(crate) fn nft_transfer_history() -> Vec { possible_spam: false, }, chain: Chain::Bsc, + token_id: BigUint::from_str("214300047253").unwrap(), block_number: 28056726, block_timestamp: 1683627432, contract_type: ContractType::Erc721, @@ -307,6 +314,8 @@ pub(crate) fn nft_transfer_history() -> Vec { token_name: None, status: TransferStatus::Receive, possible_phishing: false, + fee_details: None, + confirmations: 0, }; let transfer3 = NftTransferHistory { @@ -318,7 +327,6 @@ pub(crate) fn nft_transfer_history() -> Vec { value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), - token_id: BigDecimal::from_str("214300044414").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), amount: BigDecimal::from_str("1").unwrap(), @@ -327,6 +335,7 @@ pub(crate) fn nft_transfer_history() -> Vec { possible_spam: false, }, chain: Chain::Bsc, + token_id: BigUint::from_str("214300044414").unwrap(), block_number: 28056721, block_timestamp: 1683627417, contract_type: ContractType::Erc721, @@ -338,26 +347,16 @@ pub(crate) fn nft_transfer_history() -> Vec { token_name: Some("Nebula Nodes".to_string()), status: TransferStatus::Receive, possible_phishing: false, + fee_details: None, + confirmations: 0, }; vec![transfer, transfer1, transfer2, transfer3] } -pub(crate) async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { +pub(crate) async fn get_nft_ctx(_chain: &Chain) -> Arc { + #[cfg(not(target_arch = "wasm32"))] + let ctx = mm_ctx_with_custom_async_db().await; + #[cfg(target_arch = "wasm32")] let ctx = mm_ctx_with_custom_db(); - let storage = NftStorageBuilder::new(&ctx).build().unwrap(); - NftListStorageOps::init(&storage, chain).await.unwrap(); - let is_initialized = NftListStorageOps::is_initialized(&storage, chain).await.unwrap(); - assert!(is_initialized); - storage -} - -pub(crate) async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { - let ctx = mm_ctx_with_custom_db(); - let storage = NftStorageBuilder::new(&ctx).build().unwrap(); - NftTransferHistoryStorageOps::init(&storage, chain).await.unwrap(); - let is_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain) - .await - .unwrap(); - assert!(is_initialized); - storage + NftCtx::from_ctx(&ctx).unwrap() } diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs index 14cc9243f0..c28c33ea54 100644 --- a/mm2src/coins/nft/storage/mod.rs +++ b/mm2src/coins/nft/storage/mod.rs @@ -1,13 +1,12 @@ +use crate::eth::EthTxFeeDetails; use crate::nft::nft_structs::{Chain, Nft, NftList, NftListFilters, NftTokenAddrId, NftTransferHistory, NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta}; use crate::WithdrawError; use async_trait::async_trait; -use derive_more::Display; use ethereum_types::Address; -use mm2_core::mm_ctx::MmArc; use mm2_err_handle::mm_error::MmResult; use mm2_err_handle::mm_error::{NotEqual, NotMmError}; -use mm2_number::BigDecimal; +use mm2_number::{BigDecimal, BigUint}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::num::NonZeroUsize; @@ -62,14 +61,14 @@ pub trait NftListStorageOps { &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error>; async fn remove_nft_from_list( &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, scanned_block: u64, ) -> MmResult; @@ -77,7 +76,7 @@ pub trait NftListStorageOps { &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error>; async fn refresh_nft_metadata(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error>; @@ -154,7 +153,7 @@ pub trait NftTransferHistoryStorageOps { &self, chain: Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error>; async fn get_transfer_by_tx_hash_and_log_index( @@ -204,41 +203,6 @@ pub trait NftTransferHistoryStorageOps { ) -> MmResult<(), Self::Error>; } -/// Represents potential errors that can occur when creating an NFT storage. -#[derive(Debug, Deserialize, Display, Serialize)] -pub enum CreateNftStorageError { - Internal(String), -} - -impl From for WithdrawError { - fn from(e: CreateNftStorageError) -> Self { - match e { - CreateNftStorageError::Internal(err) => WithdrawError::InternalError(err), - } - } -} - -/// `NftStorageBuilder` is used to create an instance that implements the [`NftListStorageOps`] -/// and [`NftTransferHistoryStorageOps`] traits. -pub struct NftStorageBuilder<'a> { - ctx: &'a MmArc, -} - -impl<'a> NftStorageBuilder<'a> { - /// Creates a new `NftStorageBuilder` instance with the provided context. - #[inline] - pub fn new(ctx: &MmArc) -> NftStorageBuilder<'_> { NftStorageBuilder { ctx } } - - /// `build` function is used to build nft storage which implements [`NftListStorageOps`] and [`NftTransferHistoryStorageOps`] traits. - #[inline] - pub fn build(&self) -> MmResult { - #[cfg(target_arch = "wasm32")] - return wasm::wasm_storage::IndexedDbNftStorage::new(self.ctx); - #[cfg(not(target_arch = "wasm32"))] - sql_storage::SqliteNftStorage::new(self.ctx) - } -} - /// `get_offset_limit` function calculates offset and limit for final result if we use pagination. fn get_offset_limit(max: bool, limit: usize, page_number: Option, total_count: usize) -> (usize, usize) { if max { @@ -272,4 +236,5 @@ pub(crate) struct TransferDetailsJson { pub(crate) operator: Option, pub(crate) from_address: Address, pub(crate) to_address: Address, + pub(crate) fee_details: Option, } diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index 4e76b93249..86166a4793 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -2,37 +2,35 @@ use crate::nft::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, ConvertChain, Nft, NftCommon, NftList, NftListFilters, NftTokenAddrId, NftTransferCommon, NftTransferHistory, NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta, UriMeta}; -use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftDetailsJson, NftListStorageOps, NftStorageError, +use crate::nft::storage::{get_offset_limit, NftDetailsJson, NftListStorageOps, NftStorageError, NftTransferHistoryStorageOps, RemoveNftResult, TransferDetailsJson}; use async_trait::async_trait; -use common::async_blocking; +use db_common::async_sql_conn::{AsyncConnError, AsyncConnection}; use db_common::sql_build::{SqlCondition, SqlQuery}; use db_common::sqlite::rusqlite::types::{FromSqlError, Type}; use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, Statement}; use db_common::sqlite::sql_builder::SqlBuilder; use db_common::sqlite::{query_single_row, string_from_row, validate_table_name, CHECK_TABLE_EXISTS_SQL}; use ethereum_types::Address; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::map_to_mm::MapToMmResult; -use mm2_err_handle::mm_error::{MmError, MmResult}; -use mm2_number::BigDecimal; +use futures::lock::MutexGuard as AsyncMutexGuard; +use mm2_err_handle::prelude::*; +use mm2_number::{BigDecimal, BigUint}; use serde_json::Value as Json; use serde_json::{self as json}; use std::collections::HashSet; use std::convert::TryInto; use std::num::NonZeroUsize; use std::str::FromStr; -use std::sync::{Arc, Mutex}; impl Chain { fn nft_list_table_name(&self) -> SqlResult { - let name = self.to_ticker() + "_nft_list"; + let name = self.to_ticker().to_owned() + "_nft_list"; validate_table_name(&name)?; Ok(name) } fn transfer_history_table_name(&self) -> SqlResult { - let name = self.to_ticker() + "_nft_transfer_history"; + let name = self.to_ticker().to_owned() + "_nft_transfer_history"; validate_table_name(&name)?; Ok(name) } @@ -82,7 +80,7 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn create_transfer_history_table_sql(chain: &Chain) -> MmResult { +fn create_transfer_history_table_sql(chain: &Chain) -> Result { let table_name = chain.transfer_history_table_name()?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( @@ -112,7 +110,7 @@ fn create_transfer_history_table_sql(chain: &Chain) -> MmResult MmResult { +fn create_scanned_nft_blocks_sql() -> Result { let table_name = scanned_nft_blocks_table_name()?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( @@ -124,26 +122,9 @@ fn create_scanned_nft_blocks_sql() -> MmResult { Ok(sql) } -impl NftStorageError for SqlError {} +impl NftStorageError for AsyncConnError {} -#[derive(Clone)] -pub struct SqliteNftStorage(Arc>); - -impl SqliteNftStorage { - pub fn new(ctx: &MmArc) -> MmResult { - let sqlite_connection = ctx - .sqlite_connection - .ok_or(MmError::new(CreateNftStorageError::Internal( - "sqlite_connection is not initialized".to_owned(), - )))?; - Ok(SqliteNftStorage(sqlite_connection.clone())) - } -} - -fn get_nft_list_builder_preimage( - chains: Vec, - filters: Option, -) -> MmResult { +fn get_nft_list_builder_preimage(chains: Vec, filters: Option) -> Result { let union_sql_strings = chains .iter() .map(|chain| { @@ -156,7 +137,7 @@ fn get_nft_list_builder_preimage( .to_string(); Ok(sql_string) }) - .collect::, SqlError>>()?; + .collect::, SqlError>>()?; let union_alias_sql = format!("({}) AS nft_list", union_sql_strings.join(" UNION ALL ")); let mut final_sql_builder = SqlBuilder::select_from(union_alias_sql); final_sql_builder.order_desc("nft_list.block_number"); @@ -181,7 +162,7 @@ fn nft_list_builder_preimage(table_name: &str, filters: Option) fn get_nft_transfer_builder_preimage( chains: Vec, filters: Option, -) -> MmResult { +) -> Result { let union_sql_strings = chains .into_iter() .map(|chain| { @@ -194,7 +175,7 @@ fn get_nft_transfer_builder_preimage( .to_string(); Ok(sql_string) }) - .collect::, SqlError>>()?; + .collect::, SqlError>>()?; let union_alias_sql = format!("({}) AS nft_history", union_sql_strings.join(" UNION ALL ")); let mut final_sql_builder = SqlBuilder::select_from(union_alias_sql); final_sql_builder.order_desc("nft_history.block_timestamp"); @@ -230,7 +211,7 @@ fn nft_history_table_builder_preimage( Ok(sql_builder) } -fn finalize_sql_builder(mut sql_builder: SqlBuilder, offset: usize, limit: usize) -> MmResult { +fn finalize_sql_builder(mut sql_builder: SqlBuilder, offset: usize, limit: usize) -> Result { let sql = sql_builder .field("*") .offset(offset) @@ -247,9 +228,9 @@ fn get_and_parse(row: &Row<'_>, column: &str) -> Result fn nft_from_row(row: &Row<'_>) -> Result { let token_address = get_and_parse(row, "token_address")?; - let token_id = get_and_parse(row, "token_id")?; + let token_id: BigUint = get_and_parse(row, "token_id")?; let chain = get_and_parse(row, "chain")?; - let amount = get_and_parse(row, "amount")?; + let amount: BigDecimal = get_and_parse(row, "amount")?; let block_number: u64 = row.get("block_number")?; let contract_type = get_and_parse(row, "contract_type")?; let possible_spam: i32 = row.get("possible_spam")?; @@ -302,7 +283,6 @@ fn nft_from_row(row: &Row<'_>) -> Result { let common = NftCommon { token_address, - token_id, amount, owner_of: nft_details.owner_of, token_hash: nft_details.token_hash, @@ -319,6 +299,7 @@ fn nft_from_row(row: &Row<'_>) -> Result { let nft = Nft { common, chain, + token_id, block_number_minted: nft_details.block_number_minted, block_number, contract_type, @@ -336,7 +317,7 @@ fn transfer_history_from_row(row: &Row<'_>) -> Result = row.get("token_uri")?; @@ -359,7 +340,6 @@ fn transfer_history_from_row(row: &Row<'_>) -> Result) -> Result) -> Result) -> Result { fn token_address_id_from_row(row: &Row<'_>) -> Result { let token_address: String = row.get("token_address")?; let token_id_str: String = row.get("token_id")?; - let token_id = BigDecimal::from_str(&token_id_str).map_err(|_| SqlError::from(FromSqlError::InvalidType))?; + let token_id = BigUint::from_str(&token_id_str).map_err(|_| SqlError::from(FromSqlError::InvalidType))?; Ok(NftTokenAddrId { token_address, token_id, }) } -fn insert_nft_in_list_sql(chain: &Chain) -> MmResult { +fn insert_nft_in_list_sql(chain: &Chain) -> Result { let table_name = chain.nft_list_table_name()?; let sql = format!( "INSERT INTO {} ( @@ -422,7 +405,7 @@ fn insert_nft_in_list_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn insert_transfer_in_history_sql(chain: &Chain) -> MmResult { +fn insert_transfer_in_history_sql(chain: &Chain) -> Result { let table_name = chain.transfer_history_table_name()?; let sql = format!( "INSERT INTO {} ( @@ -437,7 +420,7 @@ fn insert_transfer_in_history_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn upsert_last_scanned_block_sql() -> MmResult { +fn upsert_last_scanned_block_sql() -> Result { let table_name = scanned_nft_blocks_table_name()?; let sql = format!( "INSERT OR REPLACE INTO {} (chain, last_scanned_block) VALUES (?1, ?2);", @@ -446,7 +429,7 @@ fn upsert_last_scanned_block_sql() -> MmResult { Ok(sql) } -fn refresh_nft_metadata_sql(chain: &Chain) -> MmResult { +fn refresh_nft_metadata_sql(chain: &Chain) -> Result { let table_name = chain.nft_list_table_name()?; let sql = format!( "UPDATE {} SET possible_spam = ?1, possible_phishing = ?2, collection_name = ?3, symbol = ?4, token_uri = ?5, token_domain = ?6, metadata = ?7, \ @@ -457,7 +440,7 @@ fn refresh_nft_metadata_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn update_transfers_meta_by_token_addr_id_sql(chain: &Chain) -> MmResult { +fn update_transfers_meta_by_token_addr_id_sql(chain: &Chain) -> Result { let table_name = chain.transfer_history_table_name()?; let sql = format!( "UPDATE {} SET token_uri = ?1, token_domain = ?2, collection_name = ?3, image_url = ?4, image_domain = ?5, \ @@ -467,7 +450,7 @@ fn update_transfers_meta_by_token_addr_id_sql(chain: &Chain) -> MmResult MmResult { +fn update_transfer_spam_by_token_addr_id(chain: &Chain) -> Result { let table_name = chain.transfer_history_table_name()?; let sql = format!( "UPDATE {} SET possible_spam = ?1 WHERE token_address = ?2 AND token_id = ?3;", @@ -476,7 +459,7 @@ fn update_transfer_spam_by_token_addr_id(chain: &Chain) -> MmResult MmResult { +fn select_last_block_number_sql(table_name: String) -> Result { let sql = format!( "SELECT block_number FROM {} ORDER BY block_number DESC LIMIT 1", table_name @@ -490,7 +473,7 @@ fn select_last_scanned_block_sql() -> MmResult { Ok(sql) } -fn delete_nft_sql(table_name: String) -> Result> { +fn delete_nft_sql(table_name: String) -> Result { let sql = format!("DELETE FROM {} WHERE token_address=?1 AND token_id=?2", table_name); Ok(sql) } @@ -499,19 +482,19 @@ fn block_number_from_row(row: &Row<'_>) -> Result { row.get::<_, fn nft_amount_from_row(row: &Row<'_>) -> Result { row.get(0) } -fn get_nfts_by_token_address_statement(conn: &Connection, table_name: String) -> MmResult { +fn get_nfts_by_token_address_statement(conn: &Connection, table_name: String) -> Result { let sql_query = format!("SELECT * FROM {} WHERE token_address = ?", table_name); let stmt = conn.prepare(&sql_query)?; Ok(stmt) } -fn get_token_addresses_statement(conn: &Connection, table_name: String) -> MmResult { +fn get_token_addresses_statement(conn: &Connection, table_name: String) -> Result { let sql_query = format!("SELECT DISTINCT token_address FROM {}", table_name); let stmt = conn.prepare(&sql_query)?; Ok(stmt) } -fn get_transfers_from_block_statement<'a>(conn: &'a Connection, chain: &'a Chain) -> MmResult, SqlError> { +fn get_transfers_from_block_statement<'a>(conn: &'a Connection, chain: &'a Chain) -> Result, SqlError> { let table_name = chain.transfer_history_table_name()?; let sql_query = format!( "SELECT * FROM {} WHERE block_number >= ? ORDER BY block_number ASC", @@ -521,17 +504,14 @@ fn get_transfers_from_block_statement<'a>(conn: &'a Connection, chain: &'a Chain Ok(stmt) } -fn get_transfers_by_token_addr_id_statement(conn: &Connection, chain: Chain) -> MmResult { +fn get_transfers_by_token_addr_id_statement(conn: &Connection, chain: Chain) -> Result { let table_name = chain.transfer_history_table_name()?; let sql_query = format!("SELECT * FROM {} WHERE token_address = ? AND token_id = ?", table_name); let stmt = conn.prepare(&sql_query)?; Ok(stmt) } -fn get_transfers_with_empty_meta_builder<'a>( - conn: &'a Connection, - chain: &'a Chain, -) -> MmResult, SqlError> { +fn get_transfers_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Chain) -> Result, SqlError> { let table_name = chain.transfer_history_table_name()?; let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; sql_builder @@ -542,35 +522,33 @@ fn get_transfers_with_empty_meta_builder<'a>( .and_where_is_null("token_uri") .and_where_is_null("collection_name") .and_where_is_null("image_url") - .and_where_is_null("token_name"); + .and_where_is_null("token_name") + .and_where("possible_spam == 0"); drop_mutability!(sql_builder); Ok(sql_builder) } #[async_trait] -impl NftListStorageOps for SqliteNftStorage { - type Error = SqlError; +impl NftListStorageOps for AsyncMutexGuard<'_, AsyncConnection> { + type Error = AsyncConnError; async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { - let selfi = self.clone(); let sql_nft_list = create_nft_list_table_sql(chain)?; - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { conn.execute(&sql_nft_list, []).map(|_| ())?; conn.execute(&create_scanned_nft_blocks_sql()?, []).map(|_| ())?; Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn is_initialized(&self, chain: &Chain) -> MmResult { let table_name = chain.nft_list_table_name()?; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - let nft_list_initialized = query_single_row(&conn, CHECK_TABLE_EXISTS_SQL, [table_name], string_from_row)?; + self.call(move |conn| { + let nft_list_initialized = query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [table_name], string_from_row)?; let scanned_nft_blocks_initialized = query_single_row( - &conn, + conn, CHECK_TABLE_EXISTS_SQL, [scanned_nft_blocks_table_name()?], string_from_row, @@ -578,6 +556,7 @@ impl NftListStorageOps for SqliteNftStorage { Ok(nft_list_initialized.is_some() && scanned_nft_blocks_initialized.is_some()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_nft_list( @@ -588,9 +567,7 @@ impl NftListStorageOps for SqliteNftStorage { page_number: Option, filters: Option, ) -> MmResult { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_builder = get_nft_list_builder_preimage(chains, filters)?; let total_count_builder_sql = sql_builder .clone() @@ -616,6 +593,7 @@ impl NftListStorageOps for SqliteNftStorage { Ok(result) }) .await + .map_to_mm(AsyncConnError::from) } async fn add_nfts_to_list(&self, chain: Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> @@ -623,9 +601,7 @@ impl NftListStorageOps for SqliteNftStorage { I: IntoIterator + Send + 'static, I::IntoIter: Send, { - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; for nft in nfts { @@ -638,7 +614,7 @@ impl NftListStorageOps for SqliteNftStorage { let details_json = json::to_string(&details_json).expect("serialization should not fail"); let params = [ Some(eth_addr_to_hex(&nft.common.token_address)), - Some(nft.common.token_id.to_string()), + Some(nft.token_id.to_string()), Some(nft.chain.to_string()), Some(nft.common.amount.to_string()), Some(nft.block_number.to_string()), @@ -667,45 +643,44 @@ impl NftListStorageOps for SqliteNftStorage { ]; sql_transaction.execute(&insert_nft_in_list_sql(&chain)?, params)?; } - let scanned_block_params = [chain.to_ticker(), last_scanned_block.to_string()]; + let scanned_block_params = [chain.to_ticker().to_string(), last_scanned_block.to_string()]; sql_transaction.execute(&upsert_last_scanned_block_sql()?, scanned_block_params)?; sql_transaction.commit()?; Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_nft( &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error> { let table_name = chain.nft_list_table_name()?; - let sql = format!("SELECT * FROM {} WHERE token_address=?1 AND token_id=?2", table_name); - let params = [token_address, token_id.to_string()]; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, params, nft_from_row).map_to_mm(SqlError::from) + self.call(move |conn| { + let sql = format!("SELECT * FROM {} WHERE token_address=?1 AND token_id=?2", table_name); + let params = [token_address, token_id.to_string()]; + let nft = query_single_row(conn, &sql, params, nft_from_row)?; + Ok(nft) }) .await + .map_to_mm(AsyncConnError::from) } async fn remove_nft_from_list( &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, scanned_block: u64, ) -> MmResult { let table_name = chain.nft_list_table_name()?; let sql = delete_nft_sql(table_name)?; let params = [token_address, token_id.to_string()]; - let scanned_block_params = [chain.to_ticker(), scanned_block.to_string()]; - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + let scanned_block_params = [chain.to_ticker().to_string(), scanned_block.to_string()]; + self.call(move |conn| { let sql_transaction = conn.transaction()?; let rows_num = sql_transaction.execute(&sql, params)?; @@ -719,13 +694,14 @@ impl NftListStorageOps for SqliteNftStorage { Ok(remove_nft_result) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_nft_amount( &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error> { let table_name = chain.nft_list_table_name()?; let sql = format!( @@ -733,19 +709,17 @@ impl NftListStorageOps for SqliteNftStorage { table_name ); let params = [token_address, token_id.to_string()]; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, params, nft_amount_from_row).map_to_mm(SqlError::from) + self.call(move |conn| { + let amount = query_single_row(conn, &sql, params, nft_amount_from_row)?; + Ok(amount) }) .await + .map_to_mm(AsyncConnError::from) } async fn refresh_nft_metadata(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error> { let sql = refresh_nft_metadata_sql(chain)?; - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [ Some(i32::from(nft.common.possible_spam).to_string()), @@ -769,41 +743,40 @@ impl NftListStorageOps for SqliteNftStorage { nft.uri_meta.external_domain, nft.uri_meta.image_details.map(|v| v.to_string()), Some(eth_addr_to_hex(&nft.common.token_address)), - Some(nft.common.token_id.to_string()), + Some(nft.token_id.to_string()), ]; sql_transaction.execute(&sql, params)?; sql_transaction.commit()?; Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let table_name = chain.nft_list_table_name()?; let sql = select_last_block_number_sql(table_name)?; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, [], block_number_from_row).map_to_mm(SqlError::from) + self.call(move |conn| { + let block_number = query_single_row(conn, &sql, [], block_number_from_row)?; + Ok(block_number) }) .await? .map(|b| b.try_into()) .transpose() - .map_to_mm(|e| SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e))) + .map_to_mm(|e| AsyncConnError::Rusqlite(SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e)))) } async fn get_last_scanned_block(&self, chain: &Chain) -> MmResult, Self::Error> { let sql = select_last_scanned_block_sql()?; let params = [chain.to_ticker()]; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, params, block_number_from_row).map_to_mm(SqlError::from) + self.call(move |conn| { + let block_number = query_single_row(conn, &sql, params, block_number_from_row)?; + Ok(block_number) }) .await? .map(|b| b.try_into()) .transpose() - .map_to_mm(|e| SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e))) + .map_to_mm(|e| AsyncConnError::Rusqlite(SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e)))) } async fn update_nft_amount(&self, chain: &Chain, nft: Nft, scanned_block: u64) -> MmResult<(), Self::Error> { @@ -812,15 +785,13 @@ impl NftListStorageOps for SqliteNftStorage { "UPDATE {} SET amount = ?1 WHERE token_address = ?2 AND token_id = ?3;", table_name ); - let scanned_block_params = [chain.to_ticker(), scanned_block.to_string()]; - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + let scanned_block_params = [chain.to_ticker().to_string(), scanned_block.to_string()]; + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [ Some(nft.common.amount.to_string()), Some(eth_addr_to_hex(&nft.common.token_address)), - Some(nft.common.token_id.to_string()), + Some(nft.token_id.to_string()), ]; sql_transaction.execute(&sql, params)?; sql_transaction.execute(&upsert_last_scanned_block_sql()?, scanned_block_params)?; @@ -828,6 +799,7 @@ impl NftListStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn update_nft_amount_and_block_number(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error> { @@ -836,16 +808,14 @@ impl NftListStorageOps for SqliteNftStorage { "UPDATE {} SET amount = ?1, block_number = ?2 WHERE token_address = ?3 AND token_id = ?4;", table_name ); - let scanned_block_params = [chain.to_ticker(), nft.block_number.to_string()]; - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + let scanned_block_params = [chain.to_ticker().to_string(), nft.block_number.to_string()]; + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [ Some(nft.common.amount.to_string()), Some(nft.block_number.to_string()), Some(eth_addr_to_hex(&nft.common.token_address)), - Some(nft.common.token_id.to_string()), + Some(nft.token_id.to_string()), ]; sql_transaction.execute(&sql, params)?; sql_transaction.execute(&upsert_last_scanned_block_sql()?, scanned_block_params)?; @@ -853,20 +823,20 @@ impl NftListStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_nfts_by_token_address(&self, chain: Chain, token_address: String) -> MmResult, Self::Error> { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let table_name = chain.nft_list_table_name()?; - let mut stmt = get_nfts_by_token_address_statement(&conn, table_name)?; + let mut stmt = get_nfts_by_token_address_statement(conn, table_name)?; let nfts = stmt .query_map([token_address], nft_from_row)? .collect::, _>>()?; Ok(nfts) }) .await + .map_to_mm(AsyncConnError::from) } async fn update_nft_spam_by_token_address( @@ -875,11 +845,9 @@ impl NftListStorageOps for SqliteNftStorage { token_address: String, possible_spam: bool, ) -> MmResult<(), Self::Error> { - let selfi = self.clone(); let table_name = chain.nft_list_table_name()?; let sql = format!("UPDATE {} SET possible_spam = ?1 WHERE token_address = ?2;", table_name); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [Some(i32::from(possible_spam).to_string()), Some(token_address.clone())]; sql_transaction.execute(&sql, params)?; @@ -887,17 +855,14 @@ impl NftListStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_animation_external_domains(&self, chain: &Chain) -> MmResult, Self::Error> { - let selfi = self.clone(); let table_name = chain.nft_list_table_name()?; - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_query = format!( - "SELECT DISTINCT animation_domain FROM {} - UNION - SELECT DISTINCT external_domain FROM {}", + "SELECT DISTINCT animation_domain FROM {} UNION SELECT DISTINCT external_domain FROM {}", table_name, table_name ); let mut stmt = conn.prepare(&sql_query)?; @@ -908,6 +873,7 @@ impl NftListStorageOps for SqliteNftStorage { Ok(domains) }) .await + .map_to_mm(AsyncConnError::from) } async fn update_nft_phishing_by_domain( @@ -916,17 +882,13 @@ impl NftListStorageOps for SqliteNftStorage { domain: String, possible_phishing: bool, ) -> MmResult<(), Self::Error> { - let selfi = self.clone(); - let table_name = chain.nft_list_table_name()?; let sql = format!( "UPDATE {} SET possible_phishing = ?1 WHERE token_domain = ?2 OR image_domain = ?2 OR animation_domain = ?2 OR external_domain = ?2;", table_name ); - - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [Some(i32::from(possible_phishing).to_string()), Some(domain)]; sql_transaction.execute(&sql, params)?; @@ -934,33 +896,32 @@ impl NftListStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } } #[async_trait] -impl NftTransferHistoryStorageOps for SqliteNftStorage { - type Error = SqlError; +impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { + type Error = AsyncConnError; async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { - let selfi = self.clone(); let sql_transfer_history = create_transfer_history_table_sql(chain)?; - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { conn.execute(&sql_transfer_history, []).map(|_| ())?; Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn is_initialized(&self, chain: &Chain) -> MmResult { let table_name = chain.transfer_history_table_name()?; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - let nft_list_initialized = query_single_row(&conn, CHECK_TABLE_EXISTS_SQL, [table_name], string_from_row)?; + self.call(move |conn| { + let nft_list_initialized = query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [table_name], string_from_row)?; Ok(nft_list_initialized.is_some()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_transfer_history( @@ -971,9 +932,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { page_number: Option, filters: Option, ) -> MmResult { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_builder = get_nft_transfer_builder_preimage(chains, filters)?; let total_count_builder_sql = sql_builder .clone() @@ -999,6 +958,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Ok(result) }) .await + .map_to_mm(AsyncConnError::from) } async fn add_transfers_to_history(&self, chain: Chain, transfers: I) -> MmResult<(), Self::Error> @@ -1006,11 +966,8 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { I: IntoIterator + Send + 'static, I::IntoIter: Send, { - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; - for transfer in transfers { let details_json = TransferDetailsJson { block_hash: transfer.common.block_hash, @@ -1021,6 +978,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { operator: transfer.common.operator, from_address: transfer.common.from_address, to_address: transfer.common.from_address, + fee_details: transfer.fee_details, }; let transfer_json = json::to_string(&details_json).expect("serialization should not fail"); let params = [ @@ -1031,7 +989,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Some(transfer.block_timestamp.to_string()), Some(transfer.contract_type.to_string()), Some(eth_addr_to_hex(&transfer.common.token_address)), - Some(transfer.common.token_id.to_string()), + Some(transfer.token_id.to_string()), Some(transfer.status.to_string()), Some(transfer.common.amount.to_string()), transfer.token_uri, @@ -1050,20 +1008,20 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let table_name = chain.transfer_history_table_name()?; let sql = select_last_block_number_sql(table_name)?; - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, [], block_number_from_row).map_to_mm(SqlError::from) + self.call(move |conn| { + let block_number = query_single_row(conn, &sql, [], block_number_from_row)?; + Ok(block_number) }) .await? .map(|b| b.try_into()) .transpose() - .map_to_mm(|e| SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e))) + .map_to_mm(|e| AsyncConnError::Rusqlite(SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e)))) } async fn get_transfers_from_block( @@ -1071,34 +1029,32 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { chain: Chain, from_block: u64, ) -> MmResult, Self::Error> { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - let mut stmt = get_transfers_from_block_statement(&conn, &chain)?; + self.call(move |conn| { + let mut stmt = get_transfers_from_block_statement(conn, &chain)?; let transfers = stmt .query_map([from_block], transfer_history_from_row)? .collect::, _>>()?; Ok(transfers) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_transfers_by_token_addr_id( &self, chain: Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error> { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - let mut stmt = get_transfers_by_token_addr_id_statement(&conn, chain)?; + self.call(move |conn| { + let mut stmt = get_transfers_by_token_addr_id_statement(conn, chain)?; let transfers = stmt .query_map([token_address, token_id.to_string()], transfer_history_from_row)? .collect::, _>>()?; Ok(transfers) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_transfer_by_tx_hash_and_log_index( @@ -1112,18 +1068,17 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { "SELECT * FROM {} WHERE transaction_hash=?1 AND log_index = ?2", table_name ); - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - query_single_row( - &conn, + self.call(move |conn| { + let transfer = query_single_row( + conn, &sql, [transaction_hash, log_index.to_string()], transfer_history_from_row, - ) - .map_to_mm(SqlError::from) + )?; + Ok(transfer) }) .await + .map_to_mm(AsyncConnError::from) } async fn update_transfers_meta_by_token_addr_id( @@ -1149,9 +1104,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Some(transfer_meta.token_address), Some(transfer_meta.token_id.to_string()), ]; - let selfi = self.clone(); - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; sql_transaction.execute(&sql, params)?; if set_spam { @@ -1161,17 +1114,17 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_transfers_with_empty_meta(&self, chain: Chain) -> MmResult, Self::Error> { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); - let sql_builder = get_transfers_with_empty_meta_builder(&conn, &chain)?; + self.call(move |conn| { + let sql_builder = get_transfers_with_empty_meta_builder(conn, &chain)?; let token_addr_id_pair = sql_builder.query(token_address_id_from_row)?; Ok(token_addr_id_pair) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_transfers_by_token_address( @@ -1179,17 +1132,16 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { chain: Chain, token_address: String, ) -> MmResult, Self::Error> { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let table_name = chain.transfer_history_table_name()?; - let mut stmt = get_nfts_by_token_address_statement(&conn, table_name)?; - let nfts = stmt + let mut stmt = get_nfts_by_token_address_statement(conn, table_name)?; + let transfers = stmt .query_map([token_address], transfer_history_from_row)? .collect::, _>>()?; - Ok(nfts) + Ok(transfers) }) .await + .map_to_mm(AsyncConnError::from) } async fn update_transfer_spam_by_token_address( @@ -1198,13 +1150,9 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { token_address: String, possible_spam: bool, ) -> MmResult<(), Self::Error> { - let selfi = self.clone(); - let table_name = chain.transfer_history_table_name()?; let sql = format!("UPDATE {} SET possible_spam = ?1 WHERE token_address = ?2;", table_name); - - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [Some(i32::from(possible_spam).to_string()), Some(token_address.clone())]; sql_transaction.execute(&sql, params)?; @@ -1212,31 +1160,27 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_token_addresses(&self, chain: Chain) -> MmResult, Self::Error> { - let selfi = self.clone(); - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let table_name = chain.transfer_history_table_name()?; - let mut stmt = get_token_addresses_statement(&conn, table_name)?; + let mut stmt = get_token_addresses_statement(conn, table_name)?; let addresses = stmt .query_map([], address_from_row)? .collect::, _>>()?; Ok(addresses) }) .await + .map_to_mm(AsyncConnError::from) } async fn get_domains(&self, chain: &Chain) -> MmResult, Self::Error> { - let selfi = self.clone(); let table_name = chain.transfer_history_table_name()?; - async_blocking(move || { - let conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_query = format!( - "SELECT DISTINCT token_domain FROM {} - UNION - SELECT DISTINCT image_domain FROM {}", + "SELECT DISTINCT token_domain FROM {} UNION SELECT DISTINCT image_domain FROM {}", table_name, table_name ); let mut stmt = conn.prepare(&sql_query)?; @@ -1247,6 +1191,7 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Ok(domains) }) .await + .map_to_mm(AsyncConnError::from) } async fn update_transfer_phishing_by_domain( @@ -1255,16 +1200,12 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { domain: String, possible_phishing: bool, ) -> MmResult<(), Self::Error> { - let selfi = self.clone(); - let table_name = chain.transfer_history_table_name()?; let sql = format!( "UPDATE {} SET possible_phishing = ?1 WHERE token_domain = ?2 OR image_domain = ?2;", table_name ); - - async_blocking(move || { - let mut conn = selfi.0.lock().unwrap(); + self.call(move |conn| { let sql_transaction = conn.transaction()?; let params = [Some(i32::from(possible_phishing).to_string()), Some(domain)]; sql_transaction.execute(&sql, params)?; @@ -1272,5 +1213,6 @@ impl NftTransferHistoryStorageOps for SqliteNftStorage { Ok(()) }) .await + .map_to_mm(AsyncConnError::from) } } diff --git a/mm2src/coins/nft/storage/wasm/mod.rs b/mm2src/coins/nft/storage/wasm/mod.rs index 6bbc8738c4..ab8f69af68 100644 --- a/mm2src/coins/nft/storage/wasm/mod.rs +++ b/mm2src/coins/nft/storage/wasm/mod.rs @@ -1,7 +1,6 @@ use crate::nft::storage::NftStorageError; use mm2_db::indexed_db::{DbTransactionError, InitDbError}; use mm2_err_handle::prelude::*; -use mm2_number::bigdecimal::ParseBigDecimalError; pub(crate) mod nft_idb; pub(crate) mod wasm_storage; @@ -20,7 +19,6 @@ pub enum WasmNftCacheError { NotSupported(String), InternalError(String), GetLastNftBlockError(String), - ParseBigDecimalError(ParseBigDecimalError), } impl From for WasmNftCacheError { diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index 789ec069da..faf79f663a 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -1,24 +1,21 @@ use crate::eth::eth_addr_to_hex; -use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCtx, NftList, NftListFilters, NftTransferHistory, +use crate::nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListFilters, NftTransferHistory, NftsTransferHistoryList, TransferMeta, TransferStatus}; -use crate::nft::storage::wasm::nft_idb::{NftCacheIDB, NftCacheIDBLocked}; +use crate::nft::storage::wasm::nft_idb::NftCacheIDBLocked; use crate::nft::storage::wasm::{WasmNftCacheError, WasmNftCacheResult}; -use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftTokenAddrId, - NftTransferHistoryFilters, NftTransferHistoryStorageOps, RemoveNftResult}; +use crate::nft::storage::{get_offset_limit, NftListStorageOps, NftTokenAddrId, NftTransferHistoryFilters, + NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; use common::is_initial_upgrade; use ethereum_types::Address; -use mm2_core::mm_ctx::MmArc; -use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeResult, SharedDb, TableSignature}; -use mm2_err_handle::map_mm_error::MapMmError; +use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeResult, TableSignature}; use mm2_err_handle::map_to_mm::MapToMmResult; use mm2_err_handle::prelude::MmResult; -use mm2_number::BigDecimal; +use mm2_number::BigUint; use num_traits::ToPrimitive; use serde_json::{self as json, Value as Json}; use std::collections::HashSet; use std::num::NonZeroUsize; -use std::str::FromStr; const CHAIN_TOKEN_ADD_TOKEN_ID_INDEX: &str = "chain_token_add_token_id_index"; const CHAIN_BLOCK_NUMBER_INDEX: &str = "chain_block_number_index"; @@ -26,94 +23,69 @@ const CHAIN_TOKEN_ADD_INDEX: &str = "chain_token_add_index"; const CHAIN_TOKEN_DOMAIN_INDEX: &str = "chain_token_domain_index"; const CHAIN_IMAGE_DOMAIN_INDEX: &str = "chain_image_domain_index"; -/// Provides methods for interacting with the IndexedDB storage specifically designed for NFT data. -/// -/// This struct abstracts the intricacies of fetching and storing NFT data in the IndexedDB, -/// ensuring optimal performance and data integrity. -#[derive(Clone)] -pub struct IndexedDbNftStorage { - /// The underlying shared database instance for caching NFT data. - db: SharedDb, +fn take_nft_according_to_paging_opts( + mut nfts: Vec, + max: bool, + limit: usize, + page_number: Option, +) -> WasmNftCacheResult { + let total_count = nfts.len(); + nfts.sort_by(|a, b| b.block_number.cmp(&a.block_number)); + let (offset, limit) = get_offset_limit(max, limit, page_number, total_count); + Ok(NftList { + nfts: nfts.into_iter().skip(offset).take(limit).collect(), + skipped: offset, + total: total_count, + }) } -impl IndexedDbNftStorage { - /// Construct a new `IndexedDbNftStorage` using the given MM context. - /// - /// This method ensures that a proper NFT context (`NftCtx`) exists within the MM context - /// and initializes the underlying storage as required. - pub fn new(ctx: &MmArc) -> MmResult { - let nft_ctx = NftCtx::from_ctx(ctx).map_to_mm(CreateNftStorageError::Internal)?; - Ok(IndexedDbNftStorage { - db: nft_ctx.nft_cache_db.clone(), - }) - } - - /// Lock the underlying database to ensure exclusive access, maintaining data consistency during operations. - async fn lock_db(&self) -> WasmNftCacheResult> { - self.db.get_or_initialize().await.mm_err(WasmNftCacheError::from) - } - - fn take_nft_according_to_paging_opts( - mut nfts: Vec, - max: bool, - limit: usize, - page_number: Option, - ) -> WasmNftCacheResult { - let total_count = nfts.len(); - nfts.sort_by(|a, b| b.block_number.cmp(&a.block_number)); - let (offset, limit) = get_offset_limit(max, limit, page_number, total_count); - Ok(NftList { - nfts: nfts.into_iter().skip(offset).take(limit).collect(), - skipped: offset, - total: total_count, - }) - } - - fn filter_nfts(nfts: I, filters: Option) -> WasmNftCacheResult> - where - I: Iterator, - { - let mut filtered_nfts = Vec::new(); - for nft_table in nfts { - let nft = nft_details_from_item(nft_table)?; - if let Some(filters) = &filters { +fn filter_nfts(nfts: I, filters: Option) -> WasmNftCacheResult> +where + I: Iterator, +{ + let mut filtered_nfts = Vec::new(); + for nft_table in nfts { + let nft = nft_details_from_item(nft_table)?; + match filters { + Some(filters) => { if filters.passes_spam_filter(&nft) && filters.passes_phishing_filter(&nft) { filtered_nfts.push(nft); } - } else { - filtered_nfts.push(nft); - } + }, + None => filtered_nfts.push(nft), } - Ok(filtered_nfts) } + Ok(filtered_nfts) +} - fn take_transfers_according_to_paging_opts( - mut transfers: Vec, - max: bool, - limit: usize, - page_number: Option, - ) -> WasmNftCacheResult { - let total_count = transfers.len(); - transfers.sort_by(|a, b| b.block_timestamp.cmp(&a.block_timestamp)); - let (offset, limit) = get_offset_limit(max, limit, page_number, total_count); - Ok(NftsTransferHistoryList { - transfer_history: transfers.into_iter().skip(offset).take(limit).collect(), - skipped: offset, - total: total_count, - }) - } +fn take_transfers_according_to_paging_opts( + mut transfers: Vec, + max: bool, + limit: usize, + page_number: Option, +) -> WasmNftCacheResult { + let total_count = transfers.len(); + transfers.sort_by(|a, b| b.block_timestamp.cmp(&a.block_timestamp)); + let (offset, limit) = get_offset_limit(max, limit, page_number, total_count); + Ok(NftsTransferHistoryList { + transfer_history: transfers.into_iter().skip(offset).take(limit).collect(), + skipped: offset, + total: total_count, + }) +} - fn filter_transfers( - transfers: I, - filters: Option, - ) -> WasmNftCacheResult> - where - I: Iterator, - { - let mut filtered_transfers = Vec::new(); - for transfers_table in transfers { - let transfer = transfer_details_from_item(transfers_table)?; - if let Some(filters) = &filters { +fn filter_transfers( + transfers: I, + filters: Option, +) -> WasmNftCacheResult> +where + I: Iterator, +{ + let mut filtered_transfers = Vec::new(); + for transfers_table in transfers { + let transfer = transfer_details_from_item(transfers_table)?; + match filters { + Some(filters) => { if filters.is_status_match(&transfer) && filters.is_date_match(&transfer) && filters.passes_spam_filter(&transfer) @@ -121,12 +93,11 @@ impl IndexedDbNftStorage { { filtered_transfers.push(transfer); } - } else { - filtered_transfers.push(transfer); - } + }, + None => filtered_transfers.push(transfer), } - Ok(filtered_transfers) } + Ok(filtered_transfers) } impl NftListFilters { @@ -157,7 +128,7 @@ impl NftTransferHistoryFilters { } #[async_trait] -impl NftListStorageOps for IndexedDbNftStorage { +impl NftListStorageOps for NftCacheIDBLocked<'_> { type Error = WasmNftCacheError; async fn init(&self, _chain: &Chain) -> MmResult<(), Self::Error> { Ok(()) } @@ -172,8 +143,7 @@ impl NftListStorageOps for IndexedDbNftStorage { page_number: Option, filters: Option, ) -> MmResult { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let mut nfts = Vec::new(); for chain in chains { @@ -182,10 +152,10 @@ impl NftListStorageOps for IndexedDbNftStorage { .await? .into_iter() .map(|(_item_id, nft)| nft); - let filtered = Self::filter_nfts(nft_tables, filters)?; + let filtered = filter_nfts(nft_tables, filters)?; nfts.extend(filtered); } - Self::take_nft_according_to_paging_opts(nfts, max, limit, page_number) + take_nft_according_to_paging_opts(nfts, max, limit, page_number) } async fn add_nfts_to_list(&self, chain: Chain, nfts: I, last_scanned_block: u64) -> MmResult<(), Self::Error> @@ -193,8 +163,7 @@ impl NftListStorageOps for IndexedDbNftStorage { I: IntoIterator + Send + 'static, I::IntoIter: Send, { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; for nft in nfts { @@ -215,15 +184,14 @@ impl NftListStorageOps for IndexedDbNftStorage { &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? - .with_value(token_id.to_string())?; + .with_value(BeBigUint::from(token_id))?; if let Some((_item_id, item)) = table.get_item_by_unique_multi_index(index_keys).await? { Ok(Some(nft_details_from_item(item)?)) @@ -236,18 +204,17 @@ impl NftListStorageOps for IndexedDbNftStorage { &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, scanned_block: u64, ) -> MmResult { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? - .with_value(token_id.to_string())?; + .with_value(BeBigUint::from(token_id))?; let last_scanned_block = LastScannedBlockTable { chain: chain.to_string(), @@ -269,15 +236,14 @@ impl NftListStorageOps for IndexedDbNftStorage { &self, chain: &Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? - .with_value(token_id.to_string())?; + .with_value(BeBigUint::from(token_id))?; if let Some((_item_id, item)) = table.get_item_by_unique_multi_index(index_keys).await? { Ok(Some(nft_details_from_item(item)?.common.amount.to_string())) @@ -287,13 +253,12 @@ impl NftListStorageOps for IndexedDbNftStorage { } async fn refresh_nft_metadata(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(eth_addr_to_hex(&nft.common.token_address))? - .with_value(nft.common.token_id.to_string())?; + .with_value(BeBigUint::from(nft.token_id.clone()))?; let nft_item = NftListTable::from_nft(&nft)?; table.replace_item_by_unique_multi_index(index_keys, &nft_item).await?; @@ -301,15 +266,13 @@ impl NftListStorageOps for IndexedDbNftStorage { } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; get_last_block_from_table(chain, table, CHAIN_BLOCK_NUMBER_INDEX).await } async fn get_last_scanned_block(&self, chain: &Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; if let Some((_item_id, item)) = table.get_item_by_unique_index("chain", chain.to_string()).await? { let last_scanned_block = item @@ -323,15 +286,14 @@ impl NftListStorageOps for IndexedDbNftStorage { } async fn update_nft_amount(&self, chain: &Chain, nft: Nft, scanned_block: u64) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(eth_addr_to_hex(&nft.common.token_address))? - .with_value(nft.common.token_id.to_string())?; + .with_value(BeBigUint::from(nft.token_id.clone()))?; let nft_item = NftListTable::from_nft(&nft)?; nft_table @@ -348,15 +310,14 @@ impl NftListStorageOps for IndexedDbNftStorage { } async fn update_nft_amount_and_block_number(&self, chain: &Chain, nft: Nft) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let nft_table = db_transaction.table::().await?; let last_scanned_block_table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(eth_addr_to_hex(&nft.common.token_address))? - .with_value(nft.common.token_id.to_string())?; + .with_value(BeBigUint::from(nft.token_id.clone()))?; let nft_item = NftListTable::from_nft(&nft)?; nft_table @@ -373,8 +334,7 @@ impl NftListStorageOps for IndexedDbNftStorage { } async fn get_nfts_by_token_address(&self, chain: Chain, token_address: String) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_INDEX) @@ -395,8 +355,7 @@ impl NftListStorageOps for IndexedDbNftStorage { token_address: String, possible_spam: bool, ) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let chain_str = chain.to_string(); @@ -419,7 +378,7 @@ impl NftListStorageOps for IndexedDbNftStorage { let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(&chain_str)? .with_value(eth_addr_to_hex(&nft.common.token_address))? - .with_value(nft.common.token_id.to_string())?; + .with_value(BeBigUint::from(nft.token_id.clone()))?; let item = NftListTable::from_nft(&nft)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; @@ -428,8 +387,7 @@ impl NftListStorageOps for IndexedDbNftStorage { } async fn get_animation_external_domains(&self, chain: &Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let mut domains = HashSet::new(); @@ -451,8 +409,7 @@ impl NftListStorageOps for IndexedDbNftStorage { domain: String, possible_phishing: bool, ) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let chain_str = chain.to_string(); @@ -467,7 +424,7 @@ impl NftListStorageOps for IndexedDbNftStorage { } #[async_trait] -impl NftTransferHistoryStorageOps for IndexedDbNftStorage { +impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { type Error = WasmNftCacheError; async fn init(&self, _chain: &Chain) -> MmResult<(), Self::Error> { Ok(()) } @@ -482,8 +439,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { page_number: Option, filters: Option, ) -> MmResult { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let mut transfers = Vec::new(); for chain in chains { @@ -492,10 +448,10 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { .await? .into_iter() .map(|(_item_id, transfer)| transfer); - let filtered = Self::filter_transfers(transfer_tables, filters)?; + let filtered = filter_transfers(transfer_tables, filters)?; transfers.extend(filtered); } - Self::take_transfers_according_to_paging_opts(transfers, max, limit, page_number) + take_transfers_according_to_paging_opts(transfers, max, limit, page_number) } async fn add_transfers_to_history(&self, _chain: Chain, transfers: I) -> MmResult<(), Self::Error> @@ -503,8 +459,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { I: IntoIterator + Send + 'static, I::IntoIter: Send, { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; for transfer in transfers { let transfer_item = NftTransferHistoryTable::from_transfer_history(&transfer)?; @@ -514,8 +469,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; get_last_block_from_table(chain, table, CHAIN_BLOCK_NUMBER_INDEX).await } @@ -525,8 +479,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { chain: Chain, from_block: u64, ) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let items = table .cursor_builder() @@ -552,16 +505,15 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { &self, chain: Chain, token_address: String, - token_id: BigDecimal, + token_id: BigUint, ) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? - .with_value(token_id.to_string())?; + .with_value(BeBigUint::from(token_id))?; table .get_items_by_multi_index(index_keys) @@ -577,8 +529,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { transaction_hash: String, log_index: u32, ) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? @@ -598,15 +549,14 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { transfer_meta: TransferMeta, set_spam: bool, ) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let chain_str = chain.to_string(); let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(&chain_str)? .with_value(&transfer_meta.token_address)? - .with_value(transfer_meta.token_id.to_string())?; + .with_value(BeBigUint::from(transfer_meta.token_id))?; let transfers: Result, _> = table .get_items_by_multi_index(index_keys) @@ -640,8 +590,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { } async fn get_transfers_with_empty_meta(&self, chain: Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let items = table .cursor_builder() @@ -660,10 +609,11 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { && item.collection_name.is_none() && item.image_url.is_none() && item.token_name.is_none() + && !item.possible_spam { res.insert(NftTokenAddrId { token_address: item.token_address, - token_id: BigDecimal::from_str(&item.token_id).map_err(WasmNftCacheError::ParseBigDecimalError)?, + token_id: BigUint::from(item.token_id), }); } } @@ -675,8 +625,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { chain: Chain, token_address: String, ) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_INDEX) @@ -697,8 +646,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { token_address: String, possible_spam: bool, ) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let chain_str = chain.to_string(); @@ -730,8 +678,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { } async fn get_token_addresses(&self, chain: Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let items = table.get_items("chain", chain.to_string()).await?; @@ -744,8 +691,7 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { } async fn get_domains(&self, chain: &Chain) -> MmResult, Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; let mut domains = HashSet::new(); @@ -767,10 +713,8 @@ impl NftTransferHistoryStorageOps for IndexedDbNftStorage { domain: String, possible_phishing: bool, ) -> MmResult<(), Self::Error> { - let locked_db = self.lock_db().await?; - let db_transaction = locked_db.get_inner().transaction().await?; + let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let chain_str = chain.to_string(); update_transfer_phishing_for_index(&table, &chain_str, CHAIN_TOKEN_DOMAIN_INDEX, &domain, possible_phishing) .await?; @@ -822,7 +766,7 @@ async fn update_nft_phishing_for_index( let index_keys = MultiIndex::new(CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain)? .with_value(eth_addr_to_hex(&nft.common.token_address))? - .with_value(nft.common.token_id.to_string())?; + .with_value(BeBigUint::from(nft.token_id))?; table.replace_item_by_unique_multi_index(index_keys, &nft_item).await?; } Ok(()) @@ -877,7 +821,7 @@ impl BlockNumberTable for NftTransferHistoryTable { #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct NftListTable { token_address: String, - token_id: String, + token_id: BeBigUint, chain: String, amount: String, block_number: BeBigUint, @@ -899,7 +843,7 @@ impl NftListTable { let details_json = json::to_value(nft).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; Ok(NftListTable { token_address: eth_addr_to_hex(&nft.common.token_address), - token_id: nft.common.token_id.to_string(), + token_id: BeBigUint::from(nft.token_id.clone()), chain: nft.chain.to_string(), amount: nft.common.amount.to_string(), block_number: BeBigUint::from(nft.block_number), @@ -952,7 +896,7 @@ pub(crate) struct NftTransferHistoryTable { block_timestamp: BeBigUint, contract_type: ContractType, token_address: String, - token_id: String, + token_id: BeBigUint, status: TransferStatus, amount: String, token_uri: Option, @@ -980,7 +924,7 @@ impl NftTransferHistoryTable { block_timestamp: BeBigUint::from(transfer.block_timestamp), contract_type: transfer.contract_type, token_address: eth_addr_to_hex(&transfer.common.token_address), - token_id: transfer.common.token_id.to_string(), + token_id: BeBigUint::from(transfer.token_id.clone()), status: transfer.status, amount: transfer.common.amount.to_string(), token_uri: transfer.token_uri.clone(), diff --git a/mm2src/db_common/Cargo.toml b/mm2src/db_common/Cargo.toml index 7a469bca71..e161be857e 100644 --- a/mm2src/db_common/Cargo.toml +++ b/mm2src/db_common/Cargo.toml @@ -13,5 +13,8 @@ log = "0.4.17" uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] +crossbeam-channel = "0.5.1" +futures = "0.3.1" rusqlite = { version = "0.28", features = ["bundled"] } sql-builder = "3.1.1" +tokio = { version = "1.20", default-features = false, features = ["macros"] } diff --git a/mm2src/db_common/src/async_conn_tests.rs b/mm2src/db_common/src/async_conn_tests.rs new file mode 100644 index 0000000000..4002b4ac3c --- /dev/null +++ b/mm2src/db_common/src/async_conn_tests.rs @@ -0,0 +1,253 @@ +use crate::async_sql_conn::{AsyncConnError, AsyncConnection, InternalError, Result as AsyncConnResult}; +use rusqlite::{ffi, ErrorCode}; +use std::fmt::Display; + +#[tokio::test] +async fn open_in_memory_test() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await; + assert!(conn.is_ok()); + Ok(()) +} + +#[tokio::test] +async fn call_success_test() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + let result = conn + .call(|conn| { + conn.execute( + "CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);", + [], + ) + .map_err(|e| e.into()) + }) + .await; + + assert_eq!(0, result.unwrap()); + + Ok(()) +} + +#[tokio::test] +async fn call_unwrap_success_test() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + let result = conn + .call_unwrap(|conn| { + conn.execute( + "CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);", + [], + ) + .unwrap() + }) + .await; + + assert_eq!(0, result); + + Ok(()) +} + +#[tokio::test] +async fn call_failure_test() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + let result = conn + .call(|conn| conn.execute("Invalid sql", []).map_err(|e| e.into())) + .await; + + assert!(match result.unwrap_err() { + AsyncConnError::Rusqlite(e) => { + e == rusqlite::Error::SqlInputError { + error: ffi::Error { + code: ErrorCode::Unknown, + extended_code: 1, + }, + msg: "near \"Invalid\": syntax error".to_string(), + sql: "Invalid sql".to_string(), + offset: 0, + } + }, + _ => false, + }); + + Ok(()) +} + +#[tokio::test] +async fn close_success_test() -> AsyncConnResult<()> { + let mut conn = AsyncConnection::open_in_memory().await?; + + assert!(conn.close().await.is_ok()); + + Ok(()) +} + +#[tokio::test] +async fn double_close_test() -> AsyncConnResult<()> { + let mut conn = AsyncConnection::open_in_memory().await?; + + let mut conn2 = conn.clone(); + + assert!(conn.close().await.is_ok()); + assert!(conn2.close().await.is_ok()); + + Ok(()) +} + +#[tokio::test] +async fn close_call_test() -> AsyncConnResult<()> { + let mut conn = AsyncConnection::open_in_memory().await?; + + let conn2 = conn.clone(); + + assert!(conn.close().await.is_ok()); + + let result = conn2 + .call(|conn| conn.execute("SELECT 1;", []).map_err(|e| e.into())) + .await; + + assert!(matches!(result.unwrap_err(), AsyncConnError::ConnectionClosed)); + + Ok(()) +} + +#[tokio::test] +#[should_panic] +async fn close_call_unwrap_test() { + let mut conn = AsyncConnection::open_in_memory().await.unwrap(); + + let conn2 = conn.clone(); + + assert!(conn.close().await.is_ok()); + + conn2.call_unwrap(|conn| conn.execute("SELECT 1;", [])).await.unwrap(); +} + +#[tokio::test] +async fn close_failure_test() -> AsyncConnResult<()> { + let mut conn = AsyncConnection::open_in_memory().await?; + + conn.call(|conn| { + conn.execute( + "CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);", + [], + ) + .map_err(|e| e.into()) + }) + .await?; + + conn.call(|conn| { + // Leak a prepared statement to make the database uncloseable + // See https://www.sqlite.org/c3ref/close.html for details regarding this behaviour + let stmt = Box::new(conn.prepare("INSERT INTO person VALUES (1, ?1);").unwrap()); + Box::leak(stmt); + Ok(()) + }) + .await?; + + assert!(match conn.close().await.unwrap_err() { + AsyncConnError::Close((_, e)) => { + e == rusqlite::Error::SqliteFailure( + ffi::Error { + code: ErrorCode::DatabaseBusy, + extended_code: 5, + }, + Some("unable to close due to unfinalized statements or unfinished backups".to_string()), + ) + }, + _ => false, + }); + + Ok(()) +} + +#[tokio::test] +async fn debug_format_test() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + assert_eq!("AsyncConnection".to_string(), format!("{conn:?}")); + + Ok(()) +} + +#[tokio::test] +async fn test_error_display() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + let error = AsyncConnError::Close((conn, rusqlite::Error::InvalidQuery)); + assert_eq!( + "Close((AsyncConnection, \"Query is not read-only\"))", + format!("{error}") + ); + + let error = AsyncConnError::ConnectionClosed; + assert_eq!("ConnectionClosed", format!("{error}")); + + let error = AsyncConnError::Rusqlite(rusqlite::Error::InvalidQuery); + assert_eq!("Rusqlite(\"Query is not read-only\")", format!("{error}")); + + Ok(()) +} + +#[tokio::test] +async fn test_error_source() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + let error = AsyncConnError::Close((conn, rusqlite::Error::InvalidQuery)); + assert_eq!( + std::error::Error::source(&error) + .and_then(|e| e.downcast_ref::()) + .unwrap(), + &rusqlite::Error::InvalidQuery, + ); + + let error = AsyncConnError::ConnectionClosed; + assert_eq!( + std::error::Error::source(&error).and_then(|e| e.downcast_ref::()), + None, + ); + + let error = AsyncConnError::Rusqlite(rusqlite::Error::InvalidQuery); + assert_eq!( + std::error::Error::source(&error) + .and_then(|e| e.downcast_ref::()) + .unwrap(), + &rusqlite::Error::InvalidQuery, + ); + + Ok(()) +} + +fn failable_func(_: &rusqlite::Connection) -> std::result::Result<(), MyError> { Err(MyError::MySpecificError) } + +#[tokio::test] +async fn test_ergonomic_errors() -> AsyncConnResult<()> { + let conn = AsyncConnection::open_in_memory().await?; + + let res = conn + .call(|conn| failable_func(conn).map_err(|e| AsyncConnError::Internal(InternalError(e.to_string())))) + .await + .unwrap_err(); + + let err = std::error::Error::source(&res) + .and_then(|e| e.downcast_ref::()) + .unwrap() + .to_string(); + + assert_eq!(err, MyError::MySpecificError.to_string()); + + Ok(()) +} + +#[derive(Debug)] +enum MyError { + MySpecificError, +} + +impl Display for MyError { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) } +} + +impl std::error::Error for MyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} diff --git a/mm2src/db_common/src/async_sql_conn.rs b/mm2src/db_common/src/async_sql_conn.rs new file mode 100644 index 0000000000..78357405a1 --- /dev/null +++ b/mm2src/db_common/src/async_sql_conn.rs @@ -0,0 +1,292 @@ +use crate::sqlite::rusqlite::Error as SqlError; +use crossbeam_channel::Sender; +use futures::channel::oneshot::{self}; +use rusqlite::OpenFlags; +use std::fmt::{self, Debug, Display}; +use std::path::Path; +use std::thread; + +/// Represents the errors specific for AsyncConnection. +#[derive(Debug)] +pub enum AsyncConnError { + /// The connection to the SQLite has been closed and cannot be queried anymore. + ConnectionClosed, + /// An error occurred while closing the SQLite connection. + /// This `Error` variant contains the [`AsyncConnection`], which can be used to retry the close operation + /// and the underlying [`SqlError`] that made it impossible to close the database. + Close((AsyncConnection, SqlError)), + /// A `Rusqlite` error occurred. + Rusqlite(SqlError), + /// An application-specific error occurred. + Internal(InternalError), +} + +impl Display for AsyncConnError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AsyncConnError::ConnectionClosed => write!(f, "ConnectionClosed"), + AsyncConnError::Close((_, e)) => write!(f, "Close((AsyncConnection, \"{e}\"))"), + AsyncConnError::Rusqlite(e) => write!(f, "Rusqlite(\"{e}\")"), + AsyncConnError::Internal(e) => write!(f, "Internal(\"{e}\")"), + } + } +} + +impl std::error::Error for AsyncConnError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + AsyncConnError::ConnectionClosed => None, + AsyncConnError::Close((_, e)) => Some(e), + AsyncConnError::Rusqlite(e) => Some(e), + AsyncConnError::Internal(e) => Some(e), + } + } +} + +#[derive(Debug)] +pub struct InternalError(pub String); + +impl fmt::Display for InternalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } +} + +impl std::error::Error for InternalError {} + +impl From for AsyncConnError { + fn from(value: SqlError) -> Self { AsyncConnError::Rusqlite(value) } +} + +/// The result returned on method calls in this crate. +pub type Result = std::result::Result; + +type CallFn = Box; + +enum Message { + Execute(CallFn), + Close(oneshot::Sender>), +} + +/// A handle to call functions in background thread. +#[derive(Clone)] +pub struct AsyncConnection { + sender: Sender, +} + +impl AsyncConnection { + /// Open a new connection to a SQLite database. + /// + /// `AsyncConnection::open(path)` is equivalent to + /// `AsyncConnection::open_with_flags(path, OpenFlags::SQLITE_OPEN_READ_WRITE | + /// OpenFlags::SQLITE_OPEN_CREATE)`. + /// + /// # Failure + /// + /// Will return `Err` if `path` cannot be converted to a C-compatible + /// string or if the underlying SQLite open call fails. + pub async fn open>(path: P) -> Result { + let path = path.as_ref().to_owned(); + start(move || rusqlite::Connection::open(path)).await + } + + /// Open a new AsyncConnection to an in-memory SQLite database. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite open call fails. + pub async fn open_in_memory() -> Result { start(rusqlite::Connection::open_in_memory).await } + + /// Open a new AsyncConnection to a SQLite database. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a + /// description of valid flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if `path` cannot be converted to a C-compatible + /// string or if the underlying SQLite open call fails. + pub async fn open_with_flags>(path: P, flags: OpenFlags) -> Result { + let path = path.as_ref().to_owned(); + start(move || rusqlite::Connection::open_with_flags(path, flags)).await + } + + /// Open a new AsyncConnection to a SQLite database using the specific flags + /// and vfs name. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a + /// description of valid flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if either `path` or `vfs` cannot be converted to a + /// C-compatible string or if the underlying SQLite open call fails. + pub async fn open_with_flags_and_vfs>(path: P, flags: OpenFlags, vfs: &str) -> Result { + let path = path.as_ref().to_owned(); + let vfs = vfs.to_owned(); + start(move || rusqlite::Connection::open_with_flags_and_vfs(path, flags, &vfs)).await + } + + /// Open a new AsyncConnection to an in-memory SQLite database. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a + /// description of valid flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite open call fails. + pub async fn open_in_memory_with_flags(flags: OpenFlags) -> Result { + start(move || rusqlite::Connection::open_in_memory_with_flags(flags)).await + } + + /// Open a new connection to an in-memory SQLite database using the + /// specific flags and vfs name. + /// + /// [Database Connection](http://www.sqlite.org/c3ref/open.html) for a + /// description of valid flag combinations. + /// + /// # Failure + /// + /// Will return `Err` if `vfs` cannot be converted to a C-compatible + /// string or if the underlying SQLite open call fails. + pub async fn open_in_memory_with_flags_and_vfs(flags: OpenFlags, vfs: &str) -> Result { + let vfs = vfs.to_owned(); + start(move || rusqlite::Connection::open_in_memory_with_flags_and_vfs(flags, &vfs)).await + } + + /// Call a function in background thread and get the result asynchronously. + /// + /// # Failure + /// + /// Will return `Err` if the database connection has been closed. + pub async fn call(&self, function: F) -> Result + where + F: FnOnce(&mut rusqlite::Connection) -> Result + 'static + Send, + R: Send + 'static, + { + let (sender, receiver) = oneshot::channel::>(); + + self.sender + .send(Message::Execute(Box::new(move |conn| { + let value = function(conn); + let _ = sender.send(value); + }))) + .map_err(|_| AsyncConnError::ConnectionClosed)?; + + receiver.await.map_err(|_| AsyncConnError::ConnectionClosed)? + } + + /// Call a function in background thread and get the result asynchronously. + /// + /// This method can cause a `panic` if the underlying database connection is closed. + /// it is a more user-friendly alternative to the [`AsyncConnection::call`] method. + /// It should be safe if the connection is never explicitly closed (using the [`AsyncConnection::close`] call). + /// + /// Calling this on a closed connection will cause a `panic`. + pub async fn call_unwrap(&self, function: F) -> R + where + F: FnOnce(&mut rusqlite::Connection) -> R + Send + 'static, + R: Send + 'static, + { + let (sender, receiver) = oneshot::channel::(); + + self.sender + .send(Message::Execute(Box::new(move |conn| { + let value = function(conn); + let _ = sender.send(value); + }))) + .expect("database connection should be open"); + + receiver.await.expect("Bug occurred, please report") + } + + /// Close the database AsyncConnection. + /// + /// This is functionally equivalent to the `Drop` implementation for + /// `AsyncConnection`. It consumes the `AsyncConnection`, but on error returns it + /// to the caller for retry purposes. + /// + /// If successful, any following `close` operations performed + /// on `AsyncConnection` copies will succeed immediately. + /// + /// On the other hand, any calls to [`AsyncConnection::call`] will return a [`AsyncConnError::ConnectionClosed`], + /// and any calls to [`AsyncConnection::call_unwrap`] will cause a `panic`. + /// + /// # Failure + /// + /// Will return `Err` if the underlying SQLite close call fails. + pub async fn close(&mut self) -> Result<()> { + let (sender, receiver) = oneshot::channel::>(); + + if let Err(crossbeam_channel::SendError(_)) = self.sender.send(Message::Close(sender)) { + // If the channel is closed on the other side, it means the connection closed successfully + // This is a safeguard against calling close on a `Copy` of the connection + return Ok(()); + } + + let result = receiver.await; + + if result.is_err() { + // If we get a RecvError at this point, it also means the channel closed in the meantime + // we can assume the connection is closed + return Ok(()); + } + + result.unwrap().map_err(|e| AsyncConnError::Close((self.clone(), e))) + } +} + +impl Debug for AsyncConnection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AsyncConnection").finish() } +} + +async fn start(open: F) -> Result +where + F: FnOnce() -> rusqlite::Result + Send + 'static, +{ + let (sender, receiver) = crossbeam_channel::unbounded::(); + let (result_sender, result_receiver) = oneshot::channel(); + + thread::spawn(move || { + let mut conn = match open() { + Ok(c) => c, + Err(e) => { + let _ = result_sender.send(Err(e)); + return; + }, + }; + + if let Err(_e) = result_sender.send(Ok(())) { + return; + } + + while let Ok(message) = receiver.recv() { + match message { + Message::Execute(f) => f(&mut conn), + Message::Close(s) => { + let result = conn.close(); + + match result { + Ok(v) => { + if s.send(Ok(v)).is_err() { + // terminate the thread + return; + } + break; + }, + Err((c, e)) => { + conn = c; + if s.send(Err(e)).is_err() { + // terminate the thread + return; + } + }, + } + }, + } + } + }); + + result_receiver + .await + .map_err(|e| AsyncConnError::Internal(InternalError(e.to_string()))) + .map(|_| AsyncConnection { sender }) +} diff --git a/mm2src/db_common/src/lib.rs b/mm2src/db_common/src/lib.rs index bd34839ae7..c1806e3b97 100644 --- a/mm2src/db_common/src/lib.rs +++ b/mm2src/db_common/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(all(test, not(target_arch = "wasm32")))] +mod async_conn_tests; +#[cfg(not(target_arch = "wasm32"))] pub mod async_sql_conn; #[cfg(not(target_arch = "wasm32"))] mod sql_condition; #[cfg(not(target_arch = "wasm32"))] mod sql_constraint; #[cfg(not(target_arch = "wasm32"))] mod sql_create; diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index d2537425c1..91931a4b7b 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -26,7 +26,9 @@ cfg_wasm32! { } cfg_native! { + use db_common::async_sql_conn::AsyncConnection; use db_common::sqlite::rusqlite::Connection; + use futures::lock::Mutex as AsyncMutex; use futures_rustls::webpki::DNSNameRef; use mm2_metrics::prometheus; use mm2_metrics::MmMetricsError; @@ -110,8 +112,10 @@ pub struct MmCtx { /// The RPC sender forwarding requests to writing part of underlying stream. #[cfg(target_arch = "wasm32")] pub wasm_rpc: Constructible, + /// Deprecated, please use `async_sqlite_connection` for new implementations. #[cfg(not(target_arch = "wasm32"))] pub sqlite_connection: Constructible>>, + /// Deprecated, please create `shared_async_sqlite_conn` for new implementations and call db `KOMODEFI-shared.db`. #[cfg(not(target_arch = "wasm32"))] pub shared_sqlite_conn: Constructible>>, pub mm_version: String, @@ -128,6 +132,9 @@ pub struct MmCtx { pub db_namespace: DbNamespaceId, /// The context belonging to the `nft` mod: `NftCtx`. pub nft_ctx: Mutex>>, + /// asynchronous handle for rusqlite connection. + #[cfg(not(target_arch = "wasm32"))] + pub async_sqlite_connection: Constructible>>, } impl MmCtx { @@ -172,6 +179,8 @@ impl MmCtx { #[cfg(target_arch = "wasm32")] db_namespace: DbNamespaceId::Main, nft_ctx: Mutex::new(None), + #[cfg(not(target_arch = "wasm32"))] + async_sqlite_connection: Constructible::default(), } } @@ -309,7 +318,7 @@ impl MmCtx { #[cfg(not(target_arch = "wasm32"))] pub fn init_sqlite_connection(&self) -> Result<(), String> { let sqlite_file_path = self.dbdir().join("MM2.db"); - log::debug!("Trying to open SQLite database file {}", sqlite_file_path.display()); + log_sqlite_file_open_attempt(&sqlite_file_path); let connection = try_s!(Connection::open(sqlite_file_path)); try_s!(self.sqlite_connection.pin(Arc::new(Mutex::new(connection)))); Ok(()) @@ -318,12 +327,21 @@ impl MmCtx { #[cfg(not(target_arch = "wasm32"))] pub fn init_shared_sqlite_conn(&self) -> Result<(), String> { let sqlite_file_path = self.shared_dbdir().join("MM2-shared.db"); - log::debug!("Trying to open SQLite database file {}", sqlite_file_path.display()); + log_sqlite_file_open_attempt(&sqlite_file_path); let connection = try_s!(Connection::open(sqlite_file_path)); try_s!(self.shared_sqlite_conn.pin(Arc::new(Mutex::new(connection)))); Ok(()) } + #[cfg(not(target_arch = "wasm32"))] + pub async fn init_async_sqlite_connection(&self) -> Result<(), String> { + let sqlite_file_path = self.dbdir().join("KOMODEFI.db"); + log_sqlite_file_open_attempt(&sqlite_file_path); + let async_conn = try_s!(AsyncConnection::open(sqlite_file_path).await); + try_s!(self.async_sqlite_connection.pin(Arc::new(AsyncMutex::new(async_conn)))); + Ok(()) + } + #[cfg(not(target_arch = "wasm32"))] pub fn sqlite_conn_opt(&self) -> Option> { self.sqlite_connection.as_option().map(|conn| conn.lock().unwrap()) @@ -705,3 +723,15 @@ impl MmCtxBuilder { MmArc::new(ctx) } } + +#[cfg(not(target_arch = "wasm32"))] +fn log_sqlite_file_open_attempt(sqlite_file_path: &Path) { + match sqlite_file_path.canonicalize() { + Ok(absolute_path) => { + log::debug!("Trying to open SQLite database file {}", absolute_path.display()); + }, + Err(_) => { + log::debug!("Trying to open SQLite database file {}", sqlite_file_path.display()); + }, + } +} diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 80912cbb04..a107961646 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -461,6 +461,9 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { .map_to_mm(MmInitError::ErrorSqliteInitializing)?; ctx.init_shared_sqlite_conn() .map_to_mm(MmInitError::ErrorSqliteInitializing)?; + ctx.init_async_sqlite_connection() + .await + .map_to_mm(MmInitError::ErrorSqliteInitializing)?; init_and_migrate_db(&ctx).await?; migrate_db(&ctx)?; } diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs index 1ac3bb619d..16c65a2c01 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs @@ -246,12 +246,26 @@ pub async fn my_balance(ctx: MmArc, req: Json) -> Result>, Stri Ok(try_s!(Response::builder().body(res))) } +#[cfg(not(target_arch = "wasm32"))] +async fn close_async_connection(ctx: &MmArc) { + if let Some(async_conn) = ctx.async_sqlite_connection.as_option() { + let mut conn = async_conn.lock().await; + if let Err(e) = conn.close().await { + error!("Error stopping AsyncConnection: {}", e); + } + } +} + pub async fn stop(ctx: MmArc) -> Result>, String> { dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; // Should delay the shutdown a bit in order not to trip the "stop" RPC call in unit tests. // Stopping immediately leads to the "stop" RPC call failing with the "errno 10054" sometimes. let fut = async move { Timer::sleep(0.05).await; + + #[cfg(not(target_arch = "wasm32"))] + close_async_connection(&ctx).await; + if let Err(e) = ctx.stop() { error!("Error stopping MmCtx: {}", e); } diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 0f33a332e2..2d917886b1 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -907,6 +907,20 @@ pub fn mm_ctx_with_custom_db() -> MmArc { ctx } +#[cfg(not(target_arch = "wasm32"))] +pub async fn mm_ctx_with_custom_async_db() -> MmArc { + use db_common::async_sql_conn::AsyncConnection; + use futures::lock::Mutex as AsyncMutex; + use std::sync::Arc; + + let ctx = MmCtxBuilder::new().into_mm_arc(); + + let connection = AsyncConnection::open_in_memory().await.unwrap(); + let _ = ctx.async_sqlite_connection.pin(Arc::new(AsyncMutex::new(connection))); + + ctx +} + /// Automatically kill a wrapped process. pub struct RaiiKill { pub handle: Child, From c66afb370886420d3aaaf8bf04393d9b629f7cbe Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Thu, 14 Dec 2023 05:59:12 +0200 Subject: [PATCH 35/40] fix(config): accept a string as rpcport value (#2026) --- mm2src/mm2_core/src/mm_ctx.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index 91931a4b7b..04de2e4d87 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -200,7 +200,21 @@ impl MmCtx { #[cfg(not(target_arch = "wasm32"))] pub fn rpc_ip_port(&self) -> Result { - let port = self.conf["rpcport"].as_u64().unwrap_or(7783); + let port = match self.conf.get("rpcport") { + Some(rpcport) => { + // Check if it's a number or a string that can be parsed into a number + rpcport + .as_u64() + .or_else(|| rpcport.as_str().and_then(|s| s.parse::().ok())) + .ok_or_else(|| { + format!( + "Invalid `rpcport` value. Expected a positive integer, but received: {}", + rpcport + ) + })? + }, + None => 7783, // Default port if `rpcport` does not exist in the config + }; if port < 1000 { return ERR!("rpcport < 1000"); } From da8a23b14e6d7488846dc0db7e658b83a02fa9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Thu, 14 Dec 2023 14:21:36 +0300 Subject: [PATCH 36/40] deps(network): sync with upstream yamux (#2030) * libp2p-yamux now uses yamux v0.13 (new version) by default and fall back to yamux v0.12 (old version) when setting any configuration options. * Additionally, this commit increases the backpressure buffer cap from 25 to 256. * Use new protocol version (Version2) for peer exchange and request-response behaviours. --- Cargo.lock | 71 ++++++++++++------- mm2src/mm2_p2p/Cargo.toml | 4 +- .../mm2_p2p/src/behaviours/peers_exchange.rs | 5 +- .../src/behaviours/request_response.rs | 5 +- 4 files changed, 55 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fba39200e..5981ce776e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3433,7 +3433,7 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libp2p" version = "0.52.1" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "bytes 1.4.0", "futures 0.3.28", @@ -3465,7 +3465,7 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" version = "0.2.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "libp2p-core", "libp2p-identity", @@ -3476,7 +3476,7 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" version = "0.2.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "libp2p-core", "libp2p-identity", @@ -3487,7 +3487,7 @@ dependencies = [ [[package]] name = "libp2p-core" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "either", "fnv", @@ -3514,7 +3514,7 @@ dependencies = [ [[package]] name = "libp2p-dns" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "futures 0.3.28", "libp2p-core", @@ -3528,7 +3528,7 @@ dependencies = [ [[package]] name = "libp2p-floodsub" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "asynchronous-codec", "cuckoofilter", @@ -3548,7 +3548,7 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" version = "0.45.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "asynchronous-codec", "base64 0.21.2", @@ -3579,7 +3579,7 @@ dependencies = [ [[package]] name = "libp2p-identify" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "asynchronous-codec", "either", @@ -3619,7 +3619,7 @@ dependencies = [ [[package]] name = "libp2p-mdns" version = "0.44.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "data-encoding", "futures 0.3.28", @@ -3639,7 +3639,7 @@ dependencies = [ [[package]] name = "libp2p-metrics" version = "0.13.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "instant", "libp2p-core", @@ -3655,7 +3655,7 @@ dependencies = [ [[package]] name = "libp2p-noise" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "bytes 1.4.0", "curve25519-dalek 3.2.0", @@ -3679,7 +3679,7 @@ dependencies = [ [[package]] name = "libp2p-ping" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "either", "futures 0.3.28", @@ -3696,7 +3696,7 @@ dependencies = [ [[package]] name = "libp2p-request-response" version = "0.25.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "async-trait", "futures 0.3.28", @@ -3713,7 +3713,7 @@ dependencies = [ [[package]] name = "libp2p-swarm" version = "0.43.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "either", "fnv", @@ -3735,7 +3735,7 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" version = "0.33.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "heck", "proc-macro-warning", @@ -3747,7 +3747,7 @@ dependencies = [ [[package]] name = "libp2p-tcp" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "futures 0.3.28", "futures-timer", @@ -3763,7 +3763,7 @@ dependencies = [ [[package]] name = "libp2p-wasm-ext" version = "0.40.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "futures 0.3.28", "js-sys", @@ -3776,7 +3776,7 @@ dependencies = [ [[package]] name = "libp2p-websocket" version = "0.42.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "either", "futures 0.3.28", @@ -3795,13 +3795,15 @@ dependencies = [ [[package]] name = "libp2p-yamux" version = "0.44.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ + "either", "futures 0.3.28", "libp2p-core", - "log", "thiserror", - "yamux", + "tracing", + "yamux 0.12.1", + "yamux 0.13.1", ] [[package]] @@ -4740,7 +4742,7 @@ checksum = "d8883adfde9756c1d30b0f519c9b8c502a94b41ac62f696453c37c7fc0a958ce" [[package]] name = "multistream-select" version = "0.13.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "bytes 1.4.0", "futures 0.3.28", @@ -5627,7 +5629,7 @@ dependencies = [ [[package]] name = "quick-protobuf-codec" version = "0.2.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "asynchronous-codec", "bytes 1.4.0", @@ -6379,7 +6381,7 @@ checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "rw-stream-sink" version = "0.4.0" -source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.1#e7c2fd1ef9d20cc4d29768b0a422a0f0ac9df82d" +source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.2#15dd3a3c32b1d9b393cbb108f479675cacf71b99" dependencies = [ "futures 0.3.28", "pin-project", @@ -9411,14 +9413,31 @@ dependencies = [ [[package]] name = "yamux" -version = "0.10.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" dependencies = [ "futures 0.3.28", "log", "nohash-hasher", "parking_lot 0.12.0", + "pin-project", + "rand 0.8.4", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1d0148b89300047e72994bee99ecdabd15a9166a7b70c8b8c37c314dcc9002" +dependencies = [ + "futures 0.3.28", + "instant", + "log", + "nohash-hasher", + "parking_lot 0.12.0", + "pin-project", "rand 0.8.4", "static_assertions", ] diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index a4331bcb4b..d1b85635f7 100644 --- a/mm2src/mm2_p2p/Cargo.toml +++ b/mm2src/mm2_p2p/Cargo.toml @@ -29,12 +29,12 @@ void = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] instant = "0.1.12" -libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.1", default-features = false, features = ["dns", "identify", "floodsub", "gossipsub", "noise", "ping", "request-response", "secp256k1", "tcp", "tokio", "websocket", "macros", "yamux"] } +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.2", default-features = false, features = ["dns", "identify", "floodsub", "gossipsub", "noise", "ping", "request-response", "secp256k1", "tcp", "tokio", "websocket", "macros", "yamux"] } tokio = { version = "1.20", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] instant = { version = "0.1.12", features = ["wasm-bindgen"] } -libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.1", default-features = false, features = ["identify", "floodsub", "noise", "gossipsub", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket", "macros", "yamux"] } +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.2", default-features = false, features = ["identify", "floodsub", "noise", "gossipsub", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket", "macros", "yamux"] } [dev-dependencies] async-std = "1.6.2" diff --git a/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs b/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs index 38f3b35dc8..52007a335c 100644 --- a/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs +++ b/mm2src/mm2_p2p/src/behaviours/peers_exchange.rs @@ -49,12 +49,14 @@ impl<'de> Deserialize<'de> for PeerIdSerde { #[derive(Debug, Clone)] pub enum PeersExchangeProtocol { Version1, + Version2, } impl AsRef for PeersExchangeProtocol { fn as_ref(&self) -> &str { match self { PeersExchangeProtocol::Version1 => "/peers-exchange/1", + PeersExchangeProtocol::Version2 => "/peers-exchange/2", } } } @@ -205,7 +207,8 @@ impl NetworkBehaviour for PeersExchange { #[allow(clippy::new_without_default)] impl PeersExchange { pub fn new(network_info: NetworkInfo) -> Self { - let protocol = iter::once((PeersExchangeProtocol::Version1, ProtocolSupport::Full)); + // We don't want to support V1 since it was only used in 7777 old layer. + let protocol = iter::once((PeersExchangeProtocol::Version2, ProtocolSupport::Full)); let config = RequestResponseConfig::default(); let request_response = RequestResponse::new(protocol, config); PeersExchange { diff --git a/mm2src/mm2_p2p/src/behaviours/request_response.rs b/mm2src/mm2_p2p/src/behaviours/request_response.rs index b3b949cedb..7051a2d8e6 100644 --- a/mm2src/mm2_p2p/src/behaviours/request_response.rs +++ b/mm2src/mm2_p2p/src/behaviours/request_response.rs @@ -63,12 +63,14 @@ struct PendingRequest { #[derive(Debug, Clone)] pub enum Protocol { Version1, + Version2, } impl AsRef for Protocol { fn as_ref(&self) -> &str { match self { Protocol::Version1 => "/request-response/1", + Protocol::Version2 => "/request-response/2", } } } @@ -393,7 +395,8 @@ impl From> for Reques /// Build a request-response network behaviour. pub fn build_request_response_behaviour() -> RequestResponseBehaviour { let config = RequestResponseConfig::default(); - let protocol = core::iter::once((Protocol::Version1, ProtocolSupport::Full)); + // We don't want to support V1 since it was only used in 7777 old layer. + let protocol = core::iter::once((Protocol::Version2, ProtocolSupport::Full)); let inner = RequestResponse::new(protocol, config); let (tx, rx) = mpsc::unbounded(); From 1755d577b64b4f096ac48eb6cf96685abdd57ad5 Mon Sep 17 00:00:00 2001 From: smk762 <35845239+smk762@users.noreply.github.com> Date: Thu, 14 Dec 2023 20:16:02 +0800 Subject: [PATCH 37/40] fix(price_endpoints): add cached url (#2032) An additional PRICE_ENDPOINTS url which is a cached copy of https://prices.komodian.info/api/v2/tickers and is updated every minute was added by this commit. This should serve as a reliable fallback when rate limiting becomes an issue. --- mm2src/coins/lp_price.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/lp_price.rs b/mm2src/coins/lp_price.rs index 12f11e79ad..d67721917c 100644 --- a/mm2src/coins/lp_price.rs +++ b/mm2src/coins/lp_price.rs @@ -10,9 +10,10 @@ use std::collections::HashMap; #[cfg(feature = "run-docker-tests")] use std::str::FromStr; use std::str::Utf8Error; -const PRICE_ENDPOINTS: [&str; 2] = [ +const PRICE_ENDPOINTS: [&str; 3] = [ "https://prices.komodian.info/api/v2/tickers", "https://prices.cipig.net:1717/api/v2/tickers", + "https://defi-stats.komodo.earth/api/v3/prices/tickers_v2", ]; #[derive(Debug)] From 6d7d05fb78e07e84e6089647cd88cb3408796017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Thu, 14 Dec 2023 19:08:59 +0300 Subject: [PATCH 38/40] chore(network): write network information to stdout (#2034) This allows finding mm2 ports in stdout --- mm2src/mm2_p2p/src/behaviours/atomicdex.rs | 2 ++ mm2src/mm2_p2p/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs index 91f423c4e2..498c7dee14 100644 --- a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs +++ b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs @@ -600,6 +600,8 @@ fn start_gossipsub( let noise_config = noise::Config::new(&local_key).expect("Signing libp2p-noise static DH keypair failed."); let network_info = node_type.to_network_info(); + info!("Network information: {:?}", network_info); + let transport = match network_info { NetworkInfo::InMemory => build_memory_transport(noise_config), NetworkInfo::Distributed { .. } => build_dns_ws_transport(noise_config, node_type.wss_certs()), diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index 7aca2eebd1..953e711423 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -41,7 +41,7 @@ lazy_static! { static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum NetworkInfo { /// The in-memory network. InMemory, @@ -53,7 +53,7 @@ impl NetworkInfo { pub fn in_memory(&self) -> bool { matches!(self, NetworkInfo::InMemory) } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct NetworkPorts { pub tcp: u16, pub wss: u16, From f2efb10c10a6dcbd2ad5031801cc26e658ce0a93 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Fri, 15 Dec 2023 16:39:13 +0200 Subject: [PATCH 39/40] chore(release): add changelog entries for v2.0.0-beta (#2037) --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eded4bcbc3..e61f3274cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,57 @@ +## v2.0.0-beta - 2023-12-15 +**Features:** +- KMD Burn [#2010](https://github.com/KomodoPlatform/komodo-defi-framework/issues/2010) + - Burn 25% of taker fee when paid in KMD [#2006](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2006). +- Trading Protocol Upgrade [#1895](https://github.com/KomodoPlatform/atomicDEX-API/issues/1895) + - Implement successful swaps v2 of UTXO to UTXO coins in [#1958](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1958). + - Add Swaps V2 message exchange using Protobuf in [#1958](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1958). + - Storing upgraded swaps data to SQLite DB was partially implemented in [#1980](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1980). + - Protocol enhancement for UTXO coins by adding one more funding tx for taker, which can be reclaimed immediately if maker back-outs was implemented in [#1980](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1980). +- Event Streaming [#1901](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1901) + - Streaming channels using mpsc and SSE to send data to clients continuously was implemented in [#1945](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1945). + - NETWORK event was implemented to show this new functionality in [#1945](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1945). + - Wasm event streaming using workers was added in [#1978](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1978). + - `COIN_BALANCE` events for Tendermint Protocol were added in [#1978](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1978). + +**Enhancements/Fixes:** +- Network Enhancements: + - P2P layer now uses the latest stable libp2p version in [#1878](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1878). + - `7777` network was deprecated in [#2020](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2020). + - Seednodes for `netid` `8762` were updated in [#2024](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2024). + - `libp2p-yamux` now uses yamux `v0.13` (new version) by default and fall back to yamux `v0.12` (old version) when setting any configuration options in [#2030](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2030). + - The backpressure buffer cap was increased from `25` to `256` in [#2030](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2030). + - New protocol version (Version2) was used for peer exchange and request-response behaviours in [#2030](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2030). + - Network information is now written to stdout to find mm2 ports easily after [#2034](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2034). +- NFT integration [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) + - `exclude_spam` and `exclude_phishing` params were added for `get_nft_list` and `get_nft_transfers` RPCs in [#1959](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1959). + - `nft_cache_db` was added in `NftCtx` for non wasm targets in [#1989](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1989). + - `AsyncConnection` structure that can be used as async wrapper for sqlite connection was added in [#1989](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1989). + - `async_sqlite_connection` field was added to `MmCtx` in [#1989](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1989). + - Spam transfers with empty meta no longer update after [#1989](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1989). +- ARRR/Pirate: + - ARRR infrastructure for lightwallet servers uses a fork of lightwalletd, the grpc service was renamed `from cash.z.wallet.sdk.rpc` to `pirate.wallet.sdk.rpc` to use the lightwalletd fork in [#1963](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1963). + - Previous blocks/wallet sync will be resumed if `sync_params` are not provided after restart in [#1967](https://github.com/KomodoPlatform/atomicDEX-API/issues/1967). +- Adex-CLI [#1682](https://github.com/KomodoPlatform/atomicDEX-API/issues/1682) + - Exact dependency versions of `hyper-rustls`, `rustls` and other deps was set in [#1956](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1956). + - A warning was added on insecure cli configuration file mode in [#1956](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1956). +- Storable State Machine abstraction was added while having few changes to existing state machines in [#1958](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1958). +- EVM web3 requests timeout was reduced to 20s in [#1973](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1973). +- Fixed 0.0001 min threshold for TakerFee was removed in [#1971](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1971). +- The minimum trading volume for evm and tendermint was changed to be the smallest possible amount of the coin in [#1971](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1971). +- Minimum trading price is reduced to be any value above 0 in [#1971](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1971). +- Cryptocondition script type was added to utxo transactions in [#1991](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1991). +- On response error the next web3 node is tried in [#1998](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1998). +- Watchtower taker-side restart bug was fixed in [#1908](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1908). +- 'version' method was added to `PUBLIC_METHODS` that require no login in [#2001](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2001). +- `rpcport` value can now accept a string after [#2026](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2026). +- An additional `PRICE_ENDPOINTS` url which is a cached copy of `https://prices.komodian.info/api/v2/tickers` and is updated every minute was added in [#2032](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2032). + +**NB - Backwards compatibility breaking changes:** +- `7777` Network deprecation and the upgrade to a new p2p layer breaks compatibility with previous versions of Komodo DeFi Framework. Connections between nodes/clients running an older version of Komodo DeFi Framework and nodes/clients running this latest version will not be possible. To avoid this, all nodes/clients must be upgraded to the latest version of Komodo DeFi Framework. +- Because of KMD burn of a part of the taker fee, the taker fee outputs for any `coin/KMD` swap are changed and makers running older versions will not be able to validate the taker fee, this will cause the swap to fail. This case will never happen anyway because older versions will not be able to connect to this latest version due to the network upgrade. +- Because of the removal of the fixed 0.0001 min threshold for TakerFee, taker fee validation will also fail for these cases. Again, this case will never happen as the previous case. + + ## v1.0.7-beta - 2023-09-08 **Features:** - Trading Protocol Upgrade [#1895](https://github.com/KomodoPlatform/atomicDEX-API/issues/1895) From 466d4f25debbcd0c4a05a27bb0ec9a56ca38c71e Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Fri, 15 Dec 2023 17:44:00 +0200 Subject: [PATCH 40/40] fix(p2p): handle encode_and_sign errors (#2038) --- mm2src/mm2_main/src/lp_ordermatch.rs | 24 +++++++++++++++++++++--- mm2src/mm2_main/src/lp_swap.rs | 16 ++++++++++++++-- mm2src/mm2_p2p/src/lib.rs | 6 ++++-- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 92055cf43d..688f9ffbb2 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1049,7 +1049,13 @@ fn maker_order_created_p2p_notify( let to_broadcast = new_protocol::OrdermatchMessage::MakerOrderCreated(message.clone()); let (key_pair, peer_id) = p2p_keypair_and_peer_id_to_broadcast(&ctx, order.p2p_keypair()); - let encoded_msg = encode_and_sign(&to_broadcast, key_pair.private_ref()).unwrap(); + let encoded_msg = match encode_and_sign(&to_broadcast, key_pair.private_ref()) { + Ok(msg) => msg, + Err(e) => { + error!("Couldn't encode and sign the 'maker_order_created' message: {}", e); + return; + }, + }; let item: OrderbookItem = (message, hex::encode(key_pair.public_slice())).into(); insert_or_update_my_order(&ctx, item, order); broadcast_p2p_msg(&ctx, topic, encoded_msg, peer_id); @@ -1074,7 +1080,13 @@ fn maker_order_updated_p2p_notify( ) { let msg: new_protocol::OrdermatchMessage = message.clone().into(); let (secret, peer_id) = p2p_private_and_peer_id_to_broadcast(&ctx, p2p_privkey); - let encoded_msg = encode_and_sign(&msg, &secret).unwrap(); + let encoded_msg = match encode_and_sign(&msg, &secret) { + Ok(msg) => msg, + Err(e) => { + error!("Couldn't encode and sign the 'maker_order_updated' message: {}", e); + return; + }, + }; process_my_maker_order_updated(&ctx, &message); broadcast_p2p_msg(&ctx, topic, encoded_msg, peer_id); } @@ -2325,7 +2337,13 @@ fn broadcast_ordermatch_message( p2p_privkey: Option<&KeyPair>, ) { let (secret, peer_id) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey); - let encoded_msg = encode_and_sign(&msg, &secret).unwrap(); + let encoded_msg = match encode_and_sign(&msg, &secret) { + Ok(encoded_msg) => encoded_msg, + Err(e) => { + error!("Failed to encode and sign ordermatch message: {}", e); + return; + }, + }; broadcast_p2p_msg(ctx, topic, encoded_msg, peer_id); } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 8d207542fc..96fd72805f 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -290,7 +290,13 @@ pub fn broadcast_swap_msg_every_delayed( /// Broadcast the swap message once pub fn broadcast_swap_message(ctx: &MmArc, topic: String, msg: T, p2p_privkey: &Option) { let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); - let encoded_msg = encode_and_sign(&msg, &p2p_private).unwrap(); + let encoded_msg = match encode_and_sign(&msg, &p2p_private) { + Ok(m) => m, + Err(e) => { + error!("Error encoding and signing swap message: {}", e); + return; + }, + }; broadcast_p2p_msg(ctx, topic, encoded_msg, from); } @@ -301,7 +307,13 @@ pub fn broadcast_p2p_tx_msg(ctx: &MmArc, topic: String, msg: &TransactionEnum, p } let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); - let encoded_msg = encode_and_sign(&msg.tx_hex(), &p2p_private).unwrap(); + let encoded_msg = match encode_and_sign(&msg.tx_hex(), &p2p_private) { + Ok(m) => m, + Err(e) => { + error!("Error encoding and signing tx message: {}", e); + return; + }, + }; broadcast_p2p_msg(ctx, topic, encoded_msg, from); } diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index 953e711423..6163e757a1 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -78,9 +78,11 @@ struct SignedMessageSerdeHelper<'a> { } pub fn encode_and_sign(message: &T, secret: &[u8; 32]) -> Result, rmp_serde::encode::Error> { - let secret = SecretKey::from_slice(secret).unwrap(); + let secret = SecretKey::from_slice(secret) + .map_err(|e| rmp_serde::encode::Error::Syntax(format!("Error {} parsing secret", e)))?; let encoded = encode_message(message)?; - let sig_hash = SecpMessage::from_slice(&sha256(&encoded)).expect("Message::from_slice should never fail"); + let sig_hash = SecpMessage::from_slice(&sha256(&encoded)) + .map_err(|e| rmp_serde::encode::Error::Syntax(format!("Error {} parsing message", e)))?; let sig = SECP_SIGN.sign(&sig_hash, &secret); let serialized_sig = sig.serialize_compact(); let pubkey = PublicKey::from(Secp256k1Pubkey::from_secret_key(&*SECP_SIGN, &secret));