From d35e3ba5728c7316d7317a982364c08d44ceae3c Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Fri, 3 Mar 2023 17:58:51 +0300 Subject: [PATCH 01/79] implement lock free sequence avoid logic Signed-off-by: ozkanonur --- mm2src/coins/tendermint/tendermint_coin.rs | 399 ++++++++++--------- mm2src/coins/tendermint/tendermint_token.rs | 9 +- mm2src/mm2_main/tests/mm2_tests/iris_swap.rs | 2 - 3 files changed, 222 insertions(+), 188 deletions(-) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index f99e55967b..a8ac4fd6ce 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -214,7 +214,6 @@ pub struct TendermintCoinImpl { pub(super) denom: Denom, chain_id: ChainId, gas_price: Option, - pub(super) sequence_lock: AsyncMutex<()>, pub(crate) tokens_info: PaMutex>, /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation /// or on [`MmArc::stop`]. @@ -265,6 +264,7 @@ pub enum TendermintCoinRpcError { InvalidResponse(String), PerformError(String), RpcClientError(String), + InternalError(String), } impl From for TendermintCoinRpcError { @@ -282,6 +282,7 @@ impl From for BalanceError { TendermintCoinRpcError::Prost(e) => BalanceError::InvalidResponse(e.to_string()), TendermintCoinRpcError::PerformError(e) => BalanceError::Transport(e), TendermintCoinRpcError::RpcClientError(e) => BalanceError::Transport(e), + TendermintCoinRpcError::InternalError(e) => BalanceError::Internal(e), } } } @@ -293,6 +294,7 @@ impl From for ValidatePaymentError { TendermintCoinRpcError::Prost(e) => ValidatePaymentError::InvalidRpcResponse(e.to_string()), TendermintCoinRpcError::PerformError(e) => ValidatePaymentError::Transport(e), TendermintCoinRpcError::RpcClientError(e) => ValidatePaymentError::Transport(e), + TendermintCoinRpcError::InternalError(e) => ValidatePaymentError::InternalError(e), } } } @@ -498,7 +500,6 @@ impl TendermintCoin { chain_id, gas_price: protocol_info.gas_price, avg_blocktime: conf.avg_blocktime, - sequence_lock: AsyncMutex::new(()), tokens_info: PaMutex::new(HashMap::new()), abortable_system, history_sync_state: Mutex::new(history_sync_state), @@ -596,23 +597,88 @@ impl TendermintCoin { sha256(&htlc_id).to_string().to_uppercase() } + pub(super) async fn seq_safe_send_raw_tx_bytes( + &self, + tx_payload: Any, + fee: Fee, + timeout_height: u64, + memo: String, + ) -> Result<(String, Raw), TransactionErr> { + let (tx_id, tx_raw) = loop { + let tx_raw = try_tx_s!(self.any_to_signed_raw_tx( + try_tx_s!(self.my_account_info().await), + tx_payload.clone(), + fee.clone(), + timeout_height, + memo.clone(), + )); + + match self.send_raw_tx_bytes(&try_tx_s!(tx_raw.to_bytes())).compat().await { + Ok(tx_id) => break (tx_id, tx_raw), + Err(e) => { + if e.contains("Wrong account sequence catched") { + debug!("Got wrong account sequence, trying again."); + continue; + } + + return Err(crate::TransactionErr::Plain(ERRL!("{}", e))); + }, + }; + }; + + Ok((tx_id, tx_raw)) + } + #[allow(deprecated)] pub(super) async fn calculate_fee( &self, - base_denom: Denom, - tx_bytes: Vec, + msg: Any, + timeout_height: u64, + memo: String, ) -> MmResult { let path = AbciPath::from_str(ABCI_SIMULATE_TX_PATH).expect("valid path"); - let request = SimulateRequest { tx_bytes, tx: None }; - let request = AbciRequest::new( - Some(path), - request.encode_to_vec(), - ABCI_REQUEST_HEIGHT, - ABCI_REQUEST_PROVE, - ); - let raw_response = self.rpc_client().await?.perform(request).await?; - let response = SimulateResponse::decode(raw_response.response.value.as_slice())?; + let (response, raw_response) = loop { + let account_info = self.my_account_info().await?; + let tx_bytes = self + .gen_simulated_tx(account_info, msg.clone(), timeout_height, memo.clone()) + .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; + + let request = SimulateRequest { tx_bytes, tx: None }; + let request = AbciRequest::new( + Some(path.clone()), + request.encode_to_vec().clone(), + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE, + ); + + let raw_response = self.rpc_client().await?.perform(request).await?; + + if raw_response + .response + .log + .to_string() + .contains("incorrect account sequence") + { + debug!("Got wrong account sequence, trying again."); + continue; + } + + match raw_response.response.code { + cosmrs::tendermint::abci::Code::Ok => {}, + cosmrs::tendermint::abci::Code::Err(_) => { + return MmError::err(TendermintCoinRpcError::InvalidResponse(format!( + "Could not read gas_info. Invalid Response: {}", + raw_response.response.log + ))); + }, + }; + + break ( + SimulateResponse::decode(raw_response.response.value.as_slice())?, + raw_response, + ); + }; let gas = response.gas_info.as_ref().ok_or_else(|| { TendermintCoinRpcError::InvalidResponse(format!( @@ -624,7 +690,7 @@ impl TendermintCoin { let amount = ((gas.gas_used as f64 * 1.5) * self.gas_price()).ceil(); let fee_amount = Coin { - denom: base_denom, + denom: self.platform_denom().parse().expect("Platform denom parse can't fail"), amount: (amount as u64).into(), }; @@ -632,18 +698,54 @@ impl TendermintCoin { } #[allow(deprecated)] - pub(super) async fn calculate_fee_amount_as_u64(&self, tx_bytes: Vec) -> MmResult { + pub(super) async fn calculate_fee_amount_as_u64( + &self, + msg: Any, + timeout_height: u64, + memo: String, + ) -> MmResult { let path = AbciPath::from_str(ABCI_SIMULATE_TX_PATH).expect("valid path"); - let request = SimulateRequest { tx_bytes, tx: None }; - let request = AbciRequest::new( - Some(path), - request.encode_to_vec(), - ABCI_REQUEST_HEIGHT, - ABCI_REQUEST_PROVE, - ); - let raw_response = self.rpc_client().await?.perform(request).await?; - let response = SimulateResponse::decode(raw_response.response.value.as_slice())?; + let (response, raw_response) = loop { + let account_info = self.my_account_info().await?; + let tx_bytes = self + .gen_simulated_tx(account_info, msg.clone(), timeout_height, memo.clone()) + .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; + let request = SimulateRequest { tx_bytes, tx: None }; + let request = AbciRequest::new( + Some(path.clone()), + request.encode_to_vec(), + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE, + ); + + let raw_response = self.rpc_client().await?.perform(request).await?; + + if raw_response + .response + .log + .to_string() + .contains("incorrect account sequence") + { + debug!("Got wrong account sequence, trying again."); + continue; + } + + match raw_response.response.code { + cosmrs::tendermint::abci::Code::Ok => {}, + cosmrs::tendermint::abci::Code::Err(_) => { + return MmError::err(TendermintCoinRpcError::InvalidResponse(format!( + "Could not read gas_info. Invalid Response: {}", + raw_response.response.log + ))); + }, + }; + + break ( + SimulateResponse::decode(raw_response.response.value.as_slice())?, + raw_response, + ); + }; let gas = response.gas_info.as_ref().ok_or_else(|| { TendermintCoinRpcError::InvalidResponse(format!( @@ -868,29 +970,27 @@ impl TendermintCoin { let create_htlc_tx = try_tx_s!(coin.gen_create_htlc_tx(denom, &to, amount, &secret_hash, time_lock as u64)); - let _sequence_lock = coin.sequence_lock.lock().await; let current_block = try_tx_s!(coin.current_block().compat().await); let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info = try_tx_s!(coin.my_account_info().await); - let simulated_tx = try_tx_s!(coin.gen_simulated_tx( - account_info.clone(), - create_htlc_tx.msg_payload.clone(), - timeout_height, - TX_DEFAULT_MEMO.into(), - )); - - let fee = try_tx_s!(coin.calculate_fee(coin.denom.clone(), simulated_tx).await); - - let tx_raw = try_tx_s!(coin.any_to_signed_raw_tx( - account_info.clone(), - create_htlc_tx.msg_payload.clone(), - fee, - timeout_height, - TX_DEFAULT_MEMO.into(), - )); + let fee = try_tx_s!( + coin.calculate_fee( + create_htlc_tx.msg_payload.clone(), + timeout_height, + TX_DEFAULT_MEMO.to_owned() + ) + .await + ); - let _tx_id = try_tx_s!(coin.send_raw_tx_bytes(&try_tx_s!(tx_raw.to_bytes())).compat().await); + let (_tx_id, tx_raw) = try_tx_s!( + coin.seq_safe_send_raw_tx_bytes( + create_htlc_tx.msg_payload.clone(), + fee.clone(), + timeout_height, + TX_DEFAULT_MEMO.into(), + ) + .await + ); Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { data: tx_raw.into(), @@ -927,30 +1027,18 @@ impl TendermintCoin { let coin = self.clone(); let fut = async move { - let _sequence_lock = coin.sequence_lock.lock().await; - let account_info = try_tx_s!(coin.my_account_info().await); - let current_block = try_tx_s!(coin.current_block().compat().await.map_to_mm(WithdrawError::Transport)); let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let simulated_tx = try_tx_s!(coin.gen_simulated_tx( - account_info.clone(), - tx_payload.clone(), - timeout_height, - TX_DEFAULT_MEMO.into(), - )); - - let fee = try_tx_s!(coin.calculate_fee(coin.denom.clone(), simulated_tx).await); - - let tx_raw = try_tx_s!(coin - .any_to_signed_raw_tx(account_info, tx_payload, fee, timeout_height, memo) - .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))); - - let tx_bytes = try_tx_s!(tx_raw - .to_bytes() - .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))); + let fee = try_tx_s!( + coin.calculate_fee(tx_payload.clone(), timeout_height, TX_DEFAULT_MEMO.to_owned()) + .await + ); - let _tx_id = try_tx_s!(coin.send_raw_tx_bytes(&tx_bytes).compat().await); + let (_tx_id, tx_raw) = try_tx_s!( + coin.seq_safe_send_raw_tx_bytes(tx_payload.clone(), fee.clone(), timeout_height, memo.clone()) + .await + ); Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { data: tx_raw.into(), @@ -1156,23 +1244,15 @@ impl TendermintCoin { })?; let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info = self.my_account_info().await?; - let simulated_tx = self - .gen_simulated_tx( - account_info.clone(), + let fee_uamount = self + .calculate_fee_amount_as_u64( create_htlc_tx.msg_payload.clone(), timeout_height, - TX_DEFAULT_MEMO.into(), + TX_DEFAULT_MEMO.to_owned(), ) - .map_err(|e| { - MmError::new(TradePreimageError::InternalError(format!( - "Tx simulation failed. {:?}", - e - ))) - })?; + .await?; - let fee_uamount = self.calculate_fee_amount_as_u64(simulated_tx).await?; let fee_amount = big_decimal_from_sat_unsigned(fee_uamount, self.decimals); Ok(TradeFee { @@ -1201,7 +1281,6 @@ impl TendermintCoin { })?; let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info = self.my_account_info().await?; let msg_send = MsgSend { from_address: self.account_id.clone(), @@ -1214,16 +1293,9 @@ impl TendermintCoin { .to_any() .map_err(|e| MmError::new(TradePreimageError::InternalError(e.to_string())))?; - let simulated_tx = self - .gen_simulated_tx(account_info.clone(), msg_send, timeout_height, TX_DEFAULT_MEMO.into()) - .map_err(|e| { - MmError::new(TradePreimageError::InternalError(format!( - "Tx simulation failed. {:?}", - e - ))) - })?; - - let fee_uamount = self.calculate_fee_amount_as_u64(simulated_tx).await?; + let fee_uamount = self + .calculate_fee_amount_as_u64(msg_send.clone(), timeout_height, TX_DEFAULT_MEMO.to_owned()) + .await?; let fee_amount = big_decimal_from_sat_unsigned(fee_uamount, decimals); Ok(TradeFee { @@ -1470,17 +1542,14 @@ impl MmCoin for TendermintCoin { .await .map_to_mm(WithdrawError::Transport)?; - let _sequence_lock = coin.sequence_lock.lock().await; let account_info = coin.my_account_info().await?; let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - - let simulated_tx = coin - .gen_simulated_tx(account_info.clone(), msg_send.clone(), timeout_height, memo.clone()) - .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; // >> END TX SIMULATION FOR FEE CALCULATION - let fee_amount_u64 = coin.calculate_fee_amount_as_u64(simulated_tx).await?; + let fee_amount_u64 = coin + .calculate_fee_amount_as_u64(msg_send.clone(), timeout_height, memo.clone()) + .await?; let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, coin.decimals()); let fee_amount = Coin { @@ -1736,6 +1805,8 @@ impl MarketCoinOps for TendermintCoin { self.send_raw_tx_bytes(&tx_bytes) } + /// Consider using `seq_safe_raw_tx_bytes` instead. + /// This is considered as unsafe due to sequence mismatches. fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { // as sanity check try_fus!(Raw::from_bytes(tx)); @@ -1935,30 +2006,27 @@ impl SwapOps for TendermintCoin { let coin = self.clone(); let fut = async move { - let _sequence_lock = coin.sequence_lock.lock().await; let current_block = try_tx_s!(coin.current_block().compat().await); let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info = try_tx_s!(coin.my_account_info().await); - - let simulated_tx = try_tx_s!(coin.gen_simulated_tx( - account_info.clone(), - claim_htlc_tx.msg_payload.clone(), - timeout_height, - TX_DEFAULT_MEMO.into(), - )); - - let fee = try_tx_s!(coin.calculate_fee(coin.denom.clone(), simulated_tx).await); - - let tx_raw = try_tx_s!(coin.any_to_signed_raw_tx( - account_info, - claim_htlc_tx.msg_payload, - fee, - timeout_height, - TX_DEFAULT_MEMO.into(), - )); + let fee = try_tx_s!( + coin.calculate_fee( + claim_htlc_tx.msg_payload.clone(), + timeout_height, + TX_DEFAULT_MEMO.to_owned() + ) + .await + ); - let tx_id = try_tx_s!(coin.send_raw_tx_bytes(&try_tx_s!(tx_raw.to_bytes())).compat().await); + let (_tx_id, tx_raw) = try_tx_s!( + coin.seq_safe_send_raw_tx_bytes( + claim_htlc_tx.msg_payload.clone(), + fee.clone(), + timeout_height, + TX_DEFAULT_MEMO.into(), + ) + .await + ); Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { data: tx_raw.into(), @@ -1993,30 +2061,27 @@ impl SwapOps for TendermintCoin { let coin = self.clone(); let fut = async move { - let _sequence_lock = coin.sequence_lock.lock().await; let current_block = try_tx_s!(coin.current_block().compat().await); let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info = try_tx_s!(coin.my_account_info().await); - - let simulated_tx = try_tx_s!(coin.gen_simulated_tx( - account_info.clone(), - claim_htlc_tx.msg_payload.clone(), - timeout_height, - TX_DEFAULT_MEMO.into(), - )); - - let fee = try_tx_s!(coin.calculate_fee(coin.denom.clone(), simulated_tx).await); - - let tx_raw = try_tx_s!(coin.any_to_signed_raw_tx( - account_info, - claim_htlc_tx.msg_payload, - fee, - timeout_height, - TX_DEFAULT_MEMO.into(), - )); + let fee = try_tx_s!( + coin.calculate_fee( + claim_htlc_tx.msg_payload.clone(), + timeout_height, + TX_DEFAULT_MEMO.into(), + ) + .await + ); - let tx_id = try_tx_s!(coin.send_raw_tx_bytes(&try_tx_s!(tx_raw.to_bytes())).compat().await); + let (tx_id, tx_raw) = try_tx_s!( + coin.seq_safe_send_raw_tx_bytes( + claim_htlc_tx.msg_payload.clone(), + fee.clone(), + timeout_height, + TX_DEFAULT_MEMO.into(), + ) + .await + ); Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { data: tx_raw.into(), @@ -2351,7 +2416,7 @@ pub mod tendermint_coin_tests { fn test_htlc_create_and_claim() { let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; - let protocol_conf = get_iris_usdc_ibc_protocol(); + let protocol_conf = get_iris_protocol(); let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); @@ -2365,7 +2430,7 @@ pub mod tendermint_coin_tests { let coin = block_on(TendermintCoin::init( &ctx, - "USDC-IBC".to_string(), + "IRIS".to_string(), conf, protocol_conf, rpc_urls, @@ -2375,7 +2440,6 @@ pub mod tendermint_coin_tests { .unwrap(); // << BEGIN HTLC CREATION - let base_denom: Denom = "unyan".parse().unwrap(); let to: AccountId = IRIS_TESTNET_HTLC_PAIR2_ADDRESS.parse().unwrap(); const UAMOUNT: u64 = 1; let amount: cosmrs::Decimal = UAMOUNT.into(); @@ -2391,33 +2455,22 @@ pub mod tendermint_coin_tests { let current_block = block_on(async { current_block_fut.await.unwrap() }); let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info_fut = coin.my_account_info(); - let account_info = block_on(async { account_info_fut.await.unwrap() }); - - let simulated_tx = coin - .gen_simulated_tx( - account_info.clone(), + let fee = block_on(async { + coin.calculate_fee( create_htlc_tx.msg_payload.clone(), timeout_height, - TX_DEFAULT_MEMO.into(), - ) - .unwrap(); - - let fee = block_on(async { coin.calculate_fee(base_denom.clone(), simulated_tx).await.unwrap() }); - - let raw_tx = block_on(async { - coin.any_to_signed_raw_tx( - account_info.clone(), - create_htlc_tx.msg_payload.clone(), - fee, - timeout_height, - TX_DEFAULT_MEMO.into(), + TX_DEFAULT_MEMO.to_owned(), ) + .await .unwrap() }); - let tx_bytes = raw_tx.to_bytes().unwrap(); - let send_tx_fut = coin.send_raw_tx_bytes(&tx_bytes).compat(); + let send_tx_fut = coin.seq_safe_send_raw_tx_bytes( + create_htlc_tx.msg_payload.clone(), + fee, + timeout_height, + TX_DEFAULT_MEMO.into(), + ); block_on(async { send_tx_fut.await.unwrap(); }); @@ -2446,36 +2499,22 @@ pub mod tendermint_coin_tests { let current_block = common::block_on(async { current_block_fut.await.unwrap() }); let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let account_info_fut = coin.my_account_info(); - let account_info = block_on(async { account_info_fut.await.unwrap() }); - - let simulated_tx = coin - .gen_simulated_tx( - account_info.clone(), + let fee = block_on(async { + coin.calculate_fee( claim_htlc_tx.msg_payload.clone(), timeout_height, - TX_DEFAULT_MEMO.into(), + TX_DEFAULT_MEMO.to_owned(), ) - .unwrap(); + .await + .unwrap() + }); - let fee = block_on(async { coin.calculate_fee(base_denom.clone(), simulated_tx).await.unwrap() }); + let send_tx_fut = + coin.seq_safe_send_raw_tx_bytes(claim_htlc_tx.msg_payload, fee, timeout_height, TX_DEFAULT_MEMO.into()); - let raw_tx = coin - .any_to_signed_raw_tx( - account_info, - claim_htlc_tx.msg_payload, - fee, - timeout_height, - TX_DEFAULT_MEMO.into(), - ) - .unwrap(); + let (tx_id, _tx_raw) = block_on(async { send_tx_fut.await.unwrap() }); - let tx_bytes = raw_tx.to_bytes().unwrap(); - let send_tx_fut = coin.send_raw_tx_bytes(&tx_bytes).compat(); - block_on(async { - send_tx_fut.await.unwrap(); - }); - println!("Claim HTLC tx hash {}", hex::encode_upper(sha256(&tx_bytes).as_slice())); + println!("Claim HTLC tx hash {}", tx_id); // >> END HTLC CLAIMING } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 3a0b6af9f1..1da2915074 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -515,16 +515,13 @@ impl MmCoin for TendermintToken { .await .map_to_mm(WithdrawError::Transport)?; - let _sequence_lock = platform.sequence_lock.lock().await; let account_info = platform.my_account_info().await?; - let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; - let simulated_tx = platform - .gen_simulated_tx(account_info.clone(), msg_send.clone(), timeout_height, memo.clone()) - .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + let fee_amount_u64 = platform + .calculate_fee_amount_as_u64(msg_send.clone(), timeout_height, memo.clone()) + .await?; - let fee_amount_u64 = platform.calculate_fee_amount_as_u64(simulated_tx).await?; let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, platform.decimals()); if base_denom_balance < fee_amount_u64 { diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs index c6dd0265b6..6375b31e8c 100644 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs @@ -17,8 +17,6 @@ const TBNB_URLS: &[&str] = &["https://data-seed-prebsc-1-s3.binance.org:8545/"]; const TBNB_SWAP_CONTRACT: &str = "0xB1Ad803ea4F57401639c123000C75F5B66E4D123"; #[test] -#[ignore] -// TODO https://github.com/KomodoPlatform/atomicDEX-API/issues/1569 fn start_swap_operation() { let pairs = [ ("USDC-IBC-IRIS", "IRIS-NIMDA"), From 3eaacda6d65ed0576156d4489e062076c08c9da9 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Fri, 3 Mar 2023 18:18:40 +0300 Subject: [PATCH 02/79] add sequence error check in `send_raw_tx_bytes` Signed-off-by: ozkanonur --- mm2src/coins/tendermint/tendermint_coin.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index a8ac4fd6ce..f9f9811af8 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -1819,6 +1819,25 @@ impl MarketCoinOps for TendermintCoin { .broadcast_tx_commit(tx_bytes.into()) .await ); + + if broadcast_res + .check_tx + .log + .to_string() + .contains("incorrect account sequence") + || broadcast_res + .deliver_tx + .log + .to_string() + .contains("incorrect account sequence") + { + return ERR!( + "Wrong account sequence catched. check_tx log: {}, deliver_tx log: {}", + broadcast_res.check_tx.log, + broadcast_res.deliver_tx.log + ); + } + if !broadcast_res.check_tx.code.is_ok() { return ERR!("Tx check failed {:?}", broadcast_res.check_tx); } From 433474618a42b67a3a371d550b309dc88243b7ef Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Mon, 6 Mar 2023 21:42:58 +0300 Subject: [PATCH 03/79] update error handling and `fn platform_denom` Signed-off-by: ozkanonur --- mm2src/coins/tendermint/tendermint_coin.rs | 51 +++++++------------ .../tendermint/tendermint_tx_history_v2.rs | 2 +- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 0cfa0487e9..75129070d9 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -92,9 +92,11 @@ pub(crate) const TX_DEFAULT_MEMO: &str = ""; const MAX_TIME_LOCK: i64 = 34560; const MIN_TIME_LOCK: i64 = 50; +const ACCOUNT_SEQUENCE_ERR: &str = "incorrect account sequence"; + #[async_trait] pub trait TendermintCommons { - fn platform_denom(&self) -> String; + fn platform_denom(&self) -> &Denom; fn set_history_sync_state(&self, new_state: HistorySyncState); @@ -392,7 +394,7 @@ impl From for SearchForSwapTxSpendErr { #[async_trait] impl TendermintCommons for TendermintCoin { - fn platform_denom(&self) -> String { self.denom.to_string() } + fn platform_denom(&self) -> &Denom { &self.denom } fn set_history_sync_state(&self, new_state: HistorySyncState) { *self.history_sync_state.lock().unwrap() = new_state; @@ -616,7 +618,7 @@ impl TendermintCoin { match self.send_raw_tx_bytes(&try_tx_s!(tx_raw.to_bytes())).compat().await { Ok(tx_id) => break (tx_id, tx_raw), Err(e) => { - if e.contains("Wrong account sequence catched") { + if e.contains(ACCOUNT_SEQUENCE_ERR) { debug!("Got wrong account sequence, trying again."); continue; } @@ -654,22 +656,17 @@ impl TendermintCoin { let raw_response = self.rpc_client().await?.perform(request).await?; - if raw_response - .response - .log - .to_string() - .contains("incorrect account sequence") - { + if raw_response.response.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { debug!("Got wrong account sequence, trying again."); continue; } match raw_response.response.code { cosmrs::tendermint::abci::Code::Ok => {}, - cosmrs::tendermint::abci::Code::Err(_) => { + cosmrs::tendermint::abci::Code::Err(ecode) => { return MmError::err(TendermintCoinRpcError::InvalidResponse(format!( - "Could not read gas_info. Invalid Response: {}", - raw_response.response.log + "Could not read gas_info. Error code: {} Message: {}", + ecode, raw_response.response.log ))); }, }; @@ -690,7 +687,7 @@ impl TendermintCoin { let amount = ((gas.gas_used as f64 * 1.5) * self.gas_price()).ceil(); let fee_amount = Coin { - denom: self.platform_denom().parse().expect("Platform denom parse can't fail"), + denom: self.platform_denom().clone(), amount: (amount as u64).into(), }; @@ -721,22 +718,17 @@ impl TendermintCoin { let raw_response = self.rpc_client().await?.perform(request).await?; - if raw_response - .response - .log - .to_string() - .contains("incorrect account sequence") - { + if raw_response.response.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { debug!("Got wrong account sequence, trying again."); continue; } match raw_response.response.code { cosmrs::tendermint::abci::Code::Ok => {}, - cosmrs::tendermint::abci::Code::Err(_) => { + cosmrs::tendermint::abci::Code::Err(ecode) => { return MmError::err(TendermintCoinRpcError::InvalidResponse(format!( - "Could not read gas_info. Invalid Response: {}", - raw_response.response.log + "Could not read gas_info. Error code: {} Message: {}", + ecode, raw_response.response.log ))); }, }; @@ -1820,19 +1812,12 @@ impl MarketCoinOps for TendermintCoin { .await ); - if broadcast_res - .check_tx - .log - .to_string() - .contains("incorrect account sequence") - || broadcast_res - .deliver_tx - .log - .to_string() - .contains("incorrect account sequence") + if broadcast_res.check_tx.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) + || broadcast_res.deliver_tx.log.to_string().contains(ACCOUNT_SEQUENCE_ERR) { return ERR!( - "Wrong account sequence catched. check_tx log: {}, deliver_tx log: {}", + "{}. check_tx log: {}, deliver_tx log: {}", + ACCOUNT_SEQUENCE_ERR, broadcast_res.check_tx.log, broadcast_res.deliver_tx.log ); diff --git a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs index f833594401..fa637e858a 100644 --- a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs +++ b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs @@ -652,7 +652,7 @@ where } let tx_sent_by_me = address == transfer_details.from; - let is_platform_coin_tx = transfer_details.denom == coin.platform_denom(); + let is_platform_coin_tx = transfer_details.denom == coin.platform_denom().to_string(); let is_self_tx = transfer_details.to == transfer_details.from && tx_sent_by_me; let is_sign_claim_htlc = tx_sent_by_me && matches!(transfer_details.transfer_event_type, TransferEventType::ClaimHtlc); From 44c63975dfd5c00d359072532a1c5cf566fb5f0e Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 7 Mar 2023 14:21:43 +0300 Subject: [PATCH 04/79] upgrade crossbeam from vulnerable version Signed-off-by: ozkanonur --- Cargo.lock | 34 +++++++++++++++++++++++++++++----- deny.toml | 2 -- mm2src/coins/Cargo.toml | 2 +- mm2src/common/Cargo.toml | 2 +- mm2src/common/log.rs | 2 +- mm2src/mm2_main/Cargo.toml | 2 +- 6 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7fae16a35..a12c266fd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,7 +458,7 @@ dependencies = [ "bitvec 0.18.5", "blake2s_simd", "byteorder 1.4.3", - "crossbeam", + "crossbeam 0.7.3", "ff 0.8.0", "futures 0.1.29", "futures-cpupool", @@ -1043,7 +1043,7 @@ dependencies = [ "chain", "common", "cosmrs", - "crossbeam", + "crossbeam 0.8.2", "crypto", "db_common", "derive_more", @@ -1180,7 +1180,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "chrono", - "crossbeam", + "crossbeam 0.8.2", "crossterm", "findshlibs", "fnv", @@ -1370,10 +1370,24 @@ dependencies = [ "crossbeam-channel 0.4.4", "crossbeam-deque 0.7.4", "crossbeam-epoch 0.8.2", - "crossbeam-queue", + "crossbeam-queue 0.2.3", "crossbeam-utils 0.7.2", ] +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel 0.5.1", + "crossbeam-deque 0.8.1", + "crossbeam-epoch 0.9.5", + "crossbeam-queue 0.3.8", + "crossbeam-utils 0.8.8", +] + [[package]] name = "crossbeam-channel" version = "0.4.4" @@ -1455,6 +1469,16 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.8", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -4342,7 +4366,7 @@ dependencies = [ "coins_activation", "common", "crc32fast", - "crossbeam", + "crossbeam 0.8.2", "crypto", "db_common", "derive_more", diff --git a/deny.toml b/deny.toml index 0120610616..aab8038f2b 100644 --- a/deny.toml +++ b/deny.toml @@ -225,7 +225,6 @@ skip = [ { name = "getrandom", version = "*" }, { name = "group", version = "*" }, { name = "hashbrown", version = "*" }, - { name = "hdrhistogram", version = "*" }, { name = "hex", version = "*" }, { name = "hmac", version = "*" }, { name = "http", version = "*" }, @@ -288,7 +287,6 @@ skip = [ { name = "tiny-keccak", version = "*" }, { name = "tokio-util", version = "*" }, { name = "trie-root", version = "*" }, - { name = "uint", version = "*" }, { name = "unicode-xid", version = "*" }, { name = "unsigned-varint", version = "*" }, { name = "url", version = "*" }, diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 1d77bb0001..76d9aac3e8 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -30,7 +30,7 @@ cfg-if = "1.0" chain = { path = "../mm2_bitcoin/chain" } common = { path = "../common" } cosmrs = { version = "0.7", default-features = false } -crossbeam = "0.7" +crossbeam = "0.8" crypto = { path = "../crypto" } db_common = { path = "../db_common" } derive_more = "0.99" diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 2327ebd957..81cb1e96bf 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1" backtrace = "0.3" bytes = "1.1" cfg-if = "1.0" -crossbeam = "0.7" +crossbeam = "0.8" fnv = "1.0.6" futures01 = { version = "0.1", package = "futures" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } diff --git a/mm2src/common/log.rs b/mm2src/common/log.rs index 733d0e8f83..700dc0b9dd 100644 --- a/mm2src/common/log.rs +++ b/mm2src/common/log.rs @@ -98,7 +98,7 @@ impl Gravity { fn flush(&self) { let logged_with_log_output = LOG_CALLBACK.lock().is_some(); let mut tail = self.tail.lock(); - while let Ok(chunk) = self.landing.pop() { + while let Some(chunk) = self.landing.pop() { if !logged_with_log_output { writeln(&chunk) } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 5989c999c7..d03104b361 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -33,7 +33,7 @@ coins = { path = "../coins" } coins_activation = { path = "../coins_activation" } common = { path = "../common" } crc32fast = { version = "1.3.2", features = ["std", "nightly"] } -crossbeam = "0.7" +crossbeam = "0.8" crypto = { path = "../crypto" } db_common = { path = "../db_common" } derive_more = "0.99" From f8bf71e9069ec343d0feb1a01fb27812e9efdbea Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 7 Mar 2023 17:10:13 +0300 Subject: [PATCH 05/79] disable solana Signed-off-by: ozkanonur --- mm2src/coins/Cargo.toml | 15 ++- mm2src/coins/lp_coins.rs | 101 ++++++++++++++---- mm2src/coins/solana.rs | 3 - mm2src/coins_activation/Cargo.toml | 4 + mm2src/coins_activation/src/lib.rs | 14 ++- mm2src/mm2_main/Cargo.toml | 5 +- mm2src/mm2_main/src/lp_ordermatch.rs | 3 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 11 +- mm2src/mm2_main/tests/docker_tests/mod.rs | 2 +- 9 files changed, 119 insertions(+), 39 deletions(-) diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 76d9aac3e8..d80c87ac01 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -6,9 +6,8 @@ edition = "2018" [features] zhtlc-native-tests = [] # TODO -# Remove this once the solana integration becomes stable/completed. -disable-solana-tests = [] -default = ["disable-solana-tests"] +enable-solana = ["dep:solana-client", "dep:solana-sdk", "dep:solana-transaction-status", "dep:spl-token", "dep:spl-associated-token-account"] +default = [] [lib] name = "coins" @@ -95,11 +94,11 @@ web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", d zbase32 = "0.1.2" [target.'cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))'.dependencies] -solana-client = { version = "1", default-features = false } -solana-sdk = { version = "1", default-features = false } -solana-transaction-status = "1" -spl-token = { version = "3" } -spl-associated-token-account = "1" +solana-client = { version = "1", default-features = false, optional = true } +solana-sdk = { version = "1", default-features = false, optional = true } +solana-transaction-status = { version = "1", optional = true } +spl-token = { version = "3", optional = true } +spl-associated-token-account = { version = "1", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = { version = "0.3.27" } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 265b3e69ea..3bb0ce5c51 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -236,11 +236,26 @@ pub mod tx_history_storage; #[doc(hidden)] #[allow(unused_variables)] -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] pub mod solana; -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] pub use solana::spl::SplToken; -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] pub use solana::{SolanaActivationParams, SolanaCoin, SolanaFeeDetails}; pub mod utxo; @@ -1085,7 +1100,12 @@ pub enum TxFeeDetails { Qrc20(Qrc20FeeDetails), Slp(SlpFeeDetails), Tendermint(TendermintFeeDetails), - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] Solana(SolanaFeeDetails), } @@ -1101,7 +1121,12 @@ impl<'de> Deserialize<'de> for TxFeeDetails { Utxo(UtxoFeeDetails), Eth(EthTxFeeDetails), Qrc20(Qrc20FeeDetails), - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] Solana(SolanaFeeDetails), Tendermint(TendermintFeeDetails), } @@ -1110,7 +1135,12 @@ impl<'de> Deserialize<'de> for TxFeeDetails { TxFeeDetailsUnTagged::Utxo(f) => Ok(TxFeeDetails::Utxo(f)), TxFeeDetailsUnTagged::Eth(f) => Ok(TxFeeDetails::Eth(f)), TxFeeDetailsUnTagged::Qrc20(f) => Ok(TxFeeDetails::Qrc20(f)), - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] TxFeeDetailsUnTagged::Solana(f) => Ok(TxFeeDetails::Solana(f)), TxFeeDetailsUnTagged::Tendermint(f) => Ok(TxFeeDetails::Tendermint(f)), } @@ -1129,7 +1159,12 @@ impl From for TxFeeDetails { fn from(qrc20_details: Qrc20FeeDetails) -> Self { TxFeeDetails::Qrc20(qrc20_details) } } -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] impl From for TxFeeDetails { fn from(solana_details: SolanaFeeDetails) -> Self { TxFeeDetails::Solana(solana_details) } } @@ -2111,9 +2146,19 @@ pub enum MmCoinEnum { SlpToken(SlpToken), Tendermint(TendermintCoin), TendermintToken(TendermintToken), - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] SolanaCoin(SolanaCoin), - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] SplToken(SplToken), #[cfg(not(target_arch = "wasm32"))] LightningCoin(LightningCoin), @@ -2132,12 +2177,22 @@ impl From for MmCoinEnum { fn from(c: TestCoin) -> MmCoinEnum { MmCoinEnum::Test(c) } } -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] impl From for MmCoinEnum { fn from(c: SolanaCoin) -> MmCoinEnum { MmCoinEnum::SolanaCoin(c) } } -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] impl From for MmCoinEnum { fn from(c: SplToken) -> MmCoinEnum { MmCoinEnum::SplToken(c) } } @@ -2194,9 +2249,19 @@ impl Deref for MmCoinEnum { #[cfg(not(target_arch = "wasm32"))] MmCoinEnum::ZCoin(ref c) => c, MmCoinEnum::Test(ref c) => c, - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] MmCoinEnum::SolanaCoin(ref c) => c, - #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] + #[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") + ))] MmCoinEnum::SplToken(ref c) => c, } } @@ -2509,9 +2574,9 @@ pub enum CoinProtocol { network: BlockchainNetwork, confirmation_targets: PlatformCoinConfirmationTargets, }, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] SOLANA, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] SPLTOKEN { platform: String, token_contract_address: String, @@ -2797,11 +2862,11 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result return ERR!("ZHTLC protocol is not supported by lp_coininit"), #[cfg(not(target_arch = "wasm32"))] CoinProtocol::LIGHTNING { .. } => return ERR!("Lightning protocol is not supported by lp_coininit"), - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinProtocol::SOLANA => { return ERR!("Solana protocol is not supported by lp_coininit - use enable_solana_with_tokens instead") }, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinProtocol::SPLTOKEN { .. } => { return ERR!("SplToken protocol is not supported by lp_coininit - use enable_spl instead") }, @@ -3367,7 +3432,7 @@ pub fn address_by_coin_conf_and_pubkey_str( CoinProtocol::LIGHTNING { .. } => { ERR!("address_by_coin_conf_and_pubkey_str is not implemented for lightning protocol yet!") }, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinProtocol::SOLANA | CoinProtocol::SPLTOKEN { .. } => { ERR!("Solana pubkey is the public address - you do not need to use this rpc call.") }, diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index cd92a1c30f..83f4b1817c 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -47,11 +47,8 @@ pub mod solana_common; mod solana_decode_tx_helpers; pub mod spl; -#[cfg(all(test, not(feature = "disable-solana-tests")))] mod solana_common_tests; -#[cfg(all(test, not(feature = "disable-solana-tests")))] mod solana_tests; -#[cfg(all(test, not(feature = "disable-solana-tests")))] mod spl_tests; pub const SOLANA_DEFAULT_DECIMALS: u64 = 9; diff --git a/mm2src/coins_activation/Cargo.toml b/mm2src/coins_activation/Cargo.toml index 7506494822..aaaca5d0bb 100644 --- a/mm2src/coins_activation/Cargo.toml +++ b/mm2src/coins_activation/Cargo.toml @@ -6,6 +6,10 @@ edition = "2018" [lib] doctest = false +[features] +enable-solana = [] +default = [] + [dependencies] async-trait = "0.1" coins = { path = "../coins" } diff --git a/mm2src/coins_activation/src/lib.rs b/mm2src/coins_activation/src/lib.rs index 5f4245af6c..34bd11e902 100644 --- a/mm2src/coins_activation/src/lib.rs +++ b/mm2src/coins_activation/src/lib.rs @@ -7,9 +7,19 @@ mod l2; mod platform_coin_with_tokens; mod prelude; mod slp_token_activation; -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] mod solana_with_tokens_activation; -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] mod spl_token_activation; mod standalone_coin; mod tendermint_token_activation; diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index d03104b361..1d318b9797 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -17,9 +17,8 @@ native = [] # Deprecated track-ctx-pointer = ["common/track-ctx-pointer"] zhtlc-native-tests = ["coins/zhtlc-native-tests"] # TODO -# Remove this once the solana integration becomes stable/completed. -disable-solana-tests = [] -default = ["disable-solana-tests"] +enable-solana = [] +default = [] [dependencies] async-std = { version = "1.5", features = ["unstable"] } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 7fa4cb69d0..beda0108ff 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -5625,6 +5625,7 @@ pub enum OrderbookAddress { #[derive(Debug, Display)] enum OrderbookAddrErr { AddrFromPubkeyError(String), + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinIsNotSupported(String), DeserializationError(json::Error), InvalidPlatformCoinProtocol(String), @@ -5696,7 +5697,7 @@ fn orderbook_address( ))), } }, - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinProtocol::SOLANA | CoinProtocol::SPLTOKEN { .. } => { MmError::err(OrderbookAddrErr::CoinIsNotSupported(coin.to_owned())) }, diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index a553ff9c51..653130f367 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -30,7 +30,12 @@ use coins::utxo::slp::SlpToken; use coins::utxo::utxo_standard::UtxoStandardCoin; use coins::{add_delegation, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, verify_message, withdraw}; -#[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] +#[cfg(all( + feature = "enable-solana", + not(target_os = "ios"), + not(target_os = "android"), + not(target_arch = "wasm32") +))] use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_standalone_coin, enable_platform_coin_with_tokens, enable_token, init_l2, init_l2_status, init_l2_user_action, init_standalone_coin, @@ -183,11 +188,11 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, withdraw).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { - #[cfg(all(not(target_os = "ios"), not(target_os = "android")))] + #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] "enable_solana_with_tokens" => { handle_mmrpc(ctx, request, enable_platform_coin_with_tokens::).await }, - #[cfg(all(not(target_os = "ios"), not(target_os = "android")))] + #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] "enable_spl" => handle_mmrpc(ctx, request, enable_token::).await, "z_coin_tx_history" => handle_mmrpc(ctx, request, coins::my_tx_history_v2::z_coin_tx_history_rpc).await, _ => MmError::err(DispatcherError::NoSuchMethod), diff --git a/mm2src/mm2_main/tests/docker_tests/mod.rs b/mm2src/mm2_main/tests/docker_tests/mod.rs index 837d09fcd0..300ebbc63c 100644 --- a/mm2src/mm2_main/tests/docker_tests/mod.rs +++ b/mm2src/mm2_main/tests/docker_tests/mod.rs @@ -8,7 +8,7 @@ mod swap_watcher_tests; mod swaps_confs_settings_sync_tests; mod swaps_file_lock_tests; -#[cfg(not(feature = "disable-solana-tests"))] mod solana_tests; +#[cfg(feature = "enable-solana")] mod solana_tests; // dummy test helping IDE to recognize this as test module #[test] From ce99a1c616e9b9a9f8042a3760ed034faf1cb886 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 7 Mar 2023 17:39:10 +0300 Subject: [PATCH 06/79] save dev state Signed-off-by: ozkanonur --- Cargo.lock | 56 +++++++++++++++++++++++++------------ deny.toml | 5 +--- mm2src/coins/Cargo.toml | 16 ++++++++--- mm2src/floodsub/Cargo.toml | 2 +- mm2src/gossipsub/Cargo.toml | 2 +- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a12c266fd2..2f1fe896be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3207,6 +3207,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "io-lifetimes" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "iovec" version = "0.1.4" @@ -3399,9 +3409,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.126" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" @@ -3907,6 +3917,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.6" @@ -5344,9 +5360,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65a1118354442de7feb8a2a76f3d80ef01426bd45542c8c1fdffca41a758f846" +checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" dependencies = [ "bytes 1.1.0", "cfg-if 1.0.0", @@ -5854,15 +5870,6 @@ version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "reqwest" version = "0.11.9" @@ -6101,6 +6108,20 @@ dependencies = [ "semver 1.0.6", ] +[[package]] +name = "rustix" +version = "0.36.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "rustls" version = "0.19.1" @@ -7796,16 +7817,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" dependencies = [ "cfg-if 1.0.0", "fastrand", - "libc", "redox_syscall 0.2.10", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys 0.42.0", ] [[package]] diff --git a/deny.toml b/deny.toml index aab8038f2b..d8475f692b 100644 --- a/deny.toml +++ b/deny.toml @@ -61,12 +61,9 @@ ignore = [ "RUSTSEC-2021-0145", "RUSTSEC-2020-0056", "RUSTSEC-2022-0080", - "RUSTSEC-2020-0036", - "RUSTSEC-2021-0139", "RUSTSEC-2021-0059", "RUSTSEC-2021-0060", - "RUSTSEC-2022-0090", - "RUSTSEC-2023-0018", + "RUSTSEC-2022-0090" ] # Threshold for security vulnerabilities, any vulnerability with a CVSS score # lower than the range specified will be ignored. Note that ignored advisories diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index d80c87ac01..648d4b3d7d 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -6,7 +6,15 @@ edition = "2018" [features] zhtlc-native-tests = [] # TODO -enable-solana = ["dep:solana-client", "dep:solana-sdk", "dep:solana-transaction-status", "dep:spl-token", "dep:spl-associated-token-account"] +enable-solana = [ + "dep:bincode", + "dep:ed25519-dalek-bip32", + "dep:solana-client", + "dep:solana-sdk", + "dep:solana-transaction-status", + "dep:spl-token", + "dep:spl-associated-token-account" +] default = [] [lib] @@ -22,7 +30,6 @@ base58 = "0.2.0" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } bitcoin_hashes = "0.10.0" bitcrypto = { path = "../mm2_bitcoin/crypto" } -bincode = "1.3.3" byteorder = "1.3" bytes = "0.4" cfg-if = "1.0" @@ -34,7 +41,6 @@ crypto = { path = "../crypto" } db_common = { path = "../db_common" } derive_more = "0.99" ed25519-dalek = "1.0.1" -ed25519-dalek-bip32 = "0.2.0" enum_from = { path = "../derives/enum_from" } ethabi = { version = "17.0.0" } ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git" } @@ -94,6 +100,8 @@ web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", d zbase32 = "0.1.2" [target.'cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))'.dependencies] +bincode = { version = "1.3.3", default-features = false, optional = true } +ed25519-dalek-bip32 = { version = "0.2.0", default-features = false, optional = true } solana-client = { version = "1", default-features = false, optional = true } solana-sdk = { version = "1", default-features = false, optional = true } solana-transaction-status = { version = "1", optional = true } @@ -142,5 +150,5 @@ winapi = "0.3" mm2_test_helpers = { path = "../mm2_test_helpers" } [build-dependencies] -prost-build = { version = "0.10.3", default-features = false } +prost-build = { version = "0.10.4", default-features = false } tonic-build = { version = "0.7", features = ["prost", "compression"] } diff --git a/mm2src/floodsub/Cargo.toml b/mm2src/floodsub/Cargo.toml index 614cdd9b30..cb385420d8 100644 --- a/mm2src/floodsub/Cargo.toml +++ b/mm2src/floodsub/Cargo.toml @@ -22,4 +22,4 @@ rand = "0.7" smallvec = "1.0" [build-dependencies] -prost-build = "0.10.3" +prost-build = "0.10.4" diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml index 7df4785fc1..8354f4b0e9 100644 --- a/mm2src/gossipsub/Cargo.toml +++ b/mm2src/gossipsub/Cargo.toml @@ -38,4 +38,4 @@ libp2p-yamux = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45. quickcheck = "0.9.2" [build-dependencies] -prost-build = "0.10.3" +prost-build = "0.10.4" From 6b721e0ce246444536875cf47de7ce8acebfe00d Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 7 Mar 2023 17:50:31 +0300 Subject: [PATCH 07/79] save dev state Signed-off-by: ozkanonur --- deny.toml | 3 --- mm2src/floodsub/Cargo.toml | 2 +- mm2src/gossipsub/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/deny.toml b/deny.toml index d8475f692b..a58cbd21e0 100644 --- a/deny.toml +++ b/deny.toml @@ -50,9 +50,6 @@ notice = "warn" # RUSTSEC-2020-0071 is related to time crate, which is used only by chrono in our deps tree, remove when https://github.com/chronotope/chrono/issues/700 is resolved # RUSTSEC-2022-0040 is related to owning-ref, which seems unmaintained. We need to find a way to get rid of it. https://github.com/KomodoPlatform/atomicDEX-API/issues/1429 -# RUSTSEC-2022-0055 is axum/axum-core vulnerability, which seems to be related only to server-side, which we don't utilize. -# RUSTSEC-2023-0001 is tokio Windows-specific bug in the code that we don't use -# Unignore RUSTSEC-2022-0084 after updating libp2p ignore = [ "RUSTSEC-2020-0071", "RUSTSEC-2022-0040", diff --git a/mm2src/floodsub/Cargo.toml b/mm2src/floodsub/Cargo.toml index cb385420d8..b54b60448c 100644 --- a/mm2src/floodsub/Cargo.toml +++ b/mm2src/floodsub/Cargo.toml @@ -22,4 +22,4 @@ rand = "0.7" smallvec = "1.0" [build-dependencies] -prost-build = "0.10.4" +prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml index 8354f4b0e9..ad7ddbc03e 100644 --- a/mm2src/gossipsub/Cargo.toml +++ b/mm2src/gossipsub/Cargo.toml @@ -38,4 +38,4 @@ libp2p-yamux = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45. quickcheck = "0.9.2" [build-dependencies] -prost-build = "0.10.4" +prost-build = { version = "0.10.4", default-features = false } From 49e620e350ee0e8684561145c5ceff94e038c79b Mon Sep 17 00:00:00 2001 From: Alina Sharon <52405288+laruh@users.noreply.github.com> Date: Tue, 7 Mar 2023 22:07:07 +0700 Subject: [PATCH 08/79] feat: NFT integration poc (#1652) * wip * wip * wip some structures were added * wip * wip * wip * fn get_my_address was moved into lp_coins.rs * wip get_my_address * get_my_address works * send_moralis_request, errors, get_nft_list wip * add targets for send_moralis_request * wip * wip * wip use fn slurp_req_body in fn send_moralis_request * wip fix wasm * wip cursor in get_nft_list * wip impl Deserialize for Wrap * get_nft_list * remove unnecessary notes * wip get_nft_transfers * get_nft_transfers works * polish code * polish code * remove Option from some fields in Nft struct * wip get_nft_metadata * use NftWrapper in fn get_nft_metadata, add some doc comments * remove allow(dead_code) * change order in Chain enum * fix doc comment * beautify json * add from in withdraw requests * String::new(), serde UPPERCASE, pub(crate) SerdeStringWrap,line break * use ok_or_else, remove if cursor is null, remove !nfts_list.is_empty() * derive order, simplify match protocol * replace Chain::Bnb with Chain::Bsc * change status code matching, add derive Copy * fn withdraw_erc721 * move nft from eth to coin crate * remove memo * wip * add fn coins_conf_check * add from_stringify * add feature enable-nft-integration, fix coins_conf_check * add doc comment for withdraw_nft * fix coins_conf_check * fix deref which would be done by auto-deref * fix conflicts, add feature log file * fix wasm --------- Reviewed-by: @ozkanonur, @sergeyboyko0791, @borngraced, @shamardy --- .../2023-feb/features/nft_integration_poc | 4 + mm2src/coins/Cargo.toml | 1 + mm2src/coins/eth.rs | 164 ++++++++- mm2src/coins/eth/erc1155_abi.json | 314 ++++++++++++++++ mm2src/coins/eth/erc721_abi.json | 346 ++++++++++++++++++ mm2src/coins/eth/v2_activation.rs | 2 +- mm2src/coins/lp_coins.rs | 148 ++++++-- mm2src/coins/my_tx_history_v2.rs | 3 +- mm2src/coins/nft.rs | 267 ++++++++++++++ mm2src/coins/nft/nft_errors.rs | 68 ++++ mm2src/coins/nft/nft_structs.rs | 231 ++++++++++++ mm2src/common/common.rs | 1 + .../common/shared_ref_counter/src/enable.rs | 2 +- mm2src/mm2_main/Cargo.toml | 1 + .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 16 +- mm2src/mm2_metamask/src/metamask_error.rs | 4 +- mm2src/mm2_net/src/native_http.rs | 22 +- mm2src/mm2_net/src/transport.rs | 9 +- 18 files changed, 1563 insertions(+), 40 deletions(-) create mode 100644 dev-logs/2023-feb/features/nft_integration_poc create mode 100644 mm2src/coins/eth/erc1155_abi.json create mode 100644 mm2src/coins/eth/erc721_abi.json create mode 100644 mm2src/coins/nft.rs create mode 100644 mm2src/coins/nft/nft_errors.rs create mode 100644 mm2src/coins/nft/nft_structs.rs diff --git a/dev-logs/2023-feb/features/nft_integration_poc b/dev-logs/2023-feb/features/nft_integration_poc new file mode 100644 index 0000000000..bfdaff18c6 --- /dev/null +++ b/dev-logs/2023-feb/features/nft_integration_poc @@ -0,0 +1,4 @@ +NFT integration PoC added. Includes ERC721 support for ETH and BSC. + + +author: @laruh \ No newline at end of file diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 1d77bb0001..73d955ff45 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -9,6 +9,7 @@ zhtlc-native-tests = [] # Remove this once the solana integration becomes stable/completed. disable-solana-tests = [] default = ["disable-solana-tests"] +enable-nft-integration = [] [lib] name = "coins" diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6d7ecc942d..11ad6acc27 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -21,6 +21,8 @@ // Copyright © 2022 AtomicDEX. All rights reserved. // use super::eth::Action::{Call, Create}; +#[cfg(feature = "enable-nft-integration")] +use crate::nft::nft_structs::{Chain, ContractType, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry}; @@ -97,7 +99,12 @@ pub use rlp; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; -use v2_activation::build_address_and_priv_key_policy; +#[cfg(feature = "enable-nft-integration")] +use crate::nft::WithdrawNftResult; +use crate::MyWalletAddress; +#[cfg(feature = "enable-nft-integration")] +use crate::{lp_coinfind_or_err, MmCoinEnum, TransactionType}; +use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; mod nonce; use nonce::ParityNonce; @@ -109,6 +116,8 @@ use nonce::ParityNonce; const SWAP_CONTRACT_ABI: &str = include_str!("eth/swap_contract_abi.json"); /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md const ERC20_ABI: &str = include_str!("eth/erc20_abi.json"); +/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md +const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub const PAYMENT_STATE_UNINITIALIZED: u8 = 0; pub const PAYMENT_STATE_SENT: u8 = 1; @@ -147,6 +156,7 @@ const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); + pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); } pub type Web3RpcFut = Box> + Send>; @@ -876,6 +886,124 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } +#[cfg(feature = "enable-nft-integration")] +pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { + let ticker = match req.chain { + Chain::Bsc => "BNB", + Chain::Eth => "ETH", + }; + let _coin = lp_coinfind_or_err(&ctx, ticker).await?; + unimplemented!() +} + +#[cfg(feature = "enable-nft-integration")] +pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { + let ticker = match req.chain { + Chain::Bsc => "BNB", + Chain::Eth => "ETH", + }; + let coin = lp_coinfind_or_err(&ctx, ticker).await?; + let eth_coin = match coin { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(WithdrawError::CoinDoesntSupportNftWithdraw { + coin: coin.ticker().to_owned(), + }) + }, + }; + let from_addr = valid_addr_from_str(&req.from).map_to_mm(WithdrawError::InvalidAddress)?; + if eth_coin.my_address != from_addr { + return MmError::err(WithdrawError::AddressMismatchError { + my_address: eth_coin.my_address.to_string(), + from: req.from, + }); + } + let to_addr = valid_addr_from_str(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; + let token_addr = addr_from_str(&req.token_address).map_to_mm(WithdrawError::InvalidAddress)?; + let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { + EthCoinType::Eth => { + let function = ERC721_CONTRACT.function("safeTransferFrom")?; + let token_id_u256 = U256::from_dec_str(&req.token_id.to_string()) + .map_err(|e| format!("{:?}", e)) + .map_to_mm(NumConversError::new)?; + let data = function.encode_input(&[ + Token::Address(from_addr), + Token::Address(to_addr), + Token::Uint(token_id_u256), + ])?; + (0.into(), data, token_addr, eth_coin.ticker()) + }, + EthCoinType::Erc20 { .. } => { + return MmError::err(WithdrawError::InternalError( + "Erc20 coin type doesnt support withdraw nft".to_owned(), + )) + }, + }; + let (gas, gas_price) = match req.fee { + Some(WithdrawFee::EthGas { gas_price, gas }) => { + let gas_price = wei_from_big_decimal(&gas_price, 9)?; + (gas.into(), gas_price) + }, + Some(fee_policy) => { + let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); + return MmError::err(WithdrawError::InvalidFeePolicy(error)); + }, + None => { + let gas_price = eth_coin.get_gas_price().compat().await?; + let estimate_gas_req = CallRequest { + value: Some(eth_value), + data: Some(data.clone().into()), + from: Some(eth_coin.my_address), + to: Some(call_addr), + gas: None, + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + gas_price: Some(gas_price), + ..CallRequest::default() + }; + // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; + (gas_limit, gas_price) + }, + }; + let _nonce_lock = eth_coin.nonce_lock.lock().await; + let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) + .compat() + .timeout_secs(30.) + .await? + .map_to_mm(WithdrawError::Transport)?; + + let tx = UnSignedEthTx { + nonce, + value: eth_value, + action: Action::Call(call_addr), + data, + gas, + gas_price, + }; + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); + let signed = tx.sign(secret, eth_coin.chain_id); + let signed_bytes = rlp::encode(&signed); + let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + Ok(TransactionNftDetails { + tx_hex: BytesJson::from(signed_bytes.to_vec()), + tx_hash: format!("{:02x}", signed.tx_hash()), + from: vec![req.from], + to: vec![req.to], + contract_type: ContractType::Erc721, + token_address: req.token_address, + token_id: req.token_id, + amount: 1.into(), + fee_details: Some(fee_details.into()), + coin: eth_coin.ticker.clone(), + block_height: 0, + timestamp: now_ms() / 1000, + internal_id: 0, + transaction_type: TransactionType::NftTransfer, + }) +} + #[derive(Clone)] pub struct EthCoin(Arc); impl Deref for EthCoin { @@ -4762,3 +4890,37 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 }, } } + +#[derive(Debug, Deserialize, Serialize, Display)] +pub enum GetEthAddressError { + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + EthActivationV2Error(EthActivationV2Error), + Internal(String), +} + +impl From for GetEthAddressError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { GetEthAddressError::PrivKeyPolicyNotAllowed(e) } +} + +impl From for GetEthAddressError { + fn from(e: EthActivationV2Error) -> Self { GetEthAddressError::EthActivationV2Error(e) } +} + +impl From for GetEthAddressError { + fn from(e: CryptoCtxError) -> Self { GetEthAddressError::Internal(e.to_string()) } +} + +/// `get_eth_address` returns wallet address for coin with `ETH` protocol type. +pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult { + let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?; + // Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible. + let priv_key_policy = EthPrivKeyBuildPolicy::try_from(priv_key_policy)?; + + let (my_address, ..) = build_address_and_priv_key_policy(&ctx.conf, priv_key_policy).await?; + let wallet_address = checksum_address(&format!("{:#02x}", my_address)); + + Ok(MyWalletAddress { + coin: ticker.to_owned(), + wallet_address, + }) +} diff --git a/mm2src/coins/eth/erc1155_abi.json b/mm2src/coins/eth/erc1155_abi.json new file mode 100644 index 0000000000..211a562a85 --- /dev/null +++ b/mm2src/coins/eth/erc1155_abi.json @@ -0,0 +1,314 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/coins/eth/erc721_abi.json b/mm2src/coins/eth/erc721_abi.json new file mode 100644 index 0000000000..20e0fca0b4 --- /dev/null +++ b/mm2src/coins/eth/erc721_abi.json @@ -0,0 +1,346 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 3395cbed70..0fb6a5d02c 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -6,7 +6,7 @@ use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; -#[derive(Display, EnumFromTrait, Serialize, SerializeErrorType)] +#[derive(Debug, Deserialize, Display, EnumFromTrait, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum EthActivationV2Error { InvalidPayload(String), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 265b3e69ea..f4a883afc7 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -200,7 +200,7 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut}; pub mod coins_tests; pub mod eth; -use eth::{eth_coin_from_conf_and_request, EthCoin, EthTxFeeDetails, SignedEthTx}; +use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; pub mod hd_confirm_address; pub mod hd_pubkey; @@ -255,6 +255,8 @@ use utxo::utxo_standard::{utxo_standard_coin_with_policy, UtxoStandardCoin}; use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; +#[cfg(feature = "enable-nft-integration")] pub mod nft; + #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; @@ -331,6 +333,38 @@ impl From for RawTransactionError { } } +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetMyAddressError { + CoinsConfCheckError(String), + CoinIsNotSupported(String), + #[from_stringify("CryptoCtxError")] + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + #[from_stringify("serde_json::Error")] + #[display(fmt = "Invalid request error error: {}", _0)] + InvalidRequest(String), + #[display(fmt = "Get Eth address error: {}", _0)] + GetEthAddressError(GetEthAddressError), +} + +impl From for GetMyAddressError { + fn from(e: GetEthAddressError) -> Self { GetMyAddressError::GetEthAddressError(e) } +} + +impl HttpStatusCode for GetMyAddressError { + fn status_code(&self) -> StatusCode { + match self { + GetMyAddressError::CoinsConfCheckError(_) + | GetMyAddressError::CoinIsNotSupported(_) + | GetMyAddressError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetMyAddressError::Internal(_) | GetMyAddressError::GetEthAddressError(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } +} + #[derive(Deserialize)] pub struct RawTransactionRequest { pub coin: String, @@ -343,6 +377,17 @@ pub struct RawTransactionRes { pub tx_hex: BytesJson, } +#[derive(Debug, Deserialize)] +pub struct MyAddressReq { + coin: String, +} + +#[derive(Debug, Serialize)] +pub struct MyWalletAddress { + coin: String, + wallet_address: String, +} + pub type SignatureResult = Result>; pub type VerificationResult = Result>; @@ -361,7 +406,7 @@ pub enum TxHistoryError { InternalError(String), } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Deserialize)] pub enum PrivKeyPolicyNotAllowed { #[display(fmt = "Hardware Wallet is not supported")] HardwareWalletNotSupported, @@ -1165,6 +1210,7 @@ pub enum TransactionType { msg_type: CustomTendermintMsgType, token_id: Option, }, + NftTransfer, } /// Transaction details @@ -1730,6 +1776,12 @@ pub enum WithdrawError { #[from_stringify("NumConversError", "UnexpectedDerivationMethod", "PrivKeyPolicyNotAllowed")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[display(fmt = "{} coin doesn't support NFT withdrawing", coin)] + CoinDoesntSupportNftWithdraw { coin: String }, + #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] + AddressMismatchError { my_address: String, from: String }, + #[display(fmt = "Contract type {} doesnt support 'withdraw_nft' yet", _0)] + ContractTypeDoesntSupportNftWithdrawing(String), } impl HttpStatusCode for WithdrawError { @@ -1748,7 +1800,10 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::FromAddressNotFound | WithdrawError::UnexpectedFromAddress(_) | WithdrawError::UnknownAccount { .. } - | WithdrawError::UnexpectedUserAction { .. } => StatusCode::BAD_REQUEST, + | WithdrawError::UnexpectedUserAction { .. } + | WithdrawError::CoinDoesntSupportNftWithdraw { .. } + | WithdrawError::AddressMismatchError { .. } + | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, @@ -1831,7 +1886,7 @@ impl WithdrawError { } } -#[derive(Serialize, Display, Debug, EnumFromStringify, SerializeErrorType)] +#[derive(Debug, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum SignatureError { #[display(fmt = "Invalid request: {}", _0)] @@ -1856,7 +1911,7 @@ impl HttpStatusCode for SignatureError { } } -#[derive(Serialize, Display, Debug, SerializeErrorType)] +#[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum VerificationError { #[display(fmt = "Invalid request: {}", _0)] @@ -2478,7 +2533,7 @@ pub trait CoinWithDerivationMethod { } #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "type", content = "protocol_data")] pub enum CoinProtocol { UTXO, @@ -2698,34 +2753,11 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result Result; } + +/// `get_my_address` function returns wallet address for necessary coin without its activation. +/// Currently supports only coins with `ETH` protocol type. +pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult { + let ticker = req.coin.as_str(); + let coins_en = coin_conf(&ctx, ticker); + coins_conf_check(&ctx, &coins_en, ticker, None).map_to_mm(GetMyAddressError::CoinsConfCheckError)?; + + let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; + + let my_address = match protocol { + CoinProtocol::ETH => get_eth_address(&ctx, ticker).await?, + _ => { + return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( + "{} doesn't support get_my_address", + req.coin + ))); + }, + }; + + Ok(my_address) +} + +fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Json>) -> Result<(), String> { + if coins_en.is_null() { + let warning = format!( + "Warning, coin {} is used without a corresponding configuration.", + ticker + ); + ctx.log.log( + "😅", + #[allow(clippy::unnecessary_cast)] + &[&("coin" as &str), &ticker, &("no-conf" as &str)], + &warning, + ); + } + + if let Some(req) = req { + if coins_en["mm2"].is_null() && req["mm2"].is_null() { + return ERR!(concat!( + "mm2 param is not set neither in coins config nor enable request, assuming that coin is not supported" + )); + } + } else if coins_en["mm2"].is_null() { + return ERR!(concat!( + "mm2 param is not set in coins config, assuming that coin is not supported" + )); + } + + if coins_en["protocol"].is_null() { + return ERR!( + r#""protocol" field is missing in coins file. The file format is deprecated, please execute ./mm2 update_config command to convert it or download a new one"# + ); + } + Ok(()) +} diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index 971ab7b19e..1635be9e80 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -233,7 +233,8 @@ impl<'a, Addr: Clone + DisplayAddress + Eq + std::hash::Hash, Tx: Transaction> T TransactionType::StakingDelegation | TransactionType::RemoveDelegation | TransactionType::FeeForTokenTx - | TransactionType::StandardTransfer => tx_hash.clone(), + | TransactionType::StandardTransfer + | TransactionType::NftTransfer => tx_hash.clone(), }; TransactionDetails { diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs new file mode 100644 index 0000000000..0dc9a0aef3 --- /dev/null +++ b/mm2src/coins/nft.rs @@ -0,0 +1,267 @@ +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::{MmError, MmResult}; + +pub(crate) mod nft_errors; +pub(crate) mod nft_structs; + +use crate::WithdrawError; +use nft_errors::GetNftInfoError; +use nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, + NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, WithdrawNftReq}; + +use crate::eth::{get_eth_address, withdraw_erc721}; +use common::{APPLICATION_JSON, X_API_KEY}; +use http::header::ACCEPT; +use serde_json::Value as Json; + +/// url for moralis requests +const URL_MORALIS: &str = "https://deep-index.moralis.io/api/v2/"; +/// query parameter for moralis request: The format of the token ID +const FORMAT_DECIMAL_MORALIS: &str = "format=decimal"; +/// query parameter for moralis request: The transfer direction +const DIRECTION_BOTH_MORALIS: &str = "direction=both"; + +pub type WithdrawNftResult = Result>; + +/// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. +pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; + + let mut res_list = Vec::new(); + + for chain in req.chains { + let (coin_str, chain_str) = match chain { + Chain::Bsc => ("BNB", "bsc"), + Chain::Eth => ("ETH", "eth"), + }; + let my_address = get_eth_address(&ctx, coin_str).await?; + let uri_without_cursor = format!( + "{}{}/nft?chain={}&{}", + URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS + ); + + // The cursor returned in the previous response (used for getting the next page). + let mut cursor = String::new(); + loop { + let uri = format!("{}{}", uri_without_cursor, cursor); + let response = send_moralis_request(uri.as_str(), api_key).await?; + if let Some(nfts_list) = response["result"].as_array() { + for nft_json in nfts_list { + let nft_wrapper: NftWrapper = serde_json::from_str(&nft_json.to_string())?; + let nft = Nft { + chain, + token_address: nft_wrapper.token_address, + token_id: nft_wrapper.token_id.0, + amount: nft_wrapper.amount.0, + owner_of: nft_wrapper.owner_of, + token_hash: nft_wrapper.token_hash, + block_number_minted: *nft_wrapper.block_number_minted, + block_number: *nft_wrapper.block_number, + contract_type: nft_wrapper.contract_type.map(|v| v.0), + name: nft_wrapper.name, + symbol: nft_wrapper.symbol, + token_uri: nft_wrapper.token_uri, + metadata: nft_wrapper.metadata, + last_token_uri_sync: nft_wrapper.last_token_uri_sync, + last_metadata_sync: nft_wrapper.last_metadata_sync, + minter_address: nft_wrapper.minter_address, + }; + // collect NFTs from the page + res_list.push(nft); + } + // if cursor is not null, there are other NFTs on next page, + // and we need to send new request with cursor to get info from the next page. + if let Some(cursor_res) = response["cursor"].as_str() { + cursor = format!("{}{}", "&cursor=", cursor_res); + continue; + } else { + break; + } + } + } + } + drop_mutability!(res_list); + let nft_list = NftList { + count: res_list.len() as u64, + nfts: res_list, + }; + Ok(nft_list) +} + +/// `get_nft_metadata` function returns info of one specific NFT. +/// Current implementation sends request to Moralis. +/// Later, after adding caching, metadata lookup can be performed using previously obtained NFTs info without +/// sending new moralis request. The moralis request can be sent as a fallback, if the data was not found in the cache. +pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult { + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; + let chain_str = match req.chain { + Chain::Bsc => "bsc", + Chain::Eth => "eth", + }; + let uri = format!( + "{}nft/{}/{}?chain={}&{}", + URL_MORALIS, req.token_address, req.token_id, chain_str, FORMAT_DECIMAL_MORALIS + ); + let response = send_moralis_request(uri.as_str(), api_key).await?; + let nft_wrapper: NftWrapper = serde_json::from_str(&response.to_string())?; + let nft_metadata = Nft { + chain: req.chain, + token_address: nft_wrapper.token_address, + token_id: nft_wrapper.token_id.0, + amount: nft_wrapper.amount.0, + owner_of: nft_wrapper.owner_of, + token_hash: nft_wrapper.token_hash, + block_number_minted: *nft_wrapper.block_number_minted, + block_number: *nft_wrapper.block_number, + contract_type: nft_wrapper.contract_type.map(|v| v.0), + name: nft_wrapper.name, + symbol: nft_wrapper.symbol, + token_uri: nft_wrapper.token_uri, + metadata: nft_wrapper.metadata, + last_token_uri_sync: nft_wrapper.last_token_uri_sync, + last_metadata_sync: nft_wrapper.last_metadata_sync, + minter_address: nft_wrapper.minter_address, + }; + Ok(nft_metadata) +} + +/// `get_nft_transfers` function returns a transfer history of NFTs on ETH or/and BNb chains owned by user. +/// Currently doesnt support filters. +pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult { + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; + + let mut res_list = Vec::new(); + + for chain in req.chains { + let (coin_str, chain_str) = match chain { + Chain::Bsc => ("BNB", "bsc"), + Chain::Eth => ("ETH", "eth"), + }; + let my_address = get_eth_address(&ctx, coin_str).await?; + let uri_without_cursor = format!( + "{}{}/nft/transfers?chain={}&{}&{}", + URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS, DIRECTION_BOTH_MORALIS + ); + + // The cursor returned in the previous response (used for getting the next page). + let mut cursor = String::new(); + loop { + let uri = format!("{}{}", uri_without_cursor, cursor); + let response = send_moralis_request(uri.as_str(), api_key).await?; + if let Some(transfer_list) = response["result"].as_array() { + for transfer in transfer_list { + let transfer_wrapper: NftTransferHistoryWrapper = serde_json::from_str(&transfer.to_string())?; + let transfer_history = NftTransferHistory { + chain, + block_number: *transfer_wrapper.block_number, + block_timestamp: transfer_wrapper.block_timestamp, + block_hash: transfer_wrapper.block_hash, + transaction_hash: transfer_wrapper.transaction_hash, + transaction_index: transfer_wrapper.transaction_index, + log_index: transfer_wrapper.log_index, + value: transfer_wrapper.value.0, + contract_type: transfer_wrapper.contract_type.0, + transaction_type: transfer_wrapper.transaction_type, + token_address: transfer_wrapper.token_address, + token_id: transfer_wrapper.token_id.0, + from_address: transfer_wrapper.from_address, + to_address: transfer_wrapper.to_address, + amount: transfer_wrapper.amount.0, + verified: transfer_wrapper.verified, + operator: transfer_wrapper.operator, + }; + // collect NFTs transfers from the page + res_list.push(transfer_history); + } + // if the cursor is not null, there are other NFTs transfers on next page, + // and we need to send new request with cursor to get info from the next page. + if let Some(cursor_res) = response["cursor"].as_str() { + cursor = format!("{}{}", "&cursor=", cursor_res); + continue; + } else { + break; + } + } + } + } + drop_mutability!(res_list); + let transfer_history_list = NftsTransferHistoryList { + count: res_list.len() as u64, + transfer_history: res_list, + }; + Ok(transfer_history_list) +} + +/// `withdraw_nft` function generates, signs and returns a transaction that transfers NFT +/// from my address to recipient's address. +/// This method generates a raw transaction which should then be broadcast using `send_raw_transaction`. +/// Currently support ERC721 withdrawing, ERC1155 support will be added later. +pub async fn withdraw_nft(ctx: MmArc, req_type: WithdrawNftReq) -> WithdrawNftResult { + match req_type { + WithdrawNftReq::WithdrawErc1155(_) => MmError::err(WithdrawError::ContractTypeDoesntSupportNftWithdrawing( + "ERC1155".to_owned(), + )), + WithdrawNftReq::WithdrawErc721(erc721_req) => withdraw_erc721(ctx, erc721_req).await, + } +} + +#[cfg(not(target_arch = "wasm32"))] +async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { + use http::header::HeaderValue; + use mm2_net::transport::slurp_req_body; + + let request = http::Request::builder() + .method("GET") + .uri(uri) + .header(X_API_KEY, api_key) + .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(GetNftInfoError::Transport(format!( + "Response !200 from {}: {}, {}", + uri, status, body + )))); + } + Ok(body) +} + +#[cfg(target_arch = "wasm32")] +async fn send_moralis_request(uri: &str, api_key: &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(GetNftInfoError::$errtype(ERRL!("{:?}", e)))), + } + }; + } + + let result = FetchRequest::get(uri) + .cors() + .body_utf8("".to_owned()) + .header(X_API_KEY, api_key) + .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(GetNftInfoError::Transport(ERRL!( + "!200: {}, {}", + status_code, + response_str + )))); + } + + let response: Json = try_or!(serde_json::from_str(&response_str), InvalidResponse); + Ok(response) +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs new file mode 100644 index 0000000000..d48753266b --- /dev/null +++ b/mm2src/coins/nft/nft_errors.rs @@ -0,0 +1,68 @@ +use crate::eth::GetEthAddressError; +use common::HttpStatusCode; +use derive_more::Display; +use enum_from::EnumFromStringify; +use http::StatusCode; +use mm2_net::transport::SlurpError; +use serde::{Deserialize, Serialize}; +use web3::Error; + +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetNftInfoError { + /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. + #[from_stringify("http::Error")] + #[display(fmt = "Invalid request: {}", _0)] + InvalidRequest(String), + #[display(fmt = "Transport: {}", _0)] + Transport(String), + #[from_stringify("serde_json::Error")] + #[display(fmt = "Invalid response: {}", _0)] + InvalidResponse(String), + #[display(fmt = "Internal: {}", _0)] + Internal(String), + GetEthAddressError(GetEthAddressError), + #[display(fmt = "X-API-Key is missing")] + ApiKeyError, +} + +impl From for GetNftInfoError { + fn from(e: SlurpError) -> Self { + let error_str = e.to_string(); + match e { + SlurpError::ErrorDeserializing { .. } => GetNftInfoError::InvalidResponse(error_str), + SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetNftInfoError::Transport(error_str), + SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GetNftInfoError::Internal(error_str), + } + } +} + +impl From for GetNftInfoError { + fn from(e: Error) -> Self { + let error_str = e.to_string(); + match e { + web3::Error::InvalidResponse(_) | web3::Error::Decoder(_) | web3::Error::Rpc(_) => { + GetNftInfoError::InvalidResponse(error_str) + }, + web3::Error::Transport(_) | web3::Error::Io(_) => GetNftInfoError::Transport(error_str), + _ => GetNftInfoError::Internal(error_str), + } + } +} + +impl From for GetNftInfoError { + fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) } +} + +impl HttpStatusCode for GetNftInfoError { + fn status_code(&self) -> StatusCode { + match self { + GetNftInfoError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetNftInfoError::InvalidResponse(_) => StatusCode::FAILED_DEPENDENCY, + GetNftInfoError::ApiKeyError => StatusCode::FORBIDDEN, + GetNftInfoError::Transport(_) | GetNftInfoError::Internal(_) | GetNftInfoError::GetEthAddressError(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } +} diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs new file mode 100644 index 0000000000..efe89cfab4 --- /dev/null +++ b/mm2src/coins/nft/nft_structs.rs @@ -0,0 +1,231 @@ +use crate::{TransactionType, TxFeeDetails, WithdrawFee}; +use mm2_number::BigDecimal; +use rpc::v1::types::Bytes as BytesJson; +use serde::Deserialize; +use std::str::FromStr; + +#[derive(Debug, Deserialize)] +pub struct NftListReq { + pub(crate) chains: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct NftMetadataReq { + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) chain: Chain, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub(crate) enum Chain { + Bsc, + Eth, +} + +#[derive(Debug, Display)] +pub(crate) enum ParseContractTypeError { + UnsupportedContractType, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] +pub(crate) enum ContractType { + Erc1155, + Erc721, +} + +impl FromStr for ContractType { + type Err = ParseContractTypeError; + + #[inline] + fn from_str(s: &str) -> Result { + match s { + "ERC1155" => Ok(ContractType::Erc1155), + "ERC721" => Ok(ContractType::Erc721), + _ => Err(ParseContractTypeError::UnsupportedContractType), + } + } +} + +#[derive(Debug, Serialize)] +pub struct Nft { + pub(crate) chain: Chain, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: BigDecimal, + pub(crate) owner_of: String, + pub(crate) token_hash: String, + pub(crate) block_number_minted: u64, + pub(crate) block_number: u64, + pub(crate) contract_type: Option, + pub(crate) name: Option, + pub(crate) symbol: Option, + pub(crate) token_uri: Option, + pub(crate) metadata: Option, + pub(crate) last_token_uri_sync: Option, + pub(crate) last_metadata_sync: Option, + pub(crate) minter_address: Option, +} + +/// This structure is for deserializing NFT json to struct. +/// Its needed to convert fields properly, because all fields in json have string type. +#[derive(Debug, Deserialize)] +pub(crate) struct NftWrapper { + pub(crate) token_address: String, + pub(crate) token_id: SerdeStringWrap, + pub(crate) amount: SerdeStringWrap, + pub(crate) owner_of: String, + pub(crate) token_hash: String, + pub(crate) block_number_minted: SerdeStringWrap, + pub(crate) block_number: SerdeStringWrap, + pub(crate) contract_type: Option>, + pub(crate) name: Option, + pub(crate) symbol: Option, + pub(crate) token_uri: Option, + pub(crate) metadata: Option, + pub(crate) last_token_uri_sync: Option, + pub(crate) last_metadata_sync: Option, + pub(crate) minter_address: Option, +} + +#[derive(Debug)] +pub(crate) struct SerdeStringWrap(pub(crate) T); + +impl<'de, T> Deserialize<'de> for SerdeStringWrap +where + T: std::str::FromStr, + T::Err: std::fmt::Debug + std::fmt::Display, +{ + fn deserialize>(deserializer: D) -> Result { + let value: &str = Deserialize::deserialize(deserializer)?; + let value: T = match value.parse() { + Ok(v) => v, + Err(e) => return Err(::custom(e)), + }; + Ok(SerdeStringWrap(value)) + } +} + +impl std::ops::Deref for SerdeStringWrap { + type Target = T; + fn deref(&self) -> &T { &self.0 } +} + +#[derive(Debug, Serialize)] +pub struct NftList { + pub(crate) count: u64, + pub(crate) nfts: Vec, +} + +#[allow(dead_code)] +#[derive(Clone, Deserialize)] +pub struct WithdrawErc1155 { + pub(crate) chain: Chain, + from: String, + to: String, + token_address: String, + token_id: BigDecimal, + amount: BigDecimal, + #[serde(default)] + max: bool, + fee: Option, +} + +#[derive(Clone, Deserialize)] +pub struct WithdrawErc721 { + pub(crate) chain: Chain, + pub(crate) from: String, + pub(crate) to: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) fee: Option, +} + +#[derive(Clone, Deserialize)] +#[serde(tag = "type", content = "withdraw_data")] +pub enum WithdrawNftReq { + WithdrawErc1155(WithdrawErc1155), + WithdrawErc721(WithdrawErc721), +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct TransactionNftDetails { + /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction + pub(crate) tx_hex: BytesJson, + pub(crate) tx_hash: String, + /// NFTs are sent from these addresses + pub(crate) from: Vec, + /// NFTs are sent to these addresses + pub(crate) to: Vec, + pub(crate) contract_type: ContractType, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: BigDecimal, + pub(crate) fee_details: Option, + /// The coin transaction belongs to + pub(crate) coin: String, + /// Block height + pub(crate) block_height: u64, + /// Transaction timestamp + pub(crate) timestamp: u64, + /// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash + pub(crate) internal_id: i64, + /// Type of transactions, default is StandardTransfer + #[serde(default)] + pub(crate) transaction_type: TransactionType, +} + +#[derive(Debug, Deserialize)] +pub struct NftTransfersReq { + pub(crate) chains: Vec, +} + +#[derive(Debug, Serialize)] +pub(crate) struct NftTransferHistory { + pub(crate) chain: Chain, + pub(crate) block_number: u64, + pub(crate) block_timestamp: String, + pub(crate) block_hash: String, + /// Transaction hash in hexadecimal format + pub(crate) transaction_hash: String, + pub(crate) transaction_index: u64, + pub(crate) log_index: u64, + pub(crate) value: BigDecimal, + pub(crate) contract_type: ContractType, + pub(crate) transaction_type: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) from_address: String, + pub(crate) to_address: String, + pub(crate) amount: BigDecimal, + pub(crate) verified: u64, + pub(crate) operator: Option, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct NftTransferHistoryWrapper { + pub(crate) block_number: SerdeStringWrap, + pub(crate) block_timestamp: String, + pub(crate) block_hash: String, + /// Transaction hash in hexadecimal format + pub(crate) transaction_hash: String, + pub(crate) transaction_index: u64, + pub(crate) log_index: u64, + pub(crate) value: SerdeStringWrap, + pub(crate) contract_type: SerdeStringWrap, + pub(crate) transaction_type: String, + pub(crate) token_address: String, + pub(crate) token_id: SerdeStringWrap, + pub(crate) from_address: String, + pub(crate) to_address: String, + pub(crate) amount: SerdeStringWrap, + pub(crate) verified: u64, + pub(crate) operator: Option, +} + +#[derive(Debug, Serialize)] +pub struct NftsTransferHistoryList { + pub(crate) count: u64, + pub(crate) transfer_history: Vec, +} diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 5a8edbd7da..e2f638c02f 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -174,6 +174,7 @@ cfg_wasm32! { } pub const X_GRPC_WEB: &str = "x-grpc-web"; +pub const X_API_KEY: &str = "X-API-Key"; pub const APPLICATION_JSON: &str = "application/json"; pub const APPLICATION_GRPC_WEB: &str = "application/grpc-web"; pub const APPLICATION_GRPC_WEB_PROTO: &str = "application/grpc-web+proto"; diff --git a/mm2src/common/shared_ref_counter/src/enable.rs b/mm2src/common/shared_ref_counter/src/enable.rs index bc4f6a0a67..205b36aab7 100644 --- a/mm2src/common/shared_ref_counter/src/enable.rs +++ b/mm2src/common/shared_ref_counter/src/enable.rs @@ -77,7 +77,7 @@ impl SharedRc { let existing_pointers = self.existing_pointers.read().expect(LOCKING_ERROR); log!(level, "{} exists at:", ident); for (_idx, location) in existing_pointers.iter() { - log!(level, "\t{}", stringify_location(*location)); + log!(level, "\t{}", stringify_location(location)); } } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 5989c999c7..6ec2dff43e 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -20,6 +20,7 @@ zhtlc-native-tests = ["coins/zhtlc-native-tests"] # Remove this once the solana integration becomes stable/completed. disable-solana-tests = [] default = ["disable-solana-tests"] +enable-nft-integration = ["coins/enable-nft-integration"] [dependencies] async-std = { version = "1.5", features = ["unstable"] } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index a553ff9c51..ec51a48f15 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -11,6 +11,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s mm2::rpc::lp_commands::{get_public_key, get_public_key_hash, get_shared_db_id, trezor_connection_status}}; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; +#[cfg(feature = "enable-nft-integration")] use coins::nft; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, get_enabled_coins::get_enabled_coins, @@ -28,8 +29,8 @@ use coins::utxo::bch::BchCoin; use coins::utxo::qtum::QtumCoin; use coins::utxo::slp::SlpToken; use coins::utxo::utxo_standard::UtxoStandardCoin; -use coins::{add_delegation, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, verify_message, - withdraw}; +use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, + verify_message, withdraw}; #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_standalone_coin, enable_platform_coin_with_tokens, enable_token, @@ -42,6 +43,8 @@ use http::Response; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_rpc::mm_protocol::{MmRpcBuilder, MmRpcRequest, MmRpcVersion}; +#[cfg(feature = "enable-nft-integration")] +use nft::{get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_nft}; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; use std::net::SocketAddr; @@ -159,7 +162,14 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_current_mtp_rpc).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, + "get_my_address" => handle_mmrpc(ctx, request, get_my_address).await, "get_new_address" => handle_mmrpc(ctx, request, get_new_address).await, + #[cfg(feature = "enable-nft-integration")] + "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, + #[cfg(feature = "enable-nft-integration")] + "get_nft_metadata" => handle_mmrpc(ctx, request, get_nft_metadata).await, + #[cfg(feature = "enable-nft-integration")] + "get_nft_transfers" => handle_mmrpc(ctx, request, get_nft_transfers).await, "get_public_key" => handle_mmrpc(ctx, request, get_public_key).await, "get_public_key_hash" => handle_mmrpc(ctx, request, get_public_key_hash).await, "get_raw_transaction" => handle_mmrpc(ctx, request, get_raw_transaction).await, @@ -181,6 +191,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, + #[cfg(feature = "enable-nft-integration")] + "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(not(target_os = "ios"), not(target_os = "android")))] diff --git a/mm2src/mm2_metamask/src/metamask_error.rs b/mm2src/mm2_metamask/src/metamask_error.rs index 29e3201b9a..68db8af025 100644 --- a/mm2src/mm2_metamask/src/metamask_error.rs +++ b/mm2src/mm2_metamask/src/metamask_error.rs @@ -1,7 +1,7 @@ use derive_more::Display; use jsonrpc_core::{Error as RPCError, ErrorCode as RpcErrorCode}; use mm2_err_handle::prelude::*; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use web3::Error as Web3Error; const USER_CANCELLED_ERROR_CODE: RpcErrorCode = RpcErrorCode::ServerError(4001); @@ -55,7 +55,7 @@ impl From for MetamaskError { /// so please extend it if it's required **only**. /// /// Please also note that this enum is fieldless. -#[derive(Clone, Debug, Display, Serialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Display, Serialize, PartialEq)] pub enum MetamaskRpcError { EthProviderNotFound, #[display(fmt = "User cancelled request")] diff --git a/mm2src/mm2_net/src/native_http.rs b/mm2src/mm2_net/src/native_http.rs index ea4b0b2912..641a0cae24 100644 --- a/mm2src/mm2_net/src/native_http.rs +++ b/mm2src/mm2_net/src/native_http.rs @@ -1,10 +1,11 @@ -use crate::transport::{SlurpError, SlurpResult}; +use crate::transport::{SlurpError, SlurpResult, SlurpResultJson}; use common::wio::{drive03, HYPER}; use common::APPLICATION_JSON; use futures::channel::oneshot::Canceled; use http::{header, Request}; use hyper::Body; use mm2_err_handle::prelude::*; +use serde_json::Value as Json; impl From for SlurpError { fn from(_: Canceled) -> Self { SlurpError::Internal("Spawned Slurp future has been canceled".to_owned()) } @@ -49,6 +50,25 @@ pub async fn slurp_req(request: Request>) -> SlurpResult { Ok((status, headers, output.to_vec())) } +/// Executes a Hyper request, requires [`Request`] and return the response status, headers and body as Json. +pub async fn slurp_req_body(request: Request) -> SlurpResultJson { + let uri = request.uri().to_string(); + + let request_f = HYPER.request(request); + let response = drive03(request_f) + .await? + .map_to_mm(|e| SlurpError::from_hyper_error(e, uri.clone()))?; + let status = response.status(); + let headers = response.headers().clone(); + // Get the response body bytes. + let body_bytes = hyper::body::to_bytes(response.into_body()) + .await + .map_to_mm(|e| SlurpError::from_hyper_error(e, uri.clone()))?; + let body_str = String::from_utf8(body_bytes.to_vec()).map_to_mm(|e| SlurpError::Internal(e.to_string()))?; + let body: Json = serde_json::from_str(&body_str)?; + Ok((status, headers, body)) +} + /// Executes a GET request, returning the response status, headers and body. pub async fn slurp_url(url: &str) -> SlurpResult { let req = Request::builder().uri(url).body(Vec::new())?; diff --git a/mm2src/mm2_net/src/transport.rs b/mm2src/mm2_net/src/transport.rs index ad51122993..18e4f4feb6 100644 --- a/mm2src/mm2_net/src/transport.rs +++ b/mm2src/mm2_net/src/transport.rs @@ -4,15 +4,18 @@ use ethkey::Secret; use http::{HeaderMap, StatusCode}; use mm2_err_handle::prelude::*; use serde::{Deserialize, Serialize}; +use serde_json::{Error, Value as Json}; #[cfg(not(target_arch = "wasm32"))] -pub use crate::native_http::{slurp_post_json, slurp_req, slurp_url}; +pub use crate::native_http::{slurp_post_json, slurp_req, slurp_req_body, slurp_url}; #[cfg(target_arch = "wasm32")] pub use crate::wasm_http::{slurp_post_json, slurp_url}; pub type SlurpResult = Result<(StatusCode, HeaderMap, Vec), MmError>; +pub type SlurpResultJson = Result<(StatusCode, HeaderMap, Json), MmError>; + #[derive(Debug, Deserialize, Display, Serialize)] pub enum SlurpError { #[display(fmt = "Error deserializing '{}' response: {}", uri, error)] @@ -27,6 +30,10 @@ pub enum SlurpError { Internal(String), } +impl From for SlurpError { + fn from(e: Error) -> Self { SlurpError::Internal(e.to_string()) } +} + impl From for JsonRpcErrorType { fn from(err: SlurpError) -> Self { match err { From 32d2ed5511a1334c1619290654706e63ed81d495 Mon Sep 17 00:00:00 2001 From: caglarkaya Date: Tue, 7 Mar 2023 18:29:39 +0300 Subject: [PATCH 09/79] feat: Watcher rewards for ETH swaps (#1658) * add watcher_validate_taker_payment unit tests for eth, erc20 and utxo * reuse ethereum validate_fee method for watcher validations * add contract support parameter to eth coin activation parameters * fix lock_duration overflow bug by adding validation * use the refactored etomic swap contract * fix a watcher bug in wait_for_htlc_tx_spend and add a test case * change swap contract address and abi * remove the contract_supports_watchers method * add watcher_reward to negotiaton data * add watcher reward functionality * fix merge conflicts * remove log file * remove watcher reward related parts from negotiation data * disable the watcher rewards * small fixes * disable watcher rewards * remove log file * minor fixes * add test case for multiple watchers spending the same payment * use serde default for contract_supports_watchers field * use multiple attempts to get gas price for watcher rewards --------- Reviewed-by: @ozkanonur, @shamardy --- mm2src/coins/eth.rs | 950 +++++++++--------- mm2src/coins/eth/eth_tests.rs | 68 +- mm2src/coins/eth/eth_wasm_tests.rs | 4 +- mm2src/coins/eth/swap_contract_abi.json | 175 ++-- mm2src/coins/eth/v2_activation.rs | 4 + mm2src/coins/lightning.rs | 59 +- mm2src/coins/lp_coins.rs | 76 +- mm2src/coins/qrc20.rs | 71 +- mm2src/coins/qrc20/qrc20_tests.rs | 52 +- mm2src/coins/solana.rs | 52 +- mm2src/coins/solana/spl.rs | 56 +- mm2src/coins/tendermint/tendermint_coin.rs | 182 ++-- mm2src/coins/tendermint/tendermint_token.rs | 53 +- mm2src/coins/test_coin.rs | 51 +- mm2src/coins/utxo/bch.rs | 103 +- mm2src/coins/utxo/qtum.rs | 98 +- mm2src/coins/utxo/slp.rs | 61 +- mm2src/coins/utxo/utxo_common.rs | 228 ++--- mm2src/coins/utxo/utxo_standard.rs | 102 +- mm2src/coins/utxo/utxo_tests.rs | 28 +- mm2src/coins/z_coin.rs | 107 +- mm2src/mm2_main/src/lp_swap.rs | 68 ++ mm2src/mm2_main/src/lp_swap/maker_swap.rs | 85 +- .../src/lp_swap/recreate_swap_data.rs | 5 +- mm2src/mm2_main/src/lp_swap/swap_watcher.rs | 50 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 123 ++- .../tests/docker_tests/docker_tests_common.rs | 19 +- .../tests/docker_tests/docker_tests_inner.rs | 36 +- .../tests/docker_tests/qrc20_tests.rs | 77 +- .../tests/docker_tests/swap_watcher_tests.rs | 938 ++++++++++++++++- mm2src/mm2_main/tests/mm2_tests/iris_swap.rs | 4 +- .../tests/mm2_tests/mm2_tests_inner.rs | 20 +- mm2src/mm2_test_helpers/src/for_tests.rs | 19 +- 33 files changed, 2520 insertions(+), 1504 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 11ad6acc27..56715e1296 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -75,23 +75,22 @@ cfg_wasm32! { } use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, - CoinProtocol, CoinTransportMetrics, CoinsContext, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, - IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, NegotiateSwapContractAddrErr, - NumConversError, NumConversResult, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, - RawTransactionResult, RefundError, RefundResult, RpcClientType, RpcTransportEventHandler, - RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, - TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, - VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, - EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_RECEIVER_ERR_LOG, - INVALID_SENDER_ERR_LOG}; + CoinProtocol, CoinTransportMetrics, CoinsContext, EthValidateFeeArgs, FeeApproxStage, FoundSwapTxSpend, + HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, + NegotiateSwapContractAddrErr, NumConversError, NumConversResult, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, + RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, + WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG, + INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, + INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; pub use rlp; #[cfg(test)] mod eth_tests; @@ -430,6 +429,7 @@ pub struct EthCoinImpl { sign_message_prefix: Option, swap_contract_address: Address, fallback_swap_contract: Option
, + contract_supports_watchers: bool, web3: Web3, /// The separate web3 instances kept to get nonce, will replace the web3 completely soon web3_instances: Vec, @@ -1022,224 +1022,71 @@ impl SwapOps for EthCoin { ) } - fn send_maker_payment(&self, maker_payment: SendMakerPaymentArgs) -> TransactionFut { - let taker_addr = try_tx_fus!(addr_from_raw_pubkey(maker_payment.other_pubkey)); - let swap_contract_address = try_tx_fus!(maker_payment.swap_contract_address.try_to_address()); - + fn send_maker_payment(&self, maker_payment: SendPaymentArgs) -> TransactionFut { Box::new( - self.send_hash_time_locked_payment( - self.etomic_swap_id(maker_payment.time_lock, maker_payment.secret_hash), - try_tx_fus!(wei_from_big_decimal(&maker_payment.amount, self.decimals)), - maker_payment.time_lock, - maker_payment.secret_hash, - taker_addr, - swap_contract_address, - ) - .map(TransactionEnum::from), + self.send_hash_time_locked_payment(maker_payment) + .map(TransactionEnum::from), ) } - fn send_taker_payment(&self, taker_payment: SendTakerPaymentArgs) -> TransactionFut { - let maker_addr = try_tx_fus!(addr_from_raw_pubkey(taker_payment.other_pubkey)); - let swap_contract_address = try_tx_fus!(taker_payment.swap_contract_address.try_to_address()); - + fn send_taker_payment(&self, taker_payment: SendPaymentArgs) -> TransactionFut { Box::new( - self.send_hash_time_locked_payment( - self.etomic_swap_id(taker_payment.time_lock, taker_payment.secret_hash), - try_tx_fus!(wei_from_big_decimal(&taker_payment.amount, self.decimals)), - taker_payment.time_lock, - taker_payment.secret_hash, - maker_addr, - swap_contract_address, - ) - .map(TransactionEnum::from), + self.send_hash_time_locked_payment(taker_payment) + .map(TransactionEnum::from), ) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(maker_spends_payment_args.other_payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = - try_tx_fus!(maker_spends_payment_args.swap_contract_address.try_to_address(), signed); - + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { Box::new( - self.spend_hash_time_locked_payment( - signed, - maker_spends_payment_args.secret_hash, - swap_contract_address, - maker_spends_payment_args.secret, - ) - .map(TransactionEnum::from), + self.spend_hash_time_locked_payment(maker_spends_payment_args) + .map(TransactionEnum::from), ) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(taker_spends_payment_args.other_payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = try_tx_fus!(taker_spends_payment_args.swap_contract_address.try_to_address()); + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { Box::new( - self.spend_hash_time_locked_payment( - signed, - taker_spends_payment_args.secret_hash, - swap_contract_address, - taker_spends_payment_args.secret, - ) - .map(TransactionEnum::from), + self.spend_hash_time_locked_payment(taker_spends_payment_args) + .map(TransactionEnum::from), ) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(taker_refunds_payment_args.payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = try_tx_fus!(taker_refunds_payment_args.swap_contract_address.try_to_address()); - + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new( - self.refund_hash_time_locked_payment(swap_contract_address, signed, taker_refunds_payment_args.secret_hash) + self.refund_hash_time_locked_payment(taker_refunds_payment_args) .map(TransactionEnum::from), ) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(maker_refunds_payment_args.payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = try_tx_fus!(maker_refunds_payment_args.swap_contract_address.try_to_address()); - + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new( - self.refund_hash_time_locked_payment(swap_contract_address, signed, maker_refunds_payment_args.secret_hash) + self.refund_hash_time_locked_payment(maker_refunds_payment_args) .map(TransactionEnum::from), ) } - fn validate_fee( - &self, - validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { - let selfi = self.clone(); + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::SignedEthTx(t) => t.clone(), _ => panic!(), }; - let sender_addr = try_fus!(addr_from_raw_pubkey(validate_fee_args.expected_sender)); - let fee_addr = try_fus!(addr_from_raw_pubkey(validate_fee_args.fee_addr)); - let amount = validate_fee_args.amount.clone(); - let min_block_number = validate_fee_args.min_block_number; - - let fut = async move { - let expected_value = try_s!(wei_from_big_decimal(&amount, selfi.decimals)); - let tx_from_rpc = try_s!(selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).await); - let tx_from_rpc = match tx_from_rpc { - Some(t) => t, - None => return ERR!("Didn't find provided tx {:?} on ETH node", tx), - }; - - if tx_from_rpc.from != Some(sender_addr) { - return ERR!( - "Fee tx {:?} was sent from wrong address, expected {:?}", - tx_from_rpc, - sender_addr - ); - } - - if let Some(block_number) = tx_from_rpc.block_number { - if block_number <= min_block_number.into() { - return ERR!( - "Fee tx {:?} confirmed before min_block {}", - tx_from_rpc, - min_block_number, - ); - } - } - match &selfi.coin_type { - EthCoinType::Eth => { - if tx_from_rpc.to != Some(fee_addr) { - return ERR!( - "Fee tx {:?} was sent to wrong address, expected {:?}", - tx_from_rpc, - fee_addr - ); - } - - if tx_from_rpc.value < expected_value { - return ERR!( - "Fee tx {:?} value is less than expected {:?}", - tx_from_rpc, - expected_value - ); - } - }, - EthCoinType::Erc20 { - platform: _, - token_addr, - } => { - if tx_from_rpc.to != Some(*token_addr) { - return ERR!( - "ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", - tx_from_rpc, - token_addr - ); - } - - let function = try_s!(ERC20_CONTRACT.function("transfer")); - let decoded_input = try_s!(decode_contract_call(function, &tx_from_rpc.input.0)); - - if decoded_input[0] != Token::Address(fee_addr) { - return ERR!( - "ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", - decoded_input[0], - fee_addr - ); - } - - match decoded_input[1] { - Token::Uint(value) => { - if value < expected_value { - return ERR!("ERC20 Fee tx value {} is less than expected {}", value, expected_value); - } - }, - _ => return ERR!("Should have got uint token but got {:?}", decoded_input[1]), - } - }, - } - - Ok(()) - }; - Box::new(fut.boxed().compat()) + validate_fee_impl(self.clone(), EthValidateFeeArgs { + fee_tx_hash: &tx.hash, + expected_sender: validate_fee_args.expected_sender, + fee_addr: validate_fee_args.fee_addr, + amount: validate_fee_args.amount, + min_block_number: validate_fee_args.min_block_number, + uuid: validate_fee_args.uuid, + }) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { - let swap_contract_address = try_f!(input - .swap_contract_address - .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidParameter)); - self.validate_payment( - &input.payment_tx, - input.time_lock, - &input.other_pub, - &input.secret_hash, - input.amount, - swap_contract_address, - ) + self.validate_payment(input) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { - let swap_contract_address = try_f!(input - .swap_contract_address - .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidParameter)); - self.validate_payment( - &input.payment_tx, - input.time_lock, - &input.other_pub, - &input.secret_hash, - input.amount, - swap_contract_address, - ) + self.validate_payment(input) } fn check_if_my_payment_sent( @@ -1317,6 +1164,7 @@ impl SwapOps for EthCoin { swap_contract_address, input.secret_hash, input.search_from_block, + input.watcher_reward, ) .await } @@ -1331,6 +1179,7 @@ impl SwapOps for EthCoin { swap_contract_address, input.secret_hash, input.search_from_block, + input.watcher_reward, ) .await } @@ -1339,9 +1188,15 @@ impl SwapOps for EthCoin { unimplemented!(); } - async fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + _secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); - let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); + let function_name = get_function_name("receiverSpend", watcher_reward); + let function = try_s!(SWAP_CONTRACT.function(&function_name)); // Validate contract call; expected to be receiverSpend. // https://www.4byte.directory/signatures/?bytes4_signature=02ed292b. @@ -1471,7 +1326,10 @@ impl SwapOps for EthCoin { MmError::err(ValidateInstructionsErr::UnsupportedCoin(self.ticker().to_string())) } - fn is_supported_by_watchers(&self) -> bool { true } + fn is_supported_by_watchers(&self) -> bool { + false + //self.contract_supports_watchers + } } #[async_trait] @@ -1491,11 +1349,8 @@ impl MakerSwapTakerCoin for EthCoin { #[async_trait] impl WatcherOps for EthCoin { fn send_maker_payment_spend_preimage(&self, input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(input.preimage)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - Box::new( - self.watcher_spend_hash_time_locked_payment(signed, input.secret_hash, input.secret, input.taker_pub) + self.watcher_spends_hash_time_locked_payment(input) .map(TransactionEnum::from), ) } @@ -1531,107 +1386,25 @@ impl WatcherOps for EthCoin { Box::new(fut.boxed().compat()) } - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(watcher_refunds_payment_args.payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - + fn send_taker_payment_refund_preimage(&self, args: RefundPaymentArgs) -> TransactionFut { Box::new( - self.watcher_refunds_hash_time_locked_payment( - signed, - watcher_refunds_payment_args.secret_hash, - watcher_refunds_payment_args.other_pubkey, - ) - .map(TransactionEnum::from), + self.watcher_refunds_hash_time_locked_payment(args) + .map(TransactionEnum::from), ) } fn watcher_validate_taker_fee(&self, validate_fee_args: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> { - let selfi = self.clone(); - let sender_addr = - try_f!(addr_from_raw_pubkey(&validate_fee_args.sender_pubkey) - .map_to_mm(ValidatePaymentError::InvalidParameter)); - let fee_addr = - try_f!(addr_from_raw_pubkey(&validate_fee_args.fee_addr).map_to_mm(ValidatePaymentError::InvalidParameter)); - let min_block_number = validate_fee_args.min_block_number; - let taker_fee_hash = validate_fee_args.taker_fee_hash; - - let fut = async move { - let tx_from_rpc = selfi - .web3 - .eth() - .transaction(TransactionId::Hash(H256::from_slice(taker_fee_hash.as_slice()))) - .await - .map_to_mm(|e| ValidatePaymentError::InvalidRpcResponse(e.to_string()))?; - - let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { - ValidatePaymentError::TxDoesNotExist(format!( - "Didn't find provided tx {:?} on ETH node", - H256::from_slice(taker_fee_hash.as_slice()) - )) - })?; - - if tx_from_rpc.from != Some(sender_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: Fee tx {:?} was sent from wrong address, expected {:?}", - INVALID_SENDER_ERR_LOG, tx_from_rpc, sender_addr - ))); - } - - if let Some(block_number) = tx_from_rpc.block_number { - if block_number <= min_block_number.into() { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: Fee tx {:?} confirmed before min_block {}", - EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number - ))); - } - } - - //TODO: Validate if taker fee is old - - match &selfi.coin_type { - EthCoinType::Eth => { - if tx_from_rpc.to != Some(fee_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: Fee tx {:?} was sent to wrong address, expected {:?}", - INVALID_RECEIVER_ERR_LOG, tx_from_rpc, fee_addr - ))); - } - }, - EthCoinType::Erc20 { - platform: _, - token_addr, - } => { - if tx_from_rpc.to != Some(*token_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", - INVALID_CONTRACT_ADDRESS_ERR_LOG, tx_from_rpc, token_addr - ))); - } - - let function = ERC20_CONTRACT - .function("transfer") - .map_to_mm(|e| ValidatePaymentError::InternalError(e.to_string()))?; - let decoded_input = function - .decode_input(&tx_from_rpc.input.0) - .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; - let address_input = get_function_input_data(&decoded_input, function, 0) - .map_to_mm(ValidatePaymentError::TxDeserializationError)?; - if address_input != Token::Address(fee_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", - INVALID_RECEIVER_ERR_LOG, address_input, fee_addr - ))); - } - }, - } - - Ok(()) - }; + validate_fee_impl(self.clone(), EthValidateFeeArgs { + fee_tx_hash: &H256::from_slice(validate_fee_args.taker_fee_hash.as_slice()), + expected_sender: &validate_fee_args.sender_pubkey, + fee_addr: &validate_fee_args.fee_addr, + amount: &BigDecimal::from(0), + min_block_number: validate_fee_args.min_block_number, + uuid: &[], + }) - Box::new(fut.boxed().compat()) + // TODO: Add validations specific for watchers + // 1.Validate if taker fee is old } fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { @@ -1660,8 +1433,7 @@ impl WatcherOps for EthCoin { if tx_from_rpc.from != Some(sender) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx {:?} was sent from wrong address, expected {:?}", - tx_from_rpc, sender + "{INVALID_SENDER_ERR_LOG}: Payment tx {tx_from_rpc:?} was sent from wrong address, expected {sender:?}" ))); } @@ -1675,7 +1447,7 @@ impl WatcherOps for EthCoin { && Some(swap_contract_address) != fallback_swap_contract { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx {tx_from_rpc:?} was sent to wrong address, expected either {expected_swap_contract_address:?} or the fallback {fallback_swap_contract:?}" + "{INVALID_CONTRACT_ADDRESS_ERR_LOG}: Payment tx {tx_from_rpc:?} was sent to wrong address, expected either {expected_swap_contract_address:?} or the fallback {fallback_swap_contract:?}" ))); } @@ -1686,26 +1458,28 @@ impl WatcherOps for EthCoin { .map_to_mm(ValidatePaymentError::Transport)?; if status != PAYMENT_STATE_SENT.into() { return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( - "Payment state is not PAYMENT_STATE_SENT, got {}", - status + "{INVALID_PAYMENT_STATE_ERR_LOG}: Payment state is not PAYMENT_STATE_SENT, got {status}" ))); } + let min_watcher_reward = input.min_watcher_reward.ok_or_else(|| { + ValidatePaymentError::InvalidParameter("Minimum watcher reward argument is not provided".to_string()) + })?; + match &selfi.coin_type { EthCoinType::Eth => { + let function_name = get_function_name("ethPayment", input.min_watcher_reward.is_some()); let function = SWAP_CONTRACT - .function("ethPayment") + .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = function - .decode_input(&tx_from_rpc.input.0) + let decoded = decode_contract_call(function, &tx_from_rpc.input.0) .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!( - "Invalid 'swap_id' {:?}, expected {:?}", - decoded, swap_id + "{INVALID_SWAP_ID_ERR_LOG}: Invalid 'swap_id' {decoded:?}, expected {swap_id:?}" ))); } @@ -1713,9 +1487,7 @@ impl WatcherOps for EthCoin { .map_to_mm(ValidatePaymentError::TxDeserializationError)?; if receiver_input != Token::Address(receiver) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx receiver arg {:?} is invalid, expected {:?}", - receiver_input, - Token::Address(receiver) + "{INVALID_RECEIVER_ERR_LOG}: Payment tx receiver arg {receiver_input:?} is invalid, expected {:?}", Token::Address(receiver) ))); } @@ -1738,24 +1510,46 @@ impl WatcherOps for EthCoin { Token::Uint(U256::from(input.time_lock)), ))); } + + let watcher_reward = get_function_input_data(&decoded, function, 4) + .map_to_mm(ValidatePaymentError::TxDeserializationError)? + .into_uint() + .ok_or_else(|| { + ValidatePaymentError::WrongPaymentTx("Invalid type for watcher reward argument".to_string()) + })? + .as_u64(); + + if watcher_reward < min_watcher_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{INSUFFICIENT_WATCHER_REWARD_ERR_LOG}: Provided watcher reward {} is less than the minimum required amount {}", + watcher_reward, min_watcher_reward, + ))); + } }, EthCoinType::Erc20 { platform: _, token_addr, } => { + let function_name = get_function_name("erc20Payment", input.min_watcher_reward.is_some()); let function = SWAP_CONTRACT - .function("erc20Payment") + .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = function - .decode_input(&tx_from_rpc.input.0) + let decoded = decode_contract_call(function, &tx_from_rpc.input.0) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + if tx_from_rpc.value.as_u64() < min_watcher_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{INSUFFICIENT_WATCHER_REWARD_ERR_LOG}: Provided watcher reward {} is less than the minimum required amount {}", + tx_from_rpc.value.as_u64(), + min_watcher_reward, + ))); + } + 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!( - "Invalid 'swap_id' {:?}, expected {:?}", - decoded, swap_id + "{INVALID_SWAP_ID_ERR_LOG}: Invalid 'swap_id' {decoded:?}, expected {swap_id:?}" ))); } @@ -1773,9 +1567,7 @@ impl WatcherOps for EthCoin { .map_to_mm(ValidatePaymentError::TxDeserializationError)?; if receiver_addr_input != Token::Address(receiver) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx receiver arg {:?} is invalid, expected {:?}", - receiver_addr_input, - Token::Address(receiver), + "{INVALID_RECEIVER_ERR_LOG}: Payment tx receiver arg {receiver_addr_input:?} is invalid, expected {:?}", Token::Address(receiver), ))); } @@ -1822,6 +1614,7 @@ impl WatcherOps for EthCoin { swap_contract_address, input.secret_hash, input.search_from_block, + input.watcher_reward, ) .await } @@ -2023,7 +1816,18 @@ impl MarketCoinOps for EthCoin { ) -> TransactionFut { let unverified: UnverifiedTransaction = try_tx_fus!(rlp::decode(tx_bytes)); let tx = try_tx_fus!(SignedEthTx::new(unverified)); - let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address()); + + let swap_contract_address = match swap_contract_address { + Some(addr) => try_tx_fus!(addr.try_to_address()), + None => match tx.action { + Call(address) => address, + Create => { + return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( + "Invalid payment action: the payment action cannot be create" + )))) + }, + }, + }; let func_name = match self.coin_type { EthCoinType::Eth => "ethPayment", @@ -3018,30 +2822,44 @@ impl EthCoin { } } - fn send_hash_time_locked_payment( - &self, - id: Vec, - value: U256, - time_lock: u32, - secret_hash: &[u8], - receiver_addr: Address, - swap_contract_address: Address, - ) -> EthTxFut { - let secret_hash = if secret_hash.len() == 32 { - ripemd160(secret_hash).to_vec() + 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 trade_amount = try_tx_fus!(wei_from_big_decimal(&args.amount, self.decimals)); + + let secret_hash = if args.secret_hash.len() == 32 { + ripemd160(args.secret_hash).to_vec() } else { - secret_hash.to_vec() + args.secret_hash.to_vec() }; match &self.coin_type { EthCoinType::Eth => { - let function = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); - let data = try_tx_fus!(function.encode_input(&[ - Token::FixedBytes(id), - Token::Address(receiver_addr), - Token::FixedBytes(secret_hash), - Token::Uint(U256::from(time_lock)) - ])); + let function_name = get_function_name("ethPayment", args.watcher_reward.is_some()); + let function = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let mut value = trade_amount; + + let data = match args.watcher_reward { + Some(reward) => { + value += U256::from(reward); + + try_tx_fus!(function.encode_input(&[ + Token::FixedBytes(id), + Token::Address(receiver_addr), + Token::FixedBytes(secret_hash), + Token::Uint(U256::from(args.time_lock)), + Token::Uint(U256::from(reward)), + ])) + }, + None => try_tx_fus!(function.encode_input(&[ + Token::FixedBytes(id), + Token::Address(receiver_addr), + Token::FixedBytes(secret_hash), + Token::Uint(U256::from(args.time_lock)), + ])), + }; + self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS)) }, EthCoinType::Erc20 { @@ -3052,16 +2870,18 @@ impl EthCoin { .allowance(swap_contract_address) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))); - let function = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", args.watcher_reward.is_some()); + let function = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let data = try_tx_fus!(function.encode_input(&[ Token::FixedBytes(id), - Token::Uint(value), + Token::Uint(trade_amount), Token::Address(*token_addr), Token::Address(receiver_addr), Token::FixedBytes(secret_hash), - Token::Uint(U256::from(time_lock)) + Token::Uint(U256::from(args.time_lock)) ])); + let value = U256::from(args.watcher_reward.unwrap_or(0)); let arc = self.clone(); Box::new(allowance_fut.and_then(move |allowed| -> EthTxFut { @@ -3070,7 +2890,7 @@ impl EthCoin { arc.approve(swap_contract_address, U256::max_value()) .and_then(move |_approved| { arc.sign_and_send_transaction( - 0.into(), + value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS), @@ -3079,7 +2899,7 @@ impl EthCoin { ) } else { Box::new(arc.sign_and_send_transaction( - 0.into(), + value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS), @@ -3090,17 +2910,15 @@ impl EthCoin { } } - fn watcher_spend_hash_time_locked_payment( - &self, - payment: SignedEthTx, - _secret_hash: &[u8], - secret: &[u8], - taker_pub: &[u8], - ) -> EthTxFut { - let spend_func = try_tx_fus!(SWAP_CONTRACT.function("watcherSpend")); + fn watcher_spends_hash_time_locked_payment(&self, input: SendMakerPaymentSpendPreimageInput) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(input.preimage)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + + let function_name = get_function_name("receiverSpend", input.watcher_reward); + let spend_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let clone = self.clone(); - let secret_vec = secret.to_vec(); - let taker_addr = addr_from_raw_pubkey(taker_pub).unwrap(); + let secret_vec = input.secret.to_vec(); + let taker_addr = addr_from_raw_pubkey(input.taker_pub).unwrap(); let swap_contract_address = match payment.action { Call(address) => address, Create => { @@ -3110,10 +2928,12 @@ impl EthCoin { }, }; + let watcher_reward = input.watcher_reward; match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3130,13 +2950,15 @@ impl EthCoin { } let value = payment.value; + let watcher_reward_amount = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); let data = try_tx_fus!(spend_func.encode_input(&[ swap_id_input, Token::Uint(value), Token::FixedBytes(secret_vec.clone()), Token::Address(Address::default()), Token::Address(payment.sender()), - Token::Address(taker_addr) + Token::Address(taker_addr), + watcher_reward_amount, ])); clone.sign_and_send_transaction( @@ -3152,9 +2974,10 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3176,9 +2999,9 @@ impl EthCoin { Token::FixedBytes(secret_vec.clone()), Token::Address(token_addr), Token::Address(payment.sender()), - Token::Address(taker_addr) + Token::Address(taker_addr), + Token::Uint(payment.value) ])); - clone.sign_and_send_transaction( 0.into(), Action::Call(swap_contract_address), @@ -3191,15 +3014,16 @@ impl EthCoin { } } - fn watcher_refunds_hash_time_locked_payment( - &self, - payment: SignedEthTx, - _secret_hash: &[u8], - taker_pub: &[u8], - ) -> EthTxFut { - let refund_func = try_tx_fus!(SWAP_CONTRACT.function("watcherRefund")); + fn watcher_refunds_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.payment_tx)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + let watcher_reward = args.watcher_reward; + + let function_name = get_function_name("senderRefund", watcher_reward); + let refund_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let clone = self.clone(); - let taker_addr = addr_from_raw_pubkey(taker_pub).unwrap(); + let taker_addr = addr_from_raw_pubkey(args.other_pubkey).unwrap(); let swap_contract_address = match payment.action { Call(address) => address, Create => { @@ -3211,10 +3035,11 @@ impl EthCoin { match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); - let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); + let receiver_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); let hash_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 2)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3231,13 +3056,16 @@ impl EthCoin { } let value = payment.value; + let watcher_reward_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); + let data = try_tx_fus!(refund_func.encode_input(&[ swap_id_input.clone(), Token::Uint(value), hash_input.clone(), Token::Address(Address::default()), Token::Address(taker_addr), - amount_input.clone(), + receiver_input.clone(), + watcher_reward_input, ])); clone.sign_and_send_transaction( @@ -3253,12 +3081,14 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); - let token_addr_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 3)); - let sender_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); + let receiver_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 3)); + let hash_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); Box::new( state_f @@ -3275,10 +3105,11 @@ impl EthCoin { let data = try_tx_fus!(refund_func.encode_input(&[ swap_id_input.clone(), amount_input.clone(), - sender_input.clone(), + hash_input.clone(), Token::Address(token_addr), Token::Address(taker_addr), - token_addr_input.clone(), + receiver_input.clone(), + Token::Uint(payment.value), ])); clone.sign_and_send_transaction( @@ -3293,20 +3124,22 @@ impl EthCoin { } } - fn spend_hash_time_locked_payment( - &self, - payment: SignedEthTx, - _secret_hash: &[u8], - swap_contract_address: Address, - secret: &[u8], - ) -> EthTxFut { - let spend_func = try_tx_fus!(SWAP_CONTRACT.function("receiverSpend")); + fn spend_hash_time_locked_payment(&self, args: SpendPaymentArgs) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.other_payment_tx)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); + let watcher_reward = args.watcher_reward; + + let function_name = get_function_name("receiverSpend", watcher_reward); + let spend_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let clone = self.clone(); - let secret_vec = secret.to_vec(); + let secret_vec = args.secret.to_vec(); match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); @@ -3322,14 +3155,25 @@ impl EthCoin { )))); } - let value = payment.value; - let data = try_tx_fus!(spend_func.encode_input(&[ - decoded[0].clone(), - Token::Uint(value), - Token::FixedBytes(secret_vec), - Token::Address(Address::default()), - Token::Address(payment.sender()), - ])); + let data = if watcher_reward { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(payment.value), + Token::FixedBytes(secret_vec), + Token::Address(Address::default()), + Token::Address(payment.sender()), + Token::Address(clone.my_address), + decoded[4].clone(), + ])) + } else { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(payment.value), + Token::FixedBytes(secret_vec), + Token::Address(Address::default()), + Token::Address(payment.sender()), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3344,7 +3188,8 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); @@ -3360,13 +3205,25 @@ impl EthCoin { state )))); } - let data = try_tx_fus!(spend_func.encode_input(&[ - decoded[0].clone(), - decoded[1].clone(), - Token::FixedBytes(secret_vec), - Token::Address(token_addr), - Token::Address(payment.sender()), - ])); + let data = if watcher_reward { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + Token::FixedBytes(secret_vec), + Token::Address(token_addr), + Token::Address(payment.sender()), + Token::Address(clone.my_address), + Token::Uint(payment.value), + ])) + } else { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + Token::FixedBytes(secret_vec), + Token::Address(token_addr), + Token::Address(payment.sender()), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3380,18 +3237,22 @@ impl EthCoin { } } - fn refund_hash_time_locked_payment( - &self, - swap_contract_address: Address, - payment: SignedEthTx, - _secret_hash: &[u8], - ) -> EthTxFut { - let refund_func = try_tx_fus!(SWAP_CONTRACT.function("senderRefund")); + fn refund_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.payment_tx)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); + + let watcher_reward = args.watcher_reward; + let function_name = get_function_name("senderRefund", watcher_reward); + let refund_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let clone = self.clone(); match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); @@ -3408,13 +3269,25 @@ impl EthCoin { } let value = payment.value; - let data = try_tx_fus!(refund_func.encode_input(&[ - decoded[0].clone(), - Token::Uint(value), - decoded[2].clone(), - Token::Address(Address::default()), - decoded[1].clone(), - ])); + let data = if watcher_reward { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(value), + decoded[2].clone(), + Token::Address(Address::default()), + Token::Address(clone.my_address), + decoded[1].clone(), + decoded[4].clone(), + ])) + } else { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(value), + decoded[2].clone(), + Token::Address(Address::default()), + decoded[1].clone(), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3429,7 +3302,9 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); Box::new( @@ -3444,13 +3319,25 @@ impl EthCoin { )))); } - let data = try_tx_fus!(refund_func.encode_input(&[ - decoded[0].clone(), - decoded[1].clone(), - decoded[4].clone(), - Token::Address(token_addr), - decoded[3].clone(), - ])); + let data = if watcher_reward { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + decoded[4].clone(), + Token::Address(token_addr), + Token::Address(clone.my_address), + decoded[3].clone(), + Token::Uint(payment.value), + ])) + } else { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + decoded[4].clone(), + Token::Address(token_addr), + decoded[3].clone(), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3671,28 +3558,26 @@ impl EthCoin { Box::new(self.web3.eth().logs(filter).compat().map_err(|e| ERRL!("{}", e))) } - fn validate_payment( - &self, - payment_tx: &[u8], - time_lock: u32, - sender_pub: &[u8], - secret_hash: &[u8], - amount: BigDecimal, - expected_swap_contract_address: Address, - ) -> ValidatePaymentFut<()> { - let unsigned: UnverifiedTransaction = try_f!(rlp::decode(payment_tx)); + fn validate_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { + 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 sender = try_f!(addr_from_raw_pubkey(sender_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); - let expected_value = try_f!(wei_from_big_decimal(&amount, self.decimals)); + let sender = try_f!(addr_from_raw_pubkey(&input.other_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); + let selfi = self.clone(); - let swap_id = selfi.etomic_swap_id(time_lock, secret_hash); - let secret_hash = if secret_hash.len() == 32 { - ripemd160(secret_hash).to_vec() + let swap_id = selfi.etomic_swap_id(input.time_lock, &input.secret_hash); + let secret_hash = if input.secret_hash.len() == 32 { + ripemd160(&input.secret_hash).to_vec() } else { - secret_hash.to_vec() + input.secret_hash.to_vec() }; + let mut expected_value = try_f!(wei_from_big_decimal(&input.amount, self.decimals)); let fut = async move { let status = selfi .payment_status(expected_swap_contract_address, Token::FixedBytes(swap_id.clone())) @@ -3727,17 +3612,36 @@ impl EthCoin { ))); } + let function_name = get_function_name("ethPayment", input.min_watcher_reward.is_some()); + let function = SWAP_CONTRACT + .function(&function_name) + .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; + + let decoded = decode_contract_call(function, &tx_from_rpc.input.0) + .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + + if let Some(min_reward) = input.min_watcher_reward { + let reward = decoded[4].clone().into_uint().ok_or_else(|| { + ValidatePaymentError::WrongPaymentTx("Invalid type for watcher reward argument".to_string()) + })?; + if reward.as_u64() < min_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Insufficient watcher reward {}, must be at least {}", + reward, min_reward + ))); + } + + expected_value += reward; + } + drop_mutability!(expected_value); + if tx_from_rpc.value != expected_value { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Payment tx value arg {:?} is invalid, expected {:?}", tx_from_rpc, expected_value ))); } - let function = SWAP_CONTRACT - .function("ethPayment") - .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = decode_contract_call(function, &tx_from_rpc.input.0) - .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + if decoded[0] != Token::FixedBytes(swap_id.clone()) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Invalid 'swap_id' {:?}, expected {:?}", @@ -3761,11 +3665,11 @@ impl EthCoin { ))); } - if decoded[3] != Token::Uint(U256::from(time_lock)) { + if decoded[3] != Token::Uint(U256::from(input.time_lock)) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Payment tx time_lock arg {:?} is invalid, expected {:?}", decoded[3], - Token::Uint(U256::from(time_lock)), + Token::Uint(U256::from(input.time_lock)), ))); } }, @@ -3779,11 +3683,21 @@ impl EthCoin { tx_from_rpc, expected_swap_contract_address, ))); } + if let Some(min_reward) = input.min_watcher_reward { + if tx_from_rpc.value.as_u64() < min_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Payment tx value arg {} is less than the minimum expected watcher reward {}", + tx_from_rpc.value, min_reward + ))); + } + } + let function_name = get_function_name("erc20Payment", input.min_watcher_reward.is_some()); let function = SWAP_CONTRACT - .function("erc20Payment") + .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; let decoded = decode_contract_call(function, &tx_from_rpc.input.0) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + if decoded[0] != Token::FixedBytes(swap_id.clone()) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Invalid 'swap_id' {:?}, expected {:?}", @@ -3823,11 +3737,11 @@ impl EthCoin { ))); } - if decoded[5] != Token::Uint(U256::from(time_lock)) { + if decoded[5] != Token::Uint(U256::from(input.time_lock)) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Payment tx time_lock arg {:?} is invalid, expected {:?}", decoded[5], - Token::Uint(U256::from(time_lock)), + Token::Uint(U256::from(input.time_lock)), ))); } }, @@ -3868,16 +3782,17 @@ impl EthCoin { swap_contract_address: Address, _secret_hash: &[u8], search_from_block: u64, + watcher_reward: bool, ) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(tx)); let tx = try_s!(SignedEthTx::new(unverified)); let func_name = match self.coin_type { - EthCoinType::Eth => "ethPayment", - EthCoinType::Erc20 { .. } => "erc20Payment", + EthCoinType::Eth => get_function_name("ethPayment", watcher_reward), + EthCoinType::Erc20 { .. } => get_function_name("erc20Payment", watcher_reward), }; - let payment_func = try_s!(SWAP_CONTRACT.function(func_name)); + let payment_func = try_s!(SWAP_CONTRACT.function(&func_name)); let decoded = try_s!(decode_contract_call(payment_func, &tx.data)); let id = match decoded.first() { Some(Token::FixedBytes(bytes)) => bytes.clone(), @@ -3956,7 +3871,7 @@ impl EthCoin { } /// Get gas price - fn get_gas_price(&self) -> Web3RpcFut { + pub fn get_gas_price(&self) -> Web3RpcFut { let coin = self.clone(); let fut = async move { // TODO refactor to error_log_passthrough once simple maker bot is merged @@ -4412,6 +4327,108 @@ impl GuiAuthMessages for EthCoin { } } +fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { + let fee_tx_hash = validate_fee_args.fee_tx_hash.to_owned(); + let sender_addr = try_f!( + addr_from_raw_pubkey(validate_fee_args.expected_sender).map_to_mm(ValidatePaymentError::InvalidParameter) + ); + let fee_addr = + try_f!(addr_from_raw_pubkey(validate_fee_args.fee_addr).map_to_mm(ValidatePaymentError::InvalidParameter)); + let amount = validate_fee_args.amount.clone(); + let min_block_number = validate_fee_args.min_block_number; + + let fut = async move { + let expected_value = wei_from_big_decimal(&amount, coin.decimals)?; + let tx_from_rpc = coin.web3.eth().transaction(TransactionId::Hash(fee_tx_hash)).await?; + + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!("Didn't find provided tx {:?} on ETH node", fee_tx_hash)) + })?; + + if tx_from_rpc.from != Some(sender_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} was sent from wrong address, expected {:?}", + INVALID_SENDER_ERR_LOG, tx_from_rpc, sender_addr + ))); + } + + if let Some(block_number) = tx_from_rpc.block_number { + if block_number <= min_block_number.into() { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} confirmed before min_block {}", + EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number + ))); + } + } + match &coin.coin_type { + EthCoinType::Eth => { + if tx_from_rpc.to != Some(fee_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} was sent to wrong address, expected {:?}", + INVALID_RECEIVER_ERR_LOG, tx_from_rpc, fee_addr + ))); + } + + if tx_from_rpc.value < expected_value { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Fee tx {:?} value is less than expected {:?}", + tx_from_rpc, expected_value + ))); + } + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + if tx_from_rpc.to != Some(*token_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", + INVALID_CONTRACT_ADDRESS_ERR_LOG, tx_from_rpc, token_addr + ))); + } + + let function = ERC20_CONTRACT + .function("transfer") + .map_to_mm(|e| ValidatePaymentError::InternalError(e.to_string()))?; + let decoded_input = decode_contract_call(function, &tx_from_rpc.input.0) + .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; + let address_input = get_function_input_data(&decoded_input, function, 0) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + + if address_input != Token::Address(fee_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", + INVALID_RECEIVER_ERR_LOG, address_input, fee_addr + ))); + } + + let value_input = get_function_input_data(&decoded_input, function, 1) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + + match value_input { + Token::Uint(value) => { + if value < expected_value { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ERC20 Fee tx value {} is less than expected {}", + value, expected_value + ))); + } + }, + _ => { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Should have got uint token but got {:?}", + value_input + ))) + }, + } + }, + } + + Ok(()) + }; + Box::new(fut.boxed().compat()) +} + fn get_function_input_data(decoded: &[Token], func: &Function, index: usize) -> Result { decoded.get(index).cloned().ok_or(format!( "Missing input in function {}: No input found at index {}", @@ -4420,6 +4437,14 @@ fn get_function_input_data(decoded: &[Token], func: &Function, index: usize) -> )) } +fn get_function_name(name: &str, watcher_reward: bool) -> String { + if watcher_reward { + format!("{}{}", name, "Reward") + } else { + name.to_owned() + } +} + pub fn addr_from_raw_pubkey(pubkey: &[u8]) -> Result { let pubkey = try_s!(PublicKey::from_slice(pubkey).map_err(|e| ERRL!("{:?}", e))); let eth_public = Public::from_slice(&pubkey.serialize_uncompressed()[1..65]); @@ -4660,13 +4685,13 @@ pub async fn eth_coin_from_conf_and_request( if swap_contract_address == Address::default() { return ERR!("swap_contract_address can't be zero address"); } - let fallback_swap_contract: Option
= try_s!(json::from_value(req["fallback_swap_contract"].clone())); if let Some(fallback) = fallback_swap_contract { if fallback == Address::default() { return ERR!("fallback_swap_contract can't be zero address"); } } + let contract_supports_watchers = req["contract_supports_watchers"].as_bool().unwrap_or_default(); let (my_address, key_pair) = try_s!(build_address_and_priv_key_policy(conf, priv_key_policy).await); @@ -4757,6 +4782,7 @@ pub async fn eth_coin_from_conf_and_request( sign_message_prefix, swap_contract_address, fallback_swap_contract, + contract_supports_watchers, decimals, ticker: ticker.into(), gas_station_url: try_s!(json::from_value(req["gas_station_url"].clone())), diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 954080fdf3..f4890d6a77 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -66,6 +66,7 @@ fn eth_coin_for_test( priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract, + contract_supports_watchers: false, ticker, web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -232,6 +233,7 @@ fn send_and_refund_erc20_payment() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -250,7 +252,7 @@ fn send_and_refund_erc20_payment() { erc20_tokens_infos: Default::default(), abortable_system: AbortableQueue::default(), })); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -259,19 +261,21 @@ fn send_and_refund_erc20_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); log!("{:?}", payment); block_on(Timer::sleep(60.)); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment.tx_hex(), time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, secret_hash: &[1; 20], swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -299,6 +303,7 @@ fn send_and_refund_eth_payment() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -317,7 +322,7 @@ fn send_and_refund_eth_payment() { erc20_tokens_infos: Default::default(), abortable_system: AbortableQueue::default(), })); - let send_maker_payment_args = SendMakerPaymentArgs { + let send_maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -326,19 +331,21 @@ fn send_and_refund_eth_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(send_maker_payment_args).wait().unwrap(); log!("{:?}", payment); block_on(Timer::sleep(60.)); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment.tx_hex(), time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, secret_hash: &[1; 20], swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -374,6 +381,7 @@ fn test_nonce_several_urls() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![ Web3Instance { web3: web3_infura.clone(), @@ -439,6 +447,7 @@ fn test_wait_for_payment_spend_timeout() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -509,6 +518,7 @@ fn test_search_for_swap_tx_spend_was_spent() { priv_key_policy: key_pair.into(), swap_contract_address, fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -553,9 +563,10 @@ fn test_search_for_swap_tx_spend_was_spent() { ]; let spend_tx = FoundSwapTxSpend::Spent(signed_eth_tx_from_bytes(&spend_tx).unwrap().into()); - let found_tx = block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 15643279)) - .unwrap() - .unwrap(); + let found_tx = + block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 15643279, false)) + .unwrap() + .unwrap(); assert_eq!(spend_tx, found_tx); } @@ -620,6 +631,7 @@ fn test_search_for_swap_tx_spend_was_refunded() { priv_key_policy: key_pair.into(), swap_contract_address, fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "BAT".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -667,9 +679,10 @@ fn test_search_for_swap_tx_spend_was_refunded() { ]; let refund_tx = FoundSwapTxSpend::Refunded(signed_eth_tx_from_bytes(&refund_tx).unwrap().into()); - let found_tx = block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 13638713)) - .unwrap() - .unwrap(); + let found_tx = + block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 13638713, false)) + .unwrap() + .unwrap(); assert_eq!(refund_tx, found_tx); } @@ -1036,8 +1049,11 @@ fn validate_dex_fee_invalid_sender_eth() { min_block_number: 0, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("was sent from wrong address")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), + } } #[test] @@ -1067,8 +1083,11 @@ fn validate_dex_fee_invalid_sender_erc() { min_block_number: 0, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("was sent from wrong address")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), + } } fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] { @@ -1102,8 +1121,11 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { min_block_number: 11784793, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("confirmed before min_block")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), + } } #[test] @@ -1136,8 +1158,11 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { min_block_number: 11823975, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("confirmed before min_block")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), + } } #[test] @@ -1276,6 +1301,7 @@ fn test_message_hash() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -1320,6 +1346,7 @@ fn test_sign_verify_message() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -1375,6 +1402,7 @@ fn test_eth_extract_secret() { priv_key_policy: key_pair.into(), swap_contract_address, fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -1404,7 +1432,7 @@ fn test_eth_extract_secret() { 100, 189, 72, 74, 221, 144, 66, 170, 68, 121, 29, 105, 19, 194, 35, 245, 196, 131, 236, 29, 105, 101, 30, ]; - let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice())); + let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice(), false)); assert!(secret.is_ok()); let expect_secret = &[ 168, 151, 11, 232, 224, 253, 63, 180, 26, 114, 23, 184, 27, 10, 161, 80, 178, 251, 73, 204, 80, 174, 97, 118, @@ -1427,7 +1455,7 @@ fn test_eth_extract_secret() { 6, 108, 165, 181, 188, 40, 56, 47, 211, 229, 221, 73, 5, 15, 89, 81, 117, 225, 216, 108, 98, 226, 119, 232, 94, 184, 42, 106, ]; - let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice())) + let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice(), false)) .err() .unwrap(); assert!(secret.contains("Expected 'receiverSpend' contract call signature")); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index f8e22335df..d5ed0fc61c 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -31,6 +31,7 @@ async fn test_send() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -49,7 +50,7 @@ async fn test_send() { erc20_tokens_infos: Default::default(), abortable_system: AbortableQueue::default(), })); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: 1000, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -58,6 +59,7 @@ async fn test_send() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).compat().await; console::log_1(&format!("{:?}", tx).into()); diff --git a/mm2src/coins/eth/swap_contract_abi.json b/mm2src/coins/eth/swap_contract_abi.json index 7003d9a181..1ff37734b9 100644 --- a/mm2src/coins/eth/swap_contract_abi.json +++ b/mm2src/coins/eth/swap_contract_abi.json @@ -1,8 +1,29 @@ [ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "Log", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "number", + "type": "uint256" + } + ], + "name": "LogNumber", + "type": "event" }, { "anonymous": false, @@ -84,7 +105,7 @@ ], "name": "erc20Payment", "outputs": [], - "stateMutability": "payable", + "stateMutability": "nonpayable", "type": "function" }, { @@ -110,9 +131,9 @@ "type": "address" }, { - "internalType": "bytes32", + "internalType": "bytes20", "name": "_secretHash", - "type": "bytes32" + "type": "bytes20" }, { "internalType": "uint64", @@ -120,7 +141,7 @@ "type": "uint64" } ], - "name": "erc20PaymentSha256", + "name": "erc20PaymentReward", "outputs": [], "stateMutability": "payable", "type": "function" @@ -165,49 +186,25 @@ "name": "_receiver", "type": "address" }, - { - "internalType": "bytes32", - "name": "_secretHash", - "type": "bytes32" - }, - { - "internalType": "uint64", - "name": "_lockTime", - "type": "uint64" - } - ], - "name": "ethPaymentSha256", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "payments", - "outputs": [ { "internalType": "bytes20", - "name": "paymentHash", + "name": "_secretHash", "type": "bytes20" }, { "internalType": "uint64", - "name": "lockTime", + "name": "_lockTime", "type": "uint64" }, { - "internalType": "enum EtomicSwap.PaymentState", - "name": "state", - "type": "uint8" + "internalType": "uint256", + "name": "_watcherReward", + "type": "uint256" } ], - "stateMutability": "view", + "name": "ethPaymentReward", + "outputs": [], + "stateMutability": "payable", "type": "function" }, { @@ -243,25 +240,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "secret_hash_algos", - "outputs": [ - { - "internalType": "enum EtomicSwap.SecretHashAlgo", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -275,22 +253,32 @@ "type": "uint256" }, { - "internalType": "bytes20", - "name": "_secretHash", - "type": "bytes20" + "internalType": "bytes32", + "name": "_secret", + "type": "bytes32" }, { "internalType": "address", "name": "_tokenAddress", "type": "address" }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, { "internalType": "address", "name": "_receiver", "type": "address" + }, + { + "internalType": "uint256", + "name": "_watcherReward", + "type": "uint256" } ], - "name": "senderRefund", + "name": "receiverSpendReward", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -308,9 +296,9 @@ "type": "uint256" }, { - "internalType": "bytes32", - "name": "_secretHash", - "type": "bytes32" + "internalType": "bytes20", + "name": "_paymentHash", + "type": "bytes20" }, { "internalType": "address", @@ -323,7 +311,7 @@ "type": "address" } ], - "name": "senderRefundSha256", + "name": "senderRefund", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -342,7 +330,7 @@ }, { "internalType": "bytes20", - "name": "_secretHash", + "name": "_paymentHash", "type": "bytes20" }, { @@ -359,49 +347,50 @@ "internalType": "address", "name": "_receiver", "type": "address" + }, + { + "internalType": "uint256", + "name": "_watcherReward", + "type": "uint256" } ], - "name": "watcherRefund", + "name": "senderRefundReward", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, { "inputs": [ { "internalType": "bytes32", - "name": "_id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_secret", + "name": "", "type": "bytes32" - }, + } + ], + "name": "payments", + "outputs": [ { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" + "internalType": "bytes20", + "name": "paymentHash", + "type": "bytes20" }, { - "internalType": "address", - "name": "_sender", - "type": "address" + "internalType": "uint64", + "name": "lockTime", + "type": "uint64" }, { - "internalType": "address", - "name": "_receiver", - "type": "address" + "internalType": "enum EtomicSwap.PaymentState", + "name": "state", + "type": "uint8" } ], - "name": "watcherSpend", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" } ] \ No newline at end of file diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 0fb6a5d02c..d2251a6686 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -90,6 +90,8 @@ pub struct EthActivationV2Request { pub rpc_mode: EthRpcMode, pub swap_contract_address: Address, pub fallback_swap_contract: Option
, + #[serde(default)] + pub contract_supports_watchers: bool, pub gas_station_url: Option, pub gas_station_decimals: Option, #[serde(default)] @@ -197,6 +199,7 @@ impl EthCoin { sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, fallback_swap_contract: self.fallback_swap_contract, + contract_supports_watchers: self.contract_supports_watchers, decimals, ticker, gas_station_url: self.gas_station_url.clone(), @@ -292,6 +295,7 @@ pub async fn eth_coin_from_conf_and_request_v2( sign_message_prefix, swap_contract_address: req.swap_contract_address, fallback_swap_contract: req.fallback_swap_contract, + contract_supports_watchers: req.contract_supports_watchers, decimals: ETH_DECIMALS, ticker, gas_station_url: req.gas_station_url, diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 8a69514384..98d787257f 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -18,15 +18,13 @@ use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendSpendPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, - TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + Transaction, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, + UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, + WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::bech32::ToBase32; @@ -523,7 +521,7 @@ impl LightningCoin { Ok(PaymentInstructions::Lightning(invoice)) } - fn spend_swap_payment(&self, spend_payment_args: SendSpendPaymentArgs<'_>) -> TransactionFut { + fn spend_swap_payment(&self, spend_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { let payment_hash = try_tx_fus!(payment_hash_from_slice(spend_payment_args.other_payment_tx)); let mut preimage = [b' '; 32]; preimage.copy_from_slice(spend_payment_args.secret); @@ -601,7 +599,7 @@ impl SwapOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs<'_>) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let PaymentInstructions::Lightning(invoice) = try_tx_fus!(maker_payment_args .payment_instructions .clone() @@ -615,7 +613,7 @@ impl SwapOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs<'_>) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let PaymentInstructions::Lightning(invoice) = try_tx_fus!(taker_payment_args .payment_instructions .clone() @@ -634,44 +632,29 @@ impl SwapOps for LightningCoin { } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { self.spend_swap_payment(maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { self.spend_swap_payment(taker_spends_payment_args) } - fn send_taker_refunds_payment( - &self, - _taker_refunds_payment_args: SendTakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund lightning HTLC".into(), ))) } - fn send_maker_refunds_payment( - &self, - _maker_refunds_payment_args: SendMakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund lightning HTLC".into(), ))) } // Todo: This validates the dummy fee 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 validate_fee( - &self, - _validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { Box::new(futures01::future::ok(())) } @@ -782,7 +765,12 @@ impl SwapOps for LightningCoin { unimplemented!(); } - async fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + _secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { let payment_hash = payment_hash_from_slice(spend_tx).map_err(|e| e.to_string())?; let payment_hex = hex::encode(payment_hash.0); @@ -967,10 +955,7 @@ impl WatcherOps for LightningCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f4a883afc7..41ede7f020 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -45,6 +45,7 @@ use crypto::{Bip32Error, CryptoCtx, CryptoCtxError, DerivationPath, GlobalHDAcco Secp256k1Secret, WithHwRpcError}; use derive_more::Display; use enum_from::{EnumFromStringify, EnumFromTrait}; +use ethereum_types::H256; use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; use futures::{FutureExt, TryFutureExt}; @@ -280,13 +281,6 @@ pub type RawTransactionResult = Result = Box> + Send + 'a>; pub type RefundResult = Result>; -pub type SendMakerPaymentArgs<'a> = SendSwapPaymentArgs<'a>; -pub type SendTakerPaymentArgs<'a> = SendSwapPaymentArgs<'a>; -pub type SendMakerSpendsTakerPaymentArgs<'a> = SendSpendPaymentArgs<'a>; -pub type SendTakerSpendsMakerPaymentArgs<'a> = SendSpendPaymentArgs<'a>; -pub type SendTakerRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; -pub type SendMakerRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; -pub type SendWatcherRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; pub type IguanaPrivKey = Secp256k1Secret; @@ -296,6 +290,11 @@ pub const EARLY_CONFIRMATION_ERR_LOG: &str = "Early confirmation"; pub const OLD_TRANSACTION_ERR_LOG: &str = "Old transaction"; pub const INVALID_RECEIVER_ERR_LOG: &str = "Invalid receiver"; pub const INVALID_CONTRACT_ADDRESS_ERR_LOG: &str = "Invalid contract address"; +pub const INVALID_PAYMENT_STATE_ERR_LOG: &str = "Invalid payment state"; +pub const INVALID_SWAP_ID_ERR_LOG: &str = "Invalid swap id"; +pub const INVALID_SCRIPT_ERR_LOG: &str = "Invalid script"; +pub const INVALID_REFUND_TX_ERR_LOG: &str = "Invalid refund transaction"; +pub const INSUFFICIENT_WATCHER_REWARD_ERR_LOG: &str = "Insufficient watcher reward"; #[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] @@ -563,6 +562,7 @@ pub struct WatcherValidatePaymentInput { pub secret_hash: Vec, pub try_spv_proof_until: u64, pub confirmations: u64, + pub min_watcher_reward: Option, } #[derive(Clone, Debug)] @@ -577,6 +577,7 @@ pub struct ValidatePaymentInput { pub try_spv_proof_until: u64, pub confirmations: u64, pub unique_swap_data: Vec, + pub min_watcher_reward: Option, } #[derive(Clone, Debug)] @@ -587,6 +588,7 @@ pub struct WatcherSearchForSwapTxSpendInput<'a> { pub secret_hash: &'a [u8], pub tx: &'a [u8], pub search_from_block: u64, + pub watcher_reward: bool, } #[derive(Clone, Debug)] @@ -595,6 +597,7 @@ pub struct SendMakerPaymentSpendPreimageInput<'a> { pub secret_hash: &'a [u8], pub secret: &'a [u8], pub taker_pub: &'a [u8], + pub watcher_reward: bool, } pub struct SearchForSwapTxSpendInput<'a> { @@ -605,10 +608,11 @@ pub struct SearchForSwapTxSpendInput<'a> { pub search_from_block: u64, pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, } #[derive(Clone, Debug)] -pub struct SendSwapPaymentArgs<'a> { +pub struct SendPaymentArgs<'a> { pub time_lock_duration: u64, pub time_lock: u32, /// This is either: @@ -620,10 +624,11 @@ pub struct SendSwapPaymentArgs<'a> { pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], pub payment_instructions: &'a Option, + pub watcher_reward: Option, } #[derive(Clone, Debug)] -pub struct SendSpendPaymentArgs<'a> { +pub struct SpendPaymentArgs<'a> { /// This is either: /// * 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`]. @@ -637,10 +642,11 @@ pub struct SendSpendPaymentArgs<'a> { pub secret_hash: &'a [u8], pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, } #[derive(Clone, Debug)] -pub struct SendRefundPaymentArgs<'a> { +pub struct RefundPaymentArgs<'a> { pub payment_tx: &'a [u8], pub time_lock: u32, /// This is either: @@ -650,6 +656,7 @@ pub struct SendRefundPaymentArgs<'a> { pub secret_hash: &'a [u8], pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, } #[derive(Clone, Debug)] @@ -674,6 +681,15 @@ pub struct ValidateFeeArgs<'a> { pub uuid: &'a [u8], } +pub struct EthValidateFeeArgs<'a> { + pub fee_tx_hash: &'a H256, + pub expected_sender: &'a [u8], + pub fee_addr: &'a [u8], + pub amount: &'a BigDecimal, + pub min_block_number: u64, + pub uuid: &'a [u8], +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub enum PaymentInstructions { #[cfg(not(target_arch = "wasm32"))] @@ -714,28 +730,19 @@ pub enum RefundError { pub trait SwapOps { fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut; - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs<'_>) -> TransactionFut; + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs<'_>) -> TransactionFut; + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs<'_>, - ) -> TransactionFut; + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs<'_>, - ) -> TransactionFut; + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs<'_>) - -> TransactionFut; + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut; - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs<'_>) - -> TransactionFut; + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut; - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) - -> Box + Send>; + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()>; fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()>; @@ -756,7 +763,12 @@ pub trait SwapOps { input: SearchForSwapTxSpendInput<'_>, ) -> Result, String>; - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String>; + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String>; fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result>; @@ -827,6 +839,9 @@ pub trait SwapOps { fn is_supported_by_watchers(&self) -> bool { false } + // Do we also need a method for the fallback contract? + fn contract_supports_watchers(&self) -> bool { true } + fn maker_locktime_multiplier(&self) -> f64 { 2.0 } } @@ -852,10 +867,7 @@ pub trait MakerSwapTakerCoin { pub trait WatcherOps { fn send_maker_payment_spend_preimage(&self, input: SendMakerPaymentSpendPreimageInput) -> TransactionFut; - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut; + fn send_taker_payment_refund_preimage(&self, watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut; fn create_taker_payment_refund_preimage( &self, @@ -2270,6 +2282,8 @@ impl MmCoinEnum { _ => false, } } + + pub fn is_eth(&self) -> bool { matches!(self, MmCoinEnum::EthCoin(_)) } } #[async_trait] diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index cddbfaf6d5..2bb6fe9a76 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -18,17 +18,15 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, Broadca use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, - TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest, WithdrawResult}; + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -765,7 +763,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { let time_lock = maker_payment_args.time_lock; 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); @@ -783,7 +781,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { let time_lock = taker_payment_args.time_lock; 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); @@ -801,10 +799,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(maker_spends_payment_args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); let swap_contract_address = try_tx_fus!(maker_spends_payment_args.swap_contract_address.try_to_address()); @@ -820,10 +815,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(taker_spends_payment_args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); let secret = taker_spends_payment_args.secret.to_vec(); @@ -839,7 +831,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(taker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); let swap_contract_address = try_tx_fus!(taker_refunds_payment_args.swap_contract_address.try_to_address()); @@ -854,7 +846,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(maker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); let swap_contract_address = try_tx_fus!(maker_refunds_payment_args.swap_contract_address.try_to_address()); @@ -869,10 +861,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn validate_fee( - &self, - validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let fee_tx = validate_fee_args.fee_tx; let min_block_number = validate_fee_args.min_block_number; let fee_tx = match fee_tx { @@ -880,20 +869,26 @@ impl SwapOps for Qrc20Coin { _ => panic!("Unexpected TransactionEnum"), }; let fee_tx_hash = fee_tx.hash().reversed().into(); - if !try_fus!(check_all_utxo_inputs_signed_by_pub( + let inputs_signed_by_pub = try_f!(check_all_utxo_inputs_signed_by_pub( fee_tx, validate_fee_args.expected_sender - )) { - return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address"))); + )); + if !inputs_signed_by_pub { + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx("The dex fee was sent from wrong address".to_string()).into(), + )); } - let fee_addr = try_fus!(self.contract_address_from_raw_pubkey(validate_fee_args.fee_addr)); - let expected_value = try_fus!(wei_from_big_decimal(validate_fee_args.amount, self.utxo.decimals)); + 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 selfi = self.clone(); let fut = async move { selfi .validate_fee_impl(fee_tx_hash, fee_addr, expected_value, min_block_number) .await + .map_to_mm(ValidatePaymentError::WrongPaymentTx) }; Box::new(fut.boxed().compat()) } @@ -997,7 +992,12 @@ impl SwapOps for Qrc20Coin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { self.extract_secret_impl(secret_hash, spend_tx) } @@ -1130,10 +1130,7 @@ impl WatcherOps for Qrc20Coin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 12f606cb26..7354b5a103 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -170,6 +170,7 @@ fn test_validate_maker_payment() { try_spv_proof_until: now_ms() / 1000 + 30, confirmations: 1, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; coin.validate_maker_payment(input.clone()).wait().unwrap(); @@ -314,7 +315,7 @@ fn test_send_taker_fee() { uuid: &[], }) .wait(); - assert_eq!(result, Ok(())); + assert!(result.is_ok()); } #[test] @@ -342,7 +343,7 @@ fn test_validate_fee() { uuid: &[], }) .wait(); - assert_eq!(result, Ok(())); + assert!(result.is_ok()); let fee_addr_dif = hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc05").unwrap(); let err = coin @@ -356,9 +357,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("QRC20 Fee tx was sent to wrong address")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("QRC20 Fee tx was sent to wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong receiver address, found {:?}", err), + } let err = coin .validate_fee(ValidateFeeArgs { @@ -371,9 +376,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("was sent from wrong address")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", err), + } let err = coin .validate_fee(ValidateFeeArgs { @@ -386,9 +395,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("confirmed before min_block")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", err), + } let amount_dif = BigDecimal::from_str("0.02").unwrap(); let err = coin @@ -402,9 +415,15 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("QRC20 Fee tx value 1000000 is less than expected 2000000")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("QRC20 Fee tx value 1000000 is less than expected 2000000")) + }, + _ => panic!("Expected `WrongPaymentTx` invalid fee value, found {:?}", err), + } // QTUM tx "8a51f0ffd45f34974de50f07c5bf2f0949da4e88433f8f75191953a442cf9310" let tx = TransactionEnum::UtxoTx("020000000113640281c9332caeddd02a8dd0d784809e1ad87bda3c972d89d5ae41f5494b85010000006a47304402207c5c904a93310b8672f4ecdbab356b65dd869a426e92f1064a567be7ccfc61ff02203e4173b9467127f7de4682513a21efb5980e66dbed4da91dff46534b8e77c7ef012102baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1afeffffff020001b2c4000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acbc4dd20c2f0000001976a9144208fa7be80dcf972f767194ad365950495064a488ac76e70800".into()); @@ -420,9 +439,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("Expected 'transfer' contract call")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Expected 'transfer' contract call")), + _ => panic!("Expected `WrongPaymentTx` invalid contract call, found {:?}", err), + } } #[test] @@ -478,7 +501,7 @@ fn test_extract_secret() { // taker spent maker payment - d3f5dab4d54c14b3d7ed8c7f5c8cc7f47ccf45ce589fdc7cd5140a3c1c3df6e1 let tx_hex = hex::decode("01000000033f56ecafafc8602fde083ba868d1192d6649b8433e42e1a2d79ba007ea4f7abb010000006b48304502210093404e90e40d22730013035d31c404c875646dcf2fad9aa298348558b6d65ba60220297d045eac5617c1a3eddb71d4bca9772841afa3c4c9d6c68d8d2d42ee6de3950121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff9cac7fe90d597922a1d92e05306c2215628e7ea6d5b855bfb4289c2944f4c73a030000006b483045022100b987da58c2c0c40ce5b6ef2a59e8124ed4ef7a8b3e60c7fb631139280019bc93022069649bcde6fe4dd5df9462a1fcae40598488d6af8c324cd083f5c08afd9568be0121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff70b9870f2b0c65d220a839acecebf80f5b44c3ca4c982fa2fdc5552c037f5610010000006a473044022071b34dd3ebb72d29ca24f3fa0fc96571c815668d3b185dd45cc46a7222b6843f02206c39c030e618d411d4124f7b3e7ca1dd5436775bd8083a85712d123d933a51300121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff020000000000000000c35403a0860101284ca402ed292b806a1835a1b514ad643f2acdb5c8db6b6a9714accff3275ea0d79a3f23be8fd00000000000000000000000000000000000000000000000000000000001312d000101010101010101010101010101010101010101010101010101010101010101000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000009e032d4b0090a11dc40fe6c47601499a35d55fbb14ba8b71f3544b93e2f681f996da519a98ace0107ac2c02288d4010000001976a914783cf0be521101942da509846ea476e683aad83288ac0f047f5f").unwrap(); - let secret = block_on(coin.extract_secret(secret_hash, &tx_hex)).unwrap(); + let secret = block_on(coin.extract_secret(secret_hash, &tx_hex, false)).unwrap(); assert_eq!(secret, expected_secret); } @@ -499,7 +522,7 @@ fn test_extract_secret_malicious() { let spend_tx = hex::decode("01000000022bc8299981ec0cea664cdf9df4f8306396a02e2067d6ac2d3770b34646d2bc2a010000006b483045022100eb13ef2d99ac1cd9984045c2365654b115dd8a7815b7fbf8e2a257f0b93d1592022060d648e73118c843e97f75fafc94e5ff6da70ec8ba36ae255f8c96e2626af6260121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffffd92a0a10ac6d144b36033916f67ae79889f40f35096629a5cd87be1a08f40ee7010000006b48304502210080cdad5c4770dfbeb760e215494c63cc30da843b8505e75e7bf9e8dad18568000220234c0b11c41bfbcdd50046c69059976aedabe17657fe43d809af71e9635678e20121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff030000000000000000c35403a0860101284ca402ed292b8620ad3b72361a5aeba5dffd333fb64750089d935a1ec974d6a91ef4f24ff6ba0000000000000000000000000000000000000000000000000000000001312d000202020202020202020202020202020202020202020202020202020202020202000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000009e032d4b0090a11dc40fe6c47601499a35d55fbb14ba8b71f3544b93e2f681f996da519a98ace0107ac20000000000000000c35403a0860101284ca402ed292b8620ad3b72361a5aeba5dffd333fb64750089d935a1ec974d6a91ef4f24ff6ba0000000000000000000000000000000000000000000000000000000001312d000101010101010101010101010101010101010101010101010101010101010101000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000009e032d4b0090a11dc40fe6c47601499a35d55fbb14ba8b71f3544b93e2f681f996da519a98ace0107ac2b8ea82d3010000001976a914783cf0be521101942da509846ea476e683aad83288ac735d855f").unwrap(); let expected_secret = &[1; 32]; let secret_hash = &*dhash160(expected_secret); - let actual = block_on(coin.extract_secret(secret_hash, &spend_tx)); + let actual = block_on(coin.extract_secret(secret_hash, &spend_tx, false)); assert_eq!(actual, Ok(expected_secret.to_vec())); } @@ -1043,6 +1066,7 @@ fn test_validate_maker_payment_malicious() { confirmations: 1, other_pub: maker_pub, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let error = coin .validate_maker_payment(input) diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index cd92a1c30f..3f8dc1e6c9 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -5,15 +5,13 @@ use crate::solana::spl::SplTokenInfo; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -489,35 +487,27 @@ impl MarketCoinOps for SolanaCoin { impl SwapOps for SolanaCoin { fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_payment(&self, _taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, _maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, _taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> Box + Send> { - unimplemented!() - } + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } @@ -548,7 +538,14 @@ impl SwapOps for SolanaCoin { unimplemented!(); } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + unimplemented!() + } fn is_auto_refundable(&self) -> bool { false } @@ -657,10 +654,7 @@ impl WatcherOps for SolanaCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 05b4e76566..c005a09de9 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -4,16 +4,14 @@ use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, Suf use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, - RawTransactionFut, RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, SignatureResult, SolanaCoin, - TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, - TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, - WithdrawResult}; + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bincode::serialize; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; @@ -309,35 +307,25 @@ impl MarketCoinOps for SplToken { impl SwapOps for SplToken { fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_payment(&self, _taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, _maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, _taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - todo!() - } + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { todo!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> Box + Send> { - unimplemented!() - } + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } @@ -368,7 +356,14 @@ impl SwapOps for SplToken { unimplemented!(); } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + unimplemented!() + } fn is_auto_refundable(&self) -> bool { false } @@ -477,10 +472,7 @@ impl WatcherOps for SplToken { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index cb70b0b8fb..1d09ee3b18 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -9,11 +9,9 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionRes, RefundError, RefundResult, RpcCommonOps, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, @@ -970,30 +968,33 @@ impl TendermintCoin { decimals: u8, uuid: &[u8], denom: String, - ) -> Box + Send> { + ) -> ValidatePaymentFut<()> { let tx = match fee_tx { TransactionEnum::CosmosTransaction(tx) => tx.clone(), invalid_variant => { - return Box::new(futures01::future::err(ERRL!( - "Unexpected tx variant {:?}", - invalid_variant - ))) + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx(format!("Unexpected tx variant {:?}", invalid_variant)).into(), + )) }, }; - let uuid = try_fus!(Uuid::from_slice(uuid)).to_string(); + let uuid = try_f!(Uuid::from_slice(uuid).map_to_mm(|r| ValidatePaymentError::InvalidParameter(r.to_string()))) + .to_string(); + let sender_pubkey_hash = dhash160(expected_sender); - let expected_sender_address = - try_fus!(AccountId::new(&self.account_prefix, sender_pubkey_hash.as_slice())).to_string(); + let expected_sender_address = try_f!(AccountId::new(&self.account_prefix, sender_pubkey_hash.as_slice()) + .map_to_mm(|r| ValidatePaymentError::InvalidParameter(r.to_string()))) + .to_string(); let dex_fee_addr_pubkey_hash = dhash160(fee_addr); - let expected_dex_fee_address = try_fus!(AccountId::new( + let expected_dex_fee_address = try_f!(AccountId::new( &self.account_prefix, dex_fee_addr_pubkey_hash.as_slice() - )) + ) + .map_to_mm(|r| ValidatePaymentError::InvalidParameter(r.to_string()))) .to_string(); - let expected_amount = try_fus!(sat_from_big_decimal(amount, decimals)); + let expected_amount = try_f!(sat_from_big_decimal(amount, decimals)); let expected_amount = CoinProto { denom, amount: expected_amount.to_string(), @@ -1001,45 +1002,61 @@ impl TendermintCoin { let coin = self.clone(); let fut = async move { - let tx_body = try_s!(TxBody::decode(tx.data.body_bytes.as_slice())); + let tx_body = TxBody::decode(tx.data.body_bytes.as_slice()) + .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; if tx_body.messages.len() != 1 { - return ERR!("Tx body must have exactly one message"); + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Tx body must have exactly one message".to_string(), + )); } - let msg = try_s!(MsgSendProto::decode(tx_body.messages[0].value.as_slice())); + let msg = MsgSendProto::decode(tx_body.messages[0].value.as_slice()) + .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; if msg.to_address != expected_dex_fee_address { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Dex fee is sent to wrong address: {}, expected {}", - msg.to_address, - expected_dex_fee_address - ); + msg.to_address, expected_dex_fee_address + ))); } if msg.amount.len() != 1 { - return ERR!("Msg must have exactly one Coin"); + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Msg must have exactly one Coin".to_string(), + )); } if msg.amount[0] != expected_amount { - return ERR!("Invalid amount {:?}, expected {:?}", msg.amount[0], expected_amount); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Invalid amount {:?}, expected {:?}", + msg.amount[0], expected_amount + ))); } if msg.from_address != expected_sender_address { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Invalid sender: {}, expected {}", - msg.from_address, - expected_sender_address - ); + msg.from_address, expected_sender_address + ))); } if tx_body.memo != uuid { - return ERR!("Invalid memo: {}, expected {}", msg.from_address, uuid); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Invalid memo: {}, expected {}", + msg.from_address, uuid + ))); } let encoded_tx = tx.data.encode_to_vec(); let hash = hex::encode_upper(sha256(&encoded_tx).as_slice()); - let encoded_from_rpc = try_s!(coin.request_tx(hash).await).encode_to_vec(); + let encoded_from_rpc = coin + .request_tx(hash) + .await + .map_err(|e| MmError::new(ValidatePaymentError::TxDeserializationError(e.into_inner().to_string())))? + .encode_to_vec(); if encoded_tx != encoded_from_rpc { - return ERR!("Transaction from RPC doesn't match the input"); + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Transaction from RPC doesn't match the input".to_string(), + )); } Ok(()) }; @@ -1888,7 +1905,7 @@ impl SwapOps for TendermintCoin { self.send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { self.send_htlc_for_denom( maker_payment_args.time_lock_duration, maker_payment_args.other_pubkey, @@ -1899,7 +1916,7 @@ impl SwapOps for TendermintCoin { ) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { self.send_htlc_for_denom( taker_payment_args.time_lock_duration, taker_payment_args.other_pubkey, @@ -1910,10 +1927,7 @@ impl SwapOps for TendermintCoin { ) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = try_tx_fus!(cosmrs::Tx::from_bytes(maker_spends_payment_args.other_payment_tx)); let msg = try_tx_fus!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); let htlc_proto: CreateHtlcProtoRep = try_tx_fus!(prost::Message::decode(msg.value.as_slice())); @@ -1968,10 +1982,7 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = try_tx_fus!(cosmrs::Tx::from_bytes(taker_spends_payment_args.other_payment_tx)); let msg = try_tx_fus!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); let htlc_proto: CreateHtlcProtoRep = try_tx_fus!(prost::Message::decode(msg.value.as_slice())); @@ -2026,19 +2037,19 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund IRIS HTLC".into(), ))) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund IRIS HTLC".into(), ))) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { self.validate_fee_for_denom( validate_fee_args.fee_tx, validate_fee_args.expected_sender, @@ -2085,7 +2096,12 @@ impl SwapOps for TendermintCoin { self.search_for_swap_tx_spend(input).await.map_err(|e| e.to_string()) } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { let tx = try_s!(cosmrs::Tx::from_bytes(spend_tx)); let msg = try_s!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); let htlc_proto: super::iris::htlc_proto::ClaimHtlcProtoRep = @@ -2214,10 +2230,7 @@ impl WatcherOps for TendermintCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } @@ -2646,7 +2659,7 @@ pub mod tendermint_coin_tests { }); let invalid_amount = 1.into(); - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &create_htlc_tx, expected_sender: &[], @@ -2656,9 +2669,18 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("failed to decode Protobuf message: MsgSend.amount")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::TxDeserializationError(err) => { + assert!(err.contains("failed to decode Protobuf message: MsgSend.amount")) + }, + _ => panic!( + "Expected `WrongPaymentTx` MsgSend.amount decode failure, found {:?}", + error + ), + } // just a random transfer tx not related to AtomicDEX, should fail on recipient address check // https://nyancat.iobscan.io/#/tx?txHash=65815814E7D74832D87956144C1E84801DC94FE9A509D207A0ABC3F17775E5DF @@ -2671,7 +2693,7 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(random_transfer_tx_bytes.as_slice()).unwrap(), }); - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &random_transfer_tx, expected_sender: &[], @@ -2681,9 +2703,13 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("sent to wrong address")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("sent to wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong address, found {:?}", error), + } // dex fee tx sent during real swap // https://nyancat.iobscan.io/#/tx?txHash=8AA6B9591FE1EE93C8B89DE4F2C59B2F5D3473BD9FB5F3CFF6A5442BEDC881D7 @@ -2700,7 +2726,7 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(dex_fee_tx.encode_to_vec().as_slice()).unwrap(), }); - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &dex_fee_tx, expected_sender: &[], @@ -2710,13 +2736,17 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("Invalid amount")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid amount")), + _ => panic!("Expected `WrongPaymentTx` invalid amount, found {:?}", error), + } let valid_amount: BigDecimal = "0.0001".parse().unwrap(); // valid amount but invalid sender - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &dex_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -2726,12 +2756,16 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("Invalid sender")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid sender")), + _ => panic!("Expected `WrongPaymentTx` invalid sender, found {:?}", error), + } // invalid memo - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &dex_fee_tx, expected_sender: &pubkey, @@ -2741,9 +2775,13 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("Invalid memo")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid memo")), + _ => panic!("Expected `WrongPaymentTx` invalid memo, found {:?}", error), + } // https://nyancat.iobscan.io/#/tx?txHash=5939A9D1AF57BB828714E0C4C4D7F2AEE349BB719B0A1F25F8FBCC3BB227C5F9 let fee_with_memo_hash = "5939A9D1AF57BB828714E0C4C4D7F2AEE349BB719B0A1F25F8FBCC3BB227C5F9"; @@ -2822,6 +2860,7 @@ pub mod tendermint_coin_tests { try_spv_proof_until: 0, confirmations: 0, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let validate_err = coin.validate_taker_payment(input).wait().unwrap_err(); match validate_err.into_inner() { @@ -2847,6 +2886,7 @@ pub mod tendermint_coin_tests { try_spv_proof_until: 0, confirmations: 0, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let validate_err = block_on( coin.validate_payment_for_denom(input, "nim".parse().unwrap(), 6) @@ -2919,6 +2959,7 @@ pub mod tendermint_coin_tests { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let spend_tx = match block_on(coin.search_for_swap_tx_spend_my(input)).unwrap().unwrap() { @@ -2992,6 +3033,7 @@ pub mod tendermint_coin_tests { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; match block_on(coin.search_for_swap_tx_spend_my(input)).unwrap().unwrap() { diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 3a0b6af9f1..777df3ac67 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -6,15 +6,14 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -107,7 +106,7 @@ impl SwapOps for TendermintToken { .send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { self.platform_coin.send_htlc_for_denom( maker_payment_args.time_lock_duration, maker_payment_args.other_pubkey, @@ -118,7 +117,7 @@ impl SwapOps for TendermintToken { ) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { self.platform_coin.send_htlc_for_denom( taker_payment_args.time_lock_duration, taker_payment_args.other_pubkey, @@ -129,35 +128,29 @@ impl SwapOps for TendermintToken { ) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { self.platform_coin .send_maker_spends_taker_payment(maker_spends_payment_args) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { self.platform_coin .send_taker_spends_maker_payment(taker_spends_payment_args) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to be refunded".into(), ))) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to be refunded".into(), ))) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { self.platform_coin.validate_fee_for_denom( validate_fee_args.fee_tx, validate_fee_args.expected_sender, @@ -206,8 +199,15 @@ impl SwapOps for TendermintToken { self.platform_coin.search_for_swap_tx_spend_other(input).await } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { - self.platform_coin.extract_secret(secret_hash, spend_tx).await + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + self.platform_coin + .extract_secret(secret_hash, spend_tx, watcher_reward) + .await } fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result> { @@ -327,10 +327,7 @@ impl WatcherOps for TendermintToken { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 8b59b92cae..5383922dd2 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -4,14 +4,13 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransaction TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, - PaymentInstructionsErr, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureResult, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, + TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -108,35 +107,27 @@ impl MarketCoinOps for TestCoin { impl SwapOps for TestCoin { fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_payment(&self, _taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, _maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, _taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> Box + Send> { - unimplemented!() - } + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } fn validate_maker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } @@ -167,7 +158,14 @@ impl SwapOps for TestCoin { unimplemented!(); } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + unimplemented!() + } fn is_auto_refundable(&self) -> bool { false } @@ -273,10 +271,7 @@ impl WatcherOps for TestCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index e04d345877..02eefb15c9 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -12,14 +12,13 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, CoinWithDerivationMethod, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, - RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureResult, SwapOps, TakerSwapMakerCoin, TradePreimageValue, - TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; + RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use derive_more::Display; @@ -835,86 +834,36 @@ impl SwapOps for BchCoin { } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { - utxo_common::send_maker_payment( - self.clone(), - maker_payment_args.time_lock, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - maker_payment_args.secret_hash, - ) + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_payment(self.clone(), maker_payment_args) } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { - utxo_common::send_taker_payment( - self.clone(), - taker_payment_args.time_lock, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - taker_payment_args.swap_unique_data, - ) + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment(self.clone(), taker_payment_args) } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_maker_spends_taker_payment( - self.clone(), - maker_spends_payment_args.other_payment_tx, - maker_spends_payment_args.time_lock, - maker_spends_payment_args.other_pubkey, - maker_spends_payment_args.secret, - maker_spends_payment_args.secret_hash, - maker_spends_payment_args.swap_unique_data, - ) + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_spends_taker_payment(self.clone(), maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment( - self.clone(), - taker_spends_payment_args.other_payment_tx, - taker_spends_payment_args.time_lock, - taker_spends_payment_args.other_pubkey, - taker_spends_payment_args.secret, - taker_spends_payment_args.secret_hash, - taker_spends_payment_args.swap_unique_data, - ) + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment(self.clone(), taker_spends_payment_args) } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment( - self.clone(), - taker_refunds_payment_args.payment_tx, - taker_refunds_payment_args.time_lock, - taker_refunds_payment_args.other_pubkey, - taker_refunds_payment_args.secret_hash, - taker_refunds_payment_args.swap_unique_data, - ) + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment( - self.clone(), - maker_refunds_payment_args.payment_tx, - maker_refunds_payment_args.time_lock, - maker_refunds_payment_args.other_pubkey, - maker_refunds_payment_args.secret_hash, - maker_refunds_payment_args.swap_unique_data, - ) + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -976,7 +925,12 @@ impl SwapOps for BchCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -1125,10 +1079,7 @@ impl WatcherOps for BchCoin { } #[inline] - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { utxo_common::send_taker_payment_refund_preimage(self, watcher_refunds_payment_args) } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index ab3f336ceb..d5ad345f92 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -26,12 +26,10 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails UtxoTxHistoryOps}; use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, DelegationError, DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, + TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; @@ -532,86 +530,36 @@ impl SwapOps for QtumCoin { } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { - utxo_common::send_maker_payment( - self.clone(), - maker_payment_args.time_lock, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - maker_payment_args.swap_unique_data, - ) + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_payment(self.clone(), maker_payment_args) } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { - utxo_common::send_taker_payment( - self.clone(), - taker_payment_args.time_lock, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - taker_payment_args.swap_unique_data, - ) + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment(self.clone(), taker_payment_args) } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_maker_spends_taker_payment( - self.clone(), - maker_spends_payment_args.other_payment_tx, - maker_spends_payment_args.time_lock, - maker_spends_payment_args.other_pubkey, - maker_spends_payment_args.secret, - maker_spends_payment_args.secret_hash, - maker_spends_payment_args.swap_unique_data, - ) + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_spends_taker_payment(self.clone(), maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment( - self.clone(), - taker_spends_payment_args.other_payment_tx, - taker_spends_payment_args.time_lock, - taker_spends_payment_args.other_pubkey, - taker_spends_payment_args.secret, - taker_spends_payment_args.secret_hash, - taker_spends_payment_args.swap_unique_data, - ) + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment(self.clone(), taker_spends_payment_args) } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment( - self.clone(), - taker_refunds_payment_args.payment_tx, - taker_refunds_payment_args.time_lock, - taker_refunds_payment_args.other_pubkey, - taker_refunds_payment_args.secret_hash, - taker_refunds_payment_args.swap_unique_data, - ) + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment( - self.clone(), - maker_refunds_payment_args.payment_tx, - maker_refunds_payment_args.time_lock, - maker_refunds_payment_args.other_pubkey, - maker_refunds_payment_args.secret_hash, - maker_refunds_payment_args.swap_unique_data, - ) + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -673,7 +621,12 @@ impl SwapOps for QtumCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -818,10 +771,7 @@ impl WatcherOps for QtumCoin { } #[inline] - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { utxo_common::send_taker_payment_refund_preimage(self, watcher_refunds_payment_args) } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 1f7cf01839..67d85faf12 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -16,16 +16,14 @@ use crate::utxo::{generate_and_send_tx, sat_from_big_decimal, ActualTxFee, Addit use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructions, PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest}; + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentInput, VerificationError, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; @@ -752,7 +750,7 @@ impl SlpToken { validate_fut .compat() .await - .map_to_mm(ValidateDexFeeError::ValidatePaymentError)?; + .map_err(|e| MmError::new(ValidateDexFeeError::ValidatePaymentError(e.into_inner().to_string())))?; Ok(()) } @@ -1224,7 +1222,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat().map(|tx| tx.into())) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); 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(); @@ -1242,7 +1240,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); let amount = try_tx_fus!(sat_from_big_decimal(&taker_payment_args.amount, self.decimals())); let secret_hash = taker_payment_args.secret_hash.to_owned(); @@ -1261,10 +1259,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = maker_spends_payment_args.other_payment_tx.to_owned(); let taker_pub = try_tx_fus!(Public::from_slice(maker_spends_payment_args.other_pubkey)); let secret = maker_spends_payment_args.secret.to_owned(); @@ -1283,10 +1278,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = taker_spends_payment_args.other_payment_tx.to_owned(); let maker_pub = try_tx_fus!(Public::from_slice(taker_spends_payment_args.other_pubkey)); let secret = taker_spends_payment_args.secret.to_owned(); @@ -1305,7 +1297,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let tx = taker_refunds_payment_args.payment_tx.to_owned(); let maker_pub = try_tx_fus!(Public::from_slice(taker_refunds_payment_args.other_pubkey)); let secret_hash = taker_refunds_payment_args.secret_hash.to_owned(); @@ -1323,7 +1315,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat().map_err(TransactionErr::Plain)) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let tx = maker_refunds_payment_args.payment_tx.to_owned(); let taker_pub = try_tx_fus!(Public::from_slice(maker_refunds_payment_args.other_pubkey)); let secret_hash = maker_refunds_payment_args.secret_hash.to_owned(); @@ -1341,7 +1333,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -1353,10 +1345,9 @@ impl SwapOps for SlpToken { let min_block_number = validate_fee_args.min_block_number; let fut = async move { - try_s!( - coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount, min_block_number) - .await - ); + coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount, min_block_number) + .await + .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string())))?; Ok(()) }; Box::new(fut.boxed().compat()) @@ -1415,7 +1406,12 @@ impl SwapOps for SlpToken { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -1530,10 +1526,7 @@ impl WatcherOps for SlpToken { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } @@ -2119,6 +2112,7 @@ mod slp_tests { try_spv_proof_until: now_ms() / 1000 + 60, unique_swap_data: Vec::new(), swap_contract_address: None, + min_watcher_reward: None, }; block_on(fusd.validate_htlc(input)).unwrap(); } @@ -2253,6 +2247,7 @@ mod slp_tests { try_spv_proof_until: now_ms() / 1000 + 60, confirmations: 1, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let validity_err = block_on(fusd.validate_htlc(input)).unwrap_err(); match validity_err.into_inner() { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 19db7c9f32..1323a2a349 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -13,12 +13,13 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, HDAccountAddressId, - RawTransactionError, RawTransactionRequest, RawTransactionRes, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, - SwapOps, TradePreimageValue, TransactionFut, TxFeeDetails, TxMarshalingErr, ValidateAddressResult, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, - WithdrawResult, WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, + RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TxFeeDetails, + TxMarshalingErr, ValidateAddressResult, 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}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; @@ -1189,28 +1190,21 @@ where send_outputs_from_my_address(coin, vec![output]) } -pub fn send_maker_payment( - coin: T, - time_lock: u32, - taker_pub: &[u8], - secret_hash: &[u8], - amount: BigDecimal, - swap_unique_data: &[u8], -) -> TransactionFut +pub fn send_maker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { - let maker_htlc_key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let maker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let SwapPaymentOutputsResult { payment_address, outputs, } = try_tx_fus!(generate_swap_payment_outputs( &coin, - time_lock, + args.time_lock, maker_htlc_key_pair.public_slice(), - taker_pub, - secret_hash, - amount + args.other_pubkey, + args.secret_hash, + args.amount )); let send_fut = match &coin.as_ref().rpc_client { UtxoRpcClientEnum::Electrum(_) => Either::A(send_outputs_from_my_address(coin, outputs)), @@ -1227,28 +1221,21 @@ where Box::new(send_fut) } -pub fn send_taker_payment( - coin: T, - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - amount: BigDecimal, - swap_unique_data: &[u8], -) -> TransactionFut +pub fn send_taker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { - let taker_htlc_key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let taker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let SwapPaymentOutputsResult { payment_address, outputs, } = try_tx_fus!(generate_swap_payment_outputs( &coin, - time_lock, + args.time_lock, taker_htlc_key_pair.public_slice(), - maker_pub, - secret_hash, - amount + args.other_pubkey, + args.secret_hash, + args.amount )); let send_fut = match &coin.as_ref().rpc_client { @@ -1266,36 +1253,29 @@ where Box::new(send_fut) } -pub fn send_maker_spends_taker_payment( - coin: T, - taker_payment_tx: &[u8], - time_lock: u32, - taker_pub: &[u8], - secret: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_maker_spends_taker_payment(coin: T, args: SpendPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e))); + 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 key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() - .push_data(secret) + .push_data(args.secret) .push_opcode(Opcode::OP_0) .into_script(); let redeem_script = payment_script( - time_lock, - secret_hash, - &try_tx_fus!(Public::from_slice(taker_pub)), + args.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) @@ -1499,36 +1479,30 @@ pub fn create_taker_payment_refund_preimage( Box::new(fut.boxed().compat()) } -pub fn send_taker_spends_maker_payment( - coin: T, - maker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_taker_spends_maker_payment(coin: T, args: SpendPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); + 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 key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() - .push_data(secret) + .push_data(args.secret) .push_opcode(Opcode::OP_0) .into_script(); let redeem_script = payment_script( - time_lock, - secret_hash, - &try_tx_fus!(Public::from_slice(maker_pub)), + args.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) @@ -1566,32 +1540,26 @@ pub fn send_taker_spends_maker_payment( Box::new(fut.boxed().compat()) } -pub fn send_taker_refunds_payment( - coin: T, - taker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_taker_refunds_payment(coin: T, args: RefundPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); let mut prev_transaction: UtxoTx = - try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); + try_tx_fus!(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_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); } - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + 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 redeem_script = payment_script( - time_lock, - secret_hash, + args.time_lock, + args.secret_hash, key_pair.public(), - &try_tx_fus!(Public::from_slice(maker_pub)), + &try_tx_fus!(Public::from_slice(args.other_pubkey)), ) .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) @@ -1631,7 +1599,7 @@ pub fn send_taker_refunds_payment( pub fn send_taker_payment_refund_preimage( coin: &T, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, + watcher_refunds_payment_args: RefundPaymentArgs, ) -> TransactionFut { let coin = coin.clone(); let transaction: UtxoTx = try_tx_fus!( @@ -1648,31 +1616,24 @@ pub fn send_taker_payment_refund_preimage( Box::new(fut.boxed().compat()) } -pub fn send_maker_refunds_payment( - coin: T, - maker_payment_tx: &[u8], - time_lock: u32, - taker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_maker_refunds_payment(coin: T, args: RefundPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); + let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.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 key_pair = coin.derive_htlc_key_pair(swap_unique_data); + 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 redeem_script = payment_script( - time_lock, - secret_hash, + args.time_lock, + args.secret_hash, key_pair.public(), - &try_tx_fus!(Public::from_slice(taker_pub)), + &try_tx_fus!(Public::from_slice(args.other_pubkey)), ) .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) @@ -1904,67 +1865,77 @@ pub fn validate_fee( amount: &BigDecimal, min_block_number: u64, fee_addr: &[u8], -) -> Box + Send> { +) -> ValidatePaymentFut<()> { let amount = amount.clone(); - let address = try_fus!(address_from_raw_pubkey( + let address = try_f!(address_from_raw_pubkey( fee_addr, 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(ValidatePaymentError::TxDeserializationError)); - if !try_fus!(check_all_utxo_inputs_signed_by_pub(&tx, sender_pubkey)) { - return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address"))); + let inputs_signed_by_pub = try_f!(check_all_utxo_inputs_signed_by_pub(&tx, sender_pubkey)); + if !inputs_signed_by_pub { + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx(format!( + "{INVALID_SENDER_ERR_LOG}: Taker payment does not belong to the verified public key" + )) + .into(), + )); } + let fut = async move { - let amount = try_s!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); - let tx_from_rpc = try_s!( - coin.as_ref() - .rpc_client - .get_verbose_transaction(&tx.hash().reversed().into()) - .compat() - .await - ); + let amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; + let tx_from_rpc = coin + .as_ref() + .rpc_client + .get_verbose_transaction(&tx.hash().reversed().into()) + .compat() + .await?; - if try_s!(is_tx_confirmed_before_block(&coin, &tx_from_rpc, min_block_number).await) { - return ERR!( - "Fee tx {:?} confirmed before min_block {}", - tx_from_rpc, - min_block_number, - ); + let tx_confirmed_before_block = is_tx_confirmed_before_block(&coin, &tx_from_rpc, min_block_number) + .await + .map_to_mm(ValidatePaymentError::InternalError)?; + + if tx_confirmed_before_block { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} confirmed before min_block {}", + EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number + ))); } if tx_from_rpc.hex.0 != serialize(&tx).take() && tx_from_rpc.hex.0 != serialize_with_flags(&tx, SERIALIZE_TRANSACTION_WITNESS).take() { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx {:?} doesn't match tx data from rpc {:?}", - tx, - tx_from_rpc - ); + tx, tx_from_rpc + ))); } match tx.outputs.get(output_index) { Some(out) => { let expected_script_pubkey = Builder::build_p2pkh(&address.hash).to_bytes(); if out.script_pubkey != expected_script_pubkey { - return ERR!( - "Provided dex fee tx output script_pubkey doesn't match expected {:?} {:?}", - out.script_pubkey, - expected_script_pubkey - ); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Provided dex fee tx output script_pubkey doesn't match expected {:?} {:?}", + INVALID_RECEIVER_ERR_LOG, out.script_pubkey, expected_script_pubkey + ))); } if out.value < amount { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx output value is less than expected {:?} {:?}", - out.value, - amount - ); + out.value, amount + ))); } }, None => { - return ERR!("Provided dex fee tx {:?} does not have output {}", tx, output_index); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Provided dex fee tx {:?} does not have output {}", + tx, output_index + ))) }, } Ok(()) @@ -2015,9 +1986,9 @@ pub fn watcher_validate_taker_payment( let fut = async move { let inputs_signed_by_pub = check_all_utxo_inputs_signed_by_pub(&taker_payment_tx, &input.taker_pub)?; if !inputs_signed_by_pub { - return MmError::err(ValidatePaymentError::WrongPaymentTx( - "Taker payment does not belong to the verified public key".to_string(), - )); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{INVALID_SENDER_ERR_LOG}: Taker payment does not belong to the verified public key" + ))); } let taker_payment_locking_script = match taker_payment_tx.outputs.get(DEFAULT_SWAP_VOUT) { @@ -2031,8 +2002,7 @@ pub fn watcher_validate_taker_payment( if taker_payment_locking_script != Builder::build_p2sh(&dhash160(&expected_redeem).into()).to_bytes() { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx locking script {:?} doesn't match expected", - taker_payment_locking_script + "{INVALID_SCRIPT_ERR_LOG}: Payment tx locking script {taker_payment_locking_script:?} doesn't match expected" ))); } @@ -2057,7 +2027,7 @@ pub fn watcher_validate_taker_payment( if expected_redeem.as_slice() != redeem_script { return MmError::err(ValidatePaymentError::WrongPaymentTx( - "Taker payment tx locking script doesn't match with taker payment refund redeem script".to_string(), + format!("{INVALID_REFUND_TX_ERR_LOG}: Taker payment tx locking script doesn't match with taker payment refund redeem script") )); } diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 34746570f7..360fb2d566 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -24,13 +24,11 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, SignatureResult, SwapOps, - TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; @@ -296,86 +294,36 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { - utxo_common::send_maker_payment( - self.clone(), - maker_payment_args.time_lock, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - maker_payment_args.swap_unique_data, - ) + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_payment(self.clone(), maker_payment_args) } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { - utxo_common::send_taker_payment( - self.clone(), - taker_payment_args.time_lock, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - taker_payment_args.swap_unique_data, - ) + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment(self.clone(), taker_payment_args) } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_maker_spends_taker_payment( - self.clone(), - maker_spends_payment_args.other_payment_tx, - maker_spends_payment_args.time_lock, - maker_spends_payment_args.other_pubkey, - maker_spends_payment_args.secret, - maker_spends_payment_args.secret_hash, - maker_spends_payment_args.swap_unique_data, - ) + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_spends_taker_payment(self.clone(), maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment( - self.clone(), - taker_spends_payment_args.other_payment_tx, - taker_spends_payment_args.time_lock, - taker_spends_payment_args.other_pubkey, - taker_spends_payment_args.secret, - taker_spends_payment_args.secret_hash, - taker_spends_payment_args.swap_unique_data, - ) + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment(self.clone(), taker_spends_payment_args) } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment( - self.clone(), - taker_refunds_payment_args.payment_tx, - taker_refunds_payment_args.time_lock, - taker_refunds_payment_args.other_pubkey, - taker_refunds_payment_args.secret_hash, - taker_refunds_payment_args.swap_unique_data, - ) + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment( - self.clone(), - maker_refunds_payment_args.payment_tx, - maker_refunds_payment_args.time_lock, - maker_refunds_payment_args.other_pubkey, - maker_refunds_payment_args.secret_hash, - maker_refunds_payment_args.swap_unique_data, - ) + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -437,7 +385,12 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -577,11 +530,8 @@ impl WatcherOps for UtxoStandardCoin { } #[inline] - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_payment_refund_preimage(self, watcher_refunds_payment_args) + fn send_taker_payment_refund_preimage(&self, refund_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment_refund_preimage(self, refund_payment_args) } #[inline] diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index cec80eb40c..ae54d5fd57 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::coin_balance::HDAddressBalance; +use crate::coin_errors::ValidatePaymentError; use crate::hd_confirm_address::for_tests::MockableConfirmAddress; use crate::hd_confirm_address::{HDConfirmAddress, HDConfirmAddressError}; use crate::hd_wallet::HDAccountsMap; @@ -27,9 +28,10 @@ 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::INVALID_SENDER_ERR_LOG; use crate::{BlockHeightAndTime, CoinBalance, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, - SendMakerSpendsTakerPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, - TxMarshalingErr, ValidateFeeArgs}; + SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, TxMarshalingErr, + ValidateFeeArgs}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; use common::executor::Timer; use common::{block_on, now_ms, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -149,7 +151,7 @@ fn test_extract_secret() { let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); let expected_secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); let secret_hash = &*dhash160(&expected_secret); - let secret = block_on(coin.extract_secret(secret_hash, &tx_hex)).unwrap(); + let secret = block_on(coin.extract_secret(secret_hash, &tx_hex, false)).unwrap(); assert_eq!(secret, expected_secret); } @@ -159,7 +161,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { let coin = utxo_coin_for_test(client.into(), None, false); let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); let secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); - let maker_spends_payment_args = SendMakerSpendsTakerPaymentArgs { + let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx_hex, time_lock: 777, other_pubkey: &coin.my_public_key().unwrap().to_vec(), @@ -167,6 +169,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { secret_hash: &*dhash160(&secret), swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let tx_err = coin .send_maker_spends_taker_payment(maker_spends_payment_args) @@ -498,6 +501,7 @@ fn test_search_for_swap_tx_spend_electrum_was_spent() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -532,6 +536,7 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -2535,8 +2540,12 @@ fn test_validate_fee_wrong_sender() { min_block_number: 0, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("was sent from wrong address")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains(INVALID_SENDER_ERR_LOG)), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), + } } #[test] @@ -2560,8 +2569,11 @@ fn test_validate_fee_min_block() { min_block_number: 810329, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("confirmed before min_block")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), + } } #[test] diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 72c86f38a1..7a145473f3 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -17,16 +17,14 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructions, PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawRequest}; + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionDetails, TransactionEnum, TransactionFut, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -1115,7 +1113,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs<'_>) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let selfi = self.clone(); 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)); @@ -1139,7 +1137,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs<'_>) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let selfi = self.clone(); 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)); @@ -1163,10 +1161,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs<'_>, - ) -> TransactionFut { + 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; @@ -1197,10 +1192,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs<'_>, - ) -> TransactionFut { + 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; @@ -1231,10 +1223,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment( - &self, - taker_refunds_payment_args: SendTakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(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; @@ -1262,10 +1251,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_maker_refunds_payment( - &self, - maker_refunds_payment_args: SendMakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(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; @@ -1293,41 +1279,41 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn validate_fee( - &self, - validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let z_tx = match validate_fee_args.fee_tx { TransactionEnum::ZTransaction(t) => t.clone(), _ => panic!("Unexpected tx {:?}", validate_fee_args.fee_tx), }; - let amount_sat = try_fus!(sat_from_big_decimal(validate_fee_args.amount, self.utxo_arc.decimals)); + let amount_sat = try_f!(sat_from_big_decimal(validate_fee_args.amount, 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; let coin = self.clone(); let fut = async move { let tx_hash = H256::from(z_tx.txid().0).reversed(); - let tx_from_rpc = try_s!( - coin.utxo_rpc_client() - .get_verbose_transaction(&tx_hash.into()) - .compat() - .await - ); + let tx_from_rpc = coin + .utxo_rpc_client() + .get_verbose_transaction(&tx_hash.into()) + .compat() + .await + .map_err(|e| MmError::new(ValidatePaymentError::InvalidRpcResponse(e.into_inner().to_string())))?; + let mut encoded = Vec::with_capacity(1024); z_tx.write(&mut encoded).expect("Writing should not fail"); if encoded != tx_from_rpc.hex.0 { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Encoded transaction {:?} does not match the tx {:?} from RPC", - encoded, - tx_from_rpc - ); + encoded, tx_from_rpc + ))); } let block_height = match tx_from_rpc.height { Some(h) => { if h < min_block_number { - return ERR!("Dex fee tx {:?} confirmed before min block {}", z_tx, min_block_number); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee tx {:?} confirmed before min block {}", + z_tx, min_block_number + ))); } else { BlockHeight::from_u32(h as u32) } @@ -1346,29 +1332,34 @@ impl SwapOps for ZCoin { z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &coin.z_fields.dex_fee_addr, ); - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Dex fee was sent to the invalid address {}, expected {}", - encoded, - expected - ); + encoded, expected + ))); } if note.value != amount_sat { - return ERR!("Dex fee has invalid amount {}, expected {}", note.value, amount_sat); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid amount {}, expected {}", + note.value, amount_sat + ))); } if memo != expected_memo { - return ERR!("Dex fee has invalid memo {:?}, expected {:?}", memo, expected_memo); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid memo {:?}, expected {:?}", + memo, expected_memo + ))); } return Ok(()); } } - ERR!( + MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "The dex fee tx {:?} has no shielded outputs or outputs decryption failed", z_tx - ) + ))) }; Box::new(fut.boxed().compat()) @@ -1419,7 +1410,12 @@ impl SwapOps for ZCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -1539,10 +1535,7 @@ impl WatcherOps for ZCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 711dbdb064..c84402b902 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -59,6 +59,7 @@ use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId}; use bitcrypto::{dhash160, sha256}; +use coins::eth::Web3RpcError; use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::time_cache::DuplicateCache; @@ -67,6 +68,7 @@ use common::{bits256, calc_total_pages, log::{error, info}, now_ms, var, HttpStatusCode, PagingOptions, StatusCode}; use derive_more::Display; +use futures::compat::Future01CompatExt; use http::Response; use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_err_handle::prelude::*; @@ -682,6 +684,72 @@ pub fn dex_fee_amount_from_taker_coin(taker_coin: &MmCoinEnum, maker_coin: &str, dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &dex_fee_threshold) } +#[derive(Debug, Display)] +pub enum WatcherRewardError { + RPCError(Web3RpcError), + InvalidCoinType(String), +} + +// This needs discussion. We need a way to determine the watcher reward amount, and a way to validate it at watcher side so +// that watchers won't accept it if it's less than the expected amount. This has to be done for all coin types, because watcher rewards +// will be required by both parties even if only one side is ETH coin. Artem's suggestion was first calculating the reward for ETH and +// converting the value to other coins, which is what I'm planning to do. I based the values to be higher than the gas amounts used +// when the watcher spends the maker payment or refunds the taker payment. For the validation, I check if the reward is higher +// than a minimum amount using the min_watcher_reward method. I can't make an exact comparison because the gas price and relative +// price of the coins will be different when the taker/maker sends their payment and when the watcher receives the message. This should +// work fine if we pick the WATCHER_REWARD_GAS and MIN_WATCHER_REWARD_GAS constants good. The advantage of this is that the reward will +// be directly based on the amount of gas burned when the watcher will call the contract functions (Artem's idea was to make it slightly +// profitable for the watchers). The disadvantage is the comparison during the validations using a separate minimum value. If we want +// validations with exact values, there are two other ways I could think of: +// 1. Precalculating fixed rewards for all coins. The disadvantage is that the gas price and the relative price of coins will change over +// time and the reward will deviate from the actual gas used by the watchers, and we can't keep updating the values. +// 2. Picking the reward to be a percentage of the trade amount like the taker fee. The disadvantage is it will be extremely hard to +// pick the right ratio such that it will be slightly profitable for watchers. +pub async fn watcher_reward_amount( + coin: &MmCoinEnum, + other_coin: &MmCoinEnum, +) -> Result> { + const WATCHER_REWARD_GAS: u64 = 100_000; + watcher_reward_from_gas(coin, other_coin, WATCHER_REWARD_GAS).await +} + +pub async fn min_watcher_reward( + coin: &MmCoinEnum, + other_coin: &MmCoinEnum, +) -> Result> { + const MIN_WATCHER_REWARD_GAS: u64 = 70_000; + watcher_reward_from_gas(coin, other_coin, MIN_WATCHER_REWARD_GAS).await +} + +pub async fn watcher_reward_from_gas( + coin: &MmCoinEnum, + other_coin: &MmCoinEnum, + gas: u64, +) -> Result> { + match (coin, other_coin) { + (MmCoinEnum::EthCoin(coin), _) | (_, MmCoinEnum::EthCoin(coin)) => { + let mut attempts = 0; + loop { + match coin.get_gas_price().compat().await { + Ok(gas_price) => return Ok(gas * gas_price.as_u64()), + Err(err) => { + if attempts >= 3 { + return MmError::err(WatcherRewardError::RPCError(err.into_inner())); + } else { + attempts += 1; + Timer::sleep(10.).await; + } + }, + }; + } + }, + _ => Err(WatcherRewardError::InvalidCoinType( + "At least one coin must be ETH to use watcher reward".to_string(), + ) + .into()), + } +} + #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] pub struct NegotiationDataV1 { started_at: u64, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 7f12d7ec3d..00d068dbf5 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -14,11 +14,12 @@ use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::{MakerOrderBuilder, OrderConfirmationsSettings}; use crate::mm2::lp_price::fetch_swap_coins_price; -use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration}; +use crate::mm2::lp_swap::{broadcast_swap_message, min_watcher_reward, taker_payment_spend_duration, + watcher_reward_amount}; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MmCoinEnum, - PaymentInstructions, PaymentInstructionsErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, TradeFee, TradePreimageValue, - TransactionEnum, ValidateFeeArgs, ValidatePaymentInput}; + PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, TransactionEnum, ValidateFeeArgs, + ValidatePaymentInput}; use common::log::{debug, error, info, warn}; use common::{bits256, executor::Timer, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::SerializableSecp256k1Keypair; @@ -184,6 +185,7 @@ pub struct MakerSwapMut { taker_payment_spend_confirmed: bool, maker_payment_refund: Option, payment_instructions: Option, + watcher_reward: bool, } #[cfg(test)] @@ -387,6 +389,7 @@ impl MakerSwap { maker_payment_refund: None, taker_payment_spend_confirmed: false, payment_instructions: None, + watcher_reward: false, }), ctx, secret, @@ -562,6 +565,11 @@ impl MakerSwap { p2p_privkey: self.p2p_privkey.map(SerializableSecp256k1Keypair::from), }; + // This value will be true if both sides support & want to use watchers and either the taker or the maker coin is ETH. + // This requires a communication between the parties before the swap starts, which will be done during the ordermatch phase + // or via negotiation messages in the next sprint. + self.w().watcher_reward = false; + Ok((Some(MakerSwapCommand::Negotiate), vec![MakerSwapEvent::Started(data)])) } @@ -802,11 +810,25 @@ impl MakerSwap { }) .compat(); + let reward_amount = if self.r().watcher_reward { + let reward = match watcher_reward_amount(&self.maker_coin, &self.taker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(MakerSwapCommand::Finish), vec![ + MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let transaction = match transaction_f.await { Ok(res) => match res { Some(tx) => tx, None => { - let payment_fut = self.maker_coin.send_maker_payment(SendMakerPaymentArgs { + 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, other_pubkey: &*self.r().other_maker_coin_htlc_pub, @@ -815,6 +837,7 @@ impl MakerSwap { swap_contract_address: &self.r().data.maker_coin_swap_contract_address, swap_unique_data: &unique_data, payment_instructions: &self.r().payment_instructions, + watcher_reward: reward_amount, }); match payment_fut.compat().await { @@ -959,6 +982,20 @@ impl MakerSwap { ])); } + let min_watcher_reward = if self.r().watcher_reward { + let reward = match min_watcher_reward(&self.taker_coin, &self.maker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(MakerSwapCommand::Finish), vec![ + MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + 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, @@ -970,7 +1007,9 @@ impl MakerSwap { swap_contract_address: self.r().data.taker_coin_swap_contract_address.clone(), try_spv_proof_until: wait_taker_payment, confirmations, + min_watcher_reward, }; + let validated_f = self.taker_coin.validate_taker_payment(validate_input).compat(); if let Err(e) = validated_f.await { @@ -1006,17 +1045,16 @@ impl MakerSwap { ])); } - let spend_fut = self - .taker_coin - .send_maker_spends_taker_payment(SendMakerSpendsTakerPaymentArgs { - other_payment_tx: &self.r().taker_payment.clone().unwrap().tx_hex, - time_lock: self.taker_payment_lock.load(Ordering::Relaxed) as u32, - other_pubkey: &*self.r().other_taker_coin_htlc_pub, - secret: &self.r().data.secret.0, - secret_hash: &self.secret_hash(), - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, - swap_unique_data: &self.unique_swap_data(), - }); + 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, + other_pubkey: &*self.r().other_taker_coin_htlc_pub, + secret: &self.r().data.secret.0, + secret_hash: &self.secret_hash(), + swap_contract_address: &self.r().data.taker_coin_swap_contract_address, + swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, + }); let transaction = match spend_fut.compat().await { Ok(t) => t, @@ -1141,13 +1179,14 @@ impl MakerSwap { } } - let spend_fut = self.maker_coin.send_maker_refunds_payment(SendMakerRefundsPaymentArgs { + let spend_fut = self.maker_coin.send_maker_refunds_payment(RefundPaymentArgs { payment_tx: &maker_payment, time_lock: locktime as u32, other_pubkey: &*self.r().other_maker_coin_htlc_pub, secret_hash: self.secret_hash().as_slice(), swap_contract_address: &self.r().data.maker_coin_swap_contract_address, swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, }); let transaction = match spend_fut.compat().await { @@ -1304,6 +1343,7 @@ impl MakerSwap { let secret = selfi.r().data.secret.0; let unique_data = selfi.unique_swap_data(); + let watcher_reward = selfi.r().watcher_reward; let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -1313,6 +1353,7 @@ impl MakerSwap { search_from_block: taker_coin_start_block, swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; // check if the taker payment is not spent yet match selfi.taker_coin.search_for_swap_tx_spend_other(search_input).await { @@ -1336,7 +1377,7 @@ impl MakerSwap { selfi .taker_coin - .send_maker_spends_taker_payment(SendMakerSpendsTakerPaymentArgs { + .send_maker_spends_taker_payment(SpendPaymentArgs { other_payment_tx: taker_payment_hex, time_lock: timelock, other_pubkey: other_taker_coin_htlc_pub.as_slice(), @@ -1344,6 +1385,7 @@ impl MakerSwap { secret_hash: &selfi.secret_hash(), swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &selfi.unique_swap_data(), + watcher_reward, }) .compat() .await @@ -1374,6 +1416,7 @@ impl MakerSwap { let payment_instructions = self.r().payment_instructions.clone(); let maybe_maker_payment = self.r().maker_payment.clone(); + let watcher_reward = self.r().watcher_reward; let maker_payment = match maybe_maker_payment { Some(tx) => tx.tx_hex.0, None => { @@ -1407,6 +1450,7 @@ impl MakerSwap { search_from_block: maker_coin_start_block, swap_contract_address: &maker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; // validate that maker payment is not spent match self.maker_coin.search_for_swap_tx_spend_my(search_input).await { @@ -1440,13 +1484,14 @@ impl MakerSwap { if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund_htlc { return ERR!("Too early to refund, wait until {}", now_ms() / 1000 + seconds_to_wait); } - let fut = self.maker_coin.send_maker_refunds_payment(SendMakerRefundsPaymentArgs { + let fut = self.maker_coin.send_maker_refunds_payment(RefundPaymentArgs { payment_tx: &maker_payment, time_lock: maker_payment_lock, other_pubkey: other_maker_coin_htlc_pub.as_slice(), secret_hash: secret_hash.as_slice(), swap_contract_address: &maker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }); let transaction = match fut.compat().await { @@ -2387,7 +2432,6 @@ mod maker_swap_tests { let taker_coin = MmCoinEnum::Test(TestCoin::default()); let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); let err = block_on(maker_swap.recover_funds()).expect_err("Expected an error"); - println!("{}", err); assert!(err.contains("Taker payment was already refunded")); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_OTHER_CALLED }); @@ -2493,7 +2537,6 @@ mod maker_swap_tests { let taker_coin = MmCoinEnum::Test(TestCoin::default()); let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); let err = block_on(maker_swap.recover_funds()).expect_err("Expected an error"); - println!("{}", err); assert!(err.contains("Taker payment was already spent")); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_OTHER_CALLED }); 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 7135c13306..a816695d84 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -448,7 +448,8 @@ async fn convert_maker_to_taker_events( return events; }, MakerSwapEvent::TakerPaymentSpent(tx_ident) => { - let secret = match maker_coin.extract_secret(&secret_hash.0, &tx_ident.tx_hex).await { + //Is the watcher_reward argument important here? + let secret = match maker_coin.extract_secret(&secret_hash.0, &tx_ident.tx_hex, false).await { Ok(secret) => H256Json::from(secret.as_slice()), Err(e) => { push_event!(TakerSwapEvent::TakerPaymentWaitForSpendFailed(ERRL!("{}", e).into())); @@ -528,7 +529,7 @@ mod tests { #[test] fn test_recreate_taker_swap() { - TestCoin::extract_secret.mock_safe(|_coin, _secret_hash, _spend_tx| { + TestCoin::extract_secret.mock_safe(|_coin, _secret_hash, _spend_tx, _watcher_reward| { let secret = hex::decode("23a6bb64bc0ab2cc14cb84277d8d25134b814e5f999c66e578c9bba3c5e2d3a4").unwrap(); MockResult::Return(Box::pin(async move { Ok(secret) })) }); diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index 26067ae625..09b42f2863 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -1,10 +1,9 @@ -use super::{broadcast_p2p_tx_msg, lp_coinfind, taker_payment_spend_deadline, tx_helper_topic, H256Json, SwapsContext, - WAIT_CONFIRM_INTERVAL}; +use super::{broadcast_p2p_tx_msg, get_payment_locktime, lp_coinfind, min_watcher_reward, taker_payment_spend_deadline, + tx_helper_topic, H256Json, SwapsContext, WAIT_CONFIRM_INTERVAL}; use crate::mm2::MmError; use async_trait::async_trait; -use coins::{CanRefundHtlc, FoundSwapTxSpend, MmCoinEnum, SendMakerPaymentSpendPreimageInput, - SendWatcherRefundsPaymentArgs, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput}; +use coins::{CanRefundHtlc, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, SendMakerPaymentSpendPreimageInput, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput}; use common::executor::{AbortSettings, SpawnAbortable, Timer}; use common::log::{debug, error, info}; use common::state_machine::prelude::*; @@ -33,6 +32,7 @@ struct WatcherContext { verified_pub: Vec, data: TakerSwapWatcherData, conf: WatcherConf, + watcher_reward: bool, } impl WatcherContext { @@ -141,6 +141,7 @@ enum WatcherError { MakerPaymentSpendFailed(String), MakerPaymentCouldNotBeFound(String), TakerPaymentRefundFailed(String), + InternalError(String), } impl Stopped { @@ -232,6 +233,20 @@ impl State for ValidateTakerPayment { ))); } + let min_watcher_reward = if watcher_ctx.watcher_reward { + let reward = match min_watcher_reward(&watcher_ctx.taker_coin, &watcher_ctx.maker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InternalError(err.into_inner().to_string()).into(), + ))) + }, + }; + Some(reward) + } else { + None + }; + let validate_input = WatcherValidatePaymentInput { payment_tx: taker_payment_hex.clone(), taker_payment_refund_preimage: watcher_ctx.data.taker_payment_refund_preimage.clone(), @@ -244,6 +259,7 @@ impl State for ValidateTakerPayment { secret_hash: watcher_ctx.data.secret_hash.clone(), try_spv_proof_until: taker_payment_spend_deadline, confirmations, + min_watcher_reward, }; let validated_f = watcher_ctx @@ -276,6 +292,7 @@ impl State for WaitForTakerPaymentSpend { secret_hash: &watcher_ctx.data.secret_hash, tx: &self.taker_payment_hex, search_from_block: watcher_ctx.data.taker_coin_start_block, + watcher_reward: watcher_ctx.watcher_reward, }; loop { @@ -349,7 +366,7 @@ impl State for WaitForTakerPaymentSpend { let tx_hex = tx.tx_hex(); let secret = match watcher_ctx .taker_coin - .extract_secret(&watcher_ctx.data.secret_hash, &tx_hex) + .extract_secret(&watcher_ctx.data.secret_hash, &tx_hex, watcher_ctx.watcher_reward) .await { Ok(bytes) => H256Json::from(bytes.as_slice()), @@ -377,6 +394,7 @@ impl State for SpendMakerPayment { secret: &self.secret.0, secret_hash: &watcher_ctx.data.secret_hash, taker_pub: &watcher_ctx.verified_pub, + watcher_reward: watcher_ctx.watcher_reward, }); let transaction = match spend_fut.compat().await { @@ -441,13 +459,14 @@ impl State for RefundTakerPayment { let refund_fut = watcher_ctx .taker_coin - .send_taker_payment_refund_preimage(SendWatcherRefundsPaymentArgs { + .send_taker_payment_refund_preimage(RefundPaymentArgs { payment_tx: &watcher_ctx.data.taker_payment_refund_preimage, 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, swap_unique_data: &[], + watcher_reward: watcher_ctx.watcher_reward, }); let transaction = match refund_fut.compat().await { Ok(t) => t, @@ -557,6 +576,15 @@ impl Drop for SwapWatcherLock { } fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, verified_pub: Vec) { + // TODO: See if more data validations can be added here + if watcher_data.lock_duration != get_payment_locktime() + && watcher_data.lock_duration != get_payment_locktime() * 4 + && watcher_data.lock_duration != get_payment_locktime() * 10 + { + error!("Invalid lock duration"); + return; + } + let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); if swap_ctx.swap_msgs.lock().unwrap().contains_key(&watcher_data.uuid) { return; @@ -595,6 +623,11 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri }, }; + if !taker_coin.is_supported_by_watchers() || !maker_coin.is_supported_by_watchers() { + log!("One of the coins or their contracts does not support watchers"); + return; + } + log_tag!( ctx, ""; @@ -605,6 +638,8 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri ); let conf = json::from_value::(ctx.conf["watcher_conf"].clone()).unwrap_or_default(); + //let watcher_reward = taker_coin.is_eth() || maker_coin.is_eth(); + let watcher_reward = false; let watcher_ctx = WatcherContext { ctx, maker_coin, @@ -612,6 +647,7 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri verified_pub, data: watcher_data, conf, + watcher_reward, }; let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(watcher_ctx); state_machine.run(ValidateTakerFee {}).await; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index f87cfb1b5e..1a1e583282 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -13,12 +13,11 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_mes use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::{MatchBy, OrderConfirmationsSettings, TakerAction, TakerOrderBuilder}; use crate::mm2::lp_price::fetch_swap_coins_price; -use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, tx_helper_topic, wait_for_maker_payment_conf_duration, - TakerSwapWatcherData}; +use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, min_watcher_reward, tx_helper_topic, + wait_for_maker_payment_conf_duration, watcher_reward_amount, TakerSwapWatcherData}; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MmCoinEnum, - PaymentInstructions, PaymentInstructionsErr, SearchForSwapTxSpendInput, SendSpendPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, TradeFee, - TradePreimageValue, ValidatePaymentInput}; + PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, ValidatePaymentInput}; use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -511,6 +510,7 @@ pub struct TakerSwapMut { taker_payment_refund: Option, secret_hash: BytesJson, secret: H256Json, + watcher_reward: bool, payment_instructions: Option, } @@ -850,6 +850,7 @@ impl TakerSwap { taker_payment_refund: None, secret_hash: BytesJson::default(), secret: H256Json::default(), + watcher_reward: false, payment_instructions: None, }), ctx, @@ -1030,6 +1031,11 @@ impl TakerSwap { p2p_privkey: self.p2p_privkey.map(SerializableSecp256k1Keypair::from), }; + // This value will be true if both sides support & want to use watchers and either the taker or the maker coin is ETH. + // This requires a communication between the parties before the swap starts, which will be done during the ordermatch phase + // or via negotiation messages in the next sprint. + self.w().watcher_reward = false; + Ok((Some(TakerSwapCommand::Negotiate), vec![TakerSwapEvent::Started(data)])) } @@ -1131,6 +1137,7 @@ impl TakerSwap { let taker_coin_swap_contract_bytes = taker_coin_swap_contract_addr .clone() .map_or_else(Vec::new, |bytes| bytes.0); + let my_negotiation_data = self.get_my_negotiation_data( maker_data.secret_hash().to_vec(), maker_coin_swap_contract_bytes, @@ -1320,6 +1327,20 @@ impl TakerSwap { } info!("After wait confirm"); + let min_watcher_reward = if self.r().watcher_reward { + let reward = match min_watcher_reward(&self.maker_coin, &self.taker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::TakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let validate_input = ValidatePaymentInput { payment_tx: self.r().maker_payment.clone().unwrap().tx_hex.0, time_lock_duration: self.r().data.lock_duration, @@ -1331,6 +1352,7 @@ impl TakerSwap { try_spv_proof_until: self.r().data.maker_payment_wait, confirmations, unique_swap_data: self.unique_swap_data(), + min_watcher_reward, }; let validated = self.maker_coin.validate_maker_payment(validate_input).compat().await; @@ -1398,6 +1420,21 @@ impl TakerSwap { amount: &self.taker_amount.to_decimal(), payment_instructions: &self.r().payment_instructions, }); + + let reward_amount = if self.r().watcher_reward { + let reward = match watcher_reward_amount(&self.taker_coin, &self.maker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::TakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let transaction = match f.compat().await { Ok(res) => match res { Some(tx) => tx, @@ -1406,7 +1443,7 @@ impl TakerSwap { Ok(_) => self.r().data.started_at as u32, Err(_) => self.r().data.taker_payment_lock as u32, }; - let payment_fut = self.taker_coin.send_taker_payment(SendTakerPaymentArgs { + let payment_fut = self.taker_coin.send_taker_payment(SendPaymentArgs { time_lock_duration: self.r().data.lock_duration, time_lock, other_pubkey: &*self.r().other_taker_coin_htlc_pub, @@ -1415,6 +1452,7 @@ impl TakerSwap { swap_contract_address: &self.r().data.taker_coin_swap_contract_address, swap_unique_data: &unique_data, payment_instructions: &self.r().payment_instructions, + watcher_reward: reward_amount, }); match payment_fut.compat().await { @@ -1598,7 +1636,12 @@ impl TakerSwap { }; let secret_hash = self.r().secret_hash.clone(); - let secret = match self.taker_coin.extract_secret(&secret_hash.0, &tx_ident.tx_hex).await { + let watcher_reward = self.r().watcher_reward; + let secret = match self + .taker_coin + .extract_secret(&secret_hash.0, &tx_ident.tx_hex, watcher_reward) + .await + { Ok(bytes) => H256Json::from(bytes.as_slice()), Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![ @@ -1622,7 +1665,7 @@ impl TakerSwap { TakerSwapEvent::MakerPaymentSpendFailed("Explicit test failure".into()), ])); } - let spend_fut = self.maker_coin.send_taker_spends_maker_payment(SendSpendPaymentArgs { + 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, other_pubkey: &*self.r().other_maker_coin_htlc_pub, @@ -1630,6 +1673,7 @@ impl TakerSwap { secret_hash: &self.r().secret_hash.0, swap_contract_address: &self.r().data.maker_coin_swap_contract_address, swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, }); let transaction = match spend_fut.compat().await { Ok(t) => t, @@ -1715,13 +1759,14 @@ impl TakerSwap { } } - let refund_fut = self.taker_coin.send_taker_refunds_payment(SendTakerRefundsPaymentArgs { + let refund_fut = self.taker_coin.send_taker_refunds_payment(RefundPaymentArgs { payment_tx: &taker_payment, time_lock: locktime as u32, other_pubkey: &*self.r().other_taker_coin_htlc_pub, secret_hash: &self.r().secret_hash.0, swap_contract_address: &self.r().data.taker_coin_swap_contract_address, swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, }); let transaction = match refund_fut.compat().await { @@ -1879,6 +1924,7 @@ impl TakerSwap { let taker_payment_lock = self.r().data.taker_payment_lock; let taker_coin_start_block = self.r().data.taker_coin_start_block; let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); + let watcher_reward = self.r().watcher_reward; let unique_data = self.unique_swap_data(); macro_rules! check_maker_payment_is_not_spent { @@ -1892,6 +1938,7 @@ impl TakerSwap { search_from_block: maker_coin_start_block, swap_contract_address: &maker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; match self.maker_coin.search_for_swap_tx_spend_other(search_input).await { @@ -1951,17 +1998,16 @@ impl TakerSwap { let secret = self.r().secret.0; let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); - let fut = self - .maker_coin - .send_taker_spends_maker_payment(SendTakerSpendsMakerPaymentArgs { - other_payment_tx: &maker_payment, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, - other_pubkey: other_maker_coin_htlc_pub.as_slice(), - secret: &secret, - secret_hash: &secret_hash, - swap_contract_address: &maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - }); + 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, + other_pubkey: other_maker_coin_htlc_pub.as_slice(), + secret: &secret, + secret_hash: &secret_hash, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + watcher_reward: self.r().watcher_reward, + }); let transaction = match fut.compat().await { Ok(t) => t, @@ -1994,6 +2040,7 @@ impl TakerSwap { search_from_block: taker_coin_start_block, swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; let taker_payment_spend = try_s!(self.taker_coin.search_for_swap_tx_spend_my(search_input).await); @@ -2003,19 +2050,22 @@ impl TakerSwap { check_maker_payment_is_not_spent!(); let secret_hash = self.r().secret_hash.clone(); let tx_hex = tx.tx_hex(); - let secret = try_s!(self.taker_coin.extract_secret(&secret_hash.0, &tx_hex).await); - - let fut = self - .maker_coin - .send_taker_spends_maker_payment(SendTakerSpendsMakerPaymentArgs { - other_payment_tx: &maker_payment, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, - other_pubkey: other_maker_coin_htlc_pub.as_slice(), - secret: &secret, - secret_hash: &secret_hash, - swap_contract_address: &maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - }); + let secret = try_s!( + self.taker_coin + .extract_secret(&secret_hash.0, &tx_hex, watcher_reward) + .await + ); + + 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, + other_pubkey: other_maker_coin_htlc_pub.as_slice(), + secret: &secret, + secret_hash: &secret_hash, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + watcher_reward: self.r().watcher_reward, + }); let transaction = match fut.compat().await { Ok(t) => t, @@ -2054,13 +2104,14 @@ impl TakerSwap { return ERR!("Too early to refund, wait until {}", now_ms() / 1000 + seconds_to_wait); } - let fut = self.taker_coin.send_taker_refunds_payment(SendTakerRefundsPaymentArgs { + let fut = self.taker_coin.send_taker_refunds_payment(RefundPaymentArgs { payment_tx: &taker_payment, time_lock: taker_payment_lock as u32, other_pubkey: other_taker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }); let transaction = match fut.compat().await { @@ -2574,7 +2625,7 @@ mod taker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); - TestCoin::extract_secret.mock_safe(|_, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); + TestCoin::extract_secret.mock_safe(|_, _, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { @@ -2683,7 +2734,7 @@ mod taker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); - TestCoin::extract_secret.mock_safe(|_, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); + TestCoin::extract_secret.mock_safe(|_, _, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); static mut SEARCH_TX_SPEND_CALLED: bool = false; TestCoin::search_for_swap_tx_spend_my.mock_safe(|_, _| { 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 9e81d2c3b3..fe4145e002 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -1,5 +1,7 @@ pub use common::{block_on, now_ms}; pub use mm2_number::MmNumber; +use mm2_test_helpers::for_tests::ETH_SEPOLIA_NODE; +use mm2_test_helpers::for_tests::ETH_SEPOLIA_SWAP_CONTRACT; pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, check_stats_swap_status, enable_native_bch, mm_dump, MarketMakerIt, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; @@ -163,15 +165,16 @@ pub fn fill_eth(to_addr: &str) { .unwrap(); } -pub fn generate_eth_coin_with_random_privkey() -> EthCoin { +// Generates an ethereum coin in the sepolia network with the given seed +pub fn generate_eth_coin_with_seed(seed: &str) -> EthCoin { let req = json!({ "method": "enable", "coin": "ETH", - "urls": ETH_DEV_NODES, - "swap_contract_address": ETH_DEV_SWAP_CONTRACT, + "urls": ETH_SEPOLIA_NODE, + "swap_contract_address": ETH_SEPOLIA_SWAP_CONTRACT, }); - let priv_key = random_secp256k1_secret(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); + 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( &MM_CTX, "ETH", @@ -187,8 +190,8 @@ pub fn generate_jst_with_seed(seed: &str) -> EthCoin { let req = json!({ "method": "enable", "coin": "JST", - "urls": ETH_DEV_NODES, - "swap_contract_address": ETH_DEV_SWAP_CONTRACT, + "urls": ETH_SEPOLIA_NODE, + "swap_contract_address": ETH_SEPOLIA_SWAP_CONTRACT, }); let keypair = key_pair_from_seed(seed).unwrap(); @@ -200,7 +203,7 @@ pub fn generate_jst_with_seed(seed: &str) -> EthCoin { &req, CoinProtocol::ERC20 { platform: "ETH".into(), - contract_address: String::from("0x2b294F029Fde858b2c62184e8390591755521d8E"), + contract_address: String::from("0x948BF5172383F1Bc0Fdf3aBe0630b855694A5D2c"), }, priv_key_policy, )) 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 891a817503..d638f285ce 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -5,9 +5,8 @@ use bitcrypto::dhash160; use chain::OutPoint; use coins::utxo::rpc_clients::UnspentInfo; use coins::utxo::{GetUtxoListOps, UtxoCommonOps}; -use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; +use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, + SpendPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; use common::{block_on, now_ms}; use futures01::Future; use mm2_number::{BigDecimal, MmNumber}; @@ -28,7 +27,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -37,19 +36,21 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -68,6 +69,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -99,7 +101,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -108,19 +110,21 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -139,6 +143,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -155,7 +160,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { let secret_hash = dhash160(&secret); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_pubkey, @@ -164,13 +169,14 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_spends_payment_args = SendMakerSpendsTakerPaymentArgs { + let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_pubkey, @@ -178,6 +184,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { secret_hash: secret_hash.as_slice(), swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let spend_tx = coin .send_maker_spends_taker_payment(maker_spends_payment_args) @@ -196,6 +203,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -212,7 +220,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { let time_lock = (now_ms() / 1000) as u32 - 3600; let secret_hash = dhash160(&secret); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_pubkey, @@ -221,13 +229,14 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_pubkey, @@ -235,6 +244,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { secret_hash: secret_hash.as_slice(), swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let spend_tx = coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -253,6 +263,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -272,7 +283,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { let mut unspents = vec![]; let mut sent_tx = vec![]; for i in 0..100 { - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: time_lock + i, other_pubkey: my_pubkey, @@ -281,6 +292,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); if let TransactionEnum::UtxoTx(tx) = tx { diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 838a148095..9233477c00 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -6,10 +6,9 @@ use coins::utxo::qtum::{qtum_coin_with_priv_key, QtumCoin}; use coins::utxo::rpc_clients::UtxoRpcClientEnum; use coins::utxo::utxo_common::big_decimal_from_sat; use coins::utxo::{UtxoActivationParams, UtxoCommonOps}; -use coins::{CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SwapOps, TradePreimageValue, TransactionEnum, ValidatePaymentInput}; +use coins::{CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, TradePreimageValue, + TransactionEnum, ValidatePaymentInput}; use common::log::debug; use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; @@ -182,7 +181,7 @@ fn test_taker_spends_maker_payment() { let secret = &[1; 32]; let secret_hash = dhash160(secret).to_vec(); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -191,6 +190,7 @@ fn test_taker_spends_maker_payment() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -217,9 +217,10 @@ fn test_taker_spends_maker_payment() { try_spv_proof_until: wait_until + 30, confirmations, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; taker_coin.validate_maker_payment(input).wait().unwrap(); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &maker_pub, @@ -227,6 +228,7 @@ fn test_taker_spends_maker_payment() { secret_hash: &secret_hash, swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = taker_coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -275,7 +277,7 @@ fn test_maker_spends_taker_payment() { let secret = &[1; 32]; let secret_hash = dhash160(secret).to_vec(); let amount = BigDecimal::try_from(0.2).unwrap(); - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &maker_pub, @@ -284,6 +286,7 @@ fn test_maker_spends_taker_payment() { swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = taker_coin.send_taker_payment(taker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -310,9 +313,10 @@ fn test_maker_spends_taker_payment() { try_spv_proof_until: wait_until + 30, confirmations, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; maker_coin.validate_taker_payment(input).wait().unwrap(); - let maker_spends_payment_args = SendMakerSpendsTakerPaymentArgs { + let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, @@ -320,6 +324,7 @@ fn test_maker_spends_taker_payment() { secret_hash: &secret_hash, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = maker_coin .send_maker_spends_taker_payment(maker_spends_payment_args) @@ -357,7 +362,7 @@ fn test_maker_refunds_payment() { let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); - let maker_payment = SendMakerPaymentArgs { + let maker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -366,6 +371,7 @@ fn test_maker_refunds_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(maker_payment).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -382,13 +388,14 @@ fn test_maker_refunds_payment() { let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, secret_hash, swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -417,7 +424,7 @@ fn test_taker_refunds_payment() { let maker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &maker_pub, @@ -426,6 +433,7 @@ fn test_taker_refunds_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_taker_payment(taker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -442,13 +450,14 @@ fn test_taker_refunds_payment() { let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); - let taker_refunds_payment_args = SendTakerRefundsPaymentArgs { + let taker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &maker_pub, secret_hash, swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_taker_refunds_payment(taker_refunds_payment_args) @@ -474,7 +483,7 @@ fn test_check_if_my_payment_sent() { let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -483,6 +492,7 @@ fn test_check_if_my_payment_sent() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -524,7 +534,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { let secret = &[1; 32]; let secret_hash = dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: taker_pub, @@ -533,6 +543,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -547,7 +558,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) .wait() .unwrap(); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: maker_pub, @@ -555,6 +566,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { secret_hash: secret_hash.as_slice(), swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = taker_coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -578,6 +590,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { search_from_block, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let actual = block_on(maker_coin.search_for_swap_tx_spend_my(search_input)); let expected = Ok(Some(FoundSwapTxSpend::Spent(spend))); @@ -594,7 +607,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { let secret = &[1; 32]; let secret_hash = &*dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -603,6 +616,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -617,13 +631,14 @@ fn test_search_for_swap_tx_spend_maker_refunded() { .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, secret_hash, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = maker_coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -647,6 +662,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { search_from_block, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let actual = block_on(maker_coin.search_for_swap_tx_spend_my(search_input)); let expected = Ok(Some(FoundSwapTxSpend::Refunded(refund))); @@ -663,7 +679,7 @@ fn test_search_for_swap_tx_spend_not_spent() { let secret = &[1; 32]; let secret_hash = &*dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -672,6 +688,7 @@ fn test_search_for_swap_tx_spend_not_spent() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -695,6 +712,7 @@ fn test_search_for_swap_tx_spend_not_spent() { search_from_block, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let actual = block_on(maker_coin.search_for_swap_tx_spend_my(search_input)); // maker payment hasn't been spent or refunded yet @@ -713,7 +731,7 @@ fn test_wait_for_tx_spend() { let secret = &[1; 32]; let secret_hash = dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: taker_pub, @@ -722,6 +740,7 @@ fn test_wait_for_tx_spend() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -762,7 +781,7 @@ fn test_wait_for_tx_spend() { let payment_hex = payment_tx_hex.clone(); thread::spawn(move || { thread::sleep(Duration::from_secs(5)); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_hex, time_lock: timelock, other_pubkey: &maker_pub_c, @@ -770,6 +789,7 @@ fn test_wait_for_tx_spend() { secret_hash: secret_hash.as_slice(), swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = taker_coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -1028,7 +1048,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount.to_decimal(), &[]) .wait() .expect("!send_taker_fee"); - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -1037,6 +1057,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let _taker_payment_tx = coin @@ -1426,7 +1447,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let maker_payment = SendMakerPaymentArgs { + let maker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -1435,19 +1456,21 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -1466,6 +1489,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -1481,7 +1505,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment = SendTakerPaymentArgs { + let taker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -1490,19 +1514,21 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -1521,6 +1547,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .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 98ea02ce44..6bd474ff32 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -1,18 +1,20 @@ -use crate::docker_tests::docker_tests_common::{eth_distributor, generate_eth_coin_with_random_privkey, - generate_jst_with_seed}; +use crate::docker_tests::docker_tests_common::{eth_distributor, generate_eth_coin_with_seed, generate_jst_with_seed}; use crate::integration_tests_common::*; use crate::{generate_utxo_coin_with_privkey, generate_utxo_coin_with_random_privkey, random_secp256k1_secret, SecretKey}; use coins::coin_errors::ValidatePaymentError; -use coins::utxo::UtxoCommonOps; -use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoinEnum, SearchForSwapTxSpendInput, SendTakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SwapOps, WatcherOps, WatcherValidateTakerFeeInput, - EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; +use coins::utxo::{dhash160, UtxoCommonOps}; +use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoinEnum, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendPaymentArgs, SwapOps, WatcherOps, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + EARLY_CONFIRMATION_ERR_LOG, INSUFFICIENT_WATCHER_REWARD_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_ms, DEX_FEE_ADDR_RAW_PUBKEY}; +use crypto::privkey::key_pair_from_secret; use futures01::Future; -use mm2_main::mm2::lp_swap::{dex_fee_amount_from_taker_coin, get_payment_locktime, 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_from_taker_coin, get_payment_locktime, min_watcher_reward, + watcher_reward_amount, 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_conf, eth_testnet_conf, mm_dump, my_balance, mycoin1_conf, @@ -30,7 +32,8 @@ fn enable_eth_and_jst(mm_node: &MarketMakerIt) { "ETH", ETH_SEPOLIA_NODE, ETH_SEPOLIA_SWAP_CONTRACT, - Some(ETH_SEPOLIA_SWAP_CONTRACT) + Some(ETH_SEPOLIA_SWAP_CONTRACT), + true ))); dbg!(block_on(enable_eth_coin( @@ -38,7 +41,8 @@ fn enable_eth_and_jst(mm_node: &MarketMakerIt) { "JST", ETH_SEPOLIA_NODE, ETH_SEPOLIA_SWAP_CONTRACT, - Some(ETH_SEPOLIA_SWAP_CONTRACT) + Some(ETH_SEPOLIA_SWAP_CONTRACT), + true ))); } @@ -55,7 +59,7 @@ fn test_watcher_spends_maker_payment_spend_eth_erc20() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -81,6 +85,7 @@ fn test_watcher_spends_maker_payment_spend_eth_erc20() { let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_before = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_before = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); @@ -96,12 +101,101 @@ fn test_watcher_spends_maker_payment_spend_eth_erc20() { let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_after = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_after = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; let volume = BigDecimal::from_str("0.01").unwrap(); assert_eq!(alice_jst_balance_before - volume.clone(), alice_jst_balance_after); assert_eq!(bob_jst_balance_before + volume.clone(), bob_jst_balance_after); assert_eq!(alice_eth_balance_before + volume.clone(), alice_eth_balance_after); assert_eq!(bob_eth_balance_before - volume.clone(), bob_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); +} + +#[test] +#[ignore] +fn test_two_watchers_spend_maker_payment_eth_erc20() { + let coins = json!([eth_testnet_conf(), eth_jst_conf(ETH_SEPOLIA_TOKEN_CONTRACT)]); + + let alice_passphrase = + String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let alice_conf = Mm2TestConf::seednode_using_watchers(&alice_passphrase, &coins); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), 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()); + + let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let watcher1_passphrase = + String::from("also shoot benefit prefer juice shell thank unfair canal monkey style afraid"); + let watcher1_conf = + Mm2TestConf::watcher_light_node(&watcher1_passphrase, &coins, &[&mm_alice.ip.to_string()], WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 0., + refund_start_factor: 1.5, + search_interval: 1.0, + }) + .conf; + let mut mm_watcher1 = MarketMakerIt::start(watcher1_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); + let (_watcher_dump_log, _watcher_dump_dashboard) = mm_dump(&mm_watcher1.log_path); + + let watcher2_passphrase = + String::from("also shoot benefit shell thank prefer juice unfair canal monkey style afraid"); + let watcher2_conf = + Mm2TestConf::watcher_light_node(&watcher2_passphrase, &coins, &[&mm_alice.ip.to_string()], WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 0., + refund_start_factor: 1.5, + search_interval: 1.0, + }) + .conf; + let mut mm_watcher2 = MarketMakerIt::start(watcher2_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); + let (_watcher_dump_log, _watcher_dump_dashboard) = mm_dump(&mm_watcher1.log_path); + + enable_eth_and_jst(&mm_alice); + enable_eth_and_jst(&mm_bob); + enable_eth_and_jst(&mm_watcher1); + enable_eth_and_jst(&mm_watcher2); + + let alice_eth_balance_before = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); + let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let bob_eth_balance_before = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); + let bob_jst_balance_before = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher1_eth_balance_before = block_on(my_balance(&mm_watcher1, "ETH")).balance; + let watcher2_eth_balance_before = block_on(my_balance(&mm_watcher2, "ETH")).balance; + + block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); + + block_on(mm_alice.wait_for_log(180., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); + block_on(mm_alice.stop()).unwrap(); + block_on(mm_watcher1.wait_for_log(180., |log| log.contains(MAKER_PAYMENT_SPEND_SENT_LOG))).unwrap(); + block_on(mm_watcher2.wait_for_log(180., |log| log.contains(MAKER_PAYMENT_SPEND_SENT_LOG))).unwrap(); + thread::sleep(Duration::from_secs(25)); + + let mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), alice_conf.rpc_password.clone(), None).unwrap(); + enable_eth_and_jst(&mm_alice); + + let alice_eth_balance_after = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); + let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let bob_eth_balance_after = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); + let bob_jst_balance_after = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher1_eth_balance_after = block_on(my_balance(&mm_watcher1, "ETH")).balance; + let watcher2_eth_balance_after = block_on(my_balance(&mm_watcher2, "ETH")).balance; + + let volume = BigDecimal::from_str("0.01").unwrap(); + assert_eq!(alice_jst_balance_before - volume.clone(), alice_jst_balance_after); + assert_eq!(bob_jst_balance_before + volume.clone(), bob_jst_balance_after); + assert_eq!(alice_eth_balance_before + volume.clone(), alice_eth_balance_after); + assert_eq!(bob_eth_balance_before - volume.clone(), bob_eth_balance_after); + if watcher1_eth_balance_after > watcher1_eth_balance_before { + assert_eq!(watcher2_eth_balance_after, watcher2_eth_balance_after); + } + if watcher2_eth_balance_after > watcher2_eth_balance_before { + assert_eq!(watcher1_eth_balance_after, watcher1_eth_balance_after); + } } #[test] @@ -117,7 +211,7 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -144,6 +238,7 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_before = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_before = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("JST", "ETH")], 1., 1., 0.01)); @@ -159,6 +254,7 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_after = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_after = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; let volume = BigDecimal::from_str("0.01").unwrap(); @@ -166,6 +262,48 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { assert_eq!(bob_jst_balance_before - volume.clone(), bob_jst_balance_after); assert_eq!(alice_eth_balance_before - volume.clone(), alice_eth_balance_after); assert_eq!(bob_eth_balance_before + volume.clone(), bob_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); +} + +#[test] +#[ignore] +fn test_watcher_waits_for_taker_eth() { + let coins = json!([eth_testnet_conf(), eth_jst_conf(ETH_SEPOLIA_TOKEN_CONTRACT)]); + + let alice_passphrase = + String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let alice_conf = Mm2TestConf::seednode_using_watchers(&alice_passphrase, &coins); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), 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()); + + let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let watcher_passphrase = + String::from("also shoot benefit prefer juice shell thank unfair canal monkey style afraid"); + let watcher_conf = + Mm2TestConf::watcher_light_node(&watcher_passphrase, &coins, &[&mm_alice.ip.to_string()], WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 1., + refund_start_factor: 1.5, + search_interval: 1., + }) + .conf; + + let mut mm_watcher = MarketMakerIt::start(watcher_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); + let (_watcher_dump_log, _watcher_dump_dashboard) = mm_dump(&mm_watcher.log_path); + + enable_eth_and_jst(&mm_alice); + enable_eth_and_jst(&mm_bob); + enable_eth_and_jst(&mm_watcher); + + block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); + + block_on(mm_watcher.wait_for_log(160., |log| log.contains(MAKER_PAYMENT_SPEND_FOUND_LOG))).unwrap(); } #[test] @@ -187,7 +325,7 @@ fn test_watcher_refunds_taker_payment_erc20() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -217,6 +355,7 @@ fn test_watcher_refunds_taker_payment_erc20() { let alice_eth_balance_before = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); @@ -232,9 +371,11 @@ fn test_watcher_refunds_taker_payment_erc20() { let alice_eth_balance_after = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; assert_eq!(alice_jst_balance_before, alice_jst_balance_after); assert_eq!(alice_eth_balance_before, alice_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); } #[test] @@ -256,7 +397,7 @@ fn test_watcher_refunds_taker_payment_eth() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -286,6 +427,7 @@ fn test_watcher_refunds_taker_payment_eth() { let alice_eth_balance_before = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("JST", "ETH")], 1., 1., 0.01)); @@ -301,9 +443,11 @@ fn test_watcher_refunds_taker_payment_eth() { let alice_eth_balance_after = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; assert_eq!(alice_jst_balance_before, alice_jst_balance_after); assert_eq!(alice_eth_balance_before, alice_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); } #[test] @@ -315,16 +459,8 @@ fn test_watcher_validate_taker_fee_eth() { let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); let taker_pubkey = taker_keypair.public(); - let maker_coin = generate_eth_coin_with_random_privkey(); - let maker_keypair = maker_coin.derive_htlc_key_pair(&[]); - let maker_pubkey = maker_keypair.public(); - let taker_amount = MmNumber::from((10, 1)); - let fee_amount = dex_fee_amount_from_taker_coin( - &MmCoinEnum::EthCoin(taker_coin.clone()), - maker_coin.ticker(), - &taker_amount, - ); + let fee_amount = dex_fee_amount_from_taker_coin(&MmCoinEnum::EthCoin(taker_coin.clone()), "ETH", &taker_amount); let taker_fee = taker_coin .send_taker_fee( &DEX_FEE_ADDR_RAW_PUBKEY, @@ -350,10 +486,11 @@ fn test_watcher_validate_taker_fee_eth() { .wait(); assert!(validate_taker_fee_res.is_ok()); + let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); let error = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { taker_fee_hash: taker_fee.tx_hash().into_vec(), - sender_pubkey: maker_pubkey.to_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), min_block_number: 0, fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), lock_duration, @@ -416,6 +553,7 @@ fn test_watcher_validate_taker_fee_eth() { } #[test] +#[ignore] fn test_watcher_validate_taker_fee_erc20() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run let lock_duration = get_payment_locktime(); @@ -425,16 +563,8 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); let taker_pubkey = taker_keypair.public(); - let maker_coin = generate_eth_coin_with_random_privkey(); - let maker_keypair = maker_coin.derive_htlc_key_pair(&[]); - let maker_pubkey = maker_keypair.public(); - let taker_amount = MmNumber::from((10, 1)); - let fee_amount = dex_fee_amount_from_taker_coin( - &MmCoinEnum::EthCoin(taker_coin.clone()), - maker_coin.ticker(), - &taker_amount, - ); + let fee_amount = dex_fee_amount_from_taker_coin(&MmCoinEnum::EthCoin(taker_coin.clone()), "ETH", &taker_amount); let taker_fee = taker_coin .send_taker_fee( &DEX_FEE_ADDR_RAW_PUBKEY, @@ -460,10 +590,11 @@ fn test_watcher_validate_taker_fee_erc20() { .wait(); assert!(validate_taker_fee_res.is_ok()); + let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); let error = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { taker_fee_hash: taker_fee.tx_hash().into_vec(), - sender_pubkey: maker_pubkey.to_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), min_block_number: 0, fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), lock_duration, @@ -525,6 +656,529 @@ fn test_watcher_validate_taker_fee_erc20() { } } +#[test] +#[ignore] +fn test_watcher_validate_taker_payment_eth() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + + let seed = String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let taker_coin = generate_eth_coin_with_seed(&seed); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + let amount = BigDecimal::from_str("0.01").unwrap(); + let secret_hash = dhash160(&MakerSwap::generate_secret()); + let watcher_reward = Some( + block_on(watcher_reward_amount( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .unwrap(), + ); + + let min_watcher_reward = Some( + block_on(min_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .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: amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let validate_taker_payment_res = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait(); + assert!(validate_taker_payment_res.is_ok()); + + let error = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: maker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SENDER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SENDER_ERR_LOG, error + ), + } + + let taker_payment_wrong_contract = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: amount.clone(), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment_wrong_contract.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_CONTRACT_ADDRESS_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_CONTRACT_ADDRESS_ERR_LOG, error + ), + } + + // Used to get wrong swap id + let wrong_secret_hash = dhash160(&MakerSwap::generate_secret()); + let error = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains(INVALID_PAYMENT_STATE_ERR_LOG)) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` {}, found {:?}", + INVALID_PAYMENT_STATE_ERR_LOG, error + ), + } + + let taker_payment_wrong_secret = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment_wrong_secret.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SWAP_ID_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SWAP_ID_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_RECEIVER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_RECEIVER_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: min_watcher_reward.map(|min_reward| min_reward * 3), + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INSUFFICIENT_WATCHER_REWARD_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INSUFFICIENT_WATCHER_REWARD_ERR_LOG, error + ), + } +} + +#[test] +#[ignore] +fn test_watcher_validate_taker_payment_erc20() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + + let seed = String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + 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_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + + let secret_hash = dhash160(&MakerSwap::generate_secret()); + let watcher_reward = Some( + block_on(watcher_reward_amount( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .unwrap(), + ); + let min_watcher_reward = Some( + block_on(min_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .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: BigDecimal::from(10), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let validate_taker_payment_res = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait(); + assert!(validate_taker_payment_res.is_ok()); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: maker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SENDER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SENDER_ERR_LOG, error + ), + } + + let taker_payment_wrong_contract = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment_wrong_contract.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_CONTRACT_ADDRESS_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_CONTRACT_ADDRESS_ERR_LOG, error + ), + } + + // Used to get wrong swap id + let wrong_secret_hash = dhash160(&MakerSwap::generate_secret()); + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains(INVALID_PAYMENT_STATE_ERR_LOG)) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` {}, found {:?}", + INVALID_PAYMENT_STATE_ERR_LOG, error + ), + } + + let taker_payment_wrong_secret = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment_wrong_secret.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SWAP_ID_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SWAP_ID_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_RECEIVER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_RECEIVER_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: min_watcher_reward.map(|min_reward| min_reward * 3), + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INSUFFICIENT_WATCHER_REWARD_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INSUFFICIENT_WATCHER_REWARD_ERR_LOG, error + ), + } +} + #[test] fn test_watcher_spends_maker_payment_spend_utxo() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 100.into()); @@ -540,10 +1194,11 @@ fn test_watcher_spends_maker_payment_spend_utxo() { let mut mm_alice = MarketMakerIt::start(alice_conf.clone(), DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - let bob_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice - .ip - .to_string()]) - .conf; + let bob_conf = + Mm2TestConf::light_node_using_watchers(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice + .ip + .to_string()]) + .conf; let mut mm_bob = MarketMakerIt::start(bob_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); @@ -620,10 +1275,11 @@ fn test_watcher_waits_for_taker_utxo() { let mut mm_alice = MarketMakerIt::start(alice_conf.clone(), DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - let bob_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice - .ip - .to_string()]) - .conf; + let bob_conf = + Mm2TestConf::light_node_using_watchers(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice + .ip + .to_string()]) + .conf; let mut mm_bob = MarketMakerIt::start(bob_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); @@ -680,10 +1336,11 @@ fn test_watcher_refunds_taker_payment_utxo() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - let bob_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice - .ip - .to_string()]) - .conf; + let bob_conf = + Mm2TestConf::light_node_using_watchers(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice + .ip + .to_string()]) + .conf; let mut mm_bob = MarketMakerIt::start(bob_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); @@ -869,6 +1526,186 @@ fn test_watcher_validate_taker_fee_utxo() { } } +#[test] +fn test_watcher_validate_taker_payment_utxo() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + let time_lock_duration = get_payment_locktime(); + let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + + let (_ctx, taker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let taker_pubkey = taker_coin.my_public_key().unwrap(); + + 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()); + + 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() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) + .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 validate_taker_payment_res = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait(); + assert!(validate_taker_payment_res.is_ok()); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: maker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SENDER_ERR_LOG)) + }, + _ => panic!("Expected `WrongPaymentTx` {INVALID_SENDER_ERR_LOG}, found {:?}", error), + } + + let wrong_secret_hash = dhash160(&MakerSwap::generate_secret()); + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SCRIPT_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SCRIPT_ERR_LOG, error + ), + } + + // Wrong time lock + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock: 500, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SCRIPT_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SCRIPT_ERR_LOG, error + ), + } + + let wrong_taker_payment_refund_preimage = taker_coin + .create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + wrong_secret_hash.as_slice(), + &None, + &[], + ) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: wrong_taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_REFUND_TX_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_REFUND_TX_ERR_LOG, error + ), + } +} + #[test] fn test_send_taker_payment_refund_preimage_utxo() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run @@ -876,7 +1713,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -885,6 +1722,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); @@ -898,13 +1736,14 @@ fn test_send_taker_payment_refund_preimage_utxo() { .unwrap(); let refund_tx = coin - .send_taker_payment_refund_preimage(SendWatcherRefundsPaymentArgs { + .send_taker_payment_refund_preimage(RefundPaymentArgs { payment_tx: &refund_tx.tx_hex(), swap_contract_address: &None, secret_hash: &[0; 20], other_pubkey: my_public_key, time_lock, swap_unique_data: &[], + watcher_reward: false, }) .wait() .unwrap(); @@ -921,6 +1760,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs index c6dd0265b6..832bdca84c 100644 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs @@ -110,8 +110,8 @@ pub async fn trade_base_rel_iris( .await ); dbg!(enable_electrum(&mm_alice, "RICK", false, RICK_ELECTRUM_ADDRS).await); - dbg!(enable_eth_coin(&mm_bob, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None).await); - dbg!(enable_eth_coin(&mm_alice, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, 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); 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 d6d4042ace..98425fda32 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -7450,7 +7450,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7459,7 +7460,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7468,7 +7470,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7477,7 +7480,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); let uuids = block_on(start_swaps( @@ -7538,6 +7542,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", None, + false ))); dbg!(block_on(enable_eth_coin( @@ -7547,6 +7552,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", None, + false ))); dbg!(block_on(enable_eth_coin( @@ -7555,7 +7561,8 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7564,7 +7571,8 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); let uuids = block_on(start_swaps( diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 2680ced419..74d0bd1fb8 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -147,7 +147,7 @@ pub const ETH_DEV_NODES: &[&str] = &["http://195.201.0.6:8565"]; pub const ETH_DEV_SWAP_CONTRACT: &str = "0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd"; pub const ETH_SEPOLIA_NODE: &[&str] = &["https://rpc-sepolia.rockx.com/"]; -pub const ETH_SEPOLIA_SWAP_CONTRACT: &str = "0xA25E0e06fB139CDc2f9f11675877DaD9EdD1C352"; +pub const ETH_SEPOLIA_SWAP_CONTRACT: &str = "0x5BCC05dD32a87fABEDBcbbfeb77476eaD1F7051C"; pub const ETH_SEPOLIA_TOKEN_CONTRACT: &str = "0x948BF5172383F1Bc0Fdf3aBe0630b855694A5D2c"; pub const BCHD_TESTNET_URLS: &[&str] = &["https://bchd-testnet.greyh.at:18335"]; @@ -216,6 +216,21 @@ impl Mm2TestConf { } } + pub fn light_node_using_watchers(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_watchers": true + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn watcher_light_node(passphrase: &str, coins: &Json, seednodes: &[&str], conf: WatcherConf) -> Self { Mm2TestConf { conf: json!({ @@ -1576,6 +1591,7 @@ pub async fn enable_eth_coin( urls: &[&str], swap_contract_address: &str, fallback_swap_contract: Option<&str>, + contract_supports_watcher: bool, ) -> Json { let enable = mm .rpc(&json!({ @@ -1586,6 +1602,7 @@ pub async fn enable_eth_coin( "swap_contract_address": swap_contract_address, "fallback_swap_contract": fallback_swap_contract, "mm2": 1, + "contract_supports_watchers": contract_supports_watcher })) .await .unwrap(); From 211492e586d301c7ba0da56585bf6b759782fd23 Mon Sep 17 00:00:00 2001 From: "Sergey O. Boyko" <58207208+sergeyboyko0791@users.noreply.github.com> Date: Tue, 7 Mar 2023 23:05:58 +0700 Subject: [PATCH 10/79] fix: IndexedDB Cursor Iterator (#1678) * Save dev state * Refactor IndexedDB Cursor * Add `CursorIter::next` * Add `IdbEmptyCursor` * Fix merge conflicts * Fix and optimize `IndexedDbBlockHeaderStorage` * Make `height: BeBigUint` instead of `u64` * Fix `BlockHeaderStorageTable::TICKER_HEIGHT_INDEX` index * Fix TODOs * Fix PR issues --------- Reviewed-by: @borngraced, @shamardy --- Cargo.lock | 1 + .../utxo/utxo_block_header_storage/mod.rs | 7 +- .../wasm/block_header_table.rs | 8 +- .../wasm/indexeddb_block_header_storage.rs | 100 ++- mm2src/mm2_db/Cargo.toml | 3 +- .../src/indexed_db/drivers/cursor/cursor.rs | 241 ++++-- .../indexed_db/drivers/cursor/empty_cursor.rs | 14 + .../drivers/cursor/multi_key_bound_cursor.rs | 94 +-- .../drivers/cursor/multi_key_cursor.rs | 48 +- .../drivers/cursor/single_key_bound_cursor.rs | 29 +- .../drivers/cursor/single_key_cursor.rs | 38 +- .../src/indexed_db/drivers/object_store.rs | 35 +- .../src/indexed_db/drivers/transaction.rs | 4 +- .../mm2_db/src/indexed_db/indexed_cursor.rs | 765 +++++++----------- mm2src/mm2_db/src/indexed_db/indexed_db.rs | 93 ++- .../mm2_main/src/lp_swap/my_swaps_storage.rs | 18 +- 16 files changed, 683 insertions(+), 815 deletions(-) create mode 100644 mm2src/mm2_db/src/indexed_db/drivers/cursor/empty_cursor.rs diff --git a/Cargo.lock b/Cargo.lock index e7fae16a35..fabae55f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4239,6 +4239,7 @@ dependencies = [ "async-trait", "common", "derive_more", + "enum_from", "futures 0.3.15", "hex 0.4.3", "itertools", diff --git a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs index 1932d9afda..888d067f02 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs @@ -58,7 +58,6 @@ impl BlockHeaderStorage { }) } - #[cfg(test)] pub(crate) fn into_inner(self) -> Box { self.inner } } @@ -108,7 +107,7 @@ impl BlockHeaderStorageOps for BlockHeaderStorage { async fn is_table_empty(&self) -> Result<(), BlockHeaderStorageError> { self.inner.is_table_empty().await } } -#[cfg(test)] +#[cfg(any(test, target_arch = "wasm32"))] mod block_headers_storage_tests { use super::*; use chain::BlockHeaderBits; @@ -313,7 +312,7 @@ mod native_tests { fn test_remove_headers_up_to_height() { block_on(test_remove_headers_up_to_height_impl(FOR_COIN_GET)) } } -#[cfg(all(test, target_arch = "wasm32"))] +#[cfg(target_arch = "wasm32")] mod wasm_test { use super::*; use crate::utxo::utxo_block_header_storage::block_headers_storage_tests::*; @@ -323,7 +322,7 @@ mod wasm_test { wasm_bindgen_test_configure!(run_in_browser); - const FOR_COIN: &str = "RICK"; + const FOR_COIN: &str = "tBTC"; #[wasm_bindgen_test] async fn test_storage_init() { diff --git a/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs b/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs index 79121043d6..951d0aa938 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs @@ -1,8 +1,8 @@ -use mm2_db::indexed_db::{DbUpgrader, OnUpgradeResult, TableSignature}; +use mm2_db::indexed_db::{BeBigUint, DbUpgrader, OnUpgradeResult, TableSignature}; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct BlockHeaderStorageTable { - pub height: u64, + pub height: BeBigUint, pub bits: u32, pub hash: String, pub raw_header: String, @@ -10,7 +10,7 @@ pub struct BlockHeaderStorageTable { } impl BlockHeaderStorageTable { - pub const HEIGHT_TICKER_INDEX: &str = "block_height_ticker_index"; + pub const TICKER_HEIGHT_INDEX: &str = "block_height_ticker_index"; pub const HASH_TICKER_INDEX: &str = "block_hash_ticker_index"; } @@ -21,7 +21,7 @@ impl TableSignature for BlockHeaderStorageTable { match (old_version, new_version) { (0, 1) => { let table = upgrader.create_table(Self::table_name())?; - table.create_multi_index(Self::HEIGHT_TICKER_INDEX, &["height", "ticker"], true)?; + table.create_multi_index(Self::TICKER_HEIGHT_INDEX, &["ticker", "height"], true)?; table.create_multi_index(Self::HASH_TICKER_INDEX, &["hash", "ticker"], true)?; table.create_index("ticker", false)?; }, 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 c5a02dd151..287f42b7ec 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 @@ -3,10 +3,10 @@ use super::BlockHeaderStorageTable; use async_trait::async_trait; use chain::BlockHeader; use mm2_core::mm_ctx::MmArc; -use mm2_db::indexed_db::cursor_prelude::{CollectCursor, WithOnly}; -use mm2_db::indexed_db::{ConstructibleDb, DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder, +use mm2_db::indexed_db::{BeBigUint, ConstructibleDb, DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder, InitDbResult, MultiIndex, SharedDb}; use mm2_err_handle::prelude::*; +use num_traits::ToPrimitive; use primitives::hash::H256; use serialization::Reader; use spv_validation::storage::{BlockHeaderStorageError, BlockHeaderStorageOps}; @@ -93,15 +93,15 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { let bits: u32 = header.bits.into(); let headers_to_store = BlockHeaderStorageTable { ticker: ticker.clone(), - height, + height: BeBigUint::from(height), bits, hash, raw_header, }; - let index_keys = MultiIndex::new(BlockHeaderStorageTable::HEIGHT_TICKER_INDEX) - .with_value(&height) - .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? + let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .with_value(&ticker) + .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? + .with_value(&BeBigUint::from(height)) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; block_headers_db @@ -149,10 +149,10 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { .table::() .await .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; - let index_keys = MultiIndex::new(BlockHeaderStorageTable::HEIGHT_TICKER_INDEX) - .with_value(&height) - .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? + let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .with_value(&ticker) + .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? + .with_value(&BeBigUint::from(height)) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; Ok(block_headers_db @@ -178,21 +178,30 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { .await .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; - // Todo: use open_cursor with direction to optimze this process. - let res = block_headers_db - .open_cursor("ticker") - .await - .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? + let maybe_item = block_headers_db + .cursor_builder() .only("ticker", ticker.clone()) .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? - .collect() + // We need to provide any constraint on the `height` property + // since `ticker_height` consists of both `ticker` and `height` properties. + .bound("height", BeBigUint::from(0u64), BeBigUint::from(u64::MAX)) + // Cursor returns values from the lowest to highest key indexes. + // But we need to get the most highest height, so reverse the cursor direction. + .reverse() + .open_cursor(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .await .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? - .into_iter() - .map(|(_item_id, item)| item.height) - .collect::>(); + .next() + .await + .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?; - Ok(res.into_iter().max()) + maybe_item + .map(|(_, item)| { + item.height + .to_u64() + .ok_or_else(|| BlockHeaderStorageError::get_err(&ticker, "height is too large".to_string())) + }) + .transpose() } async fn get_last_block_header_with_non_max_bits( @@ -214,25 +223,29 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { .await .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; - // Todo: use open_cursor with direction to optimze this process. - let res = block_headers_db - .open_cursor("ticker") - .await - .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? + let mut cursor = block_headers_db + .cursor_builder() .only("ticker", ticker.clone()) .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? - .collect() + // We need to provide any constraint on the `height` property + // since `ticker_height` consists of both `ticker` and `height` properties. + .bound("height", BeBigUint::from(0u64), BeBigUint::from(u64::MAX)) + // Cursor returns values from the lowest to highest key indexes. + // But we need to get the most highest height, so reverse the cursor direction. + .reverse() + .open_cursor(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) + .await + .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?; + + while let Some((_item_id, header)) = cursor + .next() .await .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? - .into_iter() - .map(|(_item_id, item)| item) - .collect::>(); - let res = res - .into_iter() - .filter_map(|e| if e.bits != max_bits { Some(e) } else { None }) - .collect::>(); - - for header in res { + { + if header.bits == max_bits { + continue; + } + let serialized = &hex::decode(header.raw_header).map_err(|e| BlockHeaderStorageError::DecodeError { coin: ticker.clone(), reason: e.to_string(), @@ -273,11 +286,18 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { .with_value(&ticker) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; - Ok(block_headers_db + let maybe_item = block_headers_db .get_item_by_unique_multi_index(index_keys) .await - .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))? - .map(|raw| raw.1.height as i64)) + .map_err(|err| BlockHeaderStorageError::get_err(&ticker, err.to_string()))?; + + maybe_item + .map(|(_, item)| { + item.height + .to_i64() + .ok_or_else(|| BlockHeaderStorageError::get_err(&ticker, "height is too large".to_string())) + }) + .transpose() } async fn remove_headers_up_to_height(&self, to_height: u64) -> Result<(), BlockHeaderStorageError> { @@ -297,10 +317,10 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; for height in 0..=to_height { - let index_keys = MultiIndex::new(BlockHeaderStorageTable::HEIGHT_TICKER_INDEX) - .with_value(&height) - .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? + let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .with_value(&ticker) + .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? + .with_value(&BeBigUint::from(height)) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; block_headers_db diff --git a/mm2src/mm2_db/Cargo.toml b/mm2src/mm2_db/Cargo.toml index 5933140509..5c3d53d005 100644 --- a/mm2src/mm2_db/Cargo.toml +++ b/mm2src/mm2_db/Cargo.toml @@ -10,6 +10,7 @@ doctest = false async-trait = "0.1" common = { path = "../common" } derive_more = "0.99" +enum_from = { path = "../derives/enum_from" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } itertools = "0.10" hex = "0.4.2" @@ -26,4 +27,4 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } wasm-bindgen = { version = "0.2.50", features = ["nightly"] } wasm-bindgen-futures = { version = "0.4.1" } wasm-bindgen-test = { version = "0.3.2" } -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", "IdbCursorDirection", "IdbFactory", "IdbIndex", "IdbIndexParameters", "IdbObjectStore", "IdbObjectStoreParameters", "IdbOpenDbRequest", "IdbKeyRange", "IdbTransaction", "IdbTransactionMode", "IdbVersionChangeEvent", "MessageEvent", "WebSocket"] } diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs index 694fec6f16..0fdc26261b 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs @@ -1,9 +1,9 @@ use super::construct_event_closure; use crate::indexed_db::db_driver::{InternalItem, ItemId}; use crate::indexed_db::BeBigUint; -use async_trait::async_trait; use common::wasm::{deserialize_from_js, serialize_to_js, stringify_js_error}; use derive_more::Display; +use enum_from::EnumFromTrait; use futures::channel::mpsc; use futures::StreamExt; use js_sys::Array; @@ -12,22 +12,23 @@ use serde_json::{self as json, Value as Json}; use std::convert::TryInto; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use web_sys::{IdbCursorWithValue, IdbIndex, IdbKeyRange, IdbRequest}; +use web_sys::{IdbCursorDirection, IdbCursorWithValue, IdbIndex, IdbKeyRange, IdbRequest}; +mod empty_cursor; mod multi_key_bound_cursor; mod multi_key_cursor; mod single_key_bound_cursor; mod single_key_cursor; -pub use multi_key_bound_cursor::IdbMultiKeyBoundCursor; -pub use multi_key_cursor::IdbMultiKeyCursor; -pub use single_key_bound_cursor::IdbSingleKeyBoundCursor; -pub use single_key_cursor::IdbSingleKeyCursor; +use empty_cursor::IdbEmptyCursor; +use multi_key_bound_cursor::IdbMultiKeyBoundCursor; +use multi_key_cursor::IdbMultiKeyCursor; +use single_key_bound_cursor::IdbSingleKeyBoundCursor; +use single_key_cursor::IdbSingleKeyCursor; pub type CursorResult = Result>; -pub type DbFilter = Box (CollectItemAction, CollectCursorAction) + Send>; -#[derive(Debug, Display, PartialEq)] +#[derive(Debug, Display, EnumFromTrait, PartialEq)] pub enum CursorError { #[display( fmt = "Error serializing the '{}' value of the index field '{}' : {:?}", @@ -59,6 +60,7 @@ pub enum CursorError { )] IncorrectNumberOfKeysPerIndex { expected: usize, found: usize }, #[display(fmt = "Error occurred due to an unexpected state: {:?}", _0)] + #[from_trait(WithInternal::internal)] UnexpectedState(String), #[display(fmt = "Incorrect usage of the cursor: {:?}", description)] IncorrectUsage { description: String }, @@ -81,6 +83,13 @@ pub enum CursorBoundValue { BigUint(BeBigUint), } +#[derive(Default)] +pub struct CursorFilters { + pub(crate) only_keys: Vec<(String, Json)>, + pub(crate) bound_keys: Vec<(String, CursorBoundValue, CursorBoundValue)>, + pub(crate) reverse: bool, +} + impl From for CursorBoundValue { fn from(uint: u32) -> Self { CursorBoundValue::Uint(uint) } } @@ -160,143 +169,209 @@ impl CursorBoundValue { } #[derive(Debug, PartialEq)] -pub enum CollectCursorAction { +pub enum CursorAction { Continue, ContinueWithValue(JsValue), Stop, } #[derive(Debug, PartialEq)] -pub enum CollectItemAction { +pub enum CursorItemAction { Include, Skip, } -#[async_trait(?Send)] -pub trait CursorOps: Sized { - fn db_index(&self) -> &IdbIndex; - +pub trait CursorDriverImpl: Sized { fn key_range(&self) -> CursorResult>; - fn on_collect_iter(&mut self, key: JsValue, value: &Json) - -> CursorResult<(CollectItemAction, CollectCursorAction)>; + fn on_iteration(&mut self, key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)>; +} - /// Collect items that match the specified bounds. - async fn collect(mut self) -> CursorResult> { - let (tx, mut rx) = mpsc::channel(1); +pub(crate) struct CursorDriver { + /// An actual cursor implementation. + inner: IdbCursorEnum, + cursor_request: IdbRequest, + cursor_item_rx: mpsc::Receiver>, + /// Whether we got `CursorAction::Stop` at the last iteration or not. + stopped: bool, + /// We need to hold the closures in memory till `cursor` exists. + _onsuccess_closure: Closure, + _onerror_closure: Closure, +} - let db_index = self.db_index(); - let cursor_request_result = match self.key_range()? { +impl CursorDriver { + pub(crate) fn init_cursor(db_index: IdbIndex, filters: CursorFilters) -> CursorResult { + let reverse = filters.reverse; + let inner = IdbCursorEnum::new(filters)?; + + let cursor_request_result = match inner.key_range()? { + Some(key_range) if reverse => { + db_index.open_cursor_with_range_and_direction(&key_range, IdbCursorDirection::Prev) + }, Some(key_range) => db_index.open_cursor_with_range(&key_range), + // Please note that `IndexedDb` doesn't allow to open a cursor with a direction + // but without a key range. + None if reverse => { + return MmError::err(CursorError::ErrorOpeningCursor { + description: format!("Direction cannot be specified without a range"), + }) + }, None => db_index.open_cursor(), }; let cursor_request = cursor_request_result.map_err(|e| CursorError::ErrorOpeningCursor { description: stringify_js_error(&e), })?; - let onsuccess_closure = construct_event_closure(Ok, tx.clone()); - let onerror_closure = construct_event_closure(Err, tx); + let (cursor_item_tx, cursor_item_rx) = mpsc::channel(1); + + let onsuccess_closure = construct_event_closure(Ok, cursor_item_tx.clone()); + let onerror_closure = construct_event_closure(Err, cursor_item_tx); cursor_request.set_onsuccess(Some(onsuccess_closure.as_ref().unchecked_ref())); cursor_request.set_onerror(Some(onerror_closure.as_ref().unchecked_ref())); - let mut collected_items = Vec::new(); + Ok(CursorDriver { + inner, + cursor_request, + cursor_item_rx, + stopped: false, + _onsuccess_closure: onsuccess_closure, + _onerror_closure: onerror_closure, + }) + } + + pub(crate) async fn next(&mut self) -> CursorResult> { + loop { + // Check if we got `CursorAction::Stop` at the last iteration. + if self.stopped { + return Ok(None); + } + + let event = match self.cursor_item_rx.next().await { + Some(event) => event, + None => { + self.stopped = true; + return Ok(None); + }, + }; - while let Some(event) = rx.next().await { let _cursor_event = event.map_to_mm(|e| CursorError::ErrorOpeningCursor { description: stringify_js_error(&e), })?; - let cursor = match cursor_from_request(&cursor_request)? { + let cursor = match cursor_from_request(&self.cursor_request)? { Some(cursor) => cursor, - // no more items, stop the loop - None => break, + // No more items. + None => { + self.stopped = true; + return Ok(None); + }, }; let (key, js_value) = match (cursor.key(), cursor.value()) { (Ok(key), Ok(js_value)) => (key, js_value), - // no more items, stop the loop - _ => break, + // No more items. + _ => { + self.stopped = true; + return Ok(None); + }, }; let item: InternalItem = deserialize_from_js(js_value).map_to_mm(|e| CursorError::ErrorDeserializingItem(e.to_string()))?; - let (item_action, cursor_action) = self.on_collect_iter(key, &item.item)?; - match item_action { - CollectItemAction::Include => collected_items.push(item.into_pair()), - CollectItemAction::Skip => (), - } + let (item_action, cursor_action) = self.inner.on_iteration(key)?; match cursor_action { - CollectCursorAction::Continue => cursor.continue_().map_to_mm(|e| CursorError::AdvanceError { + CursorAction::Continue => cursor.continue_().map_to_mm(|e| CursorError::AdvanceError { description: stringify_js_error(&e), })?, - CollectCursorAction::ContinueWithValue(next_value) => { + CursorAction::ContinueWithValue(next_value) => { cursor .continue_with_key(&next_value) .map_to_mm(|e| CursorError::AdvanceError { description: stringify_js_error(&e), })? }, - // don't advance the cursor, just stop the loop - CollectCursorAction::Stop => break, + // Don't advance the cursor. + // Here we set the `stopped` flag so we return `Ok(None)` at the next iteration immediately. + // This is required because `item_action` can be `CollectItemAction::Include`, + // and at this iteration we will return `Ok(Some)`. + CursorAction::Stop => self.stopped = true, } - } - Ok(collected_items) + match item_action { + CursorItemAction::Include => return Ok(Some(item.into_pair())), + // Try to fetch the next item. + CursorItemAction::Skip => (), + } + } } } -pub struct IdbCursorBuilder { - db_index: IdbIndex, +pub(crate) enum IdbCursorEnum { + Empty(IdbEmptyCursor), + SingleKey(IdbSingleKeyCursor), + SingleKeyBound(IdbSingleKeyBoundCursor), + MultiKey(IdbMultiKeyCursor), + MultiKeyBound(IdbMultiKeyBoundCursor), } -impl IdbCursorBuilder { - pub fn new(db_index: IdbIndex) -> IdbCursorBuilder { IdbCursorBuilder { db_index } } - - /// Returns a cursor that is a representation of a range that includes records - /// whose value of the `field_name` field equals to the `field_value` value. - pub fn single_key_cursor( - self, - field_name: String, - field_value: Json, - collect_filter: Option, - ) -> IdbSingleKeyCursor { - IdbSingleKeyCursor::new(self.db_index, field_name, field_value, collect_filter) - } +impl IdbCursorEnum { + fn new(cursor_filters: CursorFilters) -> CursorResult { + if cursor_filters.only_keys.len() > 1 && cursor_filters.bound_keys.is_empty() { + return Ok(IdbCursorEnum::MultiKey(IdbMultiKeyCursor::new( + cursor_filters.only_keys, + ))); + } + if !cursor_filters.bound_keys.is_empty() + && (cursor_filters.only_keys.len() + cursor_filters.bound_keys.len() > 1) + { + return Ok(IdbCursorEnum::MultiKeyBound(IdbMultiKeyBoundCursor::new( + cursor_filters.only_keys, + cursor_filters.bound_keys, + )?)); + } // otherwise we're sure that there is either one `only`, or one `bound`, or no constraint specified. + + if let Some((field_name, field_value)) = cursor_filters.only_keys.into_iter().next() { + return Ok(IdbCursorEnum::SingleKey(IdbSingleKeyCursor::new( + field_name, + field_value, + ))); + } + + if let Some((field_name, lower_bound, upper_bound)) = cursor_filters.bound_keys.into_iter().next() { + return Ok(IdbCursorEnum::SingleKeyBound(IdbSingleKeyBoundCursor::new( + field_name, + lower_bound, + upper_bound, + )?)); + } - /// Returns a cursor that is a representation of a range that includes records - /// whose value of the `field_name` field is lower than `lower_bound` and greater than `upper_bound`. - pub fn single_key_bound_cursor( - self, - field_name: String, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - collect_filter: Option, - ) -> CursorResult { - IdbSingleKeyBoundCursor::new(self.db_index, field_name, lower_bound, upper_bound, collect_filter) + // There are no constraint specified. + Ok(IdbCursorEnum::Empty(IdbEmptyCursor)) } +} - /// Returns a cursor that is a representation of a range that includes records - /// whose fields have only the specified values `only_values`. - pub fn multi_key_cursor( - self, - only_values: Vec<(String, Json)>, - collect_filter: Option, - ) -> CursorResult { - IdbMultiKeyCursor::new(self.db_index, only_values, collect_filter) +impl CursorDriverImpl for IdbCursorEnum { + fn key_range(&self) -> CursorResult> { + match self { + IdbCursorEnum::Empty(empty) => empty.key_range(), + IdbCursorEnum::SingleKey(single) => single.key_range(), + IdbCursorEnum::SingleKeyBound(single_bound) => single_bound.key_range(), + IdbCursorEnum::MultiKey(multi) => multi.key_range(), + IdbCursorEnum::MultiKeyBound(multi_bound) => multi_bound.key_range(), + } } - /// Returns a cursor that is a representation of a range that includes records - /// with the multiple `only` and `bound` restrictions. - pub fn multi_key_bound_cursor( - self, - only_values: Vec<(String, Json)>, - bound_values: Vec<(String, CursorBoundValue, CursorBoundValue)>, - collect_filter: Option, - ) -> CursorResult { - IdbMultiKeyBoundCursor::new(self.db_index, only_values, bound_values, collect_filter) + fn on_iteration(&mut self, key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)> { + match self { + IdbCursorEnum::Empty(empty) => empty.on_iteration(key), + IdbCursorEnum::SingleKey(single) => single.on_iteration(key), + IdbCursorEnum::SingleKeyBound(single_bound) => single_bound.on_iteration(key), + IdbCursorEnum::MultiKey(multi) => multi.on_iteration(key), + IdbCursorEnum::MultiKeyBound(multi_bound) => multi_bound.on_iteration(key), + } } } diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/empty_cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/empty_cursor.rs new file mode 100644 index 0000000000..441180d8bb --- /dev/null +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/empty_cursor.rs @@ -0,0 +1,14 @@ +use super::{CursorAction, CursorDriverImpl, CursorItemAction, CursorResult}; +use wasm_bindgen::prelude::*; +use web_sys::IdbKeyRange; + +/// The representation of a range that includes all records. +pub struct IdbEmptyCursor; + +impl CursorDriverImpl for IdbEmptyCursor { + fn key_range(&self) -> CursorResult> { Ok(None) } + + fn on_iteration(&mut self, _key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)> { + Ok((CursorItemAction::Include, CursorAction::Continue)) + } +} diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs index 836204e94b..6a6b224fcd 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs @@ -1,37 +1,29 @@ -use super::{index_key_as_array, CollectCursorAction, CollectItemAction, CursorBoundValue, CursorError, CursorOps, - CursorResult, DbFilter}; -use async_trait::async_trait; +use super::{index_key_as_array, CursorAction, CursorBoundValue, CursorDriverImpl, CursorError, CursorItemAction, + CursorResult}; use common::{deserialize_from_js, serialize_to_js, stringify_js_error}; use js_sys::Array; use mm2_err_handle::prelude::*; use serde_json::{json, Value as Json}; use wasm_bindgen::prelude::*; -use web_sys::{IdbIndex, IdbKeyRange}; +use web_sys::IdbKeyRange; /// The representation of a range that includes records /// with the multiple `only` and `bound` restrictions. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/bound pub struct IdbMultiKeyBoundCursor { - db_index: IdbIndex, only_values: Vec<(String, Json)>, bound_values: Vec<(String, CursorBoundValue, CursorBoundValue)>, - /// An additional predicate that may be used to filter records. - collect_filter: Option, } impl IdbMultiKeyBoundCursor { pub(super) fn new( - db_index: IdbIndex, only_values: Vec<(String, Json)>, bound_values: Vec<(String, CursorBoundValue, CursorBoundValue)>, - collect_filter: Option, ) -> CursorResult { Self::check_bounds(&only_values, &bound_values)?; Ok(IdbMultiKeyBoundCursor { - db_index, only_values, bound_values, - collect_filter, }) } @@ -83,10 +75,7 @@ impl IdbMultiKeyBoundCursor { } } -#[async_trait(?Send)] -impl CursorOps for IdbMultiKeyBoundCursor { - fn db_index(&self) -> &IdbIndex { &self.db_index } - +impl CursorDriverImpl for IdbMultiKeyBoundCursor { fn key_range(&self) -> CursorResult> { let lower = Array::new(); let upper = Array::new(); @@ -119,11 +108,7 @@ impl CursorOps for IdbMultiKeyBoundCursor { /// The range `IDBKeyRange.bound([2,2], [4,4])` includes values like `[3,0]` and `[3,5]` as `[2,2] < [3,0] < [3,5] < [4,4]`, /// so we need to do additional filtering. /// For more information on why it's required, see https://stackoverflow.com/a/32976384. - fn on_collect_iter( - &mut self, - index_key: JsValue, - value: &Json, - ) -> CursorResult<(CollectItemAction, CollectCursorAction)> { + fn on_iteration(&mut self, index_key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)> { let index_keys_js_array = index_key_as_array(index_key)?; let index_keys: Vec = index_keys_js_array .iter() @@ -172,8 +157,8 @@ impl CursorOps for IdbMultiKeyBoundCursor { return Ok(( // the `actual_index_value` is not in our expected bounds - CollectItemAction::Skip, - CollectCursorAction::ContinueWithValue(new_index.into()), + CursorItemAction::Skip, + CursorAction::ContinueWithValue(new_index.into()), )); } if &actual_index_value > upper_bound { @@ -192,13 +177,13 @@ impl CursorOps for IdbMultiKeyBoundCursor { return Ok(( // the `actual_index_value` is not in our expected bounds - CollectItemAction::Skip, - CollectCursorAction::ContinueWithValue(new_index.into()), + CursorItemAction::Skip, + CursorAction::ContinueWithValue(new_index.into()), )); } // otherwise there is no an index greater than actual `index`, stop the cursor - return Ok((CollectItemAction::Skip, CollectCursorAction::Stop)); + return Ok((CursorItemAction::Skip, CursorAction::Stop)); } let increased_index_key = actual_index_value.next(); @@ -209,19 +194,13 @@ impl CursorOps for IdbMultiKeyBoundCursor { idx_in_index += 1; idx_in_bounds += 1; } - - // `index_key` is in our expected bounds - if let Some(ref mut filter) = self.collect_filter { - return Ok(filter(value)); - } - Ok((CollectItemAction::Include, CollectCursorAction::Continue)) + Ok((CursorItemAction::Include, CursorAction::Continue)) } } mod tests { use super::*; use common::log::wasm_log::register_wasm_log; - use wasm_bindgen::JsCast; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -230,10 +209,10 @@ mod tests { fn test_in_bound_indexes(cursor: &mut IdbMultiKeyBoundCursor, input_indexes: Vec) { for input_index in input_indexes { let input_index_js_value = serialize_to_js(&input_index).unwrap(); - let result = cursor.on_collect_iter(input_index_js_value, &Json::Null); + let result = cursor.on_iteration(input_index_js_value); assert_eq!( result, - Ok((CollectItemAction::Include, CollectCursorAction::Continue)), + Ok((CursorItemAction::Include, CursorAction::Continue)), "'{}' index is expected to be in a bound", input_index ); @@ -247,19 +226,16 @@ mod tests { for (input_index, expected_next) in input_indexes { let input_index_js_value = serialize_to_js(&input_index).unwrap(); let (item_action, cursor_action) = cursor - .on_collect_iter(input_index_js_value, &Json::Null) + .on_iteration(input_index_js_value) .expect(&format!("Error due to the index '{:?}'", input_index)); let actual_next: Json = match cursor_action { - CollectCursorAction::ContinueWithValue(next_index_js_value) => { + CursorAction::ContinueWithValue(next_index_js_value) => { deserialize_from_js(next_index_js_value).expect("Error deserializing next index}") }, - action => panic!( - "Expected 'CollectCursorAction::ContinueWithValue', found '{:?}'", - action - ), + action => panic!("Expected 'CursorAction::ContinueWithValue', found '{:?}'", action), }; - assert_eq!(item_action, CollectItemAction::Skip); + assert_eq!(item_action, CursorItemAction::Skip); assert_eq!(actual_next, expected_next); } } @@ -268,10 +244,10 @@ mod tests { fn test_out_of_bound_indexes(cursor: &mut IdbMultiKeyBoundCursor, input_indexes: Vec) { for input_index in input_indexes { let input_index_js_value = serialize_to_js(&input_index).unwrap(); - let result = cursor.on_collect_iter(input_index_js_value, &Json::Null); + let result = cursor.on_iteration(input_index_js_value); assert_eq!( result, - Ok((CollectItemAction::Skip, CollectCursorAction::Stop)), + Ok((CursorItemAction::Skip, CursorAction::Stop)), "'{}' index is expected to be out of bound", input_index ); @@ -280,7 +256,7 @@ mod tests { /// This test doesn't check [`IdbMultiKeyBoundCursor::filter`]. #[wasm_bindgen_test] - fn test_on_collect_iter_multiple_only_and_bound_values() { + fn test_on_iteration_multiple_only_and_bound_values() { register_wasm_log(); let only_values = vec![ @@ -311,10 +287,7 @@ mod tests { ), ]; - // Use [`wasm_bindgen::JsCast::unchecked_from_js`] to create not-valid `IdbIndex` - // that is not used by [`IdbMultiKeyBoundCursor::on_collect_iter`] anyway. - let db_index = IdbIndex::unchecked_from_js(JsValue::NULL); - let mut cursor = IdbMultiKeyBoundCursor::new(db_index, only_values, bound_values, None).unwrap(); + let mut cursor = IdbMultiKeyBoundCursor::new(only_values, bound_values).unwrap(); ////////////////// @@ -388,7 +361,7 @@ mod tests { /// This test doesn't check [`IdbMultiKeyBoundCursor::filter`]. #[wasm_bindgen_test] - fn test_on_collect_iter_multiple_bound_values() { + fn test_on_iteration_multiple_bound_values() { register_wasm_log(); let only_values = Vec::new(); @@ -405,10 +378,7 @@ mod tests { ), ]; - // Use [`wasm_bindgen::JsCast::unchecked_from_js`] to create not-valid `IdbIndex` - // that is not used by [`IdbMultiKeyBoundCursor::on_collect_iter`] anyway. - let db_index = IdbIndex::unchecked_from_js(JsValue::NULL); - let mut cursor = IdbMultiKeyBoundCursor::new(db_index, only_values, bound_values, None).unwrap(); + let mut cursor = IdbMultiKeyBoundCursor::new(only_values, bound_values).unwrap(); ////////////////// @@ -453,7 +423,7 @@ mod tests { /// This test doesn't check [`IdbMultiKeyBoundCursor::filter`]. #[wasm_bindgen_test] - fn test_on_collect_iter_single_only_and_bound_values() { + fn test_on_iteration_single_only_and_bound_values() { register_wasm_log(); let only_values = vec![("field1".to_owned(), json!(2))]; @@ -463,10 +433,7 @@ mod tests { CursorBoundValue::Uint(5), // upper bound )]; - // Use [`wasm_bindgen::JsCast::unchecked_from_js`] to create not-valid `IdbIndex` - // that is not used by [`IdbMultiKeyBoundCursor::on_collect_iter`] anyway. - let db_index = IdbIndex::unchecked_from_js(JsValue::NULL); - let mut cursor = IdbMultiKeyBoundCursor::new(db_index, only_values, bound_values, None).unwrap(); + let mut cursor = IdbMultiKeyBoundCursor::new(only_values, bound_values).unwrap(); ////////////////// @@ -502,7 +469,7 @@ mod tests { } #[wasm_bindgen_test] - fn test_on_collect_iter_error() { + fn test_on_iteration_error() { register_wasm_log(); let only_values = vec![("field1".to_owned(), json!(2u32))]; @@ -512,10 +479,7 @@ mod tests { CursorBoundValue::Uint(5), // upper bound )]; - // Use [`wasm_bindgen::JsCast::unchecked_from_js`] to create not-valid `IdbIndex` - // that is not used by [`IdbMultiKeyBoundCursor::on_collect_iter`] anyway. - let db_index = IdbIndex::unchecked_from_js(JsValue::NULL); - let mut cursor = IdbMultiKeyBoundCursor::new(db_index, only_values, bound_values, None).unwrap(); + let mut cursor = IdbMultiKeyBoundCursor::new(only_values, bound_values).unwrap(); ////////////////// @@ -531,7 +495,7 @@ mod tests { for (input_index, expected_type) in input_indexes { let input_index_js_value = serialize_to_js(&input_index).unwrap(); let error = cursor - .on_collect_iter(input_index_js_value, &Json::Null) + .on_iteration(input_index_js_value) .expect_err(&format!("'{:?}' must lead to 'CursorError::TypeMismatch'", input_index)); match error.into_inner() { CursorError::TypeMismatch { expected, .. } => assert_eq!(expected, expected_type), @@ -549,7 +513,7 @@ mod tests { for input_index in input_indexes { let input_index_js_value = serialize_to_js(&input_index).unwrap(); let error = cursor - .on_collect_iter(input_index_js_value, &Json::Null) + .on_iteration(input_index_js_value) .expect_err(&format!("'{:?}' must lead to 'CursorError::TypeMismatch'", input_index)); match error.into_inner() { CursorError::IncorrectNumberOfKeysPerIndex { .. } => (), diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_cursor.rs index c161ada34e..f6b128aa1b 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_cursor.rs @@ -1,52 +1,23 @@ -use super::{CollectCursorAction, CollectItemAction, CursorError, CursorOps, CursorResult, DbFilter}; -use async_trait::async_trait; +use super::{CursorAction, CursorDriverImpl, CursorError, CursorItemAction, CursorResult}; use common::{serialize_to_js, stringify_js_error}; use js_sys::Array; use mm2_err_handle::prelude::*; use serde_json::Value as Json; use wasm_bindgen::prelude::*; -use web_sys::{IdbIndex, IdbKeyRange}; +use web_sys::IdbKeyRange; /// The representation of a range that includes records /// whose fields have only the specified [`IdbSingleCursor::only_values`] values. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/only pub struct IdbMultiKeyCursor { - db_index: IdbIndex, only_values: Vec<(String, Json)>, - /// An additional predicate that can be used to filter records. - collect_filter: Option, } impl IdbMultiKeyCursor { - pub(super) fn new( - db_index: IdbIndex, - only_values: Vec<(String, Json)>, - collect_filter: Option, - ) -> CursorResult { - Self::check_only_values(&only_values)?; - Ok(IdbMultiKeyCursor { - db_index, - only_values, - collect_filter, - }) - } - - fn check_only_values(only_values: &Vec<(String, Json)>) -> CursorResult<()> { - if only_values.len() < 2 { - let description = format!( - "Incorrect usage of 'IdbMultiKeyCursor': expected more than one cursor bound, found '{}'", - only_values.len(), - ); - return MmError::err(CursorError::IncorrectUsage { description }); - } - Ok(()) - } + pub(super) fn new(only_values: Vec<(String, Json)>) -> IdbMultiKeyCursor { IdbMultiKeyCursor { only_values } } } -#[async_trait(?Send)] -impl CursorOps for IdbMultiKeyCursor { - fn db_index(&self) -> &IdbIndex { &self.db_index } - +impl CursorDriverImpl for IdbMultiKeyCursor { fn key_range(&self) -> CursorResult> { let only = Array::new(); @@ -65,14 +36,7 @@ impl CursorOps for IdbMultiKeyCursor { Ok(Some(key_range)) } - fn on_collect_iter( - &mut self, - _key: JsValue, - value: &Json, - ) -> CursorResult<(CollectItemAction, CollectCursorAction)> { - if let Some(ref mut filter) = self.collect_filter { - return Ok(filter(value)); - } - Ok((CollectItemAction::Include, CollectCursorAction::Continue)) + fn on_iteration(&mut self, _key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)> { + Ok((CursorItemAction::Include, CursorAction::Continue)) } } diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_bound_cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_bound_cursor.rs index d3138f09e9..92e7ee087b 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_bound_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_bound_cursor.rs @@ -1,40 +1,31 @@ -use super::{CollectCursorAction, CollectItemAction, CursorBoundValue, CursorError, CursorOps, CursorResult, DbFilter}; -use async_trait::async_trait; +use super::{CursorAction, CursorBoundValue, CursorDriverImpl, CursorError, CursorItemAction, CursorResult}; use common::{log::warn, stringify_js_error}; use mm2_err_handle::prelude::*; -use serde_json::Value as Json; use wasm_bindgen::prelude::*; -use web_sys::{IdbIndex, IdbKeyRange}; +use web_sys::IdbKeyRange; /// The representation of a range that includes records /// whose value of the [`IdbSingleBoundCursor::field_name`] field is lower than [`IdbSingleBoundCursor::lower_bound_value`] /// and greater than [`IdbSingleBoundCursor::upper_bound_value`]. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/bound pub struct IdbSingleKeyBoundCursor { - db_index: IdbIndex, #[allow(dead_code)] field_name: String, lower_bound: CursorBoundValue, upper_bound: CursorBoundValue, - /// An additional predicate that may be used to filter records. - collect_filter: Option, } impl IdbSingleKeyBoundCursor { pub(super) fn new( - db_index: IdbIndex, field_name: String, lower_bound: CursorBoundValue, upper_bound: CursorBoundValue, - collect_filter: Option, ) -> CursorResult { Self::check_bounds(&lower_bound, &upper_bound)?; Ok(IdbSingleKeyBoundCursor { - db_index, field_name, lower_bound, upper_bound, - collect_filter, }) } } @@ -56,10 +47,7 @@ impl IdbSingleKeyBoundCursor { } } -#[async_trait(?Send)] -impl CursorOps for IdbSingleKeyBoundCursor { - fn db_index(&self) -> &IdbIndex { &self.db_index } - +impl CursorDriverImpl for IdbSingleKeyBoundCursor { fn key_range(&self) -> CursorResult> { let key_range = IdbKeyRange::bound(&self.lower_bound.to_js_value()?, &self.upper_bound.to_js_value()?) .map_to_mm(|e| CursorError::InvalidKeyRange { @@ -68,14 +56,7 @@ impl CursorOps for IdbSingleKeyBoundCursor { Ok(Some(key_range)) } - fn on_collect_iter( - &mut self, - _key: JsValue, - value: &Json, - ) -> CursorResult<(CollectItemAction, CollectCursorAction)> { - if let Some(ref mut filter) = self.collect_filter { - return Ok(filter(value)); - } - Ok((CollectItemAction::Include, CollectCursorAction::Continue)) + fn on_iteration(&mut self, _key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)> { + Ok((CursorItemAction::Include, CursorAction::Continue)) } } diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_cursor.rs index f24f0da1f8..b3d6efa476 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/single_key_cursor.rs @@ -1,46 +1,29 @@ -use super::{CollectCursorAction, CollectItemAction, CursorError, CursorOps, CursorResult, DbFilter}; -use async_trait::async_trait; -use common::{log::warn, serialize_to_js, stringify_js_error}; +use super::{CursorAction, CursorDriverImpl, CursorError, CursorItemAction, CursorResult}; +use common::{serialize_to_js, stringify_js_error}; use mm2_err_handle::prelude::*; use serde_json::Value as Json; use wasm_bindgen::prelude::*; -use web_sys::{IdbIndex, IdbKeyRange}; +use web_sys::IdbKeyRange; /// The representation of a range that includes records /// whose value of the [`IdbSingleKeyCursor::field_name`] field equals to the [`IdbSingleKeyCursor::field_value`] value. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange/only pub struct IdbSingleKeyCursor { - db_index: IdbIndex, #[allow(dead_code)] field_name: String, field_value: Json, - /// An additional predicate that may be used to filter records. - collect_filter: Option, } impl IdbSingleKeyCursor { - pub(super) fn new( - db_index: IdbIndex, - field_name: String, - field_value: Json, - filter: Option, - ) -> IdbSingleKeyCursor { - if filter.is_none() { - warn!("Consider using 'IdbObjectStoreImpl::get_items' instead of 'IdbSingleKeyCursor'"); - } + pub(super) fn new(field_name: String, field_value: Json) -> IdbSingleKeyCursor { IdbSingleKeyCursor { - db_index, field_name, field_value, - collect_filter: None, } } } -#[async_trait(?Send)] -impl CursorOps for IdbSingleKeyCursor { - fn db_index(&self) -> &IdbIndex { &self.db_index } - +impl CursorDriverImpl for IdbSingleKeyCursor { fn key_range(&self) -> CursorResult> { let js_value = serialize_to_js(&self.field_value).map_to_mm(|e| CursorError::ErrorSerializingIndexFieldValue { @@ -55,14 +38,7 @@ impl CursorOps for IdbSingleKeyCursor { Ok(Some(key_range)) } - fn on_collect_iter( - &mut self, - _key: JsValue, - value: &Json, - ) -> CursorResult<(CollectItemAction, CollectCursorAction)> { - if let Some(ref mut filter) = self.collect_filter { - return Ok(filter(value)); - } - Ok((CollectItemAction::Include, CollectCursorAction::Continue)) + fn on_iteration(&mut self, _key: JsValue) -> CursorResult<(CursorItemAction, CursorAction)> { + Ok((CursorItemAction::Include, CursorAction::Continue)) } } diff --git a/mm2src/mm2_db/src/indexed_db/drivers/object_store.rs b/mm2src/mm2_db/src/indexed_db/drivers/object_store.rs index 53c9be6104..a7b8081e9c 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/object_store.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/object_store.rs @@ -1,5 +1,4 @@ use super::{construct_event_closure, DbTransactionError, DbTransactionResult, InternalItem, ItemId}; -use crate::indexed_db::db_driver::cursor::IdbCursorBuilder; use common::{deserialize_from_js, serialize_to_js, stringify_js_error}; use futures::channel::mpsc; use futures::StreamExt; @@ -9,7 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use web_sys::{IdbObjectStore, IdbRequest}; +use web_sys::{IdbIndex, IdbObjectStore, IdbRequest}; pub struct IdbObjectStoreImpl { pub(crate) object_store: IdbObjectStore, @@ -58,10 +57,7 @@ impl IdbObjectStoreImpl { let index = index_str.to_owned(); let index_value_js = try_serialize_index_value!(serialize_to_js(&index_value), index_str); - let db_index = match self.object_store.index(index_str) { - Ok(index) => index, - Err(_) => return MmError::err(DbTransactionError::NoSuchIndex { index }), - }; + let db_index = self.open_index(index_str)?; let get_request = match db_index.get_all_with_key(&index_value_js) { Ok(request) => request, Err(e) => { @@ -90,10 +86,7 @@ impl IdbObjectStoreImpl { let index = index_str.to_owned(); let index_value_js = try_serialize_index_value!(serialize_to_js(&index_value), index); - let db_index = match self.object_store.index(index_str) { - Ok(index) => index, - Err(_) => return MmError::err(DbTransactionError::NoSuchIndex { index }), - }; + let db_index = self.open_index(index_str)?; let get_request = match db_index.get_all_keys_with_key(&index_value_js) { Ok(request) => request, Err(e) => { @@ -141,10 +134,7 @@ impl IdbObjectStoreImpl { let index = index_str.to_owned(); let index_value_js = try_serialize_index_value!(serialize_to_js(&index_value), index); - let db_index = match self.object_store.index(index_str) { - Ok(index) => index, - Err(_) => return MmError::err(DbTransactionError::NoSuchIndex { index }), - }; + let db_index = self.open_index(index_str)?; let count_request = match db_index.count_with_key(&index_value_js) { Ok(request) => request, Err(e) => { @@ -248,16 +238,13 @@ impl IdbObjectStoreImpl { Ok(()) } - pub(crate) fn cursor_builder(&self, index_str: &str) -> DbTransactionResult { - let db_index = match self.object_store.index(index_str) { - Ok(index) => index, - Err(_) => { - return MmError::err(DbTransactionError::NoSuchIndex { - index: index_str.to_owned(), - }) - }, - }; - Ok(IdbCursorBuilder::new(db_index)) + pub(crate) fn open_index(&self, index_str: &str) -> DbTransactionResult { + match self.object_store.index(index_str) { + Ok(index) => Ok(index), + Err(_) => MmError::err(DbTransactionError::NoSuchIndex { + index: index_str.to_owned(), + }), + } } async fn wait_for_request_complete(request: &IdbRequest) -> Result { diff --git a/mm2src/mm2_db/src/indexed_db/drivers/transaction.rs b/mm2src/mm2_db/src/indexed_db/drivers/transaction.rs index aef31a4e7d..f76794b2de 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/transaction.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/transaction.rs @@ -1,6 +1,7 @@ use super::IdbObjectStoreImpl; use common::wasm::stringify_js_error; use derive_more::Display; +use enum_from::EnumFromTrait; use mm2_err_handle::prelude::*; use serde_json::Value as Json; use std::collections::HashSet; @@ -11,7 +12,7 @@ use web_sys::IdbTransaction; pub type DbTransactionResult = Result>; -#[derive(Debug, Display, PartialEq)] +#[derive(Debug, Display, EnumFromTrait, PartialEq)] pub enum DbTransactionError { #[display(fmt = "No such table '{}'", table)] NoSuchTable { table: String }, @@ -44,6 +45,7 @@ pub enum DbTransactionError { description: String, }, #[display(fmt = "Error occurred due to an unexpected state: {:?}", _0)] + #[from_trait(WithInternal::internal)] UnexpectedState(String), #[display(fmt = "Transaction was aborted")] TransactionAborted, diff --git a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs index e04c622ddf..04dd7c37cb 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs @@ -18,10 +18,13 @@ //! you can use [`WithBound::bound`] along with [`WithOnly::only`]: //! ```rust //! let table = open_table_somehow(); -//! let all_rick_morty_swaps = table.open_cursor("search_index") +//! let all_rick_morty_swaps = table +//! .cursor_builder() //! .only("base_coin", "RICK", "MORTY")? //! .bound("base_coin_value", 10, 13) //! .bound("started_at", 1000000030.into(), u32::MAX.into()) +//! .open_cursor("search_index") +//! .await? //! .collect() //! .await?; //! ``` @@ -49,13 +52,12 @@ //! because ['RICK', 'MORTY', 13] < ['RICK', 'MORTY', 13, 1000000030], //! although it is expected to be within the specified bounds. -use crate::indexed_db::db_driver::cursor::{CollectCursorAction, CollectItemAction, CursorBoundValue, CursorOps, - DbFilter, IdbCursorBuilder}; +use crate::indexed_db::db_driver::cursor::CursorBoundValue; +pub(crate) use crate::indexed_db::db_driver::cursor::{CursorDriver, CursorFilters}; pub use crate::indexed_db::db_driver::cursor::{CursorError, CursorResult}; -use crate::indexed_db::{ItemId, TableSignature}; -use async_trait::async_trait; +use crate::indexed_db::{DbTable, ItemId, TableSignature}; use futures::channel::{mpsc, oneshot}; -use futures::StreamExt; +use futures::{SinkExt, StreamExt}; use mm2_err_handle::prelude::*; use serde::Serialize; use serde_json::{self as json, Value as Json}; @@ -65,102 +67,20 @@ use std::marker::PhantomData; pub(super) type DbCursorEventTx = mpsc::UnboundedSender; pub(super) type DbCursorEventRx = mpsc::UnboundedReceiver; -pub enum DbCursorEvent { - Collect { - options: CursorCollectOptions, - result_tx: oneshot::Sender>>, - }, -} - -pub enum CursorCollectOptions { - SingleKey { - field_name: String, - field_value: Json, - filter: Option, - }, - SingleKeyBound { - field_name: String, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - filter: Option, - }, - MultiKey { - keys: Vec<(String, Json)>, - filter: Option, - }, - MultiKeyBound { - only_keys: Vec<(String, Json)>, - bound_keys: Vec<(String, CursorBoundValue, CursorBoundValue)>, - filter: Option, - }, -} - -pub async fn cursor_event_loop(mut rx: DbCursorEventRx, cursor_builder: IdbCursorBuilder) { - while let Some(event) = rx.next().await { - match event { - DbCursorEvent::Collect { options, result_tx } => { - on_collect_cursor_event(result_tx, options, cursor_builder).await; - return; - }, - } - } +pub struct CursorBuilder<'transaction, 'reference, Table: TableSignature> { + db_table: &'reference DbTable<'transaction, Table>, + filters: CursorFilters, } -async fn on_collect_cursor_event( - result_tx: oneshot::Sender>>, - options: CursorCollectOptions, - cursor_builder: IdbCursorBuilder, -) { - async fn on_collect_cursor_event_impl( - options: CursorCollectOptions, - cursor_builder: IdbCursorBuilder, - ) -> CursorResult> { - match options { - CursorCollectOptions::SingleKey { - field_name, - field_value, - filter, - } => { - cursor_builder - .single_key_cursor(field_name, field_value, filter) - .collect() - .await - }, - CursorCollectOptions::SingleKeyBound { - field_name, - lower_bound, - upper_bound, - filter, - } => { - cursor_builder - .single_key_bound_cursor(field_name, lower_bound, upper_bound, filter)? - .collect() - .await - }, - CursorCollectOptions::MultiKey { keys, filter } => { - cursor_builder.multi_key_cursor(keys, filter)?.collect().await - }, - CursorCollectOptions::MultiKeyBound { - only_keys, - bound_keys, - filter, - } => { - cursor_builder - .multi_key_bound_cursor(only_keys, bound_keys, filter)? - .collect() - .await - }, +impl<'transaction, 'reference, Table: TableSignature> CursorBuilder<'transaction, 'reference, Table> { + pub(crate) fn new(db_table: &'reference DbTable<'transaction, Table>) -> Self { + CursorBuilder { + db_table, + filters: CursorFilters::default(), } } - let result = on_collect_cursor_event_impl(options, cursor_builder).await; - result_tx.send(result).ok(); -} - -pub trait WithOnly: Sized { - type ResultCursor; - - fn only(self, field_name: &str, field_value: Value) -> CursorResult + pub fn only(mut self, field_name: &str, field_value: Value) -> CursorResult where Value: Serialize + fmt::Debug, { @@ -170,367 +90,93 @@ pub trait WithOnly: Sized { value: field_value_str, description: e.to_string(), })?; - Ok(self.only_json(field_name, field_value)) - } - fn only_json(self, field_name: &str, field_value: Json) -> Self::ResultCursor; -} - -pub trait WithBound: Sized { - type ResultCursor; + self.filters.only_keys.push((field_name.to_owned(), field_value)); + Ok(self) + } - fn bound(self, field_name: &str, lower_bound: Value, upper_bound: Value) -> Self::ResultCursor + pub fn bound(mut self, field_name: &str, lower_bound: Value, upper_bound: Value) -> Self where CursorBoundValue: From, { let lower_bound = CursorBoundValue::from(lower_bound); let upper_bound = CursorBoundValue::from(upper_bound); - self.bound_values(field_name, lower_bound, upper_bound) - } - - fn bound_values( - self, - field_name: &str, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - ) -> Self::ResultCursor; -} - -pub trait WithFilter: Sized { - fn filter(self, filter: F) -> Self - where - F: FnMut(&Json) -> (CollectItemAction, CollectCursorAction) + Send + 'static, - { - let filter = Box::new(filter); - self.filter_boxed(filter) - } - - fn filter_boxed(self, filter: DbFilter) -> Self; -} - -#[async_trait] -pub trait CollectCursor { - async fn collect(self) -> CursorResult>; -} - -#[async_trait] -impl + Send> CollectCursor for T { - async fn collect(self) -> CursorResult> { self.collect_impl().await } -} - -#[async_trait] -pub(crate) trait CollectCursorImpl: Sized { - fn into_collect_options(self) -> CursorCollectOptions; - - fn event_tx(&self) -> DbCursorEventTx; - - async fn collect_impl(self) -> CursorResult> { - let event_tx = self.event_tx(); - let options = self.into_collect_options(); - - let (result_tx, result_rx) = oneshot::channel(); - let event = DbCursorEvent::Collect { result_tx, options }; - let items: Vec<(ItemId, Json)> = send_event_recv_response(&event_tx, event, result_rx).await?; - - items - .into_iter() - .map(|(item_id, item)| json::from_value(item).map(|item| (item_id, item))) - .map(|res| res.map_to_mm(|e| CursorError::ErrorDeserializingItem(e.to_string()))) - // Item = CursorResult<(ItemId, Table)> - .collect() - } -} - -pub struct DbEmptyCursor<'a, Table: TableSignature> { - event_tx: DbCursorEventTx, - filter: Option, - phantom: PhantomData<&'a Table>, -} - -impl<'a, Table: TableSignature> WithOnly for DbEmptyCursor<'a, Table> { - type ResultCursor = DbSingleKeyCursor<'a, Table>; - - fn only_json(self, field_name: &str, field_value: Json) -> Self::ResultCursor { - DbSingleKeyCursor { - event_tx: self.event_tx, - field_name: field_name.to_owned(), - field_value, - filter: self.filter, - phantom: PhantomData::default(), - } - } -} - -impl<'a, Table: TableSignature> WithBound for DbEmptyCursor<'a, Table> { - type ResultCursor = DbSingleKeyBoundCursor<'a, Table>; - - fn bound_values( - self, - field_name: &str, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - ) -> Self::ResultCursor { - DbSingleKeyBoundCursor { - event_tx: self.event_tx, - field_name: field_name.to_owned(), - lower_bound, - upper_bound, - filter: self.filter, - phantom: PhantomData::default(), - } - } -} - -impl<'a, Table: TableSignature> WithFilter for DbEmptyCursor<'a, Table> { - fn filter_boxed(mut self, filter: DbFilter) -> Self { - self.filter = Some(filter); + self.filters + .bound_keys + .push((field_name.to_owned(), lower_bound, upper_bound)); self } -} - -impl<'a, Table: TableSignature> DbEmptyCursor<'a, Table> { - pub(super) fn new(event_tx: DbCursorEventTx) -> DbEmptyCursor<'a, Table> { - DbEmptyCursor { - event_tx, - filter: None, - phantom: PhantomData::default(), - } - } -} - -pub struct DbSingleKeyCursor<'a, Table: TableSignature> { - event_tx: DbCursorEventTx, - field_name: String, - field_value: Json, - filter: Option, - phantom: PhantomData<&'a Table>, -} - -impl<'a, Table: TableSignature> WithOnly for DbSingleKeyCursor<'a, Table> { - type ResultCursor = DbMultiKeyCursor<'a, Table>; - - fn only_json(self, field_name: &str, field_value: Json) -> Self::ResultCursor { - let keys = vec![ - (self.field_name, self.field_value), - (field_name.to_owned(), field_value), - ]; - DbMultiKeyCursor { - event_tx: self.event_tx, - keys, - filter: self.filter, - phantom: PhantomData::default(), - } - } -} - -impl<'a, Table: TableSignature> WithBound for DbSingleKeyCursor<'a, Table> { - type ResultCursor = DbMultiKeyBoundCursor<'a, Table>; - - fn bound_values( - self, - field_name: &str, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - ) -> Self::ResultCursor { - let only_keys = vec![(self.field_name, self.field_value)]; - let bound_keys = vec![(field_name.to_owned(), lower_bound, upper_bound)]; - DbMultiKeyBoundCursor { - event_tx: self.event_tx, - only_keys, - bound_keys, - filter: self.filter, - phantom: PhantomData::default(), - } - } -} -impl<'a, Table: TableSignature> WithFilter for DbSingleKeyCursor<'a, Table> { - fn filter_boxed(mut self, filter: DbFilter) -> Self { - self.filter = Some(filter); + pub fn reverse(mut self) -> Self { + self.filters.reverse = true; self } -} - -#[async_trait] -impl<'a, Table: TableSignature> CollectCursorImpl
for DbSingleKeyCursor<'a, Table> { - fn into_collect_options(self) -> CursorCollectOptions { - CursorCollectOptions::SingleKey { - field_name: self.field_name, - field_value: self.field_value, - filter: self.filter, - } - } - - fn event_tx(&self) -> DbCursorEventTx { self.event_tx.clone() } -} - -/// `DbSingleKeyBoundCursor` doesn't implement `WithOnly` trait, because indexes MUST start with `only` values. -pub struct DbSingleKeyBoundCursor<'a, Table: TableSignature> { - event_tx: DbCursorEventTx, - field_name: String, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - filter: Option, - phantom: PhantomData<&'a Table>, -} -impl<'a, Table: TableSignature> WithBound for DbSingleKeyBoundCursor<'a, Table> { - type ResultCursor = DbMultiKeyBoundCursor<'a, Table>; - - fn bound_values( - self, - field_name: &str, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - ) -> Self::ResultCursor { - let bound_keys = vec![ - (self.field_name, self.lower_bound, self.upper_bound), - (field_name.to_owned(), lower_bound, upper_bound), - ]; - DbMultiKeyBoundCursor { - event_tx: self.event_tx, - only_keys: Vec::new(), - bound_keys, - filter: self.filter, + /// Opens a cursor by the specified `index`. + /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openCursor + pub async fn open_cursor(self, index: &str) -> CursorResult> { + let event_tx = + self.db_table + .open_cursor(index, self.filters) + .await + .mm_err(|e| CursorError::ErrorOpeningCursor { + description: e.to_string(), + })?; + Ok(CursorIter { + event_tx, phantom: PhantomData::default(), - } - } -} - -impl<'a, Table: TableSignature> WithFilter for DbSingleKeyBoundCursor<'a, Table> { - fn filter_boxed(mut self, filter: DbFilter) -> Self { - self.filter = Some(filter); - self + }) } } -#[async_trait] -impl<'a, Table: TableSignature> CollectCursorImpl
for DbSingleKeyBoundCursor<'a, Table> { - fn into_collect_options(self) -> CursorCollectOptions { - CursorCollectOptions::SingleKeyBound { - field_name: self.field_name, - lower_bound: self.lower_bound, - upper_bound: self.upper_bound, - filter: self.filter, - } - } - - fn event_tx(&self) -> DbCursorEventTx { self.event_tx.clone() } -} - -pub struct DbMultiKeyCursor<'a, Table: TableSignature> { +pub struct CursorIter<'transaction, Table> { event_tx: DbCursorEventTx, - keys: Vec<(String, Json)>, - filter: Option, - phantom: PhantomData<&'a Table>, -} - -impl<'a, Table: TableSignature> WithOnly for DbMultiKeyCursor<'a, Table> { - type ResultCursor = DbMultiKeyCursor<'a, Table>; - - fn only_json(mut self, field_name: &str, field_value: Json) -> Self::ResultCursor { - self.keys.push((field_name.to_owned(), field_value)); - self - } + phantom: PhantomData<&'transaction Table>, } -impl<'a, Table: TableSignature> WithBound for DbMultiKeyCursor<'a, Table> { - type ResultCursor = DbMultiKeyBoundCursor<'a, Table>; - - fn bound_values( - self, - field_name: &str, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - ) -> Self::ResultCursor { - DbMultiKeyBoundCursor { - event_tx: self.event_tx, - only_keys: self.keys, - bound_keys: vec![(field_name.to_owned(), lower_bound, upper_bound)], - filter: self.filter, - phantom: PhantomData::default(), - } - } -} - -impl<'a, Table: TableSignature> WithFilter for DbMultiKeyCursor<'a, Table> { - fn filter_boxed(mut self, filter: DbFilter) -> Self { - self.filter = Some(filter); - self +impl<'transaction, Table: TableSignature> CursorIter<'transaction, Table> { + /// Advances the iterator and returns the next value. + /// Please note that the items are sorted by the index keys. + pub async fn next(&mut self) -> CursorResult> { + let (result_tx, result_rx) = oneshot::channel(); + self.event_tx + .send(DbCursorEvent::NextItem { result_tx }) + .await + .map_to_mm(|e| CursorError::UnexpectedState(format!("Error sending cursor event: {e}")))?; + let maybe_item = result_rx + .await + .map_to_mm(|e| CursorError::UnexpectedState(format!("Error receiving cursor item: {e}")))??; + let (item_id, item) = match maybe_item { + Some((item_id, item)) => (item_id, item), + None => return Ok(None), + }; + let item = json::from_value(item).map_to_mm(|e| CursorError::ErrorDeserializingItem(e.to_string()))?; + Ok(Some((item_id, item))) } -} -#[async_trait] -impl<'a, Table: TableSignature> CollectCursorImpl
for DbMultiKeyCursor<'a, Table> { - fn into_collect_options(self) -> CursorCollectOptions { - CursorCollectOptions::MultiKey { - keys: self.keys, - filter: self.filter, + pub async fn collect(mut self) -> CursorResult> { + let mut result = Vec::new(); + while let Some((item_id, item)) = self.next().await? { + result.push((item_id, item)); } - } - - fn event_tx(&self) -> DbCursorEventTx { self.event_tx.clone() } -} - -/// `DbMultiKeyBoundCursor` doesn't implement `WithOnly` trait, because indexes MUST start with `only` values. -pub struct DbMultiKeyBoundCursor<'a, Table: TableSignature> { - event_tx: DbCursorEventTx, - only_keys: Vec<(String, Json)>, - bound_keys: Vec<(String, CursorBoundValue, CursorBoundValue)>, - filter: Option, - phantom: PhantomData<&'a Table>, -} - -impl<'a, Table: TableSignature> WithBound for DbMultiKeyBoundCursor<'a, Table> { - type ResultCursor = DbMultiKeyBoundCursor<'a, Table>; - - fn bound_values( - mut self, - field_name: &str, - lower_bound: CursorBoundValue, - upper_bound: CursorBoundValue, - ) -> Self::ResultCursor { - self.bound_keys.push((field_name.to_owned(), lower_bound, upper_bound)); - self + Ok(result) } } -impl<'a, Table: TableSignature> WithFilter for DbMultiKeyBoundCursor<'a, Table> { - fn filter_boxed(mut self, filter: DbFilter) -> Self { - self.filter = Some(filter); - self - } +pub enum DbCursorEvent { + NextItem { + result_tx: oneshot::Sender>>, + }, } -#[async_trait] -impl<'a, Table: TableSignature> CollectCursorImpl
for DbMultiKeyBoundCursor<'a, Table> { - fn into_collect_options(self) -> CursorCollectOptions { - CursorCollectOptions::MultiKeyBound { - only_keys: self.only_keys, - bound_keys: self.bound_keys, - filter: self.filter, +pub(crate) async fn cursor_event_loop(mut rx: DbCursorEventRx, mut cursor: CursorDriver) { + while let Some(event) = rx.next().await { + match event { + DbCursorEvent::NextItem { result_tx } => { + result_tx.send(cursor.next().await).ok(); + }, } } - - fn event_tx(&self) -> DbCursorEventTx { self.event_tx.clone() } -} - -async fn send_event_recv_response( - event_tx: &mpsc::UnboundedSender, - event: Event, - result_rx: oneshot::Receiver>, -) -> CursorResult { - if let Err(e) = event_tx.unbounded_send(event) { - let error = format!("Error sending event: {}", e); - return MmError::err(CursorError::UnexpectedState(error)); - } - match result_rx.await { - Ok(result) => result, - Err(e) => { - let error = format!("Error receiving result: {}", e); - MmError::err(CursorError::UnexpectedState(error)) - }, - } } mod tests { @@ -609,6 +255,15 @@ mod tests { } } + #[track_caller] + async fn next_item(cursor_iter: &mut CursorIter<'_, Table>) -> Option
{ + cursor_iter + .next() + .await + .expect("!CursorIter::next") + .map(|(_item_id, item)| item) + } + /// The table with `BeBigUint` parameters. #[derive(Clone, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] #[serde(deny_unknown_fields)] @@ -705,13 +360,14 @@ mod tests { // Get every item that satisfies the following [num_x, num_y] bound. let actual_items = table + .cursor_builder() + .bound("timestamp_x", num_x.clone(), num_y.clone()) .open_cursor("timestamp_x") .await - .expect("!DbTable::open_cursor") - .bound("timestamp_x", num_x.clone(), num_y.clone()) + .expect("!CursorBuilder::open_cursor") .collect() .await - .expect("!DbSingleKeyBoundCursor::collect") + .expect("!CursorIter::collect") .into_iter() // Map `(ItemId, TimestampTable)` into `BeBigUint`. .map(|(_item_id, item)| item.timestamp_x) @@ -759,14 +415,15 @@ mod tests { fill_table(&table, items).await; let mut actual_items = table + .cursor_builder() + .only("base_coin", "RICK") + .expect("!CursorBuilder::only") .open_cursor("base_coin") .await - .expect("!DbTable::open_cursor") - .only("base_coin", "RICK") - .expect("!DbEmptyCursor::only") + .expect("!CursorBuilder::open_cursor") .collect() .await - .expect("!DbSingleKeyCursor::collect") + .expect("!CursorIter::collect") .into_iter() .map(|(_item_id, item)| item) .collect::>(); @@ -812,13 +469,14 @@ mod tests { fill_table(&table, items).await; let mut actual_items = table + .cursor_builder() + .bound("rel_coin_value", 5u32, u32::MAX) .open_cursor("rel_coin_value") .await - .expect("!DbTable::open_cursor") - .bound("rel_coin_value", 5u32, u32::MAX) + .expect("!CursorBuilder::open_cursor") .collect() .await - .expect("!DbSingleKeyCursor::collect") + .expect("!CursorIter::collect") .into_iter() .map(|(_item_id, item)| item) .collect::>(); @@ -870,18 +528,19 @@ mod tests { fill_table(&table, items).await; let mut actual_items = table - .open_cursor("basecoin_basecoinvalue_startedat_index") - .await - .expect("!DbTable::open_cursor") + .cursor_builder() .only("base_coin", "RICK") - .expect("!DbEmptyCursor::only") + .expect("!CursorBuilder::only") .only("base_coin_value", 12) - .expect("!DbSingleKeyCursor::only") + .expect("!CursorBuilder::only") .only("started_at", 721) - .expect("!DbMultiKeyCursor::only") + .expect("!CursorBuilder::only") + .open_cursor("basecoin_basecoinvalue_startedat_index") + .await + .expect("!CursorBuilder::open_cursor") .collect() .await - .expect("!DbMultiKeyCursor::collect") + .expect("!CursorIter::collect") .into_iter() .map(|(_item_id, item)| item) .collect::>(); @@ -946,19 +605,20 @@ mod tests { fill_table(&table, items).await; let actual_items = table - .open_cursor("all_fields_index") - .await - .expect("!DbTable::open_cursor") + .cursor_builder() .only("base_coin", "RICK") - .expect("!DbEmptyCursor::only") + .expect("!CursorBuilder::only") .only("rel_coin", "QRC20") - .expect("!DbEmptyCursor::only") + .expect("!CursorBuilder::only") .bound("base_coin_value", 3u32, 8u32) .bound("rel_coin_value", 10u32, 12u32) .bound("started_at", 600i32, 800i32) + .open_cursor("all_fields_index") + .await + .expect("!CursorBuilder::open_cursor") .collect() .await - .expect("!DbMultiKeyCursor::collect") + .expect("!CursorIter::collect") .into_iter() .map(|(_item_id, item)| item) .collect::>(); @@ -1006,15 +666,16 @@ mod tests { fill_table(&table, items).await; let actual_items = table - .open_cursor("timestamp_xyz") - .await - .expect("!DbTable::open_cursor") + .cursor_builder() .bound("timestamp_x", BeBigUint::from(u64::MAX - 1), BeBigUint::from(u128::MAX)) .bound("timestamp_y", 0u32, 5u32) .bound("timestamp_z", BeBigUint::from(u64::MAX), BeBigUint::from(u128::MAX - 2)) + .open_cursor("timestamp_xyz") + .await + .expect("!CursorBuilder::open_cursor") .collect() .await - .expect("!DbMultiKeyCursor::collect") + .expect("!CursorIter::collect") .into_iter() .map(|(_item_id, item)| item) .collect::>(); @@ -1029,4 +690,198 @@ mod tests { assert_eq!(actual_items, expected_items); } + + #[wasm_bindgen_test] + async fn test_iter_without_constraints() { + const DB_NAME: &str = "TEST_ITER_WITHOUT_CONSTRAINTS"; + const DB_VERSION: u32 = 1; + + register_wasm_log(); + + let items = vec![ + swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), + swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), + swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281), + swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92), + ]; + + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .expect("!IndexedDb::init"); + let transaction = db.transaction().await.expect("!IndexedDb::transaction"); + let table = transaction + .table::() + .await + .expect("!DbTransaction::open_table"); + fill_table(&table, items).await; + + let mut cursor_iter = table + .cursor_builder() + .open_cursor("rel_coin_value") + .await + .expect("!CursorBuilder::open_cursor"); + + // The items must be sorted by `rel_coin_value`. + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281)) + ); + assert!(next_item(&mut cursor_iter).await.is_none()); + // Try to poll one more time. This should not fail but return `None`. + assert!(next_item(&mut cursor_iter).await.is_none()); + } + + #[wasm_bindgen_test] + async fn test_rev_iter_without_constraints() { + const DB_NAME: &str = "TEST_REV_ITER_WITHOUT_CONSTRAINTS"; + const DB_VERSION: u32 = 1; + + register_wasm_log(); + + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .expect("!IndexedDb::init"); + let transaction = db.transaction().await.expect("!IndexedDb::transaction"); + let table = transaction + .table::() + .await + .expect("!DbTransaction::open_table"); + + table + .cursor_builder() + .reverse() + .open_cursor("rel_coin_value") + .await + .map(|_| ()) + .expect_err( + "CursorBuilder::open_cursor should have failed because 'reverse' can be used with key range only", + ); + } + + #[wasm_bindgen_test] + async fn test_iter_single_key_bound_cursor() { + const DB_NAME: &str = "TEST_ITER_SINGLE_KEY_BOUND_CURSOR"; + const DB_VERSION: u32 = 1; + + register_wasm_log(); + + let items = vec![ + swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), + swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), + swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281), // + + swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92), // + + swap_item!("uuid5", "QRC20", "RICK", 2, 4, 721), + swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + + ]; + + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .expect("!IndexedDb::init"); + let transaction = db.transaction().await.expect("!IndexedDb::transaction"); + let table = transaction + .table::() + .await + .expect("!DbTransaction::open_table"); + fill_table(&table, items).await; + + let mut cursor_iter = table + .cursor_builder() + .bound("rel_coin_value", 5u32, u32::MAX) + .open_cursor("rel_coin_value") + .await + .expect("!CursorBuilder::open_cursor"); + + // The items must be sorted by `rel_coin_value`. + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281)) + ); + assert!(next_item(&mut cursor_iter).await.is_none()); + // Try to poll one more time. This should not fail but return `None`. + assert!(next_item(&mut cursor_iter).await.is_none()); + } + + #[wasm_bindgen_test] + async fn test_rev_iter_single_key_bound_cursor() { + const DB_NAME: &str = "TEST_REV_ITER_SINGLE_KEY_BOUND_CURSOR"; + const DB_VERSION: u32 = 1; + + register_wasm_log(); + + let items = vec![ + swap_item!("uuid1", "RICK", "MORTY", 10, 3, 700), + swap_item!("uuid2", "MORTY", "KMD", 95000, 1, 721), + swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281), // + + swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92), // + + swap_item!("uuid5", "QRC20", "RICK", 2, 4, 721), + swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + + ]; + + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + .with_version(DB_VERSION) + .with_table::() + .build() + .await + .expect("!IndexedDb::init"); + let transaction = db.transaction().await.expect("!IndexedDb::transaction"); + let table = transaction + .table::() + .await + .expect("!DbTransaction::open_table"); + fill_table(&table, items).await; + + let mut cursor_iter = table + .cursor_builder() + .bound("rel_coin_value", 5u32, u32::MAX) + .reverse() + .open_cursor("rel_coin_value") + .await + .expect("!CursorBuilder::open_cursor"); + + // The items must be sorted in reverse order by `rel_coin_value`. + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid3", "RICK", "XYZ", 7, u32::MAX, 1281)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214)) + ); + assert_eq!( + next_item(&mut cursor_iter).await, + Some(swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92)) + ); + assert!(next_item(&mut cursor_iter).await.is_none()); + // Try to poll one more time. This should not fail but return `None`. + assert!(next_item(&mut cursor_iter).await.is_none()); + } } diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index 897161464f..97fc4d684d 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -48,15 +48,15 @@ pub use db_driver::{DbTransactionError, DbTransactionResult, DbUpgrader, InitDbE pub use db_lock::{ConstructibleDb, DbLocked, SharedDb, WeakDb}; use db_driver::{IdbDatabaseBuilder, IdbDatabaseImpl, IdbObjectStoreImpl, IdbTransactionImpl, OnUpgradeNeededCb}; -use indexed_cursor::{cursor_event_loop, DbCursorEventTx, DbEmptyCursor}; +use indexed_cursor::{cursor_event_loop, CursorBuilder, CursorDriver, CursorError, CursorFilters, CursorResult, + DbCursorEventTx}; type DbEventTx = mpsc::UnboundedSender; type DbTransactionEventTx = mpsc::UnboundedSender; type DbTableEventTx = mpsc::UnboundedSender; pub mod cursor_prelude { - pub use crate::indexed_db::indexed_cursor::{CollectCursor, CursorError, CursorResult, WithBound, WithFilter, - WithOnly}; + pub use crate::indexed_db::indexed_cursor::{CursorError, CursorResult}; } pub trait TableSignature: DeserializeOwned + Serialize + 'static { @@ -174,21 +174,20 @@ pub struct IndexedDb { event_tx: DbEventTx, } -async fn send_event_recv_response( +async fn send_event_recv_response( event_tx: &mpsc::UnboundedSender, event: Event, - result_rx: oneshot::Receiver>, -) -> DbTransactionResult { + result_rx: oneshot::Receiver>, +) -> MmResult +where + Error: WithInternal + NotMmError, +{ if let Err(e) = event_tx.unbounded_send(event) { - let error = format!("Error sending event: {}", e); - return MmError::err(DbTransactionError::UnexpectedState(error)); + return MmError::err(Error::internal(format!("Error sending event: {}", e))); } match result_rx.await { Ok(result) => result, - Err(e) => { - let error = format!("Error receiving result: {}", e); - MmError::err(DbTransactionError::UnexpectedState(error)) - }, + Err(e) => MmError::err(Error::internal(format!("Error receiving result: {}", e))), } } @@ -232,9 +231,9 @@ impl IndexedDb { } } -pub struct DbTransaction<'a> { +pub struct DbTransaction<'transaction> { event_tx: DbTransactionEventTx, - phantom: PhantomData<&'a ()>, + phantom: PhantomData<&'transaction ()>, } impl DbTransaction<'_> { @@ -297,9 +296,9 @@ impl DbTransaction<'_> { } } -pub struct DbTable<'a, Table: TableSignature> { +pub struct DbTable<'transaction, Table: TableSignature> { event_tx: DbTableEventTx, - phantom: PhantomData<&'a Table>, + phantom: PhantomData<&'transaction Table>, } pub enum AddOrIgnoreResult { @@ -307,7 +306,7 @@ pub enum AddOrIgnoreResult { ExistAlready(ItemId), } -impl DbTable<'_, Table> { +impl<'transaction, Table: TableSignature> DbTable<'transaction, Table> { /// Adds the given item to the table. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add pub async fn add_item(&self, item: &Table) -> DbTransactionResult { @@ -636,16 +635,10 @@ impl DbTable<'_, Table> { send_event_recv_response(&self.event_tx, event, result_rx).await } - /// Opens a cursor by the specified `index`. - /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openCursor - pub async fn open_cursor(&self, index: &str) -> DbTransactionResult> { - let (result_tx, result_rx) = oneshot::channel(); - let event = internal::DbTableEvent::OpenCursor { - index: index.to_owned(), - result_tx, - }; - let cursor_event_tx = send_event_recv_response(&self.event_tx, event, result_rx).await?; - Ok(DbEmptyCursor::new(cursor_event_tx)) + /// Returns a `CursorBuilder` builder. It can be used to open a cursor at the specified with specific key bounds. + /// See [`CursorBuilder::open_cursor`]. + pub fn cursor_builder<'reference>(&'reference self) -> CursorBuilder<'transaction, 'reference, Table> { + CursorBuilder::new(self) } /// Whether the transaction is aborted. @@ -655,6 +648,21 @@ impl DbTable<'_, Table> { send_event_recv_response(&self.event_tx, event, result_rx).await } + /// Opens a cursor by the specified `index`. + /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/openCursor + async fn open_cursor(&self, index: &str, filters: CursorFilters) -> CursorResult { + let (result_tx, result_rx) = oneshot::channel(); + let event = internal::DbTableEvent::OpenCursor { + index: index.to_owned(), + filters, + result_tx, + }; + let cursor_event_tx = send_event_recv_response(&self.event_tx, event, result_rx) + .await + .mm_err(|e| CursorError::UnexpectedState(e.to_string()))?; + Ok(cursor_event_tx) + } + fn deserialize_items(items: Vec<(ItemId, Json)>) -> DbTransactionResult> { items .into_iter() @@ -726,8 +734,12 @@ async fn table_event_loop(mut rx: mpsc::UnboundedReceiver { result_tx.send(Ok(table.aborted())).ok(); }, - internal::DbTableEvent::OpenCursor { index, result_tx } => { - open_cursor(&table, index, result_tx); + internal::DbTableEvent::OpenCursor { + index, + filters, + result_tx, + } => { + open_cursor(&table, index, filters, result_tx); }, } } @@ -761,18 +773,30 @@ impl MultiIndex { fn open_cursor( table: &IdbObjectStoreImpl, index: String, - result_tx: oneshot::Sender>, + filters: CursorFilters, + result_tx: oneshot::Sender>, ) { - let cursor_builder = match table.cursor_builder(&index) { - Ok(builder) => builder, + let db_index = match table.open_index(&index) { + Ok(db_index) => db_index, + Err(tr_err) => { + let cursor_err = tr_err.map(|tr_err| CursorError::ErrorOpeningCursor { + description: tr_err.to_string(), + }); + result_tx.send(Err(cursor_err)).ok(); + return; + }, + }; + let cursor = match CursorDriver::init_cursor(db_index, filters) { + Ok(cursor) => cursor, Err(e) => { result_tx.send(Err(e)).ok(); return; }, }; + let (event_tx, event_rx) = mpsc::unbounded(); - let fut = async move { cursor_event_loop(event_rx, cursor_builder).await }; + let fut = async move { cursor_event_loop(event_rx, cursor).await }; // `cursor_event_loop` will finish almost immediately once `event_tx` is dropped. spawn_local(fut); @@ -843,7 +867,8 @@ mod internal { }, OpenCursor { index: String, - result_tx: oneshot::Sender>, + filters: CursorFilters, + result_tx: oneshot::Sender>, }, } } diff --git a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs index 6d2dc630bb..e38b8c5419 100644 --- a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs +++ b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs @@ -201,37 +201,41 @@ mod wasm_impl { let items = match (&filter.my_coin, &filter.other_coin) { (Some(my_coin), Some(other_coin)) => { my_swaps_table - .open_cursor("with_my_other_coins") - .await? + .cursor_builder() .only("my_coin", my_coin)? .only("other_coin", other_coin)? .bound("started_at", from_timestamp, to_timestamp) + .open_cursor("with_my_other_coins") + .await? .collect() .await? }, (Some(my_coin), None) => { my_swaps_table - .open_cursor("with_my_coin") - .await? + .cursor_builder() .only("my_coin", my_coin)? .bound("started_at", from_timestamp, to_timestamp) + .open_cursor("with_my_coin") + .await? .collect() .await? }, (None, Some(other_coin)) => { my_swaps_table - .open_cursor("with_other_coin") - .await? + .cursor_builder() .only("other_coin", other_coin)? .bound("started_at", from_timestamp, to_timestamp) + .open_cursor("with_other_coin") + .await? .collect() .await? }, (None, None) => { my_swaps_table + .cursor_builder() + .bound("started_at", from_timestamp, to_timestamp) .open_cursor("started_at") .await? - .bound("started_at", from_timestamp, to_timestamp) .collect() .await? }, From a2552db1b1e825103681eac343b8df03ff662b3b Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 7 Mar 2023 18:32:48 +0200 Subject: [PATCH 11/79] fix: add #[allow(dead_code)] to into_inner fn of BlockHeaderStorage impl --- mm2src/coins/utxo/utxo_block_header_storage/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs index 888d067f02..dfe1ae3839 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs @@ -58,6 +58,7 @@ impl BlockHeaderStorage { }) } + #[allow(dead_code)] pub(crate) fn into_inner(self) -> Box { self.inner } } From 1a3bc4559fd8a71f63bfe45d135bda97792600c9 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 12:08:38 +0300 Subject: [PATCH 12/79] update jemalloc config, enable `share-generics` nightly feat, use lld Signed-off-by: ozkanonur --- .cargo/config | 26 +++++++++++++++++++++++++- Dockerfile | 5 +---- Dockerfile.x86_64-linux-android | 1 + README.md | 6 ------ azure-pipelines-build-stage-job.yml | 1 - azure-pipelines-release-stage-job.yml | 1 - 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.cargo/config b/.cargo/config index 056307787e..aaacb7ffb5 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,3 +1,27 @@ +[env] +JEMALLOC_SYS_WITH_MALLOC_CONF = "percpu_arena:percpu,oversize_threshold:0,background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000" + +[target.'cfg(all())'] +rustflags = [ "-Zshare-generics=y" ] + +# Install lld using package manager +[target.x86_64-unknown-linux-gnu] +linker = "clang" +rustflags = [ "-Clink-arg=-fuse-ld=lld" ] + +# `brew install llvm` +[target.x86_64-apple-darwin] +rustflags = [ + "-C", + "link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", +] + +[target.aarch64-apple-darwin] +rustflags = [ + "-C", + "link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld", +] + [target.wasm32-unknown-unknown] runner = 'wasm-bindgen-test-runner' -rustflags = ["--cfg=web_sys_unstable_apis"] +rustflags = [ "--cfg=web_sys_unstable_apis" ] diff --git a/Dockerfile b/Dockerfile index 1fdc5226c2..6a1ba87559 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN \ 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 &&\ + 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 @@ -48,9 +48,6 @@ RUN cd /mm2 && cargo fetch # Only needed when we're developing or changing something locally. #COPY . /mm2 -# Important for x86_64 builds -ENV JEMALLOC_SYS_WITH_MALLOC_CONF="background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto" - # Build MM1 and MM2. # Increased verbosity here allows us to see the MM1 CMake logs. RUN cd /mm2 &&\ diff --git a/Dockerfile.x86_64-linux-android b/Dockerfile.x86_64-linux-android index e321d76eda..54b61089f4 100644 --- a/Dockerfile.x86_64-linux-android +++ b/Dockerfile.x86_64-linux-android @@ -4,6 +4,7 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ ca-certificates \ cmake \ + lld \ gcc \ libc6-dev \ make \ diff --git a/README.md b/README.md index da537c40f6..b5e9acaa39 100755 --- a/README.md +++ b/README.md @@ -89,12 +89,6 @@ If you want to build from source, the following prerequisites are required: rustup component add rustfmt-preview ``` -**Note for x86_64 UNIX systems**: -To have more efficient memory consumption please execute the following command before building mm2. (It's also good to have before launching mm2.) -```sh -export JEMALLOC_SYS_WITH_MALLOC_CONF="background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto" -``` - 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). diff --git a/azure-pipelines-build-stage-job.yml b/azure-pipelines-build-stage-job.yml index 8cdcc82e9e..6af4e13cfb 100644 --- a/azure-pipelines-build-stage-job.yml +++ b/azure-pipelines-build-stage-job.yml @@ -50,7 +50,6 @@ jobs: condition: ne ( variables['Build.Reason'], 'PullRequest' ) env: MANUAL_MM_VERSION: true - JEMALLOC_SYS_WITH_MALLOC_CONF: "background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto" - task: Docker@2 displayName: Build & Push container of dev branch condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) diff --git a/azure-pipelines-release-stage-job.yml b/azure-pipelines-release-stage-job.yml index 06ef60c34e..5ca79b0dd6 100644 --- a/azure-pipelines-release-stage-job.yml +++ b/azure-pipelines-release-stage-job.yml @@ -67,7 +67,6 @@ jobs: condition: eq( variables['DEBUG_UPLOADED'], '' ) env: MANUAL_MM_VERSION: true - JEMALLOC_SYS_WITH_MALLOC_CONF: "background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto" - bash: | zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Debug target-xenial/debug/mm2 target-xenial/debug/libmm2.a -j displayName: 'Prepare debug build upload Linux' From c4305a1a6fa520e1b21b535a08f689ffcbd03a54 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 13:17:05 +0300 Subject: [PATCH 13/79] comment out lld configs Signed-off-by: ozkanonur --- .cargo/config | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.cargo/config b/.cargo/config index aaacb7ffb5..564b1fb152 100644 --- a/.cargo/config +++ b/.cargo/config @@ -4,23 +4,23 @@ JEMALLOC_SYS_WITH_MALLOC_CONF = "percpu_arena:percpu,oversize_threshold:0,backgr [target.'cfg(all())'] rustflags = [ "-Zshare-generics=y" ] -# Install lld using package manager -[target.x86_64-unknown-linux-gnu] -linker = "clang" -rustflags = [ "-Clink-arg=-fuse-ld=lld" ] - -# `brew install llvm` -[target.x86_64-apple-darwin] -rustflags = [ - "-C", - "link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", -] - -[target.aarch64-apple-darwin] -rustflags = [ - "-C", - "link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld", -] +# # Install lld using package manager +# [target.x86_64-unknown-linux-gnu] +# linker = "clang" +# rustflags = [ "-Clink-arg=-fuse-ld=lld" ] +# +# # `brew install llvm` +# [target.x86_64-apple-darwin] +# rustflags = [ +# "-C", +# "link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld", +# ] +# +# [target.aarch64-apple-darwin] +# rustflags = [ +# "-C", +# "link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld", +# ] [target.wasm32-unknown-unknown] runner = 'wasm-bindgen-test-runner' From 03dadebac61662665dbaba56cd1731980f731afe Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Wed, 8 Mar 2023 14:22:38 +0200 Subject: [PATCH 14/79] ci: disable mac ci steps (#1701) Reviewed-by: ozkanonur --- azure-pipelines.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 44e1a63d57..ce9e62712b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -41,10 +41,10 @@ stages: name: 'MM2_Lint_Linux' os: 'Linux' - - template: azure-pipelines-lint-stage-job.yml # Template reference - parameters: - name: 'MM2_Lint_MacOS' - os: 'Darwin' +# - template: azure-pipelines-lint-stage-job.yml # Template reference +# parameters: +# name: 'MM2_Lint_MacOS' +# os: 'Darwin' - template: azure-pipelines-lint-stage-job.yml # Template reference parameters: @@ -72,9 +72,9 @@ stages: - template: azure-pipelines-android-job.yml parameters: os: 'Linux' - - template: azure-pipelines-ios-job.yml - parameters: - os: 'Darwin' +# - template: azure-pipelines-ios-job.yml +# parameters: +# os: 'Darwin' - stage: Desktop displayName: Desktop Build and test @@ -91,15 +91,15 @@ stages: alice_userpass: 'ALICE_USERPASS_LINUX' telegram_api_key: 'TELEGRAM_API_KEY' - - template: azure-pipelines-build-stage-job.yml # Template reference - parameters: - name: 'MM2_Build_MacOS' - os: 'Darwin' - bob_passphrase: 'BOB_PASSPHRASE_MAC' - bob_userpass: 'BOB_USERPASS_MAC' - alice_passphrase: 'ALICE_PASSPHRASE_MAC' - alice_userpass: 'ALICE_USERPASS_MAC' - telegram_api_key: 'TELEGRAM_API_KEY' +# - template: azure-pipelines-build-stage-job.yml # Template reference +# parameters: +# name: 'MM2_Build_MacOS' +# os: 'Darwin' +# bob_passphrase: 'BOB_PASSPHRASE_MAC' +# bob_userpass: 'BOB_USERPASS_MAC' +# alice_passphrase: 'ALICE_PASSPHRASE_MAC' +# alice_userpass: 'ALICE_USERPASS_MAC' +# telegram_api_key: 'TELEGRAM_API_KEY' - template: azure-pipelines-build-stage-job.yml # Template reference parameters: From 667e976de6046be108360af7e73e205664f18ed6 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 16:34:36 +0300 Subject: [PATCH 15/79] create check, fmt, lint multi-os pipelines Signed-off-by: ozkanonur --- .github/workflows/check_fmt_lint.yml | 72 +++++++++++++ azure-pipelines-android-job.yml | 64 ------------ azure-pipelines-build-stage-job.yml | 127 ----------------------- azure-pipelines-ios-job.yml | 49 --------- azure-pipelines-lint-stage-job.yml | 60 ----------- azure-pipelines-release-stage-job.yml | 140 -------------------------- azure-pipelines-wasm-stage-job.yml | 72 ------------- azure-pipelines.yml | 113 --------------------- 8 files changed, 72 insertions(+), 625 deletions(-) create mode 100644 .github/workflows/check_fmt_lint.yml delete mode 100644 azure-pipelines-android-job.yml delete mode 100644 azure-pipelines-build-stage-job.yml delete mode 100644 azure-pipelines-ios-job.yml delete mode 100644 azure-pipelines-lint-stage-job.yml delete mode 100644 azure-pipelines-release-stage-job.yml delete mode 100644 azure-pipelines-wasm-stage-job.yml delete mode 100644 azure-pipelines.yml diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/check_fmt_lint.yml new file mode 100644 index 0000000000..999312cb16 --- /dev/null +++ b/.github/workflows/check_fmt_lint.yml @@ -0,0 +1,72 @@ +name: Check, Fmt, Lint +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + check: + timeout-minutes: 7 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + + fmt: + needs: check + timeout-minutes: 7 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + components: rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + lint: + needs: fmt + timeout-minutes: 7 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + components: clippy + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings diff --git a/azure-pipelines-android-job.yml b/azure-pipelines-android-job.yml deleted file mode 100644 index 817cc9582a..0000000000 --- a/azure-pipelines-android-job.yml +++ /dev/null @@ -1,64 +0,0 @@ -parameters: - os: '' - -jobs: - - job: MM2_Android - timeoutInMinutes: 0 # 0 means infinite for self-hosted agent - pool: - name: Default - demands: agent.os -equals ${{ parameters.os }} - steps: - - checkout: self # self represents the repo where the initial Pipelines YAML file was found - clean: ${{ eq( variables['Build.Reason'], 'Schedule' ) }} # clean up only on Scheduled build - - bash: | - if [ $CLEANUP = "true" ] - then - git clean -ffdx - fi - displayName: Clean Up - failOnStderr: false - continueOnError: true - - bash: | - export TAG="$(git rev-parse --short=9 HEAD)" - echo "##vso[task.setvariable variable=COMMIT_HASH]${TAG}" - displayName: Setup ENV - - bash: | - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_android_armv7_CI - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - export PATH=$PATH:/home/azureagent/android-ndk/arch-ndk/x86_64/android-ndk/bin - CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --profile ci --crate-type=staticlib --package mm2_bin_lib - displayName: 'Build armv7' - env: - MANUAL_MM_VERSION: true - - bash: | - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_android_aarch64_CI - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - export PATH=$PATH:/home/azureagent/android-ndk/arch-ndk/x86_64/android-ndk/bin - CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --profile ci --crate-type=staticlib --package mm2_bin_lib - displayName: 'Build aarch64' - env: - MANUAL_MM_VERSION: true - - bash: | - rm -rf upload - mkdir upload - mv target/armv7-linux-androideabi/ci/libmm2lib.a upload/libmm2.a - zip upload/mm2-$(COMMIT_HASH)-android-armv7-CI upload/libmm2.a -j - rm upload/libmm2.a - mv target/aarch64-linux-android/ci/libmm2lib.a upload/libmm2.a - zip upload/mm2-$(COMMIT_HASH)-android-aarch64-CI upload/libmm2.a -j - rm upload/libmm2.a - displayName: 'Prepare upload' - - task: CopyFilesOverSSH@0 - inputs: - sshEndpoint: nightly_build_server - sourceFolder: 'upload' # Optional - contents: "**" - targetFolder: "uploads/$(Build.SourceBranchName)" # Optional - overwrite: true - displayName: 'Upload nightly' diff --git a/azure-pipelines-build-stage-job.yml b/azure-pipelines-build-stage-job.yml deleted file mode 100644 index 6af4e13cfb..0000000000 --- a/azure-pipelines-build-stage-job.yml +++ /dev/null @@ -1,127 +0,0 @@ -# Job template for MM2 Build - -parameters: - name: '' # defaults for any parameters that aren't specified - os: '' - bob_passphrase: '' - bob_userpass: '' - alice_passphrase: '' - alice_userpass: '' - telegram_api_key: '' - -jobs: - - job: ${{ parameters.name }} - timeoutInMinutes: 0 # 0 means infinite for self-hosted agent - pool: - name: Default - demands: agent.os -equals ${{ parameters.os }} - steps: - - checkout: self # self represents the repo where the initial Pipelines YAML file was found - clean: ${{ eq( variables['Build.Reason'], 'Schedule' ) }} # clean up only on Scheduled build - - bash: | - if [ $CLEANUP = "true" ] - then - git clean -ffdx - fi - displayName: Clean Up - failOnStderr: false - continueOnError: true - # https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-a-job-scoped-variable-from-a-script - - bash: | - export TAG="$(git rev-parse --short=9 HEAD)" - echo "##vso[task.setvariable variable=COMMIT_HASH]${TAG}" - displayName: Setup ENV - # On MacOS, cross-compile for x86_64-apple-darwin - - bash: | - rm -rf upload - mkdir upload - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_$(Agent.OS)_CI - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - if [ $AGENT_OS = "Darwin" ] - then - cargo build --bin mm2 --profile ci --target x86_64-apple-darwin - else - cargo build --bin mm2 --profile ci - fi - displayName: 'Build MM2' - condition: ne ( variables['Build.Reason'], 'PullRequest' ) - env: - MANUAL_MM_VERSION: true - - task: Docker@2 - displayName: Build & Push container of dev branch - condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) - inputs: - containerRegistry: dockerhub - repository: komodoofficial/atomicdexapi - command: buildAndPush - tags: | - dev-$(COMMIT_HASH) - dev-latest - Dockerfile: Dockerfile.dev-release - - bash: | - rm -rf atomicdex-deployments - git clone git@github.com:KomodoPlatform/atomicdex-deployments.git - if [ -d "atomicdex-deployments/atomicDEX-API" ]; then - cd atomicdex-deployments/atomicDEX-API - sed -i "1s/^.*$/$(COMMIT_HASH)/" .commit - git add .commit - git commit -m "[atomicDEX-API] $(COMMIT_HASH) is committed for git & container registry" - git push - fi - condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) - displayName: 'Update playground deployment' - # Explicit --test-threads=16 makes testing process slightly faster on agents that have <16 CPU cores. - # Always run tests on mm2.1 branch and PRs - # On MacOS, run for x86_64-apple-darwin - - bash: | - if [ $AGENT_OS = "Darwin" ] - then - cargo test --all --target x86_64-apple-darwin --profile ci -- --test-threads=16 - else - cargo test --all --profile ci -- --test-threads=32 - fi - displayName: 'Test MM2' - timeoutInMinutes: 22 - env: - BOB_PASSPHRASE: $(${{ parameters.bob_passphrase }}) - BOB_USERPASS: $(${{ parameters.bob_userpass }}) - ALICE_PASSPHRASE: $(${{ parameters.alice_passphrase }}) - ALICE_USERPASS: $(${{ parameters.alice_userpass }}) - TELEGRAM_API_KEY: $(${{ parameters.telegram_api_key }}) - RUST_LOG: debug - MANUAL_MM_VERSION: true - condition: or( eq( variables['Build.Reason'], 'PullRequest' ), eq( variables['Build.SourceBranchName'], 'mm2.1' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) - - bash: | - containers=$(docker ps -q | wc -l) - echo $containers - if [ $containers -gt 0 ]; then - docker rm -f $(docker ps -q) - fi - displayName: 'Clean up Docker containers' - # Run unconditionally even if previous steps failed - condition: true - - bash: | - zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-CI target/ci/mm2 -j - displayName: 'Prepare CI build upload Linux' - condition: and ( eq( variables['Agent.OS'], 'Linux' ), ne ( variables['Build.Reason'], 'PullRequest' ) ) - - bash: | - zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-CI target/x86_64-apple-darwin/ci/mm2 -j - displayName: 'Prepare CI build upload MacOS' - condition: and ( eq( variables['Agent.OS'], 'Darwin' ), ne ( variables['Build.Reason'], 'PullRequest' ) ) - - powershell: | - 7z a .\upload\mm2-$(COMMIT_HASH)-$(Agent.OS)-CI.zip .\target\ci\mm2.exe .\target\ci\*.dll "$Env:windir\system32\msvcr100.dll" "$Env:windir\system32\msvcp140.dll" "$Env:windir\system32\vcruntime140.dll" - displayName: 'Prepare CI build upload Windows' - condition: and ( eq( variables['Agent.OS'], 'Windows_NT' ), ne ( variables['Build.Reason'], 'PullRequest' ) ) - # https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/copy-files-over-ssh?view=vsts - - task: CopyFilesOverSSH@0 - inputs: - sshEndpoint: nightly_build_server - sourceFolder: 'upload' # Optional - contents: "**" - targetFolder: "uploads/$(Build.SourceBranchName)" # Optional - overwrite: true - displayName: 'Upload nightly' - condition: ne ( variables['Build.Reason'], 'PullRequest' ) diff --git a/azure-pipelines-ios-job.yml b/azure-pipelines-ios-job.yml deleted file mode 100644 index d513287742..0000000000 --- a/azure-pipelines-ios-job.yml +++ /dev/null @@ -1,49 +0,0 @@ -parameters: - os: '' - -jobs: - - job: MM2_iOS - timeoutInMinutes: 0 # 0 means infinite for self-hosted agent - pool: - name: Default - demands: agent.os -equals ${{ parameters.os }} - steps: - - checkout: self # self represents the repo where the initial Pipelines YAML file was found - clean: ${{ eq( variables['Build.Reason'], 'Schedule' ) }} # clean up only on Scheduled build - - bash: | - if [ $CLEANUP = "true" ] - then - git clean -ffdx - fi - displayName: Clean Up - failOnStderr: false - continueOnError: true - - bash: | - export TAG="$(git rev-parse --short=9 HEAD)" - echo "##vso[task.setvariable variable=COMMIT_HASH]${TAG}" - displayName: Setup ENV - - bash: | - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_ios_aarch64_CI - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - cargo rustc --target aarch64-apple-ios --lib --profile ci --package mm2_bin_lib --crate-type=staticlib - displayName: 'Build iOS lib' - env: - MANUAL_MM_VERSION: true - - bash: | - rm -rf upload - mkdir upload - mv target/aarch64-apple-ios/ci/libmm2lib.a upload/libmm2.a - zip upload/mm2-$(COMMIT_HASH)-ios-aarch64-CI upload/libmm2.a -j - rm upload/libmm2.a - displayName: 'Prepare upload' - - task: CopyFilesOverSSH@0 - inputs: - sshEndpoint: nightly_build_server - sourceFolder: 'upload' # Optional - contents: "**" - targetFolder: "uploads/$(Build.SourceBranchName)" # Optional - overwrite: true - displayName: 'Upload nightly' diff --git a/azure-pipelines-lint-stage-job.yml b/azure-pipelines-lint-stage-job.yml deleted file mode 100644 index 8f696eb8b2..0000000000 --- a/azure-pipelines-lint-stage-job.yml +++ /dev/null @@ -1,60 +0,0 @@ -# Job template for MM2 Build - -parameters: - name: '' # defaults for any parameters that aren't specified - os: '' - bob_passphrase: '' - bob_userpass: '' - alice_passphrase: '' - alice_userpass: '' - telegram_api_key: '' - -jobs: - - job: ${{ parameters.name }} - timeoutInMinutes: 0 # 0 means infinite for self-hosted agent - pool: - name: Default - demands: agent.os -equals ${{ parameters.os }} - steps: - - checkout: self # self represents the repo where the initial Pipelines YAML file was found - clean: ${{ eq( variables['Build.Reason'], 'Schedule' ) }} # clean up only on Scheduled build - - bash: | - if [ $CLEANUP = "true" ] - then - git clean -ffdx - fi - displayName: Clean Up - failOnStderr: false - continueOnError: true - - bash: | - cargo fmt -- --check - displayName: 'Check rustfmt warnings' - env: - MANUAL_MM_VERSION: true - - bash: | - if [ $AGENT_OS = "Darwin" ] - then - cargo clippy --profile ci --target x86_64-apple-darwin -- -D warnings - else - cargo clippy --profile ci -- -D warnings - fi - displayName: 'Check Clippy warnings' - env: - MANUAL_MM_VERSION: true - - bash: | - if [ $AGENT_OS = "Darwin" ] - then - cargo check --tests --profile ci --target x86_64-apple-darwin - else - cargo check --tests --profile ci - fi - displayName: 'Check Tests' - env: - MANUAL_MM_VERSION: true - - bash: | - cargo udeps - displayName: 'Check unused dependencies' - - bash: | - cargo deny check bans --hide-inclusion-graph - cargo deny check advisories --hide-inclusion-graph - displayName: 'Cargo deny checks' diff --git a/azure-pipelines-release-stage-job.yml b/azure-pipelines-release-stage-job.yml deleted file mode 100644 index 5ca79b0dd6..0000000000 --- a/azure-pipelines-release-stage-job.yml +++ /dev/null @@ -1,140 +0,0 @@ -# Job template for MM2 Release stage build - -parameters: - name: '' # defaults for any parameters that aren't specified - os: '' - bob_passphrase: '' - bob_userpass: '' - alice_passphrase: '' - alice_userpass: '' - -jobs: - - job: ${{ parameters.name }} - timeoutInMinutes: 0 # 0 means infinite for self-hosted agent - pool: - name: Default - demands: agent.os -equals ${{ parameters.os }} - steps: - # https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-a-job-scoped-variable-from-a-script - - bash: | - export SHORT_HASH="$(git rev-parse --short=9 HEAD)" - echo "##vso[task.setvariable variable=COMMIT_HASH]${SHORT_HASH}" - export TAG="$(git tag -l --points-at HEAD)" - echo "##vso[task.setvariable variable=COMMIT_TAG]${TAG}" - if [ -z $TAG ]; then - echo "Commit tag is empty" - export RELEASE_TAG=beta-2.1.$(Build.BuildId) - else - export DEBUG_UPLOADED="$(curl -s https://api.github.com/repos/KomodoPlatform/atomicDEX-API/releases/tags/$TAG | grep $(Agent.OS)-Debug)" - export RELEASE_UPLOADED="$(curl -s https://api.github.com/repos/KomodoPlatform/atomicDEX-API/releases/tags/$TAG | grep $(Agent.OS)-Release)" - export RELEASE_TAG=$TAG - fi - echo DEBUG_UPLOADED:$DEBUG_UPLOADED - echo RELEASE_UPLOADED:$RELEASE_UPLOADED - echo RELEASE_TAG:$RELEASE_TAG - echo "##vso[task.setvariable variable=DEBUG_UPLOADED]${DEBUG_UPLOADED}" - echo "##vso[task.setvariable variable=RELEASE_UPLOADED]${RELEASE_UPLOADED}" - echo "##vso[task.setvariable variable=RELEASE_TAG]${RELEASE_TAG}" - displayName: Setup ENV - - bash: | - rm -rf upload - mkdir upload - displayName: 'Recreate upload dir' - - bash: | - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_$(Agent.OS)_Debug - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - if [ $AGENT_OS = "Linux" ] - then - docker build -f Dockerfile.ubuntu.ci -t mm2_builder . - export UID=$(id -u) - export GID=$(id -g) - docker run \ - --user $UID:$GID \ - -v /home/azureagent/docker-cargo-cache/git:/root/.cargo/git \ - -v /home/azureagent/docker-cargo-cache/registry:/root/.cargo/registry \ - -v $PWD:$PWD \ - -w $PWD \ - -e HOME=/root \ - mm2_builder \ - /bin/bash -c "source /root/.cargo/env && cargo build -vv --target-dir target-xenial" - else - cargo build -vv - fi - displayName: 'Build MM2 Debug' - condition: eq( variables['DEBUG_UPLOADED'], '' ) - env: - MANUAL_MM_VERSION: true - - bash: | - zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Debug target-xenial/debug/mm2 target-xenial/debug/libmm2.a -j - displayName: 'Prepare debug build upload Linux' - condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['DEBUG_UPLOADED'], '' ) ) - - bash: | - cd target/debug - zip ../../upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Debug mm2.dSYM mm2 libmm2.a -r - displayName: 'Prepare debug build upload MacOS' - condition: and( eq( variables['Agent.OS'], 'Darwin' ), eq( variables['DEBUG_UPLOADED'], '' ) ) - - powershell: | - 7z a .\upload\mm2-$(COMMIT_HASH)-$(Agent.OS)-Debug.zip .\target\debug\mm2.exe .\target\debug\mm2.lib .\target\debug\*.dll "$Env:windir\system32\msvcr100.dll" "$Env:windir\system32\msvcp140.dll" "$Env:windir\system32\vcruntime140.dll" - displayName: 'Prepare debug build upload Windows' - condition: and( eq( variables['Agent.OS'], 'Windows_NT' ), eq( variables['DEBUG_UPLOADED'], '' ) ) - - bash: | - rm -f MM_VERSION - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_$(Agent.OS)_Release - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - touch mm2src/common/build.rs - if [ $AGENT_OS = "Linux" ] - then - docker build -f Dockerfile.ubuntu.ci -t mm2_builder . - export UID=$(id -u) - export GID=$(id -g) - docker run \ - --user $UID:$GID \ - -v /home/azureagent/docker-cargo-cache/git:/root/.cargo/git \ - -v /home/azureagent/docker-cargo-cache/registry:/root/.cargo/registry \ - -v $PWD:$PWD \ - -w $PWD \ - -e HOME=/root \ - mm2_builder \ - /bin/bash -c "source /root/.cargo/env && cargo build --release -vv --target-dir target-xenial" - else - cargo build --release -vv - fi - displayName: 'Build MM2 Release' - condition: eq( variables['RELEASE_UPLOADED'], '' ) - env: - MANUAL_MM_VERSION: true - - bash: | - objcopy --only-keep-debug target-xenial/release/mm2 target-xenial/release/mm2.debug - objcopy --only-keep-debug target-xenial/release/libmm2.a target-xenial/release/libmm2.debug.a - strip -g target-xenial/release/mm2 - strip -g target-xenial/release/libmm2.a - zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Release target-xenial/release/mm2 target-xenial/release/libmm2.a -j - zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Release-debuginfo.zip target-xenial/release/mm2.debug target-xenial/release/libmm2.debug.a -j - displayName: 'Prepare release build upload Linux' - condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['RELEASE_UPLOADED'], '' ) ) - - bash: | - cd target/release - zip ../../upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Release.zip mm2 libmm2.a - zip ../../upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Release.dSYM.zip mm2.dSYM -r - displayName: 'Prepare release build upload MacOS' - condition: and( eq( variables['Agent.OS'], 'Darwin' ), eq( variables['RELEASE_UPLOADED'], '' ) ) - - task: Docker@2 - displayName: Build and Push Docker Release image - condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['RELEASE_UPLOADED'], '' ) ) - inputs: - containerRegistry: dockerhub - repository: komodoofficial/atomicdexapi - command: buildAndPush - tags: | - $(RELEASE_TAG) - Dockerfile: Dockerfile.release - - powershell: | - 7z a .\upload\mm2-$(COMMIT_HASH)-$(Agent.OS)-Release.zip .\target\release\mm2.exe .\target\release\mm2.lib .\target\release\*.dll "$Env:windir\system32\msvcr100.dll" "$Env:windir\system32\msvcp140.dll" "$Env:windir\system32\vcruntime140.dll" - displayName: 'Prepare release build upload Windows' - condition: and( eq( variables['Agent.OS'], 'Windows_NT' ), eq( variables['RELEASE_UPLOADED'], '' ) ) diff --git a/azure-pipelines-wasm-stage-job.yml b/azure-pipelines-wasm-stage-job.yml deleted file mode 100644 index 350b194eae..0000000000 --- a/azure-pipelines-wasm-stage-job.yml +++ /dev/null @@ -1,72 +0,0 @@ -# Job template for MM2 Build - -parameters: - name: '' # defaults for any parameters that aren't specified - os: '' - bob_passphrase: '' - bob_userpass: '' - alice_passphrase: '' - alice_userpass: '' - telegram_api_key: '' - -jobs: - - job: ${{ parameters.name }} - timeoutInMinutes: 0 # 0 means infinite for self-hosted agent - pool: - name: Default - demands: agent.os -equals ${{ parameters.os }} - steps: - - checkout: self # self represents the repo where the initial Pipelines YAML file was found - clean: ${{ eq( variables['Build.Reason'], 'Schedule' ) }} # clean up only on Scheduled build - - bash: | - if [ $CLEANUP = "true" ] - then - git clean -ffdx - fi - displayName: Clean Up - failOnStderr: false - continueOnError: true - # https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-a-job-scoped-variable-from-a-script - - bash: | - export TAG="$(git rev-parse --short=9 HEAD)" - echo "##vso[task.setvariable variable=COMMIT_HASH]${TAG}" - displayName: Setup ENV - # Build WASM. - - bash: | - rm -rf upload - mkdir upload - VERSION=$(Build.BuildId)_$(Build.SourceBranchName)_$(COMMIT_HASH)_$(Agent.OS)_Release - if ! grep -q $VERSION MM_VERSION; then - echo $VERSION > MM_VERSION - fi - cat MM_VERSION - CC=clang-8 wasm-pack build mm2src/mm2_bin_lib --release --target web --out-dir ../../target/target-wasm-release - displayName: 'Build MM2 WASM Release' - condition: ne ( variables['Build.Reason'], 'PullRequest' ) - env: - MANUAL_MM_VERSION: true - - bash: | - CC=clang-8 cargo test --package mm2_main --target wasm32-unknown-unknown --release - displayName: 'Test MM2 WASM' - env: - WASM_BINDGEN_TEST_TIMEOUT: 180 - GECKODRIVER: '/home/azureagent/wasm/geckodriver' - BOB_PASSPHRASE: $(${{ parameters.bob_passphrase }}) - ALICE_PASSPHRASE: $(${{ parameters.alice_passphrase }}) - MANUAL_MM_VERSION: true - condition: or( eq( variables['Build.Reason'], 'PullRequest' ), eq( variables['Build.SourceBranchName'], 'mm2.1' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) - - bash: | - cd target/target-wasm-release/ - zip ../../upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Wasm-Release mm2lib_bg.wasm mm2lib.js snippets -r - displayName: 'Prepare release WASM build upload Linux' - condition: ne ( variables['Build.Reason'], 'PullRequest' ) - # https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/copy-files-over-ssh?view=vsts - - task: CopyFilesOverSSH@0 - inputs: - sshEndpoint: nightly_build_server - sourceFolder: 'upload' # Optional - contents: "**" - targetFolder: "uploads/$(Build.SourceBranchName)" # Optional - overwrite: true - displayName: 'Upload nightly' - condition: ne ( variables['Build.Reason'], 'PullRequest' ) \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 44e1a63d57..0000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,113 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -variables: - - group: passphrases - -trigger: - branches: - include: - - refs/heads/* - paths: - exclude: - - docs/* - - README.md - - mm2src/README.md - - etomic_build/* - - iguana/Readme.md - - .gitignore - -pr: # https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema#pr-trigger - drafts: false - -# https://docs.microsoft.com/ru-ru/azure/devops/pipelines/process/scheduled-triggers?view=azure-devops&tabs=yaml#scheduled-triggers -# Triggers clean checkout to compile from scratch, remove old builds artifacts, and free disk space on CI agents. -# https://github.com/KomodoPlatform/atomicDEX-API/blob/957fd856fb74d462058a5ad47ec46d79e3bf1d83/azure-pipelines-build-stage-job.yml#L20 -schedules: - - cron: "0 0 * * *" - displayName: Scheduled clean midnight UTC build - branches: - include: - - dev - -stages: - - stage: Lint - displayName: Formatting, Clippy, and other checks - jobs: - - template: azure-pipelines-lint-stage-job.yml # Template reference - parameters: - name: 'MM2_Lint_Linux' - os: 'Linux' - - - template: azure-pipelines-lint-stage-job.yml # Template reference - parameters: - name: 'MM2_Lint_MacOS' - os: 'Darwin' - - - template: azure-pipelines-lint-stage-job.yml # Template reference - parameters: - name: 'MM2_Lint_Win' - os: 'Windows_NT' - - - stage: WASM - displayName: WASM build and test - condition: succeeded('Lint') - jobs: - - template: azure-pipelines-wasm-stage-job.yml # Template reference - parameters: - name: 'MM2_WASM_Linux' - os: 'Linux' - bob_passphrase: 'BOB_PASSPHRASE_LINUX' - bob_userpass: 'BOB_USERPASS_LINUX' - alice_passphrase: 'ALICE_PASSPHRASE_LINUX' - alice_userpass: 'ALICE_USERPASS_LINUX' - telegram_api_key: 'TELEGRAM_API_KEY' - - - stage: Mobile - displayName: Mobile libs build - condition: and(succeeded('WASM'), or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranchName'], 'mm2.1'), eq(variables['Build.SourceBranchName'], 'dev'))) - jobs: - - template: azure-pipelines-android-job.yml - parameters: - os: 'Linux' - - template: azure-pipelines-ios-job.yml - parameters: - os: 'Darwin' - - - stage: Desktop - displayName: Desktop Build and test - condition: or(eq(dependencies.Mobile.result, 'Succeeded'), and(eq(dependencies.Mobile.result, 'Skipped'), succeeded('WASM'))) - dependsOn: Mobile - jobs: - - template: azure-pipelines-build-stage-job.yml # Template reference - parameters: - name: 'MM2_Build_Linux' - os: 'Linux' - bob_passphrase: 'BOB_PASSPHRASE_LINUX' - bob_userpass: 'BOB_USERPASS_LINUX' - alice_passphrase: 'ALICE_PASSPHRASE_LINUX' - alice_userpass: 'ALICE_USERPASS_LINUX' - telegram_api_key: 'TELEGRAM_API_KEY' - - - template: azure-pipelines-build-stage-job.yml # Template reference - parameters: - name: 'MM2_Build_MacOS' - os: 'Darwin' - bob_passphrase: 'BOB_PASSPHRASE_MAC' - bob_userpass: 'BOB_USERPASS_MAC' - alice_passphrase: 'ALICE_PASSPHRASE_MAC' - alice_userpass: 'ALICE_USERPASS_MAC' - telegram_api_key: 'TELEGRAM_API_KEY' - - - template: azure-pipelines-build-stage-job.yml # Template reference - parameters: - name: 'MM2_Build_Windows' - os: 'Windows_NT' - bob_passphrase: 'BOB_PASSPHRASE_WIN' - bob_userpass: 'BOB_USERPASS_WIN' - alice_passphrase: 'ALICE_PASSPHRASE_WIN' - alice_userpass: 'ALICE_USERPASS_WIN' - telegram_api_key: 'TELEGRAM_API_KEY' - From 5075a902b6b3a875bbead8fce9b95d8b162ba16e Mon Sep 17 00:00:00 2001 From: shamardy Date: Mon, 6 Mar 2023 22:34:07 +0200 Subject: [PATCH 16/79] change release branch from mm2.1 to main --- azure-pipelines-build-stage-job.yml | 4 ++-- azure-pipelines-wasm-stage-job.yml | 2 +- azure-pipelines.yml | 2 +- docs/GIT_FLOW_AND_WORKING_PROCESS.md | 12 ++++++------ docs/PR_REVIEW_CHECKLIST.md | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/azure-pipelines-build-stage-job.yml b/azure-pipelines-build-stage-job.yml index 8cdcc82e9e..ec9e5ee837 100644 --- a/azure-pipelines-build-stage-job.yml +++ b/azure-pipelines-build-stage-job.yml @@ -75,7 +75,7 @@ jobs: condition: and( eq( variables['Agent.OS'], 'Linux' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) displayName: 'Update playground deployment' # Explicit --test-threads=16 makes testing process slightly faster on agents that have <16 CPU cores. - # Always run tests on mm2.1 branch and PRs + # Always run tests on main branch and PRs # On MacOS, run for x86_64-apple-darwin - bash: | if [ $AGENT_OS = "Darwin" ] @@ -94,7 +94,7 @@ jobs: TELEGRAM_API_KEY: $(${{ parameters.telegram_api_key }}) RUST_LOG: debug MANUAL_MM_VERSION: true - condition: or( eq( variables['Build.Reason'], 'PullRequest' ), eq( variables['Build.SourceBranchName'], 'mm2.1' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) + condition: or( eq( variables['Build.Reason'], 'PullRequest' ), eq( variables['Build.SourceBranchName'], 'main' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) - bash: | containers=$(docker ps -q | wc -l) echo $containers diff --git a/azure-pipelines-wasm-stage-job.yml b/azure-pipelines-wasm-stage-job.yml index 350b194eae..5ea9024769 100644 --- a/azure-pipelines-wasm-stage-job.yml +++ b/azure-pipelines-wasm-stage-job.yml @@ -54,7 +54,7 @@ jobs: BOB_PASSPHRASE: $(${{ parameters.bob_passphrase }}) ALICE_PASSPHRASE: $(${{ parameters.alice_passphrase }}) MANUAL_MM_VERSION: true - condition: or( eq( variables['Build.Reason'], 'PullRequest' ), eq( variables['Build.SourceBranchName'], 'mm2.1' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) + condition: or( eq( variables['Build.Reason'], 'PullRequest' ), eq( variables['Build.SourceBranchName'], 'main' ), eq( variables['Build.SourceBranchName'], 'dev' ) ) - bash: | cd target/target-wasm-release/ zip ../../upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Wasm-Release mm2lib_bg.wasm mm2lib.js snippets -r diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ce9e62712b..ae43127780 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -67,7 +67,7 @@ stages: - stage: Mobile displayName: Mobile libs build - condition: and(succeeded('WASM'), or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranchName'], 'mm2.1'), eq(variables['Build.SourceBranchName'], 'dev'))) + condition: and(succeeded('WASM'), or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranchName'], 'main'), eq(variables['Build.SourceBranchName'], 'dev'))) jobs: - template: azure-pipelines-android-job.yml parameters: diff --git a/docs/GIT_FLOW_AND_WORKING_PROCESS.md b/docs/GIT_FLOW_AND_WORKING_PROCESS.md index 7f010a669d..e9dc6a4821 100644 --- a/docs/GIT_FLOW_AND_WORKING_PROCESS.md +++ b/docs/GIT_FLOW_AND_WORKING_PROCESS.md @@ -1,11 +1,11 @@ # Git flow -1. There are two permanent branches: mm2.1 (master/release) and dev. -2. The goal is to have both mm2.1 and dev ready to be released at any point in time - all auto-tests pass, no incompatibilities introduced, etc. -3. The dev is merged to mm2.1 when a new release is planned. +1. There are two permanent branches: main (master/release) and dev. +2. The goal is to have both main and dev ready to be released at any point in time - all auto-tests pass, no incompatibilities introduced, etc. +3. The dev is merged to main when a new release is planned. 4. The feature branch lifetime should be <= 1-2 weeks. 5. Big task should be decomposed to the several feature branches. Each of which is merged periodically with a code review process. The new feature branch should be started from dev afterward. -6. In certain cases, the dev might not be in a "releasable" state. In this case, if the feature branch has no dependencies updates, it might be merged directly to mm2.1 if required (hotfix, very useful feature, blocker). Dev is synced with mm2.1 afterward. +6. In certain cases, the dev might not be in a "releasable" state. In this case, if the feature branch has no dependencies updates, it might be merged directly to main if required (hotfix, very useful feature, blocker). Dev is synced with main afterward. 7. For convenience, we can consider making several minor features/fixes in the single feature branch. Pros: @@ -20,10 +20,10 @@ Cons: 1. It's desired to have a separate issue for any bug report or feature request. 2. Once the issue is created, add it to the MM2.0 Github project. Select an appropriate column. -3. Decide whether you should base your feature branch on mm2.1 or dev. For hotfixes or minor useful features that don't include any dependencies updates choose mm2.1. In other cases choose dev. +3. Decide whether you should base your feature branch on main or dev. For hotfixes or minor useful features that don't include any dependencies updates choose main. In other cases choose dev. 5. PR titles must have a prefix that displays the current status of PR. Such as `[wip] X feat integration`, `[r2r] X feat integration`, where `[wip]` prefix stands for "Work in Progress", and `[r2r]` for Ready to Review. 4. PRs to dev can be merged right after approval. Request the tests in the dev branch from Tony by assigning the issue to him and moving it to the `Testing` column. Provide a detailed explanation of what changed and what should be tested. Indicate the critical points. -5. PRs to mm2.1 must be tested by QA *before* merging. +5. PRs to main must be tested by QA *before* merging. 6. If documentation update is required, prepare examples and notify smk762. Assign issue to him. Move the issue to the documentation column. Smk will then prepare PR in [developer-docs](https://github.com/KomodoPlatform/developer-docs) repo. 7. Review the docs PR. Smk will request it from the feature implementor. diff --git a/docs/PR_REVIEW_CHECKLIST.md b/docs/PR_REVIEW_CHECKLIST.md index 689f4f6f1a..fcf5d02dd4 100644 --- a/docs/PR_REVIEW_CHECKLIST.md +++ b/docs/PR_REVIEW_CHECKLIST.md @@ -12,4 +12,4 @@ the test is unstable, please clarify it with the team. - [ ] Indicate code that is worth moving to a separate module or crate. - [ ] Check if the code can be improved/simplified: it might be overly abstracted or require the additional abstraction layer instead for a better design. - [ ] Follow SOLID if applicable. -- [ ] For PRs targeting release (mm2.1) branch check that QA tested and approved it. \ No newline at end of file +- [ ] For PRs targeting release (main) branch check that QA tested and approved it. \ No newline at end of file From 9d827c7e42dae57506535998a14ef19f9d3222cf Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 7 Mar 2023 15:43:52 +0200 Subject: [PATCH 17/79] replace mm2.1 to main in eth_tests --- mm2src/coins/eth/eth_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index f4890d6a77..10912f09cd 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -495,7 +495,7 @@ fn test_wait_for_payment_spend_timeout() { #[test] #[ignore] -/// Ignored temporarily until dev is merged to mm2.1 +/// Ignored temporarily until dev is merged to main fn test_search_for_swap_tx_spend_was_spent() { let key_pair = KeyPair::from_secret_slice( &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), @@ -605,7 +605,7 @@ fn test_gas_station() { #[test] #[ignore] -/// Ignored temporarily until dev is merged to mm2.1 +/// Ignored temporarily until dev is merged to main fn test_search_for_swap_tx_spend_was_refunded() { let key_pair = KeyPair::from_secret_slice( &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), From da636a2f8e729c65404d552e6e5bbbd79264d746 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 19:15:11 +0300 Subject: [PATCH 18/79] improve checks for x86 Signed-off-by: ozkanonur --- .github/workflows/check_fmt_lint.yml | 69 +++++++++------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/check_fmt_lint.yml index 37aca030e0..843022dbae 100644 --- a/.github/workflows/check_fmt_lint.yml +++ b/.github/workflows/check_fmt_lint.yml @@ -1,4 +1,4 @@ -name: Check, Fmt, Lint +name: fmt, check, lint on: push: branches: @@ -10,63 +10,38 @@ on: - dev jobs: - fmt: - timeout-minutes: 15 + x86-checks: + timeout-minutes: 20 runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] rust: [nightly-2022-10-29] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true components: rustfmt - - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy + rustup default ${{ matrix.rust }} + + - name: Turn off debug assertations + run: | + mkdir -p .cargo + echo """ + [profile.dev] + debug = false" >> .cargo/config.toml - check: - needs: fmt - timeout-minutes: 15 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - rust: [nightly-2022-10-29] - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - uses: actions-rs/cargo@v1 - with: - command: check + - name: fmt check + run: cargo fmt -- --check - lint: - needs: check - timeout-minutes: 15 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - rust: [nightly-2022-10-29] - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - components: clippy - - name: Run cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: -- -D warnings + - name: build check + run: cargo check + + - name: code lint + run: cargo clippy -- --D warnings From 1ac04ed14118bf2fe1d4bff74f6dbff1ef0451e6 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 19:19:59 +0300 Subject: [PATCH 19/79] update pipeline Signed-off-by: ozkanonur --- .github/workflows/check_fmt_lint.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/check_fmt_lint.yml index 843022dbae..40f8ab957c 100644 --- a/.github/workflows/check_fmt_lint.yml +++ b/.github/workflows/check_fmt_lint.yml @@ -19,12 +19,6 @@ jobs: rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - components: rustfmt - name: Install toolchain run: | rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy @@ -32,7 +26,6 @@ jobs: - name: Turn off debug assertations run: | - mkdir -p .cargo echo """ [profile.dev] debug = false" >> .cargo/config.toml From 66c458aa370d02d1a1d37b41c15022cb8dc43a49 Mon Sep 17 00:00:00 2001 From: Kadan Stadelmann Date: Wed, 8 Mar 2023 17:24:52 +0100 Subject: [PATCH 20/79] docs: introduce CHANGELOG.md (#1680) * propose change.log add a chronological "living" changelog file as a index for the dev.logs * introduce CHANGELOG.md * [docs] update changelog * [docs] add changelog date/tag * update changelog * update date * re-enable mac ci, add one more change log --------- Co-authored-by: shamardy Reviewed-by: ozkanonur --- CHANGELOG.md | 108 ++++++++++++++++++ azure-pipelines.yml | 32 +++--- dev-logs/2023-feb/enhancements/.gitkeep | 0 dev-logs/2023-feb/features/.gitkeep | 0 dev-logs/2023-feb/fixes/.gitkeep | 0 .../fix_swap_time_difference_err_message | 4 - dev-logs/2023-feb/upgrades/.gitkeep | 0 .../upgrades/axum_core_upgrade_to_0_2_9 | 6 - .../upgrades/bumpalo_upgrade_to_3_12_0 | 6 - .../upgrades/libp2p_upgrade_to_0_45_1 | 6 - .../2023-feb/upgrades/tokio_upgrade_to_1_25_0 | 6 - 11 files changed, 124 insertions(+), 44 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 dev-logs/2023-feb/enhancements/.gitkeep delete mode 100644 dev-logs/2023-feb/features/.gitkeep delete mode 100644 dev-logs/2023-feb/fixes/.gitkeep delete mode 100644 dev-logs/2023-feb/fixes/fix_swap_time_difference_err_message delete mode 100644 dev-logs/2023-feb/upgrades/.gitkeep delete mode 100644 dev-logs/2023-feb/upgrades/axum_core_upgrade_to_0_2_9 delete mode 100644 dev-logs/2023-feb/upgrades/bumpalo_upgrade_to_3_12_0 delete mode 100644 dev-logs/2023-feb/upgrades/libp2p_upgrade_to_0_45_1 delete mode 100644 dev-logs/2023-feb/upgrades/tokio_upgrade_to_1_25_0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..1122bfa5e3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,108 @@ +## v1.0.0-beta - 2023-03-08 + +**Features:** +- ARRR integration [#927](https://github.com/KomodoPlatform/atomicDEX-API/issues/927): + - Zcoin native mode support was added [#1438](https://github.com/KomodoPlatform/atomicDEX-API/pull/1438) + - Multi lightwalletd servers support was added [#1472](https://github.com/KomodoPlatform/atomicDEX-API/pull/1472) + - Allow passing Zcash params file path to activation request [#1538](https://github.com/KomodoPlatform/atomicDEX-API/pull/1538) + - Checksum verification of Zcash params files was added [#1549](https://github.com/KomodoPlatform/atomicDEX-API/pull/1549) +- Tendermint integration [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - Tendermint HTLC implementation [#1454](https://github.com/KomodoPlatform/atomicDEX-API/pull/1454) + - Tendermint swap support (POC level) [#1468](https://github.com/KomodoPlatform/atomicDEX-API/pull/1454) + - Complete tendermint support for swaps and tx history implementation [#1526](https://github.com/KomodoPlatform/atomicDEX-API/pull/1526) + - Improve rpc client rotation of tendermint [#1675](https://github.com/KomodoPlatform/atomicDEX-API/pull/1675) +- HD Wallet [#740](https://github.com/KomodoPlatform/atomicDEX-API/issues/740) + - Implement Global HD account activation mode [#1512](https://github.com/KomodoPlatform/atomicDEX-API/pull/1512) + - `mm2_rmd160` property was removed from the HD account table. Now, either Iguana or an HD account share the same HD account records [#1672](https://github.com/KomodoPlatform/atomicDEX-API/pull/1672) +- Hardware Wallet [#964](https://github.com/KomodoPlatform/atomicDEX-API/issues/964) + - Implement TX history V2 for UTXO coins activated with a Hardware wallet [#1467](https://github.com/KomodoPlatform/atomicDEX-API/pull/1467) + - Fix KMD withdraw with Trezor [#1628](https://github.com/KomodoPlatform/atomicDEX-API/pull/1628) + - `task::get_new_address::*` RPCs were added to replace the legacy `get_new_address` RPC [#1672](https://github.com/KomodoPlatform/atomicDEX-API/pull/1672) + - `trezor_connection_status` RPC was added to allow the GUI to poll the Trezor connection status [#1672](https://github.com/KomodoPlatform/atomicDEX-API/pull/1672) +- Simple Payment Verification [#1612](https://github.com/KomodoPlatform/atomicDEX-API/issues/1612) + - Implement unit test for `Block header UTXO Loop` [#1519](https://github.com/KomodoPlatform/atomicDEX-API/pull/1519) + - `SPV` with minimal storage requirements and fast block headers sync time was implemented [#1585](https://github.com/KomodoPlatform/atomicDEX-API/pull/1585) + - Block headers storage was implemented for `IndexedDB` [#1644](https://github.com/KomodoPlatform/atomicDEX-API/pull/1644) + - `SPV` was re-enabled in `WASM` [#1644](https://github.com/KomodoPlatform/atomicDEX-API/pull/1644) +- New RPCs + - gui-auth and `enable_eth_with_tokens` `enable_erc20` RPCs were added [#1335](https://github.com/KomodoPlatform/atomicDEX-API/pull/1335) + - `get_current_mtp` RPC was added [#1340](https://github.com/KomodoPlatform/atomicDEX-API/pull/1340) + - `max_maker_vol` RPC was added [#1618](https://github.com/KomodoPlatform/atomicDEX-API/pull/1618) +- Lightning integration `WIP` [#1045](https://github.com/KomodoPlatform/atomicDEX-API/issues/1045) + - [rust-lightning](https://github.com/lightningdevkit/rust-lightning) was updated to [v0.0.110](https://github.com/lightningdevkit/rust-lightning/releases/tag/v0.0.110) in [#1452](https://github.com/KomodoPlatform/atomicDEX-API/pull/1452) + - Inbound channels details was added to SQL channels history in [#1339](https://github.com/KomodoPlatform/atomicDEX-API/pull/1339) + - Blocking was fixed for sync rust-lightning functions that calls other I/O functions or that has mutexes that can be held for some time in [#1452](https://github.com/KomodoPlatform/atomicDEX-API/pull/1452) + - Default fees are retrieved from rpc instead of config when starting lightning [#1452](https://github.com/KomodoPlatform/atomicDEX-API/pull/1452) + - 0 confirmations channels feature was added in [#1452](https://github.com/KomodoPlatform/atomicDEX-API/pull/1452) + - An `update_channel` RPC was added that updates a channel that is open without closing it in [#1452](https://github.com/KomodoPlatform/atomicDEX-API/pull/1452) + - Lightning RPCs now use the `lightning::` namespace in [#1497](https://github.com/KomodoPlatform/atomicDEX-API/pull/1497) + - `TakerFee` and `MakerPayment` swap messages were modified to include payment instructions for the other side, in the case of lightning this payment instructions is a lightning invoice [#1497](https://github.com/KomodoPlatform/atomicDEX-API/pull/1497) + - `MakerPaymentInstructionsReceived`/`TakerPaymentInstructionsReceived` events are added to `MakerSwapEvent`/`TakerSwapEvent` in [#1497](https://github.com/KomodoPlatform/atomicDEX-API/pull/1497), for more info check this [comment](https://github.com/KomodoPlatform/atomicDEX-API/issues/1045#issuecomment-1410449770) + - Lightning swaps were implemented in [#1497](https://github.com/KomodoPlatform/atomicDEX-API/pull/1497), [#1557 + ](https://github.com/KomodoPlatform/atomicDEX-API/pull/1557) + - Lightning swap refunds were implemented in [#1592](https://github.com/KomodoPlatform/atomicDEX-API/pull/1592) + - `MakerPaymentRefundStarted`, `TakerPaymentRefundStarted`, `MakerPaymentRefundFinished`, `TakerPaymentRefundFinished` events were added to swap error events in [#1592](https://github.com/KomodoPlatform/atomicDEX-API/pull/1592), for more info check this [comment](https://github.com/KomodoPlatform/atomicDEX-API/issues/1045#issuecomment-1410449770) + - Enabling lightning now uses the task manager [#1513](https://github.com/KomodoPlatform/atomicDEX-API/pull/1513) + - Disabling lightning coin or calling `stop` RPC now drops the `BackgroundProcessor` which persists the latest network graph and scorer to disk [#1513](https://github.com/KomodoPlatform/atomicDEX-API/pull/1513), [#1490](https://github.com/KomodoPlatform/atomicDEX-API/pull/1490) + - `avg_blocktime` from platform/utxo coin is used for l2/lightning estimating of the number of blocks swap payments are locked for [#1606](https://github.com/KomodoPlatform/atomicDEX-API/pull/1606) +- MetaMask `WIP` [#1167](https://github.com/KomodoPlatform/atomicDEX-API/issues/1167) + - Login with a MetaMask wallet [#1551](https://github.com/KomodoPlatform/atomicDEX-API/pull/1551) + - Check if corresponding ETH chain is known by MetaMask wallet on coin activation using `wallet_switchEthereumChain` [#1674](https://github.com/KomodoPlatform/atomicDEX-API/pull/1674) + - Refactor ETH/ERC20 withdraw taking into account that the only way to sign a transaction is to send it using `eth_sendTransaction` [#1674](https://github.com/KomodoPlatform/atomicDEX-API/pull/1674) + - Extract address's public key using `eth_singTypedDataV4` [#1674](https://github.com/KomodoPlatform/atomicDEX-API/pull/1674) + - Perform swaps with coins activated with MetaMask [#1674](https://github.com/KomodoPlatform/atomicDEX-API/pull/1674) + +**Enhancements/Fixes:** +- Update `rust-web3` crate [#1674](https://github.com/KomodoPlatform/atomicDEX-API/pull/1674) +- Custom enum from stringify derive macro to derive From implementations for enums [#1502](https://github.com/KomodoPlatform/atomicDEX-API/pull/1502) +- Validate that `input_tx` is calling `'receiverSpend'` in `eth::extract_secret` [#1596](https://github.com/KomodoPlatform/atomicDEX-API/pull/1596) +- Validate all Swap parameters at the Negotiation stage [#1475](https://github.com/KomodoPlatform/atomicDEX-API/pull/1475) +- created safe number type castings [#1517](https://github.com/KomodoPlatform/atomicDEX-API/pull/1517) +- Improve `stop` functionality [#1490](https://github.com/KomodoPlatform/atomicDEX-API/pull/1490) +- A possible seednode p2p thread panicking attack due to `GetKnownPeers` msg was fixed in [#1445](https://github.com/KomodoPlatform/atomicDEX-API/pull/1445) +- NAV `cold_staking` script type was added to fix a problem in NAV tx history in [#1466](https://github.com/KomodoPlatform/atomicDEX-API/pull/1466) +- SPV was temporarily disabled in WASM in [#1479](https://github.com/KomodoPlatform/atomicDEX-API/pull/1479) +- `BTC-segwit` swap locktimes was fixed in [#1548](https://github.com/KomodoPlatform/atomicDEX-API/pull/1548) by using orderbook ticker instead of ticker in swap locktimes calculations. +- BTC block headers deserialization was fixed for version 4 and `KAWPOW_VERSION` in [#1452](https://github.com/KomodoPlatform/atomicDEX-API/pull/1452) +- Error messages for failing swaps due to a time difference between maker and taker are now more informative after [#1677](https://github.com/KomodoPlatform/atomicDEX-API/pull/1677) +- Fix `LBC` block header deserialization bug [#1343](https://github.com/KomodoPlatform/atomicDEX-API/pull/1343) +- Fix `NMC` block header deserialization bug [#1409](https://github.com/KomodoPlatform/atomicDEX-API/pull/1409) +- Refactor mm2 error handling for some structures [#1444](https://github.com/KomodoPlatform/atomicDEX-API/pull/1444) +- Tx wait for confirmation timeout fix [#1446](https://github.com/KomodoPlatform/atomicDEX-API/pull/1446) +- Retry tx wait confirmation if not on chain [#1474](https://github.com/KomodoPlatform/atomicDEX-API/pull/1474) +- Fix electrum "response is too large (over 2M bytes)" error for block header download [#1506](https://github.com/KomodoPlatform/atomicDEX-API/pull/1506) +- Deactivate tokens with platform coin [#1525](https://github.com/KomodoPlatform/atomicDEX-API/pull/1525) +- Enhanced logging in` spv` and `rpc_client` mods [#1594](https://github.com/KomodoPlatform/atomicDEX-API/pull/1594) +- Update metrics related dep && refactoring [#1312](https://github.com/KomodoPlatform/atomicDEX-API/pull/1312) +- Fix rick and morty genesis block deserialization [#1647](https://github.com/KomodoPlatform/atomicDEX-API/pull/1647) +- In `librustzcash` bumped `bech32` to `0.9.1`(which we already have in mm2, so we will not have 2 versions of `bech32`) +- Use dev branch as a target branch for Dependabot [#1424](https://github.com/KomodoPlatform/atomicDEX-API/pull/1424) +- Fixed Zhtlc orders is_mine bug (orders had "is_mine":false) [#1489](https://github.com/KomodoPlatform/atomicDEX-API/pull/1489) +- Grouped SwapOps method arguments into new groups(structures) [#1529](https://github.com/KomodoPlatform/atomicDEX-API/pull/1529) +- Handling multiple rpcs optimization [#1480](https://github.com/KomodoPlatform/atomicDEX-API/issues/1480) + - Tendermint multiple rpcs optimization [#1568](https://github.com/KomodoPlatform/atomicDEX-API/pull/1568) + - Multiple rpcs optimization for `z_rpc` and `http_transport` [#1653](https://github.com/KomodoPlatform/atomicDEX-API/pull/1653) + - Refactor p2p message processing flow (related with one of the security problem) [#1436](https://github.com/KomodoPlatform/atomicDEX-API/pull/1436) +- Solana tests are disabled [#1660](https://github.com/KomodoPlatform/atomicDEX-API/pull/1660) +- Some of vulnerable dependencies(tokio, libp2p) are fixed [#1666](https://github.com/KomodoPlatform/atomicDEX-API/pull/1666) +- Add `mm2_stop` WASM FFI [#1628](https://github.com/KomodoPlatform/atomicDEX-API/pull/1628) +- Use `futures_timer` crate and fix some unstable tests [#1511](https://github.com/KomodoPlatform/atomicDEX-API/pull/1511) +- Fix `Timer::sleep_ms` in WASM [#1514](https://github.com/KomodoPlatform/atomicDEX-API/pull/1514) +- Fix a race condition in `AbortableQueue` [#1528](https://github.com/KomodoPlatform/atomicDEX-API/pull/1528) +- Spawn `process_json_request` so the RPC requests can be processed asynchronously [#1620](https://github.com/KomodoPlatform/atomicDEX-API/pull/1620) +- Fix `task::-::cancel` if the RPC task is an awaiting status [#1582](https://github.com/KomodoPlatform/atomicDEX-API/pull/1582) +- `disable_coin` should fail if there are tokens dependent on the platform [#1651](https://github.com/KomodoPlatform/atomicDEX-API/pull/1651) +- Implement a repeatable future [#1564](https://github.com/KomodoPlatform/atomicDEX-API/pull/1564) +- Version handling was enhanced [#1686](https://github.com/KomodoPlatform/atomicDEX-API/pull/1686) + - Version of `mm2_bin_lib` from cargo manifest is now used for the API version + - `--version`, `-v`, `version` arguments now print the mm2 version +- Workflow for VirusTotal results was added to CI [#1676](https://github.com/KomodoPlatform/atomicDEX-API/pull/1676) +- `parity-ethereum` and `testcontainers-rs` crates from KomodoPlatform repo are now used [#1690](https://github.com/KomodoPlatform/atomicDEX-API/pull/1690) +- Testnet node of atom was updated, RUSTSEC-2023-0018 was ignored [#1692](https://github.com/KomodoPlatform/atomicDEX-API/pull/1692) +- Timestamp value sent from the peer in `PubkeyKeepAlive` msg was ignored and the received timestamp was used instead [#1668](https://github.com/KomodoPlatform/atomicDEX-API/pull/1668) +- Change release branch from mm2.1 to main in CI [#1697](https://github.com/KomodoPlatform/atomicDEX-API/pull/1697) +- CHANGELOG.md was introduced to have a complete log of code changes [#1680](https://github.com/KomodoPlatform/atomicDEX-API/pull/1680) +- Small fixes [#1518](https://github.com/KomodoPlatform/atomicDEX-API/pull/1518), [#1515](https://github.com/KomodoPlatform/atomicDEX-API/pull/1515), [#1550](https://github.com/KomodoPlatform/atomicDEX-API/pull/1657), [#1657](https://github.com/KomodoPlatform/atomicDEX-API/pull/1657) + +**NB - Backwards compatibility breaking changes:** +- Because of [#1548](https://github.com/KomodoPlatform/atomicDEX-API/pull/1548), old nodes will not be able to swap BTC segwit with new nodes since locktimes are exchanged and validated in the negotiation messages. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ae43127780..7fa370d87a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -41,10 +41,10 @@ stages: name: 'MM2_Lint_Linux' os: 'Linux' -# - template: azure-pipelines-lint-stage-job.yml # Template reference -# parameters: -# name: 'MM2_Lint_MacOS' -# os: 'Darwin' + - template: azure-pipelines-lint-stage-job.yml # Template reference + parameters: + name: 'MM2_Lint_MacOS' + os: 'Darwin' - template: azure-pipelines-lint-stage-job.yml # Template reference parameters: @@ -72,9 +72,9 @@ stages: - template: azure-pipelines-android-job.yml parameters: os: 'Linux' -# - template: azure-pipelines-ios-job.yml -# parameters: -# os: 'Darwin' + - template: azure-pipelines-ios-job.yml + parameters: + os: 'Darwin' - stage: Desktop displayName: Desktop Build and test @@ -91,15 +91,15 @@ stages: alice_userpass: 'ALICE_USERPASS_LINUX' telegram_api_key: 'TELEGRAM_API_KEY' -# - template: azure-pipelines-build-stage-job.yml # Template reference -# parameters: -# name: 'MM2_Build_MacOS' -# os: 'Darwin' -# bob_passphrase: 'BOB_PASSPHRASE_MAC' -# bob_userpass: 'BOB_USERPASS_MAC' -# alice_passphrase: 'ALICE_PASSPHRASE_MAC' -# alice_userpass: 'ALICE_USERPASS_MAC' -# telegram_api_key: 'TELEGRAM_API_KEY' + - template: azure-pipelines-build-stage-job.yml # Template reference + parameters: + name: 'MM2_Build_MacOS' + os: 'Darwin' + bob_passphrase: 'BOB_PASSPHRASE_MAC' + bob_userpass: 'BOB_USERPASS_MAC' + alice_passphrase: 'ALICE_PASSPHRASE_MAC' + alice_userpass: 'ALICE_USERPASS_MAC' + telegram_api_key: 'TELEGRAM_API_KEY' - template: azure-pipelines-build-stage-job.yml # Template reference parameters: diff --git a/dev-logs/2023-feb/enhancements/.gitkeep b/dev-logs/2023-feb/enhancements/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dev-logs/2023-feb/features/.gitkeep b/dev-logs/2023-feb/features/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dev-logs/2023-feb/fixes/.gitkeep b/dev-logs/2023-feb/fixes/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dev-logs/2023-feb/fixes/fix_swap_time_difference_err_message b/dev-logs/2023-feb/fixes/fix_swap_time_difference_err_message deleted file mode 100644 index 91d0075a1a..0000000000 --- a/dev-logs/2023-feb/fixes/fix_swap_time_difference_err_message +++ /dev/null @@ -1,4 +0,0 @@ -Changed the error message for failing swaps due to a time difference between maker and taker to a more informative one - - -author: @shamardy \ No newline at end of file diff --git a/dev-logs/2023-feb/upgrades/.gitkeep b/dev-logs/2023-feb/upgrades/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/dev-logs/2023-feb/upgrades/axum_core_upgrade_to_0_2_9 b/dev-logs/2023-feb/upgrades/axum_core_upgrade_to_0_2_9 deleted file mode 100644 index 2dd6415bca..0000000000 --- a/dev-logs/2023-feb/upgrades/axum_core_upgrade_to_0_2_9 +++ /dev/null @@ -1,6 +0,0 @@ -axum-core upgraded to 0.2.9 because current version(0.2.4) considered as vulnerable - -further informations https://rustsec.org/advisories/RUSTSEC-2022-0055.html - - -author: @ozkanonur \ No newline at end of file diff --git a/dev-logs/2023-feb/upgrades/bumpalo_upgrade_to_3_12_0 b/dev-logs/2023-feb/upgrades/bumpalo_upgrade_to_3_12_0 deleted file mode 100644 index fcb627621b..0000000000 --- a/dev-logs/2023-feb/upgrades/bumpalo_upgrade_to_3_12_0 +++ /dev/null @@ -1,6 +0,0 @@ -bumpalo upgraded to 3.12.0 because current version(3.4.0) considered as vulnerable - -further informations https://rustsec.org/advisories/RUSTSEC-2022-0078.html - - -author: @ozkanonur \ No newline at end of file diff --git a/dev-logs/2023-feb/upgrades/libp2p_upgrade_to_0_45_1 b/dev-logs/2023-feb/upgrades/libp2p_upgrade_to_0_45_1 deleted file mode 100644 index 60933a2681..0000000000 --- a/dev-logs/2023-feb/upgrades/libp2p_upgrade_to_0_45_1 +++ /dev/null @@ -1,6 +0,0 @@ -libp2p upgraded to 0.45.1 because current version(0.45.0) considered as vulnerable - -further informations https://rustsec.org/advisories/RUSTSEC-2022-0084.html - - -author: @ozkanonur \ No newline at end of file diff --git a/dev-logs/2023-feb/upgrades/tokio_upgrade_to_1_25_0 b/dev-logs/2023-feb/upgrades/tokio_upgrade_to_1_25_0 deleted file mode 100644 index 9aff385837..0000000000 --- a/dev-logs/2023-feb/upgrades/tokio_upgrade_to_1_25_0 +++ /dev/null @@ -1,6 +0,0 @@ -tokio upgraded to 1.25.0 because current version(1.18.2) considered as vulnerable - -further informations https://rustsec.org/advisories/RUSTSEC-2023-0001.html and https://rustsec.org/advisories/RUSTSEC-2023-0005.html - - -author: @ozkanonur \ No newline at end of file From 0bbfc4ea4da46d71742b01ca46e1355aab098bba Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 19:37:06 +0300 Subject: [PATCH 21/79] exclude check step Signed-off-by: ozkanonur --- .github/workflows/check_fmt_lint.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/check_fmt_lint.yml index 40f8ab957c..b3d91ef70c 100644 --- a/.github/workflows/check_fmt_lint.yml +++ b/.github/workflows/check_fmt_lint.yml @@ -1,4 +1,4 @@ -name: fmt, check, lint +name: Code format & lint on: push: branches: @@ -33,8 +33,5 @@ jobs: - name: fmt check run: cargo fmt -- --check - - name: build check - run: cargo check - - name: code lint - run: cargo clippy -- --D warnings + run: cargo clippy --profile ci -- --D warnings From c110fcefbf0f9167631cfee81bbe09a2f6e57830 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 19:37:47 +0300 Subject: [PATCH 22/79] update pipeline Signed-off-by: ozkanonur --- .github/workflows/check_fmt_lint.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/check_fmt_lint.yml index b3d91ef70c..c17ad99921 100644 --- a/.github/workflows/check_fmt_lint.yml +++ b/.github/workflows/check_fmt_lint.yml @@ -24,12 +24,6 @@ jobs: rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy rustup default ${{ matrix.rust }} - - name: Turn off debug assertations - run: | - echo """ - [profile.dev] - debug = false" >> .cargo/config.toml - - name: fmt check run: cargo fmt -- --check From 98d0594de03484e12d74f23408f92ae1f6189439 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 8 Mar 2023 19:46:24 +0300 Subject: [PATCH 23/79] add wasm lint step Signed-off-by: ozkanonur --- .github/workflows/check_fmt_lint.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/check_fmt_lint.yml index c17ad99921..55f9f8768c 100644 --- a/.github/workflows/check_fmt_lint.yml +++ b/.github/workflows/check_fmt_lint.yml @@ -10,7 +10,7 @@ on: - dev jobs: - x86-checks: + fmt-and-x86-lint: timeout-minutes: 20 runs-on: ${{ matrix.os }} strategy: @@ -27,5 +27,22 @@ jobs: - name: fmt check run: cargo fmt -- --check - - name: code lint + - name: x86 code lint run: cargo clippy --profile ci -- --D warnings + + wasm-lint: + timeout-minutes: 20 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy + rustup default ${{ matrix.rust }} + + - name: wasm code lint + run: cargo clippy --target wasm32-unknown-unknown --profile ci -- --D warnings From 0058f7107509aade6268c56cf3f33933d8220d3b Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Thu, 9 Mar 2023 13:05:31 +0300 Subject: [PATCH 24/79] improvide wasm stack and add wasm pipeline Signed-off-by: ozkanonur --- .../{check_fmt_lint.yml => fmt-and-lint.yml} | 3 +- mm2src/coins/eth.rs | 2 +- mm2src/coins/eth/eth_wasm_tests.rs | 1 - .../coins/hd_wallet_storage/wasm_storage.rs | 22 ++++--- .../tx_history_storage/tx_history_v2_tests.rs | 8 +-- .../wasm/tx_history_storage_v1.rs | 16 ++--- .../wasm/tx_history_storage_v2.rs | 58 +++++++++---------- mm2src/coins/utxo/rpc_clients.rs | 1 + .../utxo/utxo_block_header_storage/mod.rs | 10 ++-- .../wasm/block_header_table.rs | 13 ++--- .../wasm/indexeddb_block_header_storage.rs | 8 +-- mm2src/coins/utxo/utxo_common_tests.rs | 6 +- mm2src/coins/utxo/utxo_wasm_tests.rs | 2 +- mm2src/crypto/src/metamask_ctx.rs | 2 +- mm2src/mm2_bin_lib/src/mm2_bin.rs | 3 + mm2src/mm2_db/src/indexed_db/db_lock.rs | 6 +- .../mm2_db/src/indexed_db/drivers/builder.rs | 9 ++- .../src/indexed_db/drivers/cursor/cursor.rs | 19 +++--- .../drivers/cursor/multi_key_bound_cursor.rs | 2 +- .../mm2_db/src/indexed_db/indexed_cursor.rs | 2 +- mm2src/mm2_db/src/indexed_db/indexed_db.rs | 31 +++++----- .../src/account/storage/wasm_storage.rs | 33 ++++++----- mm2src/mm2_main/src/lp_ordermatch.rs | 2 +- .../src/lp_ordermatch/my_orders_storage.rs | 2 +- .../src/lp_ordermatch/ordermatch_wasm_db.rs | 20 +++---- mm2src/mm2_main/src/lp_swap.rs | 2 +- .../mm2_main/src/lp_swap/my_swaps_storage.rs | 2 +- mm2src/mm2_main/src/lp_swap/saved_swap.rs | 2 +- mm2src/mm2_main/src/lp_swap/swap_lock.rs | 2 +- mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs | 26 ++++----- mm2src/mm2_main/src/rpc.rs | 2 +- mm2src/mm2_net/src/wasm_ws.rs | 4 +- mm2src/mm2_test_helpers/src/for_tests.rs | 3 +- 33 files changed, 150 insertions(+), 174 deletions(-) rename .github/workflows/{check_fmt_lint.yml => fmt-and-lint.yml} (95%) diff --git a/.github/workflows/check_fmt_lint.yml b/.github/workflows/fmt-and-lint.yml similarity index 95% rename from .github/workflows/check_fmt_lint.yml rename to .github/workflows/fmt-and-lint.yml index 55f9f8768c..d7ee3cf52f 100644 --- a/.github/workflows/check_fmt_lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -1,4 +1,4 @@ -name: Code format & lint +name: CI on: push: branches: @@ -43,6 +43,7 @@ jobs: run: | rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy rustup default ${{ matrix.rust }} + rustup target add wasm32-unknown-unknown - name: wasm code lint run: cargo clippy --target wasm32-unknown-unknown --profile ci -- --D warnings diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 56715e1296..fdcfc13c0f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4064,7 +4064,7 @@ impl MmCoin for EthCoin { &[&"tx_history", &self.ticker], &ERRL!("Transaction history is not supported for ETH/ERC20 coins"), ); - return Box::new(futures01::future::ok(())); + Box::new(futures01::future::ok(())) } cfg_native! { let coin = self.clone(); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index d5ed0fc61c..e462472ada 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -11,7 +11,6 @@ wasm_bindgen_test_configure!(run_in_browser); fn pass() { let ctx = MmCtxBuilder::default().into_mm_arc(); let _coins_context = CoinsContext::from_ctx(&ctx).unwrap(); - assert_eq!(1, 1); } #[wasm_bindgen_test] diff --git a/mm2src/coins/hd_wallet_storage/wasm_storage.rs b/mm2src/coins/hd_wallet_storage/wasm_storage.rs index 721a4b09a7..a9e11143e2 100644 --- a/mm2src/coins/hd_wallet_storage/wasm_storage.rs +++ b/mm2src/coins/hd_wallet_storage/wasm_storage.rs @@ -98,18 +98,16 @@ impl TableSignature for HDAccountTable { fn table_name() -> &'static str { "hd_account" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_multi_index(WALLET_ID_INDEX, &["coin", "hd_wallet_rmd160"], false)?; - table.create_multi_index( - WALLET_ACCOUNT_ID_INDEX, - &["coin", "hd_wallet_rmd160", "account_id"], - true, - )?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(WALLET_ID_INDEX, &["coin", "hd_wallet_rmd160"], false)?; + table.create_multi_index( + WALLET_ACCOUNT_ID_INDEX, + &["coin", "hd_wallet_rmd160", "account_id"], + true, + )?; } + Ok(()) } } @@ -318,7 +316,7 @@ impl HDWalletIndexedDbStorage { /// This function is used in `hd_wallet_storage::tests`. pub(super) async fn get_all_storage_items(ctx: &MmArc) -> Vec { - let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); + let coins_ctx = CoinsContext::from_ctx(ctx).unwrap(); let db = coins_ctx.hd_wallet_db.get_or_initialize().await.unwrap(); let transaction = db.inner.transaction().await.unwrap(); let table = transaction.table::().await.unwrap(); diff --git a/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs b/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs index 69e9bb4eee..d160b68fa3 100644 --- a/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs +++ b/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs @@ -393,7 +393,7 @@ async fn test_get_raw_tx_bytes_on_add_transactions_impl() { let tx_hash = "6686ee013620d31ba645b27d581fed85437ce00f46b595a576718afac4dd5b69"; - let maybe_tx_hex = storage.tx_bytes_from_cache(&wallet_id, &tx_hash).await.unwrap(); + let maybe_tx_hex = storage.tx_bytes_from_cache(&wallet_id, tx_hash).await.unwrap(); assert!(maybe_tx_hex.is_none()); let tx1 = get_bch_tx_details("6686ee013620d31ba645b27d581fed85437ce00f46b595a576718afac4dd5b69"); @@ -409,11 +409,7 @@ async fn test_get_raw_tx_bytes_on_add_transactions_impl() { .await .unwrap(); - let tx_hex = storage - .tx_bytes_from_cache(&wallet_id, &tx_hash) - .await - .unwrap() - .unwrap(); + let tx_hex = storage.tx_bytes_from_cache(&wallet_id, tx_hash).await.unwrap().unwrap(); assert_eq!(tx_hex, expected_tx_hex); } diff --git a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs index 80d573de23..90ef077cde 100644 --- a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs +++ b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs @@ -60,8 +60,10 @@ impl HistoryId { fn new(ticker: &str, wallet_address: &str) -> HistoryId { HistoryId(format!("{}_{}", ticker, wallet_address)) } fn as_str(&self) -> &str { &self.0 } +} - fn to_string(&self) -> String { self.0.clone() } +impl std::fmt::Display for HistoryId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", &self.0) } } #[derive(Debug, Deserialize, Serialize)] @@ -74,13 +76,11 @@ impl TableSignature for TxHistoryTableV1 { fn table_name() -> &'static str { "tx_history" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_index("history_id", true)?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_index("history_id", true)?; } + Ok(()) } } @@ -94,7 +94,7 @@ mod tests { #[wasm_bindgen_test] async fn test_tx_history() { - const DB_NAME: &'static str = "TEST_TX_HISTORY"; + const DB_NAME: &str = "TEST_TX_HISTORY"; let db = TxHistoryDb::init(DbIdentifier::for_test(DB_NAME)) .await .expect("!TxHistoryDb::init_with_fs_path"); diff --git a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs index 12255a6a10..1b19565bf7 100644 --- a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs +++ b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs @@ -417,32 +417,29 @@ impl TableSignature for TxHistoryTableV2 { fn table_name() -> &'static str { "tx_history_v2" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_multi_index(TxHistoryTableV2::WALLET_ID_INDEX, &["coin", "hd_wallet_rmd160"], false)?; - table.create_multi_index( - TxHistoryTableV2::WALLET_ID_INTERNAL_ID_INDEX, - &["coin", "hd_wallet_rmd160", "internal_id"], - true, - )?; - table.create_multi_index( - TxHistoryTableV2::WALLET_ID_TX_HASH_INDEX, - &["coin", "hd_wallet_rmd160", "tx_hash"], - false, - )?; - table.create_multi_index( - TxHistoryTableV2::WALLET_ID_CONFIRMATION_STATUS_INDEX, - &["coin", "hd_wallet_rmd160", "confirmation_status"], - false, - )?; - table.create_multi_index( - TxHistoryTableV2::WALLET_ID_TOKEN_ID_INDEX, - &["coin", "hd_wallet_rmd160", "token_id"], - false, - )?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(TxHistoryTableV2::WALLET_ID_INDEX, &["coin", "hd_wallet_rmd160"], false)?; + table.create_multi_index( + TxHistoryTableV2::WALLET_ID_INTERNAL_ID_INDEX, + &["coin", "hd_wallet_rmd160", "internal_id"], + true, + )?; + table.create_multi_index( + TxHistoryTableV2::WALLET_ID_TX_HASH_INDEX, + &["coin", "hd_wallet_rmd160", "tx_hash"], + false, + )?; + table.create_multi_index( + TxHistoryTableV2::WALLET_ID_CONFIRMATION_STATUS_INDEX, + &["coin", "hd_wallet_rmd160", "confirmation_status"], + false, + )?; + table.create_multi_index( + TxHistoryTableV2::WALLET_ID_TOKEN_ID_INDEX, + &["coin", "hd_wallet_rmd160", "token_id"], + false, + )?; } Ok(()) } @@ -474,12 +471,9 @@ impl TableSignature for TxCacheTableV2 { fn table_name() -> &'static str { "tx_cache_v2" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_multi_index(TxCacheTableV2::COIN_TX_HASH_INDEX, &["coin", "tx_hash"], true)?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(TxCacheTableV2::COIN_TX_HASH_INDEX, &["coin", "tx_hash"], true)?; } Ok(()) } diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index 2029702ee5..e06715c99d 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -1436,6 +1436,7 @@ enum ElectrumConfig { } /// Electrum client configuration +#[allow(clippy::upper_case_acronyms)] #[cfg(target_arch = "wasm32")] #[derive(Clone, Debug, Serialize)] enum ElectrumConfig { diff --git a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs index dfe1ae3839..85a5954e81 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs @@ -137,7 +137,7 @@ mod block_headers_storage_tests { let block_header: BlockHeader = "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".into(); headers.insert(520481, block_header); storage.add_block_headers_to_storage(headers).await.unwrap(); - assert!(!storage.is_table_empty().await.is_ok()); + assert!(storage.is_table_empty().await.is_err()); } pub(crate) async fn test_get_block_header_impl(for_coin: &str) { @@ -152,7 +152,7 @@ mod block_headers_storage_tests { headers.insert(520481, block_header); storage.add_block_headers_to_storage(headers).await.unwrap(); - assert!(!storage.is_table_empty().await.is_ok()); + assert!(storage.is_table_empty().await.is_err()); let hex = storage.get_block_header_raw(520481).await.unwrap().unwrap(); assert_eq!(hex, "0000002076d41d3e4b0bfd4c0d3b30aa69fdff3ed35d85829efd04000000000000000000b386498b583390959d9bac72346986e3015e83ac0b54bc7747a11a494ac35c94bb3ce65a53fb45177f7e311c".to_string()); @@ -189,7 +189,7 @@ mod block_headers_storage_tests { headers.insert(201593, block_header); storage.add_block_headers_to_storage(headers).await.unwrap(); - assert!(!storage.is_table_empty().await.is_ok()); + assert!(storage.is_table_empty().await.is_err()); let actual_block_header = storage .get_last_block_header_with_non_max_bits(MAX_BITS_BTC) @@ -222,7 +222,7 @@ mod block_headers_storage_tests { headers.insert(201593, block_header); storage.add_block_headers_to_storage(headers).await.unwrap(); - assert!(!storage.is_table_empty().await.is_ok()); + assert!(storage.is_table_empty().await.is_err()); let last_block_height = storage.get_last_block_height().await.unwrap(); assert_eq!(last_block_height.unwrap(), 201595); @@ -250,7 +250,7 @@ mod block_headers_storage_tests { headers.insert(201593, block_header); storage.add_block_headers_to_storage(headers).await.unwrap(); - assert!(!storage.is_table_empty().await.is_ok()); + assert!(storage.is_table_empty().await.is_err()); // Remove 2 headers from storage. storage.remove_headers_up_to_height(201594).await.unwrap(); diff --git a/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs b/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs index 951d0aa938..315a94cdd8 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/wasm/block_header_table.rs @@ -18,14 +18,11 @@ impl TableSignature for BlockHeaderStorageTable { fn table_name() -> &'static str { "block_header_storage_cache_table" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_multi_index(Self::TICKER_HEIGHT_INDEX, &["ticker", "height"], true)?; - table.create_multi_index(Self::HASH_TICKER_INDEX, &["hash", "ticker"], true)?; - table.create_index("ticker", false)?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index(Self::TICKER_HEIGHT_INDEX, &["ticker", "height"], true)?; + table.create_multi_index(Self::HASH_TICKER_INDEX, &["hash", "ticker"], true)?; + table.create_index("ticker", false)?; } Ok(()) } 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 287f42b7ec..3f5e9aae8e 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 @@ -101,7 +101,7 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .with_value(&ticker) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? - .with_value(&BeBigUint::from(height)) + .with_value(BeBigUint::from(height)) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; block_headers_db @@ -152,7 +152,7 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .with_value(&ticker) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? - .with_value(&BeBigUint::from(height)) + .with_value(BeBigUint::from(height)) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; Ok(block_headers_db @@ -281,7 +281,7 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { .await .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; let index_keys = MultiIndex::new(BlockHeaderStorageTable::HASH_TICKER_INDEX) - .with_value(&hash.to_string()) + .with_value(hash.to_string()) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? .with_value(&ticker) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; @@ -320,7 +320,7 @@ impl BlockHeaderStorageOps for IDBBlockHeadersStorage { let index_keys = MultiIndex::new(BlockHeaderStorageTable::TICKER_HEIGHT_INDEX) .with_value(&ticker) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))? - .with_value(&BeBigUint::from(height)) + .with_value(BeBigUint::from(height)) .map_err(|err| BlockHeaderStorageError::table_err(&ticker, err.to_string()))?; block_headers_db diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index eef16a64ba..c8d9406cdc 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -19,9 +19,9 @@ use std::convert::TryFrom; use std::num::NonZeroUsize; use std::time::Duration; -pub(super) const TEST_COIN_NAME: &'static str = "RICK"; +pub(super) const TEST_COIN_NAME: &str = "RICK"; // Made-up hrp for rick to test p2wpkh script -pub(super) const TEST_COIN_HRP: &'static str = "rck"; +pub(super) const TEST_COIN_HRP: &str = "rck"; pub(super) const TEST_COIN_DECIMALS: u8 = 8; const MORTY_HD_TX_HISTORY_STR: &str = include_str!("../for_tests/MORTY_HD_tx_history_fixtures.json"); @@ -185,7 +185,7 @@ pub(super) fn get_morty_hd_transactions_ordered(tx_hashes: &[&str]) -> Vec ElectrumClient { let ctx = MmCtxBuilder::default().into_mm_arc(); diff --git a/mm2src/crypto/src/metamask_ctx.rs b/mm2src/crypto/src/metamask_ctx.rs index 4c9e0e681e..a36b957e79 100644 --- a/mm2src/crypto/src/metamask_ctx.rs +++ b/mm2src/crypto/src/metamask_ctx.rs @@ -58,7 +58,7 @@ impl MetamaskCtx { .await?; let sig = sig.strip_prefix("0x").unwrap_or(&sig); - let signature = Signature::from_str(&sig) + let signature = Signature::from_str(sig) .map_to_mm(|_| MetamaskError::Internal(format!("'{sig}' signature is invalid")))?; let pubkey = recover_pubkey(hash, signature).mm_err(|_| { let error = format!("Couldn't recover a public key from the signature: '{sig}'"); diff --git a/mm2src/mm2_bin_lib/src/mm2_bin.rs b/mm2src/mm2_bin_lib/src/mm2_bin.rs index cad9e8be88..053d9dd9b4 100644 --- a/mm2src/mm2_bin_lib/src/mm2_bin.rs +++ b/mm2src/mm2_bin_lib/src/mm2_bin.rs @@ -1,6 +1,9 @@ #[cfg(not(target_arch = "wasm32"))] use mm2_main::mm2::mm2_main; +#[cfg(not(target_arch = "wasm32"))] const MM_VERSION: &str = env!("MM_VERSION"); + +#[cfg(not(target_arch = "wasm32"))] const MM_DATETIME: &str = env!("MM_DATETIME"); #[cfg(all(target_os = "linux", target_arch = "x86_64", target_env = "gnu"))] diff --git a/mm2src/mm2_db/src/indexed_db/db_lock.rs b/mm2src/mm2_db/src/indexed_db/db_lock.rs index b7106c252c..2a650f0c5f 100644 --- a/mm2src/mm2_db/src/indexed_db/db_lock.rs +++ b/mm2src/mm2_db/src/indexed_db/db_lock.rs @@ -26,7 +26,7 @@ impl ConstructibleDb { ConstructibleDb { mutex: AsyncMutex::new(None), db_namespace: ctx.db_namespace, - wallet_rmd160: ctx.rmd160().clone(), + wallet_rmd160: *ctx.rmd160(), } } @@ -37,7 +37,7 @@ impl ConstructibleDb { ConstructibleDb { mutex: AsyncMutex::new(None), db_namespace: ctx.db_namespace, - wallet_rmd160: ctx.shared_db_id().clone(), + wallet_rmd160: *ctx.shared_db_id(), } } @@ -50,7 +50,7 @@ impl ConstructibleDb { return Ok(unwrap_db_instance(locked_db)); } - let db_id = DbIdentifier::new::(self.db_namespace, self.wallet_rmd160.clone()); + let db_id = DbIdentifier::new::(self.db_namespace, self.wallet_rmd160); let db = Db::init(db_id).await?; *locked_db = Some(db); diff --git a/mm2src/mm2_db/src/indexed_db/drivers/builder.rs b/mm2src/mm2_db/src/indexed_db/drivers/builder.rs index ef8c6ea086..7d8c6bb79c 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/builder.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/builder.rs @@ -130,8 +130,8 @@ impl IdbDatabaseBuilder { }, }; - let db = Self::get_db_from_request(&db_request)?; - let transaction = Self::get_transaction_from_request(&db_request)?; + let db = Self::get_db_from_request(db_request)?; + let transaction = Self::get_transaction_from_request(db_request)?; let version_event = match event.dyn_into::() { Ok(version) => version, @@ -145,9 +145,8 @@ impl IdbDatabaseBuilder { let old_version = version_event.old_version() as u32; let new_version = version_event .new_version() - .ok_or(MmError::new(InitDbError::InvalidVersion( - "Expected a new_version".to_owned(), - )))? as u32; + .ok_or_else(|| MmError::new(InitDbError::InvalidVersion("Expected a new_version".to_owned())))? + as u32; let upgrader = DbUpgrader::new(db, transaction); for on_upgrade_needed_cb in handlers { diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs index 0fdc26261b..bba549d3bd 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/cursor.rs @@ -125,8 +125,8 @@ impl CursorBoundValue { pub fn to_js_value(&self) -> CursorResult { match self { - CursorBoundValue::Uint(uint) => Ok(JsValue::from(*uint as u32)), - CursorBoundValue::Int(int) => Ok(JsValue::from(*int as i32)), + CursorBoundValue::Uint(uint) => Ok(JsValue::from(*uint)), + CursorBoundValue::Int(int) => Ok(JsValue::from(*int)), CursorBoundValue::BigUint(int) => serialize_to_js(int).map_to_mm(|e| CursorError::InvalidKeyRange { description: e.to_string(), }), @@ -137,12 +137,13 @@ impl CursorBoundValue { // `matches` macro leads to the following error: // (CursorBoundValue::Uint(_), CursorBoundValue::Uint(_)) // ^ no rules expected this token in macro call - match (self, other) { + + matches!( + (self, other), (CursorBoundValue::Int(_), CursorBoundValue::Int(_)) - | (CursorBoundValue::Uint(_), CursorBoundValue::Uint(_)) - | (CursorBoundValue::BigUint(_), CursorBoundValue::BigUint(_)) => true, - _ => false, - } + | (CursorBoundValue::Uint(_), CursorBoundValue::Uint(_)) + | (CursorBoundValue::BigUint(_), CursorBoundValue::BigUint(_)) + ) } fn deserialize_with_expected_type(value: &Json, expected: &Self) -> CursorResult { @@ -213,8 +214,8 @@ impl CursorDriver { // but without a key range. None if reverse => { return MmError::err(CursorError::ErrorOpeningCursor { - description: format!("Direction cannot be specified without a range"), - }) + description: "Direction cannot be specified without a range".to_owned(), + }); }, None => db_index.open_cursor(), }; diff --git a/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs b/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs index 6a6b224fcd..18a5962498 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/cursor/multi_key_bound_cursor.rs @@ -227,7 +227,7 @@ mod tests { let input_index_js_value = serialize_to_js(&input_index).unwrap(); let (item_action, cursor_action) = cursor .on_iteration(input_index_js_value) - .expect(&format!("Error due to the index '{:?}'", input_index)); + .unwrap_or_else(|_| panic!("Error due to the index '{:?}'", input_index)); let actual_next: Json = match cursor_action { CursorAction::ContinueWithValue(next_index_js_value) => { diff --git a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs index 04dd7c37cb..cdbc019f1a 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs @@ -251,7 +251,7 @@ mod tests { table .add_item(&item) .await - .expect(&format!("Error adding {:?} item", item)); + .unwrap_or_else(|_| panic!("Error adding {:?} item", item)); } } diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index 97fc4d684d..3bad052869 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -101,7 +101,7 @@ impl DbIdentifier { } } - pub fn display_rmd160(&self) -> String { hex::encode(&*self.wallet_rmd160) } + pub fn display_rmd160(&self) -> String { hex::encode(*self.wallet_rmd160) } } pub struct IndexedDbBuilder { @@ -310,7 +310,7 @@ impl<'transaction, Table: TableSignature> DbTable<'transaction, Table> { /// Adds the given item to the table. /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add pub async fn add_item(&self, item: &Table) -> DbTransactionResult { - let item = json::to_value(&item).map_to_mm(|e| DbTransactionError::ErrorSerializingItem(e.to_string()))?; + let item = json::to_value(item).map_to_mm(|e| DbTransactionError::ErrorSerializingItem(e.to_string()))?; let (result_tx, result_rx) = oneshot::channel(); let event = internal::DbTableEvent::AddItem { item, result_tx }; @@ -335,12 +335,10 @@ impl<'transaction, Table: TableSignature> DbTable<'transaction, Table> { match ids.len() { 0 => self.add_item(item).await.map(AddOrIgnoreResult::Added), 1 => Ok(AddOrIgnoreResult::ExistAlready(ids[0])), - got_items => { - return MmError::err(DbTransactionError::MultipleItemsByUniqueIndex { - index: index.to_owned(), - got_items, - }); - }, + got_items => MmError::err(DbTransactionError::MultipleItemsByUniqueIndex { + index: index.to_owned(), + got_items, + }), } } @@ -528,12 +526,10 @@ impl<'transaction, Table: TableSignature> DbTable<'transaction, Table> { let item_id = ids[0]; self.replace_item(item_id, item).await }, - got_items => { - return MmError::err(DbTransactionError::MultipleItemsByUniqueIndex { - index: index.to_owned(), - got_items, - }); - }, + got_items => MmError::err(DbTransactionError::MultipleItemsByUniqueIndex { + index: index.to_owned(), + got_items, + }), } } @@ -1296,9 +1292,10 @@ mod tests { version: u32, expected_old_new_versions: Option<(u32, u32)>, ) -> Result<(), String> { - let mut versions = LAST_VERSIONS.lock().expect("!LAST_VERSIONS.lock()"); - *versions = None; - drop(versions); + { + let mut versions = LAST_VERSIONS.lock().expect("!LAST_VERSIONS.lock()"); + *versions = None; + } let _db = IndexedDbBuilder::new(db_identifier) .with_version(version) 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 8f2d97e9f0..8392294c78 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs @@ -152,7 +152,7 @@ impl WasmAccountStorage { db_transaction: &DbTransaction<'_>, enabled_account_id: EnabledAccountId, ) -> AccountStorageResult<()> { - match Self::load_enabled_account_id(&db_transaction).await? { + match Self::load_enabled_account_id(db_transaction).await? { // If there is an enabled account **and** its ID is the same as `enabled_account_id`. Some(actual_enabled) if actual_enabled == enabled_account_id => (), _ => return Ok(()), @@ -390,17 +390,15 @@ impl TableSignature for AccountTable { fn table_name() -> &'static str { "gui_account" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_multi_index( - AccountTable::ACCOUNT_ID_INDEX, - &["account_type", "account_idx", "device_pubkey"], - true, - )?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index( + AccountTable::ACCOUNT_ID_INDEX, + &["account_type", "account_idx", "device_pubkey"], + true, + )?; } + Ok(()) } } @@ -476,12 +474,15 @@ impl TableSignature for EnabledAccountTable { fn table_name() -> &'static str { "gui_enabled_account" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let _table = upgrader.create_table(Self::table_name())?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_multi_index( + AccountTable::ACCOUNT_ID_INDEX, + &["account_type", "account_idx", "device_pubkey"], + true, + )?; } + Ok(()) } } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index beda0108ff..4dab963549 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2751,7 +2751,7 @@ impl OrdermatchContext { #[cfg(target_arch = "wasm32")] pub async fn ordermatch_db(&self) -> InitDbResult> { - Ok(self.ordermatch_db.get_or_initialize().await?) + self.ordermatch_db.get_or_initialize().await } } diff --git a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs index 3fa0c7970a..1fbbba5ca6 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs @@ -755,7 +755,7 @@ mod tests { } async fn get_all_items(ctx: &MmArc) -> Vec
{ - let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); + let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).unwrap(); let db = ordermatch_ctx.ordermatch_db().await.unwrap(); let transaction = db.transaction().await.unwrap(); let table = transaction.table::
().await.unwrap(); 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 264a696879..812a802999 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs @@ -105,13 +105,10 @@ pub mod tables { fn table_name() -> &'static str { "my_filtering_history_orders" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_index("uuid", true)?; - // TODO add other indexes during [`MyOrdersStorage::select_orders_by_filter`] implementation. - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_index("uuid", true)?; + // TODO add other indexes during [`MyOrdersStorage::select_orders_by_filter`] implementation. } Ok(()) } @@ -124,12 +121,9 @@ pub mod tables { new_version: u32, table_name: &'static str, ) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(table_name)?; - table.create_index("uuid", true)?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(table_name)?; + table.create_index("uuid", true)?; } Ok(()) } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index c84402b902..318e1389cd 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -444,7 +444,7 @@ impl SwapsContext { } #[cfg(target_arch = "wasm32")] - pub async fn swap_db(&self) -> InitDbResult> { Ok(self.swap_db.get_or_initialize().await?) } + pub async fn swap_db(&self) -> InitDbResult> { self.swap_db.get_or_initialize().await } } #[derive(Debug, Deserialize)] diff --git a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs index e38b8c5419..66926eddf3 100644 --- a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs +++ b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs @@ -451,7 +451,7 @@ mod wasm_tests { // unknown UUID from_uuid: Some(from_uuid), }; - let actual = take_according_to_paging_opts(uuids.clone(), &paging) + let actual = take_according_to_paging_opts(uuids, &paging) .expect_err("'take_according_to_paging_opts' must return an error"); assert_eq!(actual.into_inner(), MySwapsError::FromUuidNotFound(from_uuid)); } diff --git a/mm2src/mm2_main/src/lp_swap/saved_swap.rs b/mm2src/mm2_main/src/lp_swap/saved_swap.rs index a4afb8dafe..3519d45596 100644 --- a/mm2src/mm2_main/src/lp_swap/saved_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/saved_swap.rs @@ -348,7 +348,7 @@ mod tests { wasm_bindgen_test_configure!(run_in_browser); async fn get_all_items(ctx: &MmArc) -> Vec<(ItemId, SavedSwapTable)> { - let swaps_ctx = SwapsContext::from_ctx(&ctx).unwrap(); + let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction diff --git a/mm2src/mm2_main/src/lp_swap/swap_lock.rs b/mm2src/mm2_main/src/lp_swap/swap_lock.rs index f9f84a6cb7..f1b6269e92 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_lock.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_lock.rs @@ -205,7 +205,7 @@ mod tests { wasm_bindgen_test_configure!(run_in_browser); async fn get_all_items(ctx: &MmArc) -> Vec<(ItemId, SwapLockTable)> { - let swaps_ctx = SwapsContext::from_ctx(&ctx).unwrap(); + let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction.table::().await.expect("Error opening table"); 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 58511edb2d..d680ba4eb8 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_wasm_db.rs @@ -84,16 +84,13 @@ pub mod tables { fn table_name() -> &'static str { "my_swaps" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(Self::table_name())?; - table.create_index("uuid", true)?; - table.create_index("started_at", false)?; - table.create_multi_index("with_my_coin", &["my_coin", "started_at"], false)?; - table.create_multi_index("with_other_coin", &["other_coin", "started_at"], false)?; - table.create_multi_index("with_my_other_coins", &["my_coin", "other_coin", "started_at"], false)?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(Self::table_name())?; + table.create_index("uuid", true)?; + table.create_index("started_at", false)?; + table.create_multi_index("with_my_coin", &["my_coin", "started_at"], false)?; + table.create_multi_index("with_other_coin", &["other_coin", "started_at"], false)?; + table.create_multi_index("with_my_other_coins", &["my_coin", "other_coin", "started_at"], false)?; } Ok(()) } @@ -106,12 +103,9 @@ pub mod tables { new_version: u32, table_name: &'static str, ) -> OnUpgradeResult<()> { - match (old_version, new_version) { - (0, 1) => { - let table = upgrader.create_table(table_name)?; - table.create_index("uuid", true)?; - }, - _ => (), + if let (0, 1) = (old_version, new_version) { + let table = upgrader.create_table(table_name)?; + table.create_index("uuid", true)?; } Ok(()) } diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index c94ec2714f..c0c338dab6 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -144,7 +144,7 @@ async fn process_json_batch_requests(ctx: MmArc, requests: &[Json], client: Sock #[cfg(target_arch = "wasm32")] async fn process_json_request(ctx: MmArc, req_json: Json, client: SocketAddr) -> Result { if let Some(requests) = req_json.as_array() { - return process_json_batch_requests(ctx, &requests, client) + return process_json_batch_requests(ctx, requests, client) .await .map_err(|e| ERRL!("{}", e)); } diff --git a/mm2src/mm2_net/src/wasm_ws.rs b/mm2src/mm2_net/src/wasm_ws.rs index 40daa603dd..bf8008de54 100644 --- a/mm2src/mm2_net/src/wasm_ws.rs +++ b/mm2src/mm2_net/src/wasm_ws.rs @@ -313,7 +313,7 @@ impl WebSocketImpl { let (tx, rx) = mpsc::channel(1024); let onopen_closure = construct_ws_event_closure(|_: JsValue| WsTransportEvent::Establish, tx.clone()); - let onclose_closure = construct_ws_event_closure(|close: CloseEvent| WsTransportEvent::from(close), tx.clone()); + let onclose_closure = construct_ws_event_closure::(WsTransportEvent::from, tx.clone()); let onerror_closure = construct_ws_event_closure( |_: JsValue| WsTransportEvent::Error(WsTransportError::UnderlyingError), tx.clone(), @@ -323,7 +323,7 @@ impl WebSocketImpl { Ok(response) => WsTransportEvent::Incoming(response), Err(e) => WsTransportEvent::Error(WsTransportError::ErrorDecodingIncoming(e)), }, - tx.clone(), + tx, ); ws.set_onopen(Some(onopen_closure.as_ref().unchecked_ref())); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 74d0bd1fb8..8bbe120604 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -1154,7 +1154,8 @@ impl MarketMakerIt { } else { StatusCode::INTERNAL_SERVER_ERROR }; - let body_str = json::to_string(&body).expect(&format!("Response {:?} is not a valid JSON", body)); + let body_str = + json::to_string(&body).unwrap_or_else(|_| panic!("Response {:?} is not a valid JSON", body)); Ok((status_code, body_str, HeaderMap::new())) }, Err(e) => Ok((StatusCode::INTERNAL_SERVER_ERROR, e, HeaderMap::new())), From 45d728546ecb6e65de9323f79cac5057af3eafe4 Mon Sep 17 00:00:00 2001 From: Onur Date: Thu, 9 Mar 2023 17:52:36 +0300 Subject: [PATCH 25/79] feat: cosmos ibc transfer implementation (#1636) * save dev state (p.o.c) Signed-off-by: ozkanonur * implement `ibc_withdraw` RPC Signed-off-by: ozkanonur * impl integration test for `ibc_withdraw` Signed-off-by: ozkanonur * unify tendermint `ibc_withdraw` methods Signed-off-by: ozkanonur * create `mm2_git` crate and implement Git abstraction layer Signed-off-by: ozkanonur * implement `ibc_transfer_channels` and `ibc_chains` Signed-off-by: ozkanonur * add wasm compatibility to `mm2_git::github_client` Signed-off-by: ozkanonur * use `KomodoPlatform` source for `chain-registry` Signed-off-by: ozkanonur * move ibc rpc related sources into `coins::rpc_command` Signed-off-by: ozkanonur * inline `try_from` for `MsgTransfer` Signed-off-by: ozkanonur * use request ticker itself instead of platform one Signed-off-by: ozkanonur * add new error variant `WithdrawError::ActionNotAllowed` Signed-off-by: ozkanonur * typo fix Signed-off-by: ozkanonur --------- Signed-off-by: ozkanonur Reviewed-by: shamardy , borngraced , laruh --- Cargo.lock | 14 + Cargo.toml | 1 + mm2src/coins/Cargo.toml | 1 + mm2src/coins/lp_coins.rs | 3 + mm2src/coins/rpc_command/mod.rs | 1 + .../rpc_command/tendermint/ibc_chains.rs | 35 ++ .../tendermint/ibc_transfer_channels.rs | 76 ++++ .../rpc_command/tendermint/ibc_withdraw.rs | 27 ++ mm2src/coins/rpc_command/tendermint/mod.rs | 14 + mm2src/coins/tendermint/ibc/ibc_proto.rs | 20 + mm2src/coins/tendermint/ibc/mod.rs | 6 + mm2src/coins/tendermint/ibc/transfer_v1.rs | 108 ++++++ mm2src/coins/tendermint/mod.rs | 3 + mm2src/coins/tendermint/rpc/mod.rs | 5 +- mm2src/coins/tendermint/tendermint_coin.rs | 342 ++++++++++++++++-- mm2src/coins/tendermint/tendermint_token.rs | 152 +++++++- mm2src/mm2_git/Cargo.toml | 16 + mm2src/mm2_git/src/github_client.rs | 132 +++++++ mm2src/mm2_git/src/lib.rs | 59 +++ .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 4 + .../tests/mm2_tests/tendermint_tests.rs | 50 ++- mm2src/mm2_net/src/native_http.rs | 18 +- mm2src/mm2_net/src/transport.rs | 4 +- mm2src/mm2_net/src/wasm_http.rs | 18 + mm2src/mm2_test_helpers/src/for_tests.rs | 27 ++ 25 files changed, 1086 insertions(+), 50 deletions(-) create mode 100644 mm2src/coins/rpc_command/tendermint/ibc_chains.rs create mode 100644 mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs create mode 100644 mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs create mode 100644 mm2src/coins/rpc_command/tendermint/mod.rs create mode 100644 mm2src/coins/tendermint/ibc/ibc_proto.rs create mode 100644 mm2src/coins/tendermint/ibc/mod.rs create mode 100644 mm2src/coins/tendermint/ibc/transfer_v1.rs create mode 100644 mm2src/mm2_git/Cargo.toml create mode 100644 mm2src/mm2_git/src/github_client.rs create mode 100644 mm2src/mm2_git/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fabae55f36..0e94215126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1077,6 +1077,7 @@ dependencies = [ "mm2_core", "mm2_db", "mm2_err_handle", + "mm2_git", "mm2_io", "mm2_metamask", "mm2_metrics", @@ -4290,6 +4291,19 @@ dependencies = [ "web3", ] +[[package]] +name = "mm2_git" +version = "0.1.0" +dependencies = [ + "async-trait", + "common", + "http 0.2.7", + "mm2_err_handle", + "mm2_net", + "serde", + "serde_json", +] + [[package]] name = "mm2_gui_storage" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 9c3c237abc..421bdca2c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "mm2src/mm2_db", "mm2src/mm2_err_handle", "mm2src/mm2_eth", + "mm2src/mm2_git", "mm2src/mm2_io", "mm2src/mm2_libp2p", "mm2src/mm2_metamask", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 73d955ff45..7da462f746 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -58,6 +58,7 @@ lazy_static = "1.4" libc = "0.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } +mm2_git = { path = "../mm2_git" } mm2_io = { path = "../mm2_io" } mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 41ede7f020..55d2bd7680 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1794,6 +1794,8 @@ pub enum WithdrawError { AddressMismatchError { my_address: String, from: String }, #[display(fmt = "Contract type {} doesnt support 'withdraw_nft' yet", _0)] ContractTypeDoesntSupportNftWithdrawing(String), + #[display(fmt = "Action not allowed for coin: {}", _0)] + ActionNotAllowed(String), } impl HttpStatusCode for WithdrawError { @@ -1815,6 +1817,7 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnexpectedUserAction { .. } | WithdrawError::CoinDoesntSupportNftWithdraw { .. } | WithdrawError::AddressMismatchError { .. } + | WithdrawError::ActionNotAllowed(_) | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] diff --git a/mm2src/coins/rpc_command/mod.rs b/mm2src/coins/rpc_command/mod.rs index 945cea77fe..c401853b2d 100644 --- a/mm2src/coins/rpc_command/mod.rs +++ b/mm2src/coins/rpc_command/mod.rs @@ -8,3 +8,4 @@ pub mod init_create_account; pub mod init_scan_for_new_addresses; pub mod init_withdraw; #[cfg(not(target_arch = "wasm32"))] pub mod lightning; +pub mod tendermint; diff --git a/mm2src/coins/rpc_command/tendermint/ibc_chains.rs b/mm2src/coins/rpc_command/tendermint/ibc_chains.rs new file mode 100644 index 0000000000..67ed93e9fa --- /dev/null +++ b/mm2src/coins/rpc_command/tendermint/ibc_chains.rs @@ -0,0 +1,35 @@ +use common::HttpStatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::MmError; + +use crate::tendermint; + +pub type IBCChainRegistriesResult = Result>; + +#[derive(Clone, Serialize)] +pub struct IBCChainRegistriesResponse { + pub(crate) chain_registry_list: Vec, +} + +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] +#[serde(tag = "error_type", content = "error_data")] +pub enum IBCChainsRequestError { + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + InternalError(String), +} + +impl HttpStatusCode for IBCChainsRequestError { + fn status_code(&self) -> common::StatusCode { + match self { + IBCChainsRequestError::Transport(_) => common::StatusCode::SERVICE_UNAVAILABLE, + IBCChainsRequestError::InternalError(_) => common::StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[inline(always)] +pub async fn ibc_chains(_ctx: MmArc, _req: serde_json::Value) -> IBCChainRegistriesResult { + tendermint::get_ibc_chain_list().await +} diff --git a/mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs b/mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs new file mode 100644 index 0000000000..fce69042c6 --- /dev/null +++ b/mm2src/coins/rpc_command/tendermint/ibc_transfer_channels.rs @@ -0,0 +1,76 @@ +use common::HttpStatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::MmError; + +use crate::{lp_coinfind_or_err, MmCoinEnum}; + +pub type IBCTransferChannelsResult = Result>; + +#[derive(Clone, Deserialize)] +pub struct IBCTransferChannelsRequest { + pub(crate) coin: String, + pub(crate) destination_chain_registry_name: String, +} + +#[derive(Clone, Serialize)] +pub struct IBCTransferChannelsResponse { + pub(crate) ibc_transfer_channels: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +pub(crate) struct IBCTransferChannel { + pub(crate) channel_id: String, + pub(crate) ordering: String, + pub(crate) version: String, + pub(crate) tags: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct IBCTransferChannelTag { + pub(crate) status: String, + pub(crate) preferred: bool, + pub(crate) dex: Option, +} + +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, PartialEq)] +#[serde(tag = "error_type", content = "error_data")] +pub enum IBCTransferChannelsRequestError { + #[display(fmt = "No such coin {}", _0)] + NoSuchCoin(String), + #[display( + fmt = "Only tendermint based coins are allowed for `ibc_transfer_channels` operation. Current coin: {}", + _0 + )] + UnsupportedCoin(String), + #[display(fmt = "Could not find '{}' registry source.", _0)] + RegistrySourceCouldNotFound(String), + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + InternalError(String), +} + +impl HttpStatusCode for IBCTransferChannelsRequestError { + fn status_code(&self) -> common::StatusCode { + match self { + IBCTransferChannelsRequestError::UnsupportedCoin(_) | IBCTransferChannelsRequestError::NoSuchCoin(_) => { + common::StatusCode::BAD_REQUEST + }, + IBCTransferChannelsRequestError::RegistrySourceCouldNotFound(_) => common::StatusCode::NOT_FOUND, + IBCTransferChannelsRequestError::Transport(_) => common::StatusCode::SERVICE_UNAVAILABLE, + IBCTransferChannelsRequestError::InternalError(_) => common::StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +pub async fn ibc_transfer_channels(ctx: MmArc, req: IBCTransferChannelsRequest) -> IBCTransferChannelsResult { + let coin = lp_coinfind_or_err(&ctx, &req.coin) + .await + .map_err(|_| IBCTransferChannelsRequestError::NoSuchCoin(req.coin.clone()))?; + + match coin { + MmCoinEnum::Tendermint(coin) => coin.get_ibc_transfer_channels(req).await, + MmCoinEnum::TendermintToken(token) => token.platform_coin.get_ibc_transfer_channels(req).await, + _ => MmError::err(IBCTransferChannelsRequestError::UnsupportedCoin(req.coin)), + } +} diff --git a/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs b/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs new file mode 100644 index 0000000000..a490ef5cec --- /dev/null +++ b/mm2src/coins/rpc_command/tendermint/ibc_withdraw.rs @@ -0,0 +1,27 @@ +use common::Future01CompatExt; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::MmError; +use mm2_number::BigDecimal; + +use crate::{lp_coinfind_or_err, MmCoinEnum, WithdrawError, WithdrawResult}; + +#[derive(Clone, Deserialize)] +pub struct IBCWithdrawRequest { + pub(crate) ibc_source_channel: String, + pub(crate) coin: String, + pub(crate) to: String, + #[serde(default)] + pub(crate) amount: BigDecimal, + #[serde(default)] + pub(crate) max: bool, + pub(crate) memo: Option, +} + +pub async fn ibc_withdraw(ctx: MmArc, req: IBCWithdrawRequest) -> WithdrawResult { + let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + match coin { + MmCoinEnum::Tendermint(coin) => coin.ibc_withdraw(req).compat().await, + MmCoinEnum::TendermintToken(token) => token.ibc_withdraw(req).compat().await, + _ => MmError::err(WithdrawError::ActionNotAllowed(req.coin)), + } +} diff --git a/mm2src/coins/rpc_command/tendermint/mod.rs b/mm2src/coins/rpc_command/tendermint/mod.rs new file mode 100644 index 0000000000..d8211abeac --- /dev/null +++ b/mm2src/coins/rpc_command/tendermint/mod.rs @@ -0,0 +1,14 @@ +mod ibc_chains; +mod ibc_transfer_channels; +mod ibc_withdraw; + +pub use ibc_chains::*; +pub use ibc_transfer_channels::*; +pub use ibc_withdraw::*; + +// Global constants for interacting with https://github.com/KomodoPlatform/chain-registry repository +// using `mm2_git` crate. +pub(crate) const CHAIN_REGISTRY_REPO_OWNER: &str = "KomodoPlatform"; +pub(crate) const CHAIN_REGISTRY_REPO_NAME: &str = "chain-registry"; +pub(crate) const CHAIN_REGISTRY_BRANCH: &str = "master"; +pub(crate) const CHAIN_REGISTRY_IBC_DIR_NAME: &str = "_IBC"; diff --git a/mm2src/coins/tendermint/ibc/ibc_proto.rs b/mm2src/coins/tendermint/ibc/ibc_proto.rs new file mode 100644 index 0000000000..ceccb128f8 --- /dev/null +++ b/mm2src/coins/tendermint/ibc/ibc_proto.rs @@ -0,0 +1,20 @@ +#[derive(prost::Message)] +pub(crate) struct IBCTransferV1Proto { + #[prost(string, tag = "1")] + pub(crate) source_port: prost::alloc::string::String, + #[prost(string, tag = "2")] + pub(crate) source_channel: prost::alloc::string::String, + #[prost(message, optional, tag = "3")] + pub(crate) token: Option, + #[prost(string, tag = "4")] + pub(crate) sender: prost::alloc::string::String, + #[prost(string, tag = "5")] + pub(crate) receiver: prost::alloc::string::String, + #[prost(message, optional, tag = "6")] + pub(crate) timeout_height: Option, + #[prost(uint64, tag = "7")] + pub(crate) timeout_timestamp: u64, + // Not supported by some of the cosmos chains like IRIS + // #[prost(string, optional, tag = "8")] + // pub(crate) memo: Option, +} diff --git a/mm2src/coins/tendermint/ibc/mod.rs b/mm2src/coins/tendermint/ibc/mod.rs new file mode 100644 index 0000000000..9e1c905398 --- /dev/null +++ b/mm2src/coins/tendermint/ibc/mod.rs @@ -0,0 +1,6 @@ +mod ibc_proto; +pub(crate) mod transfer_v1; + +pub(crate) const IBC_OUT_SOURCE_PORT: &str = "transfer"; +pub(crate) const IBC_OUT_TIMEOUT_IN_NANOS: u64 = 60000000000 * 15; // 15 minutes +pub(crate) const IBC_GAS_LIMIT_DEFAULT: u64 = 150_000; diff --git a/mm2src/coins/tendermint/ibc/transfer_v1.rs b/mm2src/coins/tendermint/ibc/transfer_v1.rs new file mode 100644 index 0000000000..34f693aaaa --- /dev/null +++ b/mm2src/coins/tendermint/ibc/transfer_v1.rs @@ -0,0 +1,108 @@ +use super::{ibc_proto::IBCTransferV1Proto, IBC_OUT_SOURCE_PORT, IBC_OUT_TIMEOUT_IN_NANOS}; +use crate::tendermint::type_urls::IBC_TRANSFER_TYPE_URL; +use common::number_type_casting::SafeTypeCastingNumbers; +use cosmrs::{tx::{Msg, MsgProto}, + AccountId, Coin, ErrorReport}; +use std::convert::TryFrom; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct MsgTransfer { + /// the port on which the packet will be sent + pub(crate) source_port: String, + /// the channel by which the packet will be sent + pub(crate) source_channel: String, + /// the tokens to be transferred + pub(crate) token: Coin, + /// the sender address + pub(crate) sender: AccountId, + /// the recipient address on the destination chain + pub(crate) receiver: AccountId, + /// Timeout height relative to the current block height. + /// The timeout is disabled when set to 0. + pub(crate) timeout_height: Option, + /// Timeout timestamp in absolute nanoseconds since unix epoch. + /// The timeout is disabled when set to 0. + pub(crate) timeout_timestamp: u64, + // Not supported by some of the cosmos chains like IRIS + // pub(crate) memo: Option, +} + +impl MsgTransfer { + pub(crate) fn new_with_default_timeout( + source_channel: String, + sender: AccountId, + receiver: AccountId, + token: Coin, + ) -> Self { + let timestamp_as_nanos: u64 = common::get_local_duration_since_epoch() + .expect("get_local_duration_since_epoch shouldn't fail") + .as_nanos() + .into_or_max(); + + Self { + source_port: IBC_OUT_SOURCE_PORT.to_owned(), + source_channel, + sender, + receiver, + token, + timeout_height: None, + timeout_timestamp: timestamp_as_nanos + IBC_OUT_TIMEOUT_IN_NANOS, + // memo: Some(memo.clone()), + } + } +} + +impl Msg for MsgTransfer { + type Proto = IBCTransferV1Proto; +} + +impl TryFrom for MsgTransfer { + type Error = ErrorReport; + + #[inline(always)] + fn try_from(proto: IBCTransferV1Proto) -> Result { MsgTransfer::try_from(&proto) } +} + +impl TryFrom<&IBCTransferV1Proto> for MsgTransfer { + type Error = ErrorReport; + + fn try_from(proto: &IBCTransferV1Proto) -> Result { + Ok(MsgTransfer { + source_port: proto.source_port.to_owned(), + source_channel: proto.source_channel.to_owned(), + token: proto + .token + .to_owned() + .map(TryFrom::try_from) + .ok_or_else(|| ErrorReport::msg("token can't be empty"))??, + sender: proto.sender.parse()?, + receiver: proto.receiver.parse()?, + timeout_height: None, + timeout_timestamp: proto.timeout_timestamp, + // memo: proto.memo.to_owned(), + }) + } +} + +impl From for IBCTransferV1Proto { + fn from(coin: MsgTransfer) -> IBCTransferV1Proto { IBCTransferV1Proto::from(&coin) } +} + +impl From<&MsgTransfer> for IBCTransferV1Proto { + fn from(msg: &MsgTransfer) -> IBCTransferV1Proto { + IBCTransferV1Proto { + source_port: msg.source_port.to_owned(), + source_channel: msg.source_channel.to_owned(), + token: Some(msg.token.to_owned().into()), + sender: msg.sender.to_string(), + receiver: msg.receiver.to_string(), + timeout_height: None, + timeout_timestamp: msg.timeout_timestamp, + // memo: msg.memo.to_owned(), + } + } +} + +impl MsgProto for IBCTransferV1Proto { + const TYPE_URL: &'static str = IBC_TRANSFER_TYPE_URL; +} diff --git a/mm2src/coins/tendermint/mod.rs b/mm2src/coins/tendermint/mod.rs index 6e904c4567..d480a4964e 100644 --- a/mm2src/coins/tendermint/mod.rs +++ b/mm2src/coins/tendermint/mod.rs @@ -2,6 +2,7 @@ // Useful resources // https://docs.cosmos.network/ +mod ibc; mod iris; mod rpc; mod tendermint_coin; @@ -25,6 +26,8 @@ pub(crate) const TENDERMINT_COIN_PROTOCOL_TYPE: &str = "TENDERMINT"; pub(crate) const TENDERMINT_ASSET_PROTOCOL_TYPE: &str = "TENDERMINTTOKEN"; pub(crate) mod type_urls { + pub(crate) const IBC_TRANSFER_TYPE_URL: &str = "/ibc.applications.transfer.v1.MsgTransfer"; + pub(crate) const CREATE_HTLC_TYPE_URL: &str = "/irismod.htlc.MsgCreateHTLC"; pub(crate) const CLAIM_HTLC_TYPE_URL: &str = "/irismod.htlc.MsgClaimHTLC"; } diff --git a/mm2src/coins/tendermint/rpc/mod.rs b/mm2src/coins/tendermint/rpc/mod.rs index bd34834ce9..26ccdf6553 100644 --- a/mm2src/coins/tendermint/rpc/mod.rs +++ b/mm2src/coins/tendermint/rpc/mod.rs @@ -1,9 +1,10 @@ #[cfg(not(target_arch = "wasm32"))] mod tendermint_native_rpc; #[cfg(not(target_arch = "wasm32"))] -pub use tendermint_native_rpc::*; +pub(crate) use tendermint_native_rpc::*; #[cfg(target_arch = "wasm32")] mod tendermint_wasm_rpc; -#[cfg(target_arch = "wasm32")] pub use tendermint_wasm_rpc::*; +#[cfg(target_arch = "wasm32")] +pub(crate) use tendermint_wasm_rpc::*; pub(crate) const TX_SUCCESS_CODE: u32 = 0; diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 1d09ee3b18..ac05c98565 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -1,8 +1,16 @@ +use super::ibc::transfer_v1::MsgTransfer; +use super::ibc::IBC_GAS_LIMIT_DEFAULT; use super::iris::htlc::{IrisHtlc, MsgClaimHtlc, MsgCreateHtlc, HTLC_STATE_COMPLETED, HTLC_STATE_OPEN, HTLC_STATE_REFUNDED}; use super::iris::htlc_proto::{CreateHtlcProtoRep, QueryHtlcRequestProto, QueryHtlcResponseProto}; use super::rpc::*; use crate::coin_errors::{MyAddressError, ValidatePaymentError}; +use crate::rpc_command::tendermint::{IBCChainRegistriesResponse, IBCChainRegistriesResult, IBCChainsRequestError, + IBCTransferChannel, IBCTransferChannelTag, IBCTransferChannelsRequest, + IBCTransferChannelsRequestError, IBCTransferChannelsResponse, + IBCTransferChannelsResult, IBCWithdrawRequest, CHAIN_REGISTRY_BRANCH, + CHAIN_REGISTRY_IBC_DIR_NAME, CHAIN_REGISTRY_REPO_NAME, CHAIN_REGISTRY_REPO_OWNER}; +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, @@ -49,6 +57,7 @@ use itertools::Itertools; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_git::{FileMetadata, GitController, GithubClient, RepositoryOperations, GITHUB_API_URI}; use mm2_number::MmNumber; use parking_lot::Mutex as PaMutex; use primitives::hash::H256; @@ -119,6 +128,7 @@ pub struct TendermintProtocolInfo { pub account_prefix: String, chain_id: String, gas_price: Option, + chain_registry_name: Option, } #[derive(Clone)] @@ -219,6 +229,7 @@ pub struct TendermintCoinImpl { pub(super) abortable_system: AbortableQueue, pub(crate) history_sync_state: Mutex, client: TendermintRpcClient, + chain_registry_name: Option, } #[derive(Clone)] @@ -501,9 +512,235 @@ impl TendermintCoin { abortable_system, history_sync_state: Mutex::new(history_sync_state), client: TendermintRpcClient(AsyncMutex::new(client_impl)), + chain_registry_name: protocol_info.chain_registry_name, }))) } + pub fn ibc_withdraw(&self, req: IBCWithdrawRequest) -> WithdrawFut { + let coin = self.clone(); + let fut = async move { + let to_address = + AccountId::from_str(&req.to).map_to_mm(|e| WithdrawError::InvalidAddress(e.to_string()))?; + + let (balance_denom, balance_dec) = coin + .get_balance_as_unsigned_and_decimal(&coin.denom, coin.decimals()) + .await?; + + // << BEGIN TX SIMULATION FOR FEE CALCULATION + let (amount_denom, amount_dec) = if req.max { + let amount_denom = balance_denom; + (amount_denom, big_decimal_from_sat_unsigned(amount_denom, coin.decimals)) + } else { + (sat_from_big_decimal(&req.amount, coin.decimals)?, req.amount.clone()) + }; + + if !coin.is_tx_amount_enough(coin.decimals, &amount_dec) { + return MmError::err(WithdrawError::AmountTooLow { + amount: amount_dec, + threshold: coin.min_tx_amount(), + }); + } + + let received_by_me = if to_address == coin.account_id { + amount_dec + } else { + BigDecimal::default() + }; + + let memo = req.memo.unwrap_or_else(|| TX_DEFAULT_MEMO.into()); + + let msg_transfer = MsgTransfer::new_with_default_timeout( + req.ibc_source_channel.clone(), + coin.account_id.clone(), + to_address.clone(), + Coin { + denom: coin.denom.clone(), + amount: amount_denom.into(), + }, + ) + .to_any() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let current_block = coin + .current_block() + .compat() + .await + .map_to_mm(WithdrawError::Transport)?; + + let _sequence_lock = coin.sequence_lock.lock().await; + let account_info = coin.my_account_info().await?; + + let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; + + let simulated_tx = coin + .gen_simulated_tx(account_info.clone(), msg_transfer.clone(), timeout_height, memo.clone()) + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + // >> END TX SIMULATION FOR FEE CALCULATION + + let (fee_amount_u64, fee_amount_dec) = coin + .calculate_fee_as_unsigned_and_decimal(simulated_tx, coin.decimals()) + .await?; + + let fee_amount = Coin { + denom: coin.denom.clone(), + amount: fee_amount_u64.into(), + }; + + let fee = Fee::from_amount_and_gas(fee_amount, IBC_GAS_LIMIT_DEFAULT); + + let (amount_denom, total_amount) = if req.max { + if balance_denom < fee_amount_u64 { + return MmError::err(WithdrawError::NotSufficientBalance { + coin: coin.ticker.clone(), + available: balance_dec, + required: fee_amount_dec, + }); + } + let amount_denom = balance_denom - fee_amount_u64; + (amount_denom, balance_dec) + } else { + let total = &req.amount + &fee_amount_dec; + if balance_dec < total { + return MmError::err(WithdrawError::NotSufficientBalance { + coin: coin.ticker.clone(), + available: balance_dec, + required: total, + }); + } + + (sat_from_big_decimal(&req.amount, coin.decimals)?, total) + }; + + let msg_transfer = MsgTransfer::new_with_default_timeout( + req.ibc_source_channel.clone(), + coin.account_id.clone(), + to_address.clone(), + Coin { + denom: coin.denom.clone(), + amount: amount_denom.into(), + }, + ) + .to_any() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let tx_raw = coin + .any_to_signed_raw_tx(account_info, msg_transfer, fee, timeout_height, memo.clone()) + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let tx_bytes = tx_raw + .to_bytes() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let hash = sha256(&tx_bytes); + Ok(TransactionDetails { + tx_hash: hex::encode_upper(hash.as_slice()), + tx_hex: tx_bytes.into(), + from: vec![coin.account_id.to_string()], + to: vec![req.to], + my_balance_change: &received_by_me - &total_amount, + spent_by_me: total_amount.clone(), + total_amount, + received_by_me, + block_height: 0, + timestamp: 0, + fee_details: Some(TxFeeDetails::Tendermint(TendermintFeeDetails { + coin: coin.ticker.clone(), + amount: fee_amount_dec, + uamount: fee_amount_u64, + gas_limit: IBC_GAS_LIMIT_DEFAULT, + })), + coin: coin.ticker.to_string(), + internal_id: hash.to_vec().into(), + kmd_rewards: None, + transaction_type: TransactionType::default(), + memo: Some(memo), + }) + }; + Box::new(fut.boxed().compat()) + } + + pub async fn get_ibc_transfer_channels(&self, req: IBCTransferChannelsRequest) -> IBCTransferChannelsResult { + #[derive(Deserialize)] + struct ChainRegistry { + channels: Vec, + } + + #[derive(Deserialize)] + struct ChannelInfo { + channel_id: String, + port_id: String, + } + + #[derive(Deserialize)] + struct IbcChannel { + chain_1: ChannelInfo, + #[allow(dead_code)] + chain_2: ChannelInfo, + ordering: String, + version: String, + tags: Option, + } + + let src_chain_registry_name = self.chain_registry_name.as_ref().or_mm_err(|| { + IBCTransferChannelsRequestError::InternalError(format!( + "`chain_registry_name` is not set for '{}'", + self.platform_ticker() + )) + })?; + + let source_filename = format!( + "{}-{}.json", + src_chain_registry_name, req.destination_chain_registry_name + ); + + let git_controller: GitController = GitController::new(GITHUB_API_URI); + + let metadata_list = git_controller + .client + .get_file_metadata_list( + CHAIN_REGISTRY_REPO_OWNER, + CHAIN_REGISTRY_REPO_NAME, + CHAIN_REGISTRY_BRANCH, + CHAIN_REGISTRY_IBC_DIR_NAME, + ) + .await + .map_err(|e| IBCTransferChannelsRequestError::Transport(format!("{:?}", e)))?; + + let source_channel_file = metadata_list + .iter() + .find(|metadata| metadata.name == source_filename) + .or_mm_err(|| IBCTransferChannelsRequestError::RegistrySourceCouldNotFound(source_filename))?; + + let mut registry_object = git_controller + .client + .deserialize_json_source::(source_channel_file.to_owned()) + .await + .map_err(|e| IBCTransferChannelsRequestError::Transport(format!("{:?}", e)))?; + + registry_object + .channels + .retain(|ch| ch.chain_1.port_id == *IBC_OUT_SOURCE_PORT); + + let result: Vec = registry_object + .channels + .iter() + .map(|ch| IBCTransferChannel { + channel_id: ch.chain_1.channel_id.clone(), + ordering: ch.ordering.clone(), + version: ch.version.clone(), + tags: ch.tags.clone().map(|t| IBCTransferChannelTag { + status: t.status, + preferred: t.preferred, + dex: t.dex, + }), + }) + .collect(); + + Ok(IBCTransferChannelsResponse { + ibc_transfer_channels: result, + }) + } + #[inline(always)] fn gas_price(&self) -> f64 { self.gas_price.unwrap_or(DEFAULT_GAS_PRICE) } @@ -576,7 +813,7 @@ impl TendermintCoin { amount: Vec, secret_hash: &[u8], ) -> String { - // Needs to be sorted if cointains multiple coins + // Needs to be sorted if contains multiple coins // let mut amount = amount; // amount.sort(); @@ -1250,6 +1487,28 @@ impl TendermintCoin { }) } + pub(super) async fn calculate_fee_as_unsigned_and_decimal( + &self, + tx: Vec, + decimals: u8, + ) -> MmResult<(u64, BigDecimal), TendermintCoinRpcError> { + let fee_amount_u64 = self.calculate_fee_amount_as_u64(tx).await?; + let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, decimals); + + Ok((fee_amount_u64, fee_amount_dec)) + } + + pub(super) async fn get_balance_as_unsigned_and_decimal( + &self, + denom: &Denom, + decimals: u8, + ) -> MmResult<(u64, BigDecimal), TendermintCoinRpcError> { + let denom_ubalance = self.balance_for_denom(denom.to_string()).await?; + let denom_balance_dec = big_decimal_from_sat_unsigned(denom_ubalance, decimals); + + Ok((denom_ubalance, denom_balance_dec)) + } + async fn request_tx(&self, hash: String) -> MmResult { let path = AbciPath::from_str(ABCI_GET_TX_PATH).expect("valid path"); let request = GetTxRequest { hash }; @@ -1417,6 +1676,46 @@ fn clients_from_urls(rpc_urls: &[String]) -> MmResult, Tendermin Ok(clients) } +pub async fn get_ibc_chain_list() -> IBCChainRegistriesResult { + fn map_metadata_to_chain_registry_name(metadata: &FileMetadata) -> Result> { + let split_filename_by_dash: Vec<&str> = metadata.name.split('-').collect(); + let chain_registry_name = split_filename_by_dash + .first() + .or_mm_err(|| { + IBCChainsRequestError::InternalError(format!( + "Could not read chain registry name from '{}'", + metadata.name + )) + })? + .to_string(); + + Ok(chain_registry_name) + } + + let git_controller: GitController = GitController::new(GITHUB_API_URI); + + let metadata_list = git_controller + .client + .get_file_metadata_list( + CHAIN_REGISTRY_REPO_OWNER, + CHAIN_REGISTRY_REPO_NAME, + CHAIN_REGISTRY_BRANCH, + CHAIN_REGISTRY_IBC_DIR_NAME, + ) + .await + .map_err(|e| IBCChainsRequestError::Transport(format!("{:?}", e)))?; + + let chain_list: Result, MmError> = + metadata_list.iter().map(map_metadata_to_chain_registry_name).collect(); + + let mut distinct_chain_list = chain_list?; + distinct_chain_list.dedup(); + + Ok(IBCChainRegistriesResponse { + chain_registry_list: distinct_chain_list, + }) +} + #[async_trait] #[allow(unused_variables)] impl MmCoin for TendermintCoin { @@ -1435,25 +1734,19 @@ impl MmCoin for TendermintCoin { coin.account_prefix ))); } - let balance_denom = coin.balance_for_denom(coin.denom.to_string()).await?; - let balance_dec = big_decimal_from_sat_unsigned(balance_denom, coin.decimals); + + let (balance_denom, balance_dec) = coin + .get_balance_as_unsigned_and_decimal(&coin.denom, coin.decimals()) + .await?; // << BEGIN TX SIMULATION FOR FEE CALCULATION - let (amount_denom, amount_dec, total_amount) = if req.max { + let (amount_denom, amount_dec) = if req.max { let amount_denom = balance_denom; - ( - amount_denom, - big_decimal_from_sat_unsigned(amount_denom, coin.decimals), - balance_dec.clone(), - ) + (amount_denom, big_decimal_from_sat_unsigned(amount_denom, coin.decimals)) } else { let total = req.amount.clone(); - ( - sat_from_big_decimal(&req.amount, coin.decimals)?, - req.amount.clone(), - total, - ) + (sat_from_big_decimal(&req.amount, coin.decimals)?, req.amount.clone()) }; if !coin.is_tx_amount_enough(coin.decimals, &amount_dec) { @@ -1497,8 +1790,9 @@ impl MmCoin for TendermintCoin { .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; // >> END TX SIMULATION FOR FEE CALCULATION - let fee_amount_u64 = coin.calculate_fee_amount_as_u64(simulated_tx).await?; - let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, coin.decimals()); + let (fee_amount_u64, fee_amount_dec) = coin + .calculate_fee_as_unsigned_and_decimal(simulated_tx, coin.decimals()) + .await?; let fee_amount = Coin { denom: coin.denom.clone(), @@ -1507,7 +1801,7 @@ impl MmCoin for TendermintCoin { let fee = Fee::from_amount_and_gas(fee_amount, GAS_LIMIT_DEFAULT); - let (amount_denom, amount_dec, total_amount) = if req.max { + let (amount_denom, total_amount) = if req.max { if balance_denom < fee_amount_u64 { return MmError::err(WithdrawError::NotSufficientBalance { coin: coin.ticker.clone(), @@ -1516,11 +1810,7 @@ impl MmCoin for TendermintCoin { }); } let amount_denom = balance_denom - fee_amount_u64; - ( - amount_denom, - big_decimal_from_sat_unsigned(amount_denom, coin.decimals), - balance_dec, - ) + (amount_denom, balance_dec) } else { let total = &req.amount + &fee_amount_dec; if balance_dec < total { @@ -1531,11 +1821,7 @@ impl MmCoin for TendermintCoin { }); } - ( - sat_from_big_decimal(&req.amount, coin.decimals)?, - req.amount.clone(), - total, - ) + (sat_from_big_decimal(&req.amount, coin.decimals)?, total) }; let msg_send = MsgSend { @@ -2337,6 +2623,7 @@ pub mod tendermint_coin_tests { account_prefix: String::from("iaa"), chain_id: String::from("nyancat-9"), gas_price: None, + chain_registry_name: None, } } @@ -2347,6 +2634,7 @@ pub mod tendermint_coin_tests { account_prefix: String::from("iaa"), chain_id: String::from("nyancat-9"), gas_price: None, + chain_registry_name: None, } } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 777df3ac67..60deb075b7 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -1,6 +1,9 @@ +use super::ibc::transfer_v1::MsgTransfer; +use super::ibc::IBC_GAS_LIMIT_DEFAULT; /// Module containing implementation for Tendermint Tokens. They include native assets + IBC use super::{TendermintCoin, TendermintFeeDetails, GAS_LIMIT_DEFAULT, MIN_TX_SATOSHIS, TIMEOUT_HEIGHT_DELTA, TX_DEFAULT_MEMO}; +use crate::rpc_command::tendermint::IBCWithdrawRequest; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFut, BigDecimal, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, @@ -96,6 +99,140 @@ impl TendermintToken { }; Ok(TendermintToken(Arc::new(token_impl))) } + + pub fn ibc_withdraw(&self, req: IBCWithdrawRequest) -> WithdrawFut { + let platform = self.platform_coin.clone(); + let token = self.clone(); + let fut = async move { + let to_address = + AccountId::from_str(&req.to).map_to_mm(|e| WithdrawError::InvalidAddress(e.to_string()))?; + + let (base_denom_balance, base_denom_balance_dec) = platform + .get_balance_as_unsigned_and_decimal(&platform.denom, token.decimals()) + .await?; + + let (balance_denom, balance_dec) = platform + .get_balance_as_unsigned_and_decimal(&token.denom, token.decimals()) + .await?; + + let (amount_denom, amount_dec, total_amount) = if req.max { + ( + balance_denom, + big_decimal_from_sat_unsigned(balance_denom, token.decimals), + balance_dec, + ) + } else { + if balance_dec < req.amount { + return MmError::err(WithdrawError::NotSufficientBalance { + coin: token.ticker.clone(), + available: balance_dec, + required: req.amount, + }); + } + + ( + sat_from_big_decimal(&req.amount, token.decimals())?, + req.amount.clone(), + req.amount, + ) + }; + + if !platform.is_tx_amount_enough(token.decimals, &amount_dec) { + return MmError::err(WithdrawError::AmountTooLow { + amount: amount_dec, + threshold: token.min_tx_amount(), + }); + } + + let received_by_me = if to_address == platform.account_id { + amount_dec + } else { + BigDecimal::default() + }; + + let memo = req.memo.unwrap_or_else(|| TX_DEFAULT_MEMO.into()); + + let msg_transfer = MsgTransfer::new_with_default_timeout( + req.ibc_source_channel.clone(), + platform.account_id.clone(), + to_address.clone(), + Coin { + denom: token.denom.clone(), + amount: amount_denom.into(), + }, + ) + .to_any() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let current_block = token + .current_block() + .compat() + .await + .map_to_mm(WithdrawError::Transport)?; + + let _sequence_lock = platform.sequence_lock.lock().await; + let account_info = platform.my_account_info().await?; + + let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; + + let simulated_tx = platform + .gen_simulated_tx(account_info.clone(), msg_transfer.clone(), timeout_height, memo.clone()) + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let (fee_amount_u64, fee_amount_dec) = platform + .calculate_fee_as_unsigned_and_decimal(simulated_tx, platform.decimals()) + .await?; + + if base_denom_balance < fee_amount_u64 { + return MmError::err(WithdrawError::NotSufficientPlatformBalanceForFee { + coin: platform.ticker().to_string(), + available: base_denom_balance_dec, + required: fee_amount_dec, + }); + } + + let fee_amount = Coin { + denom: platform.denom.clone(), + amount: fee_amount_u64.into(), + }; + + let fee = Fee::from_amount_and_gas(fee_amount, IBC_GAS_LIMIT_DEFAULT); + + let tx_raw = platform + .any_to_signed_raw_tx(account_info, msg_transfer, fee, timeout_height, memo.clone()) + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let tx_bytes = tx_raw + .to_bytes() + .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + + let hash = sha256(&tx_bytes); + Ok(TransactionDetails { + tx_hash: hex::encode_upper(hash.as_slice()), + tx_hex: tx_bytes.into(), + from: vec![platform.account_id.to_string()], + to: vec![req.to], + my_balance_change: &received_by_me - &total_amount, + spent_by_me: total_amount.clone(), + total_amount, + received_by_me, + block_height: 0, + timestamp: 0, + fee_details: Some(TxFeeDetails::Tendermint(TendermintFeeDetails { + coin: platform.ticker().to_string(), + amount: fee_amount_dec, + uamount: fee_amount_u64, + gas_limit: IBC_GAS_LIMIT_DEFAULT, + })), + coin: token.ticker.clone(), + internal_id: hash.to_vec().into(), + kmd_rewards: None, + transaction_type: TransactionType::default(), + memo: Some(memo), + }) + }; + Box::new(fut.boxed().compat()) + } } #[async_trait] @@ -453,11 +590,13 @@ impl MmCoin for TendermintToken { ))); } - let base_denom_balance = platform.balance_for_denom(platform.denom.to_string()).await?; - let base_denom_balance_dec = big_decimal_from_sat_unsigned(base_denom_balance, token.decimals()); + let (base_denom_balance, base_denom_balance_dec) = platform + .get_balance_as_unsigned_and_decimal(&platform.denom, token.decimals()) + .await?; - let balance_denom = platform.balance_for_denom(token.denom.to_string()).await?; - let balance_dec = big_decimal_from_sat_unsigned(balance_denom, token.decimals()); + let (balance_denom, balance_dec) = platform + .get_balance_as_unsigned_and_decimal(&token.denom, token.decimals()) + .await?; let (amount_denom, amount_dec, total_amount) = if req.max { ( @@ -521,8 +660,9 @@ impl MmCoin for TendermintToken { .gen_simulated_tx(account_info.clone(), msg_send.clone(), timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let fee_amount_u64 = platform.calculate_fee_amount_as_u64(simulated_tx).await?; - let fee_amount_dec = big_decimal_from_sat_unsigned(fee_amount_u64, platform.decimals()); + let (fee_amount_u64, fee_amount_dec) = platform + .calculate_fee_as_unsigned_and_decimal(simulated_tx, platform.decimals()) + .await?; if base_denom_balance < fee_amount_u64 { return MmError::err(WithdrawError::NotSufficientPlatformBalanceForFee { diff --git a/mm2src/mm2_git/Cargo.toml b/mm2src/mm2_git/Cargo.toml new file mode 100644 index 0000000000..ee06101400 --- /dev/null +++ b/mm2src/mm2_git/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mm2_git" +version = "0.1.0" +edition = "2021" + +[lib] +doctest = false + +[dependencies] +async-trait = "0.1" +common = { path = "../common" } +http = "0.2" +mm2_err_handle = { path = "../mm2_err_handle" } +mm2_net = { path = "../mm2_net" } +serde = "1" +serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } diff --git a/mm2src/mm2_git/src/github_client.rs b/mm2src/mm2_git/src/github_client.rs new file mode 100644 index 0000000000..e13f97ba18 --- /dev/null +++ b/mm2src/mm2_git/src/github_client.rs @@ -0,0 +1,132 @@ +use async_trait::async_trait; +use mm2_err_handle::prelude::MmError; +use mm2_net::transport::slurp_url_with_headers; +use serde::de::DeserializeOwned; + +use crate::{FileMetadata, GitCommons, GitControllerError, RepositoryOperations}; + +const GITHUB_CLIENT_USER_AGENT: &str = "mm2"; + +pub struct GithubClient { + api_address: String, +} + +impl GitCommons for GithubClient { + fn new(api_address: String) -> Self { Self { api_address } } +} + +#[async_trait] +impl RepositoryOperations for GithubClient { + async fn deserialize_json_source( + &self, + file_metadata: FileMetadata, + ) -> Result> { + let (_status_code, _headers, data_buffer) = slurp_url_with_headers(&file_metadata.download_url, vec![( + http::header::USER_AGENT.as_str(), + GITHUB_CLIENT_USER_AGENT, + )]) + .await + .map_err(|e| GitControllerError::HttpError(e.to_string()))?; + + Ok( + serde_json::from_slice(&data_buffer) + .map_err(|e| GitControllerError::DeserializationError(e.to_string()))?, + ) + } + + async fn get_file_metadata_list( + &self, + owner: &str, + repository_name: &str, + branch: &str, + dir: &str, + ) -> Result, MmError> { + let uri = format!( + "{}/repos/{}/{}/contents/{}?ref={}", + &self.api_address, owner, repository_name, dir, branch + ); + + let (_status_code, _headers, data_buffer) = slurp_url_with_headers(&uri, vec![( + http::header::USER_AGENT.as_str(), + GITHUB_CLIENT_USER_AGENT, + )]) + .await + .map_err(|e| GitControllerError::HttpError(e.to_string()))?; + + Ok( + serde_json::from_slice(&data_buffer) + .map_err(|e| GitControllerError::DeserializationError(e.to_string()))?, + ) + } +} + +#[cfg(test)] +#[allow(unused)] +mod tests { + use crate::{GitController, GITHUB_API_URI}; + + use super::*; + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + struct ChainRegistry { + chain_1: ChainInfo, + chain_2: ChainInfo, + channels: Vec, + } + + #[derive(Debug, Deserialize)] + struct IbcChannel { + chain_1: ChannelInfo, + chain_2: ChannelInfo, + ordering: String, + version: String, + tags: Option, + } + + #[derive(Debug, Deserialize)] + struct ChainInfo { + chain_name: String, + client_id: String, + connection_id: String, + } + + #[derive(Debug, Deserialize)] + struct ChannelInfo { + channel_id: String, + port_id: String, + } + + #[derive(Debug, Deserialize)] + struct ChannelTag { + status: String, + preferred: bool, + dex: Option, + } + + #[test] + fn test_metadata_list_and_json_deserialization() { + const REPO_OWNER: &str = "KomodoPlatform"; + const REPO_NAME: &str = "chain-registry"; + const BRANCH: &str = "master"; + const DIR_NAME: &str = "_IBC"; + + let git_controller: GitController = GitController::new(GITHUB_API_URI); + + let metadata_list = common::block_on( + git_controller + .client + .get_file_metadata_list(REPO_OWNER, REPO_NAME, BRANCH, DIR_NAME), + ) + .unwrap(); + + assert!(!metadata_list.is_empty()); + + common::block_on( + git_controller + .client + .deserialize_json_source::(metadata_list.first().unwrap().clone()), + ) + .unwrap(); + } +} diff --git a/mm2src/mm2_git/src/lib.rs b/mm2src/mm2_git/src/lib.rs new file mode 100644 index 0000000000..15bc8408f1 --- /dev/null +++ b/mm2src/mm2_git/src/lib.rs @@ -0,0 +1,59 @@ +//! This crate provides an abstraction layer on Git for doing query/parse +//! operations over the repositories. +//! +//! Implementation of generic `GitController` provides the flexibility of +//! adding any Git clients(like Gitlab, Bitbucket, etc) when needed. + +use async_trait::async_trait; +use mm2_err_handle::prelude::MmError; +use serde::{de::DeserializeOwned, Deserialize}; + +pub mod github_client; +pub use github_client::*; + +pub const GITHUB_API_URI: &str = "https://api.github.com"; + +#[derive(Clone, Debug, Deserialize)] +pub struct FileMetadata { + pub name: String, + pub download_url: String, + pub size: usize, +} + +pub trait GitCommons { + fn new(api_address: String) -> Self; +} + +#[async_trait] +pub trait RepositoryOperations { + async fn deserialize_json_source( + &self, + file_metadata: FileMetadata, + ) -> Result>; + + async fn get_file_metadata_list( + &self, + owner: &str, + repository_name: &str, + branch: &str, + dir: &str, + ) -> Result, MmError>; +} + +pub struct GitController { + pub client: T, +} + +impl GitController { + pub fn new(api_address: &str) -> Self { + Self { + client: T::new(api_address.to_owned()), + } + } +} + +#[derive(Debug)] +pub enum GitControllerError { + DeserializationError(String), + HttpError(String), +} diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index ec51a48f15..6ecefa4138 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -12,6 +12,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; #[cfg(feature = "enable-nft-integration")] use coins::nft; +use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels, ibc_withdraw}; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, get_enabled_coins::get_enabled_coins, @@ -191,6 +192,9 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, + "ibc_withdraw" => handle_mmrpc(ctx, request, ibc_withdraw).await, + "ibc_chains" => handle_mmrpc(ctx, request, ibc_chains).await, + "ibc_transfer_channels" => handle_mmrpc(ctx, request, ibc_transfer_channels).await, #[cfg(feature = "enable-nft-integration")] "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, #[cfg(not(target_arch = "wasm32"))] diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 796162de48..51db8dd236 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -1,12 +1,13 @@ use common::block_on; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::{atom_testnet_conf, disable_coin, disable_coin_err, enable_tendermint, - enable_tendermint_token, get_tendermint_my_tx_history, iris_nimda_testnet_conf, - iris_testnet_conf, my_balance, send_raw_transaction, withdraw_v1, MarketMakerIt, - Mm2TestConf}; + enable_tendermint_token, get_tendermint_my_tx_history, ibc_withdraw, + iris_nimda_testnet_conf, 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}; +const IRIS_TEST_SEED: &str = "iris test seed"; const ATOM_TEST_BALANCE_SEED: &str = "atom test seed"; const ATOM_TEST_WITHDRAW_SEED: &str = "atom test withdraw seed"; const ATOM_TICKER: &str = "ATOM"; @@ -112,14 +113,45 @@ fn test_tendermint_withdraw() { println!("Send raw tx {}", json::to_string(&send_raw_tx).unwrap()); } +#[test] +fn test_tendermint_ibc_withdraw() { + const IBC_SOURCE_CHANNEL: &str = "channel-81"; + const IBC_TARGET_ADDRESS: &str = "cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl"; + const MY_ADDRESS: &str = "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2"; + + let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); + let platform_coin = coins[0]["coin"].as_str().unwrap(); + let token = coins[1]["coin"].as_str().unwrap(); + + let conf = Mm2TestConf::seednode(IRIS_TEST_SEED, &coins); + 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()); + + let activation_res = block_on(enable_tendermint_token(&mm, token)); + println!("Token activation {}", json::to_string(&activation_res).unwrap()); + + let tx_details = block_on(ibc_withdraw(&mm, IBC_SOURCE_CHANNEL, token, IBC_TARGET_ADDRESS, "0.1")); + println!("IBC transfer to atom address {}", json::to_string(&tx_details).unwrap()); + + let expected_spent: BigDecimal = "0.1".parse().unwrap(); + assert_eq!(tx_details.spent_by_me, expected_spent); + + assert_eq!(tx_details.to, vec![IBC_TARGET_ADDRESS.to_owned()]); + assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); + + 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()); +} + #[test] fn test_tendermint_token_activation_and_withdraw() { - const TEST_SEED: &str = "iris test seed"; let coins = json!([iris_testnet_conf(), iris_nimda_testnet_conf()]); let platform_coin = coins[0]["coin"].as_str().unwrap(); let token = coins[1]["coin"].as_str().unwrap(); - let conf = Mm2TestConf::seednode(TEST_SEED, &coins); + let conf = Mm2TestConf::seednode(IRIS_TEST_SEED, &coins); 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)); @@ -277,13 +309,7 @@ fn test_disable_tendermint_platform_coin_with_token() { let conf = Mm2TestConf::seednode(TEST_SEED, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); // Enable platform coin IRIS-TEST - let activation_res = block_on(enable_tendermint( - &mm, - platform_coin, - &[], - &["http://34.80.202.172:26657"], - false, - )); + let activation_res = block_on(enable_tendermint(&mm, platform_coin, &[], IRIS_TESTNET_RPC_URLS, false)); assert!(&activation_res.get("result").unwrap().get("address").is_some()); // Enable platform coin token IRIS-NIMDA diff --git a/mm2src/mm2_net/src/native_http.rs b/mm2src/mm2_net/src/native_http.rs index 641a0cae24..700bdb1efa 100644 --- a/mm2src/mm2_net/src/native_http.rs +++ b/mm2src/mm2_net/src/native_http.rs @@ -2,7 +2,7 @@ use crate::transport::{SlurpError, SlurpResult, SlurpResultJson}; use common::wio::{drive03, HYPER}; use common::APPLICATION_JSON; use futures::channel::oneshot::Canceled; -use http::{header, Request}; +use http::{header, HeaderValue, Request}; use hyper::Body; use mm2_err_handle::prelude::*; use serde_json::Value as Json; @@ -75,6 +75,22 @@ pub async fn slurp_url(url: &str) -> SlurpResult { slurp_req(req).await } +/// Executes a GET request with additional headers. +/// Returning the response status, headers and body. +pub async fn slurp_url_with_headers(url: &str, headers: Vec<(&'static str, &'static str)>) -> SlurpResult { + let mut req = Request::builder(); + let h = req + .headers_mut() + .or_mm_err(|| SlurpError::Internal("An error occured while accessing to the request headers.".to_string()))?; + + for (key, value) in headers { + h.insert(key, HeaderValue::from_static(value)); + } + + let req = req.uri(url).body(Vec::new())?; + slurp_req(req).await +} + /// Executes a POST request, returning the response status, headers and body. pub async fn slurp_post_json(url: &str, body: String) -> SlurpResult { let request = Request::builder() diff --git a/mm2src/mm2_net/src/transport.rs b/mm2src/mm2_net/src/transport.rs index 18e4f4feb6..2ba04b65e1 100644 --- a/mm2src/mm2_net/src/transport.rs +++ b/mm2src/mm2_net/src/transport.rs @@ -7,10 +7,10 @@ use serde::{Deserialize, Serialize}; use serde_json::{Error, Value as Json}; #[cfg(not(target_arch = "wasm32"))] -pub use crate::native_http::{slurp_post_json, slurp_req, slurp_req_body, slurp_url}; +pub use crate::native_http::{slurp_post_json, slurp_req, slurp_req_body, slurp_url, slurp_url_with_headers}; #[cfg(target_arch = "wasm32")] -pub use crate::wasm_http::{slurp_post_json, slurp_url}; +pub use crate::wasm_http::{slurp_post_json, slurp_url, slurp_url_with_headers}; pub type SlurpResult = Result<(StatusCode, HeaderMap, Vec), MmError>; diff --git a/mm2src/mm2_net/src/wasm_http.rs b/mm2src/mm2_net/src/wasm_http.rs index afa24d1848..3767c2f40c 100644 --- a/mm2src/mm2_net/src/wasm_http.rs +++ b/mm2src/mm2_net/src/wasm_http.rs @@ -24,6 +24,17 @@ pub async fn slurp_url(url: &str) -> SlurpResult { .map(|(status_code, response)| (status_code, HeaderMap::new(), response.into_bytes())) } +/// Executes a GET request with additional headers. +/// Returning the response status, headers and body. +/// Please note the return header map is empty, because `wasm_bindgen` doesn't provide the way to extract all headers. +pub async fn slurp_url_with_headers(url: &str, headers: Vec<(&str, &str)>) -> SlurpResult { + FetchRequest::get(url) + .headers(headers) + .request_str() + .await + .map(|(status_code, response)| (status_code, HeaderMap::new(), response.into_bytes())) +} + /// Executes a POST request, returning the response status, headers and body. /// Please note the return header map is empty, because `wasm_bindgen` doesn't provide the way to extract all headers. pub async fn slurp_post_json(url: &str, body: String) -> SlurpResult { @@ -86,6 +97,13 @@ impl FetchRequest { self } + pub fn headers(mut self, headers: Vec<(&str, &str)>) -> FetchRequest { + for (key, value) in headers { + self.headers.insert(key.to_owned(), value.to_owned()); + } + self + } + pub async fn request_str(self) -> FetchResult { let (tx, rx) = oneshot::channel(); Self::spawn_fetch_str(self, tx); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 74d0bd1fb8..841b62e755 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -2207,6 +2207,33 @@ pub async fn withdraw_v1(mm: &MarketMakerIt, coin: &str, to: &str, amount: &str) json::from_str(&request.1).unwrap() } +pub async fn ibc_withdraw( + mm: &MarketMakerIt, + source_channel: &str, + coin: &str, + to: &str, + amount: &str, +) -> TransactionDetails { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "ibc_withdraw", + "mmrpc": "2.0", + "params": { + "ibc_source_channel": source_channel, + "coin": coin, + "to": to, + "amount": amount + } + })) + .await + .unwrap(); + assert_eq!(request.0, StatusCode::OK, "'ibc_withdraw' failed: {}", request.1); + + let json: Json = json::from_str(&request.1).unwrap(); + json::from_value(json["result"].clone()).unwrap() +} + pub async fn withdraw_status(mm: &MarketMakerIt, task_id: u64) -> Json { let request = mm .rpc(&json!({ From a3b3cbafbcdbd54819ea97d508500717d0b0058e Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Thu, 9 Mar 2023 18:13:10 +0300 Subject: [PATCH 26/79] initialize build workflow Signed-off-by: ozkanonur --- .github/workflows/build.yml | 37 ++++++++++++++++++++++++++++++ .github/workflows/fmt-and-lint.yml | 4 ++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..d9d5b2ef09 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +name: CI +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + x86-build: + timeout-minutes: 30 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: x + run: | + echo "dbg" + + - name: Step for Mac + if: runner.os == 'macOS' + run: cargo build --bin mm2 --profile ci --target x86_64-apple-darwin + + - name: Step for Linux & Win + if: runner.os != 'macOS' + run: cargo build --bin mm2 --profile ci diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index d7ee3cf52f..e15a1c45ef 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -23,7 +23,7 @@ jobs: run: | rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy rustup default ${{ matrix.rust }} - + - name: fmt check run: cargo fmt -- --check @@ -44,6 +44,6 @@ jobs: rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy rustup default ${{ matrix.rust }} rustup target add wasm32-unknown-unknown - + - name: wasm code lint run: cargo clippy --target wasm32-unknown-unknown --profile ci -- --D warnings From 8afe44cd99a7d22eed2d89b628cf9064a744d447 Mon Sep 17 00:00:00 2001 From: smk762 Date: Thu, 9 Mar 2023 23:38:23 +0800 Subject: [PATCH 27/79] update pirate & zombie domains --- mm2src/mm2_test_helpers/src/for_tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 841b62e755..fed7f480a9 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -124,10 +124,10 @@ pub const MORTY_ELECTRUM_ADDRS: &[&str] = &[ ]; pub const ZOMBIE_TICKER: &str = "ZOMBIE"; pub const ARRR: &str = "ARRR"; -pub const ZOMBIE_ELECTRUMS: &[&str] = &["zombie.sirseven.me:10033"]; -pub const ZOMBIE_LIGHTWALLETD_URLS: &[&str] = &["http://zombie.sirseven.me:443"]; -pub const PIRATE_ELECTRUMS: &[&str] = &["pirate.sirseven.me:10032"]; -pub const PIRATE_LIGHTWALLETD_URLS: &[&str] = &["http://pirate.sirseven.me:443"]; +pub const ZOMBIE_ELECTRUMS: &[&str] = &["zombie.dragonhound.info:10033"]; +pub const ZOMBIE_LIGHTWALLETD_URLS: &[&str] = &["http://zombie.dragonhound.info:443"]; +pub const PIRATE_ELECTRUMS: &[&str] = &["pirate.dragonhound.info:10032"]; +pub const PIRATE_LIGHTWALLETD_URLS: &[&str] = &["http://pirate.dragonhound.info:443"]; pub const DEFAULT_RPC_PASSWORD: &str = "pass"; pub const QRC20_ELECTRUMS: &[&str] = &[ "electrum1.cipig.net:10071", From 11091bff8a666bf34150579b69c118730e6347ca Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Thu, 9 Mar 2023 18:48:05 +0300 Subject: [PATCH 28/79] increase fmt & lint CI timeout Signed-off-by: ozkanonur --- .github/workflows/fmt-and-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index e15a1c45ef..41b0f1b9b1 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -11,7 +11,7 @@ on: jobs: fmt-and-x86-lint: - timeout-minutes: 20 + timeout-minutes: 25 runs-on: ${{ matrix.os }} strategy: matrix: @@ -31,7 +31,7 @@ jobs: run: cargo clippy --profile ci -- --D warnings wasm-lint: - timeout-minutes: 20 + timeout-minutes: 25 runs-on: ${{ matrix.os }} strategy: matrix: From da582578441b4fe94269200f8af20997964f7055 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Thu, 9 Mar 2023 21:02:40 +0300 Subject: [PATCH 29/79] fix module doc positioning Signed-off-by: ozkanonur --- mm2src/coins/tendermint/tendermint_token.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 33ae7b852d..906960f7ba 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -1,6 +1,7 @@ +//! Module containing implementation for Tendermint Tokens. They include native assets + IBC + use super::ibc::transfer_v1::MsgTransfer; use super::ibc::IBC_GAS_LIMIT_DEFAULT; -/// Module containing implementation for Tendermint Tokens. They include native assets + IBC use super::{TendermintCoin, TendermintFeeDetails, GAS_LIMIT_DEFAULT, MIN_TX_SATOSHIS, TIMEOUT_HEIGHT_DELTA, TX_DEFAULT_MEMO}; use crate::rpc_command::tendermint::IBCWithdrawRequest; From 365c7dfb0819e711c0ba76293aa542ba8cb907f2 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Thu, 9 Mar 2023 21:04:11 +0300 Subject: [PATCH 30/79] decrease swap amount for `iris_swap` Signed-off-by: ozkanonur --- mm2src/mm2_main/tests/mm2_tests/iris_swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs index 2096b98d07..1a47e5ce63 100644 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs @@ -23,7 +23,7 @@ fn start_swap_operation() { ("IRIS-NIMDA", "RICK"), ("USDC-IBC-IRIS", "tBNB"), ]; - block_on(trade_base_rel_iris(&pairs, 1, 2, 0.01)); + block_on(trade_base_rel_iris(&pairs, 1, 2, 0.008)); } pub async fn trade_base_rel_iris( From e253d071d9f226cb1bf93ac27f632a4a5b8bfd88 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Thu, 9 Mar 2023 21:33:40 +0300 Subject: [PATCH 31/79] improve withdraw functions Signed-off-by: ozkanonur --- mm2src/coins/tendermint/tendermint_coin.rs | 12 +++++------- mm2src/coins/tendermint/tendermint_token.rs | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 00cf709145..614a6b1352 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -570,7 +570,6 @@ impl TendermintCoin { .await .map_to_mm(WithdrawError::Transport)?; - let account_info = coin.my_account_info().await?; let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; // >> END TX SIMULATION FOR FEE CALCULATION @@ -621,6 +620,7 @@ impl TendermintCoin { .to_any() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + let account_info = coin.my_account_info().await?; let tx_raw = coin .any_to_signed_raw_tx(account_info, msg_transfer, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -876,10 +876,9 @@ impl TendermintCoin { .gen_simulated_tx(account_info, msg.clone(), timeout_height, memo.clone()) .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; - let request = SimulateRequest { tx_bytes, tx: None }; let request = AbciRequest::new( Some(path.clone()), - request.encode_to_vec().clone(), + SimulateRequest { tx_bytes, tx: None }.encode_to_vec(), ABCI_REQUEST_HEIGHT, ABCI_REQUEST_PROVE, ); @@ -938,10 +937,10 @@ impl TendermintCoin { let tx_bytes = self .gen_simulated_tx(account_info, msg.clone(), timeout_height, memo.clone()) .map_to_mm(|e| TendermintCoinRpcError::InternalError(format!("{}", e)))?; - let request = SimulateRequest { tx_bytes, tx: None }; + let request = AbciRequest::new( Some(path.clone()), - request.encode_to_vec(), + SimulateRequest { tx_bytes, tx: None }.encode_to_vec(), ABCI_REQUEST_HEIGHT, ABCI_REQUEST_PROVE, ); @@ -1828,8 +1827,6 @@ impl MmCoin for TendermintCoin { .await .map_to_mm(WithdrawError::Transport)?; - let account_info = coin.my_account_info().await?; - let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; // >> END TX SIMULATION FOR FEE CALCULATION @@ -1879,6 +1876,7 @@ impl MmCoin for TendermintCoin { .to_any() .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; + let account_info = coin.my_account_info().await?; let tx_raw = coin .any_to_signed_raw_tx(account_info, msg_send, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 906960f7ba..a34b6d347e 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -171,7 +171,6 @@ impl TendermintToken { .await .map_to_mm(WithdrawError::Transport)?; - let account_info = platform.my_account_info().await?; let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; let fee_amount_u64 = platform @@ -195,6 +194,7 @@ impl TendermintToken { let fee = Fee::from_amount_and_gas(fee_amount, IBC_GAS_LIMIT_DEFAULT); + let account_info = platform.my_account_info().await?; let tx_raw = platform .any_to_signed_raw_tx(account_info, msg_transfer, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; @@ -648,7 +648,6 @@ impl MmCoin for TendermintToken { .await .map_to_mm(WithdrawError::Transport)?; - let account_info = platform.my_account_info().await?; let timeout_height = current_block + TIMEOUT_HEIGHT_DELTA; let fee_amount_u64 = platform @@ -672,6 +671,7 @@ impl MmCoin for TendermintToken { let fee = Fee::from_amount_and_gas(fee_amount, GAS_LIMIT_DEFAULT); + let account_info = platform.my_account_info().await?; let tx_raw = platform .any_to_signed_raw_tx(account_info, msg_send, fee, timeout_height, memo.clone()) .map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; From ebdc8c214c2e4b5d5a6f02b356b679a1130199e8 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Fri, 10 Mar 2023 17:30:34 +0200 Subject: [PATCH 32/79] feat: lightning ordermatching wip + library updates and more unit tests (#1655) * add a unit test to test lightning taker getting swap preimage from the chain, test is failing for now * update rust-lightning to v0.0.111 wip * update rust-lightning to v0.0.113 wip * update uuid to v1.2.2, use uuid instead of rpc_channel_id for lightning channels * add unit tests for mpp and claiming swaps on-chain * Add channel confirmation details to rpc response * add test_lightning_maker_swap_mpp * wip: use protocol info to not match with lightning orders if there are not routes between swap parties * wip: add amount to protocol info route check * wip: refactor lightning protocol info code * Add max_total_cltv_expiry_delta and final_cltv_expiry_delta to route check in lightning protocol info check * Fix funding_generated_in_block is Null in DB error when 0 conf is enabled and channel is closed before funding tx is confirmed * remove some unneeded todos, write better todos * remove more todos * review fixes: use bech32 0.9.1, return error instead of using expect for current_time * review fixes: use macro to simplify code, sort by short_channel_id * review fixes: add doc comments for uuid and channel_id * Fix channel was closed issue but closing transaction wasn't broadcasted due to a network error (all electrums were down, etc..) * fix issue in db when retrying to pay an invoice * move converting tx hex to bytes outside send transaction loop in BroadcasterInterface * import uuid::Error as UuidError in my_swaps.rs --------- Reviewed-by: ozkanonur , borngraced , caglaryucekaya --- Cargo.lock | 91 +-- mm2src/coins/Cargo.toml | 25 +- mm2src/coins/eth.rs | 12 +- mm2src/coins/lightning.rs | 191 ++++- mm2src/coins/lightning/ln_conf.rs | 14 + mm2src/coins/lightning/ln_db.rs | 35 +- mm2src/coins/lightning/ln_errors.rs | 7 +- mm2src/coins/lightning/ln_events.rs | 233 +++--- .../lightning/ln_filesystem_persister.rs | 2 +- mm2src/coins/lightning/ln_p2p.rs | 31 +- mm2src/coins/lightning/ln_platform.rs | 82 +- mm2src/coins/lightning/ln_serialization.rs | 56 +- mm2src/coins/lightning/ln_sql.rs | 243 +++--- mm2src/coins/lightning/ln_storage.rs | 2 +- mm2src/coins/lightning/ln_utils.rs | 98 +-- mm2src/coins/lp_coins.rs | 13 +- mm2src/coins/qrc20.rs | 12 +- .../rpc_command/lightning/close_channel.rs | 16 +- .../rpc_command/lightning/generate_invoice.rs | 2 + .../lightning/get_channel_details.rs | 13 +- .../rpc_command/lightning/list_channels.rs | 9 +- .../rpc_command/lightning/open_channel.rs | 18 +- .../rpc_command/lightning/update_channel.rs | 15 +- mm2src/coins/solana.rs | 12 +- mm2src/coins/solana/spl.rs | 12 +- mm2src/coins/tendermint/tendermint_coin.rs | 12 +- mm2src/coins/tendermint/tendermint_token.rs | 15 +- mm2src/coins/test_coin.rs | 12 +- mm2src/coins/utxo/bch.rs | 12 +- mm2src/coins/utxo/qtum.rs | 12 +- mm2src/coins/utxo/slp.rs | 12 +- mm2src/coins/utxo/utxo_standard.rs | 12 +- mm2src/coins/z_coin.rs | 12 +- mm2src/coins_activation/Cargo.toml | 6 +- .../src/lightning_activation.rs | 27 +- mm2src/common/Cargo.toml | 6 +- mm2src/common/common.rs | 2 +- mm2src/common/wasm.rs | 14 - mm2src/db_common/Cargo.toml | 2 +- mm2src/mm2_bitcoin/chain/Cargo.toml | 2 +- mm2src/mm2_bitcoin/chain/src/transaction.rs | 7 +- mm2src/mm2_bitcoin/keys/Cargo.toml | 2 +- mm2src/mm2_bitcoin/primitives/Cargo.toml | 2 +- mm2src/mm2_core/Cargo.toml | 2 +- mm2src/mm2_main/Cargo.toml | 2 +- mm2src/mm2_main/src/database/my_orders.rs | 7 +- mm2src/mm2_main/src/database/my_swaps.rs | 7 +- mm2src/mm2_main/src/lp_ordermatch.rs | 121 ++- .../mm2_main/src/lp_ordermatch/best_orders.rs | 11 +- .../src/lp_ordermatch/new_protocol.rs | 4 +- mm2src/mm2_main/src/lp_swap.rs | 4 +- .../mm2_main/src/lp_swap/my_swaps_storage.rs | 5 +- mm2src/mm2_main/src/ordermatch_tests.rs | 136 ++-- .../tests/mm2_tests/lightning_tests.rs | 714 ++++++++++++------ .../tests/mm2_tests/mm2_tests_inner.rs | 6 +- mm2src/mm2_test_helpers/Cargo.toml | 2 +- 56 files changed, 1526 insertions(+), 908 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e94215126..93e76392c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,9 +445,9 @@ checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" [[package]] name = "bech32" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] name = "bellman" @@ -508,20 +508,20 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.28.1" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bba324e6baf655b882df672453dbbc527bc938cadd27750ae510aaccc3a66a" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32", "bitcoin_hashes", - "secp256k1 0.22.1", + "secp256k1 0.24.3", ] [[package]] name = "bitcoin_hashes" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006cc91e1a1d99819bc5b8214be3555c1f0611b169f527a1fdc54ed1f2b745b0" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" [[package]] name = "bitcrypto" @@ -1073,7 +1073,6 @@ dependencies = [ "lightning-background-processor", "lightning-invoice", "lightning-net-tokio", - "lightning-rapid-gossip-sync", "mm2_core", "mm2_db", "mm2_err_handle", @@ -1100,7 +1099,7 @@ dependencies = [ "rustls 0.20.4", "script", "secp256k1 0.20.3", - "secp256k1 0.22.1", + "secp256k1 0.24.3", "ser_error", "ser_error_derive", "serde", @@ -1124,7 +1123,7 @@ dependencies = [ "tonic", "tonic-build", "utxo_signer", - "uuid 0.7.4", + "uuid 1.2.2", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -1216,7 +1215,7 @@ dependencies = [ "sha2 0.9.9", "shared_ref_counter", "tokio", - "uuid 0.7.4", + "uuid 1.2.2", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -1762,7 +1761,7 @@ dependencies = [ "log 0.4.14", "rusqlite", "sql-builder", - "uuid 0.7.4", + "uuid 1.2.2", ] [[package]] @@ -2131,7 +2130,7 @@ dependencies = [ [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/librustzcash.git#95fa5110b4b3ec105ae5fed1aba3847f656ec9b7" +source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.0.0#cc467f04f7449ffbca49e4ba17cabc78e90ac7d1" dependencies = [ "blake2b_simd", "byteorder 1.4.3", @@ -3816,18 +3815,18 @@ dependencies = [ [[package]] name = "lightning" -version = "0.0.110" +version = "0.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dce6da860338d5a9ddc3fd42432465310cfab93b342bbd23b41b7c1f7c509d3" +checksum = "087add70f81d2fdc6d4409bc0cef69e11ad366ef1d0068550159bd22b3ac8664" dependencies = [ "bitcoin", ] [[package]] name = "lightning-background-processor" -version = "0.0.110" +version = "0.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8de9d0de42bb933ffb8d33c6b0a75302f08b35126bfc74398ba01ad0c201f8d" +checksum = "2288d211a2ab15e2c9fb492fb99c7998df1a37f228552f703824ee678b8980c9" dependencies = [ "bitcoin", "lightning", @@ -3836,23 +3835,23 @@ dependencies = [ [[package]] name = "lightning-invoice" -version = "0.18.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32aa02b7fd0bd95e40b6ca8d9d9232b162d5e23b41bd2bc42abe9e9c78d34d72" +checksum = "e9680857590c3529cf8c7d32b04501f215f2bf1e029fdfa22f4112f66c1741e4" dependencies = [ "bech32", "bitcoin_hashes", "lightning", "num-traits", - "secp256k1 0.22.1", + "secp256k1 0.24.3", "serde", ] [[package]] name = "lightning-net-tokio" -version = "0.0.110" +version = "0.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d093fbc643835bc64c0501b52a3531d2511dcb1237d0495d68fea3adc47d" +checksum = "2e94b019ffcbd423c67bc8e65093d46cf5c00ff696b4b633936fce6e4d0cb845" dependencies = [ "bitcoin", "lightning", @@ -3861,9 +3860,9 @@ dependencies = [ [[package]] name = "lightning-rapid-gossip-sync" -version = "0.0.110" +version = "0.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391732631b14f7a1d9dc84dc3f644484d9b73190a31087b3856505cf0525bea0" +checksum = "488b68c7d24093d35a83f37c560e427f1085f4c5d37918b81b11d95cd3675a0f" dependencies = [ "bitcoin", "lightning", @@ -4230,7 +4229,7 @@ dependencies = [ "serde", "serde_json", "shared_ref_counter", - "uuid 0.7.4", + "uuid 1.2.2", ] [[package]] @@ -4420,7 +4419,7 @@ dependencies = [ "tokio", "trie-db", "trie-root 0.16.0", - "uuid 0.7.4", + "uuid 1.2.2", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -4556,7 +4555,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "uuid 0.7.4", + "uuid 1.2.2", ] [[package]] @@ -6267,11 +6266,12 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.22.1" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26947345339603ae8395f68e2f3d85a6b0a8ddfe6315818e80b8504415099db0" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "secp256k1-sys 0.5.2", + "bitcoin_hashes", + "secp256k1-sys 0.6.1", ] [[package]] @@ -6285,9 +6285,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152e20a0fd0519390fc43ab404663af8a0b794273d2a91d60ad4a39f13ffe110" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" dependencies = [ "cc", ] @@ -8610,19 +8610,20 @@ dependencies = [ [[package]] name = "uuid" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -dependencies = [ - "rand 0.6.5", - "serde", -] +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "uuid" -version = "0.8.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +dependencies = [ + "getrandom 0.2.6", + "rand 0.8.4", + "serde", +] [[package]] name = "value-bag" @@ -9132,7 +9133,7 @@ checksum = "0f9079049688da5871a7558ddacb7f04958862c703e68258594cb7a862b5e33f" [[package]] name = "zcash_client_backend" version = "0.5.0" -source = "git+https://github.com/KomodoPlatform/librustzcash.git#95fa5110b4b3ec105ae5fed1aba3847f656ec9b7" +source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.0.0#cc467f04f7449ffbca49e4ba17cabc78e90ac7d1" dependencies = [ "base64 0.13.0", "bech32", @@ -9156,7 +9157,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.3.0" -source = "git+https://github.com/KomodoPlatform/librustzcash.git#95fa5110b4b3ec105ae5fed1aba3847f656ec9b7" +source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.0.0#cc467f04f7449ffbca49e4ba17cabc78e90ac7d1" dependencies = [ "bech32", "bs58", @@ -9174,7 +9175,7 @@ dependencies = [ [[package]] name = "zcash_note_encryption" version = "0.0.0" -source = "git+https://github.com/KomodoPlatform/librustzcash.git#95fa5110b4b3ec105ae5fed1aba3847f656ec9b7" +source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.0.0#cc467f04f7449ffbca49e4ba17cabc78e90ac7d1" dependencies = [ "blake2b_simd", "byteorder 1.4.3", @@ -9188,7 +9189,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.5.0" -source = "git+https://github.com/KomodoPlatform/librustzcash.git#95fa5110b4b3ec105ae5fed1aba3847f656ec9b7" +source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.0.0#cc467f04f7449ffbca49e4ba17cabc78e90ac7d1" dependencies = [ "aes 0.6.0", "bitvec 0.18.5", @@ -9218,7 +9219,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.5.0" -source = "git+https://github.com/KomodoPlatform/librustzcash.git#95fa5110b4b3ec105ae5fed1aba3847f656ec9b7" +source = "git+https://github.com/KomodoPlatform/librustzcash.git?tag=k-1.0.0#cc467f04f7449ffbca49e4ba17cabc78e90ac7d1" dependencies = [ "bellman", "blake2b_simd", diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 7da462f746..49bd496123 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -22,7 +22,7 @@ async-trait = "0.1.52" base64 = "0.10.0" base58 = "0.2.0" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } -bitcoin_hashes = "0.10.0" +bitcoin_hashes = "0.11" bitcrypto = { path = "../mm2_bitcoin/crypto" } bincode = "1.3.3" byteorder = "1.3" @@ -90,7 +90,7 @@ utxo_signer = { path = "utxo_signer" } # using the same version as cosmrs tendermint-rpc = { version = "=0.23.7", default-features = false } tiny-bip39 = "0.8.0" -uuid = { version = "0.7", features = ["serde", "v4"] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } # One of web3 dependencies is the old `tokio-uds 0.1.7` which fails cross-compiling to ARM. # We don't need the default web3 features at all since we added our own web3 transport using shared HYPER instance. web3 = { git = "https://github.com/KomodoPlatform/rust-web3", tag = "v0.19.0", default-features = false } @@ -115,28 +115,27 @@ web-sys = { version = "0.3.55", features = ["console", "Headers", "Request", "Re [target.'cfg(not(target_arch = "wasm32"))'.dependencies] dirs = { version = "1" } -bitcoin = "0.28.1" +bitcoin = "0.29" hyper = { version = "0.14.11", features = ["client", "http2", "server", "tcp"] } # using webpki-tokio to avoid rejecting valid certificates # got "invalid certificate: UnknownIssuer" for https://ropsten.infura.io on iOS using default-features hyper-rustls = { version = "0.23", default-features = false, features = ["http1", "http2", "webpki-tokio"] } -lightning = "0.0.110" -lightning-background-processor = "0.0.110" -lightning-invoice = { version = "0.18.0", features = ["serde"] } -lightning-net-tokio = "0.0.110" -lightning-rapid-gossip-sync = "0.0.110" +lightning = "0.0.113" +lightning-background-processor = "0.0.113" +lightning-invoice = { version = "0.21.0", features = ["serde"] } +lightning-net-tokio = "0.0.113" rust-ini = { version = "0.13" } rustls = { version = "0.20", features = ["dangerous_configuration"] } -secp256k1v22 = { version = "0.22", package = "secp256k1" } +secp256k1v24 = { version = "0.24", package = "secp256k1" } tendermint-config = { version = "0.23.7", default-features = false } tokio = { version = "1.20" } tokio-rustls = { version = "0.23" } tonic = { version = "0.7", features = ["tls", "tls-webpki-roots", "compression"] } webpki-roots = { version = "0.22" } -zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.git" } -zcash_client_sqlite = { git = "https://github.com/KomodoPlatform/librustzcash.git" } -zcash_primitives = { features = ["transparent-inputs"], git = "https://github.com/KomodoPlatform/librustzcash.git" } -zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git" } +zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.0.0" } +zcash_client_sqlite = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.0.0" } +zcash_primitives = { features = ["transparent-inputs"], git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.0.0" } +zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.0.0" } [target.'cfg(windows)'.dependencies] winapi = "0.3" diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 56715e1296..3a0a5c9b8c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4242,9 +4242,17 @@ impl MmCoin for EthCoin { fn mature_confirmations(&self) -> Option { None } - fn coin_protocol_info(&self) -> Vec { Vec::new() } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - fn is_coin_protocol_supported(&self, _info: &Option>) -> bool { true } + fn is_coin_protocol_supported( + &self, + _info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { + true + } fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 98d787257f..a134d0bc53 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -34,7 +34,7 @@ use bitcrypto::ChecksumType; use bitcrypto::{dhash256, ripemd160}; use common::custom_futures::repeatable::{Ready, Retry}; use common::executor::{AbortableSystem, AbortedError, Timer}; -use common::log::{info, LogOnError, LogState}; +use common::log::{error, info, LogOnError, LogState}; use common::{async_blocking, get_local_duration_since_epoch, log, now_ms, PagingOptionsEnum}; use db_common::sqlite::rusqlite::Error as SqlError; use futures::{FutureExt, TryFutureExt}; @@ -43,8 +43,10 @@ use keys::{hash::H256, CompactSignature, KeyPair, Private, Public}; use lightning::chain::keysinterface::{KeysInterface, KeysManager, Recipient}; use lightning::ln::channelmanager::{ChannelDetails, MIN_FINAL_CLTV_EXPIRY}; use lightning::ln::{PaymentHash, PaymentPreimage}; +use lightning::routing::router::{DefaultRouter, PaymentParameters, RouteParameters, Router as RouterTrait}; +use lightning::util::ser::{Readable, Writeable}; use lightning_background_processor::BackgroundProcessor; -use lightning_invoice::utils::DefaultRouter; +use lightning_invoice::payment::Payer; use lightning_invoice::{payment, CreationError, InvoiceBuilder, SignOrCreationError}; use lightning_invoice::{Invoice, InvoiceDescription}; use ln_conf::{LightningCoinConf, PlatformCoinConfirmationTargets}; @@ -65,20 +67,22 @@ use mm2_number::{BigDecimal, MmNumber}; use parking_lot::Mutex as PaMutex; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; use script::TransactionInputSigner; -use secp256k1v22::PublicKey; +use secp256k1v24::PublicKey; use serde::Deserialize; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::fmt; +use std::io::Cursor; use std::net::SocketAddr; use std::str::FromStr; use std::sync::Arc; +use uuid::Uuid; const WAIT_FOR_REFUND_INTERVAL: f64 = 60.; pub const DEFAULT_INVOICE_EXPIRY: u32 = 3600; -pub type InvoicePayer = payment::InvoicePayer, Router, Arc, Arc, E>; +pub type InvoicePayer = payment::InvoicePayer, Router, Arc, E>; #[derive(Clone)] pub struct LightningCoin { @@ -110,10 +114,8 @@ pub struct LightningCoin { /// The lightning node router that takes care of finding routes for payments. // Todo: this should be removed once pay_invoice_with_max_total_cltv_expiry_delta similar functionality is implemented in rust-lightning pub router: Arc, - /// The lightning node scorer that takes care of scoring routes. Given the uncertainty of channel liquidity balances, - /// the scorer stores the probabilities that a route is successful based on knowledge learned from successful and unsuccessful attempts. - // Todo: this should be removed once pay_invoice_with_max_total_cltv_expiry_delta similar functionality is implemented in rust-lightning - pub scorer: Arc, + /// The lightning node logger, this is required to be passed to some function so that logs from these functions are displayed in mm2 logs. + pub logger: Arc, } impl fmt::Debug for LightningCoin { @@ -181,11 +183,11 @@ impl LightningCoin { }) } - pub(crate) async fn get_channel_by_rpc_id(&self, rpc_id: u64) -> Option { + pub(crate) async fn get_channel_by_uuid(&self, uuid: Uuid) -> Option { self.list_channels() .await .into_iter() - .find(|chan| chan.user_channel_id == rpc_id) + .find(|chan| chan.user_channel_id == uuid.as_u128()) } pub(crate) async fn pay_invoice( @@ -194,6 +196,16 @@ impl LightningCoin { max_total_cltv_expiry_delta: Option, ) -> Result> { let payment_hash = PaymentHash((invoice.payment_hash()).into_inner()); + // check if the invoice was already paid + if let Some(info) = self.db.get_payment_from_db(payment_hash).await? { + // If payment is still pending pay_invoice_with_max_total_cltv_expiry_delta/pay_invoice will return an error later + if info.status == HTLCStatus::Succeeded { + return MmError::err(PaymentError::Invoice(format!( + "Invoice with payment hash {} is already paid!", + hex::encode(payment_hash.0) + ))); + } + } let payment_type = PaymentType::OutboundPayment { destination: *invoice.payee_pub_key().unwrap_or(&invoice.recover_payee_pub_key()), }; @@ -210,7 +222,6 @@ impl LightningCoin { pay_invoice_with_max_total_cltv_expiry_delta( selfi.channel_manager, selfi.router, - selfi.scorer, &invoice, total_cltv, ) @@ -221,7 +232,8 @@ impl LightningCoin { }; let payment_info = PaymentInfo::new(payment_hash, payment_type, description, amt_msat); - self.db.add_payment_to_db(&payment_info).await?; + // So this only updates the payment in db if the user is retrying to pay an invoice payment that has failed + self.db.add_or_update_payment_in_db(&payment_info).await?; Ok(payment_info) } @@ -256,7 +268,7 @@ impl LightningCoin { pub(crate) async fn get_open_channels_by_filter( &self, filter: Option, - paging: PagingOptionsEnum, + paging: PagingOptionsEnum, limit: usize, ) -> GetOpenChannelsResult { fn apply_open_channel_filter(channel_details: &ChannelDetailsForRPC, filter: &OpenChannelsFilter) -> bool { @@ -356,11 +368,14 @@ impl LightningCoin { true } - let mut total_open_channels: Vec = - self.list_channels().await.into_iter().map(From::from).collect(); - - total_open_channels.sort_by(|a, b| a.rpc_channel_id.cmp(&b.rpc_channel_id)); + let mut total_open_channels = self.list_channels().await; + total_open_channels.sort_by(|a, b| { + b.short_channel_id + .unwrap_or(u64::MAX) + .cmp(&a.short_channel_id.unwrap_or(u64::MAX)) + }); drop_mutability!(total_open_channels); + let total_open_channels: Vec = total_open_channels.into_iter().map(From::from).collect(); let open_channels_filtered = if let Some(ref f) = filter { total_open_channels @@ -373,9 +388,9 @@ impl LightningCoin { let offset = match paging { PagingOptionsEnum::PageNumber(page) => (page.get() - 1) * limit, - PagingOptionsEnum::FromId(rpc_id) => open_channels_filtered + PagingOptionsEnum::FromId(uuid) => open_channels_filtered .iter() - .position(|x| x.rpc_channel_id == rpc_id) + .position(|x| x.uuid == uuid) .map(|pos| pos + 1) .unwrap_or_default(), }; @@ -395,6 +410,9 @@ impl LightningCoin { } } + // Todo: this can be removed after next rust-lightning release when min_final_cltv_expiry can be specified in + // Todo: create_invoice_from_channelmanager_and_duration_since_epoch_with_payment_hash https://github.com/lightningdevkit/rust-lightning/pull/1878 + // Todo: The above PR will also validate min_final_cltv_expiry. async fn create_invoice_for_hash( &self, payment_hash: PaymentHash, @@ -430,8 +448,6 @@ impl LightningCoin { .payment_hash(Hash::from_inner(payment_hash.0)) .payment_secret(payment_secret) .basic_mpp() - // Todo: This should be validated by the other side, right now this is not validated by rust-lightning and the PaymentReceived event doesn't include the final cltv of the payment for us to validate it - // Todo: This needs a PR opened to rust-lightning, I already contacted them about it and there is an issue opened for it https://github.com/lightningdevkit/rust-lightning/issues/1850 .min_final_cltv_expiry(min_final_cltv_expiry) .expiry_time(core::time::Duration::from_secs(invoice_expiry_delta_secs.into())); if let Some(amt) = amt_msat { @@ -554,15 +570,15 @@ impl LightningCoin { let fut = async move { match coin.db.get_payment_from_db(payment_hash).await { Ok(Some(payment)) => { - let amount_received = payment.amt_msat; + let amount_claimable = payment.amt_msat; // Note: locktime doesn't need to be validated since min_final_cltv_expiry should be validated in rust-lightning after fixing the below issue // https://github.com/lightningdevkit/rust-lightning/issues/1850 - // Also, PaymentReceived won't be fired if amount_received < the amount requested in the invoice, this check is probably not needed. + // Also, PaymentClaimable won't be fired if amount_claimable < the amount requested in the invoice, this check is probably not needed. // But keeping it just in case any changes happen in rust-lightning - if amount_received != Some(amt_msat as i64) { + if amount_claimable != Some(amt_msat as i64) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided payment {} amount {:?} doesn't match required amount {}", - payment_hex, amount_received, amt_msat + payment_hex, amount_claimable, amt_msat ))); } Ok(()) @@ -709,7 +725,7 @@ impl SwapOps for LightningCoin { HTLCStatus::Succeeded => Ok(Some(FoundSwapTxSpend::Spent(TransactionEnum::LightningPayment( payment_hash, )))), - HTLCStatus::Received => { + HTLCStatus::Claimable => { ERR!( "Payment {} has an invalid status of {} in the db", payment_hex, @@ -743,7 +759,7 @@ impl SwapOps for LightningCoin { return ERR!("Payment {} should be an inbound payment!", payment_hex); } match payment.status { - HTLCStatus::Pending | HTLCStatus::Received => Ok(None), + HTLCStatus::Pending | HTLCStatus::Claimable => Ok(None), HTLCStatus::Succeeded => Ok(Some(FoundSwapTxSpend::Spent(TransactionEnum::LightningPayment( payment_hash, )))), @@ -1031,9 +1047,7 @@ impl MarketCoinOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn base_coin_balance(&self) -> BalanceFut { - Box::new(self.platform_coin().my_balance().map(|res| res.spendable)) - } + fn base_coin_balance(&self) -> BalanceFut { Box::new(self.my_balance().map(|res| res.spendable)) } fn platform_ticker(&self) -> &str { self.platform_coin().ticker() } @@ -1056,6 +1070,7 @@ impl MarketCoinOps for LightningCoin { } // Todo: Add waiting for confirmations logic for the case of if the channel is closed and the htlc can be claimed on-chain + // Todo: The above is postponed and might not be needed after this issue is resolved https://github.com/lightningdevkit/rust-lightning/issues/2017 fn wait_for_confirmations( &self, tx: &[u8], @@ -1083,7 +1098,7 @@ impl MarketCoinOps for LightningCoin { match payment.payment_type { PaymentType::OutboundPayment { .. } => match payment.status { HTLCStatus::Pending | HTLCStatus::Succeeded => return Ok(()), - HTLCStatus::Received => { + HTLCStatus::Claimable => { return ERR!( "Payment {} has an invalid status of {} in the db", payment_hex, @@ -1096,7 +1111,7 @@ impl MarketCoinOps for LightningCoin { HTLCStatus::Failed => return ERR!("Lightning swap payment {} failed", payment_hex), }, PaymentType::InboundPayment => match payment.status { - HTLCStatus::Received | HTLCStatus::Succeeded => return Ok(()), + HTLCStatus::Claimable | HTLCStatus::Succeeded => return Ok(()), HTLCStatus::Pending => info!("Payment {} not received yet!", payment_hex), HTLCStatus::Failed => return ERR!("Lightning swap payment {} failed", payment_hex), }, @@ -1141,7 +1156,7 @@ impl MarketCoinOps for LightningCoin { match coin.db.get_payment_from_db(payment_hash).await { Ok(Some(payment)) => match payment.status { HTLCStatus::Pending => (), - HTLCStatus::Received => { + HTLCStatus::Claimable => { return Err(TransactionErr::Plain(ERRL!( "Payment {} has an invalid status of {} in the db", payment_hex, @@ -1193,13 +1208,31 @@ impl MarketCoinOps for LightningCoin { .to_string()) } - // Todo: min_tx_amount should depend on inbound_htlc_minimum_msat of the channel/s the payment will be sent through, 1 satoshi is used for for now (1000 of the base unit of lightning which is msat) - fn min_tx_amount(&self) -> BigDecimal { big_decimal_from_sat(1000, self.decimals()) } + // This will depend on the route/routes taken for the payment, since every channel's counterparty specifies the minimum amount they will allow to route. + // Since route is not specified at this stage yet, we can use the maximum of these minimum amounts as the min_tx_amount allowed. + // Default value: 1 msat if the counterparty is using LDK default value. + fn min_tx_amount(&self) -> BigDecimal { + let amount_in_msat = self + .channel_manager + .list_channels() + .iter() + .map(|c| c.counterparty.outbound_htlc_minimum_msat.unwrap_or(1)) + .max() + .unwrap_or(1) as i64; + big_decimal_from_sat(amount_in_msat, self.decimals()) + } // Todo: Equals to min_tx_amount for now (1 satoshi), should change this later + // Todo: doesn't take routing fees into account too, There is no way to know the route to the other side of the swap when placing the order, need to find a workaround for this fn min_trading_vol(&self) -> MmNumber { self.min_tx_amount().into() } } +#[derive(Deserialize, Serialize)] +struct LightningProtocolInfo { + node_id: PublicKeyForRPC, + route_hints: Vec>, +} + #[async_trait] impl MmCoin for LightningCoin { fn is_asset_chain(&self) -> bool { false } @@ -1312,11 +1345,91 @@ impl MmCoin for LightningCoin { fn mature_confirmations(&self) -> Option { None } - // Todo: This uses default data for now for the sake of swap P.O.C., this should be implemented probably when implementing order matching if it's needed - fn coin_protocol_info(&self) -> Vec { Vec::new() } + // Channels for users/non-routing nodes should be private, so routing hints are sent as part of the protocol info + // alongside the receiver lightning node address/pubkey. + // Note: This is required only for the side that's getting paid in lightning. + // Todo: should take in consideration JIT routing and using LSPs in next PRs + fn coin_protocol_info(&self, amount_to_receive: Option) -> Vec { + let amt_msat = match amount_to_receive.map(|a| sat_from_big_decimal(&a.into(), self.decimals())) { + Some(Ok(amt)) => amt, + Some(Err(e)) => { + error!("{}", e); + return Vec::new(); + }, + None => return Vec::new(), + }; + let route_hints = filter_channels(self.channel_manager.list_usable_channels(), Some(amt_msat)) + .iter() + .map(|h| h.encode()) + .collect(); + let node_id = PublicKeyForRPC(self.channel_manager.get_our_node_id()); + let protocol_info = LightningProtocolInfo { node_id, route_hints }; + rmp_serde::to_vec(&protocol_info).expect("Serialization should not fail") + } - // Todo: This uses default data for now for the sake of swap P.O.C., this should be implemented probably when implementing order matching if it's needed - fn is_coin_protocol_supported(&self, _info: &Option>) -> bool { true } + // Todo: should take in consideration JIT routing and using LSPs in next PRs + fn is_coin_protocol_supported( + &self, + info: &Option>, + amount_to_send: Option, + locktime: u64, + is_maker: bool, + ) -> bool { + macro_rules! log_err_and_return_false { + ($e:expr) => { + match $e { + Ok(res) => res, + Err(e) => { + error!("{}", e); + return false; + }, + } + }; + } + let final_value_msat = match amount_to_send.map(|amt| sat_from_big_decimal(&amt.into(), self.decimals())) { + Some(amt_or_err) => log_err_and_return_false!(amt_or_err), + None => return true, + }; + let protocol_info = match info.as_ref().map(rmp_serde::from_read_ref::<_, LightningProtocolInfo>) { + Some(info_or_err) => log_err_and_return_false!(info_or_err), + None => return false, + }; + let mut route_hints = Vec::new(); + for h in protocol_info.route_hints.iter() { + let hint = log_err_and_return_false!(Readable::read(&mut Cursor::new(h))); + route_hints.push(hint); + } + let mut payment_params = + PaymentParameters::from_node_id(protocol_info.node_id.into()).with_route_hints(route_hints); + let final_cltv_expiry_delta = if is_maker { + self.estimate_blocks_from_duration(locktime) + .try_into() + .expect("final_cltv_expiry_delta shouldn't exceed u32::MAX") + } else { + payment_params.max_total_cltv_expiry_delta = self + .estimate_blocks_from_duration(locktime) + .try_into() + .expect("max_total_cltv_expiry_delta shouldn't exceed u32::MAX"); + MIN_FINAL_CLTV_EXPIRY + }; + drop_mutability!(payment_params); + let route_params = RouteParameters { + payment_params, + final_value_msat, + final_cltv_expiry_delta, + }; + let payer = self.channel_manager.node_id(); + let first_hops = self.channel_manager.first_hops(); + let inflight_htlcs = self.channel_manager.compute_inflight_htlcs(); + self.router + .find_route( + &payer, + &route_params, + Some(&first_hops.iter().collect::>()), + inflight_htlcs, + ) + .is_ok() + } fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.platform.abortable_system) } diff --git a/mm2src/coins/lightning/ln_conf.rs b/mm2src/coins/lightning/ln_conf.rs index dd58946969..80c5047d62 100644 --- a/mm2src/coins/lightning/ln_conf.rs +++ b/mm2src/coins/lightning/ln_conf.rs @@ -110,6 +110,12 @@ pub struct OurChannelsConfigs { pub announced_channel: Option, /// When set, we commit to an upfront shutdown_pubkey at channel open. pub commit_upfront_shutdown_pubkey: Option, + /// The minimum balance that the other node has to maintain on their side, at all times. + /// This ensures that if our counterparty broadcasts a revoked state, we can punish them by claiming + /// at least this value on chain. + /// Default value: 1% of channel value. + /// Minimum value: 1000 sats + pub their_channel_reserve_sats: Option, } impl OurChannelsConfigs { @@ -141,6 +147,10 @@ impl OurChannelsConfigs { if let Some(commit) = config.commit_upfront_shutdown_pubkey { self.commit_upfront_shutdown_pubkey = Some(commit); } + + if let Some(reserve) = config.their_channel_reserve_sats { + self.their_channel_reserve_sats = Some(reserve); + } } } @@ -176,6 +186,10 @@ impl From for ChannelHandshakeConfig { channel_handshake_config.commit_upfront_shutdown_pubkey = commit; } + if let Some(reserve) = config.their_channel_reserve_sats { + channel_handshake_config.their_channel_reserve_proportional_millionths = reserve; + } + channel_handshake_config } } diff --git a/mm2src/coins/lightning/ln_db.rs b/mm2src/coins/lightning/ln_db.rs index 8ad4455a24..523e7daca3 100644 --- a/mm2src/coins/lightning/ln_db.rs +++ b/mm2src/coins/lightning/ln_db.rs @@ -3,13 +3,14 @@ use common::{now_ms, PagingOptionsEnum}; use db_common::sqlite::rusqlite::types::FromSqlError; use derive_more::Display; use lightning::ln::{PaymentHash, PaymentPreimage}; -use secp256k1v22::PublicKey; +use secp256k1v24::PublicKey; use serde::{Deserialize, Serialize}; use std::str::FromStr; +use uuid::Uuid; #[derive(Clone, Debug, PartialEq, Serialize)] pub struct DBChannelDetails { - pub rpc_id: i64, + pub uuid: Uuid, pub channel_id: String, pub counterparty_node_id: String, #[serde(skip_serializing_if = "Option::is_none")] @@ -37,14 +38,14 @@ pub struct DBChannelDetails { impl DBChannelDetails { #[inline] pub fn new( - rpc_id: u64, + uuid: Uuid, channel_id: [u8; 32], counterparty_node_id: PublicKey, is_outbound: bool, is_public: bool, ) -> Self { DBChannelDetails { - rpc_id: rpc_id as i64, + uuid, channel_id: hex::encode(channel_id), counterparty_node_id: counterparty_node_id.to_string(), funding_tx: None, @@ -101,7 +102,7 @@ pub struct GetClosedChannelsResult { #[serde(rename_all = "lowercase")] pub enum HTLCStatus { Pending, - Received, + Claimable, Succeeded, Failed, } @@ -112,7 +113,7 @@ impl FromStr for HTLCStatus { fn from_str(s: &str) -> Result { match s { "Pending" => Ok(HTLCStatus::Pending), - "Received" => Ok(HTLCStatus::Received), + "Claimable" => Ok(HTLCStatus::Claimable), "Succeeded" => Ok(HTLCStatus::Succeeded), "Failed" => Ok(HTLCStatus::Failed), _ => Err(FromSqlError::InvalidType), @@ -205,9 +206,6 @@ pub trait LightningDB { /// Checks if tables have been initialized or not in DB. async fn is_db_initialized(&self) -> Result; - /// Gets the last added channel rpc_id. Can be used to deduce the rpc_id for a new channel to be added to DB. - async fn get_last_channel_rpc_id(&self) -> Result; - /// Inserts a new channel record in the DB. The record's data is completed using add_funding_tx_to_db, /// add_closing_tx_to_db, add_claiming_tx_to_db when this information is available. async fn add_channel_to_db(&self, details: &DBChannelDetails) -> Result<(), Self::Error>; @@ -215,7 +213,7 @@ pub trait LightningDB { /// Updates a channel's DB record with the channel's funding transaction information. async fn add_funding_tx_to_db( &self, - rpc_id: i64, + uuid: Uuid, funding_tx: String, funding_value: i64, funding_generated_in_block: i64, @@ -228,7 +226,7 @@ pub trait LightningDB { /// Updates the is_closed value for a channel in the DB to 1. async fn update_channel_to_closed( &self, - rpc_id: i64, + uuid: Uuid, closure_reason: String, close_at: i64, ) -> Result<(), Self::Error>; @@ -240,7 +238,7 @@ pub trait LightningDB { async fn get_closed_channels_with_no_closing_tx(&self) -> Result, Self::Error>; /// Updates a channel's DB record with the channel's closing transaction hash. - async fn add_closing_tx_to_db(&self, rpc_id: i64, closing_tx: String) -> Result<(), Self::Error>; + async fn add_closing_tx_to_db(&self, uuid: Uuid, closing_tx: String) -> Result<(), Self::Error>; /// Updates a channel's DB record with information about the transaction responsible for claiming the channel's /// closing balance back to the user's address. @@ -251,8 +249,8 @@ pub trait LightningDB { claimed_balance: f64, ) -> Result<(), Self::Error>; - /// Gets a channel record from DB by the channel's rpc_id. - async fn get_channel_from_db(&self, rpc_id: u64) -> Result, Self::Error>; + /// Gets a channel record from DB by the channel's uuid. + async fn get_channel_from_db(&self, uuid: Uuid) -> Result, Self::Error>; /// Gets the list of closed channels that match the provided filter criteria. The number of requested records is /// specified by the limit parameter, the starting record to list from is specified by the paging parameter. The @@ -260,13 +258,16 @@ pub trait LightningDB { async fn get_closed_channels_by_filter( &self, filter: Option, - paging: PagingOptionsEnum, + paging: PagingOptionsEnum, limit: usize, ) -> Result; /// Inserts a new payment record in the DB. async fn add_payment_to_db(&self, info: &PaymentInfo) -> Result<(), Self::Error>; + /// Inserts or updates a payment record in the DB. + async fn add_or_update_payment_in_db(&self, info: &PaymentInfo) -> Result<(), Self::Error>; + /// Updates a payment's preimage in DB by the payment's hash. async fn update_payment_preimage_in_db( &self, @@ -277,8 +278,8 @@ pub trait LightningDB { /// Updates a payment's status in DB by the payment's hash. async fn update_payment_status_in_db(&self, hash: PaymentHash, status: &HTLCStatus) -> Result<(), Self::Error>; - /// Updates a payment's status to received in DB by the payment's hash. Also, adds the payment preimage to the db. - async fn update_payment_to_received_in_db( + /// Updates a payment's status to claimable in DB by the payment's hash. Also, adds the payment preimage to the db. + async fn update_payment_to_claimable_in_db( &self, hash: PaymentHash, preimage: PaymentPreimage, diff --git a/mm2src/coins/lightning/ln_errors.rs b/mm2src/coins/lightning/ln_errors.rs index 5b349fd43a..5d479e40b6 100644 --- a/mm2src/coins/lightning/ln_errors.rs +++ b/mm2src/coins/lightning/ln_errors.rs @@ -8,6 +8,7 @@ use http::StatusCode; use mm2_err_handle::prelude::*; use rpc_task::RpcTaskError; use std::num::TryFromIntError; +use uuid::Uuid; pub type EnableLightningResult = Result>; pub type SaveChannelClosingResult = Result>; @@ -90,10 +91,8 @@ impl From for EnableLightningError { pub enum SaveChannelClosingError { #[display(fmt = "DB error: {}", _0)] DbError(String), - #[display(fmt = "Channel with rpc id {} not found in DB", _0)] - ChannelNotFound(u64), - #[display(fmt = "funding_generated_in_block is Null in DB")] - BlockHeightNull, + #[display(fmt = "Channel with uuid {} not found in DB", _0)] + ChannelNotFound(Uuid), #[display(fmt = "Funding transaction hash is Null in DB")] FundingTxNull, #[display(fmt = "Error parsing funding transaction hash: {}", _0)] diff --git a/mm2src/coins/lightning/ln_events.rs b/mm2src/coins/lightning/ln_events.rs index 351191e1c0..c819cb19ca 100644 --- a/mm2src/coins/lightning/ln_events.rs +++ b/mm2src/coins/lightning/ln_events.rs @@ -7,7 +7,7 @@ use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode::serialize_hex; use common::executor::{AbortSettings, SpawnAbortable, SpawnFuture, Timer}; use common::log::{error, info}; -use common::now_ms; +use common::{new_uuid, now_ms}; use core::time::Duration; use futures::compat::Future01CompatExt; use lightning::chain::chaininterface::{ConfirmationTarget, FeeEstimator}; @@ -15,7 +15,7 @@ use lightning::chain::keysinterface::SpendableOutputDescriptor; use lightning::util::events::{Event, EventHandler, PaymentPurpose}; use rand::Rng; use script::{Builder, SignatureVersion}; -use secp256k1v22::Secp256k1; +use secp256k1v24::Secp256k1; use std::convert::{TryFrom, TryInto}; use std::sync::Arc; use utxo_signer::with_key_pair::sign_tx; @@ -23,6 +23,8 @@ use utxo_signer::with_key_pair::sign_tx; const TRY_LOOP_INTERVAL: f64 = 60.; /// 1 second. const CRITICAL_FUTURE_TIMEOUT: f64 = 1.0; +pub const CHANNEL_READY_LOG: &str = "Handling ChannelReady event for channel with uuid"; +pub const PAYMENT_CLAIMABLE_LOG: &str = "Handling PaymentClaimable event"; pub const SUCCESSFUL_CLAIM_LOG: &str = "Successfully claimed payment"; pub const SUCCESSFUL_SEND_LOG: &str = "Successfully sent payment"; @@ -35,7 +37,7 @@ pub struct LightningEventHandler { } impl EventHandler for LightningEventHandler { - fn handle_event(&self, event: &Event) { + fn handle_event(&self, event: Event) { match event { Event::FundingGenerationReady { temporary_channel_id, @@ -44,33 +46,34 @@ impl EventHandler for LightningEventHandler { user_channel_id, counterparty_node_id, } => self.handle_funding_generation_ready( - *temporary_channel_id, - *channel_value_satoshis, + temporary_channel_id, + channel_value_satoshis, output_script, - *user_channel_id, + user_channel_id, counterparty_node_id, ), - Event::PaymentReceived { + Event::PaymentClaimable { payment_hash, amount_msat, purpose, - } => self.handle_payment_received(*payment_hash, *amount_msat, purpose.clone()), + .. + } => self.handle_payment_claimable(payment_hash, amount_msat, purpose), Event::PaymentSent { payment_preimage, payment_hash, fee_paid_msat, .. - } => self.handle_payment_sent(*payment_preimage, *payment_hash, *fee_paid_msat), + } => self.handle_payment_sent(payment_preimage, payment_hash, fee_paid_msat), - Event::PaymentClaimed { payment_hash, amount_msat, .. } => self.handle_payment_claimed(*payment_hash, *amount_msat), + Event::PaymentClaimed { payment_hash, amount_msat, .. } => self.handle_payment_claimed(payment_hash, amount_msat), - Event::PaymentFailed { payment_hash, .. } => self.handle_payment_failed(*payment_hash), + Event::PaymentFailed { payment_hash, .. } => self.handle_payment_failed(payment_hash), - Event::PendingHTLCsForwardable { time_forwardable } => self.handle_pending_htlcs_forwards(*time_forwardable), + Event::PendingHTLCsForwardable { time_forwardable } => self.handle_pending_htlcs_forwards(time_forwardable), - Event::SpendableOutputs { outputs } => self.handle_spendable_outputs(outputs.clone()), + Event::SpendableOutputs { outputs } => self.handle_spendable_outputs(outputs), // Todo: an RPC for total amount earned Event::PaymentForwarded { fee_earned_msat, claim_from_onchain_tx, prev_channel_id, next_channel_id} => info!( @@ -86,7 +89,7 @@ impl EventHandler for LightningEventHandler { channel_id, user_channel_id, reason, - } => self.handle_channel_closed(*channel_id, *user_channel_id, reason.to_string()), + } => self.handle_channel_closed(channel_id, user_channel_id, reason.to_string()), // Todo: Add spent UTXOs to RecentlySpentOutPoints if it's not discarded Event::DiscardFunding { channel_id, transaction } => info!( @@ -113,15 +116,15 @@ impl EventHandler for LightningEventHandler { // Todo: Add information to db about why a payment failed using this event Event::PaymentPathFailed { payment_hash, - rejected_by_dest, + payment_failed_permanently, all_paths_failed, path, .. } => info!( - "Payment path: {:?}, failed for payment hash: {}, Was rejected by destination?: {}, All paths failed?: {}", + "Payment path: {:?}, failed for payment hash: {}, permanent failure?: {}, All paths failed?: {}", path.iter().map(|hop| hop.pubkey.to_string()).collect::>(), hex::encode(payment_hash.0), - rejected_by_dest, + payment_failed_permanently, all_paths_failed, ), @@ -131,7 +134,7 @@ impl EventHandler for LightningEventHandler { funding_satoshis, push_msat, channel_type: _, - } => self.handle_open_channel_request(*temporary_channel_id, *counterparty_node_id, *funding_satoshis, *push_msat), + } => self.handle_open_channel_request(temporary_channel_id, counterparty_node_id, funding_satoshis, push_msat), // Just log an error for now, but this event can be used along PaymentForwarded for a new RPC that shows stats about how a node // forward payments over it's outbound channels which can be useful for a user that wants to run a forwarding node for some profits. @@ -147,6 +150,8 @@ impl EventHandler for LightningEventHandler { // send_probe is not used for now but may be used in order matching in the future to check if a swap can happen or not. Event::ProbeSuccessful { .. } => (), Event::ProbeFailed { .. } => (), + Event::HTLCIntercepted { .. } => (), + Event::ChannelReady { user_channel_id, .. } => info!("{}: {}", CHANNEL_READY_LOG, Uuid::from_u128(user_channel_id)), } } } @@ -156,19 +161,15 @@ pub async fn init_abortable_events(platform: Arc, db: SqliteLightningD for channel_details in closed_channels_without_closing_tx { let platform_c = platform.clone(); let db = db.clone(); - let user_channel_id = channel_details.rpc_id; + let uuid = channel_details.uuid; platform.spawner().spawn(async move { if let Ok(closing_tx_hash) = platform_c .get_channel_closing_tx(channel_details) .await .error_log_passthrough() { - if let Err(e) = db.add_closing_tx_to_db(user_channel_id, closing_tx_hash).await { - log::error!( - "Unable to update channel {} closing details in DB: {}", - user_channel_id, - e - ); + if let Err(e) = db.add_closing_tx_to_db(uuid, closing_tx_hash).await { + log::error!("Unable to update channel {} closing details in DB: {}", uuid, e); } } }); @@ -188,7 +189,7 @@ pub enum SignFundingTransactionError { // Generates the raw funding transaction with one output equal to the channel value. fn sign_funding_transaction( - user_channel_id: u64, + uuid: Uuid, output_script: &Script, platform: Arc, ) -> Result { @@ -196,11 +197,11 @@ fn sign_funding_transaction( let mut unsigned = { let unsigned_funding_txs = platform.unsigned_funding_txs.lock(); unsigned_funding_txs - .get(&user_channel_id) + .get(&uuid) .ok_or_else(|| { SignFundingTransactionError::Internal(format!( - "Unsigned funding tx not found for internal channel id: {}", - user_channel_id + "Unsigned funding tx not found for channel with uuid: {}", + uuid )) })? .clone() @@ -234,20 +235,20 @@ fn sign_funding_transaction( async fn save_channel_closing_details( db: SqliteLightningDB, platform: Arc, - user_channel_id: u64, + uuid: Uuid, reason: String, ) -> SaveChannelClosingResult<()> { - db.update_channel_to_closed(user_channel_id as i64, reason, (now_ms() / 1000) as i64) + db.update_channel_to_closed(uuid, reason, (now_ms() / 1000) as i64) .await?; let channel_details = db - .get_channel_from_db(user_channel_id) + .get_channel_from_db(uuid) .await? - .ok_or_else(|| MmError::new(SaveChannelClosingError::ChannelNotFound(user_channel_id)))?; + .ok_or_else(|| MmError::new(SaveChannelClosingError::ChannelNotFound(uuid)))?; let closing_tx_hash = platform.get_channel_closing_tx(channel_details).await?; - db.add_closing_tx_to_db(user_channel_id as i64, closing_tx_hash).await?; + db.add_closing_tx_to_db(uuid, closing_tx_hash).await?; Ok(()) } @@ -288,20 +289,21 @@ impl LightningEventHandler { &self, temporary_channel_id: [u8; 32], channel_value_satoshis: u64, - output_script: &Script, - user_channel_id: u64, - counterparty_node_id: &PublicKey, + output_script: Script, + user_channel_id: u128, + counterparty_node_id: PublicKey, ) { + let uuid = Uuid::from_u128(user_channel_id); info!( - "Handling FundingGenerationReady event for internal channel id: {} with: {}", - user_channel_id, counterparty_node_id + "Handling FundingGenerationReady event for channel with uuid: {} with: {}", + uuid, counterparty_node_id ); - let funding_tx = match sign_funding_transaction(user_channel_id, output_script, self.platform.clone()) { + let funding_tx = match sign_funding_transaction(uuid, &output_script, self.platform.clone()) { Ok(tx) => tx, Err(e) => { error!( - "Error generating funding transaction for internal channel id {}: {}", - user_channel_id, + "Error generating funding transaction for channel with uuid {}: {}", + uuid, e.to_string() ); return; @@ -311,7 +313,7 @@ impl LightningEventHandler { // Give the funding transaction back to LDK for opening the channel. if let Err(e) = self.channel_manager - .funding_transaction_generated(&temporary_channel_id, counterparty_node_id, funding_tx) + .funding_transaction_generated(&temporary_channel_id, &counterparty_node_id, funding_tx) { error!("{:?}", e); return; @@ -322,7 +324,7 @@ impl LightningEventHandler { let fut = async move { let best_block_height = platform.best_block_height(); db.add_funding_tx_to_db( - user_channel_id as i64, + uuid, funding_txid.to_string(), channel_value_satoshis as i64, best_block_height as i64, @@ -335,20 +337,21 @@ impl LightningEventHandler { self.platform.spawner().spawn_with_settings(fut, settings); } - fn handle_payment_received(&self, payment_hash: PaymentHash, received_amount: u64, purpose: PaymentPurpose) { + fn handle_payment_claimable(&self, payment_hash: PaymentHash, claimable_amount: u64, purpose: PaymentPurpose) { info!( - "Handling PaymentReceived event for payment_hash: {} with amount {}", + "{} for payment_hash: {} with amount {}", + PAYMENT_CLAIMABLE_LOG, hex::encode(payment_hash.0), - received_amount + claimable_amount ); let db = self.db.clone(); let payment_preimage = match purpose { PaymentPurpose::InvoicePayment { payment_preimage, .. } => match payment_preimage { Some(preimage) => { let fut = async move { - db.update_payment_to_received_in_db(payment_hash, preimage) + db.update_payment_to_claimable_in_db(payment_hash, preimage) .await - .error_log_with_msg("Unable to update received payment info in DB!"); + .error_log_with_msg("Unable to update claimable payment info in DB!"); }; let settings = AbortSettings::default().critical_timout_s(CRITICAL_FUTURE_TIMEOUT); self.platform.spawner().spawn_with_settings(fut, settings); @@ -357,9 +360,9 @@ impl LightningEventHandler { // This is a swap related payment since we don't have the preimage yet None => { let amt_msat = Some( - received_amount + claimable_amount .try_into() - .expect("received_amount shouldn't exceed i64::MAX"), + .expect("claimable_amount shouldn't exceed i64::MAX"), ); let payment_info = PaymentInfo::new( payment_hash, @@ -367,7 +370,7 @@ impl LightningEventHandler { "Swap Payment".into(), amt_msat, ) - .with_status(HTLCStatus::Received); + .with_status(HTLCStatus::Claimable); let fut = async move { db.add_payment_to_db(&payment_info) .await @@ -382,14 +385,14 @@ impl LightningEventHandler { }, PaymentPurpose::SpontaneousPayment(preimage) => { let amt_msat = Some( - received_amount + claimable_amount .try_into() - .expect("received_amount shouldn't exceed i64::MAX"), + .expect("claimable_amount shouldn't exceed i64::MAX"), ); let payment_info = PaymentInfo::new(payment_hash, PaymentType::InboundPayment, "keysend".into(), amt_msat) .with_preimage(preimage) - .with_status(HTLCStatus::Received); + .with_status(HTLCStatus::Claimable); let fut = async move { db.add_payment_to_db(&payment_info) .await @@ -406,7 +409,7 @@ impl LightningEventHandler { fn handle_payment_claimed(&self, payment_hash: PaymentHash, amount_msat: u64) { info!( - "Received an amount of {} millisatoshis for payment hash {}", + "Claimed an amount of {} millisatoshis for payment hash {}", amount_msat, hex::encode(payment_hash.0) ); @@ -457,23 +460,21 @@ impl LightningEventHandler { self.platform.spawner().spawn_with_settings(fut, settings); } - fn handle_channel_closed(&self, channel_id: [u8; 32], user_channel_id: u64, reason: String) { + fn handle_channel_closed(&self, channel_id: [u8; 32], user_channel_id: u128, reason: String) { info!( "Channel: {} closed for the following reason: {}", hex::encode(channel_id), reason ); + let uuid = Uuid::from_u128(user_channel_id); let db = self.db.clone(); let platform = self.platform.clone(); let fut = async move { - if let Err(e) = save_channel_closing_details(db, platform, user_channel_id, reason).await { + if let Err(e) = save_channel_closing_details(db, platform, uuid, reason).await { // This is the case when a channel is closed before funding is broadcasted due to the counterparty disconnecting or other incompatibility issue. if e != SaveChannelClosingError::FundingTxNull.into() { - error!( - "Unable to update channel {} closing details in DB: {}", - user_channel_id, e - ); + error!("Unable to update channel {} closing details in DB: {}", uuid, e); } } }; @@ -620,70 +621,60 @@ impl LightningEventHandler { let channel_manager = self.channel_manager.clone(); let platform = self.platform.clone(); let fut = async move { - if let Ok(last_channel_rpc_id) = db.get_last_channel_rpc_id().await.error_log_passthrough() { - let user_channel_id = last_channel_rpc_id as u64 + 1; - - let trusted_nodes = trusted_nodes.lock().clone(); - let accepted_inbound_channel_with_0conf = trusted_nodes.contains(&counterparty_node_id) - && channel_manager - .accept_inbound_channel_from_trusted_peer_0conf( - &temporary_channel_id, - &counterparty_node_id, - user_channel_id, - ) - .is_ok(); + let uuid = new_uuid(); + let uuid_u128 = uuid.as_u128(); + let trusted_nodes = trusted_nodes.lock().clone(); + let accepted_inbound_channel_with_0conf = trusted_nodes.contains(&counterparty_node_id) + && channel_manager + .accept_inbound_channel_from_trusted_peer_0conf( + &temporary_channel_id, + &counterparty_node_id, + uuid_u128, + ) + .is_ok(); - if accepted_inbound_channel_with_0conf - || channel_manager - .accept_inbound_channel(&temporary_channel_id, &counterparty_node_id, user_channel_id) - .is_ok() + if accepted_inbound_channel_with_0conf + || channel_manager + .accept_inbound_channel(&temporary_channel_id, &counterparty_node_id, uuid_u128) + .is_ok() + { + let is_public = match channel_manager + .list_channels() + .into_iter() + .find(|chan| chan.user_channel_id == uuid_u128) { - let is_public = match channel_manager - .list_channels() - .into_iter() - .find(|chan| chan.user_channel_id == user_channel_id) - { - Some(details) => details.is_public, - None => { - error!( - "Inbound channel {} details should be found by list_channels!", - user_channel_id - ); - return; - }, - }; - - let pending_channel_details = DBChannelDetails::new( - user_channel_id, - temporary_channel_id, - counterparty_node_id, - false, - is_public, - ); - if let Err(e) = db.add_channel_to_db(&pending_channel_details).await { - error!("Unable to add new inbound channel {} to db: {}", user_channel_id, e); - } + Some(details) => details.is_public, + None => { + error!("Inbound channel {} details should be found by list_channels!", uuid); + return; + }, + }; - while let Some(details) = channel_manager - .list_channels() - .into_iter() - .find(|chan| chan.user_channel_id == user_channel_id) - { - if let Some(funding_tx) = details.funding_txo { - let best_block_height = platform.best_block_height(); - db.add_funding_tx_to_db( - user_channel_id as i64, - funding_tx.txid.to_string(), - funding_satoshis as i64, - best_block_height as i64, - ) - .await - .error_log(); - break; - } + let pending_channel_details = + DBChannelDetails::new(uuid, temporary_channel_id, counterparty_node_id, false, is_public); + if let Err(e) = db.add_channel_to_db(&pending_channel_details).await { + error!("Unable to add new inbound channel {} to db: {}", uuid, e); + } - Timer::sleep(TRY_LOOP_INTERVAL).await; + while let Some(details) = channel_manager + .list_channels() + .into_iter() + .find(|chan| chan.user_channel_id == uuid_u128) + { + if let Some(funding_tx) = details.funding_txo { + let best_block_height = platform.best_block_height(); + db.add_funding_tx_to_db( + uuid, + funding_tx.txid.to_string(), + funding_satoshis as i64, + best_block_height as i64, + ) + .await + .error_log(); + break; } + + Timer::sleep(TRY_LOOP_INTERVAL).await; } } }; diff --git a/mm2src/coins/lightning/ln_filesystem_persister.rs b/mm2src/coins/lightning/ln_filesystem_persister.rs index 49ecb111b3..d1444a62a6 100644 --- a/mm2src/coins/lightning/ln_filesystem_persister.rs +++ b/mm2src/coins/lightning/ln_filesystem_persister.rs @@ -12,7 +12,7 @@ use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParam use lightning::util::persist::KVStorePersister; use lightning::util::ser::{ReadableArgs, Writeable}; use mm2_io::fs::{check_dir_operations, invalid_data_err, read_json, write_json}; -use secp256k1v22::PublicKey; +use secp256k1v24::PublicKey; use std::collections::{HashMap, HashSet}; use std::fs; use std::io::{BufReader, BufWriter, Cursor}; diff --git a/mm2src/coins/lightning/ln_p2p.rs b/mm2src/coins/lightning/ln_p2p.rs index c93c017f06..7db3a0b4e2 100644 --- a/mm2src/coins/lightning/ln_p2p.rs +++ b/mm2src/coins/lightning/ln_p2p.rs @@ -5,19 +5,21 @@ use derive_more::Display; use lightning::chain::Access; use lightning::ln::msgs::NetAddress; use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler, SimpleArcPeerManager}; +use lightning::onion_message::SimpleArcOnionMessenger; use lightning::routing::gossip; use lightning_net_tokio::SocketDescriptor; use mm2_net::ip_addr::fetch_external_ip; use rand::RngCore; -use secp256k1v22::{PublicKey, SecretKey}; +use secp256k1v24::PublicKey; use std::net::{IpAddr, Ipv4Addr}; +use std::num::TryFromIntError; use tokio::net::TcpListener; const TRY_RECONNECTING_TO_NODE_INTERVAL: f64 = 60.; const BROADCAST_NODE_ANNOUNCEMENT_INTERVAL: u64 = 600; pub type NetworkGossip = gossip::P2PGossipSync, Arc, Arc>; - +type OnionMessenger = SimpleArcOnionMessenger; pub type PeerManager = SimpleArcPeerManager; @@ -124,7 +126,7 @@ fn netaddress_from_ipaddr(addr: IpAddr, port: u16) -> Vec { } pub async fn ln_node_announcement_loop( - channel_manager: Arc, + peer_manager: Arc, node_name: [u8; 32], node_color: [u8; 3], port: u16, @@ -144,8 +146,8 @@ pub async fn ln_node_announcement_loop( continue; }, }; - let channel_manager = channel_manager.clone(); - async_blocking(move || channel_manager.broadcast_node_announcement(node_color, node_name, addresses)).await; + let peer_manager = peer_manager.clone(); + async_blocking(move || peer_manager.broadcast_node_announcement(node_color, node_name, addresses)).await; Timer::sleep(BROADCAST_NODE_ANNOUNCEMENT_INTERVAL as f64).await; } @@ -181,8 +183,8 @@ pub async fn init_peer_manager( platform: &Arc, listening_port: u16, channel_manager: Arc, + keys_manager: Arc, gossip_sync: Arc, - node_secret: SecretKey, logger: Arc, ) -> EnableLightningResult> { // The set (possibly empty) of socket addresses on which this node accepts incoming connections. @@ -196,18 +198,33 @@ pub async fn init_peer_manager( // ephemeral_random_data is used to derive per-connection ephemeral keys let mut ephemeral_bytes = [0; 32]; rand::thread_rng().fill_bytes(&mut ephemeral_bytes); + let onion_message_handler = Arc::new(OnionMessenger::new( + keys_manager.clone(), + logger.clone(), + IgnoringMessageHandler {}, + )); let lightning_msg_handler = MessageHandler { chan_handler: channel_manager, route_handler: gossip_sync, + onion_message_handler, }; + let node_secret = keys_manager + .get_node_secret(Recipient::Node) + .map_to_mm(|_| EnableLightningError::UnsupportedMode("'start_lightning'".into(), "local node".into()))?; + let current_time: u32 = get_local_duration_since_epoch() + .map_to_mm(|e| EnableLightningError::Internal(e.to_string()))? + .as_secs() + .try_into() + .map_to_mm(|e: TryFromIntError| EnableLightningError::Internal(e.to_string()))?; // IgnoringMessageHandler is used as custom message types (experimental and application-specific messages) is not needed let peer_manager: Arc = Arc::new(PeerManager::new( lightning_msg_handler, node_secret, + current_time, &ephemeral_bytes, logger, - Arc::new(IgnoringMessageHandler {}), + IgnoringMessageHandler {}, )); // Initialize p2p networking diff --git a/mm2src/coins/lightning/ln_platform.rs b/mm2src/coins/lightning/ln_platform.rs index 3444b4cb69..f6bea230ad 100644 --- a/mm2src/coins/lightning/ln_platform.rs +++ b/mm2src/coins/lightning/ln_platform.rs @@ -22,8 +22,9 @@ use lightning::chain::{chaininterface::{BroadcasterInterface, ConfirmationTarget Confirm, Filter, WatchedOutput}; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; use spv_validation::spv_proof::TRY_SPV_PROOF_INTERVAL; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering, Ordering}; +use uuid::Uuid; const CHECK_FOR_NEW_BEST_BLOCK_INTERVAL: f64 = 60.; const TRY_LOOP_INTERVAL: f64 = 60.; @@ -168,7 +169,7 @@ pub struct Platform { /// This cache stores the outputs that the LN node has interest in. pub registered_outputs: PaMutex>, /// This cache stores transactions to be broadcasted once the other node accepts the channel - pub unsigned_funding_txs: PaMutex>, + pub unsigned_funding_txs: PaMutex>, /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation. /// and on [`MmArc::stop`]. pub abortable_system: AbortableQueue, @@ -313,14 +314,14 @@ impl Platform { ) { // Retrieve channel manager transaction IDs to check the chain for un-confirmations let channel_manager_relevant_txids = channel_manager.get_relevant_txids(); - for txid in channel_manager_relevant_txids { + for (txid, _) in channel_manager_relevant_txids { self.process_tx_for_unconfirmation(txid, Arc::clone(&channel_manager)) .await; } // Retrieve chain monitor transaction IDs to check the chain for un-confirmations let chain_monitor_relevant_txids = chain_monitor.get_relevant_txids(); - for txid in chain_monitor_relevant_txids { + for (txid, _) in chain_monitor_relevant_txids { self.process_tx_for_unconfirmation(txid, Arc::clone(&chain_monitor)) .await; } @@ -514,7 +515,9 @@ impl Platform { pub async fn get_channel_closing_tx(&self, channel_details: DBChannelDetails) -> SaveChannelClosingResult { let from_block = channel_details .funding_generated_in_block - .ok_or_else(|| MmError::new(SaveChannelClosingError::BlockHeightNull))?; + .map(|b| b.try_into()) + .transpose()? + .unwrap_or_else(|| self.best_block_height()); let tx_id = channel_details .funding_tx @@ -532,7 +535,7 @@ impl Platform { &funding_tx_bytes.into_vec(), &[], (now_ms() / 1000) + 3600, - from_block.try_into()?, + from_block, &None, TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, ) @@ -597,16 +600,41 @@ impl BroadcasterInterface for Platform { let txid = tx.txid(); let tx_hex = serialize_hex(tx); debug!("Trying to broadcast transaction: {}", tx_hex); + let tx_bytes = match hex::decode(&tx_hex) { + Ok(b) => b, + Err(e) => { + error!("Converting transaction to bytes error:{}", e); + return; + }, + }; - let fut = self.coin.send_raw_tx(&tx_hex); + let platform_coin = self.coin.clone(); let fut = async move { - match fut.compat().await { - Ok(id) => info!("Transaction broadcasted successfully: {:?} ", id), - // TODO: broadcast transaction through p2p network in case of error - Err(e) => error!("Broadcast transaction {} failed: {}", txid, e), + loop { + match platform_coin + .as_ref() + .rpc_client + .send_raw_transaction(tx_bytes.clone().into()) + .compat() + .await + { + Ok(id) => { + info!("Transaction broadcasted successfully: {:?} ", id); + break; + }, + // Todo: broadcast transaction through p2p network instead in case of error + // Todo: I don't want to rely on p2p broadcasting for now since there is no way to know if there are nodes running bitcoin in native mode or not + // Todo: Also we need to make sure that the transaction was broadcasted after relying on the p2p network + Err(e) => { + error!("Broadcast transaction {} failed: {}", txid, e); + if !e.get_inner().is_network_error() { + break; + } + Timer::sleep(TRY_LOOP_INTERVAL).await; + }, + } } }; - self.spawner().spawn(fut); } } @@ -617,33 +645,5 @@ impl Filter for Platform { fn register_tx(&self, txid: &Txid, _script_pubkey: &Script) { self.add_tx(*txid); } // Watches for any transactions that spend this output on-chain - fn register_output(&self, output: WatchedOutput) -> Option<(usize, Transaction)> { - self.add_output(output.clone()); - let block_hash = match output.block_hash { - Some(h) => H256Json::from(h.as_hash().into_inner()), - None => return None, - }; - - // Although this works for both native and electrum clients as the block hash is available, - // the filter interface which includes register_output and register_tx should be used for electrum clients only, - // this is the reason for initializing the filter as an option in the start_lightning function as it will be None - // when implementing lightning for native clients - let output_spend_fut = self.rpc_client().find_output_spend( - h256_from_txid(output.outpoint.txid), - output.script_pubkey.as_ref(), - output.outpoint.index.into(), - BlockHashOrHeight::Hash(block_hash), - ); - let maybe_output_spend_res = - tokio::task::block_in_place(move || output_spend_fut.wait()).error_log_passthrough(); - - if let Ok(Some(spent_output_info)) = maybe_output_spend_res { - match Transaction::try_from(spent_output_info.spending_tx) { - Ok(spending_tx) => return Some((spent_output_info.input_index, spending_tx)), - Err(e) => error!("Can't convert transaction error: {}", e.to_string()), - } - } - - None - } + fn register_output(&self, output: WatchedOutput) { self.add_output(output); } } diff --git a/mm2src/coins/lightning/ln_serialization.rs b/mm2src/coins/lightning/ln_serialization.rs index 511522c328..37ece14bee 100644 --- a/mm2src/coins/lightning/ln_serialization.rs +++ b/mm2src/coins/lightning/ln_serialization.rs @@ -3,11 +3,12 @@ use crate::lightning::ln_platform::h256_json_from_txid; use crate::H256Json; use lightning::chain::channelmonitor::Balance; use lightning::ln::channelmanager::ChannelDetails; -use secp256k1v22::PublicKey; +use secp256k1v24::PublicKey; use serde::{de, Serialize, Serializer}; use std::fmt; use std::net::{SocketAddr, ToSocketAddrs}; use std::str::FromStr; +use uuid::Uuid; // TODO: support connection to onion addresses #[derive(Debug, PartialEq)] @@ -101,7 +102,11 @@ impl<'de> de::Deserialize<'de> for PublicKeyForRPC { #[derive(Clone, Serialize)] pub struct ChannelDetailsForRPC { - pub rpc_channel_id: u64, + /// An internal identifier for the channel that doesn't change throughout the channels lifetime. + pub uuid: Uuid, + /// The channel's ID, prior to funding transaction generation, this is a random 32 bytes, + /// after funding transaction generation, this is the txid of the funding transaction xor the funding transaction output. + /// Note that this means this value is *not* persistent - it can change once during the lifetime of the channel. pub channel_id: H256Json, pub counterparty_node_id: PublicKeyForRPC, pub funding_tx: Option, @@ -112,6 +117,8 @@ pub struct ChannelDetailsForRPC { pub balance_msat: u64, pub outbound_capacity_msat: u64, pub inbound_capacity_msat: u64, + pub current_confirmations: Option, + pub required_confirmations: Option, // Channel is confirmed onchain, this means that funding_locked messages have been exchanged, // the channel is not currently being shut down, and the required confirmation count has been reached. pub is_ready: bool, @@ -125,7 +132,7 @@ pub struct ChannelDetailsForRPC { impl From for ChannelDetailsForRPC { fn from(details: ChannelDetails) -> ChannelDetailsForRPC { ChannelDetailsForRPC { - rpc_channel_id: details.user_channel_id, + uuid: Uuid::from_u128(details.user_channel_id), channel_id: details.channel_id.into(), counterparty_node_id: PublicKeyForRPC(details.counterparty.node_id), funding_tx: details.funding_txo.map(|tx| h256_json_from_txid(tx.txid)), @@ -135,6 +142,8 @@ impl From for ChannelDetailsForRPC { balance_msat: details.balance_msat, outbound_capacity_msat: details.outbound_capacity_msat, inbound_capacity_msat: details.inbound_capacity_msat, + current_confirmations: details.confirmations, + required_confirmations: details.confirmations_required, is_ready: details.is_channel_ready, is_usable: details.is_usable, is_public: details.is_public, @@ -279,7 +288,7 @@ pub enum ClaimableBalance { /// HTLCs which we sent to our counterparty which are claimable after a timeout (less on-chain /// fees) if the counterparty does not know the preimage for the HTLCs. These are somewhat /// likely to be claimed by our counterparty before we do. - MaybeClaimableHTLCAwaitingTimeout { + MaybeTimeoutClaimableHTLC { /// The amount available to claim, in satoshis, excluding the on-chain fees which will be /// required to do so. claimable_amount_satoshis: u64, @@ -287,6 +296,29 @@ pub enum ClaimableBalance { /// done so. claimable_height: u32, }, + /// HTLCs which we received from our counterparty which are claimable with a preimage which we + /// do not currently have. This will only be claimable if we receive the preimage from the node + /// to which we forwarded this HTLC before the timeout. + MaybePreimageClaimableHTLC { + /// The amount potentially available to claim, in satoshis, excluding the on-chain fees + /// which will be required to do so. + claimable_amount_satoshis: u64, + /// The height at which our counterparty will be able to claim the balance if we have not + /// yet received the preimage and claimed it ourselves. + expiry_height: u32, + }, + /// The channel has been closed, and our counterparty broadcasted a revoked commitment + /// transaction. + /// + /// Thus, we're able to claim all outputs in the commitment transaction, one of which has the + /// following amount. + CounterpartyRevokedOutputClaimable { + /// The amount, in satoshis, of the output which we can claim. + /// + /// Note that for outputs from HTLC balances this may be excluding some on-chain fees that + /// were already spent. + claimable_amount_satoshis: u64, + }, } impl From for ClaimableBalance { @@ -311,13 +343,25 @@ impl From for ClaimableBalance { claimable_amount_satoshis, timeout_height, }, - Balance::MaybeClaimableHTLCAwaitingTimeout { + Balance::MaybeTimeoutClaimableHTLC { claimable_amount_satoshis, claimable_height, - } => ClaimableBalance::MaybeClaimableHTLCAwaitingTimeout { + } => ClaimableBalance::MaybeTimeoutClaimableHTLC { claimable_amount_satoshis, claimable_height, }, + Balance::MaybePreimageClaimableHTLC { + claimable_amount_satoshis, + expiry_height, + } => ClaimableBalance::MaybePreimageClaimableHTLC { + claimable_amount_satoshis, + expiry_height, + }, + Balance::CounterpartyRevokedOutputClaimable { + claimable_amount_satoshis, + } => ClaimableBalance::CounterpartyRevokedOutputClaimable { + claimable_amount_satoshis, + }, } } } diff --git a/mm2src/coins/lightning/ln_sql.rs b/mm2src/coins/lightning/ln_sql.rs index b9ac1e2129..9e6088be84 100644 --- a/mm2src/coins/lightning/ln_sql.rs +++ b/mm2src/coins/lightning/ln_sql.rs @@ -4,6 +4,7 @@ use crate::lightning::ln_db::{ChannelType, ChannelVisibility, ClosedChannelsFilt use async_trait::async_trait; use common::{async_blocking, PagingOptionsEnum}; use db_common::owned_named_params; +use db_common::sqlite::rusqlite::types::Type; use db_common::sqlite::rusqlite::{params, Error as SqlError, Row, ToSql, NO_PARAMS}; use db_common::sqlite::sql_builder::SqlBuilder; use db_common::sqlite::{h256_option_slice_from_row, h256_slice_from_row, offset_by_id, query_single_row, @@ -11,9 +12,10 @@ use db_common::sqlite::{h256_option_slice_from_row, h256_slice_from_row, offset_ OwnedSqlNamedParams, SqlNamedParams, SqliteConnShared, CHECK_TABLE_EXISTS_SQL}; use gstuff::now_ms; use lightning::ln::{PaymentHash, PaymentPreimage}; -use secp256k1v22::PublicKey; +use secp256k1v24::PublicKey; use std::convert::TryInto; use std::str::FromStr; +use uuid::Uuid; fn channels_history_table(ticker: &str) -> String { ticker.to_owned() + "_channels_history" } @@ -26,7 +28,7 @@ fn create_channels_history_table_sql(for_coin: &str) -> Result let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( id INTEGER NOT NULL PRIMARY KEY, - rpc_id INTEGER NOT NULL UNIQUE, + uuid VARCHAR(255) NOT NULL UNIQUE, channel_id VARCHAR(255) NOT NULL, counterparty_node_id VARCHAR(255) NOT NULL, funding_tx VARCHAR(255), @@ -79,12 +81,9 @@ fn insert_channel_sql( let table_name = channels_history_table(for_coin); validate_table_name(&table_name)?; - let rpc_id = channel_detail.rpc_id; - let created_at = channel_detail.created_at; - let sql = format!( "INSERT INTO {} ( - rpc_id, + uuid, channel_id, counterparty_node_id, is_outbound, @@ -92,27 +91,24 @@ fn insert_channel_sql( is_closed, created_at ) VALUES ( - :rpc_id, :channel_id, :counterparty_node_id, :is_outbound, :is_public, :is_closed, :created_at + :uuid, :channel_id, :counterparty_node_id, :is_outbound, :is_public, :is_closed, :created_at )", table_name ); let params = owned_named_params! { - ":rpc_id": rpc_id, + ":uuid": channel_detail.uuid.to_string(), ":channel_id": channel_detail.channel_id.clone(), ":counterparty_node_id": channel_detail.counterparty_node_id.clone(), ":is_outbound": channel_detail.is_outbound, ":is_public": channel_detail.is_public, ":is_closed": channel_detail.is_closed, - ":created_at": created_at, + ":created_at": channel_detail.created_at, }; Ok((sql, params)) } -fn insert_payment_sql(for_coin: &str, payment_info: &PaymentInfo) -> Result<(String, OwnedSqlNamedParams), SqlError> { - let table_name = payments_history_table(for_coin); - validate_table_name(&table_name)?; - +fn payment_info_to_owned_named_params(payment_info: &PaymentInfo) -> OwnedSqlNamedParams { let payment_hash = hex::encode(payment_info.payment_hash.0); let (is_outbound, destination) = match payment_info.payment_type { PaymentType::OutboundPayment { destination } => (true, Some(destination.to_string())), @@ -121,6 +117,24 @@ fn insert_payment_sql(for_coin: &str, payment_info: &PaymentInfo) -> Result<(Str let preimage = payment_info.preimage.map(|p| hex::encode(p.0)); let status = payment_info.status.to_string(); + owned_named_params! { + ":payment_hash": payment_hash, + ":destination": destination, + ":description": payment_info.description.clone(), + ":preimage": preimage, + ":amount_msat": payment_info.amt_msat, + ":fee_paid_msat": payment_info.fee_paid_msat, + ":is_outbound": is_outbound, + ":status": status, + ":created_at": payment_info.created_at, + ":last_updated": payment_info.last_updated, + } +} + +fn insert_payment_sql(for_coin: &str, payment_info: &PaymentInfo) -> Result<(String, OwnedSqlNamedParams), SqlError> { + let table_name = payments_history_table(for_coin); + validate_table_name(&table_name)?; + let sql = format!( "INSERT INTO {} ( payment_hash, @@ -139,19 +153,32 @@ fn insert_payment_sql(for_coin: &str, payment_info: &PaymentInfo) -> Result<(Str table_name ); - let params = owned_named_params! { - ":payment_hash": payment_hash, - ":destination": destination, - ":description": payment_info.description.clone(), - ":preimage": preimage, - ":amount_msat": payment_info.amt_msat, - ":fee_paid_msat": payment_info.fee_paid_msat, - ":is_outbound": is_outbound, - ":status": status, - ":created_at": payment_info.created_at, - ":last_updated": payment_info.last_updated, - }; - Ok((sql, params)) + Ok((sql, payment_info_to_owned_named_params(payment_info))) +} + +fn upsert_payment_sql(for_coin: &str, payment_info: &PaymentInfo) -> Result<(String, OwnedSqlNamedParams), SqlError> { + let table_name = payments_history_table(for_coin); + validate_table_name(&table_name)?; + + let sql = format!( + "INSERT OR REPLACE INTO {} ( + payment_hash, + destination, + description, + preimage, + amount_msat, + fee_paid_msat, + is_outbound, + status, + created_at, + last_updated + ) VALUES ( + :payment_hash, :destination, :description, :preimage, :amount_msat, :fee_paid_msat, :is_outbound, :status, :created_at, :last_updated + )", + table_name + ); + + Ok((sql, payment_info_to_owned_named_params(payment_info))) } fn update_payment_preimage_sql(for_coin: &str) -> Result { @@ -186,7 +213,7 @@ fn update_payment_status_sql(for_coin: &str) -> Result { Ok(sql) } -fn update_received_payment_sql(for_coin: &str) -> Result { +fn update_claimable_payment_sql(for_coin: &str) -> Result { let table_name = payments_history_table(for_coin); validate_table_name(&table_name)?; @@ -221,13 +248,13 @@ fn update_sent_payment_sql(for_coin: &str) -> Result { Ok(sql) } -fn select_channel_by_rpc_id_sql(for_coin: &str) -> Result { +fn select_channel_by_uuid_sql(for_coin: &str) -> Result { let table_name = channels_history_table(for_coin); validate_table_name(&table_name)?; let sql = format!( "SELECT - rpc_id, + uuid, channel_id, counterparty_node_id, funding_tx, @@ -245,7 +272,7 @@ fn select_channel_by_rpc_id_sql(for_coin: &str) -> Result { FROM {} WHERE - rpc_id=?1", + uuid=?1", table_name ); @@ -280,7 +307,8 @@ fn select_payment_by_hash_sql(for_coin: &str) -> Result { fn channel_details_from_row(row: &Row<'_>) -> Result { let channel_details = DBChannelDetails { - rpc_id: row.get(0)?, + uuid: Uuid::parse_str(&row.get::<_, String>(0)?) + .map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e)))?, channel_id: row.get(1)?, counterparty_node_id: row.get(2)?, funding_tx: row.get(3)?, @@ -323,15 +351,6 @@ fn payment_info_from_row(row: &Row<'_>) -> Result { Ok(payment_info) } -fn get_last_channel_rpc_id_sql(for_coin: &str) -> Result { - let table_name = channels_history_table(for_coin); - validate_table_name(&table_name)?; - - let sql = format!("SELECT IFNULL(MAX(rpc_id), 0) FROM {};", table_name); - - Ok(sql) -} - fn update_funding_tx_sql(for_coin: &str) -> Result { let table_name = channels_history_table(for_coin); validate_table_name(&table_name)?; @@ -342,7 +361,7 @@ fn update_funding_tx_sql(for_coin: &str) -> Result { funding_value = ?2, funding_generated_in_block = ?3 WHERE - rpc_id = ?4;", + uuid = ?4;", table_name ); @@ -366,7 +385,7 @@ fn update_channel_to_closed_sql(for_coin: &str) -> Result { validate_table_name(&table_name)?; let sql = format!( - "UPDATE {} SET closure_reason = ?1, is_closed = ?2, closed_at = ?3 WHERE rpc_id = ?4;", + "UPDATE {} SET closure_reason = ?1, is_closed = ?2, closed_at = ?3 WHERE uuid = ?4;", table_name ); @@ -377,7 +396,7 @@ fn update_closing_tx_sql(for_coin: &str) -> Result { let table_name = channels_history_table(for_coin); validate_table_name(&table_name)?; - let sql = format!("UPDATE {} SET closing_tx = ?1 WHERE rpc_id = ?2;", table_name); + let sql = format!("UPDATE {} SET closing_tx = ?1 WHERE uuid = ?2;", table_name); Ok(sql) } @@ -393,7 +412,7 @@ fn get_channels_builder_preimage(for_coin: &str) -> Result fn add_fields_to_get_channels_sql_builder(sql_builder: &mut SqlBuilder) { sql_builder - .field("rpc_id") + .field("uuid") .field("channel_id") .field("counterparty_node_id") .field("funding_tx") @@ -633,18 +652,6 @@ impl LightningDB for SqliteLightningDB { .await } - async fn get_last_channel_rpc_id(&self) -> Result { - let sql = get_last_channel_rpc_id_sql(self.db_ticker.as_str())?; - let sqlite_connection = self.sqlite_connection.clone(); - - async_blocking(move || { - let conn = sqlite_connection.lock().unwrap(); - let count: u32 = conn.query_row(&sql, NO_PARAMS, |r| r.get(0))?; - Ok(count) - }) - .await - } - async fn add_channel_to_db(&self, details: &DBChannelDetails) -> Result<(), Self::Error> { let for_coin = self.db_ticker.clone(); let (sql, params) = insert_channel_sql(&for_coin, details)?; @@ -660,7 +667,7 @@ impl LightningDB for SqliteLightningDB { async fn add_funding_tx_to_db( &self, - rpc_id: i64, + uuid: Uuid, funding_tx: String, funding_value: i64, funding_generated_in_block: i64, @@ -671,7 +678,7 @@ impl LightningDB for SqliteLightningDB { async_blocking(move || { let mut conn = sqlite_connection.lock().unwrap(); let sql_transaction = conn.transaction()?; - let params = params!(funding_tx, funding_value, funding_generated_in_block, rpc_id); + let params = params!(funding_tx, funding_value, funding_generated_in_block, uuid.to_string()); sql_transaction.execute(&update_funding_tx_sql(&for_coin)?, params)?; sql_transaction.commit()?; Ok(()) @@ -696,7 +703,7 @@ impl LightningDB for SqliteLightningDB { async fn update_channel_to_closed( &self, - rpc_id: i64, + uuid: Uuid, closure_reason: String, closed_at: i64, ) -> Result<(), Self::Error> { @@ -707,7 +714,7 @@ impl LightningDB for SqliteLightningDB { async_blocking(move || { let mut conn = sqlite_connection.lock().unwrap(); let sql_transaction = conn.transaction()?; - let params = params!(closure_reason, is_closed, closed_at, rpc_id); + let params = params!(closure_reason, is_closed, closed_at, uuid.to_string()); sql_transaction.execute(&update_channel_to_closed_sql(&for_coin)?, params)?; sql_transaction.commit()?; Ok(()) @@ -735,14 +742,14 @@ impl LightningDB for SqliteLightningDB { .await } - async fn add_closing_tx_to_db(&self, rpc_id: i64, closing_tx: String) -> Result<(), Self::Error> { + async fn add_closing_tx_to_db(&self, uuid: Uuid, closing_tx: String) -> Result<(), Self::Error> { let for_coin = self.db_ticker.clone(); let sqlite_connection = self.sqlite_connection.clone(); async_blocking(move || { let mut conn = sqlite_connection.lock().unwrap(); let sql_transaction = conn.transaction()?; - let params = params!(closing_tx, rpc_id); + let params = params!(closing_tx, uuid.to_string()); sql_transaction.execute(&update_closing_tx_sql(&for_coin)?, params)?; sql_transaction.commit()?; Ok(()) @@ -770,9 +777,9 @@ impl LightningDB for SqliteLightningDB { .await } - async fn get_channel_from_db(&self, rpc_id: u64) -> Result, Self::Error> { - let params = [rpc_id.to_string()]; - let sql = select_channel_by_rpc_id_sql(self.db_ticker.as_str())?; + async fn get_channel_from_db(&self, uuid: Uuid) -> Result, Self::Error> { + let params = [uuid.to_string()]; + let sql = select_channel_by_uuid_sql(self.db_ticker.as_str())?; let sqlite_connection = self.sqlite_connection.clone(); async_blocking(move || { @@ -785,7 +792,7 @@ impl LightningDB for SqliteLightningDB { async fn get_closed_channels_by_filter( &self, filter: Option, - paging: PagingOptionsEnum, + paging: PagingOptionsEnum, limit: usize, ) -> Result { let mut sql_builder = get_channels_builder_preimage(self.db_ticker.as_str())?; @@ -802,10 +809,10 @@ impl LightningDB for SqliteLightningDB { let offset = match paging { PagingOptionsEnum::PageNumber(page) => (page.get() - 1) * limit, - PagingOptionsEnum::FromId(rpc_id) => { - let params = [rpc_id as u32]; + PagingOptionsEnum::FromId(uuid) => { + let params = [uuid.to_string()]; let maybe_offset = - offset_by_id(&conn, &sql_builder, params, "rpc_id", "closed_at DESC", "rpc_id = ?1")?; + offset_by_id(&conn, &sql_builder, params, "uuid", "closed_at DESC", "uuid = ?1")?; match maybe_offset { Some(offset) => offset, None => { @@ -854,6 +861,19 @@ impl LightningDB for SqliteLightningDB { .await } + async fn add_or_update_payment_in_db(&self, info: &PaymentInfo) -> Result<(), Self::Error> { + let for_coin = self.db_ticker.clone(); + let (sql, params) = upsert_payment_sql(&for_coin, info)?; + + let sqlite_connection = self.sqlite_connection.clone(); + async_blocking(move || { + let conn = sqlite_connection.lock().unwrap(); + conn.execute_named(&sql, ¶ms.as_sql_named_params())?; + Ok(()) + }) + .await + } + async fn update_payment_preimage_in_db( &self, hash: PaymentHash, @@ -894,14 +914,14 @@ impl LightningDB for SqliteLightningDB { .await } - async fn update_payment_to_received_in_db( + async fn update_payment_to_claimable_in_db( &self, hash: PaymentHash, preimage: PaymentPreimage, ) -> Result<(), Self::Error> { let for_coin = self.db_ticker.clone(); let preimage = hex::encode(preimage.0); - let status = HTLCStatus::Received.to_string(); + let status = HTLCStatus::Claimable.to_string(); let last_updated = (now_ms() / 1000) as i64; let payment_hash = hex::encode(hash.0); @@ -910,7 +930,7 @@ impl LightningDB for SqliteLightningDB { let mut conn = sqlite_connection.lock().unwrap(); let sql_transaction = conn.transaction()?; let params = params!(preimage, status, last_updated, payment_hash); - sql_transaction.execute(&update_received_payment_sql(&for_coin)?, params)?; + sql_transaction.execute(&update_claimable_payment_sql(&for_coin)?, params)?; sql_transaction.commit()?; Ok(()) }) @@ -1025,11 +1045,11 @@ impl LightningDB for SqliteLightningDB { mod tests { use super::*; use crate::lightning::ln_db::DBChannelDetails; - use common::{block_on, now_ms}; + use common::{block_on, new_uuid, now_ms}; use db_common::sqlite::rusqlite::Connection; use rand::distributions::Alphanumeric; use rand::{Rng, RngCore}; - use secp256k1v22::{Secp256k1, SecretKey}; + use secp256k1v24::{Secp256k1, SecretKey}; use std::num::NonZeroUsize; use std::sync::{Arc, Mutex}; @@ -1038,9 +1058,9 @@ mod tests { let mut channels = vec![]; let s = Secp256k1::new(); let mut bytes = [0; 32]; - for i in 0..num { + for _i in 0..num { let details = DBChannelDetails { - rpc_id: (i + 1) as i64, + uuid: new_uuid(), channel_id: { rng.fill_bytes(&mut bytes); hex::encode(bytes) @@ -1157,37 +1177,30 @@ mod tests { block_on(db.init_db()).unwrap(); - let last_channel_rpc_id = block_on(db.get_last_channel_rpc_id()).unwrap(); - assert_eq!(last_channel_rpc_id, 0); - - let channel = block_on(db.get_channel_from_db(1)).unwrap(); + let uuid_1 = new_uuid(); + let channel = block_on(db.get_channel_from_db(uuid_1)).unwrap(); assert!(channel.is_none()); let mut expected_channel_details = DBChannelDetails::new( - 1, + uuid_1, [0; 32], PublicKey::from_str("038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9").unwrap(), true, true, ); block_on(db.add_channel_to_db(&expected_channel_details)).unwrap(); - let last_channel_rpc_id = block_on(db.get_last_channel_rpc_id()).unwrap(); - assert_eq!(last_channel_rpc_id, 1); - - let actual_channel_details = block_on(db.get_channel_from_db(1)).unwrap().unwrap(); + let actual_channel_details = block_on(db.get_channel_from_db(uuid_1)).unwrap().unwrap(); assert_eq!(expected_channel_details, actual_channel_details); - // must fail because we are adding channel with the same rpc_id + // must fail because we are adding channel with the same uuid block_on(db.add_channel_to_db(&expected_channel_details)).unwrap_err(); - assert_eq!(last_channel_rpc_id, 1); - expected_channel_details.rpc_id = 2; + let uuid_2 = new_uuid(); + expected_channel_details.uuid = uuid_2; block_on(db.add_channel_to_db(&expected_channel_details)).unwrap(); - let last_channel_rpc_id = block_on(db.get_last_channel_rpc_id()).unwrap(); - assert_eq!(last_channel_rpc_id, 2); block_on(db.add_funding_tx_to_db( - 2, + uuid_2, "9cdafd6d42dcbdc06b0b5bce1866deb82630581285bbfb56870577300c0a8c6e".into(), 3000, 50000, @@ -1198,7 +1211,7 @@ mod tests { expected_channel_details.funding_value = Some(3000); expected_channel_details.funding_generated_in_block = Some(50000); - let actual_channel_details = block_on(db.get_channel_from_db(2)).unwrap().unwrap(); + let actual_channel_details = block_on(db.get_channel_from_db(uuid_2)).unwrap().unwrap(); assert_eq!(expected_channel_details, actual_channel_details); block_on(db.update_funding_tx_block_height( @@ -1208,16 +1221,17 @@ mod tests { .unwrap(); expected_channel_details.funding_generated_in_block = Some(50001); - let actual_channel_details = block_on(db.get_channel_from_db(2)).unwrap().unwrap(); + let actual_channel_details = block_on(db.get_channel_from_db(uuid_2)).unwrap().unwrap(); assert_eq!(expected_channel_details, actual_channel_details); let current_time = (now_ms() / 1000) as i64; - block_on(db.update_channel_to_closed(2, "the channel was cooperatively closed".into(), current_time)).unwrap(); + block_on(db.update_channel_to_closed(uuid_2, "the channel was cooperatively closed".into(), current_time)) + .unwrap(); expected_channel_details.closure_reason = Some("the channel was cooperatively closed".into()); expected_channel_details.is_closed = true; expected_channel_details.closed_at = Some(current_time); - let actual_channel_details = block_on(db.get_channel_from_db(2)).unwrap().unwrap(); + let actual_channel_details = block_on(db.get_channel_from_db(uuid_2)).unwrap().unwrap(); assert_eq!(expected_channel_details, actual_channel_details); let closed_channels = @@ -1226,7 +1240,7 @@ mod tests { assert_eq!(expected_channel_details, closed_channels.channels[0]); block_on(db.update_channel_to_closed( - 1, + uuid_1, "the channel was cooperatively closed".into(), (now_ms() / 1000) as i64, )) @@ -1239,7 +1253,7 @@ mod tests { assert_eq!(actual_channels.len(), 1); block_on(db.add_closing_tx_to_db( - 2, + uuid_2, "5557df9ad2c9b3c57a4df8b4a7da0b7a6f4e923b4a01daa98bf9e5a3b33e9c8f".into(), )) .unwrap(); @@ -1249,7 +1263,7 @@ mod tests { let actual_channels = block_on(db.get_closed_channels_with_no_closing_tx()).unwrap(); assert!(actual_channels.is_empty()); - let actual_channel_details = block_on(db.get_channel_from_db(2)).unwrap().unwrap(); + let actual_channel_details = block_on(db.get_channel_from_db(uuid_2)).unwrap().unwrap(); assert_eq!(expected_channel_details, actual_channel_details); block_on(db.add_claiming_tx_to_db( @@ -1262,7 +1276,7 @@ mod tests { Some("97f061634a4a7b0b0c2b95648f86b1c39b95e0cf5073f07725b7143c095b612a".into()); expected_channel_details.claimed_balance = Some(2000.333333); - let actual_channel_details = block_on(db.get_channel_from_db(2)).unwrap().unwrap(); + let actual_channel_details = block_on(db.get_channel_from_db(uuid_2)).unwrap().unwrap(); assert_eq!(expected_channel_details, actual_channel_details); } @@ -1294,13 +1308,21 @@ mod tests { let actual_payment_info = block_on(db.get_payment_from_db(PaymentHash([0; 32]))).unwrap().unwrap(); assert_eq!(expected_payment_info, actual_payment_info); + // Test add_or_update_payment_in_db + expected_payment_info.status = HTLCStatus::Succeeded; + block_on(db.add_payment_to_db(&expected_payment_info)).unwrap_err(); + block_on(db.add_or_update_payment_in_db(&expected_payment_info)).unwrap(); + + let actual_payment_info = block_on(db.get_payment_from_db(PaymentHash([0; 32]))).unwrap().unwrap(); + assert_eq!(expected_payment_info, actual_payment_info); + + // Add another payment to DB expected_payment_info.payment_hash = PaymentHash([1; 32]); expected_payment_info.payment_type = PaymentType::OutboundPayment { destination: PublicKey::from_str("038863cf8ab91046230f561cd5b386cbff8309fa02e3f0c3ed161a3aeb64a643b9") .unwrap(), }; expected_payment_info.amt_msat = None; - expected_payment_info.status = HTLCStatus::Succeeded; expected_payment_info.last_updated = (now_ms() / 1000) as i64; block_on(db.add_payment_to_db(&expected_payment_info)).unwrap(); @@ -1328,9 +1350,9 @@ mod tests { // Test update_payment_to_received_in_db let expected_preimage = PaymentPreimage([5; 32]); - block_on(db.update_payment_to_received_in_db(PaymentHash([1; 32]), expected_preimage)).unwrap(); + block_on(db.update_payment_to_claimable_in_db(PaymentHash([1; 32]), expected_preimage)).unwrap(); let payment_after_update = block_on(db.get_payment_from_db(PaymentHash([1; 32]))).unwrap().unwrap(); - assert_eq!(payment_after_update.status, HTLCStatus::Received); + assert_eq!(payment_after_update.status, HTLCStatus::Claimable); assert_eq!(payment_after_update.preimage.unwrap(), expected_preimage); // Test update_payment_to_sent_in_db @@ -1480,14 +1502,14 @@ mod tests { for channel in channels { block_on(db.add_channel_to_db(&channel)).unwrap(); block_on(db.add_funding_tx_to_db( - channel.rpc_id, + channel.uuid, channel.funding_tx.unwrap(), channel.funding_value.unwrap(), channel.funding_generated_in_block.unwrap(), )) .unwrap(); - block_on(db.update_channel_to_closed(channel.rpc_id, channel.closure_reason.unwrap(), 1655806080)).unwrap(); - block_on(db.add_closing_tx_to_db(channel.rpc_id, channel.closing_tx.clone().unwrap())).unwrap(); + block_on(db.update_channel_to_closed(channel.uuid, channel.closure_reason.unwrap(), 1655806080)).unwrap(); + block_on(db.add_closing_tx_to_db(channel.uuid, channel.closing_tx.clone().unwrap())).unwrap(); block_on(db.add_claiming_tx_to_db( channel.closing_tx.unwrap(), channel.claiming_tx.unwrap(), @@ -1526,17 +1548,6 @@ mod tests { assert_eq!(100, result.total); assert_eq!(expected_channels, actual_channels); - let from_rpc_id = 20; - let paging = PagingOptionsEnum::FromId(from_rpc_id); - let limit = 3; - - let result = block_on(db.get_closed_channels_by_filter(None, paging, limit)).unwrap(); - - let expected_channels = channels[20..23].to_vec(); - let actual_channels = result.channels; - - assert_eq!(expected_channels, actual_channels); - let mut filter = ClosedChannelsFilter { channel_id: None, counterparty_node_id: None, diff --git a/mm2src/coins/lightning/ln_storage.rs b/mm2src/coins/lightning/ln_storage.rs index 610720bc3f..9ece4d2e8f 100644 --- a/mm2src/coins/lightning/ln_storage.rs +++ b/mm2src/coins/lightning/ln_storage.rs @@ -4,7 +4,7 @@ use common::log::LogState; use lightning::routing::gossip; use lightning::routing::scoring::ProbabilisticScorer; use parking_lot::Mutex as PaMutex; -use secp256k1v22::PublicKey; +use secp256k1v24::PublicKey; use std::collections::{HashMap, HashSet}; use std::net::SocketAddr; use std::sync::{Arc, Mutex}; diff --git a/mm2src/coins/lightning/ln_utils.rs b/mm2src/coins/lightning/ln_utils.rs index eeb7e04203..8ab92629e7 100644 --- a/mm2src/coins/lightning/ln_utils.rs +++ b/mm2src/coins/lightning/ln_utils.rs @@ -9,15 +9,15 @@ use bitcoin_hashes::{sha256d, Hash}; use common::executor::SpawnFuture; use common::log::LogState; use lightning::chain::keysinterface::{InMemorySigner, KeysManager}; -use lightning::chain::{chainmonitor, BestBlock, Watch}; +use lightning::chain::{chainmonitor, BestBlock, ChannelMonitorUpdateStatus, Watch}; use lightning::ln::channelmanager::{ChainParameters, ChannelManagerReadArgs, PaymentId, PaymentSendFailure, SimpleArcChannelManager}; use lightning::routing::gossip::RoutingFees; -use lightning::routing::router::{PaymentParameters, RouteHint, RouteHintHop, RouteParameters}; +use lightning::routing::router::{PaymentParameters, RouteHint, RouteHintHop, RouteParameters, Router as RouterTrait}; use lightning::util::config::UserConfig; use lightning::util::errors::APIError; use lightning::util::ser::ReadableArgs; -use lightning_invoice::payment::{Payer, PaymentError as InvoicePaymentError, Router as RouterTrait}; +use lightning_invoice::payment::{Payer, PaymentError as InvoicePaymentError}; use mm2_core::mm_ctx::MmArc; use std::collections::hash_map::Entry; use std::fs::File; @@ -36,7 +36,7 @@ pub type ChainMonitor = chainmonitor::ChainMonitor< >; pub type ChannelManager = SimpleArcChannelManager; -pub type Router = DefaultRouter, Arc>; +pub type Router = DefaultRouter, Arc, Arc>; #[inline] fn ln_data_dir(ctx: &MmArc, ticker: &str) -> PathBuf { ctx.dbdir().join("LIGHTNING").join(ticker) } @@ -203,12 +203,15 @@ pub async fn init_channel_manager( for (_, channel_monitor) in channelmonitors.into_iter() { let funding_outpoint = channel_monitor.get_funding_txo().0; let chain_monitor = chain_monitor.clone(); - async_blocking(move || { - chain_monitor - .watch_channel(funding_outpoint, channel_monitor) - .map_to_mm(|e| EnableLightningError::IOError(format!("{:?}", e))) - }) - .await?; + if let ChannelMonitorUpdateStatus::PermanentFailure = + async_blocking(move || chain_monitor.watch_channel(funding_outpoint, channel_monitor)).await + { + let channel_id = hex::encode(funding_outpoint.to_channel_id()); + return MmError::err(EnableLightningError::IOError(format!( + "Failure to persist channel: {}!", + channel_id + ))); + } } channel_manager } else { @@ -347,7 +350,6 @@ impl From for PaymentError { pub(crate) fn pay_invoice_with_max_total_cltv_expiry_delta( channel_manager: Arc, router: Arc, - scorer: Arc, invoice: &Invoice, max_total_cltv_expiry_delta: u32, ) -> Result { @@ -370,21 +372,12 @@ pub(crate) fn pay_invoice_with_max_total_cltv_expiry_delta( final_cltv_expiry_delta: invoice.min_final_cltv_expiry() as u32, }; - pay_internal( - channel_manager, - router, - scorer, - &route_params, - invoice, - &mut 0, - &mut Vec::new(), - ) + pay_internal(channel_manager, router, &route_params, invoice, &mut 0, &mut Vec::new()) } fn pay_internal( channel_manager: Arc, router: Arc, - scorer: Arc, params: &RouteParameters, invoice: &Invoice, attempts: &mut usize, @@ -392,34 +385,36 @@ fn pay_internal( ) -> Result { let payer = channel_manager.node_id(); let first_hops = channel_manager.first_hops(); - let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner()); - // Todo: Routes should be checked before order matching also, this might require routing hints to be shared when matching orders. Just-in-time channels can solve this issue as well. + let payment_hash_inner = invoice.payment_hash().into_inner(); + let payment_hash = PaymentHash(payment_hash_inner); + let payment_id = PaymentId(payment_hash_inner); + // Todo: Would be better to implement pay_invoice_with_max_total_cltv_expiry_delta in rust-lightning + let inflight_htlcs = channel_manager.compute_inflight_htlcs(); let route = router .find_route( &payer, params, - &payment_hash, Some(&first_hops.iter().collect::>()), - &scorer.lock().unwrap(), + inflight_htlcs, ) .map_err(InvoicePaymentError::Routing)?; let payment_secret = Some(*invoice.payment_secret()); - match channel_manager.send_payment(&route, payment_hash, &payment_secret) { - Ok(payment_id) => Ok(payment_id), + match channel_manager.send_payment(&route, payment_hash, &payment_secret, payment_id) { + Ok(()) => Ok(payment_id), Err(e) => match e { PaymentSendFailure::ParameterError(_) => Err(e), PaymentSendFailure::PathParameterError(_) => Err(e), - PaymentSendFailure::AllFailedRetrySafe(err) => { + PaymentSendFailure::DuplicatePayment => Err(e), + PaymentSendFailure::AllFailedResendSafe(err) => { if *attempts > PAYMENT_RETRY_ATTEMPTS { - Err(PaymentSendFailure::AllFailedRetrySafe(errors.to_vec())) + Err(PaymentSendFailure::AllFailedResendSafe(errors.to_vec())) } else { *attempts += 1; errors.extend(err); Ok(pay_internal( channel_manager, router, - scorer, params, invoice, attempts, @@ -437,16 +432,7 @@ fn pay_internal( // recipient may misbehave and claim the funds, at which point we have to // consider the payment sent, so return `Ok()` here, ignoring any retry // errors. - let _ = retry_payment( - channel_manager, - router, - scorer, - payment_id, - payment_hash, - &retry_data, - &mut 0, - errors, - ); + let _ = retry_payment(channel_manager, router, payment_id, &retry_data, &mut 0, errors); Ok(payment_id) } else { // This may happen if we send a payment and some paths fail, but @@ -465,59 +451,39 @@ fn pay_internal( fn retry_payment( channel_manager: Arc, router: Arc, - scorer: Arc, payment_id: PaymentId, - payment_hash: PaymentHash, params: &RouteParameters, attempts: &mut usize, errors: &mut Vec, ) -> Result<(), PaymentError> { let payer = channel_manager.node_id(); let first_hops = channel_manager.first_hops(); + let inflight_htlcs = channel_manager.compute_inflight_htlcs(); let route = router .find_route( &payer, params, - &payment_hash, Some(&first_hops.iter().collect::>()), - &scorer.lock().unwrap(), + inflight_htlcs, ) .map_err(InvoicePaymentError::Routing)?; match channel_manager.retry_payment(&route, payment_id) { Ok(()) => Ok(()), - Err(PaymentSendFailure::AllFailedRetrySafe(err)) => { + Err(PaymentSendFailure::AllFailedResendSafe(err)) => { if *attempts > PAYMENT_RETRY_ATTEMPTS { - let e = PaymentSendFailure::AllFailedRetrySafe(errors.to_vec()); + let e = PaymentSendFailure::AllFailedResendSafe(errors.to_vec()); Err(InvoicePaymentError::Sending(e).into()) } else { *attempts += 1; errors.extend(err); - retry_payment( - channel_manager, - router, - scorer, - payment_id, - payment_hash, - params, - attempts, - errors, - ) + retry_payment(channel_manager, router, payment_id, params, attempts, errors) } }, Err(PaymentSendFailure::PartialFailure { failed_paths_retry, .. }) => { if let Some(retry) = failed_paths_retry { // Always return Ok for the same reason as noted in pay_internal. - let _ = retry_payment( - channel_manager, - router, - scorer, - payment_id, - payment_hash, - &retry, - attempts, - errors, - ); + let _ = retry_payment(channel_manager, router, payment_id, &retry, attempts, errors); } Ok(()) }, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 55d2bd7680..1dd6d0d12f 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2118,11 +2118,18 @@ pub trait MmCoin: /// The minimum number of confirmations at which a transaction is considered mature. fn mature_confirmations(&self) -> Option; - /// Get some of the coin config info in serialized format for p2p messaging. - fn coin_protocol_info(&self) -> Vec; + /// Get some of the coin protocol related info in serialized format for p2p messaging. + fn coin_protocol_info(&self, amount_to_receive: Option) -> Vec; /// Check if serialized coin protocol info is supported by current version. - fn is_coin_protocol_supported(&self, info: &Option>) -> bool; + /// Can also be used to check if orders can be matched or not. + fn is_coin_protocol_supported( + &self, + info: &Option>, + amount_to_send: Option, + locktime: u64, + is_maker: bool, + ) -> bool; /// Abort all coin related futures on coin deactivation. fn on_disabled(&self) -> Result<(), AbortedError>; diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 2bb6fe9a76..e8bf41228a 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1447,9 +1447,17 @@ impl MmCoin for Qrc20Coin { fn mature_confirmations(&self) -> Option { Some(self.utxo.conf.mature_confirmations) } - fn coin_protocol_info(&self) -> Vec { utxo_common::coin_protocol_info(self) } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { + utxo_common::coin_protocol_info(self) + } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { + fn is_coin_protocol_supported( + &self, + info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { utxo_common::is_coin_protocol_supported(self, info) } diff --git a/mm2src/coins/rpc_command/lightning/close_channel.rs b/mm2src/coins/rpc_command/lightning/close_channel.rs index a3fa05644c..716b7db969 100644 --- a/mm2src/coins/rpc_command/lightning/close_channel.rs +++ b/mm2src/coins/rpc_command/lightning/close_channel.rs @@ -3,6 +3,7 @@ use common::{async_blocking, HttpStatusCode}; use http::StatusCode; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use uuid::Uuid; type CloseChannelResult = Result>; @@ -13,8 +14,8 @@ pub enum CloseChannelError { UnsupportedCoin(String), #[display(fmt = "No such coin {}", _0)] NoSuchCoin(String), - #[display(fmt = "No such channel with rpc_channel_id {}", _0)] - NoSuchChannel(u64), + #[display(fmt = "No such channel with uuid {}", _0)] + NoSuchChannel(Uuid), #[display(fmt = "Closing channel error: {}", _0)] CloseChannelError(String), } @@ -40,7 +41,7 @@ impl From for CloseChannelError { #[derive(Deserialize)] pub struct CloseChannelReq { pub coin: String, - pub rpc_channel_id: u64, + pub uuid: Uuid, #[serde(default)] pub force_close: bool, } @@ -52,9 +53,9 @@ pub async fn close_channel(ctx: MmArc, req: CloseChannelReq) -> CloseChannelResu }; let channel_details = ln_coin - .get_channel_by_rpc_id(req.rpc_channel_id) + .get_channel_by_uuid(req.uuid) .await - .ok_or(CloseChannelError::NoSuchChannel(req.rpc_channel_id))?; + .ok_or(CloseChannelError::NoSuchChannel(req.uuid))?; let channel_id = channel_details.channel_id; let counterparty_node_id = channel_details.counterparty.node_id; @@ -76,8 +77,5 @@ pub async fn close_channel(ctx: MmArc, req: CloseChannelReq) -> CloseChannelResu .await?; } - Ok(format!( - "Initiated closing of channel with rpc_channel_id: {}", - req.rpc_channel_id - )) + Ok(format!("Initiated closing of channel with uuid: {}", req.uuid)) } diff --git a/mm2src/coins/rpc_command/lightning/generate_invoice.rs b/mm2src/coins/rpc_command/lightning/generate_invoice.rs index 94740793eb..83d0fc41f6 100644 --- a/mm2src/coins/rpc_command/lightning/generate_invoice.rs +++ b/mm2src/coins/rpc_command/lightning/generate_invoice.rs @@ -92,6 +92,7 @@ pub async fn generate_invoice( let network = ln_coin.platform.network.clone().into(); let channel_manager = ln_coin.channel_manager.clone(); let keys_manager = ln_coin.keys_manager.clone(); + let logger = ln_coin.logger.clone(); let amount_in_msat = req.amount_in_msat; let description = req.description.clone(); let expiry = req.expiry.unwrap_or(DEFAULT_INVOICE_EXPIRY); @@ -99,6 +100,7 @@ pub async fn generate_invoice( create_invoice_from_channelmanager( &channel_manager, keys_manager, + logger, network, amount_in_msat, description, diff --git a/mm2src/coins/rpc_command/lightning/get_channel_details.rs b/mm2src/coins/rpc_command/lightning/get_channel_details.rs index f6c18ae633..c96cf06e57 100644 --- a/mm2src/coins/rpc_command/lightning/get_channel_details.rs +++ b/mm2src/coins/rpc_command/lightning/get_channel_details.rs @@ -6,6 +6,7 @@ use db_common::sqlite::rusqlite::Error as SqlError; use http::StatusCode; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use uuid::Uuid; type GetChannelDetailsResult = Result>; @@ -16,8 +17,8 @@ pub enum GetChannelDetailsError { UnsupportedCoin(String), #[display(fmt = "No such coin {}", _0)] NoSuchCoin(String), - #[display(fmt = "Channel with rpc id: {} is not found", _0)] - NoSuchChannel(u64), + #[display(fmt = "Channel with uuid: {} is not found", _0)] + NoSuchChannel(Uuid), #[display(fmt = "DB error {}", _0)] DbError(String), } @@ -47,7 +48,7 @@ impl From for GetChannelDetailsError { #[derive(Deserialize)] pub struct GetChannelDetailsRequest { pub coin: String, - pub rpc_channel_id: u64, + pub uuid: Uuid, } #[derive(Serialize)] @@ -66,14 +67,14 @@ pub async fn get_channel_details( e => return MmError::err(GetChannelDetailsError::UnsupportedCoin(e.ticker().to_string())), }; - let channel_details = match ln_coin.get_channel_by_rpc_id(req.rpc_channel_id).await { + let channel_details = match ln_coin.get_channel_by_uuid(req.uuid).await { Some(details) => GetChannelDetailsResponse::Open(details.into()), None => GetChannelDetailsResponse::Closed( ln_coin .db - .get_channel_from_db(req.rpc_channel_id) + .get_channel_from_db(req.uuid) .await? - .ok_or(GetChannelDetailsError::NoSuchChannel(req.rpc_channel_id))?, + .ok_or(GetChannelDetailsError::NoSuchChannel(req.uuid))?, ), }; diff --git a/mm2src/coins/rpc_command/lightning/list_channels.rs b/mm2src/coins/rpc_command/lightning/list_channels.rs index a96106a99f..d3eb582633 100644 --- a/mm2src/coins/rpc_command/lightning/list_channels.rs +++ b/mm2src/coins/rpc_command/lightning/list_channels.rs @@ -7,6 +7,7 @@ use db_common::sqlite::rusqlite::Error as SqlError; use http::StatusCode; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use uuid::Uuid; type ListChannelsResult = Result>; @@ -50,7 +51,7 @@ pub struct ListOpenChannelsRequest { #[serde(default = "ten")] limit: usize, #[serde(default)] - paging_options: PagingOptionsEnum, + paging_options: PagingOptionsEnum, } #[derive(Serialize)] @@ -60,7 +61,7 @@ pub struct ListOpenChannelsResponse { skipped: usize, total: usize, total_pages: usize, - paging_options: PagingOptionsEnum, + paging_options: PagingOptionsEnum, } pub async fn list_open_channels_by_filter( @@ -93,7 +94,7 @@ pub struct ListClosedChannelsRequest { #[serde(default = "ten")] limit: usize, #[serde(default)] - paging_options: PagingOptionsEnum, + paging_options: PagingOptionsEnum, } #[derive(Serialize)] @@ -103,7 +104,7 @@ pub struct ListClosedChannelsResponse { skipped: usize, total: usize, total_pages: usize, - paging_options: PagingOptionsEnum, + paging_options: PagingOptionsEnum, } pub async fn list_closed_channels_by_filter( diff --git a/mm2src/coins/rpc_command/lightning/open_channel.rs b/mm2src/coins/rpc_command/lightning/open_channel.rs index 9d131b1b48..f79ec0b433 100644 --- a/mm2src/coins/rpc_command/lightning/open_channel.rs +++ b/mm2src/coins/rpc_command/lightning/open_channel.rs @@ -9,7 +9,7 @@ use crate::{lp_coinfind_or_err, BalanceError, CoinFindError, GenerateTxError, Mm UnexpectedDerivationMethod, UtxoRpcError}; use chain::TransactionOutput; use common::log::error; -use common::{async_blocking, HttpStatusCode}; +use common::{async_blocking, new_uuid, HttpStatusCode}; use db_common::sqlite::rusqlite::Error as SqlError; use http::StatusCode; use keys::AddressHashEnum; @@ -18,6 +18,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; use script::Builder; +use uuid::Uuid; type OpenChannelResult = Result>; @@ -127,7 +128,7 @@ pub struct OpenChannelRequest { #[derive(Serialize)] pub struct OpenChannelResponse { - rpc_channel_id: u64, + uuid: Uuid, node_address: NodeAddress, } @@ -197,22 +198,21 @@ pub async fn open_channel(ctx: MmArc, req: OpenChannelRequest) -> OpenChannelRes drop_mutability!(conf); let user_config: UserConfig = conf.into(); - let rpc_channel_id = ln_coin.db.get_last_channel_rpc_id().await? as u64 + 1; - + let uuid = new_uuid(); let temp_channel_id = async_blocking(move || { channel_manager - .create_channel(node_pubkey, amount_in_sat, push_msat, rpc_channel_id, Some(user_config)) + .create_channel(node_pubkey, amount_in_sat, push_msat, uuid.as_u128(), Some(user_config)) .map_to_mm(|e| OpenChannelError::FailureToOpenChannel(node_pubkey.to_string(), format!("{:?}", e))) }) .await?; { let mut unsigned_funding_txs = ln_coin.platform.unsigned_funding_txs.lock(); - unsigned_funding_txs.insert(rpc_channel_id, unsigned); + unsigned_funding_txs.insert(uuid, unsigned); } let pending_channel_details = DBChannelDetails::new( - rpc_channel_id, + uuid, temp_channel_id, node_pubkey, true, @@ -227,11 +227,11 @@ pub async fn open_channel(ctx: MmArc, req: OpenChannelRequest) -> OpenChannelRes .await?; if let Err(e) = ln_coin.db.add_channel_to_db(&pending_channel_details).await { - error!("Unable to add new outbound channel {} to db: {}", rpc_channel_id, e); + error!("Unable to add new outbound channel {} to db: {}", uuid, e); } Ok(OpenChannelResponse { - rpc_channel_id, + uuid, node_address: req.node_address, }) } diff --git a/mm2src/coins/rpc_command/lightning/update_channel.rs b/mm2src/coins/rpc_command/lightning/update_channel.rs index a108659ea0..ad7768831f 100644 --- a/mm2src/coins/rpc_command/lightning/update_channel.rs +++ b/mm2src/coins/rpc_command/lightning/update_channel.rs @@ -4,6 +4,7 @@ use common::{async_blocking, HttpStatusCode}; use http::StatusCode; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use uuid::Uuid; type UpdateChannelResult = Result>; @@ -14,10 +15,10 @@ pub enum UpdateChannelError { UnsupportedCoin(String), #[display(fmt = "No such coin {}", _0)] NoSuchCoin(String), - #[display(fmt = "No such channel with rpc_channel_id {}", _0)] - NoSuchChannel(u64), + #[display(fmt = "No such channel with uuid {}", _0)] + NoSuchChannel(Uuid), #[display(fmt = "Failure to channel {}: {}", _0, _1)] - FailureToUpdateChannel(u64, String), + FailureToUpdateChannel(Uuid, String), } impl HttpStatusCode for UpdateChannelError { @@ -41,7 +42,7 @@ impl From for UpdateChannelError { #[derive(Deserialize)] pub struct UpdateChannelReq { pub coin: String, - pub rpc_channel_id: u64, + pub uuid: Uuid, pub channel_options: ChannelOptions, } @@ -58,9 +59,9 @@ pub async fn update_channel(ctx: MmArc, req: UpdateChannelReq) -> UpdateChannelR }; let channel_details = ln_coin - .get_channel_by_rpc_id(req.rpc_channel_id) + .get_channel_by_uuid(req.uuid) .await - .ok_or(UpdateChannelError::NoSuchChannel(req.rpc_channel_id))?; + .ok_or(UpdateChannelError::NoSuchChannel(req.uuid))?; async_blocking(move || { let mut channel_options = ln_coin @@ -76,7 +77,7 @@ pub async fn update_channel(ctx: MmArc, req: UpdateChannelReq) -> UpdateChannelR ln_coin .channel_manager .update_channel_config(&counterparty_node_id, channel_ids, &channel_options.clone().into()) - .map_to_mm(|e| UpdateChannelError::FailureToUpdateChannel(req.rpc_channel_id, format!("{:?}", e)))?; + .map_to_mm(|e| UpdateChannelError::FailureToUpdateChannel(req.uuid, format!("{:?}", e)))?; Ok(UpdateChannelResponse { channel_options }) }) .await diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 3f8dc1e6c9..2443f31276 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -762,9 +762,17 @@ impl MmCoin for SolanaCoin { fn mature_confirmations(&self) -> Option { None } - fn coin_protocol_info(&self) -> Vec { Vec::new() } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - fn is_coin_protocol_supported(&self, _info: &Option>) -> bool { true } + fn is_coin_protocol_supported( + &self, + _info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { + true + } fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index c005a09de9..d23cb2301f 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -553,9 +553,17 @@ impl MmCoin for SplToken { fn mature_confirmations(&self) -> Option { Some(1) } - fn coin_protocol_info(&self) -> Vec { Vec::new() } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - fn is_coin_protocol_supported(&self, _info: &Option>) -> bool { true } + fn is_coin_protocol_supported( + &self, + _info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { + true + } fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index ac05c98565..082cd3e85b 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -1982,9 +1982,17 @@ impl MmCoin for TendermintCoin { fn mature_confirmations(&self) -> Option { None } - fn coin_protocol_info(&self) -> Vec { Vec::new() } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { true } + fn is_coin_protocol_supported( + &self, + _info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { + true + } fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 60deb075b7..3c6fe4a583 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -795,10 +795,19 @@ impl MmCoin for TendermintToken { fn mature_confirmations(&self) -> Option { None } - fn coin_protocol_info(&self) -> Vec { self.platform_coin.coin_protocol_info() } + fn coin_protocol_info(&self, amount_to_receive: Option) -> Vec { + self.platform_coin.coin_protocol_info(amount_to_receive) + } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { - self.platform_coin.is_coin_protocol_supported(info) + fn is_coin_protocol_supported( + &self, + info: &Option>, + amount_to_send: Option, + locktime: u64, + is_maker: bool, + ) -> bool { + self.platform_coin + .is_coin_protocol_supported(info, amount_to_send, locktime, is_maker) } fn on_disabled(&self) -> Result<(), AbortedError> { self.abortable_system.abort_all() } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 5383922dd2..7e3cb9e0eb 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -351,9 +351,17 @@ impl MmCoin for TestCoin { fn mature_confirmations(&self) -> Option { unimplemented!() } - fn coin_protocol_info(&self) -> Vec { Vec::new() } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - fn is_coin_protocol_supported(&self, _info: &Option>) -> bool { true } + fn is_coin_protocol_supported( + &self, + _info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { + true + } fn on_disabled(&self) -> Result<(), AbortedError> { Ok(()) } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 02eefb15c9..5758f269bd 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1278,9 +1278,17 @@ impl MmCoin for BchCoin { fn mature_confirmations(&self) -> Option { Some(self.utxo_arc.conf.mature_confirmations) } - fn coin_protocol_info(&self) -> Vec { utxo_common::coin_protocol_info(self) } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { + utxo_common::coin_protocol_info(self) + } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { + fn is_coin_protocol_supported( + &self, + info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { utxo_common::is_coin_protocol_supported(self, info) } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index d5ad345f92..657ecd56b9 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -967,9 +967,17 @@ impl MmCoin for QtumCoin { fn mature_confirmations(&self) -> Option { Some(self.utxo_arc.conf.mature_confirmations) } - fn coin_protocol_info(&self) -> Vec { utxo_common::coin_protocol_info(self) } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { + utxo_common::coin_protocol_info(self) + } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { + fn is_coin_protocol_supported( + &self, + info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { utxo_common::is_coin_protocol_supported(self, info) } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 67d85faf12..8beef7c130 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -1864,9 +1864,17 @@ impl MmCoin for SlpToken { fn mature_confirmations(&self) -> Option { self.platform_coin.mature_confirmations() } - fn coin_protocol_info(&self) -> Vec { Vec::new() } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - fn is_coin_protocol_supported(&self, _info: &Option>) -> bool { true } + fn is_coin_protocol_supported( + &self, + _info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { + true + } fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 360fb2d566..b3f5168b0c 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -731,9 +731,17 @@ impl MmCoin for UtxoStandardCoin { fn mature_confirmations(&self) -> Option { Some(self.utxo_arc.conf.mature_confirmations) } - fn coin_protocol_info(&self) -> Vec { utxo_common::coin_protocol_info(self) } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { + utxo_common::coin_protocol_info(self) + } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { + fn is_coin_protocol_supported( + &self, + info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { utxo_common::is_coin_protocol_supported(self, info) } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 7a145473f3..7d19ae13ee 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1659,9 +1659,17 @@ impl MmCoin for ZCoin { fn mature_confirmations(&self) -> Option { Some(self.utxo_arc.conf.mature_confirmations) } - fn coin_protocol_info(&self) -> Vec { utxo_common::coin_protocol_info(self) } + fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { + utxo_common::coin_protocol_info(self) + } - fn is_coin_protocol_supported(&self, info: &Option>) -> bool { + fn is_coin_protocol_supported( + &self, + info: &Option>, + _amount_to_send: Option, + _locktime: u64, + _is_maker: bool, + ) -> bool { utxo_common::is_coin_protocol_supported(self, info) } diff --git a/mm2src/coins_activation/Cargo.toml b/mm2src/coins_activation/Cargo.toml index 7506494822..bc11043176 100644 --- a/mm2src/coins_activation/Cargo.toml +++ b/mm2src/coins_activation/Cargo.toml @@ -32,6 +32,6 @@ serde_json = { version = "1", features = ["preserve_order", "raw_value"] } mm2_metamask = { path = "../mm2_metamask" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -lightning = "0.0.110" -lightning-background-processor = "0.0.110" -lightning-invoice = { version = "0.18.0", features = ["serde"] } +lightning = "0.0.113" +lightning-background-processor = "0.0.113" +lightning-invoice = { version = "0.21.0", features = ["serde"] } diff --git a/mm2src/coins_activation/src/lightning_activation.rs b/mm2src/coins_activation/src/lightning_activation.rs index b3da8c3f7b..c5aed05804 100644 --- a/mm2src/coins_activation/src/lightning_activation.rs +++ b/mm2src/coins_activation/src/lightning_activation.rs @@ -20,12 +20,12 @@ use common::executor::{SpawnFuture, Timer}; use crypto::hw_rpc_task::{HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use derive_more::Display; use futures::compat::Future01CompatExt; -use lightning::chain::keysinterface::{KeysInterface, Recipient}; +use lightning::chain::keysinterface::KeysInterface; use lightning::chain::Access; use lightning::routing::gossip; +use lightning::routing::router::DefaultRouter; use lightning_background_processor::{BackgroundProcessor, GossipSync}; use lightning_invoice::payment; -use lightning_invoice::utils::DefaultRouter; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use parking_lot::Mutex as PaMutex; @@ -392,10 +392,8 @@ async fn start_lightning( &platform, params.listening_port, channel_manager.clone(), + keys_manager.clone(), gossip_sync.clone(), - keys_manager - .get_node_secret(Recipient::Node) - .map_to_mm(|_| EnableLightningError::UnsupportedMode("'start_lightning'".into(), "local node".into()))?, logger.clone(), ) .await?; @@ -428,11 +426,15 @@ async fn start_lightning( // https://github.com/lightningdevkit/rust-lightning/pull/1286 // https://github.com/lightningdevkit/rust-lightning/pull/1359 let router_random_seed_bytes = keys_manager.get_secure_random_bytes(); - let router = DefaultRouter::new(network_graph.clone(), logger.clone(), router_random_seed_bytes); + let router = DefaultRouter::new( + network_graph.clone(), + logger.clone(), + router_random_seed_bytes, + scorer.clone(), + ); let invoice_payer = Arc::new(InvoicePayer::new( channel_manager.clone(), router, - scorer.clone(), logger.clone(), event_handler, // Todo: Add option for choosing payment::Retry::Timeout instead of Attempts in LightningParams @@ -468,7 +470,7 @@ async fn start_lightning( // Broadcast Node Announcement platform.spawner().spawn(ln_node_announcement_loop( - channel_manager.clone(), + peer_manager.clone(), params.node_name, params.node_color, params.listening_port, @@ -487,7 +489,12 @@ async fn start_lightning( db, open_channels_nodes, trusted_nodes, - router: Arc::new(DefaultRouter::new(network_graph, logger, router_random_seed_bytes)), - scorer, + router: Arc::new(DefaultRouter::new( + network_graph, + logger.clone(), + router_random_seed_bytes, + scorer, + )), + logger, }) } diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 2327ebd957..861fe2033e 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -32,7 +32,7 @@ log = "0.4.8" parking_lot = { version = "0.12.0", features = ["nightly"] } parking_lot_core = { version = "0.6", features = ["nightly"] } primitive-types = "0.11.1" -rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } +rand = { version = "0.7", features = ["std", "small_rng"] } serde = "1" serde_derive = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } @@ -40,7 +40,7 @@ ser_error = { path = "../derives/ser_error" } ser_error_derive = { path = "../derives/ser_error_derive" } sha2 = "0.9" shared_ref_counter = { path = "shared_ref_counter", optional = true } -uuid = { version = "0.7", features = ["serde", "v4"] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } wasm-timer = "0.2.4" [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -65,7 +65,7 @@ hyper = { version = "0.14.11", features = ["client", "http2", "server", "tcp"] } # got "invalid certificate: UnknownIssuer" for https://ropsten.infura.io on iOS using default-features hyper-rustls = { version = "0.23", default-features = false, features = ["http1", "http2", "webpki-tokio"] } libc = { version = "0.2" } -lightning = "0.0.110" +lightning = "0.0.113" log4rs = { version = "1.0", default-features = false, features = ["console_appender", "pattern_encoder"] } tokio = { version = "1.20", features = ["io-util", "rt-multi-thread", "net"] } diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index e2f638c02f..4f8cdcfcce 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -799,7 +799,7 @@ pub struct PagingOptions { pub from_uuid: Option, } -#[cfg(not(target_arch = "wasm32"))] +#[inline] pub fn new_uuid() -> Uuid { Uuid::new_v4() } pub fn first_char_to_upper(input: &str) -> String { diff --git a/mm2src/common/wasm.rs b/mm2src/common/wasm.rs index a0162e6068..2342b59383 100644 --- a/mm2src/common/wasm.rs +++ b/mm2src/common/wasm.rs @@ -1,24 +1,10 @@ use crate::filename; -use rand::RngCore; use serde::de::DeserializeOwned; use serde::Serialize; use serde_wasm_bindgen::Serializer; use std::fmt; -use uuid::{Builder, Uuid, Variant, Version}; use wasm_bindgen::prelude::*; -pub fn new_uuid() -> Uuid { - let mut rng = rand::thread_rng(); - let mut bytes = [0; 16]; - - rng.fill_bytes(&mut bytes); - - Builder::from_bytes(bytes) - .set_variant(Variant::RFC4122) - .set_version(Version::Random) - .build() -} - /// Get only the first line of the error. /// Generally, the `JsValue` error contains the stack trace of an error. /// This function cuts off the stack trace. diff --git a/mm2src/db_common/Cargo.toml b/mm2src/db_common/Cargo.toml index 87d16bbbc5..40bff9f18e 100644 --- a/mm2src/db_common/Cargo.toml +++ b/mm2src/db_common/Cargo.toml @@ -10,7 +10,7 @@ doctest = false common = { path = "../common" } hex = "0.4.2" log = "0.4.8" -uuid = { version = "0.7", features = ["serde", "v4"] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] rusqlite = { version = "0.24.2", features = ["bundled"] } diff --git a/mm2src/mm2_bitcoin/chain/Cargo.toml b/mm2src/mm2_bitcoin/chain/Cargo.toml index a107cf6735..58a1929c96 100644 --- a/mm2src/mm2_bitcoin/chain/Cargo.toml +++ b/mm2src/mm2_bitcoin/chain/Cargo.toml @@ -14,4 +14,4 @@ serialization = { path = "../serialization" } serialization_derive = { path = "../serialization_derive" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bitcoin = "0.28.1" +bitcoin = "0.29" diff --git a/mm2src/mm2_bitcoin/chain/src/transaction.rs b/mm2src/mm2_bitcoin/chain/src/transaction.rs index 8d250a583a..478d39bef5 100644 --- a/mm2src/mm2_bitcoin/chain/src/transaction.rs +++ b/mm2src/mm2_bitcoin/chain/src/transaction.rs @@ -8,7 +8,8 @@ use crypto::{dhash256, sha256}; use ext_bitcoin::blockdata::transaction::{OutPoint as ExtOutpoint, Transaction as ExtTransaction, TxIn, TxOut}; #[cfg(not(target_arch = "wasm32"))] use ext_bitcoin::hash_types::Txid; -#[cfg(not(target_arch = "wasm32"))] use ext_bitcoin::Witness; +#[cfg(not(target_arch = "wasm32"))] +use ext_bitcoin::{PackedLockTime, Sequence, Witness}; use hash::{CipherText, EncCipherText, OutCipherText, ZkProof, ZkProofSapling, H256, H512, H64}; use hex::FromHex; use ser::{deserialize, serialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; @@ -79,7 +80,7 @@ impl From for TxIn { TxIn { previous_output: txin.previous_output.into(), script_sig: txin.script_sig.take().into(), - sequence: txin.sequence, + sequence: Sequence(txin.sequence), witness: Witness::from_vec(txin.script_witness.into_iter().map(|s| s.take()).collect()), } } @@ -237,7 +238,7 @@ impl From for ExtTransaction { fn from(tx: Transaction) -> Self { ExtTransaction { version: tx.version, - lock_time: tx.lock_time, + lock_time: PackedLockTime(tx.lock_time), input: tx.inputs.into_iter().map(|i| i.into()).collect(), output: tx.outputs.into_iter().map(|o| o.into()).collect(), } diff --git a/mm2src/mm2_bitcoin/keys/Cargo.toml b/mm2src/mm2_bitcoin/keys/Cargo.toml index 3569f9ed84..7017d8ff24 100644 --- a/mm2src/mm2_bitcoin/keys/Cargo.toml +++ b/mm2src/mm2_bitcoin/keys/Cargo.toml @@ -9,7 +9,7 @@ doctest = false [dependencies] rustc-hex = "2" base58 = "0.2" -bech32 = "0.8.0" +bech32 = "0.9.1" bitcrypto = { path = "../crypto" } derive_more = "0.99" lazy_static = "1.4" diff --git a/mm2src/mm2_bitcoin/primitives/Cargo.toml b/mm2src/mm2_bitcoin/primitives/Cargo.toml index 568ee6aba0..3da53cdf33 100644 --- a/mm2src/mm2_bitcoin/primitives/Cargo.toml +++ b/mm2src/mm2_bitcoin/primitives/Cargo.toml @@ -8,6 +8,6 @@ doctest = false [dependencies] rustc-hex = "2" -bitcoin_hashes = "0.10.0" +bitcoin_hashes = "0.11" byteorder = "1.0" uint = "0.9.3" diff --git a/mm2src/mm2_core/Cargo.toml b/mm2src/mm2_core/Cargo.toml index 10d1cd1611..fb16b2eb81 100644 --- a/mm2src/mm2_core/Cargo.toml +++ b/mm2src/mm2_core/Cargo.toml @@ -22,7 +22,7 @@ rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } serde = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } shared_ref_counter = { path = "../common/shared_ref_counter" } -uuid = { version = "0.7", features = ["serde", "v4"] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(target_arch = "wasm32")'.dependencies] gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 6ec2dff43e..48849fc88e 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -90,7 +90,7 @@ sp-runtime-interface = { version = "6.0.0", default-features = false, features = sp-trie = { version = "6.0", default-features = false } trie-db = { version = "0.23.1", default-features = false } trie-root = "0.16.0" -uuid = { version = "0.7", features = ["serde", "v4"] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } wasm-timer = "0.2.4" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/mm2src/mm2_main/src/database/my_orders.rs b/mm2src/mm2_main/src/database/my_orders.rs index 9a2ed6a278..776dca8487 100644 --- a/mm2src/mm2_main/src/database/my_orders.rs +++ b/mm2src/mm2_main/src/database/my_orders.rs @@ -8,6 +8,7 @@ use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlRe use db_common::sqlite::sql_builder::SqlBuilder; use mm2_core::mm_ctx::MmArc; use std::convert::TryInto; +use std::string::ParseError; use uuid::Uuid; const MY_ORDERS_TABLE: &str = "my_orders"; @@ -179,7 +180,7 @@ fn apply_my_orders_filter(builder: &mut SqlBuilder, params: &mut Vec<(&str, Stri #[derive(Debug)] pub enum SelectRecentOrdersUuidsErr { Sql(SqlError), - Parse(uuid::parser::ParseError), + Parse(ParseError), } impl std::fmt::Display for SelectRecentOrdersUuidsErr { @@ -190,8 +191,8 @@ impl From for SelectRecentOrdersUuidsErr { fn from(err: SqlError) -> Self { SelectRecentOrdersUuidsErr::Sql(err) } } -impl From for SelectRecentOrdersUuidsErr { - fn from(err: uuid::parser::ParseError) -> Self { SelectRecentOrdersUuidsErr::Parse(err) } +impl From for SelectRecentOrdersUuidsErr { + fn from(err: ParseError) -> Self { SelectRecentOrdersUuidsErr::Parse(err) } } pub fn select_orders_by_filter( diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index e8d4f6b9bb..835de9f240 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -7,6 +7,7 @@ use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlRe use db_common::sqlite::sql_builder::SqlBuilder; use mm2_core::mm_ctx::MmArc; use std::convert::TryInto; +use uuid::Error as UuidError; const MY_SWAPS_TABLE: &str = "my_swaps"; @@ -57,7 +58,7 @@ fn insert_saved_swap_sql(swap: SavedSwap) -> Option<(&'static str, Vec)> #[derive(Debug)] pub enum SelectRecentSwapsUuidsErr { Sql(SqlError), - Parse(uuid::parser::ParseError), + Parse(UuidError), } impl std::fmt::Display for SelectRecentSwapsUuidsErr { @@ -68,8 +69,8 @@ impl From for SelectRecentSwapsUuidsErr { fn from(err: SqlError) -> Self { SelectRecentSwapsUuidsErr::Sql(err) } } -impl From for SelectRecentSwapsUuidsErr { - fn from(err: uuid::parser::ParseError) -> Self { SelectRecentSwapsUuidsErr::Parse(err) } +impl From for SelectRecentSwapsUuidsErr { + fn from(err: UuidError) -> Self { SelectRecentSwapsUuidsErr::Parse(err) } } /// Adds where clauses determined by MySwapsFilter diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 7fa4cb69d0..d8a216c87e 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1425,6 +1425,16 @@ impl<'a> TakerOrderBuilder<'a> { None }; + let base_protocol_info = match &self.action { + TakerAction::Buy => self.base_coin.coin_protocol_info(Some(self.base_amount.clone())), + TakerAction::Sell => self.base_coin.coin_protocol_info(None), + }; + + let rel_protocol_info = match &self.action { + TakerAction::Buy => self.rel_coin.coin_protocol_info(None), + TakerAction::Sell => self.rel_coin.coin_protocol_info(Some(self.rel_amount.clone())), + }; + Ok(TakerOrder { created_at: now_ms(), request: TakerRequest { @@ -1438,8 +1448,8 @@ impl<'a> TakerOrderBuilder<'a> { dest_pub_key: Default::default(), match_by: self.match_by, conf_settings: self.conf_settings, - base_protocol_info: Some(self.base_coin.coin_protocol_info()), - rel_protocol_info: Some(self.rel_coin.coin_protocol_info()), + base_protocol_info: Some(base_protocol_info), + rel_protocol_info: Some(rel_protocol_info), }, matches: Default::default(), min_volume, @@ -1455,6 +1465,16 @@ impl<'a> TakerOrderBuilder<'a> { #[cfg(test)] /// skip validation for tests fn build_unchecked(self) -> TakerOrder { + let base_protocol_info = match &self.action { + TakerAction::Buy => self.base_coin.coin_protocol_info(Some(self.base_amount.clone())), + TakerAction::Sell => self.base_coin.coin_protocol_info(None), + }; + + let rel_protocol_info = match &self.action { + TakerAction::Buy => self.rel_coin.coin_protocol_info(None), + TakerAction::Sell => self.rel_coin.coin_protocol_info(Some(self.rel_amount.clone())), + }; + TakerOrder { created_at: now_ms(), request: TakerRequest { @@ -1468,8 +1488,8 @@ impl<'a> TakerOrderBuilder<'a> { dest_pub_key: Default::default(), match_by: self.match_by, conf_settings: self.conf_settings, - base_protocol_info: Some(self.base_coin.coin_protocol_info()), - rel_protocol_info: Some(self.rel_coin.coin_protocol_info()), + base_protocol_info: Some(base_protocol_info), + rel_protocol_info: Some(rel_protocol_info), }, matches: HashMap::new(), min_volume: Default::default(), @@ -2871,8 +2891,12 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO // detect atomic lock time version implicitly by conf_settings existence in taker request let atomic_locktime_v = match maker_match.request.conf_settings { Some(_) => { - let other_conf_settings = - choose_taker_confs_and_notas(&maker_match.request, &maker_match.reserved, &maker_coin, &taker_coin); + let other_conf_settings = choose_taker_confs_and_notas( + &maker_match.request, + &maker_match.reserved.conf_settings, + &maker_coin, + &taker_coin, + ); AtomicLocktimeVersion::V2 { my_conf_settings, other_conf_settings, @@ -2961,8 +2985,12 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat let maker_amount = taker_match.reserved.get_base_amount().clone(); let taker_amount = taker_match.reserved.get_rel_amount().clone(); - let my_conf_settings = - choose_taker_confs_and_notas(&taker_order.request, &taker_match.reserved, &maker_coin, &taker_coin); + let my_conf_settings = choose_taker_confs_and_notas( + &taker_order.request, + &taker_match.reserved.conf_settings, + &maker_coin, + &taker_coin, + ); // detect atomic lock time version implicitly by conf_settings existence in maker reserved let atomic_locktime_v = match taker_match.reserved.conf_settings { Some(_) => { @@ -3118,8 +3146,8 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) { maker_order_created_p2p_notify( ctx.clone(), &order, - base.coin_protocol_info(), - rel.coin_protocol_info(), + base.coin_protocol_info(None), + rel.coin_protocol_info(Some(order.max_base_vol.clone() * order.price.clone())), ); } } @@ -3216,8 +3244,8 @@ async fn handle_timed_out_taker_orders(ctx: MmArc, ordermatch_ctx: &OrdermatchCo maker_order_created_p2p_notify( ctx.clone(), &maker_order, - base.coin_protocol_info(), - rel.coin_protocol_info(), + base.coin_protocol_info(None), + rel.coin_protocol_info(Some(maker_order.max_base_vol.clone() * maker_order.price.clone())), ); } } @@ -3330,11 +3358,29 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: reserved_messages.sort_unstable_by_key(|r| r.price()); for reserved_msg in reserved_messages { + let my_conf_settings = + choose_maker_confs_and_notas(reserved_msg.conf_settings, &my_order.request, &base_coin, &rel_coin); + let other_conf_settings = + choose_taker_confs_and_notas(&my_order.request, &reserved_msg.conf_settings, &base_coin, &rel_coin); + let atomic_locktime_v = AtomicLocktimeVersion::V2 { + my_conf_settings, + other_conf_settings, + }; + let lock_time = lp_atomic_locktime( + my_order.maker_orderbook_ticker(), + my_order.taker_orderbook_ticker(), + atomic_locktime_v, + ); // send "connect" message if reserved message targets our pubkey AND // reserved amounts match our order AND order is NOT reserved by someone else (empty matches) if (my_order.match_reserved(&reserved_msg) == MatchReservedResult::Matched && my_order.matches.is_empty()) - && base_coin.is_coin_protocol_supported(&reserved_msg.base_protocol_info) - && rel_coin.is_coin_protocol_supported(&reserved_msg.rel_protocol_info) + && base_coin.is_coin_protocol_supported(&reserved_msg.base_protocol_info, None, lock_time, false) + && rel_coin.is_coin_protocol_supported( + &reserved_msg.rel_protocol_info, + Some(reserved_msg.rel_amount.clone()), + lock_time, + false, + ) { let connect = TakerConnect { sender_pubkey: H256Json::from(our_public_id.bytes), @@ -3438,9 +3484,35 @@ async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: _ => return, // attempt to match with deactivated coin }; + let my_conf_settings = + choose_maker_confs_and_notas(order.conf_settings, &taker_request, &base_coin, &rel_coin); + let other_conf_settings = + choose_taker_confs_and_notas(&taker_request, &order.conf_settings, &base_coin, &rel_coin); + let atomic_locktime_v = AtomicLocktimeVersion::V2 { + my_conf_settings, + other_conf_settings, + }; + let maker_lock_duration = (lp_atomic_locktime( + order.base_orderbook_ticker(), + order.rel_orderbook_ticker(), + atomic_locktime_v, + ) as f64 + * rel_coin.maker_locktime_multiplier()) + .ceil() as u64; + if !order.matches.contains_key(&taker_request.uuid) - && base_coin.is_coin_protocol_supported(taker_request.base_protocol_info_for_maker()) - && rel_coin.is_coin_protocol_supported(taker_request.rel_protocol_info_for_maker()) + && base_coin.is_coin_protocol_supported( + taker_request.base_protocol_info_for_maker(), + Some(base_amount.clone()), + maker_lock_duration, + true, + ) + && rel_coin.is_coin_protocol_supported( + taker_request.rel_protocol_info_for_maker(), + None, + maker_lock_duration, + true, + ) { let reserved = MakerReserved { dest_pub_key: taker_request.sender_pubkey, @@ -3459,8 +3531,8 @@ async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: rel_nota: rel_coin.requires_notarization(), }) }), - base_protocol_info: Some(base_coin.coin_protocol_info()), - rel_protocol_info: Some(rel_coin.coin_protocol_info()), + base_protocol_info: Some(base_coin.coin_protocol_info(None)), + rel_protocol_info: Some(rel_coin.coin_protocol_info(Some(rel_amount.clone()))), }; let topic = order.orderbook_topic(); log::debug!("Request matched sending reserved {:?}", reserved); @@ -4494,9 +4566,9 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result Result Result }; let min_base_amount = base_coin.min_trading_vol(); + // Todo: Here min_trading_vol for lightning depends on inbound liquidity not outbound, will require to split min_trading_vol to two functions let min_rel_amount = rel_coin.min_trading_vol(); // Add min_volume to update_msg if min_volume is found in the request @@ -5575,7 +5648,7 @@ fn choose_maker_confs_and_notas( fn choose_taker_confs_and_notas( taker_req: &TakerRequest, - maker_reserved: &MakerReserved, + maker_conf_settings: &Option, maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum, ) -> SwapConfirmationsSettings { @@ -5599,7 +5672,7 @@ fn choose_taker_confs_and_notas( ), }, }; - if let Some(settings_from_maker) = maker_reserved.conf_settings { + if let Some(settings_from_maker) = maker_conf_settings { if settings_from_maker.rel_confs < taker_coin_confs { taker_coin_confs = settings_from_maker.rel_confs; } diff --git a/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs b/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs index 0e841a214e..965fb743c2 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs @@ -392,6 +392,7 @@ mod best_orders_test { use super::*; use crate::mm2::lp_ordermatch::ordermatch_tests::make_random_orders; use crate::mm2::lp_ordermatch::{OrderbookItem, TrieProof}; + use common::new_uuid; use std::iter::FromIterator; #[test] @@ -456,12 +457,11 @@ mod best_orders_test { let v1_serialized = rmp_serde::to_vec(&v1).unwrap(); let mut new: BestOrdersP2PRes = rmp_serde::from_read_ref(&v1_serialized).unwrap(); - new.protocol_infos.insert(Uuid::new_v4(), BaseRelProtocolInfo { + new.protocol_infos.insert(new_uuid(), BaseRelProtocolInfo { base: vec![1], rel: vec![2], }); - new.conf_infos - .insert(Uuid::new_v4(), OrderConfirmationsSettings::default()); + new.conf_infos.insert(new_uuid(), OrderConfirmationsSettings::default()); let new_serialized = rmp_serde::to_vec(&new).unwrap(); @@ -485,7 +485,7 @@ mod best_orders_test { let v2 = BestOrdersResV2 { orders: HashMap::from_iter(std::iter::once(("RICK".into(), v2_orders))), - protocol_infos: HashMap::from_iter(std::iter::once((Uuid::new_v4(), BaseRelProtocolInfo { + protocol_infos: HashMap::from_iter(std::iter::once((new_uuid(), BaseRelProtocolInfo { base: vec![1], rel: vec![2], }))), @@ -494,8 +494,7 @@ mod best_orders_test { let v2_serialized = rmp_serde::to_vec(&v2).unwrap(); let mut new: BestOrdersP2PRes = rmp_serde::from_read_ref(&v2_serialized).unwrap(); - new.conf_infos - .insert(Uuid::new_v4(), OrderConfirmationsSettings::default()); + new.conf_infos.insert(new_uuid(), OrderConfirmationsSettings::default()); let new_serialized = rmp_serde::to_vec(&new).unwrap(); diff --git a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs index 0801e0a534..4489ba0224 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs @@ -78,7 +78,7 @@ mod compact_uuid { } impl FromStr for CompactUuid { - type Err = uuid::parser::ParseError; + type Err = uuid::Error; fn from_str(str: &str) -> Result { let uuid = Uuid::parse_str(str)?; @@ -399,7 +399,7 @@ mod new_protocol_tests { } let old_msg = MakerOrderCreatedV1 { - uuid: Uuid::new_v4().into(), + uuid: new_uuid().into(), base: "RICK".to_string(), rel: "MORTY".to_string(), price: BigRational::from_integer(1.into()), diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index c84402b902..0080ca3932 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1459,7 +1459,7 @@ mod lp_swap_tests { use coins::utxo::{UtxoActivationParams, UtxoRpcMode}; use coins::MarketCoinOps; use coins::PrivKeyActivationPolicy; - use common::block_on; + use common::{block_on, new_uuid}; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_test_helpers::for_tests::{morty_conf, rick_conf, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; @@ -1931,7 +1931,7 @@ mod lp_swap_tests { println!("Taker address {}", rick_taker.my_address().unwrap()); - let uuid = Uuid::new_v4(); + let uuid = new_uuid(); let maker_amount = BigDecimal::from_str("0.1").unwrap(); let taker_amount = BigDecimal::from_str("0.1").unwrap(); let conf_settings = SwapConfirmationsSettings { diff --git a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs index e38b8c5419..7365d2dbaa 100644 --- a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs +++ b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs @@ -4,11 +4,10 @@ use common::PagingOptions; use derive_more::Display; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use uuid::Uuid; pub type MySwapsResult = Result>; -use uuid::Uuid; - #[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] #[derive(Debug, Display, PartialEq)] pub enum MySwapsError { @@ -23,7 +22,7 @@ pub enum MySwapsError { #[display(fmt = "'from_uuid' not found: {}", _0)] FromUuidNotFound(Uuid), #[display(fmt = "Error parsing uuid: {}", _0)] - UuidParse(uuid::parser::ParseError), + UuidParse(uuid::Error), #[display(fmt = "Unknown SQL error: {}", _0)] UnknownSqlError(String), #[display(fmt = "Internal error: {}", _0)] diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 9b35c5bb47..b4a0a6c761 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -28,7 +28,7 @@ fn test_match_maker_order_and_taker_request() { price: 1.into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -40,7 +40,7 @@ fn test_match_maker_order_and_taker_request() { let request = TakerRequest { base: "BASE".into(), rel: "REL".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 10.into(), @@ -66,7 +66,7 @@ fn test_match_maker_order_and_taker_request() { price: "0.5".into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -78,7 +78,7 @@ fn test_match_maker_order_and_taker_request() { let request = TakerRequest { base: "BASE".into(), rel: "REL".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 10.into(), @@ -104,7 +104,7 @@ fn test_match_maker_order_and_taker_request() { price: "0.5".into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -116,7 +116,7 @@ fn test_match_maker_order_and_taker_request() { let request = TakerRequest { base: "BASE".into(), rel: "REL".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 10.into(), @@ -142,7 +142,7 @@ fn test_match_maker_order_and_taker_request() { price: "0.5".into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -154,7 +154,7 @@ fn test_match_maker_order_and_taker_request() { let request = TakerRequest { base: "REL".into(), rel: "BASE".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 5.into(), @@ -180,7 +180,7 @@ fn test_match_maker_order_and_taker_request() { price: "0.5".into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -192,7 +192,7 @@ fn test_match_maker_order_and_taker_request() { let request = TakerRequest { base: "REL".into(), rel: "BASE".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 10.into(), @@ -218,7 +218,7 @@ fn test_match_maker_order_and_taker_request() { price: "1".into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -230,7 +230,7 @@ fn test_match_maker_order_and_taker_request() { let request = TakerRequest { base: "REL".into(), rel: "BASE".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 1.into(), @@ -258,7 +258,7 @@ fn test_match_maker_order_and_taker_request() { rel: "KMD".to_owned(), matches: HashMap::new(), started_swaps: vec![], - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -272,7 +272,7 @@ fn test_match_maker_order_and_taker_request() { base_amount: "774.205645538427044180416545".into(), rel_amount: "0.2928826881884105".into(), action: TakerAction::Sell, - uuid: Uuid::new_v4(), + uuid: new_uuid(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), match_by: MatchBy::Any, @@ -298,7 +298,7 @@ fn test_match_maker_order_and_taker_request() { rel: "REL".to_owned(), matches: HashMap::new(), started_swaps: vec![], - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -312,7 +312,7 @@ fn test_match_maker_order_and_taker_request() { base_amount: "30".into(), rel_amount: "1.5".into(), action: TakerAction::Sell, - uuid: Uuid::new_v4(), + uuid: new_uuid(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), match_by: MatchBy::Any, @@ -369,7 +369,7 @@ fn test_maker_order_available_amount() { price: 1.into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -377,9 +377,9 @@ fn test_maker_order_available_amount() { rel_orderbook_ticker: None, p2p_privkey: None, }; - maker.matches.insert(Uuid::new_v4(), MakerMatch { + maker.matches.insert(new_uuid(), MakerMatch { request: TakerRequest { - uuid: Uuid::new_v4(), + uuid: new_uuid(), base: "BASE".into(), rel: "REL".into(), base_amount: 5.into(), @@ -399,8 +399,8 @@ fn test_maker_order_available_amount() { rel_amount: 5.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), - taker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), + taker_order_uuid: new_uuid(), conf_settings: None, base_protocol_info: None, rel_protocol_info: None, @@ -409,9 +409,9 @@ fn test_maker_order_available_amount() { connected: None, last_updated: now_ms(), }); - maker.matches.insert(Uuid::new_v4(), MakerMatch { + maker.matches.insert(new_uuid(), MakerMatch { request: TakerRequest { - uuid: Uuid::new_v4(), + uuid: new_uuid(), base: "BASE".into(), rel: "REL".into(), base_amount: 1.into(), @@ -431,8 +431,8 @@ fn test_maker_order_available_amount() { rel_amount: 1.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), - taker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), + taker_order_uuid: new_uuid(), conf_settings: None, base_protocol_info: None, rel_protocol_info: None, @@ -449,7 +449,7 @@ fn test_maker_order_available_amount() { #[test] fn test_taker_match_reserved() { - let uuid = Uuid::new_v4(); + let uuid = new_uuid(); let request = TakerRequest { base: "BASE".into(), @@ -486,7 +486,7 @@ fn test_taker_match_reserved() { rel_amount: 10.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -530,7 +530,7 @@ fn test_taker_match_reserved() { rel_amount: 10.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -574,7 +574,7 @@ fn test_taker_match_reserved() { rel_amount: 1.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -618,7 +618,7 @@ fn test_taker_match_reserved() { rel_amount: 1.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -662,7 +662,7 @@ fn test_taker_match_reserved() { rel_amount: 1.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -706,7 +706,7 @@ fn test_taker_match_reserved() { rel_amount: 1.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -750,7 +750,7 @@ fn test_taker_match_reserved() { rel_amount: 1.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -794,7 +794,7 @@ fn test_taker_match_reserved() { rel_amount: 3.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -853,7 +853,7 @@ fn test_taker_order_cancellable() { let request = TakerRequest { base: "BASE".into(), rel: "REL".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 1.into(), @@ -883,7 +883,7 @@ fn test_taker_order_cancellable() { let request = TakerRequest { base: "BASE".into(), rel: "REL".into(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), dest_pub_key: H256Json::default(), sender_pubkey: H256Json::default(), base_amount: 1.into(), @@ -908,7 +908,7 @@ fn test_taker_order_cancellable() { p2p_privkey: None, }; - order.matches.insert(Uuid::new_v4(), TakerMatch { + order.matches.insert(new_uuid(), TakerMatch { last_updated: now_ms(), reserved: MakerReserved { base: "BASE".into(), @@ -917,8 +917,8 @@ fn test_taker_order_cancellable() { rel_amount: 3.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), - taker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), + taker_order_uuid: new_uuid(), conf_settings: None, base_protocol_info: None, rel_protocol_info: None, @@ -926,8 +926,8 @@ fn test_taker_order_cancellable() { connect: TakerConnect { sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), - taker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), + taker_order_uuid: new_uuid(), }, connected: None, }); @@ -1102,10 +1102,10 @@ fn test_cancel_by_all() { #[test] // https://github.com/KomodoPlatform/atomicDEX-API/issues/607 fn test_taker_order_match_by() { - let uuid = Uuid::new_v4(); + let uuid = new_uuid(); let mut not_matching_uuids = HashSet::new(); - not_matching_uuids.insert(Uuid::new_v4()); + not_matching_uuids.insert(new_uuid()); let request = TakerRequest { base: "BASE".into(), rel: "REL".into(), @@ -1141,7 +1141,7 @@ fn test_taker_order_match_by() { rel_amount: 10.into(), sender_pubkey: H256Json::default(), dest_pub_key: H256Json::default(), - maker_order_uuid: Uuid::new_v4(), + maker_order_uuid: new_uuid(), taker_order_uuid: uuid, conf_settings: None, base_protocol_info: None, @@ -1179,7 +1179,7 @@ fn test_maker_order_was_updated() { price: 1.into(), matches: HashMap::new(), started_swaps: Vec::new(), - uuid: Uuid::new_v4(), + uuid: new_uuid(), conf_settings: None, changes_history: None, save_in_history: false, @@ -1391,7 +1391,7 @@ fn test_choose_taker_confs_settings_buy_action() { let maker_reserved = MakerReserved::default(); TestCoin::requires_notarization.mock_safe(|_| MockResult::Return(true)); TestCoin::required_confirmations.mock_safe(|_| MockResult::Return(8)); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should pick settings from coins assert!(settings.taker_coin_nota); assert_eq!(settings.taker_coin_confs, 8); @@ -1409,7 +1409,7 @@ fn test_choose_taker_confs_settings_buy_action() { .build_unchecked(); // no confs and notas set let maker_reserved = MakerReserved::default(); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should pick settings from taker request // as action is buy my_coin is rel and other coin is base assert!(!settings.taker_coin_nota); @@ -1434,7 +1434,7 @@ fn test_choose_taker_confs_settings_buy_action() { base_nota: true, }; maker_reserved.conf_settings = Some(maker_conf_settings); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should pick settings from maker reserved if he requires less confs // as action is buy my_coin is rel and other coin is base in request assert!(!settings.taker_coin_nota); @@ -1459,7 +1459,7 @@ fn test_choose_taker_confs_settings_buy_action() { base_nota: true, }; maker_reserved.conf_settings = Some(maker_conf_settings); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should allow maker to use more confirmations than we require, but it shouldn't affect our settings // as action is buy my_coin is rel and other coin is base in request assert!(!settings.taker_coin_nota); @@ -1484,7 +1484,7 @@ fn test_choose_taker_confs_settings_buy_action() { rel_nota: true, }; maker_reserved.conf_settings = Some(maker_conf_settings); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // maker settings should have no effect on other_coin_confs and other_coin_nota // as action is buy my_coin is rel and other coin is base in request assert!(!settings.taker_coin_nota); @@ -1505,7 +1505,7 @@ fn test_choose_taker_confs_settings_sell_action() { let maker_reserved = MakerReserved::default(); TestCoin::requires_notarization.mock_safe(|_| MockResult::Return(true)); TestCoin::required_confirmations.mock_safe(|_| MockResult::Return(8)); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should pick settings from coins assert!(settings.taker_coin_nota); assert_eq!(settings.taker_coin_confs, 8); @@ -1524,7 +1524,7 @@ fn test_choose_taker_confs_settings_sell_action() { .build_unchecked(); // no confs and notas set let maker_reserved = MakerReserved::default(); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should pick settings from taker request // as action is sell my_coin is base and other coin is rel in request assert!(!settings.taker_coin_nota); @@ -1550,7 +1550,7 @@ fn test_choose_taker_confs_settings_sell_action() { rel_nota: false, }; maker_reserved.conf_settings = Some(maker_conf_settings); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should pick settings from maker reserved if he requires less confs // as action is sell my_coin is base and other coin is rel in request assert!(!settings.taker_coin_nota); @@ -1576,7 +1576,7 @@ fn test_choose_taker_confs_settings_sell_action() { base_nota: false, }; maker_reserved.conf_settings = Some(maker_conf_settings); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // should allow maker to use more confirmations than we require, but it shouldn't affect our settings // as action is sell my_coin is base and other coin is rel in request assert!(!settings.taker_coin_nota); @@ -1602,7 +1602,7 @@ fn test_choose_taker_confs_settings_sell_action() { base_nota: false, }; maker_reserved.conf_settings = Some(maker_conf_settings); - let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved, &coin, &coin); + let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); // maker settings should have no effect on other_coin_confs and other_coin_nota // as action is sell my_coin is base and other coin is rel in request assert!(!settings.taker_coin_nota); @@ -1633,7 +1633,7 @@ pub(super) fn make_random_orders( for _i in 0..n { let numer: u64 = rng.gen_range(2000, 10000000); let order = new_protocol::MakerOrderCreated { - uuid: Uuid::new_v4().into(), + uuid: new_uuid().into(), base: base.clone(), rel: rel.clone(), price: BigRational::new(numer.into(), 1000000.into()), @@ -1940,7 +1940,7 @@ fn test_process_order_keep_alive_requested_from_peer() { let (_, mut cmd_rx) = p2p_context_mock(); let (ctx, pubkey, secret) = make_ctx_for_tests(); - let uuid = Uuid::new_v4(); + let uuid = new_uuid(); let peer = PeerId::random().to_string(); let order = new_protocol::MakerOrderCreated { @@ -2022,7 +2022,7 @@ fn test_process_get_order_request() { let mut orderbook = block_on(ordermatch_ctx.orderbook.lock()); let order = new_protocol::MakerOrderCreated { - uuid: Uuid::new_v4().into(), + uuid: new_uuid().into(), base: "RICK".into(), rel: "MORTY".into(), price: BigRational::from_integer(1000000.into()), @@ -2209,7 +2209,7 @@ fn test_taker_request_can_match_with_maker_pubkey() { #[test] fn test_taker_request_can_match_with_uuid() { - let uuid = Uuid::new_v4(); + let uuid = new_uuid(); let coin = MmCoinEnum::Test(TestCoin::default()); // default has MatchBy::Any @@ -2881,7 +2881,7 @@ fn test_trie_state_bytes() { let price = BigRational::from_integer(1.into()); let max_volume = BigRational::from_integer(u64::MAX.into()); let min_volume = BigRational::from_integer(1.into()); - let uuid = Uuid::new_v4(); + let uuid = new_uuid(); let created_at = now_ms() / 1000; #[derive(Serialize)] @@ -2998,11 +2998,11 @@ fn check_get_orderbook_p2p_res_serde() { let v1_serialized = rmp_serde::to_vec(&v1).unwrap(); let mut new: GetOrderbookRes = rmp_serde::from_read_ref(&v1_serialized).unwrap(); - new.protocol_infos.insert(Uuid::new_v4(), BaseRelProtocolInfo { + new.protocol_infos.insert(new_uuid(), BaseRelProtocolInfo { base: vec![1], rel: vec![2], }); - new.conf_infos.insert(Uuid::new_v4(), OrderConfirmationsSettings { + new.conf_infos.insert(new_uuid(), OrderConfirmationsSettings { base_confs: 6, base_nota: false, rel_confs: 3, @@ -3030,7 +3030,7 @@ fn check_get_orderbook_p2p_res_serde() { let v2 = GetOrderbookResV2 { pubkey_orders: HashMap::from_iter(std::iter::once(("pubkey".into(), item))), - protocol_infos: HashMap::from_iter(std::iter::once((Uuid::new_v4(), BaseRelProtocolInfo { + protocol_infos: HashMap::from_iter(std::iter::once((new_uuid(), BaseRelProtocolInfo { base: vec![1], rel: vec![2], }))), @@ -3039,7 +3039,7 @@ fn check_get_orderbook_p2p_res_serde() { let v2_serialized = rmp_serde::to_vec(&v2).unwrap(); let mut new: GetOrderbookRes = rmp_serde::from_read_ref(&v2_serialized).unwrap(); - new.conf_infos.insert(Uuid::new_v4(), OrderConfirmationsSettings { + new.conf_infos.insert(new_uuid(), OrderConfirmationsSettings { base_confs: 6, base_nota: false, rel_confs: 3, @@ -3107,11 +3107,11 @@ fn check_sync_pubkey_state_p2p_res_serde() { let v1_serialized = rmp_serde::to_vec(&v1).unwrap(); let mut new: SyncPubkeyOrderbookStateRes = rmp_serde::from_read_ref(&v1_serialized).unwrap(); - new.protocol_infos.insert(Uuid::new_v4(), BaseRelProtocolInfo { + new.protocol_infos.insert(new_uuid(), BaseRelProtocolInfo { base: vec![1], rel: vec![2], }); - new.conf_infos.insert(Uuid::new_v4(), OrderConfirmationsSettings { + new.conf_infos.insert(new_uuid(), OrderConfirmationsSettings { base_confs: 6, base_nota: false, rel_confs: 3, @@ -3137,7 +3137,7 @@ fn check_sync_pubkey_state_p2p_res_serde() { alb_ordered_pair("RICK", "MORTY"), DeltaOrFullTrie::FullTrie(orders.into_iter().map(|order| (order.uuid, order.into())).collect()), ))), - protocol_infos: HashMap::from_iter(std::iter::once((Uuid::new_v4(), BaseRelProtocolInfo { + protocol_infos: HashMap::from_iter(std::iter::once((new_uuid(), BaseRelProtocolInfo { base: vec![1], rel: vec![2], }))), @@ -3146,7 +3146,7 @@ fn check_sync_pubkey_state_p2p_res_serde() { let v2_serialized = rmp_serde::to_vec(&v2).unwrap(); let mut new: SyncPubkeyOrderbookStateRes = rmp_serde::from_read_ref(&v2_serialized).unwrap(); - new.conf_infos.insert(Uuid::new_v4(), OrderConfirmationsSettings { + new.conf_infos.insert(new_uuid(), OrderConfirmationsSettings { base_confs: 6, base_nota: false, rel_confs: 3, diff --git a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs index ff2b5040fc..ab764023e7 100644 --- a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs @@ -1,5 +1,5 @@ use crate::integration_tests_common::{enable_coins_rick_morty_electrum, enable_electrum}; -use coins::lightning::ln_events::{SUCCESSFUL_CLAIM_LOG, SUCCESSFUL_SEND_LOG}; +use coins::lightning::ln_events::{CHANNEL_READY_LOG, PAYMENT_CLAIMABLE_LOG, SUCCESSFUL_CLAIM_LOG, SUCCESSFUL_SEND_LOG}; use common::executor::Timer; use common::{block_on, log}; use gstuff::now_ms; @@ -74,7 +74,11 @@ fn start_lightning_nodes(enable_0_confs: bool) -> (MarketMakerIt, MarketMakerIt, "decimals": 11, "our_channels_configs": { "inbound_channels_confirmations": 1, - "max_inbound_in_flight_htlc_percent": 100 + // todo: When this was 100% I got "lightning:channelmanager:2525] ERROR Cannot send value that would put our balance under counterparty-announced channel reserve value (1000000)" + // todo: This seems to be a bug in rust-lightning for mpp, I informed their team and will revert this to 100 if it was fixed https://github.com/lightningdevkit/rust-lightning/issues/1126#issuecomment-1414308252 + "max_inbound_in_flight_htlc_percent": 90, + // If this is set to 0 it will default to 1000 sats since it's the min allowed value + "their_channel_reserve_sats": 1000, }, "counterparty_channel_config_limits": { "outbound_channels_confirmations": 1, @@ -150,6 +154,165 @@ fn start_lightning_nodes(enable_0_confs: bool) -> (MarketMakerIt, MarketMakerIt, (mm_node_1, mm_node_2, node_1_address, node_2_address) } +async fn open_channel( + mm: &mut MarketMakerIt, + coin: &str, + address: &str, + amount: f64, + wait_for_ready_signal: bool, +) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "lightning::channels::open_channel", + "params": { + "coin": coin, + "node_address": address, + "amount": { + "type": "Exact", + "value": amount, + }, + }, + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'lightning::channels::open_channel' failed: {}", + request.1 + ); + + let res: Json = json::from_str(&request.1).unwrap(); + let uuid = res["result"]["uuid"].as_str().unwrap(); + if wait_for_ready_signal { + mm.wait_for_log(3600., |log| log.contains(&format!("{}: {}", CHANNEL_READY_LOG, uuid))) + .await + .unwrap(); + } + res +} + +async fn close_channel(mm: &MarketMakerIt, uuid: &str, force_close: bool) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "lightning::channels::close_channel", + "params": { + "coin": "tBTC-TEST-lightning", + "uuid": uuid, + "force_close": force_close, + }, + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'lightning::channels::close_channel' failed: {}", + request.1 + ); + + json::from_str(&request.1).unwrap() +} + +async fn add_trusted_node(mm: &MarketMakerIt, node_id: &str) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "lightning::nodes::add_trusted_node", + "params": { + "coin": "tBTC-TEST-lightning", + "node_id": node_id + }, + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'lightning::nodes::add_trusted_node' failed: {}", + request.1 + ); + json::from_str(&request.1).unwrap() +} + +async fn generate_invoice(mm: &MarketMakerIt, amount_in_msat: u64) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "lightning::payments::generate_invoice", + "params": { + "coin": "tBTC-TEST-lightning", + "description": "test invoice", + "amount_in_msat": amount_in_msat + }, + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'lightning::payments::generate_invoice' failed: {}", + request.1 + ); + + json::from_str(&request.1).unwrap() +} + +async fn pay_invoice(mm: &MarketMakerIt, invoice: &str) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "lightning::payments::send_payment", + "params": { + "coin": "tBTC-TEST-lightning", + "payment": { + "type": "invoice", + "invoice": invoice + } + }, + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'lightning::payments::send_payment' failed: {}", + request.1 + ); + + json::from_str(&request.1).unwrap() +} + +async fn get_payment_details(mm: &MarketMakerIt, payment_hash: &str) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "lightning::payments::get_payment_details", + "params": { + "coin": "tBTC-TEST-lightning", + "payment_hash": payment_hash + }, + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'lightning::payments::get_payment_details' failed: {}", + request.1 + ); + + json::from_str(&request.1).unwrap() +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_enable_lightning() { @@ -292,26 +455,13 @@ fn test_open_channel() { let (mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(false); let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); - let open_channel = block_on(mm_node_2.rpc(&json!({ - "userpass": mm_node_2.userpass, - "mmrpc": "2.0", - "method": "lightning::channels::open_channel", - "params": { - "coin": "tBTC-TEST-lightning", - "node_address": node_1_address, - "amount": { - "type": "Exact", - "value": 0.0002, - }, - }, - }))) - .unwrap(); - assert!( - open_channel.0.is_success(), - "!lightning::channels::open_channel: {}", - open_channel.1 - ); - + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + false, + )); block_on(mm_node_2.wait_for_log(60., |log| log.contains("Transaction broadcasted successfully"))).unwrap(); let list_channels_node_1 = block_on(mm_node_1.rpc(&json!({ @@ -384,43 +534,14 @@ fn test_send_payment() { let (mut mm_node_2, mut mm_node_1, node_2_id, node_1_id) = start_lightning_nodes(true); let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); - let add_trusted_node = block_on(mm_node_1.rpc(&json!({ - "userpass": mm_node_1.userpass, - "mmrpc": "2.0", - "method": "lightning::nodes::add_trusted_node", - "params": { - "coin": "tBTC-TEST-lightning", - "node_id": node_2_id - }, - }))) - .unwrap(); - assert!( - add_trusted_node.0.is_success(), - "!lightning::nodes::add_trusted_node: {}", - add_trusted_node.1 - ); - - let open_channel = block_on(mm_node_2.rpc(&json!({ - "userpass": mm_node_2.userpass, - "mmrpc": "2.0", - "method": "lightning::channels::open_channel", - "params": { - "coin": "tBTC-TEST-lightning", - "node_address": node_1_address, - "amount": { - "type": "Exact", - "value": 0.0002, - }, - }, - }))) - .unwrap(); - assert!( - open_channel.0.is_success(), - "!lightning::channels::open_channel: {}", - open_channel.1 - ); - - block_on(mm_node_2.wait_for_log(60., |log| log.contains("Received message ChannelReady"))).unwrap(); + block_on(add_trusted_node(&mm_node_1, &node_2_id)); + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); let send_payment = block_on(mm_node_2.rpc(&json!({ "userpass": mm_node_2.userpass, @@ -450,151 +571,108 @@ fn test_send_payment() { block_on(mm_node_2.wait_for_log(60., |log| log.contains(SUCCESSFUL_SEND_LOG))).unwrap(); // Check payment on the sending node side - let get_payment_details = block_on(mm_node_2.rpc(&json!({ - "userpass": mm_node_2.userpass, - "mmrpc": "2.0", - "method": "lightning::payments::get_payment_details", - "params": { - "coin": "tBTC-TEST-lightning", - "payment_hash": payment_hash - }, - }))) - .unwrap(); - assert!( - get_payment_details.0.is_success(), - "!lightning::payments::get_payment_details: {}", - get_payment_details.1 - ); - - let get_payment_details_res: Json = json::from_str(&get_payment_details.1).unwrap(); - let payment = &get_payment_details_res["result"]["payment_details"]; + let sender_payment_details = block_on(get_payment_details(&mm_node_2, payment_hash)); + let payment = &sender_payment_details["result"]["payment_details"]; assert_eq!(payment["status"], "succeeded"); assert_eq!(payment["amount_in_msat"], 1000); assert_eq!(payment["payment_type"]["type"], "Outbound Payment"); // Check payment on the receiving node side - let get_payment_details = block_on(mm_node_1.rpc(&json!({ - "userpass": mm_node_1.userpass, - "mmrpc": "2.0", - "method": "lightning::payments::get_payment_details", - "params": { - "coin": "tBTC-TEST-lightning", - "payment_hash": payment_hash - }, - }))) - .unwrap(); - assert!( - get_payment_details.0.is_success(), - "!lightning::payments::get_payment_details: {}", - get_payment_details.1 - ); - - let get_payment_details_res: Json = json::from_str(&get_payment_details.1).unwrap(); - let payment = &get_payment_details_res["result"]["payment_details"]; + let receiver_payment_details = block_on(get_payment_details(&mm_node_1, payment_hash)); + let payment = &receiver_payment_details["result"]["payment_details"]; assert_eq!(payment["status"], "succeeded"); assert_eq!(payment["amount_in_msat"], 1000); assert_eq!(payment["payment_type"]["type"], "Inbound Payment"); // Test generate and pay invoice - let generate_invoice = block_on(mm_node_1.rpc(&json!({ - "userpass": mm_node_1.userpass, - "mmrpc": "2.0", - "method": "lightning::payments::generate_invoice", - "params": { - "coin": "tBTC-TEST-lightning", - "description": "test invoice", - "amount_in_msat": 10000 - }, - }))) - .unwrap(); - assert!( - generate_invoice.0.is_success(), - "!lightning::payments::generate_invoice: {}", - generate_invoice.1 - ); + let generate_invoice = block_on(generate_invoice(&mm_node_1, 10000)); + let invoice = generate_invoice["result"]["invoice"].as_str().unwrap(); + let invoice_payment_hash = generate_invoice["result"]["payment_hash"].as_str().unwrap(); - let generate_invoice_res: Json = json::from_str(&generate_invoice.1).unwrap(); - log!("generate_invoice_res {:?}", generate_invoice_res); - let invoice = generate_invoice_res["result"]["invoice"].as_str().unwrap(); - let invoice_payment_hash = generate_invoice_res["result"]["payment_hash"].as_str().unwrap(); - - let pay_invoice = block_on(mm_node_2.rpc(&json!({ - "userpass": mm_node_2.userpass, - "mmrpc": "2.0", - "method": "lightning::payments::send_payment", - "params": { - "coin": "tBTC-TEST-lightning", - "payment": { - "type": "invoice", - "invoice": invoice - } - }, - }))) - .unwrap(); - assert!( - pay_invoice.0.is_success(), - "!lightning::payments::send_payment: {}", - pay_invoice.1 - ); - - let pay_invoice_res: Json = json::from_str(&pay_invoice.1).unwrap(); - log!("pay_invoice_res {:?}", pay_invoice_res); - let payment_hash = pay_invoice_res["result"]["payment_hash"].as_str().unwrap(); + let pay_invoice = block_on(pay_invoice(&mm_node_2, invoice)); + let payment_hash = pay_invoice["result"]["payment_hash"].as_str().unwrap(); block_on(mm_node_1.wait_for_log(60., |log| log.contains(SUCCESSFUL_CLAIM_LOG))).unwrap(); block_on(mm_node_2.wait_for_log(60., |log| { - log.contains(&format!( - "{} of 10000 millisatoshis with payment hash {}", - SUCCESSFUL_SEND_LOG, payment_hash - )) + log.contains(&format!("{} with payment hash {}", SUCCESSFUL_SEND_LOG, payment_hash)) })) .unwrap(); // Check payment on the sending node side - let get_payment_details = block_on(mm_node_2.rpc(&json!({ - "userpass": mm_node_2.userpass, - "mmrpc": "2.0", - "method": "lightning::payments::get_payment_details", - "params": { - "coin": "tBTC-TEST-lightning", - "payment_hash": payment_hash - }, - }))) - .unwrap(); - assert!( - get_payment_details.0.is_success(), - "!lightning::payments::get_payment_details: {}", - get_payment_details.1 - ); - - let get_payment_details_res: Json = json::from_str(&get_payment_details.1).unwrap(); - let payment = &get_payment_details_res["result"]["payment_details"]; + let sender_payment_details = block_on(get_payment_details(&mm_node_2, invoice_payment_hash)); + let payment = &sender_payment_details["result"]["payment_details"]; assert_eq!(payment["status"], "succeeded"); assert_eq!(payment["amount_in_msat"], 10000); assert_eq!(payment["payment_type"]["type"], "Outbound Payment"); assert_eq!(payment["description"], "test invoice"); // Check payment on the receiving node side - let get_payment_details = block_on(mm_node_1.rpc(&json!({ - "userpass": mm_node_1.userpass, - "mmrpc": "2.0", - "method": "lightning::payments::get_payment_details", - "params": { - "coin": "tBTC-TEST-lightning", - "payment_hash": invoice_payment_hash - }, - }))) + let receiver_payment_details = block_on(get_payment_details(&mm_node_1, invoice_payment_hash)); + let payment = &receiver_payment_details["result"]["payment_details"]; + assert_eq!(payment["status"], "succeeded"); + assert_eq!(payment["amount_in_msat"], 10000); + assert_eq!(payment["payment_type"]["type"], "Inbound Payment"); + assert_eq!(payment["description"], "test invoice"); + + block_on(mm_node_1.stop()).unwrap(); + block_on(mm_node_2.stop()).unwrap(); +} + +#[test] +// This test is ignored because it requires refilling the tBTC addresses with test coins periodically. +#[ignore] +#[cfg(not(target_arch = "wasm32"))] +fn test_mpp() { + let (mut mm_node_2, mut mm_node_1, node_2_id, node_1_id) = start_lightning_nodes(true); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + + block_on(add_trusted_node(&mm_node_1, &node_2_id)); + + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + + // Wait a few seconds for both channels to be included in the invoice routing hints, since both channels are private. + block_on(Timer::sleep(3.)); + + // Test generate and pay invoice, invoice amount is larger than one channel so payment will use the 2 channels + let generate_invoice = block_on(generate_invoice(&mm_node_1, 30000000)); + let invoice = generate_invoice["result"]["invoice"].as_str().unwrap(); + let invoice_payment_hash = generate_invoice["result"]["payment_hash"].as_str().unwrap(); + + let pay_invoice = block_on(pay_invoice(&mm_node_2, invoice)); + let payment_hash = pay_invoice["result"]["payment_hash"].as_str().unwrap(); + + block_on(mm_node_1.wait_for_log(60., |log| log.contains(SUCCESSFUL_CLAIM_LOG))).unwrap(); + block_on(mm_node_2.wait_for_log(60., |log| { + log.contains(&format!("{} with payment hash {}", SUCCESSFUL_SEND_LOG, payment_hash)) + })) .unwrap(); - assert!( - get_payment_details.0.is_success(), - "!lightning::payments::get_payment_details: {}", - get_payment_details.1 - ); - let get_payment_details_res: Json = json::from_str(&get_payment_details.1).unwrap(); - let payment = &get_payment_details_res["result"]["payment_details"]; + // Check payment on the sending node side + let sender_payment_details = block_on(get_payment_details(&mm_node_2, invoice_payment_hash)); + let payment = &sender_payment_details["result"]["payment_details"]; assert_eq!(payment["status"], "succeeded"); - assert_eq!(payment["amount_in_msat"], 10000); + assert_eq!(payment["amount_in_msat"], 30000000); + assert_eq!(payment["payment_type"]["type"], "Outbound Payment"); + assert_eq!(payment["description"], "test invoice"); + + // Check payment on the receiving node side + let receiver_payment_details = block_on(get_payment_details(&mm_node_1, invoice_payment_hash)); + let payment = &receiver_payment_details["result"]["payment_details"]; + assert_eq!(payment["status"], "succeeded"); + assert_eq!(payment["amount_in_msat"], 30000000); assert_eq!(payment["payment_type"]["type"], "Inbound Payment"); assert_eq!(payment["description"], "test invoice"); @@ -610,43 +688,15 @@ fn test_lightning_swaps() { let (mut mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(true); let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); - let add_trusted_node = block_on(mm_node_1.rpc(&json!({ - "userpass": mm_node_1.userpass, - "mmrpc": "2.0", - "method": "lightning::nodes::add_trusted_node", - "params": { - "coin": "tBTC-TEST-lightning", - "node_id": node_2_id - }, - }))) - .unwrap(); - assert!( - add_trusted_node.0.is_success(), - "!lightning::nodes::add_trusted_node: {}", - add_trusted_node.1 - ); + block_on(add_trusted_node(&mm_node_1, &node_2_id)); - let open_channel = block_on(mm_node_2.rpc(&json!({ - "userpass": mm_node_2.userpass, - "mmrpc": "2.0", - "method": "lightning::channels::open_channel", - "params": { - "coin": "tBTC-TEST-lightning", - "node_address": node_1_address, - "amount": { - "type": "Exact", - "value": 0.0002, - }, - }, - }))) - .unwrap(); - assert!( - open_channel.0.is_success(), - "!lightning::channels::open_channel: {}", - open_channel.1 - ); - - block_on(mm_node_2.wait_for_log(60., |log| log.contains("Received message ChannelReady"))).unwrap(); + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); // Enable coins on mm_node_1 side. Print the replies in case we need the "address". log!( @@ -708,6 +758,244 @@ fn test_lightning_swaps() { block_on(mm_node_2.stop()).unwrap(); } +#[test] +// This test is ignored because it requires refilling the tBTC and RICK addresses with test coins periodically. +// This test also takes a lot of time so it should always be ignored. +#[ignore] +#[cfg(not(target_arch = "wasm32"))] +fn test_lightning_taker_swap_mpp() { + let (mut mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(true); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + + block_on(add_trusted_node(&mm_node_1, &node_2_id)); + + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + + // Enable coins on mm_node_1 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_1): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_1)) + ); + + // Enable coins on mm_node_2 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_2): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_2)) + ); + + let price = 0.0025; + let volume = 0.1; + let uuids = block_on(start_swaps( + &mut mm_node_1, + &mut mm_node_2, + &[("RICK", "tBTC-TEST-lightning")], + price, + price, + volume, + )); + block_on(wait_for_swaps_finish_and_check_status( + &mut mm_node_1, + &mut mm_node_2, + &uuids, + volume, + price, + )); + block_on(mm_node_1.stop()).unwrap(); + block_on(mm_node_2.stop()).unwrap(); +} + +#[test] +// This test is ignored because it requires refilling the tBTC and RICK addresses with test coins periodically. +// This test also takes a lot of time so it should always be ignored. +#[ignore] +#[cfg(not(target_arch = "wasm32"))] +fn test_lightning_maker_swap_mpp() { + let (mut mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(true); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + + block_on(add_trusted_node(&mm_node_1, &node_2_id)); + + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + + // Enable coins on mm_node_1 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_1): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_1)) + ); + + // Enable coins on mm_node_2 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_2): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_2)) + ); + + let price = 10.; + let volume = 0.00025; + let uuids = block_on(start_swaps( + &mut mm_node_2, + &mut mm_node_1, + &[("tBTC-TEST-lightning", "RICK")], + price, + price, + volume, + )); + block_on(wait_for_swaps_finish_and_check_status( + &mut mm_node_2, + &mut mm_node_1, + &uuids, + volume, + price, + )); + block_on(mm_node_1.stop()).unwrap(); + block_on(mm_node_2.stop()).unwrap(); +} + +// Todo: not working for now, should work once on-chain claiming is implemented https://github.com/lightningdevkit/rust-lightning/issues/2017 +// Todo: watchtowers implementation is needed for such cases, if taker is offline +#[test] +// This test is ignored because it requires refilling the tBTC and RICK addresses with test coins periodically. +// This test also takes a lot of time so it should always be ignored. +#[ignore] +#[cfg(not(target_arch = "wasm32"))] +fn test_lightning_taker_gets_swap_preimage_onchain() { + let (mut mm_node_1, mut mm_node_2, node_1_id, _) = start_lightning_nodes(false); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + + let open_channel = block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + let uuid = open_channel["result"]["uuid"].as_str().unwrap(); + + // Enable coins on mm_node_1 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_1): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_1)) + ); + + // Enable coins on mm_node_2 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_2): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_2)) + ); + + let price = 0.0005; + let volume = 0.1; + let uuids = block_on(start_swaps( + &mut mm_node_1, + &mut mm_node_2, + &[("RICK", "tBTC-TEST-lightning")], + price, + price, + volume, + )); + block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); + + // Taker node force closes the channel after the maker receives the payment but before the maker claims the payment and releases the preimage + block_on(close_channel(&mm_node_2, uuid, true)); + + block_on(mm_node_1.wait_for_log(7200., |log| log.contains(&format!("[swap uuid={}] Finished", uuids[0])))).unwrap(); + block_on(mm_node_2.wait_for_log(7200., |log| log.contains(&format!("[swap uuid={}] Finished", uuids[0])))).unwrap(); + + // Todo: If the test passes the payment will be added to the tBTC balance, add a check here, find a way to inform the user of this. + + block_on(mm_node_1.stop()).unwrap(); + block_on(mm_node_2.stop()).unwrap(); +} + +// Todo: not working for now, should work once on-chain claiming is implemented https://github.com/lightningdevkit/rust-lightning/issues/2017 +// Todo: watchtowers implementation is needed for such cases, if taker is offline +#[test] +// This test is ignored because it requires refilling the tBTC and RICK addresses with test coins periodically. +// This test also takes a lot of time so it should always be ignored. +#[ignore] +#[cfg(not(target_arch = "wasm32"))] +fn test_lightning_taker_claims_mpp() { + let (mut mm_node_1, mut mm_node_2, node_1_id, _) = start_lightning_nodes(false); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + + let open_channel_1 = block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + let uuid = open_channel_1["result"]["uuid"].as_str().unwrap(); + block_on(open_channel( + &mut mm_node_2, + "tBTC-TEST-lightning", + &node_1_address, + 0.0002, + true, + )); + + // Enable coins on mm_node_1 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_1): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_1)) + ); + + // Enable coins on mm_node_2 side. Print the replies in case we need the "address". + log!( + "enable_coins (mm_node_2): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_node_2)) + ); + + let price = 0.0025; + let volume = 0.1; + let uuids = block_on(start_swaps( + &mut mm_node_1, + &mut mm_node_2, + &[("RICK", "tBTC-TEST-lightning")], + price, + price, + volume, + )); + + block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); + + // Taker node force closes the channel after the maker receives the payment but before the maker claims the payment and releases the preimage + block_on(close_channel(&mm_node_2, uuid, true)); + + block_on(mm_node_1.wait_for_log(7200., |log| log.contains(&format!("[swap uuid={}] Finished", uuids[0])))).unwrap(); + block_on(mm_node_2.wait_for_log(7200., |log| log.contains(&format!("[swap uuid={}] Finished", uuids[0])))).unwrap(); + + // Todo: If the test passes the payment will be added to the tBTC balance, add a check here, find a way to inform the user of this. + + block_on(mm_node_1.stop()).unwrap(); + block_on(mm_node_2.stop()).unwrap(); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sign_verify_message_lightning() { 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 98425fda32..786f546f82 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -1,6 +1,6 @@ use crate::integration_tests_common::*; use common::executor::Timer; -use common::{cfg_native, cfg_wasm32, get_utc_timestamp, log}; +use common::{cfg_native, cfg_wasm32, get_utc_timestamp, log, new_uuid}; use crypto::privkey::key_pair_from_seed; use http::{HeaderMap, StatusCode}; use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; @@ -1417,7 +1417,7 @@ fn test_swap_status() { "userpass": mm.userpass, "method": "my_swap_status", "params": { - "uuid":Uuid::new_v4(), + "uuid": new_uuid(), } }))) .unwrap(); @@ -1428,7 +1428,7 @@ fn test_swap_status() { "userpass": mm.userpass, "method": "stats_swap_status", "params": { - "uuid":Uuid::new_v4(), + "uuid": new_uuid(), } }))) .unwrap(); diff --git a/mm2src/mm2_test_helpers/Cargo.toml b/mm2src/mm2_test_helpers/Cargo.toml index 9b8e284947..c6030bd3d3 100644 --- a/mm2src/mm2_test_helpers/Cargo.toml +++ b/mm2src/mm2_test_helpers/Cargo.toml @@ -26,7 +26,7 @@ rpc = { path = "../mm2_bitcoin/rpc" } serde = "1" serde_derive = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } -uuid = { version = "0.7", features = ["serde", "v4"] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(target_arch = "wasm32")'.dependencies] chrono = { version = "0.4", features = ["wasmbind"] } From 4eb1557ae6c6145b09cda90b3bc8b1ef87dde6b0 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Fri, 10 Mar 2023 18:47:13 +0300 Subject: [PATCH 33/79] compute MM_VERSION and build for wasm Signed-off-by: ozkanonur --- .github/workflows/build-and-upload.yml | 101 +++++++++++++++++++++++++ .github/workflows/build.yml | 37 --------- .github/workflows/fmt-and-lint.yml | 2 +- 3 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/build-and-upload.yml delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml new file mode 100644 index 0000000000..fcbf31aa9a --- /dev/null +++ b/.github/workflows/build-and-upload.yml @@ -0,0 +1,101 @@ +name: Build and Upload +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + MANUAL_MM_VERSION: true + +jobs: + linux-x86: + timeout-minutes: 30 + runs-on: ubuntu-latest + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: Build + run: | + VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-Linux-CI" + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + cargo build --bin mm2 --profile ci + + mac-x86: + timeout-minutes: 30 + runs-on: macos-latest + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: Build + run: | + VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-Mac-CI" + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + cargo build --bin mm2 --profile ci --target x86_64-apple-darwin + + win-x86: + timeout-minutes: 30 + runs-on: windows-latest + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: Build + run: | + $VERSION=$Env:BRANCH_NAME+"_$(git rev-parse --short=7 HEAD)-Win-CI" + if (test-path "./MM_VERSION") { + remove-item "./MM_VERSION" + } + echo $VERSION > ./MM_VERSION + cargo build --bin mm2 --profile ci + + wasm: + timeout-minutes: 30 + runs-on: ubuntu-latest + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + rustup target add wasm32-unknown-unknown + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build + run: | + VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-Linux-Wasm-CI" + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + wasm-pack build mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d9d5b2ef09..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: CI -on: - push: - branches: - - main - - dev - pull_request: - branches: - - main - - dev - -jobs: - x86-build: - timeout-minutes: 30 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - rust: [nightly-2022-10-29] - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} - - - name: x - run: | - echo "dbg" - - - name: Step for Mac - if: runner.os == 'macOS' - run: cargo build --bin mm2 --profile ci --target x86_64-apple-darwin - - - name: Step for Linux & Win - if: runner.os != 'macOS' - run: cargo build --bin mm2 --profile ci diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index 41b0f1b9b1..99915aedd1 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -1,4 +1,4 @@ -name: CI +name: Format and Lint on: push: branches: From 148366baaa7fe18b93bb44b3fa7e77cc5d8c6475 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Sat, 11 Mar 2023 01:33:16 +0300 Subject: [PATCH 34/79] add arm builds Signed-off-by: ozkanonur --- .github/workflows/build-and-upload.yml | 77 ++++++++++++++++++++++++++ android-ndk.sh | 22 ++++---- 2 files changed, 88 insertions(+), 11 deletions(-) mode change 100644 => 100755 android-ndk.sh diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index fcbf31aa9a..3299a9354c 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -99,3 +99,80 @@ jobs: rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION wasm-pack build mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ + + ios-aarch64: + timeout-minutes: 30 + runs-on: macos-latest + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + rustup target add aarch64-apple-ios + + - name: Build + run: | + VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-ios-aarch64-CI" + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + cargo rustc --target aarch64-apple-ios --lib --profile ci --package mm2_bin_lib --crate-type=staticlib + + android-aarch64: + timeout-minutes: 30 + runs-on: ubuntu-latest + env: + CROSS_CONTAINER_OPTS: '--platform linux/amd64' + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + rustup target add aarch64-linux-android + + - name: Setup NDK + run: ./android-ndk.sh x86 21 + + - name: Build + run: | + VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-android-aarch64-CI" + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + + export PATH=$PATH:/android-ndk/bin + CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --profile ci --crate-type=staticlib --package mm2_bin_lib + + android-armv7: + timeout-minutes: 30 + runs-on: ubuntu-latest + env: + CROSS_CONTAINER_OPTS: '--platform linux/amd64' + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + rustup target add armv7-linux-androideabi + + - name: Setup NDK + run: ./android-ndk.sh x86 21 + + - name: Build + run: | + VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-android-armv7-CI" + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + + export PATH=$PATH:/android-ndk/bin + CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --profile ci --crate-type=staticlib --package mm2_bin_lib diff --git a/android-ndk.sh b/android-ndk.sh old mode 100644 new mode 100755 index f210ab9f9e..07ef7eb782 --- a/android-ndk.sh +++ b/android-ndk.sh @@ -4,20 +4,20 @@ NDK_URL=https://dl.google.com/android/repository/android-ndk-r21b-linux-x86_64.z main() { local arch=$1 \ - api=$2 + api=$2 local dependencies=( unzip - python - curl + python3 + curl ) - apt-get update + sudo apt-get update local purge_list=() for dep in ${dependencies[@]}; do if ! dpkg -L $dep; then - apt-get install --no-install-recommends -y $dep - purge_list+=( $dep ) + sudo apt-get install --no-install-recommends -y $dep + purge_list+=($dep) fi done @@ -27,13 +27,13 @@ main() { curl -O $NDK_URL unzip -q android-ndk-*.zip pushd android-ndk-*/ - ./build/tools/make_standalone_toolchain.py \ - --install-dir /android-ndk \ - --arch $arch \ - --api $api + sudo ./build/tools/make_standalone_toolchain.py \ + --install-dir /android-ndk \ + --arch $arch \ + --api $api # clean up - apt-get purge --auto-remove -y ${purge_list[@]} + sudo apt-get purge --auto-remove -y ${purge_list[@]} popd popd From 169964ec17c6097c736e7af527b490ca6e394935 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Tue, 14 Mar 2023 00:15:25 +0200 Subject: [PATCH 35/79] fix: wait for EVM approval transaction confirmation (#1706) * Wait for the confirmation of the approval transaction for EVM tokens before sending the swap payment * fix allowed amount check * fix wait_for_approval_confirmation_until * provide better error message for approve transaction confirmation fail * use allowance call instead of waiting for approve transaction confirmation --------- Reviewed-by: caglaryucekaya , cipig --- mm2src/coins/eth.rs | 94 +++++++++++++++---- mm2src/coins/eth/eth_tests.rs | 2 + mm2src/coins/eth/eth_wasm_tests.rs | 1 + mm2src/coins/lp_coins.rs | 1 + mm2src/mm2_main/src/lp_swap/maker_swap.rs | 3 + mm2src/mm2_main/src/lp_swap/taker_swap.rs | 1 + .../tests/docker_tests/docker_tests_inner.rs | 5 + .../tests/docker_tests/qrc20_tests.rs | 12 +++ .../tests/docker_tests/swap_watcher_tests.rs | 17 +++- 9 files changed, 113 insertions(+), 23 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 3a0a5c9b8c..e7759a8c34 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -2827,6 +2827,9 @@ impl EthCoin { 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 trade_amount = try_tx_fus!(wei_from_big_decimal(&args.amount, self.decimals)); + let watcher_reward = args.watcher_reward.map(U256::from); + let time_lock = U256::from(args.time_lock); + let gas = U256::from(ETH_GAS); let secret_hash = if args.secret_hash.len() == 32 { ripemd160(args.secret_hash).to_vec() @@ -2836,31 +2839,32 @@ impl EthCoin { match &self.coin_type { EthCoinType::Eth => { - let function_name = get_function_name("ethPayment", args.watcher_reward.is_some()); + let function_name = get_function_name("ethPayment", watcher_reward.is_some()); let function = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let mut value = trade_amount; - let data = match args.watcher_reward { + let mut value = trade_amount; + let data = match watcher_reward { Some(reward) => { - value += U256::from(reward); + value += reward; try_tx_fus!(function.encode_input(&[ Token::FixedBytes(id), Token::Address(receiver_addr), Token::FixedBytes(secret_hash), - Token::Uint(U256::from(args.time_lock)), - Token::Uint(U256::from(reward)), + Token::Uint(time_lock), + Token::Uint(reward), ])) }, None => try_tx_fus!(function.encode_input(&[ Token::FixedBytes(id), Token::Address(receiver_addr), Token::FixedBytes(secret_hash), - Token::Uint(U256::from(args.time_lock)), + Token::Uint(time_lock), ])), }; + drop_mutability!(value); - self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS)) + self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, gas) }, EthCoinType::Erc20 { platform: _, @@ -2870,7 +2874,7 @@ impl EthCoin { .allowance(swap_contract_address) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))); - let function_name = get_function_name("erc20Payment", args.watcher_reward.is_some()); + let function_name = get_function_name("erc20Payment", watcher_reward.is_some()); let function = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let data = try_tx_fus!(function.encode_input(&[ @@ -2879,30 +2883,46 @@ impl EthCoin { Token::Address(*token_addr), Token::Address(receiver_addr), Token::FixedBytes(secret_hash), - Token::Uint(U256::from(args.time_lock)) + Token::Uint(time_lock) ])); - let value = U256::from(args.watcher_reward.unwrap_or(0)); + let wait_for_required_allowance_until = args.wait_for_confirmation_until; let arc = self.clone(); Box::new(allowance_fut.and_then(move |allowed| -> EthTxFut { - if allowed < value { + if allowed < trade_amount { Box::new( arc.approve(swap_contract_address, U256::max_value()) - .and_then(move |_approved| { - arc.sign_and_send_transaction( - value, - Action::Call(swap_contract_address), - data, - U256::from(ETH_GAS), + .and_then(move |approved| { + // make sure the approve tx is confirmed by making sure that the allowed value has been updated + // this call is cheaper than waiting for confirmation calls + arc.wait_for_required_allowance( + swap_contract_address, + trade_amount, + wait_for_required_allowance_until, ) + .map_err(move |e| { + TransactionErr::Plain(ERRL!( + "Allowed value was not updated in time after sending approve transaction {:02x}: {}", + approved.tx_hash(), + e + )) + }) + .and_then(move |_| { + arc.sign_and_send_transaction( + watcher_reward.unwrap_or_else(|| 0.into()), + Action::Call(swap_contract_address), + data, + gas, + ) + }) }), ) } else { Box::new(arc.sign_and_send_transaction( - value, + watcher_reward.unwrap_or_else(|| 0.into()), Action::Call(swap_contract_address), data, - U256::from(ETH_GAS), + gas, )) } })) @@ -3499,6 +3519,40 @@ impl EthCoin { Box::new(fut.boxed().compat()) } + fn wait_for_required_allowance( + &self, + spender: Address, + required_allowance: U256, + wait_until: u64, + ) -> Web3RpcFut<()> { + const CHECK_ALLOWANCE_EVERY: f64 = 5.; + + let selfi = self.clone(); + let fut = async move { + loop { + if now_ms() / 1000 > wait_until { + return MmError::err(Web3RpcError::Internal(ERRL!( + "Waited too long until {} for allowance to be updated to at least {}", + wait_until, + required_allowance + ))); + } + + match selfi.allowance(spender).compat().await { + Ok(allowed) if allowed >= required_allowance => return Ok(()), + Ok(_allowed) => (), + Err(e) => match e.get_inner() { + Web3RpcError::Transport(e) => error!("Error {} on trying to get the allowed amount!", e), + _ => return Err(e), + }, + } + + Timer::sleep(CHECK_ALLOWANCE_EVERY).await; + } + }; + Box::new(fut.boxed().compat()) + } + fn approve(&self, spender: Address, amount: U256) -> EthTxFut { let coin = self.clone(); let fut = async move { diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 10912f09cd..3ca322ad48 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -262,6 +262,7 @@ fn send_and_refund_erc20_payment() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); log!("{:?}", payment); @@ -332,6 +333,7 @@ fn send_and_refund_eth_payment() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = coin.send_maker_payment(send_maker_payment_args).wait().unwrap(); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index d5ed0fc61c..d63c23fe6f 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -60,6 +60,7 @@ async fn test_send() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_maker_payment(maker_payment_args).compat().await; console::log_1(&format!("{:?}", tx).into()); diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 1dd6d0d12f..da239d3550 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -625,6 +625,7 @@ pub struct SendPaymentArgs<'a> { pub swap_unique_data: &'a [u8], pub payment_instructions: &'a Option, pub watcher_reward: Option, + pub wait_for_confirmation_until: u64, } #[derive(Clone, Debug)] diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 00d068dbf5..647337817d 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -828,6 +828,8 @@ impl MakerSwap { Ok(res) => match res { Some(tx) => tx, None => { + let maker_payment_wait_confirm = + wait_for_maker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); 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, @@ -838,6 +840,7 @@ impl MakerSwap { swap_unique_data: &unique_data, payment_instructions: &self.r().payment_instructions, watcher_reward: reward_amount, + wait_for_confirmation_until: maker_payment_wait_confirm, }); match payment_fut.compat().await { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 1a1e583282..bca7aa0f96 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1453,6 +1453,7 @@ impl TakerSwap { swap_unique_data: &unique_data, payment_instructions: &self.r().payment_instructions, watcher_reward: reward_amount, + wait_for_confirmation_until: self.r().data.taker_payment_lock, }); match payment_fut.compat().await { 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 d638f285ce..c89f99528a 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -37,6 +37,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); @@ -111,6 +112,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); @@ -170,6 +172,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); @@ -230,6 +233,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); @@ -293,6 +297,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); if let TransactionEnum::UtxoTx(tx) = tx { diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 9233477c00..6353005c9a 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -191,6 +191,7 @@ fn test_taker_spends_maker_payment() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -287,6 +288,7 @@ fn test_maker_spends_taker_payment() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = taker_coin.send_taker_payment(taker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -372,6 +374,7 @@ fn test_maker_refunds_payment() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = coin.send_maker_payment(maker_payment).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -434,6 +437,7 @@ fn test_taker_refunds_payment() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = coin.send_taker_payment(taker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -493,6 +497,7 @@ fn test_check_if_my_payment_sent() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -544,6 +549,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -617,6 +623,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -689,6 +696,7 @@ fn test_search_for_swap_tx_spend_not_spent() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -741,6 +749,7 @@ fn test_wait_for_tx_spend() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -1058,6 +1067,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let _taker_payment_tx = coin @@ -1457,6 +1467,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_maker_payment(maker_payment).wait().unwrap(); @@ -1515,6 +1526,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_taker_payment(taker_payment).wait().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 6bd474ff32..af4156efcc 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -670,7 +670,8 @@ fn test_watcher_validate_taker_payment_eth() { let maker_pub = maker_keypair.public(); let time_lock_duration = get_payment_locktime(); - let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + let wait_for_confirmation_until = now_ms() / 1000 + time_lock_duration; + let time_lock = wait_for_confirmation_until as u32; let amount = BigDecimal::from_str("0.01").unwrap(); let secret_hash = dhash160(&MakerSwap::generate_secret()); let watcher_reward = Some( @@ -700,6 +701,7 @@ fn test_watcher_validate_taker_payment_eth() { swap_unique_data: &[], payment_instructions: &None, watcher_reward, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -761,6 +763,7 @@ fn test_watcher_validate_taker_payment_eth() { swap_unique_data: &[], payment_instructions: &None, watcher_reward, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -830,6 +833,7 @@ fn test_watcher_validate_taker_payment_eth() { swap_unique_data: &[], payment_instructions: &None, watcher_reward, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -932,7 +936,8 @@ fn test_watcher_validate_taker_payment_erc20() { let maker_pub = maker_keypair.public(); let time_lock_duration = get_payment_locktime(); - let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + let wait_for_confirmation_until = now_ms() / 1000 + time_lock_duration; + let time_lock = wait_for_confirmation_until as u32; let secret_hash = dhash160(&MakerSwap::generate_secret()); let watcher_reward = Some( @@ -961,6 +966,7 @@ fn test_watcher_validate_taker_payment_erc20() { swap_unique_data: &[], payment_instructions: &None, watcher_reward, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -1022,6 +1028,7 @@ fn test_watcher_validate_taker_payment_erc20() { swap_unique_data: &[], payment_instructions: &None, watcher_reward, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -1091,6 +1098,7 @@ fn test_watcher_validate_taker_payment_erc20() { swap_unique_data: &[], payment_instructions: &None, watcher_reward, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -1530,7 +1538,8 @@ fn test_watcher_validate_taker_fee_utxo() { fn test_watcher_validate_taker_payment_utxo() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run let time_lock_duration = get_payment_locktime(); - let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + let wait_for_confirmation_until = now_ms() / 1000 + time_lock_duration; + let time_lock = wait_for_confirmation_until as u32; let (_ctx, taker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let taker_pubkey = taker_coin.my_public_key().unwrap(); @@ -1551,6 +1560,7 @@ fn test_watcher_validate_taker_payment_utxo() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until, }) .wait() .unwrap(); @@ -1723,6 +1733,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { swap_unique_data: &[], payment_instructions: &None, watcher_reward: None, + wait_for_confirmation_until: 0, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); From 07a9515b818c4795ca40af6dfdc9e4be19550171 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 11:31:20 +0300 Subject: [PATCH 36/79] add tests Signed-off-by: ozkanonur --- .github/workflows/build-and-upload.yml | 4 - .github/workflows/test.yml | 135 +++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 3299a9354c..05f14e1d39 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -124,8 +124,6 @@ jobs: android-aarch64: timeout-minutes: 30 runs-on: ubuntu-latest - env: - CROSS_CONTAINER_OPTS: '--platform linux/amd64' strategy: matrix: rust: [nightly-2022-10-29] @@ -152,8 +150,6 @@ jobs: android-armv7: timeout-minutes: 30 runs-on: ubuntu-latest - env: - CROSS_CONTAINER_OPTS: '--platform linux/amd64' strategy: matrix: rust: [nightly-2022-10-29] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..d7dc2df6d5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,135 @@ +name: Test +on: + push: + branches: + - main + - dev + pull_request: + branches: + - main + - dev + +jobs: + linux-x86: + timeout-minutes: 90 + runs-on: ubuntu-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_LINUX }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_LINUX }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: Test + run: | + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + cargo test --all --profile ci + + mac-x86: + timeout-minutes: 90 + runs-on: macos-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_MACOS }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_MACOS }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - uses: docker-practice/actions-setup-docker@master + timeout-minutes: 15 + - name: Build + run: | + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + cargo test --all --target x86_64-apple-darwin --profile ci + + win-x86: + timeout-minutes: 90 + runs-on: windows-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_WIN }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_WIN }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_WIN }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_WIN }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: Test + run: | + Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe + Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat + + # Set Path in variable + $config = "C:\ProgramData\Docker\config\daemon.json" + + $json = Get-Content $config | Out-String | ConvertFrom-Json + $json | Add-Member -Type NoteProperty -Name 'experimental' -Value $True + $json | ConvertTo-Json | Set-Content $config + + # Check the file content + type $config + + Restart-Service docker + Get-Service docker + + cargo test --all --profile ci + + wasm: + timeout-minutes: 90 + runs-on: ubuntu-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_LINUX }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_LINUX }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + rustup target add wasm32-unknown-unknown + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Install firefox and geckodriver + run: | + sudo apt-get update -y + sudo apt-get install -y firefox + wget https://github.com/mozilla/geckodriver/releases/download/v0.32.2/geckodriver-v0.32.2-linux64.tar.gz + sudo tar -xzvf geckodriver-v0.32.2-linux64.tar.gz -C /bin + sudo chmod +x /bin/geckodriver + + - name: Test + run: WASM_BINDGEN_TEST_TIMEOUT=360 GECKODRIVER=/bin/geckodriver wasm-pack test --firefox --headless mm2src/mm2_main + From 376e38350a96ee3b5251d1c35c52466be9c041f3 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 12:12:54 +0300 Subject: [PATCH 37/79] swtich docker deamon in win Signed-off-by: ozkanonur --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d7dc2df6d5..c1cb97c952 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,6 +94,8 @@ jobs: # Check the file content type $config + $Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon . + Restart-Service docker Get-Service docker From a5fc6f8f4cc05b3532156336814687820a9c37c2 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 12:18:03 +0300 Subject: [PATCH 38/79] test Signed-off-by: ozkanonur --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1cb97c952..80166b9219 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,11 +94,11 @@ jobs: # Check the file content type $config - $Env:ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon . - Restart-Service docker Get-Service docker + C:\ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon . + cargo test --all --profile ci wasm: From f01a7044c3b1d119acbc5e0675b649fbb34545c2 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 12:32:24 +0300 Subject: [PATCH 39/79] test Signed-off-by: ozkanonur --- .github/workflows/test.yml | 85 ++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80166b9219..aa5fc1b258 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,9 +60,48 @@ jobs: wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --target x86_64-apple-darwin --profile ci + # win-x86: + # timeout-minutes: 90 + # runs-on: windows-latest + # env: + # BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_WIN }} + # BOB_USERPASS: ${{ secrets.BOB_USERPASS_WIN }} + # ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_WIN }} + # ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_WIN }} + # TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + # strategy: + # matrix: + # rust: [nightly-2022-10-29] + # steps: + # - uses: actions/checkout@v3 + # - name: Install toolchain + # run: | + # rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + # rustup default ${{ matrix.rust }} + # + # - name: Test + # run: | + # Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe + # Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat + # + # # Set Path in variable + # $config = "C:\ProgramData\Docker\config\daemon.json" + # + # $json = Get-Content $config | Out-String | ConvertFrom-Json + # $json | Add-Member -Type NoteProperty -Name 'experimental' -Value $True + # $json | ConvertTo-Json | Set-Content $config + # + # # Check the file content + # type $config + # + # Restart-Service docker + # Get-Service docker + # + # cargo test --all --profile ci + win-x86: timeout-minutes: 90 - runs-on: windows-latest + runs-on: ubuntu-latest env: BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_WIN }} BOB_USERPASS: ${{ secrets.BOB_USERPASS_WIN }} @@ -81,25 +120,35 @@ jobs: - name: Test run: | - Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe - Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat - - # Set Path in variable - $config = "C:\ProgramData\Docker\config\daemon.json" - - $json = Get-Content $config | Out-String | ConvertFrom-Json - $json | Add-Member -Type NoteProperty -Name 'experimental' -Value $True - $json | ConvertTo-Json | Set-Content $config - - # Check the file content - type $config - - Restart-Service docker - Get-Service docker + sudo apt install mingw-w64 + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + cargo test --all --profile ci --target x86_64-pc-windows-gnu - C:\ProgramFiles\Docker\Docker\DockerCli.exe -SwitchDaemon . + mac-x86: + timeout-minutes: 90 + runs-on: macos-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_MACOS }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_MACOS }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} - cargo test --all --profile ci + - uses: docker-practice/actions-setup-docker@master + timeout-minutes: 15 + - name: Build + run: | + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + cargo test --all --target x86_64-apple-darwin --profile ci wasm: timeout-minutes: 90 From 7df9405de2850c97b3e1c071cf27516221dceaab Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 12:37:23 +0300 Subject: [PATCH 40/79] test Signed-off-by: ozkanonur --- .github/workflows/test.yml | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aa5fc1b258..d59db36ef3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -124,32 +124,6 @@ jobs: wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --profile ci --target x86_64-pc-windows-gnu - mac-x86: - timeout-minutes: 90 - runs-on: macos-latest - env: - BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_MACOS }} - BOB_USERPASS: ${{ secrets.BOB_USERPASS_MACOS }} - ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} - ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} - TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - strategy: - matrix: - rust: [nightly-2022-10-29] - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} - - - uses: docker-practice/actions-setup-docker@master - timeout-minutes: 15 - - name: Build - run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash - cargo test --all --target x86_64-apple-darwin --profile ci - wasm: timeout-minutes: 90 runs-on: ubuntu-latest From 4d2a8cd794e44209226df6b5aa76f10e805386bf Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 13:06:58 +0300 Subject: [PATCH 41/79] run docker tests in seperated job Signed-off-by: ozkanonur --- .github/workflows/test.yml | 87 +++++++++++----------- mm2src/mm2_main/Cargo.toml | 1 + mm2src/mm2_main/tests/docker_tests_main.rs | 1 + 3 files changed, 45 insertions(+), 44 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d59db36ef3..6352d282f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,7 +31,7 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --profile ci mac-x86: @@ -57,51 +57,12 @@ jobs: timeout-minutes: 15 - name: Build run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --target x86_64-apple-darwin --profile ci - # win-x86: - # timeout-minutes: 90 - # runs-on: windows-latest - # env: - # BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_WIN }} - # BOB_USERPASS: ${{ secrets.BOB_USERPASS_WIN }} - # ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_WIN }} - # ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_WIN }} - # TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - # strategy: - # matrix: - # rust: [nightly-2022-10-29] - # steps: - # - uses: actions/checkout@v3 - # - name: Install toolchain - # run: | - # rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - # rustup default ${{ matrix.rust }} - # - # - name: Test - # run: | - # Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe - # Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat - # - # # Set Path in variable - # $config = "C:\ProgramData\Docker\config\daemon.json" - # - # $json = Get-Content $config | Out-String | ConvertFrom-Json - # $json | Add-Member -Type NoteProperty -Name 'experimental' -Value $True - # $json | ConvertTo-Json | Set-Content $config - # - # # Check the file content - # type $config - # - # Restart-Service docker - # Get-Service docker - # - # cargo test --all --profile ci - win-x86: timeout-minutes: 90 - runs-on: ubuntu-latest + runs-on: windows-latest env: BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_WIN }} BOB_USERPASS: ${{ secrets.BOB_USERPASS_WIN }} @@ -120,9 +81,47 @@ jobs: - name: Test run: | - sudo apt install mingw-w64 + # Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe + # Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat + + # Set Path in variable + # $config = "C:\ProgramData\Docker\config\daemon.json" + + # $json = Get-Content $config | Out-String | ConvertFrom-Json + # $json | Add-Member -Type NoteProperty -Name 'experimental' -Value $True + # $json | ConvertTo-Json | Set-Content $config + + # Check the file content + # type $config + + # Restart-Service docker + # Get-Service docker + + cargo test --all --profile ci + + docker-tests: + timeout-minutes: 90 + runs-on: ubuntu-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_LINUX }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_LINUX }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + strategy: + matrix: + rust: [nightly-2022-10-29] + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal + rustup default ${{ matrix.rust }} + + - name: Test + run: | wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash - cargo test --all --profile ci --target x86_64-pc-windows-gnu + cargo test --test 'docker_tests_main' --features run-docker-tests --profile ci wasm: timeout-minutes: 90 diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 1e8f5200f1..0abf2a81bb 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -17,6 +17,7 @@ native = [] # Deprecated track-ctx-pointer = ["common/track-ctx-pointer"] zhtlc-native-tests = ["coins/zhtlc-native-tests"] enable-nft-integration = ["coins/enable-nft-integration"] +run-docker-tests = [] # TODO enable-solana = [] default = [] diff --git a/mm2src/mm2_main/tests/docker_tests_main.rs b/mm2src/mm2_main/tests/docker_tests_main.rs index e5a1140967..6ef9cba2d5 100644 --- a/mm2src/mm2_main/tests/docker_tests_main.rs +++ b/mm2src/mm2_main/tests/docker_tests_main.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "run-docker-tests")] #![feature(async_closure)] #![feature(custom_test_frameworks)] #![feature(test)] From ac0798455e04c2443882adf4046ae81ea15bd6dc Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 13:11:24 +0300 Subject: [PATCH 42/79] get rid of docker in mac Signed-off-by: ozkanonur --- .github/workflows/test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6352d282f9..ce06b51d3f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,9 +53,7 @@ jobs: rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal rustup default ${{ matrix.rust }} - - uses: docker-practice/actions-setup-docker@master - timeout-minutes: 15 - - name: Build + - name: Test run: | # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --target x86_64-apple-darwin --profile ci From 29e28bcd03913d0bfdc1c42b252261b4aa8b5f99 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 13:32:16 +0300 Subject: [PATCH 43/79] update test docs Signed-off-by: ozkanonur --- CONTRIBUTING.md | 2 +- docs/DEV_ENVIRONMENT.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 645223074e..2fa24a183e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ Please note we have a code of conduct, please follow it in all your interactions Before uploading any changes, please make sure that the test suite passes locally before submitting a pull request with your changes. ``` -cargo test --all +cargo test --all --features run-docker-tests ``` We also use [Clippy](https://github.com/rust-lang/rust-clippy) to avoid common mistakes diff --git a/docs/DEV_ENVIRONMENT.md b/docs/DEV_ENVIRONMENT.md index 3c827d6fdc..0253bdd25e 100644 --- a/docs/DEV_ENVIRONMENT.md +++ b/docs/DEV_ENVIRONMENT.md @@ -36,7 +36,7 @@ ``` sudo ln -s $(which podman) /usr/bin/docker ``` -9. Try `cargo test --features native --all -- --test-threads=16`. +9. Try `cargo test --features "native run-docker-tests" --all -- --test-threads=16`. ## Running WASM tests From 6a47670c39127c4b1fb6391f07aa39117c7ca57c Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 15:00:35 +0300 Subject: [PATCH 44/79] test Signed-off-by: ozkanonur --- .github/workflows/build-and-upload.yml | 6 +++--- .github/workflows/fmt-and-lint.yml | 4 ++-- .github/workflows/test.yml | 6 +++--- mm2src/mm2_git/src/github_client.rs | 12 ++++++++++++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 05f14e1d39..39bbcd49f8 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -14,7 +14,7 @@ env: MANUAL_MM_VERSION: true jobs: - linux-x86: + linux-x86-64: timeout-minutes: 30 runs-on: ubuntu-latest strategy: @@ -34,7 +34,7 @@ jobs: echo $VERSION > ./MM_VERSION cargo build --bin mm2 --profile ci - mac-x86: + mac-x86-64: timeout-minutes: 30 runs-on: macos-latest strategy: @@ -54,7 +54,7 @@ jobs: echo $VERSION > ./MM_VERSION cargo build --bin mm2 --profile ci --target x86_64-apple-darwin - win-x86: + win-x86-64: timeout-minutes: 30 runs-on: windows-latest strategy: diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index 99915aedd1..2e82ec77af 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -10,7 +10,7 @@ on: - dev jobs: - fmt-and-x86-lint: + fmt-and-x86-64-lint: timeout-minutes: 25 runs-on: ${{ matrix.os }} strategy: @@ -27,7 +27,7 @@ jobs: - name: fmt check run: cargo fmt -- --check - - name: x86 code lint + - name: x86-64 code lint run: cargo clippy --profile ci -- --D warnings wasm-lint: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce06b51d3f..6760e83e6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ on: - dev jobs: - linux-x86: + linux-x86-64: timeout-minutes: 90 runs-on: ubuntu-latest env: @@ -34,7 +34,7 @@ jobs: # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --profile ci - mac-x86: + mac-x86-64: timeout-minutes: 90 runs-on: macos-latest env: @@ -58,7 +58,7 @@ jobs: # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash cargo test --all --target x86_64-apple-darwin --profile ci - win-x86: + win-x86-64: timeout-minutes: 90 runs-on: windows-latest env: diff --git a/mm2src/mm2_git/src/github_client.rs b/mm2src/mm2_git/src/github_client.rs index e13f97ba18..f5dacd129e 100644 --- a/mm2src/mm2_git/src/github_client.rs +++ b/mm2src/mm2_git/src/github_client.rs @@ -28,6 +28,12 @@ impl RepositoryOperations for GithubClient { .await .map_err(|e| GitControllerError::HttpError(e.to_string()))?; + #[cfg(test)] + #[allow(unused_must_use)] + { + dbg!(serde_json::from_slice::(&data_buffer)); + } + Ok( serde_json::from_slice(&data_buffer) .map_err(|e| GitControllerError::DeserializationError(e.to_string()))?, @@ -53,6 +59,12 @@ impl RepositoryOperations for GithubClient { .await .map_err(|e| GitControllerError::HttpError(e.to_string()))?; + #[cfg(test)] + #[allow(unused_must_use)] + { + dbg!(serde_json::from_slice::(&data_buffer)); + } + Ok( serde_json::from_slice(&data_buffer) .map_err(|e| GitControllerError::DeserializationError(e.to_string()))?, From 5faec02723dc98144986fd5630dd92c22a088c74 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Tue, 14 Mar 2023 13:31:43 +0100 Subject: [PATCH 45/79] feat: Include maker/taker pubkeys in MM2.db stats_swaps table (#1665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * saved dev state Signed-off-by: borngraced * include pubkeys in stats_swaps.rs Signed-off-by: borngraced * remove println Signed-off-by: borngraced * refactor get_swap_pubkeys -> swap_pubkeys Signed-off-by: borngraced * change ADD_MAKER_TAKER_PUBKEYS types Signed-off-by: borngraced * remove NegotiationDataV4 Signed-off-by: borngraced * minor changes — ready for review Signed-off-by: borngraced * remove is_private_coin fn Signed-off-by: borngraced * remove NegotiationDataV4 Signed-off-by: borngraced * remove my_persistent_pub from maker/taker_swap Signed-off-by: borngraced * remove my_persistent_pub from maker/taker_swap Signed-off-by: borngraced * fix unit tests Signed-off-by: borngraced * save dev state — stats_swap swap coin pubkeys unit test Signed-off-by: borngraced * finish impl unit test Signed-off-by: borngraced * remove #[ignore] Signed-off-by: borngraced * minor changes Signed-off-by: borngraced * remove unused Signed-off-by: borngraced * undo unit tests changes Signed-off-by: borngraced --------- Signed-off-by: borngraced Signed-off-by: borngraced Reviewed-by: ozkanonur , shamardy , sergeyboyko0791 --- mm2src/mm2_main/src/database.rs | 5 +++ mm2src/mm2_main/src/database/stats_swaps.rs | 37 ++++++++++++++++++- mm2src/mm2_main/src/lp_swap.rs | 10 +++++ mm2src/mm2_main/src/lp_swap/maker_swap.rs | 19 +++++++++- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 19 +++++++++- .../tests/mm2_tests/mm2_tests_inner.rs | 12 +++--- 6 files changed, 90 insertions(+), 12 deletions(-) diff --git a/mm2src/mm2_main/src/database.rs b/mm2src/mm2_main/src/database.rs index fcb6704b67..3a6f1f3add 100644 --- a/mm2src/mm2_main/src/database.rs +++ b/mm2src/mm2_main/src/database.rs @@ -97,6 +97,10 @@ fn migration_7() -> Vec<(&'static str, Vec)> { db_common::sqlite::execute_batch(stats_swaps::ADD_COINS_PRICE_INFOMATION) } +fn migration_8() -> Vec<(&'static str, Vec)> { + db_common::sqlite::execute_batch(stats_swaps::ADD_MAKER_TAKER_PUBKEYS) +} + async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option)>> { match current_migration { 1 => Some(migration_1(ctx).await), @@ -106,6 +110,7 @@ async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option 5 => Some(migration_5()), 6 => Some(migration_6()), 7 => Some(migration_7()), + 8 => Some(migration_8()), _ => None, } } diff --git a/mm2src/mm2_main/src/database/stats_swaps.rs b/mm2src/mm2_main/src/database/stats_swaps.rs index e8b75be391..9ee2599aba 100644 --- a/mm2src/mm2_main/src/database/stats_swaps.rs +++ b/mm2src/mm2_main/src/database/stats_swaps.rs @@ -43,14 +43,23 @@ const INSERT_STATS_SWAP: &str = "INSERT INTO stats_swaps ( taker_amount, is_success, maker_coin_usd_price, - taker_coin_usd_price -) VALUES (:maker_coin, :maker_coin_ticker, :maker_coin_platform, :taker_coin, :taker_coin_ticker, :taker_coin_platform, :uuid, :started_at, :finished_at, :maker_amount, :taker_amount, :is_success, :maker_coin_usd_price, :taker_coin_usd_price)"; + taker_coin_usd_price, + maker_pubkey, + taker_pubkey +) VALUES (:maker_coin, :maker_coin_ticker, :maker_coin_platform, :taker_coin, :taker_coin_ticker, +:taker_coin_platform, :uuid, :started_at, :finished_at, :maker_amount, :taker_amount, :is_success, +:maker_coin_usd_price, :taker_coin_usd_price, :maker_pubkey, :taker_pubkey)"; pub const ADD_COINS_PRICE_INFOMATION: &[&str] = &[ "ALTER TABLE stats_swaps ADD COLUMN maker_coin_usd_price DECIMAL;", "ALTER TABLE stats_swaps ADD COLUMN taker_coin_usd_price DECIMAL;", ]; +pub const ADD_MAKER_TAKER_PUBKEYS: &[&str] = &[ + "ALTER TABLE stats_swaps ADD COLUMN maker_pubkey VARCHAR(255);", + "ALTER TABLE stats_swaps ADD COLUMN taker_pubkey VARCHAR(255);", +]; + pub const ADD_SPLIT_TICKERS: &[&str] = &[ "ALTER TABLE stats_swaps ADD COLUMN maker_coin_ticker VARCHAR(255) NOT NULL DEFAULT '';", "ALTER TABLE stats_swaps ADD COLUMN maker_coin_platform VARCHAR(255) NOT NULL DEFAULT '';", @@ -118,6 +127,7 @@ fn insert_stats_maker_swap_sql(swap: &MakerSavedSwap) -> Option<(&'static str, O return None; }, }; + let finished_at = match swap.finished_at() { Ok(t) => t.to_string(), Err(e) => { @@ -125,6 +135,15 @@ fn insert_stats_maker_swap_sql(swap: &MakerSavedSwap) -> Option<(&'static str, O return None; }, }; + + let pubkeys = match swap.swap_pubkeys() { + Ok(p) => p, + Err(e) => { + error!("Error {} on getting swap {} pubkeys", e, swap.uuid); + return None; + }, + }; + let is_success = swap .is_success() .expect("is_success can return error only when swap is not finished"); @@ -147,7 +166,10 @@ fn insert_stats_maker_swap_sql(swap: &MakerSavedSwap) -> Option<(&'static str, O ":is_success": (is_success as u32).to_string(), ":maker_coin_usd_price": swap.maker_coin_usd_price.as_ref().map(|p| p.to_string()), ":taker_coin_usd_price": swap.taker_coin_usd_price.as_ref().map(|p| p.to_string()), + ":maker_pubkey": pubkeys.maker, + ":taker_pubkey": pubkeys.taker, }; + Some((INSERT_STATS_SWAP, params)) } @@ -198,6 +220,15 @@ fn insert_stats_taker_swap_sql(swap: &TakerSavedSwap) -> Option<(&'static str, O return None; }, }; + + let pubkeys = match swap.swap_pubkeys() { + Ok(p) => p, + Err(e) => { + error!("Error {} on getting swap {} pubkeys", e, swap.uuid); + return None; + }, + }; + let is_success = swap .is_success() .expect("is_success can return error only when swap is not finished"); @@ -220,6 +251,8 @@ fn insert_stats_taker_swap_sql(swap: &TakerSavedSwap) -> Option<(&'static str, O ":is_success": (is_success as u32).to_string(), ":maker_coin_usd_price": swap.maker_coin_usd_price.as_ref().map(|p| p.to_string()), ":taker_coin_usd_price": swap.taker_coin_usd_price.as_ref().map(|p| p.to_string()), + ":maker_pubkey": pubkeys.maker, + ":taker_pubkey": pubkeys.taker, }; Some((INSERT_STATS_SWAP, params)) } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 0080ca3932..ba63b3e6de 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1450,6 +1450,16 @@ fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> } } +pub struct SwapPubkeys { + pub maker: String, + pub taker: String, +} + +impl SwapPubkeys { + #[inline] + fn new(maker: String, taker: String) -> Self { SwapPubkeys { maker, taker } } +} + #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 647337817d..d80fecc413 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -8,8 +8,8 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_messa get_locked_amount, recv_swap_msg, swap_topic, taker_payment_spend_deadline, tx_helper_topic, wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, - SavedTradeFee, SecretHashAlgo, SwapConfirmationsSettings, SwapError, SwapMsg, SwapTxDataMsg, SwapsContext, - TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; + SavedTradeFee, SecretHashAlgo, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, + SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::{MakerOrderBuilder, OrderConfirmationsSettings}; @@ -1925,6 +1925,21 @@ impl MakerSavedSwap { self.taker_coin_usd_price = Some(rates.rel); } } + + pub fn swap_pubkeys(&self) -> Result { + match self.events.first() { + Some(event) => match &event.event { + // TODO: Adjust for private coins when/if they are braodcasted + // TODO: Adjust for HD wallet when completed + MakerSwapEvent::Started(data) => Ok(SwapPubkeys::new( + data.my_persistent_pub.to_string(), + data.taker.to_string(), + )), + _ => ERR!("First swap event must be Started"), + }, + None => ERR!("Can't get maker/taker pubkey, events are empty"), + } + } } #[allow(clippy::large_enum_variant)] diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index bca7aa0f96..e21cc22d13 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -8,8 +8,8 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_mes 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, SwapTxDataMsg, - SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; + SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, + SwapTxDataMsg, SwapsContext, TransactionIdentifier, WAIT_CONFIRM_INTERVAL}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::{MatchBy, OrderConfirmationsSettings, TakerAction, TakerOrderBuilder}; use crate::mm2::lp_price::fetch_swap_coins_price; @@ -302,6 +302,21 @@ impl TakerSavedSwap { self.taker_coin_usd_price = Some(rates.rel); } } + + pub fn swap_pubkeys(&self) -> Result { + match self.events.first() { + Some(event) => match &event.event { + // TODO: Adjust for private coins when/if they are braodcasted + // TODO: Adjust for HD wallet when completed + TakerSwapEvent::Started(data) => Ok(SwapPubkeys::new( + data.maker.to_string(), + data.my_persistent_pub.to_string(), + )), + _ => ERR!("First swap event must be Started"), + }, + None => ERR!("Can't get maker/taker pubkey, events are empty"), + } + } } #[allow(clippy::large_enum_variant)] 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 786f546f82..ca257a6118 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -3349,7 +3349,7 @@ fn test_add_delegation_qtum() { "pass".into(), None, ) - .unwrap(); + .unwrap(); let json = block_on(enable_electrum(&mm, "tQTUM", false, &[ "electrum1.cipig.net:10071", @@ -3434,7 +3434,7 @@ fn test_remove_delegation_qtum() { "pass".into(), None, ) - .unwrap(); + .unwrap(); let json = block_on(enable_electrum(&mm, "tQTUM", false, &[ "electrum1.cipig.net:10071", @@ -4518,7 +4518,7 @@ fn test_tx_history_tbtc_non_segwit() { "mm2": 1, "tx_history": true, }))) - .unwrap(); + .unwrap(); assert_eq!( electrum.0, StatusCode::OK, @@ -6827,9 +6827,9 @@ fn test_sign_verify_message_eth() { "0xcdf11a9c4591fb7334daa4b21494a2590d3f7de41c7d2b333a5b61ca59da9b311b492374cc0ba4fbae53933260fa4b1c18f15d95b694629a7b0620eec77a938600" ); - let response = block_on(verify_message(&mm_bob, "ETH", - "0xcdf11a9c4591fb7334daa4b21494a2590d3f7de41c7d2b333a5b61ca59da9b311b492374cc0ba4fbae53933260fa4b1c18f15d95b694629a7b0620eec77a938600", - "0xbAB36286672fbdc7B250804bf6D14Be0dF69fa29")); + let response = block_on(verify_message(&mm_bob, "ETH", + "0xcdf11a9c4591fb7334daa4b21494a2590d3f7de41c7d2b333a5b61ca59da9b311b492374cc0ba4fbae53933260fa4b1c18f15d95b694629a7b0620eec77a938600", + "0xbAB36286672fbdc7B250804bf6D14Be0dF69fa29")); let response: RpcV2Response = json::from_value(response).unwrap(); let response = response.result; From abcd89196dc895e874bff27f4303d75a49a34958 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 15:51:44 +0300 Subject: [PATCH 46/79] disable `test_metadata_list_and_json_deserialization` for `ci` profile Signed-off-by: ozkanonur --- mm2src/mm2_git/src/github_client.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm2src/mm2_git/src/github_client.rs b/mm2src/mm2_git/src/github_client.rs index f5dacd129e..e85d6ede07 100644 --- a/mm2src/mm2_git/src/github_client.rs +++ b/mm2src/mm2_git/src/github_client.rs @@ -117,6 +117,9 @@ mod tests { } #[test] + // Since we are using shared CI runners, + // this test may fail due to rate limiting from Github. + #[cfg(not(profile = "ci"))] fn test_metadata_list_and_json_deserialization() { const REPO_OWNER: &str = "KomodoPlatform"; const REPO_NAME: &str = "chain-registry"; From 159b1b5f905743f7e652d0b44513f468b2e07b6f Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:17:23 +0200 Subject: [PATCH 47/79] docs: Add change logs for PRs merged to dev only (#1702) * add change logs for PRs merged to dev only * remove {version} - {date} and add dev branch instead * add ibc transfer change logs * add lightning PR #1655 to change logs * add changelog for #1706 * add changelog for #1694, #1665 --------- Reviewed-by: laruh, caglaryucekaya --- CHANGELOG.md | 28 +++++++++++++++++++ .../2023-feb/features/nft_integration_poc | 4 --- 2 files changed, 28 insertions(+), 4 deletions(-) delete mode 100644 dev-logs/2023-feb/features/nft_integration_poc diff --git a/CHANGELOG.md b/CHANGELOG.md index 1122bfa5e3..1101a199c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## dev branch + +**Features:** +- NFT integration `WIP` [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) + - NFT integration PoC added. Includes ERC721 support for ETH and BSC [#1652](https://github.com/KomodoPlatform/atomicDEX-API/pull/1652) +- Swap watcher nodes [#1431](https://github.com/KomodoPlatform/atomicDEX-API/issues/1431) + - Watcher rewards for ETH swaps were added [#1658](https://github.com/KomodoPlatform/atomicDEX-API/pull/1658) +- Cosmos integration `WIP` [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - `ibc_withdraw`, `ibc_chains` and `ibc_transfer_channels` RPC methods were added [#1636](https://github.com/KomodoPlatform/atomicDEX-API/pull/1636) +- Lightning integration `WIP` [#1045](https://github.com/KomodoPlatform/atomicDEX-API/issues/1045) + - [rust-lightning](https://github.com/lightningdevkit/rust-lightning) was updated to [v0.0.113](https://github.com/lightningdevkit/rust-lightning/releases/tag/v0.0.113) in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) + - Channel `current_confirmations` and `required_confirmations` were added to channel details RPCs in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) + - `Uuid` is now used for internal channel id instead of `u64` [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) + - Some unit tests were added for multi path payments in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) + - Some unit tests for claiming swaps on-chain for closed channels were added in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655), these unit tests are currently failing. + - `protocol_info` fields are now used to check if a lightning order can be matched or not in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) + - 2 issues discovered while executing a KMD/LNBTC swap were fixed in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655), these issues were: + - When electrums were down, if a channel was closed, the channel closing transaction wasn't broadcasted. A check for a network error to rebroadcast the tx after a delay was added. + - If an invoice payment failed, retring paying the same invoice would cause the payment to not be updated to successful in the DB even if it were successful. A method to update the payment in the DB was added to fix this. +- `mm2_git` crate was added to provide an abstraction layer on Git for doing query/parse operations over the repositories [#1636](https://github.com/KomodoPlatform/atomicDEX-API/pull/1636) + +**Enhancements/Fixes:** +- IndexedDB Cursor can now iterate over the items step-by-step [#1678](https://github.com/KomodoPlatform/atomicDEX-API/pull/1678) +- Uuid lib was update from v0.7.4 to v1.2.2 in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) +- A bug was fixed in [#1706](https://github.com/KomodoPlatform/atomicDEX-API/pull/1706) where EVM swap transactions were failing if sent before the approval transaction confirmation. +- Tendermint account sequence problem due to running multiple instances were fixed in [#1694](https://github.com/KomodoPlatform/atomicDEX-API/pull/1694) +- Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) + ## v1.0.0-beta - 2023-03-08 **Features:** diff --git a/dev-logs/2023-feb/features/nft_integration_poc b/dev-logs/2023-feb/features/nft_integration_poc deleted file mode 100644 index bfdaff18c6..0000000000 --- a/dev-logs/2023-feb/features/nft_integration_poc +++ /dev/null @@ -1,4 +0,0 @@ -NFT integration PoC added. Includes ERC721 support for ETH and BSC. - - -author: @laruh \ No newline at end of file From c6b07e1ee15a207f672ae3bdfa7e87c8c23e7673 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 16:20:30 +0300 Subject: [PATCH 48/79] optimize workflow configs Signed-off-by: ozkanonur --- .github/workflows/build-and-upload.yml | 49 ++++++------------- .github/workflows/fmt-and-lint.yml | 14 +++--- .github/workflows/test.yml | 35 ++++--------- .../tests/mm2_tests/tendermint_tests.rs | 13 +++++ 4 files changed, 43 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 39bbcd49f8..90c88001de 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -17,15 +17,12 @@ jobs: linux-x86-64: timeout-minutes: 30 runs-on: ubuntu-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Build run: | @@ -37,15 +34,12 @@ jobs: mac-x86-64: timeout-minutes: 30 runs-on: macos-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Build run: | @@ -57,15 +51,12 @@ jobs: win-x86-64: timeout-minutes: 30 runs-on: windows-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Build run: | @@ -79,15 +70,12 @@ jobs: wasm: timeout-minutes: 30 runs-on: ubuntu-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 rustup target add wasm32-unknown-unknown - name: Install wasm-pack @@ -103,15 +91,12 @@ jobs: ios-aarch64: timeout-minutes: 30 runs-on: macos-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 rustup target add aarch64-apple-ios - name: Build @@ -124,15 +109,12 @@ jobs: android-aarch64: timeout-minutes: 30 runs-on: ubuntu-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 rustup target add aarch64-linux-android - name: Setup NDK @@ -150,15 +132,12 @@ jobs: android-armv7: timeout-minutes: 30 runs-on: ubuntu-latest - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 rustup target add armv7-linux-androideabi - name: Setup NDK diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index 2e82ec77af..0c290233f2 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -10,19 +10,18 @@ on: - dev jobs: - fmt-and-x86-64-lint: + x86-64: timeout-minutes: 25 runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal --component rustfmt clippy + rustup default nightly-2022-10-29 - name: fmt check run: cargo fmt -- --check @@ -30,19 +29,18 @@ jobs: - name: x86-64 code lint run: cargo clippy --profile ci -- --D warnings - wasm-lint: + wasm: timeout-minutes: 25 runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal --component rustfmt clippy - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal --component rustfmt clippy + rustup default nightly-2022-10-29 rustup target add wasm32-unknown-unknown - name: wasm code lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6760e83e6c..70c7d505d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,15 +19,12 @@ jobs: ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Test run: | @@ -43,15 +40,12 @@ jobs: ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Test run: | @@ -67,15 +61,12 @@ jobs: ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_WIN }} ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_WIN }} TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Test run: | @@ -106,15 +97,12 @@ jobs: ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 - name: Test run: | @@ -130,15 +118,12 @@ jobs: ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - strategy: - matrix: - rust: [nightly-2022-10-29] steps: - uses: actions/checkout@v3 - name: Install toolchain run: | - rustup toolchain install ${{ matrix.rust }} --no-self-update --profile=minimal - rustup default ${{ matrix.rust }} + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 rustup target add wasm32-unknown-unknown - name: Install wasm-pack diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 51db8dd236..8e24b4411e 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -109,6 +109,12 @@ fn test_tendermint_withdraw() { "cosmos1w5h6wud7a8zpa539rc99ehgl9gwkad3wjsjq8v".to_owned() ]); + let tx_details = block_on(withdraw_v1( + &mm, + ATOM_TICKER, + "cosmos1w5h6wud7a8zpa539rc99ehgl9gwkad3wjsjq8v", + "0.1", + )); 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()); } @@ -141,6 +147,7 @@ fn test_tendermint_ibc_withdraw() { assert_eq!(tx_details.to, vec![IBC_TARGET_ADDRESS.to_owned()]); assert_eq!(tx_details.from, vec![MY_ADDRESS.to_owned()]); + let tx_details = block_on(ibc_withdraw(&mm, IBC_SOURCE_CHANNEL, token, IBC_TARGET_ADDRESS, "0.1")); 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()); } @@ -220,6 +227,12 @@ fn test_tendermint_token_activation_and_withdraw() { "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2".to_owned() ]); + let tx_details = block_on(withdraw_v1( + &mm, + token, + "iaa1e0rx87mdj79zejewuc4jg7ql9ud2286g2us8f2", + "0.1", + )); 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()); } From bf4ba65053802060819aa75947fd4655597870ef Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 16:58:07 +0300 Subject: [PATCH 49/79] disable incompatible tests Signed-off-by: ozkanonur --- mm2src/common/custom_futures/repeatable.rs | 1 + mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/mm2src/common/custom_futures/repeatable.rs b/mm2src/common/custom_futures/repeatable.rs index 1e31bc5a4d..5902f2a577 100644 --- a/mm2src/common/custom_futures/repeatable.rs +++ b/mm2src/common/custom_futures/repeatable.rs @@ -532,6 +532,7 @@ mod tests { } #[test] + #[cfg(not(target_os = "macos"))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_until_expired() { const ATTEMPTS_TO_FINISH: usize = 10; const LOWEST_TIMEOUT: Duration = Duration::from_millis(350); diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs index ea4bf4e289..f9b1a7588d 100644 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs +++ b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs @@ -127,6 +127,7 @@ async fn test_request_response_ok() { } #[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(); @@ -265,6 +266,7 @@ async fn test_request_response_none() { } #[tokio::test] +#[cfg(not(windows))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 async fn test_request_peers_ok_three_peers() { let _ = env_logger::try_init(); From f8ded55c61fc9b8fa48b12285a921a423b3fc7ab Mon Sep 17 00:00:00 2001 From: DeckerSU Date: Tue, 14 Mar 2023 15:48:04 +0100 Subject: [PATCH 50/79] fix: get rid of crossterm and crates dependent on it (#1710) * get rid of crossterm usage in Rust sources * disable use gstuff::ISATTY in tests * remove crossterm and dependent packages from dependencies * update CHANGELOG.md * add colored output for log Co-authored-by: Onur * add ANSI colors constants, add EOL at the end of log output * fix clippy warnings --------- Co-authored-by: Onur Reviewed-by: ozkanonur , shamardy --- CHANGELOG.md | 1 + Cargo.lock | 82 +----------------------- mm2src/common/Cargo.toml | 3 +- mm2src/mm2_core/Cargo.toml | 2 +- mm2src/mm2_io/Cargo.toml | 2 +- mm2src/mm2_net/Cargo.toml | 2 +- mm2src/mm2_rpc/Cargo.toml | 2 +- mm2src/mm2_test_helpers/Cargo.toml | 3 +- mm2src/mm2_test_helpers/src/for_tests.rs | 26 ++++---- 9 files changed, 22 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1101a199c2..c8d7435b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - A bug was fixed in [#1706](https://github.com/KomodoPlatform/atomicDEX-API/pull/1706) where EVM swap transactions were failing if sent before the approval transaction confirmation. - Tendermint account sequence problem due to running multiple instances were fixed in [#1694](https://github.com/KomodoPlatform/atomicDEX-API/pull/1694) - Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) +- Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) ## v1.0.0-beta - 2023-03-08 diff --git a/Cargo.lock b/Cargo.lock index 93e76392c2..25c8b88f06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,7 +1181,6 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "crossbeam", - "crossterm", "findshlibs", "fnv", "futures 0.1.29", @@ -1476,31 +1475,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "crossterm" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio 0.7.13", - "parking_lot 0.11.1", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" -dependencies = [ - "winapi", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -2709,7 +2683,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e38a0ee5e8e3debd1336135939e6615d283b8375fab2f99d8b89dba718502212" dependencies = [ - "crossterm", "lazy_static", "libc", ] @@ -4120,19 +4093,6 @@ dependencies = [ "adler 1.0.2", ] -[[package]] -name = "mio" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" -dependencies = [ - "libc", - "log 0.4.14", - "miow", - "ntapi", - "winapi", -] - [[package]] name = "mio" version = "0.8.6" @@ -4145,15 +4105,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - [[package]] name = "mm2-libp2p" version = "0.1.0" @@ -4537,7 +4488,6 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "common", - "crossterm", "crypto", "db_common", "futures 0.3.15", @@ -4683,15 +4633,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.3.2" @@ -6535,27 +6476,6 @@ dependencies = [ "log 0.4.14", ] -[[package]] -name = "signal-hook" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" -dependencies = [ - "libc", - "mio 0.7.13", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -8097,7 +8017,7 @@ dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.8.6", + "mio", "num_cpus", "parking_lot 0.12.0", "pin-project-lite 0.2.9", diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 861fe2033e..c71ef4d73d 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -58,8 +58,7 @@ web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomExcepti [target.'cfg(not(target_arch = "wasm32"))'.dependencies] anyhow = "1.0" chrono = "0.4" -crossterm = "0.20" -gstuff = { version = "0.7", features = ["crossterm", "nightly"] } +gstuff = { version = "0.7", features = ["nightly"] } hyper = { version = "0.14.11", features = ["client", "http2", "server", "tcp"] } # using webpki-tokio to avoid rejecting valid certificates # got "invalid certificate: UnknownIssuer" for https://ropsten.infura.io on iOS using default-features diff --git a/mm2src/mm2_core/Cargo.toml b/mm2src/mm2_core/Cargo.toml index fb16b2eb81..6d967b852b 100644 --- a/mm2src/mm2_core/Cargo.toml +++ b/mm2src/mm2_core/Cargo.toml @@ -29,4 +29,4 @@ gstuff = { version = "0.7", features = ["nightly"] } mm2_rpc = { path = "../mm2_rpc" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -gstuff = { version = "0.7", features = ["crossterm", "nightly"] } +gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_io/Cargo.toml b/mm2src/mm2_io/Cargo.toml index 67589d050f..925e0944b0 100644 --- a/mm2src/mm2_io/Cargo.toml +++ b/mm2src/mm2_io/Cargo.toml @@ -20,4 +20,4 @@ async-std = { version = "1.5", features = ["unstable"] } gstuff = { version = "0.7", features = ["nightly"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -gstuff = { version = "0.7", features = ["crossterm", "nightly"] } +gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index 045b255120..50d7cc5a3f 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -33,4 +33,4 @@ js-sys = "0.3.27" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] hyper = { version = "0.14.11", features = ["client", "http2", "server", "tcp"] } -gstuff = { version = "0.7", features = ["crossterm", "nightly"] } +gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_rpc/Cargo.toml b/mm2src/mm2_rpc/Cargo.toml index 2f08a6d9b9..5f6fc9d0b1 100644 --- a/mm2src/mm2_rpc/Cargo.toml +++ b/mm2src/mm2_rpc/Cargo.toml @@ -21,4 +21,4 @@ ser_error_derive = { path = "../derives/ser_error_derive" } gstuff = { version = "0.7", features = ["nightly"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -gstuff = { version = "0.7", features = ["crossterm", "nightly"] } +gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_test_helpers/Cargo.toml b/mm2src/mm2_test_helpers/Cargo.toml index c6030bd3d3..286c9cc3bf 100644 --- a/mm2src/mm2_test_helpers/Cargo.toml +++ b/mm2src/mm2_test_helpers/Cargo.toml @@ -34,5 +34,4 @@ gstuff = { version = "0.7", features = ["nightly"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] chrono = "0.4" -crossterm = "0.20" -gstuff = { version = "0.7", features = ["crossterm", "nightly"] } +gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index fed7f480a9..f252099082 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -20,6 +20,7 @@ use serde_json::{self as json, json, Value as Json}; use std::collections::HashMap; use std::convert::TryFrom; use std::env; +use std::io::Write; use std::net::IpAddr; use std::num::NonZeroUsize; use std::process::Child; @@ -36,7 +37,6 @@ cfg_native! { use bytes::Bytes; use futures::channel::oneshot; use futures::task::SpawnExt; - use gstuff::ISATTY; use http::Request; use regex::Regex; use std::fs; @@ -859,8 +859,9 @@ pub struct RaiiDump { #[cfg(not(target_arch = "wasm32"))] impl Drop for RaiiDump { fn drop(&mut self) { - use crossterm::execute; - use crossterm::style::{Color, Print, SetForegroundColor}; + const DARK_YELLOW_ANSI_CODE: &str = "\x1b[33m"; + const YELLOW_ANSI_CODE: &str = "\x1b[93m"; + const RESET_COLOR_ANSI_CODE: &str = "\x1b[0m"; // `term` bypasses the stdout capturing, we should only use it if the capturing was disabled. let nocapture = env::args().any(|a| a == "--nocapture"); @@ -872,15 +873,16 @@ impl Drop for RaiiDump { let log = String::from_utf8_lossy(&log); let log = log.trim(); - if let (true, true, mut stdout) = (nocapture, *ISATTY, std::io::stdout()) { - execute!( - stdout, - SetForegroundColor(Color::DarkYellow), - Print(format!("vvv {:?} vvv\n", self.log_path)), - SetForegroundColor(Color::Yellow), - Print(log), - ) - .expect("Printing to stdout failed"); + // If we want to determine is a tty or not here and write logs to stdout only if it's tty, + // we can use something like https://docs.rs/atty/latest/atty/ here, look like it's more cross-platform than gstuff::ISATTY . + + if nocapture { + std::io::stdout() + .write_all(format!("{}vvv {:?} vvv\n", DARK_YELLOW_ANSI_CODE, self.log_path).as_bytes()) + .expect("Printing to stdout failed"); + std::io::stdout() + .write_all(format!("{}{}{}\n", YELLOW_ANSI_CODE, log, RESET_COLOR_ANSI_CODE).as_bytes()) + .expect("Printing to stdout failed"); } else { log!("vvv {:?} vvv\n{}", self.log_path, log); } From 0b752f18c50ecfb6747d92379698b85cb9f63036 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 17:58:22 +0300 Subject: [PATCH 51/79] check shared runners Signed-off-by: ozkanonur --- .github/workflows/test.yml | 3 +++ mm2src/mm2_git/src/github_client.rs | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70c7d505d2..1034d34f22 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,9 @@ on: - main - dev +env: + FROM_SHARED_RUNNER: true + jobs: linux-x86-64: timeout-minutes: 90 diff --git a/mm2src/mm2_git/src/github_client.rs b/mm2src/mm2_git/src/github_client.rs index e85d6ede07..5686e78814 100644 --- a/mm2src/mm2_git/src/github_client.rs +++ b/mm2src/mm2_git/src/github_client.rs @@ -117,10 +117,13 @@ mod tests { } #[test] - // Since we are using shared CI runners, - // this test may fail due to rate limiting from Github. - #[cfg(not(profile = "ci"))] fn test_metadata_list_and_json_deserialization() { + // If we are using shared CI runners, + // this test may fail due to rate limiting. + if std::env::var("FROM_SHARED_RUNNER").is_ok() { + return; + } + const REPO_OWNER: &str = "KomodoPlatform"; const REPO_NAME: &str = "chain-registry"; const BRANCH: &str = "master"; From d1cd769ed186fc70d72cf0a59571db2ddf81cfde Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 18:41:34 +0300 Subject: [PATCH 52/79] enable cross target linting Signed-off-by: ozkanonur --- .github/workflows/fmt-and-lint.yml | 28 +++------------- mm2src/common/common.rs | 17 ++++------ mm2src/common/crash_reports.rs | 2 +- mm2src/common/custom_futures/timeout.rs | 2 +- .../abortable_system/abortable_queue.rs | 4 +-- mm2src/db_common/src/sql_query.rs | 2 +- mm2src/gossipsub/src/behaviour/tests.rs | 2 +- mm2src/gossipsub/src/mcache.rs | 32 +++++++++---------- mm2src/gossipsub/tests/smoke.rs | 6 ++-- mm2src/mm2_bitcoin/chain/src/block_header.rs | 1 + mm2src/mm2_bitcoin/keys/src/cashaddress.rs | 4 +-- mm2src/mm2_bitcoin/keys/src/keypair.rs | 22 ++++++------- mm2src/mm2_bitcoin/keys/src/segwitaddress.rs | 4 +-- .../serialization_derive/tests/raw.rs | 2 ++ mm2src/mm2_bitcoin/spv_validation/src/lib.rs | 2 +- mm2src/mm2_core/src/event_dispatcher.rs | 2 +- mm2src/mm2_err_handle/src/mm_error.rs | 4 +-- mm2src/mm2_eth/src/eip712_encode.rs | 2 +- mm2src/mm2_io/src/file_lock.rs | 4 +-- mm2src/mm2_libp2p/src/relay_address.rs | 2 +- mm2src/mm2_metrics/src/mm_metrics.rs | 8 ++--- mm2src/mm2_number/src/big_int_str.rs | 4 +-- mm2src/mm2_number/src/mm_number.rs | 2 +- mm2src/mm2_rpc/src/mm_protocol.rs | 6 ++-- 24 files changed, 72 insertions(+), 92 deletions(-) diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index 0c290233f2..ddd59385f8 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -10,12 +10,9 @@ on: - dev jobs: - x86-64: - timeout-minutes: 25 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + fmt-and-clippy: + timeout-minutes: 45 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -27,21 +24,4 @@ jobs: run: cargo fmt -- --check - name: x86-64 code lint - run: cargo clippy --profile ci -- --D warnings - - wasm: - timeout-minutes: 25 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal --component rustfmt clippy - rustup default nightly-2022-10-29 - rustup target add wasm32-unknown-unknown - - - name: wasm code lint - run: cargo clippy --target wasm32-unknown-unknown --profile ci -- --D warnings + run: cargo clippy --all-targets --profile ci -- --D warnings diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 4f8cdcfcce..e9000bd0da 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -930,16 +930,13 @@ pub fn is_acceptable_input_on_repeated_characters(entry: &str, limit: usize) -> #[test] fn test_is_acceptable_input_on_repeated_characters() { - assert_eq!(is_acceptable_input_on_repeated_characters("Hello", 3), true); - assert_eq!(is_acceptable_input_on_repeated_characters("Hellooo", 3), false); - assert_eq!( - is_acceptable_input_on_repeated_characters("SuperStrongPassword123*", 3), - true - ); - assert_eq!( - is_acceptable_input_on_repeated_characters("SuperStrongaaaPassword123*", 3), - false - ); + assert!(is_acceptable_input_on_repeated_characters("Hello", 3)); + assert!(!is_acceptable_input_on_repeated_characters("Hellooo", 3)); + assert!(is_acceptable_input_on_repeated_characters("SuperStrongPassword123*", 3)); + assert!(!is_acceptable_input_on_repeated_characters( + "SuperStrongaaaPassword123*", + 3 + )); } #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] diff --git a/mm2src/common/crash_reports.rs b/mm2src/common/crash_reports.rs index 4d5afe39c9..a749824015 100644 --- a/mm2src/common/crash_reports.rs +++ b/mm2src/common/crash_reports.rs @@ -42,7 +42,7 @@ pub extern "C" fn rust_seh_handler(exception_code: u32) { #[inline(never)] #[allow(dead_code)] fn access_violation() { - let ptr: *mut i32 = 0 as *mut i32; + let ptr: *mut i32 = std::ptr::null_mut::(); unsafe { *ptr = 123 }; } diff --git a/mm2src/common/custom_futures/timeout.rs b/mm2src/common/custom_futures/timeout.rs index 0c54ed71be..594b07ad9e 100644 --- a/mm2src/common/custom_futures/timeout.rs +++ b/mm2src/common/custom_futures/timeout.rs @@ -81,6 +81,6 @@ mod tests { fn test_timeout() { let _err = crate::block_on(Timer::sleep(0.4).timeout(Duration::from_secs_f64(0.1))).expect_err("Expected timeout"); - let _ok = crate::block_on(Timer::sleep(0.1).timeout(Duration::from_secs_f64(0.2))).expect("Expected future"); + crate::block_on(Timer::sleep(0.1).timeout(Duration::from_secs_f64(0.2))).expect("Expected future"); } } diff --git a/mm2src/common/executor/abortable_system/abortable_queue.rs b/mm2src/common/executor/abortable_system/abortable_queue.rs index 9e74181e58..99ffc70ca3 100644 --- a/mm2src/common/executor/abortable_system/abortable_queue.rs +++ b/mm2src/common/executor/abortable_system/abortable_queue.rs @@ -258,7 +258,7 @@ mod tests { let fut1 = async { Timer::sleep(0.3).await }; let fut2 = async { Timer::sleep(0.7).await }; spawner.spawn_with_settings(fut1, settings.clone()); - spawner.spawn_with_settings(fut2, settings.clone()); + spawner.spawn_with_settings(fut2, settings); { let inner = abortable_system.inner.lock(); @@ -318,7 +318,7 @@ mod tests { Timer::sleep(0.2).await; unsafe { F2_FINISHED = true }; }; - spawner.spawn_with_settings(fut2, settings.clone()); + spawner.spawn_with_settings(fut2, settings); abortable_system.abort_all().unwrap(); diff --git a/mm2src/db_common/src/sql_query.rs b/mm2src/db_common/src/sql_query.rs index 287bd8db47..4c7b6eb362 100644 --- a/mm2src/db_common/src/sql_query.rs +++ b/mm2src/db_common/src/sql_query.rs @@ -457,7 +457,7 @@ mod tests { query .field("tx_hash") .unwrap() - .and_where_in_params("height", SEARCHING_HEIGHTS.clone()) + .and_where_in_params("height", SEARCHING_HEIGHTS) .unwrap(); assert_eq!( query.clone().sql().unwrap(), diff --git a/mm2src/gossipsub/src/behaviour/tests.rs b/mm2src/gossipsub/src/behaviour/tests.rs index 5dee054c59..3c2533805c 100644 --- a/mm2src/gossipsub/src/behaviour/tests.rs +++ b/mm2src/gossipsub/src/behaviour/tests.rs @@ -55,7 +55,7 @@ mod tests { for i in 0..peer_no { let peer = PeerId::random(); - peers.push(peer.clone()); + peers.push(peer); ::inject_connection_established( &mut gs, &peer, diff --git a/mm2src/gossipsub/src/mcache.rs b/mm2src/gossipsub/src/mcache.rs index f9ae457052..e131c5e7d6 100644 --- a/mm2src/gossipsub/src/mcache.rs +++ b/mm2src/gossipsub/src/mcache.rs @@ -186,17 +186,17 @@ mod tests { fn test_get_wrong() { let mut mc = MessageCache::new_default(10, 15); - let topic1_hash = Topic::new("topic1".into()).no_hash().clone(); - let topic2_hash = Topic::new("topic2".into()).no_hash().clone(); + let topic1_hash = Topic::new("topic1".into()).no_hash(); + let topic2_hash = Topic::new("topic2".into()).no_hash(); let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - mc.put(m.clone()); + mc.put(m); // Try to get an incorrect ID let wrong_id = MessageId(String::from("wrongid")); let fetched = mc.get(&wrong_id); - assert_eq!(fetched.is_none(), true); + assert!(fetched.is_none()); } #[test] @@ -207,7 +207,7 @@ mod tests { // Try to get an incorrect ID let wrong_string = MessageId(String::from("imempty")); let fetched = mc.get(&wrong_string); - assert_eq!(fetched.is_none(), true); + assert!(fetched.is_none()); } #[test] @@ -224,7 +224,7 @@ mod tests { // Make sure it is the same fetched message match fetched { Some(x) => assert_eq!(*x, m), - _ => assert!(false), + _ => panic!("expected {:?}", m), } } @@ -233,8 +233,8 @@ mod tests { fn test_shift() { let mut mc = MessageCache::new_default(1, 5); - let topic1_hash = Topic::new("topic1".into()).no_hash().clone(); - let topic2_hash = Topic::new("topic2".into()).no_hash().clone(); + let topic1_hash = Topic::new("topic1".into()).no_hash(); + let topic2_hash = Topic::new("topic2".into()).no_hash(); // Build the message for i in 0..10 { @@ -245,7 +245,7 @@ mod tests { mc.shift(); // Ensure the shift occurred - assert!(mc.history[0].len() == 0); + assert!(mc.history[0].is_empty()); assert!(mc.history[1].len() == 10); // Make sure no messages deleted @@ -257,8 +257,8 @@ mod tests { fn test_empty_shift() { let mut mc = MessageCache::new_default(1, 5); - let topic1_hash = Topic::new("topic1".into()).no_hash().clone(); - let topic2_hash = Topic::new("topic2".into()).no_hash().clone(); + let topic1_hash = Topic::new("topic1".into()).no_hash(); + let topic2_hash = Topic::new("topic2".into()).no_hash(); // Build the message for i in 0..10 { let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); @@ -268,14 +268,14 @@ mod tests { mc.shift(); // Ensure the shift occurred - assert!(mc.history[0].len() == 0); + assert!(mc.history[0].is_empty()); assert!(mc.history[1].len() == 10); mc.shift(); assert!(mc.history[2].len() == 10); - assert!(mc.history[1].len() == 0); - assert!(mc.history[0].len() == 0); + assert!(mc.history[1].is_empty()); + assert!(mc.history[0].is_empty()); } #[test] @@ -283,8 +283,8 @@ mod tests { fn test_remove_last_from_shift() { let mut mc = MessageCache::new_default(4, 5); - let topic1_hash = Topic::new("topic1".into()).no_hash().clone(); - let topic2_hash = Topic::new("topic2".into()).no_hash().clone(); + let topic1_hash = Topic::new("topic1".into()).no_hash(); + let topic2_hash = Topic::new("topic2".into()).no_hash(); // Build the message for i in 0..10 { let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); diff --git a/mm2src/gossipsub/tests/smoke.rs b/mm2src/gossipsub/tests/smoke.rs index b9c3063cc0..44c11416fb 100644 --- a/mm2src/gossipsub/tests/smoke.rs +++ b/mm2src/gossipsub/tests/smoke.rs @@ -144,7 +144,7 @@ fn build_node() -> (Multiaddr, Swarm) { .multiplex(yamux::YamuxConfig::default()) .boxed(); - let peer_id = public_key.clone().to_peer_id(); + let peer_id = public_key.to_peer_id(); // NOTE: The graph of created nodes can be disconnected from the mesh point of view as nodes // can reach their d_lo value and not add other nodes to their mesh. To speed up this test, we @@ -157,7 +157,7 @@ fn build_node() -> (Multiaddr, Swarm) { .history_length(10) .history_gossip(10) .build(); - let behaviour = Gossipsub::new(peer_id.clone(), config); + let behaviour = Gossipsub::new(peer_id, config); let mut swarm = Swarm::new(transport, behaviour, peer_id); let port = 1 + random::(); @@ -174,7 +174,7 @@ fn multi_hop_propagation() { let _ = env_logger::try_init(); fn prop(num_nodes: u8, seed: u64) -> TestResult { - if num_nodes < 2 || num_nodes > 50 { + if !(2..=50).contains(&num_nodes) { return TestResult::discard(); } diff --git a/mm2src/mm2_bitcoin/chain/src/block_header.rs b/mm2src/mm2_bitcoin/chain/src/block_header.rs index 639670c7c7..2fce20a799 100644 --- a/mm2src/mm2_bitcoin/chain/src/block_header.rs +++ b/mm2src/mm2_bitcoin/chain/src/block_header.rs @@ -498,6 +498,7 @@ mod tests { #[test] fn test_doge_block_headers_serde_2() { // block headers of https://dogechain.info/block/3631810 and https://dogechain.info/block/3631811 + #[allow(clippy::zero_prefixed_literal)] let headers_bytes: &[u8] = &[ 02, 4, 1, 98, 0, 169, 253, 69, 196, 153, 115, 241, 239, 162, 112, 182, 254, 4, 175, 104, 238, 165, 178, 80, 67, 77, 109, 241, 134, 124, 3, 242, 203, 235, 211, 98, 185, 102, 124, 144, 105, 144, 228, 58, 25, 26, 29, diff --git a/mm2src/mm2_bitcoin/keys/src/cashaddress.rs b/mm2src/mm2_bitcoin/keys/src/cashaddress.rs index 80dcdab0f8..37066e9a62 100644 --- a/mm2src/mm2_bitcoin/keys/src/cashaddress.rs +++ b/mm2src/mm2_bitcoin/keys/src/cashaddress.rs @@ -404,7 +404,7 @@ mod tests { #[test] fn test_convert_bits() { - let (five, padded) = convert_bits(8, 5, &vec![0xFF], true); + let (five, padded) = convert_bits(8, 5, &[0xFF], true); assert!(padded, "Should have been padded"); assert_eq!(vec![0x1F, 0x1C], five); let (eight, padded) = convert_bits(5, 8, &five, false); @@ -494,7 +494,7 @@ mod tests { ]; for i in 0..4 { - let actual_address = CashAddress::decode(&encoded[i]).unwrap(); + let actual_address = CashAddress::decode(encoded[i]).unwrap(); let expected_address = expected_addresses[i].clone(); assert_eq!(actual_address, expected_address); let actual_encoded = actual_address.encode().unwrap(); diff --git a/mm2src/mm2_bitcoin/keys/src/keypair.rs b/mm2src/mm2_bitcoin/keys/src/keypair.rs index e3b3db7aae..0e29150f60 100644 --- a/mm2src/mm2_bitcoin/keys/src/keypair.rs +++ b/mm2src/mm2_bitcoin/keys/src/keypair.rs @@ -100,21 +100,21 @@ mod tests { /// Tests from: /// https://github.com/bitcoin/bitcoin/blob/a6a860796a44a2805a58391a009ba22752f64e32/src/test/key_tests.cpp - const SECRET_0: &'static str = "5KSCKP8NUyBZPCCQusxRwgmz9sfvJQEgbGukmmHepWw5Bzp95mu"; - const SECRET_1: &'static str = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"; - const SECRET_2: &'static str = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"; - const SECRET_1C: &'static str = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; - const SECRET_2C: &'static str = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"; - const SIGN_1: &'static str = "304402205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d022014ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"; - const SIGN_2: &'static str = "3044022052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd5022061d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"; + const SECRET_0: &str = "5KSCKP8NUyBZPCCQusxRwgmz9sfvJQEgbGukmmHepWw5Bzp95mu"; + const SECRET_1: &str = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"; + const SECRET_2: &str = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"; + const SECRET_1C: &str = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; + const SECRET_2C: &str = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"; + const SIGN_1: &str = "304402205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d022014ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"; + const SIGN_2: &str = "3044022052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd5022061d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"; #[allow(dead_code)] - const SIGN_COMPACT_1: &'static str = "1c5dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d14ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"; + const SIGN_COMPACT_1: &str = "1c5dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d14ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"; #[allow(dead_code)] - const SIGN_COMPACT_1C: &'static str = "205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d14ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"; + const SIGN_COMPACT_1C: &str = "205dbbddda71772d95ce91cd2d14b592cfbc1dd0aabd6a394b6c2d377bbe59d31d14ddda21494a4e221f0824f0b8b924c43fa43c0ad57dccdaa11f81a6bd4582f6"; #[allow(dead_code)] - const SIGN_COMPACT_2: &'static str = "1c52d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"; + const SIGN_COMPACT_2: &str = "1c52d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"; #[allow(dead_code)] - const SIGN_COMPACT_2C: &'static str = "2052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"; + const SIGN_COMPACT_2C: &str = "2052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"; fn check_compressed(secret: &'static str, compressed: bool) -> bool { let kp = KeyPair::from_private(secret.into()).unwrap(); diff --git a/mm2src/mm2_bitcoin/keys/src/segwitaddress.rs b/mm2src/mm2_bitcoin/keys/src/segwitaddress.rs index 4519deacf3..eb8112e238 100644 --- a/mm2src/mm2_bitcoin/keys/src/segwitaddress.rs +++ b/mm2src/mm2_bitcoin/keys/src/segwitaddress.rs @@ -209,7 +209,7 @@ mod tests { let public_key = Public::from_slice(&bytes).unwrap(); let hash = public_key.address_hash(); let hrp = "bc"; - let addr = SegwitAddress::new(&AddressHashEnum::AddressHash(hash.into()), hrp.to_string()); + let addr = SegwitAddress::new(&AddressHashEnum::AddressHash(hash), hrp.to_string()); assert_eq!(&addr.to_string(), "bc1qvzvkjn4q3nszqxrv3nraga2r822xjty3ykvkuw"); assert_eq!(addr.address_type(), Some(AddressType::P2wpkh)); } @@ -220,7 +220,7 @@ mod tests { let bytes = hex_to_bytes(script).unwrap(); let hash = sha256(&bytes); let hrp = "bc"; - let addr = SegwitAddress::new(&AddressHashEnum::WitnessScriptHash(hash.into()), hrp.to_string()); + let addr = SegwitAddress::new(&AddressHashEnum::WitnessScriptHash(hash), hrp.to_string()); assert_eq!( &addr.to_string(), "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3" diff --git a/mm2src/mm2_bitcoin/serialization_derive/tests/raw.rs b/mm2src/mm2_bitcoin/serialization_derive/tests/raw.rs index a67bbe0770..46389156eb 100644 --- a/mm2src/mm2_bitcoin/serialization_derive/tests/raw.rs +++ b/mm2src/mm2_bitcoin/serialization_derive/tests/raw.rs @@ -18,6 +18,7 @@ struct Bar { #[test] fn test_foo_serialize() { + #[allow(clippy::disallowed_names)] let foo = Foo { a: 1, b: 2, c: 3, d: 4 }; let expected = vec![1u8, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0].into(); @@ -31,6 +32,7 @@ fn test_foo_serialize() { #[test] fn test_bar_serialize() { + #[allow(clippy::disallowed_names)] let foo = Foo { a: 1, b: 2, c: 3, d: 4 }; let foo2 = Foo { a: 5, b: 6, c: 7, d: 8 }; diff --git a/mm2src/mm2_bitcoin/spv_validation/src/lib.rs b/mm2src/mm2_bitcoin/spv_validation/src/lib.rs index 4abc7e6466..a6f69a06b4 100644 --- a/mm2src/mm2_bitcoin/spv_validation/src/lib.rs +++ b/mm2src/mm2_bitcoin/spv_validation/src/lib.rs @@ -72,7 +72,7 @@ pub(crate) mod test_utils { pub(crate) fn run_test(test: T) where - T: FnOnce(&serde_json::Value) -> () + panic::UnwindSafe, + T: FnOnce(&serde_json::Value) + panic::UnwindSafe, { let fixtures = setup(); diff --git a/mm2src/mm2_core/src/event_dispatcher.rs b/mm2src/mm2_core/src/event_dispatcher.rs index 47de527142..479ca7d616 100644 --- a/mm2src/mm2_core/src/event_dispatcher.rs +++ b/mm2src/mm2_core/src/event_dispatcher.rs @@ -108,7 +108,7 @@ mod event_dispatcher_tests { impl Deref for ListenerSwapStatusChangedArc { type Target = ListenerSwapStatusChanged; - fn deref(&self) -> &ListenerSwapStatusChanged { &*self.0 } + fn deref(&self) -> &ListenerSwapStatusChanged { &self.0 } } #[async_trait] diff --git a/mm2src/mm2_err_handle/src/mm_error.rs b/mm2src/mm2_err_handle/src/mm_error.rs index d30075351e..e044ac6a6c 100644 --- a/mm2src/mm2_err_handle/src/mm_error.rs +++ b/mm2src/mm2_err_handle/src/mm_error.rs @@ -359,7 +359,7 @@ mod tests { const FORWARDED_LINE: u32 = line!() + 2; fn forward_error(actual: u64, required: u64) -> Result<(), MmError> { - let _ = generate_error(actual, required)?; + generate_error(actual, required)?; unreachable!("'generate_error' must return an error") } @@ -456,7 +456,7 @@ mod tests { const FORWARDED_LINE: u32 = line!() + 2; fn forward_error_for_box(actual: u64, required: u64) -> Result<(), MmError> { - let _ = generate_error_for_box(actual, required)?; + generate_error_for_box(actual, required)?; unreachable!("'generate_error' must return an error") } diff --git a/mm2src/mm2_eth/src/eip712_encode.rs b/mm2src/mm2_eth/src/eip712_encode.rs index 1d854dcb41..dbda992b83 100644 --- a/mm2src/mm2_eth/src/eip712_encode.rs +++ b/mm2src/mm2_eth/src/eip712_encode.rs @@ -346,7 +346,7 @@ mod tests { #[test] fn test_hash_data() { - const JSON: &'static str = r#"{ + const JSON: &str = r#"{ "primaryType": "Mail", "domain": { "name": "Ether Mail", diff --git a/mm2src/mm2_io/src/file_lock.rs b/mm2src/mm2_io/src/file_lock.rs index d529d5987f..24f064ea4d 100644 --- a/mm2src/mm2_io/src/file_lock.rs +++ b/mm2src/mm2_io/src/file_lock.rs @@ -127,7 +127,7 @@ mod file_lock_tests { fn test_file_lock_should_acquire_if_file_is_empty() { let now = now_ms() / 1000; let path = Path::new("test4.lock"); - std::fs::write(&path, &[]).unwrap(); + std::fs::write(path, []).unwrap(); let _new_lock = FileLock::lock(&path, 1.).unwrap().unwrap(); let timestamp = read_timestamp(&path).unwrap().unwrap(); assert!(timestamp >= now); @@ -137,7 +137,7 @@ mod file_lock_tests { fn test_file_lock_should_acquire_if_file_does_not_contain_parsable_timestamp() { let now = now_ms() / 1000; let path = Path::new("test5.lock"); - std::fs::write(&path, &[12, 13]).unwrap(); + std::fs::write(path, [12, 13]).unwrap(); let _new_lock = FileLock::lock(&path, 1.).unwrap().unwrap(); let timestamp = read_timestamp(&path).unwrap().unwrap(); assert!(timestamp >= now); diff --git a/mm2src/mm2_libp2p/src/relay_address.rs b/mm2src/mm2_libp2p/src/relay_address.rs index c1e6b6225a..d23c419632 100644 --- a/mm2src/mm2_libp2p/src/relay_address.rs +++ b/mm2src/mm2_libp2p/src/relay_address.rs @@ -148,7 +148,7 @@ fn test_relay_address_from_str() { ("/memory/71428421981", RelayAddress::Memory(71428421981)), ]; for (s, expected) in valid_addresses { - let actual = RelayAddress::from_str(s).expect(&format!("Error parsing '{}'", s)); + let actual = RelayAddress::from_str(s).unwrap_or_else(|_| panic!("Error parsing '{}'", s)); assert_eq!(actual, expected); } diff --git a/mm2src/mm2_metrics/src/mm_metrics.rs b/mm2src/mm2_metrics/src/mm_metrics.rs index ad3c5749b0..0814c0abde 100644 --- a/mm2src/mm2_metrics/src/mm_metrics.rs +++ b/mm2src/mm2_metrics/src/mm_metrics.rs @@ -468,10 +468,10 @@ mod test { let mut actual = metrics.collect_json().unwrap(); let actual = actual["metrics"].as_array_mut().unwrap(); for expected in expected["metrics"].as_array().unwrap() { - let index = actual.iter().position(|metric| metric == expected).expect(&format!( - "Couldn't find expected metric: {:#?} \n in {:#?}", - expected, actual - )); + let index = actual + .iter() + .position(|metric| metric == expected) + .unwrap_or_else(|| panic!("Couldn't find expected metric: {:#?} \n in {:#?}", expected, actual)); actual.remove(index); } diff --git a/mm2src/mm2_number/src/big_int_str.rs b/mm2src/mm2_number/src/big_int_str.rs index 8648548c47..d0855cfbbf 100644 --- a/mm2src/mm2_number/src/big_int_str.rs +++ b/mm2src/mm2_number/src/big_int_str.rs @@ -69,11 +69,11 @@ mod big_int_str_tests { fn test_bigint_str_deserialize() { let num = r#""1023""#; let expected: BigInt = 1023.into(); - let actual: BigIntStr = json::from_str(&num).unwrap(); + let actual: BigIntStr = json::from_str(num).unwrap(); assert_eq!(expected, actual.0); let err_num = "abc"; - let res = json::from_str::(&err_num); + let res = json::from_str::(err_num); assert!(res.is_err()); } diff --git a/mm2src/mm2_number/src/mm_number.rs b/mm2src/mm2_number/src/mm_number.rs index 64bf5d112f..ba6bdd112c 100644 --- a/mm2src/mm2_number/src/mm_number.rs +++ b/mm2src/mm2_number/src/mm_number.rs @@ -282,7 +282,7 @@ mod tests { for num in vals { let decimal: BigDecimal = BigDecimal::from_str(num).unwrap(); let expected: MmNumber = from_dec_to_ratio(&decimal).into(); - let actual: MmNumber = json::from_str(&num).unwrap(); + let actual: MmNumber = json::from_str(num).unwrap(); assert_eq!(expected, actual); } } diff --git a/mm2src/mm2_rpc/src/mm_protocol.rs b/mm2src/mm2_rpc/src/mm_protocol.rs index bc032c1465..9788236a61 100644 --- a/mm2src/mm2_rpc/src/mm_protocol.rs +++ b/mm2src/mm2_rpc/src/mm_protocol.rs @@ -186,7 +186,7 @@ mod tests { #[test] fn test_mm_rpc_response_serialize() { let ok: MmRpcResponse<_, AnError> = MmRpcBuilder::ok(vec![1, 2, 3]).build(); - let actual = json::to_value(&ok).expect("Couldn't serialize MmRpcResponse"); + let actual = json::to_value(ok).expect("Couldn't serialize MmRpcResponse"); let expected = json!({ "mmrpc": "2.0", "result": [1, 2, 3], @@ -195,7 +195,7 @@ mod tests { assert_eq!(actual, expected); let ok_with_id: MmRpcResponse<_, AnError> = MmRpcBuilder::ok(vec![1, 2, 3]).id(Some(2)).build(); - let actual = json::to_value(&ok_with_id).expect("Couldn't serialize MmRpcResponse"); + let actual = json::to_value(ok_with_id).expect("Couldn't serialize MmRpcResponse"); let expected = json!({ "mmrpc": "2.0", "result": [1, 2, 3], @@ -206,7 +206,7 @@ mod tests { let error_type = AnError::NotSufficientBalance { missing: 123 }; let err_line = line!() + 1; let err: MmRpcResponse = MmRpcBuilder::err(MmError::new(error_type)).build(); - let actual = json::to_value(&err).expect("Couldn't serialize MmRpcResponse"); + let actual = json::to_value(err).expect("Couldn't serialize MmRpcResponse"); let expected = json!({ "mmrpc": "2.0", "error": "Not sufficient balance. Top up your balance by 123", From f3435730eb689a156b7ad479ca77259d8b49b097 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 20:23:07 +0300 Subject: [PATCH 53/79] apply linting for all targets Signed-off-by: ozkanonur --- mm2src/coins/eth/eth_tests.rs | 23 +++-- mm2src/coins/lightning/ln_sql.rs | 23 +++-- mm2src/coins/lp_coins.rs | 7 ++ mm2src/coins/qrc20/history.rs | 21 +++-- mm2src/coins/qrc20/qrc20_tests.rs | 36 +++----- mm2src/coins/tendermint/tendermint_coin.rs | 4 +- mm2src/coins/utxo/bchd_grpc.rs | 8 +- mm2src/coins/utxo/slp.rs | 4 +- mm2src/coins/utxo/utxo_tests.rs | 82 ++++++++--------- mm2src/gossipsub/src/behaviour/tests.rs | 88 +++++++++---------- mm2src/gossipsub/src/mcache.rs | 14 ++- mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs | 5 +- .../spv_validation/src/helpers_validation.rs | 24 ++--- mm2src/mm2_bitcoin/spv_validation/src/lib.rs | 5 +- mm2src/mm2_bitcoin/spv_validation/src/work.rs | 2 +- mm2src/mm2_main/src/lib.rs | 8 ++ mm2src/mm2_main/src/lp_message_service.rs | 2 +- mm2src/mm2_main/src/lp_ordermatch.rs | 1 + mm2src/mm2_main/src/lp_price.rs | 4 +- mm2src/mm2_main/src/lp_swap.rs | 2 +- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 72 +++++++-------- mm2src/mm2_main/src/ordermatch_tests.rs | 46 ++++------ .../tests/mm2_tests/bch_and_slp_tests.rs | 2 +- .../tests/mm2_tests/best_orders_tests.rs | 16 ++-- mm2src/mm2_main/tests/mm2_tests/eth_tests.rs | 2 +- .../tests/mm2_tests/lightning_tests.rs | 18 ++-- .../tests/mm2_tests/mm2_tests_inner.rs | 26 +++--- mm2src/mm2_main/tests/mm2_tests/mod.rs | 1 + .../tests/mm2_tests/orderbook_sync_tests.rs | 12 +-- .../tests/mm2_tests/tendermint_tests.rs | 4 +- 30 files changed, 267 insertions(+), 295 deletions(-) diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 3ca322ad48..4536845ff4 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -48,7 +48,7 @@ fn eth_coin_for_test( {"coin":"JST","name":"jst","rpcport":80,"mm2":1,"protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":"0x2b294F029Fde858b2c62184e8390591755521d8E"}}} ] }); - let ctx = MmCtxBuilder::new().with_conf(conf.clone()).into_mm_arc(); + let ctx = MmCtxBuilder::new().with_conf(conf).into_mm_arc(); let ticker = match coin_type { EthCoinType::Eth => "ETH".to_string(), EthCoinType::Erc20 { .. } => "JST".to_string(), @@ -712,7 +712,7 @@ fn test_withdraw_impl_manual_fee() { }; coin.my_balance().wait().unwrap(); - let tx_details = block_on(withdraw_impl(coin.clone(), withdraw_req)).unwrap(); + let tx_details = block_on(withdraw_impl(coin, withdraw_req)).unwrap(); let expected = Some( EthTxFeeDetails { coin: "ETH".into(), @@ -756,7 +756,7 @@ fn test_withdraw_impl_fee_details() { }; coin.my_balance().wait().unwrap(); - let tx_details = block_on(withdraw_impl(coin.clone(), withdraw_req)).unwrap(); + let tx_details = block_on(withdraw_impl(coin, withdraw_req)).unwrap(); let expected = Some( EthTxFeeDetails { coin: "ETH".into(), @@ -995,7 +995,7 @@ fn test_get_fee_to_send_taker_fee() { vec!["http://dummy.dummy".into()], None, ); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount.clone(), FeeApproxStage::WithoutApprox)) + let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)) .expect("!get_fee_to_send_taker_fee"); assert_eq!(actual, expected_fee); } @@ -1022,8 +1022,7 @@ 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.clone(), FeeApproxStage::WithoutApprox)).unwrap_err(); + let error = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)).unwrap_err(); log!("{}", error); assert!( matches!(error.get_inner(), TradePreimageError::NotSufficientBalance { .. }), @@ -1045,8 +1044,8 @@ fn validate_dex_fee_invalid_sender_eth() { let amount: BigDecimal = "0.000526435076465".parse().unwrap(); let validate_fee_args = ValidateFeeArgs { fee_tx: &tx, - expected_sender: &*DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 0, uuid: &[], @@ -1079,8 +1078,8 @@ fn validate_dex_fee_invalid_sender_erc() { let amount: BigDecimal = "5.548262548262548262".parse().unwrap(); let validate_fee_args = ValidateFeeArgs { fee_tx: &tx, - expected_sender: &*DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 0, uuid: &[], @@ -1118,7 +1117,7 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { let validate_fee_args = ValidateFeeArgs { fee_tx: &tx, expected_sender: &compressed_public, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 11784793, uuid: &[], @@ -1155,7 +1154,7 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { let validate_fee_args = ValidateFeeArgs { fee_tx: &tx, expected_sender: &compressed_public, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 11823975, uuid: &[], diff --git a/mm2src/coins/lightning/ln_sql.rs b/mm2src/coins/lightning/ln_sql.rs index 9e6088be84..8f8f953fa1 100644 --- a/mm2src/coins/lightning/ln_sql.rs +++ b/mm2src/coins/lightning/ln_sql.rs @@ -1438,7 +1438,7 @@ mod tests { let result = block_on(db.get_payments_by_filter(Some(filter.clone()), paging.clone(), limit)).unwrap(); let expected_payments_vec: Vec = payments .iter() - .map(|p| p.clone()) + .cloned() .filter(|p| p.payment_type == PaymentType::InboundPayment) .collect(); let expected_payments = if expected_payments_vec.len() > 10 { @@ -1454,7 +1454,7 @@ mod tests { let result = block_on(db.get_payments_by_filter(Some(filter.clone()), paging.clone(), limit)).unwrap(); let expected_payments_vec: Vec = expected_payments_vec .iter() - .map(|p| p.clone()) + .cloned() .filter(|p| p.status == HTLCStatus::Succeeded) .collect(); let expected_payments = if expected_payments_vec.len() > 10 { @@ -1475,13 +1475,13 @@ mod tests { let result = block_on(db.get_payments_by_filter(Some(filter), paging, limit)).unwrap(); let expected_payments_vec: Vec = payments .iter() - .map(|p| p.clone()) - .filter(|p| p.description.contains(&substr)) + .cloned() + .filter(|p| p.description.contains(substr)) .collect(); let expected_payments = if expected_payments_vec.len() > 10 { expected_payments_vec[..10].to_vec() } else { - expected_payments_vec.clone() + expected_payments_vec }; let actual_payments = result.payments; @@ -1566,11 +1566,8 @@ mod tests { let limit = 10; let result = block_on(db.get_closed_channels_by_filter(Some(filter.clone()), paging.clone(), limit)).unwrap(); - let expected_channels_vec: Vec = channels - .iter() - .map(|chan| chan.clone()) - .filter(|chan| chan.is_outbound) - .collect(); + let expected_channels_vec: Vec = + channels.iter().cloned().filter(|chan| chan.is_outbound).collect(); let expected_channels = if expected_channels_vec.len() > 10 { expected_channels_vec[..10].to_vec() } else { @@ -1584,7 +1581,7 @@ mod tests { let result = block_on(db.get_closed_channels_by_filter(Some(filter.clone()), paging.clone(), limit)).unwrap(); let expected_channels_vec: Vec = expected_channels_vec .iter() - .map(|chan| chan.clone()) + .cloned() .filter(|chan| chan.is_public) .collect(); let expected_channels = if expected_channels_vec.len() > 10 { @@ -1603,13 +1600,13 @@ mod tests { let result = block_on(db.get_closed_channels_by_filter(Some(filter), paging, limit)).unwrap(); let expected_channels_vec: Vec = channels .iter() - .map(|chan| chan.clone()) + .cloned() .filter(|chan| chan.channel_id == channel_id) .collect(); let expected_channels = if expected_channels_vec.len() > 10 { expected_channels_vec[..10].to_vec() } else { - expected_channels_vec.clone() + expected_channels_vec }; let actual_channels = result.channels; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 4d9d6af5f0..9a525190c8 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -19,6 +19,13 @@ // marketmaker // +// `mockable` implementation uses these +#![allow( + clippy::forget_ref, + clippy::forget_copy, + clippy::swap_ptr_to_ref, + clippy::forget_non_drop +)] #![allow(uncommon_codepoints)] #![feature(integer_atomics)] #![feature(async_closure)] diff --git a/mm2src/coins/qrc20/history.rs b/mm2src/coins/qrc20/history.rs index 0e61ce0a79..b3ae9e7655 100644 --- a/mm2src/coins/qrc20/history.rs +++ b/mm2src/coins/qrc20/history.rs @@ -824,7 +824,7 @@ mod tests { let expected_id = TxInternalId::new(tx_hash.as_slice().into(), 13, 257); let actual_bytes: BytesJson = expected_id.clone().into(); - let mut expected_bytes = tx_hash.clone(); + let mut expected_bytes = tx_hash; expected_bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 13]); expected_bytes.extend_from_slice(&[0, 0, 0, 0, 0, 0, 1, 1]); assert_eq!(actual_bytes, expected_bytes.into()); @@ -848,7 +848,7 @@ mod tests { .as_slice() .into(); let tx_height = 699545; - let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash.clone())).unwrap(); + let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); let mut transfer_map = transfer_map_expected.clone(); assert_eq!( @@ -880,7 +880,7 @@ mod tests { .as_slice() .into(); let tx_height = 699545; - let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash.clone())).unwrap(); + let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); let mut transfer_map_zero_timestamp = transfer_map_expected .clone() @@ -922,10 +922,9 @@ mod tests { .as_slice() .into(); let tx_height = 699545; - let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash.clone())).unwrap(); + let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); let mut transfer_map_unexpected_tx_id = transfer_map_expected - .clone() .into_iter() .map(|(mut id, tx)| { // just another tx_hash @@ -963,9 +962,9 @@ mod tests { .as_slice() .into(); let tx_height = 681443; - let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash.clone())).unwrap(); + let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); let mut history_map_expected = HistoryMapByHash::new(); - history_map_expected.insert(tx_hash.clone(), transfer_map_expected); + history_map_expected.insert(tx_hash, transfer_map_expected); let tx_ids = vec![(tx_hash, tx_height)]; let mut history_map = HistoryMapByHash::new(); @@ -988,9 +987,9 @@ mod tests { .as_slice() .into(); let tx_height = 699545; - let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash.clone())).unwrap(); + let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); let mut history_map_expected = HistoryMapByHash::new(); - history_map_expected.insert(tx_hash.clone(), transfer_map_expected); + history_map_expected.insert(tx_hash, transfer_map_expected); let tx_ids = vec![(tx_hash, tx_height)]; let mut history_map = history_map_expected.clone(); @@ -1020,10 +1019,10 @@ mod tests { .as_slice() .into(); let tx_height = 699545; - let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash.clone())).unwrap(); + let transfer_map_expected = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); let mut history_map_expected = HistoryMapByHash::new(); // should contain only valid tx - history_map_expected.insert(tx_hash.clone(), transfer_map_expected); + history_map_expected.insert(tx_hash, transfer_map_expected); let tx_ids = vec![(tx_hash, tx_height), (tx_hash_invalid, tx_height)]; let mut history_map = HistoryMapByHash::default(); diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 7354b5a103..1c9c3fdb41 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -356,8 +356,7 @@ fn test_validate_fee() { uuid: &[], }) .wait() - .err() - .expect("Expected an error") + .expect_err("Expected an error") .into_inner(); log!("error: {:?}", err); match err { @@ -375,8 +374,7 @@ fn test_validate_fee() { uuid: &[], }) .wait() - .err() - .expect("Expected an error") + .expect_err("Expected an error") .into_inner(); log!("error: {:?}", err); match err { @@ -394,8 +392,7 @@ fn test_validate_fee() { uuid: &[], }) .wait() - .err() - .expect("Expected an error") + .expect_err("Expected an error") .into_inner(); log!("error: {:?}", err); match err { @@ -414,8 +411,7 @@ fn test_validate_fee() { uuid: &[], }) .wait() - .err() - .expect("Expected an error") + .expect_err("Expected an error") .into_inner(); log!("error: {:?}", err); match err { @@ -438,8 +434,7 @@ fn test_validate_fee() { uuid: &[], }) .wait() - .err() - .expect("Expected an error") + .expect_err("Expected an error") .into_inner(); log!("error: {:?}", err); match err { @@ -551,23 +546,19 @@ fn test_generate_token_transfer_script_pubkey() { let to_addr: UtxoAddress = "qHmJ3KA6ZAjR9wGjpFASn4gtUSeFAqdZgs".into(); let to_addr = qtum::contract_addr_from_utxo_addr(to_addr).unwrap(); let amount: U256 = 1000000000.into(); - let actual = coin - .transfer_output(to_addr.clone(), amount, gas_limit, gas_price) - .unwrap(); + let actual = coin.transfer_output(to_addr, amount, gas_limit, gas_price).unwrap(); assert_eq!(expected, actual); assert!(coin .transfer_output( - to_addr.clone(), - amount, - 0, // gas_limit cannot be zero + to_addr, amount, 0, // gas_limit cannot be zero gas_price, ) .is_err()); assert!(coin .transfer_output( - to_addr.clone(), + to_addr, amount, gas_limit, 0, // gas_price cannot be zero @@ -588,7 +579,7 @@ fn test_transfer_details_by_hash() { let tx_hex:BytesJson = hex::decode("0100000001426d27fde82e12e1ce84e73ca41e2a30420f4c94aaa37b30d4c5b8b4f762c042040000006a473044022032665891693ee732571cefaa6d322ec5114c78259f2adbe03a0d7e6b65fbf40d022035c9319ca41e5423e09a8a613ac749a20b8f5ad6ba4ad6bb60e4a020b085d009012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff050000000000000000625403a08601012844095ea7b30000000000000000000000001549128bbfb33b997949b4105b6a6371c998e212000000000000000000000000000000000000000000000000000000000000000014d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000625403a08601012844095ea7b30000000000000000000000001549128bbfb33b997949b4105b6a6371c998e21200000000000000000000000000000000000000000000000000000000000927c014d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000835403a0860101284c640c565ae300000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000000000000000000000000000000000000000000000141549128bbfb33b997949b4105b6a6371c998e212c20000000000000000835403a0860101284c640c565ae300000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000000000000000000000000000000000000000000001141549128bbfb33b997949b4105b6a6371c998e212c231754b04000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acf7cd8b5f").unwrap().into(); let details = block_on(coin.transfer_details_by_hash(tx_hash)).unwrap(); - let mut it = details.into_iter().sorted_by(|(id_x, _), (id_y, _)| id_x.cmp(&id_y)); + let mut it = details.into_iter().sorted_by(|(id_x, _), (id_y, _)| id_x.cmp(id_y)); let expected_fee_details = |total_gas_fee: &str| -> TxFeeDetails { let fee = Qrc20FeeDetails { @@ -704,7 +695,7 @@ fn test_transfer_details_by_hash() { let (_id, actual) = it.next().unwrap(); let expected = TransactionDetails { - tx_hex: tx_hex.clone(), + tx_hex, tx_hash: tx_hash_bytes.to_tx_hash(), from: vec!["qKVvtDqpnFGDxsDzck5jmLwdnD2jRH6aM8".into()], to: vec!["qXxsj5RtciAby9T7m98AgAATL4zTi4UwDG".into()], @@ -742,7 +733,7 @@ fn test_get_trade_fee() { let actual_trade_fee = coin.get_trade_fee().wait().unwrap(); let expected_trade_fee_amount = big_decimal_from_sat( - (2 * CONTRACT_CALL_GAS_FEE + SWAP_PAYMENT_GAS_FEE + EXPECTED_TX_FEE) as i64, + 2 * CONTRACT_CALL_GAS_FEE + SWAP_PAYMENT_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals, ); let expected = TradeFee { @@ -1071,8 +1062,7 @@ fn test_validate_maker_payment_malicious() { let error = coin .validate_maker_payment(input) .wait() - .err() - .expect("'erc20Payment' was called from another swap contract, expected an error") + .expect_err("'erc20Payment' was called from another swap contract, expected an error") .into_inner(); log!("error: {}", error); match error { @@ -1172,6 +1162,6 @@ fn test_send_contract_calls_recoverable_tx() { // The error variant should equal to `TxRecoverable` assert_eq!( discriminant(&tx_err), - discriminant(&TransactionErr::TxRecoverable(TransactionEnum::from(tx), String::new())) + discriminant(&TransactionErr::TxRecoverable(tx, String::new())) ); } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 4eb75fdf23..ce5531b523 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2938,7 +2938,7 @@ pub mod tendermint_coin_tests { // https://nyancat.iobscan.io/#/tx?txHash=565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137 let expected_spend_hash = "565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137"; let hash = spend_tx.tx_hash(); - assert_eq!(hex::encode_upper(&hash.0), expected_spend_hash); + assert_eq!(hex::encode_upper(hash.0), expected_spend_hash); } #[test] @@ -3290,7 +3290,7 @@ pub mod tendermint_coin_tests { // https://nyancat.iobscan.io/#/tx?txHash=565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137 let expected_spend_hash = "565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137"; let hash = spend_tx.tx_hash(); - assert_eq!(hex::encode_upper(&hash.0), expected_spend_hash); + assert_eq!(hex::encode_upper(hash.0), expected_spend_hash); } #[test] diff --git a/mm2src/coins/utxo/bchd_grpc.rs b/mm2src/coins/utxo/bchd_grpc.rs index 70e3594bb9..d07abd09ad 100644 --- a/mm2src/coins/utxo/bchd_grpc.rs +++ b/mm2src/coins/utxo/bchd_grpc.rs @@ -323,7 +323,7 @@ mod bchd_grpc_tests { let err = block_on(validate_slp_utxos(BCHD_TESTNET_URLS, &slp_utxos, &token_id)).unwrap_err(); match err.into_inner().kind { ValidateSlpUtxosErrKind::InvalidSlpTxData(_) => (), - err @ _ => panic!("Unexpected error {:?}", err), + err => panic!("Unexpected error {:?}", err), } } @@ -365,7 +365,7 @@ mod bchd_grpc_tests { assert_eq!(invalid_utxo, for_unspent); assert_eq!(expected_validity, validity_result); }, - err @ _ => panic!("Unexpected error {:?}", err), + err => panic!("Unexpected error {:?}", err), } } @@ -406,7 +406,7 @@ mod bchd_grpc_tests { assert_eq!(invalid_token_id, expected); assert_eq!(valid_token_id, actual); }, - err @ _ => panic!("Unexpected error {:?}", err), + err => panic!("Unexpected error {:?}", err), } } @@ -426,7 +426,7 @@ mod bchd_grpc_tests { CheckSlpTransactionErrKind::InvalidTransaction { reason, .. } => { println!("{}", reason); }, - err @ _ => panic!("Unexpected error {:?}", err), + err => panic!("Unexpected error {:?}", err), } } } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 8beef7c130..84332fb1d6 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -2197,7 +2197,7 @@ mod slp_tests { let err = block_on(fusd.broadcast_tx(&utxo_tx)).unwrap_err(); match err.into_inner() { BroadcastTxErr::Other(err) => assert!(err.contains("is not valid with reason outputs greater than inputs")), - e @ _ => panic!("Unexpected err {:?}", e), + e => panic!("Unexpected err {:?}", e), }; // The error variant should equal to `TxRecoverable` @@ -2260,7 +2260,7 @@ mod slp_tests { let validity_err = block_on(fusd.validate_htlc(input)).unwrap_err(); match validity_err.into_inner() { ValidatePaymentError::WrongPaymentTx(e) => println!("{:#?}", e), - err @ _ => panic!("Unexpected err {:#?}", err), + err => panic!("Unexpected err {:#?}", err), }; } diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index ae54d5fd57..9c6a0fd1b9 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -164,7 +164,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx_hex, time_lock: 777, - other_pubkey: &coin.my_public_key().unwrap().to_vec(), + other_pubkey: coin.my_public_key().unwrap(), secret: &secret, secret_hash: &*dhash160(&secret), swap_contract_address: &coin.swap_contract_address(), @@ -495,7 +495,7 @@ fn test_search_for_swap_tx_spend_electrum_was_spent() { let search_input = SearchForSwapTxSpendInput { time_lock: 1591928233, - other_pub: &*coin.my_public_key().unwrap(), + other_pub: coin.my_public_key().unwrap(), secret_hash: &*dhash160(&secret), tx: &payment_tx_bytes, search_from_block: 0, @@ -530,7 +530,7 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { let search_input = SearchForSwapTxSpendInput { time_lock: 1591933469, - other_pub: &coin.as_ref().priv_key_policy.key_pair_or_err().unwrap().public(), + other_pub: coin.as_ref().priv_key_policy.key_pair_or_err().unwrap().public(), secret_hash: &secret_hash, tx: &payment_tx_bytes, search_from_block: 0, @@ -1320,9 +1320,9 @@ fn test_get_median_time_past_from_native_does_not_have_median_in_get_block() { let blocks: Vec = json::from_str(blocks_json_str).unwrap(); let mut block_hashes: HashMap<_, _> = blocks .iter() - .map(|block| (block.height.unwrap() as u64, block.hash.clone())) + .map(|block| (block.height.unwrap() as u64, block.hash)) .collect(); - let mut blocks: HashMap<_, _> = blocks.into_iter().map(|block| (block.hash.clone(), block)).collect(); + let mut blocks: HashMap<_, _> = blocks.into_iter().map(|block| (block.hash, block)).collect(); let client = native_client_for_test(); NativeClientImpl::get_block_hash.mock_safe(move |_, block_num| { @@ -1480,7 +1480,7 @@ fn test_address_from_str_with_legacy_address_activated() { // https://github.com/KomodoPlatform/atomicDEX-API/issues/673 fn test_network_info_negative_time_offset() { let info_str = r#"{"version":1140200,"subversion":"/Shibetoshi:1.14.2/","protocolversion":70015,"localservices":"0000000000000005","localrelay":true,"timeoffset":-1,"networkactive":true,"connections":12,"networks":[{"name":"ipv4","limited":false,"reachable":true,"proxy":"","proxy_randomize_credentials":false},{"name":"ipv6","limited":false,"reachable":true,"proxy":"","proxy_randomize_credentials":false},{"name":"onion","limited":false,"reachable":true,"proxy":"127.0.0.1:9050","proxy_randomize_credentials":true}],"relayfee":1.00000000,"incrementalfee":0.00001000,"localaddresses":[],"warnings":""}"#; - let _info: NetworkInfo = json::from_str(&info_str).unwrap(); + let _info: NetworkInfo = json::from_str(info_str).unwrap(); } #[test] @@ -1636,7 +1636,7 @@ fn test_qtum_add_delegation() { }; let res = coin.add_delegation(request).wait().unwrap(); // Eligible for delegation - assert_eq!(res.my_balance_change.is_negative(), true); + assert!(res.my_balance_change.is_negative()); assert_eq!(res.total_amount, res.spent_by_me); assert!(res.spent_by_me > res.received_by_me); @@ -1646,7 +1646,7 @@ fn test_qtum_add_delegation() { }; let res = coin.add_delegation(request).wait(); // Wrong address - assert_eq!(res.is_err(), true); + assert!(res.is_err()); } #[test] @@ -1675,7 +1675,7 @@ fn test_qtum_add_delegation_on_already_delegating() { }; let res = coin.add_delegation(request).wait(); // Already Delegating - assert_eq!(res.is_err(), true); + assert!(res.is_err()); } #[test] @@ -1702,10 +1702,10 @@ fn test_qtum_get_delegation_infos() { let staking_infos = coin.get_delegation_infos().wait().unwrap(); match staking_infos.staking_infos_details { StakingInfosDetails::Qtum(staking_details) => { - assert_eq!(staking_details.am_i_staking, true); + assert!(staking_details.am_i_staking); assert_eq!(staking_details.staker.unwrap(), "qcyBHeSct7Wr4mAw18iuQ1zW5mMFYmtmBE"); // Will return false for segwit. - assert_eq!(staking_details.is_staking_supported, true); + assert!(staking_details.is_staking_supported); }, }; } @@ -1730,7 +1730,7 @@ fn test_qtum_remove_delegation() { )) .unwrap(); let res = coin.remove_delegation().wait(); - assert_eq!(res.is_err(), false); + assert!(res.is_ok()); } #[test] @@ -1879,7 +1879,7 @@ fn test_get_mature_unspent_ordered_map_from_cache_impl( block_on(coin.get_mature_unspent_ordered_list(&Address::from("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW"))) .expect("Expected an empty unspent list"); // unspents should be empty because `is_unspent_mature()` always returns false - assert!(unsafe { IS_UNSPENT_MATURE_CALLED == true }); + assert!(unsafe { IS_UNSPENT_MATURE_CALLED }); assert!(unspents.mature.is_empty()); assert_eq!(unspents.immature.len(), 1); } @@ -2024,12 +2024,12 @@ fn test_native_client_unspents_filtered_using_tx_cache_single_tx_in_cache() { let tx: UtxoTx = "0400008085202f89027f57730fcbbc2c72fb18bcc3766a713044831a117bb1cade3ed88644864f7333020000006a47304402206e3737b2fcf078b61b16fa67340cc3e79c5d5e2dc9ffda09608371552a3887450220460a332aa1b8ad8f2de92d319666f70751078b221199951f80265b4f7cef8543012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff42b916a80430b80a77e114445b08cf120735447a524de10742fac8f6a9d4170f000000006a473044022004aa053edafb9d161ea8146e0c21ed1593aa6b9404dd44294bcdf920a1695fd902202365eac15dbcc5e9f83e2eed56a8f2f0e5aded36206f9c3fabc668fd4665fa2d012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff03547b16000000000017a9143e8ad0e2bf573d32cb0b3d3a304d9ebcd0c2023b870000000000000000166a144e2b3c0323ab3c2dc6f86dc5ec0729f11e42f56103970400000000001976a91450f4f098306f988d8843004689fae28c83ef16e888ac89c5925f000000000000000000000000000000".into(); let spent_by_tx = vec![ UnspentInfo { - outpoint: tx.inputs[0].previous_output.clone(), + outpoint: tx.inputs[0].previous_output, value: 886737, height: Some(642293), }, UnspentInfo { - outpoint: tx.inputs[1].previous_output.clone(), + outpoint: tx.inputs[1].previous_output, value: 88843, height: Some(642293), }, @@ -2070,56 +2070,48 @@ fn test_native_client_unspents_filtered_using_tx_cache_single_several_chained_tx let tx_0: UtxoTx = "0400008085202f89027f57730fcbbc2c72fb18bcc3766a713044831a117bb1cade3ed88644864f7333020000006a47304402206e3737b2fcf078b61b16fa67340cc3e79c5d5e2dc9ffda09608371552a3887450220460a332aa1b8ad8f2de92d319666f70751078b221199951f80265b4f7cef8543012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff42b916a80430b80a77e114445b08cf120735447a524de10742fac8f6a9d4170f000000006a473044022004aa053edafb9d161ea8146e0c21ed1593aa6b9404dd44294bcdf920a1695fd902202365eac15dbcc5e9f83e2eed56a8f2f0e5aded36206f9c3fabc668fd4665fa2d012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff03547b16000000000017a9143e8ad0e2bf573d32cb0b3d3a304d9ebcd0c2023b870000000000000000166a144e2b3c0323ab3c2dc6f86dc5ec0729f11e42f56103970400000000001976a91450f4f098306f988d8843004689fae28c83ef16e888ac89c5925f000000000000000000000000000000".into(); let spent_by_tx_0 = vec![ UnspentInfo { - outpoint: tx_0.inputs[0].previous_output.clone(), + outpoint: tx_0.inputs[0].previous_output, value: 886737, height: Some(642293), }, UnspentInfo { - outpoint: tx_0.inputs[1].previous_output.clone(), + outpoint: tx_0.inputs[1].previous_output, value: 88843, height: Some(642293), }, ]; - block_on(coin.as_ref().recently_spent_outpoints.lock()).add_spent( - spent_by_tx_0.clone(), - tx_0.hash(), - tx_0.outputs.clone(), - ); + block_on(coin.as_ref().recently_spent_outpoints.lock()).add_spent(spent_by_tx_0.clone(), tx_0.hash(), tx_0.outputs); // https://morty.explorer.dexstats.info/tx/dbfc821e482747a3512ee6d5734f9df2aa73dab07e2fcd86abeadb462e795bf9 let tx_1: UtxoTx = "0400008085202f890347d329798b508dc28ec99d8c6f6c7ced860a19a364e1bafe391cab89aeaac731020000006a47304402203ea8b380d0a7e64348869ef7c4c2bfa966fc7b148633003332fa8d0ab0c1bc5602202cc63fabdd2a6578c52d8f4f549069b16505f2ead48edc2b8de299be15aadf9a012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff1d1fd3a6b01710647a7f4a08c6de6075cb8e78d5069fa50f10c4a2a10ded2a95000000006a47304402203868945edc0f6dc2ee43d70a69ee4ec46ca188dc493173ce58924ba9bf6ee7a50220648ff99ce458ca72800758f6a1bd3800cd05ff9c3122f23f3653c25e09d22c79012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff7932150df8b4a1852b8b84b89b0d5322bf74665fb7f76a728369fd6895d3fd48000000006a4730440220127918c6f79c11f7f2376a6f3b750ed4c7103183181ad1218afcb2625ece9599022028c05e88d3a2f97cebd84a718cda33b62b48b18f16278fa8e531fd2155e61ee8012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff0329fd12000000000017a914cafb62e3e8bdb8db3735c39b92743ac6ebc9ef20870000000000000000166a14a7416b070c9bb98f4bafae55616f005a2a30bd6014b40c00000000001976a91450f4f098306f988d8843004689fae28c83ef16e888ac8cc5925f000000000000000000000000000000".into(); let spent_by_tx_1 = vec![ UnspentInfo { - outpoint: tx_1.inputs[0].previous_output.clone(), + outpoint: tx_1.inputs[0].previous_output, value: 300803, height: Some(642293), }, UnspentInfo { - outpoint: tx_1.inputs[1].previous_output.clone(), + outpoint: tx_1.inputs[1].previous_output, value: 888544, height: Some(642293), }, UnspentInfo { - outpoint: tx_1.inputs[2].previous_output.clone(), + outpoint: tx_1.inputs[2].previous_output, value: 888642, height: Some(642293), }, ]; - block_on(coin.as_ref().recently_spent_outpoints.lock()).add_spent( - spent_by_tx_1.clone(), - tx_1.hash(), - tx_1.outputs.clone(), - ); + block_on(coin.as_ref().recently_spent_outpoints.lock()).add_spent(spent_by_tx_1.clone(), tx_1.hash(), tx_1.outputs); // https://morty.explorer.dexstats.info/tx/12ea22a7cde9efb66b76f9b84345ddfc4c34870e293bfa8eac68d7df83dffa4b let tx_2: UtxoTx = "0400008085202f8902f95b792e46dbeaab86cd2f7eb0da73aaf29d4f73d5e62e51a34727481e82fcdb020000006a4730440220347adefe33ed5afbbb8e5d453afd527319f9a50ab790023296a981da095ca4a2022029a68ef6fd5a4decf3793d4c33994eb8658408f3b14a6d439c4753b2dde954ee012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff75bd4348594f8ff2a216e5ad7533b37d47d2a2767b0b88d43972ad51895355e2000000006a473044022069b36c0f65d56e02bc179f7442806374c4163d07939090aba1da736abad9a77d022006dc39adf48e02033ae9d4a48540752ae3b3841e3ec60d2e86dececb88b9e518012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff03414111000000000017a914a153024c826a3a42c2e501eca5d7dacd3fc59976870000000000000000166a14db0e6f4d418d68dce8e5beb26cc5078e01e2e3ace2fe0800000000001976a91450f4f098306f988d8843004689fae28c83ef16e888ac8fc5925f000000000000000000000000000000".into(); let spent_by_tx_2 = vec![ UnspentInfo { - outpoint: tx_2.inputs[0].previous_output.clone(), + outpoint: tx_2.inputs[0].previous_output, value: 832532, height: Some(642293), }, UnspentInfo { - outpoint: tx_2.inputs[1].previous_output.clone(), + outpoint: tx_2.inputs[1].previous_output, value: 888823, height: Some(642293), }, @@ -2534,8 +2526,8 @@ fn test_validate_fee_wrong_sender() { let amount: BigDecimal = "0.0014157".parse().unwrap(); let validate_fee_args = ValidateFeeArgs { fee_tx: &taker_fee_tx, - expected_sender: &*DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 0, uuid: &[], @@ -2564,7 +2556,7 @@ fn test_validate_fee_min_block() { let validate_fee_args = ValidateFeeArgs { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 810329, uuid: &[], @@ -2593,7 +2585,7 @@ fn test_validate_fee_bch_70_bytes_signature() { let validate_fee_args = ValidateFeeArgs { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, - fee_addr: &*DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, amount: &amount, min_block_number: 0, uuid: &[], @@ -2748,8 +2740,7 @@ fn test_generate_tx_doge_fee() { TransactionOutput { value: 100000000, script_pubkey: vec![0; 26].into(), - } - .clone(); + }; 40 ]; @@ -2769,8 +2760,7 @@ fn test_generate_tx_doge_fee() { TransactionOutput { value: 100000000, script_pubkey: vec![0; 26].into(), - } - .clone(); + }; 60 ]; @@ -3079,7 +3069,7 @@ fn test_withdraw_to_p2pkh() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None, false); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); // Create a p2pkh address for the test coin let p2pkh_address = Address { @@ -3127,7 +3117,7 @@ fn test_withdraw_to_p2sh() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None, false); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); // Create a p2sh address for the test coin let p2sh_address = Address { @@ -3175,7 +3165,7 @@ fn test_withdraw_to_p2wpkh() { let client = NativeClient(Arc::new(NativeClientImpl::default())); - let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client.clone()), None, true); + let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, true); // Create a p2wpkh address for the test coin let p2wpkh_address = Address { @@ -3481,7 +3471,7 @@ fn test_account_balance_rpc() { let address_str = address.to_string(); let balance = addresses_map .remove(&address_str) - .expect(&format!("Unexpected address: {}", address_str)); + .unwrap_or_else(|| panic!("Unexpected address: {}", address_str)); (address, BigDecimal::from(balance)) }) .collect(); @@ -3798,7 +3788,7 @@ fn test_scan_for_new_addresses() { let address = address.to_string(); let balance = display_balances .remove(&address) - .expect(&format!("Unexpected address: {}", address)); + .unwrap_or_else(|| panic!("Unexpected address: {}", address)); MockResult::Return(Box::new(futures01::future::ok(BigDecimal::from(balance)))) }); @@ -4355,7 +4345,7 @@ fn test_block_header_utxo_loop() { }); let ctx = mm_ctx_with_custom_db(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(IguanaPrivKey::from(H256Json::from([1u8; 32]))); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(H256Json::from([1u8; 32])); let servers: Vec<_> = RICK_ELECTRUM_ADDRS .iter() .map(|server| json!({ "url": server })) diff --git a/mm2src/gossipsub/src/behaviour/tests.rs b/mm2src/gossipsub/src/behaviour/tests.rs index 3c2533805c..1c1923df0b 100644 --- a/mm2src/gossipsub/src/behaviour/tests.rs +++ b/mm2src/gossipsub/src/behaviour/tests.rs @@ -21,6 +21,7 @@ // collection of tests for the gossipsub network behaviour #[cfg(test)] +#[allow(clippy::module_inception)] mod tests { use super::super::*; use crate::GossipsubConfigBuilder; @@ -82,7 +83,7 @@ mod tests { }; } - return (gs, peers, topic_hashes); + (gs, peers, topic_hashes) } #[test] @@ -105,10 +106,9 @@ mod tests { let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { NetworkBehaviourAction::NotifyHandler { event, .. } => { for s in &event.subscriptions { - match s.action { - GossipsubSubscriptionAction::Subscribe => collected_subscriptions.push(s.clone()), - _ => {}, - }; + if s.action == GossipsubSubscriptionAction::Subscribe { + collected_subscriptions.push(s.clone()) + } } collected_subscriptions }, @@ -141,10 +141,10 @@ mod tests { for topic_hash in &topic_hashes { assert!( - gs.topic_peers.get(&topic_hash).is_some(), + gs.topic_peers.get(topic_hash).is_some(), "Topic_peers contain a topic entry" ); - assert!(gs.mesh.get(&topic_hash).is_some(), "mesh should contain a topic entry"); + assert!(gs.mesh.get(topic_hash).is_some(), "mesh should contain a topic entry"); } // unsubscribe from both topics @@ -160,10 +160,9 @@ mod tests { let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { NetworkBehaviourAction::NotifyHandler { event, .. } => { for s in &event.subscriptions { - match s.action { - GossipsubSubscriptionAction::Unsubscribe => collected_subscriptions.push(s.clone()), - _ => {}, - }; + if s.action == GossipsubSubscriptionAction::Unsubscribe { + collected_subscriptions.push(s.clone()) + } } collected_subscriptions }, @@ -179,7 +178,7 @@ mod tests { // check we clean up internal structures for topic_hash in &topic_hashes { assert!( - gs.mesh.get(&topic_hash).is_none(), + gs.mesh.get(topic_hash).is_none(), "All topics should have been removed from the mesh" ); } @@ -233,9 +232,8 @@ mod tests { .iter() .fold(vec![], |mut collected_grafts, (_, controls)| { for c in controls.iter() { - match c { - GossipsubControlAction::Graft { topic_hash: _ } => collected_grafts.push(c.clone()), - _ => {}, + if let GossipsubControlAction::Graft { topic_hash: _ } = c { + collected_grafts.push(c.clone()) } } collected_grafts @@ -278,9 +276,8 @@ mod tests { .iter() .fold(vec![], |mut collected_grafts, (_, controls)| { for c in controls.iter() { - match c { - GossipsubControlAction::Graft { topic_hash: _ } => collected_grafts.push(c.clone()), - _ => {}, + if let GossipsubControlAction::Graft { topic_hash: _ } = c { + collected_grafts.push(c.clone()) } } collected_grafts @@ -323,7 +320,7 @@ mod tests { _ => collected_publish, }); - let msg_id = (gs.config.message_id_fn)(&publishes.first().expect("Should contain > 0 entries")); + let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); assert!( publishes.len() == 20, @@ -366,7 +363,7 @@ mod tests { gs.publish(&Topic::new(fanout_topic.clone()), publish_data); assert_eq!( - gs.fanout.get(&TopicHash::from_raw(fanout_topic.clone())).unwrap().len(), + gs.fanout.get(&TopicHash::from_raw(fanout_topic)).unwrap().len(), gs.config.mesh_n, "Fanout should contain `mesh_n` peers for fanout topic" ); @@ -382,7 +379,7 @@ mod tests { _ => collected_publish, }); - let msg_id = (gs.config.message_id_fn)(&publishes.first().expect("Should contain > 0 entries")); + let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); assert_eq!( publishes.len(), @@ -415,22 +412,16 @@ mod tests { let send_events: Vec<&NetworkBehaviourAction>> = gs .events .iter() - .filter(|e| match e { - NetworkBehaviourAction::NotifyHandler { .. } => true, - _ => false, - }) + .filter(|e| matches!(e, NetworkBehaviourAction::NotifyHandler { .. })) .collect(); // check that there are two subscriptions sent to each peer for sevent in send_events.clone() { - match sevent { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert!( - event.subscriptions.len() == 2, - "There should be two subscriptions sent to each peer (1 for each topic)." - ); - }, - _ => {}, + if let NetworkBehaviourAction::NotifyHandler { event, .. } = sevent { + assert!( + event.subscriptions.len() == 2, + "There should be two subscriptions sent to each peer (1 for each topic)." + ); }; } @@ -516,7 +507,7 @@ mod tests { // Peer 0 unsubscribes from the first topic gs.handle_received_subscriptions( - &vec![GossipsubSubscription { + &[GossipsubSubscription { action: GossipsubSubscriptionAction::Unsubscribe, topic_hash: topic_hashes[0].clone(), }], @@ -545,7 +536,7 @@ mod tests { let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); // create a topic and fill it with some peers - let topic_hash = Topic::new("Test".into()).no_hash().clone(); + let topic_hash = Topic::new("Test".into()).no_hash(); let mut peers = vec![]; for _ in 0..20 { peers.push(PeerId::random()) @@ -562,10 +553,10 @@ mod tests { assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); assert!(random_peers == peers, "Expected no shuffling"); let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 0, |_| true); - assert!(random_peers.len() == 0, "Expected 0 peers to be returned"); + assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); // test the filter let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| false); - assert!(random_peers.len() == 0, "Expected 0 peers to be returned"); + assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 10, |peer| peers.contains(peer)); assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); } @@ -578,13 +569,13 @@ mod tests { let id = gs.config.message_id_fn; let message = GossipsubMessage { - source: peers[11].clone(), + source: peers[11], data: vec![1, 2, 3, 4], sequence_number: 1u64, topics: Vec::new(), }; let msg_id = id(&message); - gs.mcache.put(message.clone()); + gs.mcache.put(message); gs.handle_iwant(&peers[7], vec![msg_id.clone()]); @@ -614,7 +605,7 @@ mod tests { // perform 10 memshifts and check that it leaves the cache for shift in 1..10 { let message = GossipsubMessage { - source: peers[11].clone(), + source: peers[11], data: vec![1, 2, 3, 4], sequence_number: shift, topics: Vec::new(), @@ -674,7 +665,9 @@ mod tests { // check that we sent an IWANT request for `unknown id` let iwant_exists = match gs.control_pool.get(&peers[7]) { Some(controls) => controls.iter().any(|c| match c { - GossipsubControlAction::IWant { message_ids } => { + GossipsubControlAction::IWant { message_ids } => + { + #[allow(clippy::cmp_owned)] message_ids.iter().any(|m| *m.0 == String::from("unknown id")) }, _ => false, @@ -759,8 +752,7 @@ mod tests { .map(|&t| String::from(t)) .collect(); - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, topics.clone(), GossipsubConfig::default(), true); + let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), true); let mut their_topics = topic_hashes.clone(); // their_topics = [topic1, topic2, topic3] @@ -770,9 +762,9 @@ mod tests { gs.handle_graft(&peers[7], their_topics.clone()); - for i in 0..2 { + for item in topic_hashes.iter().take(2) { assert!( - gs.mesh.get(&topic_hashes[i]).unwrap().contains(&peers[7]), + gs.mesh.get(item).unwrap().contains(&peers[7]), "Expected peer to be in the mesh for the first 2 topics" ); } @@ -869,7 +861,7 @@ mod tests { let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); for peer in &peers { - gs.connected_relays.insert(peer.clone()); + gs.connected_relays.insert(*peer); } gs.handle_included_to_relays_mesh(&peers[0], true, 1); @@ -885,9 +877,9 @@ mod tests { let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); for (i, peer) in peers.iter().enumerate() { - gs.connected_relays.insert(peer.clone()); + gs.connected_relays.insert(*peer); if i < 13 { - gs.relays_mesh.insert(peer.clone(), 0); + gs.relays_mesh.insert(*peer, 0); } } diff --git a/mm2src/gossipsub/src/mcache.rs b/mm2src/gossipsub/src/mcache.rs index e131c5e7d6..fe92cd3c93 100644 --- a/mm2src/gossipsub/src/mcache.rs +++ b/mm2src/gossipsub/src/mcache.rs @@ -131,13 +131,12 @@ mod tests { let data: Vec = vec![u8x]; let sequence_number = x; - let m = GossipsubMessage { + GossipsubMessage { source, data, sequence_number, topics, - }; - m + } } #[test] @@ -160,8 +159,8 @@ mod tests { fn test_put_get_one() { let mut mc = MessageCache::new_default(10, 15); - let topic1_hash = Topic::new("topic1".into()).no_hash().clone(); - let topic2_hash = Topic::new("topic2".into()).no_hash().clone(); + let topic1_hash = Topic::new("topic1".into()).no_hash(); + let topic2_hash = Topic::new("topic2".into()).no_hash(); let m = gen_testm(10, vec![topic1_hash, topic2_hash]); @@ -171,13 +170,12 @@ mod tests { let fetched = mc.get(&(mc.msg_id)(&m)); - assert_eq!(fetched.is_none(), false); - assert_eq!(fetched.is_some(), true); + assert!(fetched.is_some()); // Make sure it is the same fetched message match fetched { Some(x) => assert_eq!(*x, m), - _ => assert!(false), + _ => panic!("expected {:?}", m), } } diff --git a/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs b/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs index 30402a9396..1911d11781 100644 --- a/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs +++ b/mm2src/mm2_bitcoin/rpc/src/v1/types/hash.rs @@ -194,9 +194,8 @@ mod tests { } let str_reversed = "XXXYYY"; - match H256::from_str(str_reversed) { - Ok(_) => panic!("unexpected"), - _ => (), + if H256::from_str(str_reversed).is_ok() { + panic!("unexpected"); } } diff --git a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs index 40b5e7fdc3..2f7046b3e5 100644 --- a/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs +++ b/mm2src/mm2_bitcoin/spv_validation/src/helpers_validation.rs @@ -402,7 +402,7 @@ mod tests { #[test] fn it_does_bitcoin_hash256() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("hash256", &fixtures); + let test_cases = test_utils::get_test_cases("hash256", fixtures); for case in test_cases { let input = force_deserialize_hex(case.input.as_str().unwrap()); let mut expected = H256::default(); @@ -416,7 +416,7 @@ mod tests { #[test] fn it_computes_hash256_merkle_steps() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("hash256MerkleStep", &fixtures); + let test_cases = test_utils::get_test_cases("hash256MerkleStep", fixtures); for case in test_cases { let inputs = case.input.as_array().unwrap(); let a = force_deserialize_hex(inputs[0].as_str().unwrap()); @@ -432,7 +432,7 @@ mod tests { #[test] fn it_determines_input_length() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("determineInputLength", &fixtures); + let test_cases = test_utils::get_test_cases("determineInputLength", fixtures); for case in test_cases { let input = force_deserialize_hex(case.input.as_str().unwrap()); let expected = case.output.as_u64().unwrap() as usize; @@ -444,7 +444,7 @@ mod tests { #[test] fn it_determines_output_length() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("determineOutputLength", &fixtures); + let test_cases = test_utils::get_test_cases("determineOutputLength", fixtures); for case in test_cases { let input = force_deserialize_hex(case.input.as_str().unwrap()); let expected = case.output.as_u64().unwrap() as usize; @@ -456,7 +456,7 @@ mod tests { #[test] fn it_validates_vin_syntax() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("validateVin", &fixtures); + let test_cases = test_utils::get_test_cases("validateVin", fixtures); for case in test_cases { let input = force_deserialize_hex(case.input.as_str().unwrap()); let expected = case.output.as_bool().unwrap(); @@ -468,7 +468,7 @@ mod tests { #[test] fn it_validates_vout_syntax() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("validateVout", &fixtures); + let test_cases = test_utils::get_test_cases("validateVout", fixtures); for case in test_cases { let input = force_deserialize_hex(case.input.as_str().unwrap()); let expected = case.output.as_bool().unwrap(); @@ -480,7 +480,7 @@ mod tests { #[test] fn it_verifies_hash256_merkles() { test_utils::run_test(|fixtures| { - let test_cases = test_utils::get_test_cases("verifyHash256Merkle", &fixtures); + let test_cases = test_utils::get_test_cases("verifyHash256Merkle", fixtures); for case in test_cases { let inputs = case.input.as_object().unwrap(); let extended_proof = force_deserialize_hex(inputs.get("proof").unwrap().as_str().unwrap()); @@ -489,12 +489,12 @@ mod tests { continue; } - let index = inputs.get("index").unwrap().as_u64().unwrap() as u64; + let index = inputs.get("index").unwrap().as_u64().unwrap(); let expected = case .output .as_bool() .unwrap() - .then(|| ()) + .then_some(()) .ok_or(SPVError::BadMerkleProof); // extract root and txid @@ -527,7 +527,7 @@ mod tests { let tx_id: H256 = H256::from_reversed_str("7e9797a05abafbc1542449766ef9a41838ebbf6d24cd3223d361aa07c51981df"); let merkle_pos = 1; let merkle_root: H256 = - H256::from_reversed_str("41f138275d13690e3c5d735e2f88eb6f1aaade1207eb09fa27a65b40711f3ae0").into(); + H256::from_reversed_str("41f138275d13690e3c5d735e2f88eb6f1aaade1207eb09fa27a65b40711f3ae0"); let merkle_nodes: Vec = vec![ H256::from_reversed_str("73dfb53e6f49854b09d98500d4899d5c4e703c4fa3a2ddadc2cd7f12b72d4182"), H256::from_reversed_str("4274d707b2308d39a04f2940024d382fa80d994152a50d4258f5a7feead2a563"), @@ -543,7 +543,7 @@ mod tests { let tx_id: H256 = H256::from_reversed_str("c06fbab289f723c6261d3030ddb6be121f7d2508d77862bb1e484f5cd7f92b25"); let merkle_pos = 0; let merkle_root: H256 = - H256::from_reversed_str("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719").into(); + H256::from_reversed_str("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719"); let merkle_nodes: Vec = vec![H256::from_reversed_str( "5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2", )]; @@ -558,7 +558,7 @@ mod tests { let tx_id: H256 = H256::from_reversed_str("b36bced99cc459506ad2b3af6990920b12f6dc84f9c7ed0dd2c3703f94a4b692"); let merkle_pos = 680; let merkle_root: H256 = - H256::from_reversed_str("def7a26d91789069dad448cb4b68658b7ba419f9fbd28dce7fe32ed0010e55df").into(); + H256::from_reversed_str("def7a26d91789069dad448cb4b68658b7ba419f9fbd28dce7fe32ed0010e55df"); let merkle_nodes: Vec = vec![ H256::from_reversed_str("39141331f2b7133e72913460384927b421ffdef3e24b88521e7ac54d30019409"), H256::from_reversed_str("39aeb77571ee0b0cf9feb7e121938b862f3994ff1254b34559378f6f2ed8b1fb"), diff --git a/mm2src/mm2_bitcoin/spv_validation/src/lib.rs b/mm2src/mm2_bitcoin/spv_validation/src/lib.rs index a6f69a06b4..4c57937ba2 100644 --- a/mm2src/mm2_bitcoin/spv_validation/src/lib.rs +++ b/mm2src/mm2_bitcoin/spv_validation/src/lib.rs @@ -49,8 +49,7 @@ pub(crate) mod test_utils { fn to_test_case(val: &serde_json::Value) -> TestCase { let o = val.get("output"); - let output: &serde_json::Value; - output = match o { + let output = match o { Some(v) => v, None => &serde_json::Value::Null, }; @@ -65,7 +64,7 @@ pub(crate) mod test_utils { let vals: &Vec = fixtures.get(name).unwrap().as_array().unwrap(); let mut cases = vec![]; for i in vals { - cases.push(to_test_case(&i)); + cases.push(to_test_case(i)); } cases } diff --git a/mm2src/mm2_bitcoin/spv_validation/src/work.rs b/mm2src/mm2_bitcoin/spv_validation/src/work.rs index 11a148e1a7..21566be5ce 100644 --- a/mm2src/mm2_bitcoin/spv_validation/src/work.rs +++ b/mm2src/mm2_bitcoin/spv_validation/src/work.rs @@ -191,7 +191,7 @@ pub(crate) mod tests { BLOCK_HEADERS_MAP .get(coin) .unwrap() - .into_iter() + .iter() .map(|h| (h.height, h.hex.as_str().into())) .collect() } diff --git a/mm2src/mm2_main/src/lib.rs b/mm2src/mm2_main/src/lib.rs index b72f558765..23c7b65312 100644 --- a/mm2src/mm2_main/src/lib.rs +++ b/mm2src/mm2_main/src/lib.rs @@ -1,4 +1,12 @@ #![feature(hash_raw_entry)] +// `mockable` implementation uses these +#![allow( + clippy::forget_ref, + clippy::forget_copy, + clippy::swap_ptr_to_ref, + clippy::forget_non_drop, + clippy::let_unit_value +)] #[macro_use] extern crate common; #[macro_use] extern crate gstuff; diff --git a/mm2src/mm2_main/src/lp_message_service.rs b/mm2src/mm2_main/src/lp_message_service.rs index 98a7b03a54..018d6bf971 100644 --- a/mm2src/mm2_main/src/lp_message_service.rs +++ b/mm2src/mm2_main/src/lp_message_service.rs @@ -150,7 +150,7 @@ mod message_service_tests { "RustTestChatId", true, )); - assert!(!res.is_err()); + assert!(res.is_ok()); assert!(res.unwrap()); } } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index a010c736e5..5fc9d248db 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1935,6 +1935,7 @@ impl<'a> MakerOrderBuilder<'a> { #[cfg(test)] fn build_unchecked(self) -> MakerOrder { let created_at = now_ms(); + #[allow(clippy::or_fun_call)] MakerOrder { base: self.base_coin.ticker().to_owned(), rel: self.rel_coin.ticker().to_owned(), diff --git a/mm2src/mm2_main/src/lp_price.rs b/mm2src/mm2_main/src/lp_price.rs index e35222c2b8..164270d120 100644 --- a/mm2src/mm2_main/src/lp_price.rs +++ b/mm2src/mm2_main/src/lp_price.rs @@ -332,11 +332,11 @@ mod tests { assert_eq!(rates.price, MmNumber::from("0.02")); let usdt_infos = registry.get_infos("USDT-PLG20"); - assert_eq!(usdt_infos.is_some(), true); + assert!(usdt_infos.is_some()); assert_eq!(usdt_infos.unwrap().last_price, MmNumber::from(1)); let usdt_infos = registry.get_infos("USDT"); - assert_eq!(usdt_infos.is_some(), true); + assert!(usdt_infos.is_some()); assert_eq!(usdt_infos.unwrap().last_price, MmNumber::from(1)); } } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 84caa452a1..b075506d50 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1831,7 +1831,7 @@ mod lp_swap_tests { UtxoActivationParams { mode: UtxoRpcMode::Electrum { servers: electrums - .into_iter() + .iter() .map(|url| ElectrumRpcRequest { url: url.to_string(), protocol: Default::default(), diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index d80fecc413..b452412fcc 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -1769,41 +1769,43 @@ pub struct MakerSavedSwap { #[cfg(test)] impl MakerSavedSwap { pub fn new(maker_amount: &MmNumber, taker_amount: &MmNumber) -> MakerSavedSwap { - let mut events: Vec = Vec::new(); - events.push(MakerSavedEvent { - timestamp: 0, - event: MakerSwapEvent::Started(MakerSwapData { - taker_coin: "".to_string(), - maker_coin: "".to_string(), - taker: Default::default(), - secret: Default::default(), - secret_hash: None, - my_persistent_pub: Default::default(), - lock_duration: 0, - maker_amount: maker_amount.to_decimal(), - taker_amount: taker_amount.to_decimal(), - maker_payment_confirmations: 0, - maker_payment_requires_nota: None, - taker_payment_confirmations: 0, - taker_payment_requires_nota: None, - maker_payment_lock: 0, - uuid: Default::default(), - started_at: 0, - maker_coin_start_block: 0, - taker_coin_start_block: 0, - maker_payment_trade_fee: None, - taker_payment_spend_trade_fee: None, - maker_coin_swap_contract_address: None, - taker_coin_swap_contract_address: None, - maker_coin_htlc_pubkey: None, - taker_coin_htlc_pubkey: None, - p2p_privkey: None, - }), - }); - events.push(MakerSavedEvent { - timestamp: 0, - event: MakerSwapEvent::Finished, - }); + let events = vec![ + MakerSavedEvent { + timestamp: 0, + event: MakerSwapEvent::Started(MakerSwapData { + taker_coin: "".to_string(), + maker_coin: "".to_string(), + taker: Default::default(), + secret: Default::default(), + secret_hash: None, + my_persistent_pub: Default::default(), + lock_duration: 0, + maker_amount: maker_amount.to_decimal(), + taker_amount: taker_amount.to_decimal(), + maker_payment_confirmations: 0, + maker_payment_requires_nota: None, + taker_payment_confirmations: 0, + taker_payment_requires_nota: None, + maker_payment_lock: 0, + uuid: Default::default(), + started_at: 0, + maker_coin_start_block: 0, + taker_coin_start_block: 0, + maker_payment_trade_fee: None, + taker_payment_spend_trade_fee: None, + maker_coin_swap_contract_address: None, + taker_coin_swap_contract_address: None, + maker_coin_htlc_pubkey: None, + taker_coin_htlc_pubkey: None, + p2p_privkey: None, + }), + }, + MakerSavedEvent { + timestamp: 0, + event: MakerSwapEvent::Finished, + }, + ]; + MakerSavedSwap { uuid: Default::default(), my_order_uuid: None, diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index b4a0a6c761..e550124c33 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -1213,13 +1213,15 @@ fn lp_connect_start_bob_should_not_be_invoked_if_order_match_already_connected() static mut CONNECT_START_CALLED: bool = false; lp_connect_start_bob.mock_safe(|_, _, _| { - MockResult::Return(unsafe { + unsafe { CONNECT_START_CALLED = true; - }) + } + + MockResult::Return(()) }); let connect: TakerConnect = json::from_str(r#"{"taker_order_uuid":"2f9afe84-7a89-4194-8947-45fba563118f","maker_order_uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3","method":"connect","sender_pubkey":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","dest_pub_key":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed"}"#).unwrap(); - block_on(process_taker_connect(ctx, connect.sender_pubkey.clone(), connect)); + block_on(process_taker_connect(ctx, connect.sender_pubkey, connect)); assert!(unsafe { !CONNECT_START_CALLED }); } @@ -1656,7 +1658,7 @@ pub(super) fn make_random_orders( fn pubkey_and_secret_for_test(passphrase: &str) -> (String, [u8; 32]) { let key_pair = key_pair_from_seed(passphrase).unwrap(); let pubkey = hex::encode(&**key_pair.public()); - let secret = *(&*key_pair.private().secret); + let secret = *key_pair.private().secret; (pubkey, secret) } @@ -1706,14 +1708,14 @@ fn test_process_get_orderbook_request() { let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); let mut orderbook = ordermatch_ctx.orderbook.lock(); - for order in orders_by_pubkeys.iter().map(|(_pubkey, orders)| orders).flatten() { + for order in orders_by_pubkeys.values().flatten() { orderbook.insert_or_update_order_update_trie(order.clone()); } // avoid dead lock on orderbook as process_get_orderbook_request also acquires it drop(orderbook); - let encoded = process_get_orderbook_request(ctx.clone(), "RICK".into(), "MORTY".into()) + let encoded = process_get_orderbook_request(ctx, "RICK".into(), "MORTY".into()) .unwrap() .unwrap(); @@ -1721,7 +1723,7 @@ fn test_process_get_orderbook_request() { for (pubkey, item) in orderbook.pubkey_orders { let expected = orders_by_pubkeys .get(&pubkey) - .expect(&format!("!best_orders_by_pubkeys is expected to contain {:?}", pubkey)); + .unwrap_or_else(|| panic!("!best_orders_by_pubkeys is expected to contain {:?}", pubkey)); let mut actual: Vec = item .orders @@ -1761,9 +1763,7 @@ fn test_process_get_orderbook_request_limit() { // avoid dead lock on orderbook as process_get_orderbook_request also acquires it drop(orderbook); - let err = process_get_orderbook_request(ctx.clone(), "RICK".into(), "MORTY".into()) - .err() - .expect("Expected an error"); + let err = process_get_orderbook_request(ctx, "RICK".into(), "MORTY".into()).expect_err("Expected an error"); log!("error: {}", err); assert!(err.contains("Orderbook too large")); @@ -1859,19 +1859,10 @@ fn test_request_and_fill_orderbook() { let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); let orderbook = ordermatch_ctx.orderbook.lock(); - let expected = expected_orders - .iter() - .map(|(_pubkey, orders)| orders.clone()) - .flatten() - .collect(); + let expected = expected_orders.values().flatten().cloned().collect(); assert_eq!(orderbook.order_set, expected); - let expected = expected_orders - .iter() - .map(|(_pubkey, orders)| orders) - .flatten() - .map(|(uuid, _order)| *uuid) - .collect(); + let expected = expected_orders.values().flatten().map(|(uuid, _order)| *uuid).collect(); let unordered = orderbook .unordered .get(&("RICK".to_owned(), "MORTY".to_owned())) @@ -1879,8 +1870,7 @@ fn test_request_and_fill_orderbook() { assert_eq!(*unordered, expected); let expected = expected_orders - .iter() - .map(|(_pubkey, orders)| orders) + .values() .flatten() .map(|(uuid, order)| OrderedByPriceOrder { uuid: *uuid, @@ -2199,7 +2189,7 @@ fn test_taker_request_can_match_with_maker_pubkey() { assert!(order.request.can_match_with_maker_pubkey(&maker_pubkey)); let mut set = HashSet::new(); - set.insert(maker_pubkey.clone()); + set.insert(maker_pubkey); order.request.match_by = MatchBy::Pubkeys(set); assert!(order.request.can_match_with_maker_pubkey(&maker_pubkey)); @@ -2322,7 +2312,7 @@ fn test_diff_should_not_be_written_if_hash_not_changed_on_insert() { let alb_ordered_pair = alb_ordered_pair("C1", "C2"); let pair_trie_root = pair_trie_root_by_pub(&ctx, &pubkey, &alb_ordered_pair); - for order in orders.clone() { + for order in orders { insert_or_update_order(&ctx, order); } @@ -2581,7 +2571,7 @@ fn test_process_sync_pubkey_orderbook_state_points_to_not_uptodate_trie_root() { assert_eq!(full_trie, expected); } -fn check_if_orderbook_contains_only(orderbook: &Orderbook, pubkey: &str, orders: &Vec) { +fn check_if_orderbook_contains_only(orderbook: &Orderbook, pubkey: &str, orders: &[OrderbookItem]) { let pubkey_state = orderbook.pubkeys_state.get(pubkey).expect("!pubkeys_state"); // order_set @@ -2717,7 +2707,7 @@ fn test_orderbook_sync_trie_diff_time_cache() { let bob_root = bob_state.trie_roots.get(&rick_morty_pair).unwrap(); let bob_history_on_sync = DeltaOrFullTrie::from_history( - &rick_morty_history_bob, + rick_morty_history_bob, *alice_root, *bob_root, &orderbook_bob.memory_db, @@ -2768,7 +2758,7 @@ fn test_orderbook_sync_trie_diff_time_cache() { let bob_root = bob_state.trie_roots.get(&rick_morty_pair).unwrap(); let bob_history_on_sync = DeltaOrFullTrie::from_history( - &rick_morty_history_bob, + rick_morty_history_bob, *alice_root, *bob_root, &orderbook_bob.memory_db, 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 b0887667ca..38830e6d24 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 @@ -30,7 +30,7 @@ const T_BCH_ELECTRUMS: &[&str] = &[ 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.into_iter().map(|url| json!({ "url": url })).collect() } +fn t_bch_electrums_legacy_json() -> Vec { T_BCH_ELECTRUMS.iter().map(|url| json!({ "url": url })).collect() } #[test] #[cfg(not(target_arch = "wasm32"))] 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 e817d15d51..0685731781 100644 --- a/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/best_orders_tests.rs @@ -821,9 +821,9 @@ fn test_best_orders_address_and_confirmations() { assert_eq!(best_orders[0].coin, "RICK"); assert_eq!(best_orders[0].address, rick_address); assert_eq!(best_orders[0].base_confs, 5); - assert_eq!(best_orders[0].base_nota, false); + assert!(!best_orders[0].base_nota); assert_eq!(best_orders[0].rel_confs, 10); - assert_eq!(best_orders[0].rel_nota, true); + assert!(best_orders[0].rel_nota); let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, @@ -840,9 +840,9 @@ fn test_best_orders_address_and_confirmations() { assert_eq!(best_orders[0].coin, "tBTC"); assert_eq!(best_orders[0].address, tbtc_segwit_address); assert_eq!(best_orders[0].base_confs, 10); - assert_eq!(best_orders[0].base_nota, true); + assert!(best_orders[0].base_nota); assert_eq!(best_orders[0].rel_confs, 5); - assert_eq!(best_orders[0].rel_nota, false); + assert!(!best_orders[0].rel_nota); // checking buy and sell best_orders against ("RICK", "tBTC", "0.7", "0.0002", Some("0.00015")) let rc = block_on(mm_alice.rpc(&json! ({ @@ -860,9 +860,9 @@ fn test_best_orders_address_and_confirmations() { assert_eq!(best_orders[0].coin, "tBTC"); assert_eq!(best_orders[0].address, tbtc_segwit_address); assert_eq!(best_orders[0].base_confs, 10); - assert_eq!(best_orders[0].base_nota, true); + assert!(best_orders[0].base_nota); assert_eq!(best_orders[0].rel_confs, 5); - assert_eq!(best_orders[0].rel_nota, false); + assert!(!best_orders[0].rel_nota); let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, @@ -879,9 +879,9 @@ fn test_best_orders_address_and_confirmations() { assert_eq!(best_orders[0].coin, "RICK"); assert_eq!(best_orders[0].address, rick_address); assert_eq!(best_orders[0].base_confs, 5); - assert_eq!(best_orders[0].base_nota, false); + assert!(!best_orders[0].base_nota); assert_eq!(best_orders[0].rel_confs, 10); - assert_eq!(best_orders[0].rel_nota, true); + assert!(best_orders[0].rel_nota); block_on(mm_bob.stop()).unwrap(); block_on(mm_alice.stop()).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs b/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs index 62c82f0de8..d1ae803d03 100644 --- a/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/eth_tests.rs @@ -62,7 +62,7 @@ fn test_disable_eth_coin_with_token() { }); let make_test_order = block_on(mm.rpc(&req)).unwrap(); assert_eq!(make_test_order.0, StatusCode::OK); - let order_uuid = Json::from_str(&*make_test_order.1).unwrap(); + let order_uuid = Json::from_str(&make_test_order.1).unwrap(); let order_uuid = order_uuid.get("result").unwrap().get("uuid").unwrap().as_str().unwrap(); // Try to disable platform coin, ETH. This should fail due to the dependent tokens. diff --git a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs index ab764023e7..537bc3aae1 100644 --- a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs @@ -422,7 +422,7 @@ fn test_enable_lightning() { #[cfg(not(target_arch = "wasm32"))] fn test_connect_to_node() { let (mm_node_1, mm_node_2, node_1_id, _) = start_lightning_nodes(false); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); let connect = block_on(mm_node_2.rpc(&json!({ "userpass": mm_node_2.userpass, @@ -453,7 +453,7 @@ fn test_connect_to_node() { #[cfg(not(target_arch = "wasm32"))] fn test_open_channel() { let (mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(false); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); block_on(open_channel( &mut mm_node_2, @@ -532,7 +532,7 @@ fn test_open_channel() { // This also tests 0_confs_channels fn test_send_payment() { let (mut mm_node_2, mut mm_node_1, node_2_id, node_1_id) = start_lightning_nodes(true); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); block_on(add_trusted_node(&mm_node_1, &node_2_id)); block_on(open_channel( @@ -624,7 +624,7 @@ fn test_send_payment() { #[cfg(not(target_arch = "wasm32"))] fn test_mpp() { let (mut mm_node_2, mut mm_node_1, node_2_id, node_1_id) = start_lightning_nodes(true); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); block_on(add_trusted_node(&mm_node_1, &node_2_id)); @@ -686,7 +686,7 @@ fn test_mpp() { #[cfg(not(target_arch = "wasm32"))] fn test_lightning_swaps() { let (mut mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(true); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); block_on(add_trusted_node(&mm_node_1, &node_2_id)); @@ -765,7 +765,7 @@ fn test_lightning_swaps() { #[cfg(not(target_arch = "wasm32"))] fn test_lightning_taker_swap_mpp() { let (mut mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(true); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); block_on(add_trusted_node(&mm_node_1, &node_2_id)); @@ -824,7 +824,7 @@ fn test_lightning_taker_swap_mpp() { #[cfg(not(target_arch = "wasm32"))] fn test_lightning_maker_swap_mpp() { let (mut mm_node_1, mut mm_node_2, node_1_id, node_2_id) = start_lightning_nodes(true); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); block_on(add_trusted_node(&mm_node_1, &node_2_id)); @@ -885,7 +885,7 @@ fn test_lightning_maker_swap_mpp() { #[cfg(not(target_arch = "wasm32"))] fn test_lightning_taker_gets_swap_preimage_onchain() { let (mut mm_node_1, mut mm_node_2, node_1_id, _) = start_lightning_nodes(false); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); let open_channel = block_on(open_channel( &mut mm_node_2, @@ -941,7 +941,7 @@ fn test_lightning_taker_gets_swap_preimage_onchain() { #[cfg(not(target_arch = "wasm32"))] fn test_lightning_taker_claims_mpp() { let (mut mm_node_1, mut mm_node_2, node_1_id, _) = start_lightning_nodes(false); - let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip.to_string()); + let node_1_address = format!("{}@{}:9735", node_1_id, mm_node_1.ip); let open_channel_1 = block_on(open_channel( &mut mm_node_2, 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 ca257a6118..864713a9b1 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -5710,7 +5710,7 @@ fn test_orderbook_is_mine_orders() { let asks = bob_orderbook["asks"].as_array().unwrap(); assert_eq!(asks.len(), 1, "Bob RICK/MORTY orderbook must have exactly 1 ask"); let is_mine = asks[0]["is_mine"].as_bool().unwrap(); - assert_eq!(is_mine, true); + assert!(is_mine); // Alice orderbook must show 1 not-mine order log!("Get RICK/MORTY orderbook on Alice side"); @@ -5728,7 +5728,7 @@ fn test_orderbook_is_mine_orders() { let asks = alice_orderbook["asks"].as_array().unwrap(); assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); let is_mine = asks[0]["is_mine"].as_bool().unwrap(); - assert_eq!(is_mine, false); + assert!(!is_mine); // make another order by Alice let rc = block_on(mm_alice.rpc(&json! ({ @@ -6232,7 +6232,7 @@ fn test_get_current_mtp() { ]); let passphrase = "cMhHM3PMpMrChygR4bLF7QsTdenhWpFrrmf2UezBG3eeFsz41rtL"; - let conf = Mm2TestConf::seednode(&passphrase, &coins); + let conf = Mm2TestConf::seednode(passphrase, &coins); let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); @@ -6254,10 +6254,10 @@ fn test_get_current_mtp() { .unwrap(); // Test if request is successful before proceeding. - assert_eq!(true, rc.0.is_success()); + assert!(rc.0.is_success()); let mtp_result: Json = json::from_str(&rc.1).unwrap(); // Test if mtp returns a u32 Number. - assert_eq!(true, mtp_result["result"]["mtp"].is_number()); + assert!(mtp_result["result"]["mtp"].is_number()); } #[test] @@ -6295,7 +6295,7 @@ fn test_get_public_key() { // Must be 200 assert_eq!(resp.0, 200); - let v: RpcV2Response = serde_json::from_str(&*resp.1).unwrap(); + let v: RpcV2Response = serde_json::from_str(&resp.1).unwrap(); assert_eq!( v.result.public_key, "022cd3021a2197361fb70b862c412bc8e44cff6951fa1de45ceabfdd9b4c520420" @@ -6334,7 +6334,7 @@ fn test_get_public_key_hash() { // Must be 200 assert_eq!(resp.0, StatusCode::OK); - let v: RpcV2Response = serde_json::from_str(&*resp.1).unwrap(); + let v: RpcV2Response = serde_json::from_str(&resp.1).unwrap(); // Public key hash must be "b506088aa2a3b4bb1da3a29bf00ce1a550ea1df9" assert_eq!(v.result.public_key_hash, "b506088aa2a3b4bb1da3a29bf00ce1a550ea1df9") } @@ -6481,9 +6481,9 @@ fn test_conf_settings_in_orderbook() { "Alice RICK/MORTY orderbook must have exactly 1 ask" ); assert_eq!(alice_orderbook.asks[0].base_confs, 10); - assert_eq!(alice_orderbook.asks[0].base_nota, true); + assert!(alice_orderbook.asks[0].base_nota); assert_eq!(alice_orderbook.asks[0].rel_confs, 5); - assert_eq!(alice_orderbook.asks[0].rel_nota, false); + assert!(!alice_orderbook.asks[0].rel_nota); assert_eq!( alice_orderbook.bids.len(), @@ -6491,9 +6491,9 @@ fn test_conf_settings_in_orderbook() { "Alice RICK/MORTY orderbook must have exactly 1 bid" ); assert_eq!(alice_orderbook.bids[0].base_confs, 10); - assert_eq!(alice_orderbook.bids[0].base_nota, true); + assert!(alice_orderbook.bids[0].base_nota); assert_eq!(alice_orderbook.bids[0].rel_confs, 5); - assert_eq!(alice_orderbook.bids[0].rel_nota, false); + assert!(!alice_orderbook.bids[0].rel_nota); block_on(mm_bob.stop()).unwrap(); block_on(mm_alice.stop()).unwrap(); @@ -6622,9 +6622,9 @@ fn alice_can_see_confs_in_orderbook_after_sync() { .find(|entry| entry.pubkey == bob_pubkey) .unwrap(); assert_eq!(bob_order_in_orderbook.base_confs, 10); - assert_eq!(bob_order_in_orderbook.base_nota, true); + assert!(bob_order_in_orderbook.base_nota); assert_eq!(bob_order_in_orderbook.rel_confs, 5); - assert_eq!(bob_order_in_orderbook.rel_nota, false); + assert!(!bob_order_in_orderbook.rel_nota); block_on(mm_bob.stop()).unwrap(); block_on(mm_alice.stop()).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/mod.rs b/mm2src/mm2_main/tests/mm2_tests/mod.rs index 3959e9e94d..d331a269c6 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mod.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mod.rs @@ -12,4 +12,5 @@ mod z_coin_tests; // dummy test helping IDE to recognize this as test module #[test] +#[allow(clippy::assertions_on_constants)] fn dummy() { assert!(true) } 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 f45aae813b..ac7ec3b1bf 100644 --- a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs @@ -701,9 +701,9 @@ fn test_conf_settings_in_orderbook() { "Alice RICK/MORTY orderbook must have exactly 1 ask" ); assert_eq!(alice_orderbook.asks[0].base_confs, 10); - assert_eq!(alice_orderbook.asks[0].base_nota, true); + assert!(alice_orderbook.asks[0].base_nota); assert_eq!(alice_orderbook.asks[0].rel_confs, 5); - assert_eq!(alice_orderbook.asks[0].rel_nota, false); + assert!(!alice_orderbook.asks[0].rel_nota); assert_eq!( alice_orderbook.bids.len(), @@ -711,9 +711,9 @@ fn test_conf_settings_in_orderbook() { "Alice RICK/MORTY orderbook must have exactly 1 bid" ); assert_eq!(alice_orderbook.bids[0].base_confs, 10); - assert_eq!(alice_orderbook.bids[0].base_nota, true); + assert!(alice_orderbook.bids[0].base_nota); assert_eq!(alice_orderbook.bids[0].rel_confs, 5); - assert_eq!(alice_orderbook.bids[0].rel_nota, false); + assert!(!alice_orderbook.bids[0].rel_nota); block_on(mm_bob.stop()).unwrap(); block_on(mm_alice.stop()).unwrap(); @@ -841,9 +841,9 @@ fn alice_can_see_confs_in_orderbook_after_sync() { .find(|entry| entry.pubkey == bob_pubkey) .unwrap(); assert_eq!(bob_order_in_orderbook.base_confs, 10); - assert_eq!(bob_order_in_orderbook.base_nota, true); + assert!(bob_order_in_orderbook.base_nota); assert_eq!(bob_order_in_orderbook.rel_confs, 5); - assert_eq!(bob_order_in_orderbook.rel_nota, false); + assert!(!bob_order_in_orderbook.rel_nota); block_on(mm_bob.stop()).unwrap(); block_on(mm_alice.stop()).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 8e24b4411e..31830de911 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -268,9 +268,9 @@ fn test_tendermint_tx_history() { true, )); - if let Err(_) = block_on(mm.wait_for_log(60., |log| log.contains(TX_FINISHED_LOG))) { + if block_on(mm.wait_for_log(60., |log| log.contains(TX_FINISHED_LOG))).is_err() { println!("{}", mm.log_as_utf8().unwrap()); - assert!(false, "Tx history didn't finish which is not expected"); + panic!("Tx history didn't finish which is not expected"); } // testing IRIS-TEST history From 9050e0a93fc5e882ec8c257c07915d0f83e8f967 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 20:51:26 +0300 Subject: [PATCH 54/79] add wasm lint Signed-off-by: ozkanonur --- .github/workflows/fmt-and-lint.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index ddd59385f8..6ee8a2c1a2 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -10,9 +10,12 @@ on: - dev jobs: - fmt-and-clippy: + fmt-and-lint: timeout-minutes: 45 - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -25,3 +28,17 @@ jobs: - name: x86-64 code lint run: cargo clippy --all-targets --profile ci -- --D warnings + + wasm-lint: + timeout-minutes: 45 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal --component clippy + rustup default nightly-2022-10-29 + rustup target add wasm32-unknown-unknown + + - name: wasm code lint + run: cargo clippy --target wasm32-unknown-unknown --profile ci -- --D warnings From 3b2322242396311f88564095c661fbe3f280bb29 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 20:55:00 +0300 Subject: [PATCH 55/79] update macos test Signed-off-by: ozkanonur --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1034d34f22..e5a8f0579d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,6 +49,7 @@ jobs: run: | rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + rustup target add x86_64-apple-darwin - name: Test run: | From cc0ff3052bb26f75d7639be8543b0cc7e2bbfbc7 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 15 Mar 2023 00:24:19 +0300 Subject: [PATCH 56/79] fix some clippy warnings Signed-off-by: ozkanonur --- mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs | 5 +++-- mm2src/mm2_test_helpers/src/for_tests.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs index f9b1a7588d..166a950bb2 100644 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs +++ b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs @@ -5,7 +5,8 @@ use futures::channel::{mpsc, oneshot}; use futures::{SinkExt, StreamExt}; use libp2p::PeerId; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +#[cfg(not(windows))] use std::sync::Mutex; use std::time::Duration; static TEST_LISTEN_PORT: AtomicU64 = AtomicU64::new(1); @@ -266,7 +267,7 @@ async fn test_request_response_none() { } #[tokio::test] -#[cfg(not(windows))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 +#[cfg(target_os = "linux")] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 async fn test_request_peers_ok_three_peers() { let _ = env_logger::try_init(); diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 396b3def3e..910837c666 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -20,7 +20,7 @@ use serde_json::{self as json, json, Value as Json}; use std::collections::HashMap; use std::convert::TryFrom; use std::env; -use std::io::Write; +#[cfg(not(target_arch = "wasm32"))] use std::io::Write; use std::net::IpAddr; use std::num::NonZeroUsize; use std::process::Child; From 540f7644568bdab8a186f454ca893e8fd102b661 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 22:41:39 +0300 Subject: [PATCH 57/79] seperate tests Signed-off-by: ozkanonur --- .github/workflows/test.yml | 70 +++++++++++++++++-- .../swaps_confs_settings_sync_tests.rs | 2 + 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5a8f0579d..4249fa51ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ env: FROM_SHARED_RUNNER: true jobs: - linux-x86-64: + linux-x86-64-unit: timeout-minutes: 90 runs-on: ubuntu-latest env: @@ -32,9 +32,9 @@ jobs: - name: Test run: | # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash - cargo test --all --profile ci + cargo test --bins --lib --profile ci - mac-x86-64: + mac-x86-64-unit: timeout-minutes: 90 runs-on: macos-latest env: @@ -54,9 +54,9 @@ jobs: - name: Test run: | # wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash - cargo test --all --target x86_64-apple-darwin --profile ci + cargo test --bins --lib --target x86_64-apple-darwin --profile ci - win-x86-64: + win-x86-64-unit: timeout-minutes: 90 runs-on: windows-latest env: @@ -90,7 +90,65 @@ jobs: # Restart-Service docker # Get-Service docker - cargo test --all --profile ci + cargo test --bins --lib --profile ci + + linux-x86-64-mm2-integration: + timeout-minutes: 90 + runs-on: ubuntu-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_LINUX }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_LINUX }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_LINUX }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_LINUX }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Test + run: cargo test --test 'mm2_tests_main' --profile ci + + mac-x86-64-mm2-integration: + timeout-minutes: 90 + runs-on: macos-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_MACOS }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_MACOS }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + rustup target add x86_64-apple-darwin + + - name: Test + run: cargo test --test 'mm2_tests_main' --target x86_64-apple-darwin --profile ci + + win-x86-64-mm2-integration: + timeout-minutes: 90 + runs-on: windows-latest + env: + BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_WIN }} + BOB_USERPASS: ${{ secrets.BOB_USERPASS_WIN }} + ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_WIN }} + ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_WIN }} + TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Test + run: cargo test --test 'mm2_tests_main' --profile ci docker-tests: timeout-minutes: 90 diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs index 5068aa09f3..a6c8809397 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs @@ -463,6 +463,7 @@ fn test_buy_taker_should_use_maker_confs_and_notas_for_taker_payment_if_maker_re } #[test] +#[ignore] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_buy_taker_should_not_use_maker_confs_and_notas_for_taker_payment_if_maker_requires_more() { let maker_settings = OrderConfirmationsSettings { base_confs: 1, @@ -619,6 +620,7 @@ fn test_sell_maker_should_not_use_taker_confs_and_notas_for_maker_payment_if_tak } #[test] +#[ignore] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_sell_taker_should_use_maker_confs_and_notas_for_taker_payment_if_maker_requires_less() { let maker_settings = OrderConfirmationsSettings { base_confs: 1, From 07b5791eb7640f7c00ba3c945854c074530782d3 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 14 Mar 2023 22:45:47 +0300 Subject: [PATCH 58/79] ignore unstable tests Signed-off-by: ozkanonur --- mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs | 1 + mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs | 1 + 2 files changed, 2 insertions(+) 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 c89f99528a..4e47246a3f 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -3393,6 +3393,7 @@ fn test_match_utxo_with_eth_taker_sell() { #[test] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1074 +#[ignore] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_match_utxo_with_eth_taker_buy() { 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("MYCOIN", 1000.into()); 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 af4156efcc..16c3be0c92 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -451,6 +451,7 @@ fn test_watcher_refunds_taker_payment_eth() { } #[test] +#[ignore] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_watcher_validate_taker_fee_eth() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run let lock_duration = get_payment_locktime(); From fd7f7108787f49717ad1de2bb234de949449def3 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 15 Mar 2023 01:21:08 +0300 Subject: [PATCH 59/79] disable mm2 integration tests for mac due to concurrency limits Signed-off-by: ozkanonur --- .github/workflows/test.yml | 40 ++++++++++--------- .../swaps_confs_settings_sync_tests.rs | 1 + mm2src/mm2_main/tests/mm2_tests/iris_swap.rs | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4249fa51ca..ae9b84fe07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -111,25 +111,27 @@ jobs: - name: Test run: cargo test --test 'mm2_tests_main' --profile ci - mac-x86-64-mm2-integration: - timeout-minutes: 90 - runs-on: macos-latest - env: - BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_MACOS }} - BOB_USERPASS: ${{ secrets.BOB_USERPASS_MACOS }} - ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} - ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} - TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} - steps: - - uses: actions/checkout@v3 - - name: Install toolchain - run: | - rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal - rustup default nightly-2022-10-29 - rustup target add x86_64-apple-darwin - - - name: Test - run: cargo test --test 'mm2_tests_main' --target x86_64-apple-darwin --profile ci + # https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits + # https://github.com/KomodoPlatform/atomicDEX-API/actions/runs/4419618128/jobs/7748266141#step:4:1790 + # mac-x86-64-mm2-integration: + # timeout-minutes: 90 + # runs-on: macos-latest + # env: + # BOB_PASSPHRASE: ${{ secrets.BOB_PASSPHRASE_MACOS }} + # BOB_USERPASS: ${{ secrets.BOB_USERPASS_MACOS }} + # ALICE_PASSPHRASE: ${{ secrets.ALICE_PASSPHRASE_MACOS }} + # ALICE_USERPASS: ${{ secrets.ALICE_USERPASS_MACOS }} + # TELEGRAM_API_KEY: ${{ secrets.TELEGRAM_API_KEY }} + # steps: + # - uses: actions/checkout@v3 + # - name: Install toolchain + # run: | + # rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + # rustup default nightly-2022-10-29 + # rustup target add x86_64-apple-darwin + + # - name: Test + # run: cargo test --test 'mm2_tests_main' --target x86_64-apple-darwin --profile ci win-x86-64-mm2-integration: timeout-minutes: 90 diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs index a6c8809397..85a471c7a5 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs @@ -660,6 +660,7 @@ fn test_sell_taker_should_use_maker_confs_and_notas_for_taker_payment_if_maker_r } #[test] +#[ignore] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_sell_taker_should_not_use_maker_confs_and_notas_for_taker_payment_if_maker_requires_more() { let maker_settings = OrderConfirmationsSettings { base_confs: 1, diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs index 1a47e5ce63..de0af16b3e 100644 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs @@ -21,7 +21,7 @@ fn start_swap_operation() { let pairs = [ ("USDC-IBC-IRIS", "IRIS-NIMDA"), ("IRIS-NIMDA", "RICK"), - ("USDC-IBC-IRIS", "tBNB"), + // ("USDC-IBC-IRIS", "tBNB"), having fund problems ]; block_on(trade_base_rel_iris(&pairs, 1, 2, 0.008)); } From ee370707c68e42596ee084da97eaa43384a8f42c Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 15 Mar 2023 17:51:38 +0300 Subject: [PATCH 60/79] implement container build and push on docker registry Signed-off-by: ozkanonur --- .github/workflows/build-and-upload.yml | 38 ++++++++++++++++++++++ mm2src/common/custom_futures/repeatable.rs | 1 + 2 files changed, 39 insertions(+) diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 90c88001de..114cd76276 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -31,6 +31,18 @@ jobs: echo $VERSION > ./MM_VERSION cargo build --bin mm2 --profile ci + - name: Login to dockerhub + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} docker.io + + - name: Build and push container image + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + run: | + CONTAINER_TAG="dev-$(git rev-parse --short=7 HEAD)" + docker build -t komodoofficial/atomicdexapi:"$CONTAINER_TAG" -t komodoofficial/atomicdexapi:dev-latest -f Dockerfile.dev-release . + docker push komodoofficial/atomicdexapi:"$CONTAINER_TAG" + docker push komodoofficial/atomicdexapi:dev-latest + mac-x86-64: timeout-minutes: 30 runs-on: macos-latest @@ -151,3 +163,29 @@ jobs: export PATH=$PATH:/android-ndk/bin CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --profile ci --crate-type=staticlib --package mm2_bin_lib + + deployment-commitment: + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + needs: linux-x86-64 + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Activate SSH + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.ATOMICDEX_DEPLOYMENTS_SSH }} + + - name: Commitment + run: | + git clone git@github.com:KomodoPlatform/atomicdex-deployments.git + if [ -d "atomicdex-deployments/atomicDEX-API" ]; then + cd atomicdex-deployments/atomicDEX-API + sed -i "1s/^.*$/${GITHUB_SHA::9}/" .commit + git config --global user.email "linuxci@komodoplatform.com" + git config --global user.name "linuxci" + git add .commit + git commit -m "[atomicDEX-API] ${GITHUB_SHA::9} is committed for git & container registry" + git push + fi diff --git a/mm2src/common/custom_futures/repeatable.rs b/mm2src/common/custom_futures/repeatable.rs index 5902f2a577..80f5b635f8 100644 --- a/mm2src/common/custom_futures/repeatable.rs +++ b/mm2src/common/custom_futures/repeatable.rs @@ -567,6 +567,7 @@ mod tests { } #[test] + #[cfg(not(target_os = "macos"))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_until_ms() { const ATTEMPTS_TO_FINISH: usize = 5; const LOWEST_TIMEOUT: u64 = 350; From fbaac1e8b120618a1fd893720a59fddec8050a54 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:17:20 +0200 Subject: [PATCH 61/79] fix: call eth_getTransactionByHash in wait_for_confirmations to avoid race condition (#1716) * call eth_getTransactionByHash in wait_for_confirmations to make sure it will be returned in swap payment validation * add changelog entry --------- Reviewed-by: ozkanonur , cipig, caglaryucekaya --- CHANGELOG.md | 1 + mm2src/coins/eth.rs | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d7435b61..2afdb60057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Tendermint account sequence problem due to running multiple instances were fixed in [#1694](https://github.com/KomodoPlatform/atomicDEX-API/pull/1694) - Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) +- A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. ## v1.0.0-beta - 2023-03-08 diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e7759a8c34..ddb940e331 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -1794,8 +1794,40 @@ impl MarketCoinOps for EthCoin { }; // checking if the current block is above the confirmed_at block prediction for pos chain to prevent overflow if current_block >= confirmed_at && current_block - confirmed_at + 1 >= required_confirms { - status.append(" Confirmed."); - return Ok(()); + loop { + if status.ms2deadline().unwrap() < 0 { + status.append(" Timed out."); + return ERR!( + "Waited too long until {} for transaction {:?} confirmation ", + wait_until, + tx + ); + } + + // Make sure that the transaction is returned by eth_getTransactionByHash too so that swaps don't fail at payment validation + // https://github.com/KomodoPlatform/atomicDEX-API/issues/1630#issuecomment-1401736168 + match selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).await { + Ok(Some(_)) => { + status.append(" Confirmed."); + return Ok(()); + }, + Ok(None) => error!( + "Didn't find tx: {:02x} for coin: {} on RPC node using eth_getTransactionByHash. Retrying in {} seconds", + tx.hash, + selfi.ticker(), + check_every + ), + Err(e) => error!( + "Error {} calling eth_getTransactionByHash for coin: {}, tx: {:02x}. Retrying in {} seconds", + e, + selfi.ticker(), + tx.hash, + check_every + ), + } + + Timer::sleep(check_every as f64).await; + } } } } From be5503faf5500772932b33021350546221975efb Mon Sep 17 00:00:00 2001 From: Kadan Stadelmann Date: Wed, 15 Mar 2023 21:07:22 +0100 Subject: [PATCH 62/79] docs: resolve changelog conflict (#1707) * [ci] change release branch from mm2.1 to main (#1697) * change release branch from mm2.1 to main * replace mm2.1 to main in eth_tests * ci: disable mac ci steps (#1701) Reviewed-by: ozkanonur * docs: introduce CHANGELOG.md (#1680) * propose change.log add a chronological "living" changelog file as a index for the dev.logs * introduce CHANGELOG.md * [docs] update changelog * [docs] add changelog date/tag * update changelog * update date * re-enable mac ci, add one more change log --------- Co-authored-by: shamardy Reviewed-by: ozkanonur , shamardy --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2afdb60057..e43f1ea1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## dev branch +## v1.0.1-beta - 2023-03-17 **Features:** - NFT integration `WIP` [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) @@ -28,6 +28,7 @@ - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) - A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. + ## v1.0.0-beta - 2023-03-08 **Features:** From 8cdac943c70d80353bc3d6b7bb17510d8c2c5bb3 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 15 Mar 2023 23:34:44 +0300 Subject: [PATCH 63/79] add release builds pipelines Signed-off-by: ozkanonur --- .dockerignore | 2 +- .../{build-and-upload.yml => dev-build.yml} | 24 ++- .github/workflows/release-build.yml | 158 ++++++++++++++++++ Dockerfile.release | 7 +- .../swaps_confs_settings_sync_tests.rs | 1 + 5 files changed, 174 insertions(+), 18 deletions(-) rename .github/workflows/{build-and-upload.yml => dev-build.yml} (87%) create mode 100644 .github/workflows/release-build.yml diff --git a/.dockerignore b/.dockerignore index 09656908a3..1cc2f87bf7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,7 @@ cmake-build-debug # Ignoring the Dockerfile allows us to change it without invalidating the "COPY ." step. /Dockerfile -/target +!/target/release/mm2 !/target/ci/mm2 /mm2src/*/target diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/dev-build.yml similarity index 87% rename from .github/workflows/build-and-upload.yml rename to .github/workflows/dev-build.yml index 114cd76276..f1cb7166db 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/dev-build.yml @@ -1,12 +1,10 @@ -name: Build and Upload +name: Development builds on: push: branches: - - main - dev pull_request: branches: - - main - dev env: @@ -26,17 +24,17 @@ jobs: - name: Build run: | - VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-Linux-CI" + VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION cargo build --bin mm2 --profile ci - name: Login to dockerhub - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + if: github.event_name != 'pull_request' run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} docker.io - name: Build and push container image - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + if: github.event_name != 'pull_request' run: | CONTAINER_TAG="dev-$(git rev-parse --short=7 HEAD)" docker build -t komodoofficial/atomicdexapi:"$CONTAINER_TAG" -t komodoofficial/atomicdexapi:dev-latest -f Dockerfile.dev-release . @@ -55,7 +53,7 @@ jobs: - name: Build run: | - VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-Mac-CI" + VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION cargo build --bin mm2 --profile ci --target x86_64-apple-darwin @@ -72,7 +70,7 @@ jobs: - name: Build run: | - $VERSION=$Env:BRANCH_NAME+"_$(git rev-parse --short=7 HEAD)-Win-CI" + $VERSION="$(git rev-parse --short=7 HEAD)" if (test-path "./MM_VERSION") { remove-item "./MM_VERSION" } @@ -95,7 +93,7 @@ jobs: - name: Build run: | - VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-Linux-Wasm-CI" + VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION wasm-pack build mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ @@ -113,7 +111,7 @@ jobs: - name: Build run: | - VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-ios-aarch64-CI" + VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION cargo rustc --target aarch64-apple-ios --lib --profile ci --package mm2_bin_lib --crate-type=staticlib @@ -134,7 +132,7 @@ jobs: - name: Build run: | - VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-android-aarch64-CI" + VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION @@ -157,7 +155,7 @@ jobs: - name: Build run: | - VERSION=$BRANCH_NAME"_$(git rev-parse --short=7 HEAD)-android-armv7-CI" + VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION echo $VERSION > ./MM_VERSION @@ -165,7 +163,7 @@ jobs: CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --profile ci --crate-type=staticlib --package mm2_bin_lib deployment-commitment: - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + if: github.event_name != 'pull_request' needs: linux-x86-64 timeout-minutes: 15 runs-on: ubuntu-latest diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml new file mode 100644 index 0000000000..b5eacf04ef --- /dev/null +++ b/.github/workflows/release-build.yml @@ -0,0 +1,158 @@ +name: Release builds +on: + push: + branches: + - main + +env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + MANUAL_MM_VERSION: true + +jobs: + linux-x86-64: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Build + run: | + VERSION=$(git rev-parse --short=7 HEAD) + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + cargo build --bin mm2 --release + + - name: Login to dockerhub + run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} docker.io + + - name: Build and push container image + run: | + export CONTAINER_TAG=$(./target/release/mm2 --version | awk '{print $3}') + docker build -t komodoofficial/atomicdexapi:"$CONTAINER_TAG" -t komodoofficial/atomicdexapi:main-latest -f Dockerfile.release . + docker push komodoofficial/atomicdexapi:"$CONTAINER_TAG" + docker push komodoofficial/atomicdexapi:main-latest + + mac-x86-64: + timeout-minutes: 60 + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Build + run: | + VERSION=$(git rev-parse --short=7 HEAD) + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + cargo build --bin mm2 --release --target x86_64-apple-darwin + + win-x86-64: + timeout-minutes: 60 + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Build + run: | + $VERSION="$(git rev-parse --short=7 HEAD)" + if (test-path "./MM_VERSION") { + remove-item "./MM_VERSION" + } + echo $VERSION > ./MM_VERSION + cargo build --bin mm2 --release + + wasm: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + rustup target add wasm32-unknown-unknown + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build + run: | + VERSION=$(git rev-parse --short=7 HEAD) + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + wasm-pack build --release mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ + + ios-aarch64: + timeout-minutes: 60 + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + rustup target add aarch64-apple-ios + + - name: Build + run: | + VERSION=$(git rev-parse --short=7 HEAD) + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + cargo rustc --target aarch64-apple-ios --lib --release --package mm2_bin_lib --crate-type=staticlib + + android-aarch64: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + rustup target add aarch64-linux-android + + - name: Setup NDK + run: ./android-ndk.sh x86 21 + + - name: Build + run: | + VERSION=$(git rev-parse --short=7 HEAD) + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + + export PATH=$PATH:/android-ndk/bin + CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --release --crate-type=staticlib --package mm2_bin_lib + + android-armv7: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + rustup target add armv7-linux-androideabi + + - name: Setup NDK + run: ./android-ndk.sh x86 21 + + - name: Build + run: | + VERSION=$(git rev-parse --short=7 HEAD) + rm -f ./MM_VERSION + echo $VERSION > ./MM_VERSION + + export PATH=$PATH:/android-ndk/bin + CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --release --crate-type=staticlib --package mm2_bin_lib diff --git a/Dockerfile.release b/Dockerfile.release index 0ee77e1335..0b07d7f3a0 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,6 +1,5 @@ -FROM ubuntu:bionic +FROM debian:stable-slim WORKDIR /mm2 -COPY target-xenial/release/mm2 /app/mm2 +COPY target/release/mm2 /usr/local/bin/mm2 EXPOSE 7783 -ENV PATH="/app:${PATH}" -CMD mm2 \ No newline at end of file +CMD ["mm2"] \ No newline at end of file diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs index 85a471c7a5..42447ae35b 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs @@ -424,6 +424,7 @@ fn test_buy_maker_should_not_use_taker_confs_and_notas_for_maker_payment_if_take } #[test] +#[ignore] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 fn test_buy_taker_should_use_maker_confs_and_notas_for_taker_payment_if_maker_requires_less() { let maker_settings = OrderConfirmationsSettings { base_confs: 1, From 096d70059fe98c31b3a39fd4d1ba25fcbf5466d3 Mon Sep 17 00:00:00 2001 From: Onur Date: Thu, 16 Mar 2023 16:18:53 +0300 Subject: [PATCH 64/79] build: bump mm2 version (#1722) Signed-off-by: ozkanonur Reviewed-by: shamardy --- 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 25c8b88f06..69160476d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4139,7 +4139,7 @@ dependencies = [ [[package]] name = "mm2_bin_lib" -version = "1.0.0-beta" +version = "1.0.1-beta" dependencies = [ "chrono", "common", diff --git a/mm2src/mm2_bin_lib/Cargo.toml b/mm2src/mm2_bin_lib/Cargo.toml index c2788a80c3..6ab41e38a4 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.0-beta" +version = "1.0.1-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 78d330726607ade15a2e40f6ad24d5a33e353d61 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Fri, 17 Mar 2023 14:13:43 +0300 Subject: [PATCH 65/79] add changelog entries Signed-off-by: ozkanonur --- CHANGELOG.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e43f1ea1dc..dafa605416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - 2 issues discovered while executing a KMD/LNBTC swap were fixed in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655), these issues were: - When electrums were down, if a channel was closed, the channel closing transaction wasn't broadcasted. A check for a network error to rebroadcast the tx after a delay was added. - If an invoice payment failed, retring paying the same invoice would cause the payment to not be updated to successful in the DB even if it were successful. A method to update the payment in the DB was added to fix this. -- `mm2_git` crate was added to provide an abstraction layer on Git for doing query/parse operations over the repositories [#1636](https://github.com/KomodoPlatform/atomicDEX-API/pull/1636) + - `mm2_git` crate was added to provide an abstraction layer on Git for doing query/parse operations over the repositories [#1636](https://github.com/KomodoPlatform/atomicDEX-API/pull/1636) **Enhancements/Fixes:** - IndexedDB Cursor can now iterate over the items step-by-step [#1678](https://github.com/KomodoPlatform/atomicDEX-API/pull/1678) @@ -27,7 +27,19 @@ - Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) - A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. - +- CI/CD migrated from Azure to Github runners +- CI tests are much stabilized +- Integration and unit tests are seperated on CI stack +- Jemalloc configuration updated for optimization purposes +- Codebase is updated in linting rules at wasm and test stack +- `crossbeam` bumped to `0.8` from `0.7` +- Un-used/Unstable parts of mm2 excluded from build outputs which avoids mm2 + runtime from potential UB +- Build time optimizations applied such as sharing generics instead of + duplicating them in binary (which reduces output sizes) +- `RUSTSEC-2020-0036`, `RUSTSEC-2021-0139` and `RUSTSEC-2023-0018` resolved +- Enabled linting checks for wasm and test stack on CI +- Release container base image updated to debian stable from ubuntu bionic ## v1.0.0-beta - 2023-03-08 From 7d5f586a8b7caf7dbb102731595b1e37ab75ed57 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Mon, 20 Mar 2023 15:04:19 +0100 Subject: [PATCH 66/79] fix: save pubkeys with prefix (#1717) * use pubkeys with prefix Signed-off-by: borngraced * match start event separately Signed-off-by: borngraced * improve error msgs Signed-off-by: borngraced * return error if key is none Signed-off-by: borngraced * use SwapPubkeys::new() Signed-off-by: borngraced * remove SwapPubkeys::new() Signed-off-by: borngraced * update CHANGELOG.md Signed-off-by: borngraced --------- Signed-off-by: borngraced Reviewed-by: shamardy , smk --- CHANGELOG.md | 2 +- mm2src/mm2_main/src/lp_swap.rs | 5 ---- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 32 ++++++++++++++++------- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 32 ++++++++++++++++------- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e43f1ea1dc..0939eef592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ - Uuid lib was update from v0.7.4 to v1.2.2 in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) - A bug was fixed in [#1706](https://github.com/KomodoPlatform/atomicDEX-API/pull/1706) where EVM swap transactions were failing if sent before the approval transaction confirmation. - Tendermint account sequence problem due to running multiple instances were fixed in [#1694](https://github.com/KomodoPlatform/atomicDEX-API/pull/1694) -- Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) +- Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) and [#1717](https://github.com/KomodoPlatform/atomicDEX-API/pull/1717) - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) - A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index ba63b3e6de..fb64981592 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1455,11 +1455,6 @@ pub struct SwapPubkeys { pub taker: String, } -impl SwapPubkeys { - #[inline] - fn new(maker: String, taker: String) -> Self { SwapPubkeys { maker, taker } } -} - #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index d80fecc413..0cd8affc40 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -1926,19 +1926,31 @@ impl MakerSavedSwap { } } + // TODO: Adjust for private coins when/if they are braodcasted + // TODO: Adjust for HD wallet when completed pub fn swap_pubkeys(&self) -> Result { - match self.events.first() { + let maker = match &self.events.first() { Some(event) => match &event.event { - // TODO: Adjust for private coins when/if they are braodcasted - // TODO: Adjust for HD wallet when completed - MakerSwapEvent::Started(data) => Ok(SwapPubkeys::new( - data.my_persistent_pub.to_string(), - data.taker.to_string(), - )), - _ => ERR!("First swap event must be Started"), + MakerSwapEvent::Started(started) => started.my_persistent_pub.to_string(), + _ => return ERR!("First swap event must be Started"), }, - None => ERR!("Can't get maker/taker pubkey, events are empty"), - } + None => return ERR!("Can't get maker's pubkey while events are empty"), + }; + + let taker = match self.events.get(1) { + Some(event) => match &event.event { + MakerSwapEvent::Negotiated(neg) => { + let Some(key) = neg.taker_coin_htlc_pubkey else { + return ERR!("taker's pubkey is empty"); + }; + key.to_string() + }, + _ => return ERR!("Swap must be negotiated to get taker's pubkey"), + }, + None => return ERR!("Can't get taker's pubkey while there's no Negotiated event"), + }; + + Ok(SwapPubkeys { maker, taker }) } } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index e21cc22d13..f2bc7e0cb6 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -303,19 +303,31 @@ impl TakerSavedSwap { } } + // TODO: Adjust for private coins when/if they are braodcasted + // TODO: Adjust for HD wallet when completed pub fn swap_pubkeys(&self) -> Result { - match self.events.first() { + let taker = match &self.events.first() { Some(event) => match &event.event { - // TODO: Adjust for private coins when/if they are braodcasted - // TODO: Adjust for HD wallet when completed - TakerSwapEvent::Started(data) => Ok(SwapPubkeys::new( - data.maker.to_string(), - data.my_persistent_pub.to_string(), - )), - _ => ERR!("First swap event must be Started"), + TakerSwapEvent::Started(started) => started.my_persistent_pub.to_string(), + _ => return ERR!("First swap event must be Started"), }, - None => ERR!("Can't get maker/taker pubkey, events are empty"), - } + None => return ERR!("Can't get taker's pubkey while events are empty"), + }; + + let maker = match self.events.get(1) { + Some(event) => match &event.event { + TakerSwapEvent::Negotiated(neg) => { + let Some(key) = neg.maker_coin_htlc_pubkey else { + return ERR!("maker's pubkey is empty"); + }; + key.to_string() + }, + _ => return ERR!("Swap must be negotiated to get maker's pubkey"), + }, + None => return ERR!("Can't get maker's pubkey while there's no Negotiated event"), + }; + + Ok(SwapPubkeys { maker, taker }) } } From 789e75d07ecbd4cd17612cfe7ce8bb957a7368cd Mon Sep 17 00:00:00 2001 From: Alina Sharon <52405288+laruh@users.noreply.github.com> Date: Mon, 20 Mar 2023 21:25:20 +0700 Subject: [PATCH 67/79] fix: Expand OperationFailure::Other error (#1719) * expand Other in OperationFailure * use OperationFailure in TrezorError * wip * expand failure in HwError * match errors for HwRpcError, not internal type * add display * add entry in the changelog file * add PongMessageMismatch to HwRpcError --------- Reviewed-by: shamardy --- CHANGELOG.md | 1 + .../init_utxo_standard_activation_error.rs | 18 +++- mm2src/crypto/src/hw_error.rs | 82 ++++++++++++++++++- mm2src/trezor/src/client.rs | 5 +- mm2src/trezor/src/error.rs | 39 +++++++-- 5 files changed, 128 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0939eef592..55fe9d77bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) and [#1717](https://github.com/KomodoPlatform/atomicDEX-API/pull/1717) - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) - A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. +- `OperationFailure::Other` error was expanded. New error variants were matched with `HwRpcError`, so error type will be `HwError`, not `InternalError` [#1719](https://github.com/KomodoPlatform/atomicDEX-API/pull/1719) ## v1.0.0-beta - 2023-03-08 diff --git a/mm2src/coins_activation/src/utxo_activation/init_utxo_standard_activation_error.rs b/mm2src/coins_activation/src/utxo_activation/init_utxo_standard_activation_error.rs index d6a3497640..47894c2a6f 100644 --- a/mm2src/coins_activation/src/utxo_activation/init_utxo_standard_activation_error.rs +++ b/mm2src/coins_activation/src/utxo_activation/init_utxo_standard_activation_error.rs @@ -115,7 +115,23 @@ impl InitUtxoStandardError { HwError::CannotChooseDevice { .. } => InitUtxoStandardError::HwError(HwRpcError::FoundMultipleDevices), HwError::ConnectionTimedOut { timeout } => InitUtxoStandardError::TaskTimedOut { duration: timeout }, HwError::FoundUnexpectedDevice => InitUtxoStandardError::HwError(HwRpcError::FoundUnexpectedDevice), - HwError::Failure(error) => InitUtxoStandardError::CoinCreationError { ticker, error }, + HwError::InvalidPin + | HwError::UnexpectedMessage + | HwError::ButtonExpected + | HwError::DataError + | HwError::PinExpected + | HwError::InvalidSignature + | HwError::ProcessError + | HwError::NotEnoughFunds + | HwError::NotInitialized + | HwError::WipeCodeMismatch + | HwError::InvalidSession + | HwError::FirmwareError + | HwError::FailureMessageNotFound + | HwError::UserCancelled => InitUtxoStandardError::CoinCreationError { + ticker, + error: hw_error.to_string(), + }, other => InitUtxoStandardError::Internal(other.to_string()), } } diff --git a/mm2src/crypto/src/hw_error.rs b/mm2src/crypto/src/hw_error.rs index d9e7b03610..1efd4b243f 100644 --- a/mm2src/crypto/src/hw_error.rs +++ b/mm2src/crypto/src/hw_error.rs @@ -3,7 +3,7 @@ use hw_common::primitives::Bip32Error; use mm2_err_handle::prelude::*; use serde::Serialize; use std::time::Duration; -use trezor::{TrezorError, TrezorUserInteraction}; +use trezor::{OperationFailure, TrezorError, TrezorUserInteraction}; pub type HwResult = Result>; @@ -28,11 +28,25 @@ pub enum HwError { }, #[display(fmt = "Invalid xpub received from a device: '{}'", _0)] InvalidXpub(String), - Failure(String), UnderlyingError(String), ProtocolError(String), UnexpectedUserInteractionRequest(TrezorUserInteraction), Internal(String), + InvalidPin, + UnexpectedMessage, + ButtonExpected, + DataError, + PinExpected, + InvalidSignature, + ProcessError, + NotEnoughFunds, + NotInitialized, + WipeCodeMismatch, + InvalidSession, + FirmwareError, + FailureMessageNotFound, + UserCancelled, + PongMessageMismatch, } impl From for HwError { @@ -44,10 +58,25 @@ impl From for HwError { TrezorError::DeviceDisconnected => HwError::DeviceDisconnected, TrezorError::UnderlyingError(_) => HwError::UnderlyingError(error), TrezorError::ProtocolError(_) | TrezorError::UnexpectedMessageType(_) => HwError::Internal(error), - // TODO handle the failure correctly later - TrezorError::Failure(_) => HwError::Failure(error), + TrezorError::Failure(failure) => match failure { + OperationFailure::InvalidPin => HwError::InvalidPin, + OperationFailure::UnexpectedMessage => HwError::UnexpectedMessage, + OperationFailure::ButtonExpected => HwError::ButtonExpected, + OperationFailure::DataError => HwError::DataError, + OperationFailure::PinExpected => HwError::PinExpected, + OperationFailure::InvalidSignature => HwError::InvalidSignature, + OperationFailure::ProcessError => HwError::ProcessError, + OperationFailure::NotEnoughFunds => HwError::NotEnoughFunds, + OperationFailure::NotInitialized => HwError::NotInitialized, + OperationFailure::WipeCodeMismatch => HwError::WipeCodeMismatch, + OperationFailure::InvalidSession => HwError::InvalidSession, + OperationFailure::FirmwareError => HwError::FirmwareError, + OperationFailure::FailureMessageNotFound => HwError::FailureMessageNotFound, + OperationFailure::UserCancelled => HwError::UserCancelled, + }, TrezorError::UnexpectedInteractionRequest(req) => HwError::UnexpectedUserInteractionRequest(req), TrezorError::Internal(_) => HwError::Internal(error), + TrezorError::PongMessageMismatch => HwError::PongMessageMismatch, } } } @@ -69,6 +98,36 @@ pub enum HwRpcError { FoundMultipleDevices, #[display(fmt = "Found unexpected device. Please re-initialize Hardware wallet")] FoundUnexpectedDevice, + #[display(fmt = "Pin is invalid")] + InvalidPin, + #[display(fmt = "Unexpected message")] + UnexpectedMessage, + #[display(fmt = "Button expected")] + ButtonExpected, + #[display(fmt = "Got data error")] + DataError, + #[display(fmt = "Pin expected")] + PinExpected, + #[display(fmt = "Invalid signature")] + InvalidSignature, + #[display(fmt = "Got process error")] + ProcessError, + #[display(fmt = "Not enough funds")] + NotEnoughFunds, + #[display(fmt = "Not initialized")] + NotInitialized, + #[display(fmt = "Wipe code mismatch")] + WipeCodeMismatch, + #[display(fmt = "Invalid session")] + InvalidSession, + #[display(fmt = "Got firmware error")] + FirmwareError, + #[display(fmt = "Failure message not found")] + FailureMessageNotFound, + #[display(fmt = "User cancelled action")] + UserCancelled, + #[display(fmt = "PONG message mismatch after ping")] + PongMessageMismatch, } /// The trait is implemented for those error enumerations that have `HwRpcError` variant. @@ -90,6 +149,21 @@ where HwError::CannotChooseDevice { .. } => T::hw_rpc_error(HwRpcError::FoundMultipleDevices), HwError::ConnectionTimedOut { timeout } => T::timeout(timeout), HwError::FoundUnexpectedDevice => T::hw_rpc_error(HwRpcError::FoundUnexpectedDevice), + HwError::InvalidPin => T::hw_rpc_error(HwRpcError::InvalidPin), + HwError::UnexpectedMessage => T::hw_rpc_error(HwRpcError::UnexpectedMessage), + HwError::ButtonExpected => T::hw_rpc_error(HwRpcError::ButtonExpected), + HwError::DataError => T::hw_rpc_error(HwRpcError::DataError), + HwError::PinExpected => T::hw_rpc_error(HwRpcError::PinExpected), + HwError::InvalidSignature => T::hw_rpc_error(HwRpcError::InvalidSignature), + HwError::ProcessError => T::hw_rpc_error(HwRpcError::ProcessError), + HwError::NotEnoughFunds => T::hw_rpc_error(HwRpcError::NotEnoughFunds), + HwError::NotInitialized => T::hw_rpc_error(HwRpcError::NotInitialized), + HwError::WipeCodeMismatch => T::hw_rpc_error(HwRpcError::WipeCodeMismatch), + HwError::InvalidSession => T::hw_rpc_error(HwRpcError::InvalidSession), + HwError::FirmwareError => T::hw_rpc_error(HwRpcError::FirmwareError), + HwError::FailureMessageNotFound => T::hw_rpc_error(HwRpcError::FailureMessageNotFound), + HwError::UserCancelled => T::hw_rpc_error(HwRpcError::UserCancelled), + HwError::PongMessageMismatch => T::hw_rpc_error(HwRpcError::PongMessageMismatch), other => T::internal(other.to_string()), } } diff --git a/mm2src/trezor/src/client.rs b/mm2src/trezor/src/client.rs index cb4bcc3bf5..3e06023ad5 100644 --- a/mm2src/trezor/src/client.rs +++ b/mm2src/trezor/src/client.rs @@ -116,11 +116,10 @@ impl<'a> TrezorSession<'a> { }; let result_handler = ResultHandler::<()>::new(move |pong: proto_common::Success| { - if pong.message == Some(ping_message.clone()) { + if pong.message == Some(ping_message) { Ok(()) } else { - let error = format!("Expected '{ping_message}' PONG message, found: {:?}", pong.message); - MmError::err(TrezorError::Failure(OperationFailure::Other(error))) + MmError::err(TrezorError::PongMessageMismatch) } }); self.call(req, result_handler).await diff --git a/mm2src/trezor/src/error.rs b/mm2src/trezor/src/error.rs index dfcf5303ba..60397e55a1 100644 --- a/mm2src/trezor/src/error.rs +++ b/mm2src/trezor/src/error.rs @@ -32,14 +32,25 @@ pub enum TrezorError { #[display(fmt = "Unexpected interaction request: {:?}", _0)] UnexpectedInteractionRequest(TrezorUserInteraction), Internal(String), + PongMessageMismatch, } -#[derive(Debug, Display)] +#[derive(Clone, Debug, Display)] pub enum OperationFailure { InvalidPin, - /// TODO expand it to other types. - #[display(fmt = "Operation failed due to unknown reason: {}", _0)] - Other(String), + UnexpectedMessage, + ButtonExpected, + DataError, + PinExpected, + InvalidSignature, + ProcessError, + NotEnoughFunds, + NotInitialized, + WipeCodeMismatch, + InvalidSession, + FirmwareError, + FailureMessageNotFound, + UserCancelled, } impl From for OperationFailure { @@ -48,15 +59,25 @@ impl From for OperationFailure { Some(FailureType::FailurePinInvalid) | Some(FailureType::FailurePinMismatch) => { OperationFailure::InvalidPin }, - _ => OperationFailure::Other(format!("{:?}", failure)), + Some(FailureType::FailureActionCancelled) | Some(FailureType::FailurePinCancelled) => { + OperationFailure::UserCancelled + }, + Some(FailureType::FailureUnexpectedMessage) => OperationFailure::UnexpectedMessage, + Some(FailureType::FailureButtonExpected) => OperationFailure::ButtonExpected, + Some(FailureType::FailureDataError) => OperationFailure::DataError, + Some(FailureType::FailurePinExpected) => OperationFailure::PinExpected, + Some(FailureType::FailureInvalidSignature) => OperationFailure::InvalidSignature, + Some(FailureType::FailureProcessError) => OperationFailure::ProcessError, + Some(FailureType::FailureNotEnoughFunds) => OperationFailure::NotEnoughFunds, + Some(FailureType::FailureNotInitialized) => OperationFailure::NotInitialized, + Some(FailureType::FailureWipeCodeMismatch) => OperationFailure::WipeCodeMismatch, + Some(FailureType::FailureInvalidSession) => OperationFailure::InvalidSession, + Some(FailureType::FailureFirmwareError) => OperationFailure::FirmwareError, + None => OperationFailure::FailureMessageNotFound, } } } -impl From for TrezorError { - fn from(failure: OperationFailure) -> Self { TrezorError::Failure(failure) } -} - impl From for TrezorError { fn from(e: DecodeError) -> Self { TrezorError::ProtocolError(e.to_string()) } } From 6c706ce057272b63de202f8a8d1d07ba6171941e Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Mon, 20 Mar 2023 19:47:27 +0300 Subject: [PATCH 68/79] test Signed-off-by: ozkanonur --- .github/workflows/dev-build.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index f1cb7166db..51b8f33e9d 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -29,6 +29,20 @@ jobs: echo $VERSION > ./MM_VERSION cargo build --bin mm2 --profile ci + + - name: Activate SSH + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.FILE_SERVER_SSH }} + + - name: Upload build output + run: | + NAME="mm2_$(git rev-parse --short=7 HEAD)-Linux-CI.zip" + zip $NAME target/ci/mm2 -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + scp -r ${{ secrets.FILE_SERVER_SCP }} ./$BRANCH_NAME + - name: Login to dockerhub if: github.event_name != 'pull_request' run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} docker.io From d084b629f8f3858884550a5682ae88d64a581b9b Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 21 Mar 2023 01:55:19 +0300 Subject: [PATCH 69/79] implement build uploads from CI Signed-off-by: ozkanonur --- .github/workflows/dev-build.yml | 220 ++++++++++++++++++++++++---- .github/workflows/release-build.yml | 201 +++++++++++++++++++++++-- 2 files changed, 379 insertions(+), 42 deletions(-) diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 51b8f33e9d..b79d2acb9f 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -22,26 +22,36 @@ jobs: rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION cargo build --bin mm2 --profile ci - - - name: Activate SSH - uses: webfactory/ssh-agent@v0.5.4 - with: - ssh-private-key: ${{ secrets.FILE_SERVER_SSH }} - - - name: Upload build output + - name: Compress build output run: | - NAME="mm2_$(git rev-parse --short=7 HEAD)-Linux-CI.zip" + NAME="mm2_$COMMIT_HASH-linux-x86-64.zip" zip $NAME target/ci/mm2 -j mkdir $BRANCH_NAME mv $NAME ./$BRANCH_NAME/ - scp -r ${{ secrets.FILE_SERVER_SCP }} ./$BRANCH_NAME + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" - name: Login to dockerhub if: github.event_name != 'pull_request' @@ -50,7 +60,7 @@ jobs: - name: Build and push container image if: github.event_name != 'pull_request' run: | - CONTAINER_TAG="dev-$(git rev-parse --short=7 HEAD)" + CONTAINER_TAG="dev-$COMMIT_HASH" docker build -t komodoofficial/atomicdexapi:"$CONTAINER_TAG" -t komodoofficial/atomicdexapi:dev-latest -f Dockerfile.dev-release . docker push komodoofficial/atomicdexapi:"$CONTAINER_TAG" docker push komodoofficial/atomicdexapi:dev-latest @@ -65,13 +75,37 @@ jobs: rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION cargo build --bin mm2 --profile ci --target x86_64-apple-darwin + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-mac-x86-64.zip" + zip $NAME target/x86_64-apple-darwin/ci/mm2 -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + win-x86-64: timeout-minutes: 30 runs-on: windows-latest @@ -82,15 +116,39 @@ jobs: rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $Env:GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $Env:GITHUB_ENV + - name: Build run: | - $VERSION="$(git rev-parse --short=7 HEAD)" if (test-path "./MM_VERSION") { remove-item "./MM_VERSION" } - echo $VERSION > ./MM_VERSION + echo $Env:COMMIT_HASH > ./MM_VERSION cargo build --bin mm2 --profile ci + - name: Compress build output + run: | + $NAME="mm2_$Env:COMMIT_HASH-win-x86-64.zip" + 7z a $NAME .\target\ci\mm2.exe .\target\ci\*.dll + mkdir $Env:BRANCH_NAME + mv $NAME ./$Env:BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + wasm: timeout-minutes: 30 runs-on: ubuntu-latest @@ -105,12 +163,36 @@ jobs: - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION - wasm-pack build mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ + echo $COMMIT_HASH > ./MM_VERSION + wasm-pack build mm2src/mm2_bin_lib --target web --out-dir ../../target/target-wasm-release + + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-wasm.zip" + zip $NAME ./target/target-wasm-release/mm2lib_bg.wasm ./target/target-wasm-release/mm2lib.js ./target/target-wasm-release/snippets -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" ios-aarch64: timeout-minutes: 30 @@ -123,13 +205,38 @@ jobs: rustup default nightly-2022-10-29 rustup target add aarch64-apple-ios + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION cargo rustc --target aarch64-apple-ios --lib --profile ci --package mm2_bin_lib --crate-type=staticlib + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-ios-aarch64.zip" + mv target/aarch64-apple-ios/ci/libmm2lib.a target/aarch64-apple-ios/ci/libmm2.a + zip $NAME target/aarch64-apple-ios/ci/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + android-aarch64: timeout-minutes: 30 runs-on: ubuntu-latest @@ -144,15 +251,40 @@ jobs: - name: Setup NDK run: ./android-ndk.sh x86 21 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION export PATH=$PATH:/android-ndk/bin CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --profile ci --crate-type=staticlib --package mm2_bin_lib + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-android-aarch64.zip" + mv target/aarch64-linux-android/ci/libmm2lib.a target/aarch64-linux-android/ci/libmm2.a + zip $NAME target/aarch64-linux-android/ci/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + android-armv7: timeout-minutes: 30 runs-on: ubuntu-latest @@ -167,15 +299,41 @@ jobs: - name: Setup NDK run: ./android-ndk.sh x86 21 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION export PATH=$PATH:/android-ndk/bin CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --profile ci --crate-type=staticlib --package mm2_bin_lib + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-android-armv7.zip" + mv target/armv7-linux-androideabi/ci/libmm2lib.a target/armv7-linux-androideabi/ci/libmm2.a + zip $NAME target/armv7-linux-androideabi/ci/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + + deployment-commitment: if: github.event_name != 'pull_request' needs: linux-x86-64 @@ -184,6 +342,14 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Activate SSH uses: webfactory/ssh-agent@v0.5.4 with: @@ -194,10 +360,10 @@ jobs: git clone git@github.com:KomodoPlatform/atomicdex-deployments.git if [ -d "atomicdex-deployments/atomicDEX-API" ]; then cd atomicdex-deployments/atomicDEX-API - sed -i "1s/^.*$/${GITHUB_SHA::9}/" .commit + sed -i "1s/^.*$/$COMMIT_HASH/" .commit git config --global user.email "linuxci@komodoplatform.com" git config --global user.name "linuxci" git add .commit - git commit -m "[atomicDEX-API] ${GITHUB_SHA::9} is committed for git & container registry" + git commit -m "[atomicDEX-API] $COMMIT_HASH is committed for git & container registry" git push fi diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index b5eacf04ef..8ea5ad6102 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -19,13 +19,37 @@ jobs: rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION cargo build --bin mm2 --release + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-linux-x86-64.zip" + zip $NAME target/release/mm2 -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + - name: Login to dockerhub run: docker login --username ${{ secrets.DOCKER_HUB_USERNAME }} --password ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} docker.io @@ -46,12 +70,36 @@ jobs: rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION cargo build --bin mm2 --release --target x86_64-apple-darwin + + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-mac-x86-64.zip" + zip $NAME target/x86_64-apple-darwin/release/mm2 -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" win-x86-64: timeout-minutes: 60 @@ -63,14 +111,38 @@ jobs: rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal rustup default nightly-2022-10-29 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $Env:GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $Env:GITHUB_ENV + - name: Build run: | - $VERSION="$(git rev-parse --short=7 HEAD)" if (test-path "./MM_VERSION") { remove-item "./MM_VERSION" } - echo $VERSION > ./MM_VERSION + echo $Env:COMMIT_HASH > ./MM_VERSION cargo build --bin mm2 --release + + - name: Compress build output + run: | + $NAME="mm2_$Env:COMMIT_HASH-win-x86-64.zip" + 7z a $NAME .\target\release\mm2.exe .\target\release\*.dll + mkdir $Env:BRANCH_NAME + mv $NAME ./$Env:BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" wasm: timeout-minutes: 60 @@ -86,12 +158,36 @@ jobs: - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION - wasm-pack build --release mm2src/mm2_bin_lib --target web --out-dir wasm_build/deps/pkg/ + echo $COMMIT_HASH > ./MM_VERSION + wasm-pack build --release mm2src/mm2_bin_lib --target web --out-dir ../../target/target-wasm-release + + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-wasm.zip" + zip $NAME ./target/target-wasm-release/mm2lib_bg.wasm ./target/target-wasm-release/mm2lib.js ./target/target-wasm-release/snippets -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" ios-aarch64: timeout-minutes: 60 @@ -104,13 +200,38 @@ jobs: rustup default nightly-2022-10-29 rustup target add aarch64-apple-ios + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION cargo rustc --target aarch64-apple-ios --lib --release --package mm2_bin_lib --crate-type=staticlib + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-ios-aarch64.zip" + mv target/aarch64-apple-ios/release/libmm2lib.a target/aarch64-apple-ios/release/libmm2.a + zip $NAME target/aarch64-apple-ios/release/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + android-aarch64: timeout-minutes: 60 runs-on: ubuntu-latest @@ -125,15 +246,40 @@ jobs: - name: Setup NDK run: ./android-ndk.sh x86 21 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION export PATH=$PATH:/android-ndk/bin CC_aarch64_linux_android=aarch64-linux-android21-clang CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android21-clang cargo rustc --target=aarch64-linux-android --lib --release --crate-type=staticlib --package mm2_bin_lib + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-android-aarch64.zip" + mv target/aarch64-linux-android/release/libmm2lib.a target/aarch64-linux-android/release/libmm2.a + zip $NAME target/aarch64-linux-android/release/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + android-armv7: timeout-minutes: 60 runs-on: ubuntu-latest @@ -148,11 +294,36 @@ jobs: - name: Setup NDK run: ./android-ndk.sh x86 21 + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + - name: Build run: | - VERSION=$(git rev-parse --short=7 HEAD) rm -f ./MM_VERSION - echo $VERSION > ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION export PATH=$PATH:/android-ndk/bin CC_armv7_linux_androideabi=armv7a-linux-androideabi21-clang CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER=armv7a-linux-androideabi21-clang cargo rustc --target=armv7-linux-androideabi --lib --release --crate-type=staticlib --package mm2_bin_lib + + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-android-armv7.zip" + mv target/armv7-linux-androideabi/release/libmm2lib.a target/armv7-linux-androideabi/release/libmm2.a + zip $NAME target/armv7-linux-androideabi/release/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" \ No newline at end of file From 72e9819717fab695bf32a7e8a5029b205318b19d Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 21 Mar 2023 11:31:21 +0300 Subject: [PATCH 70/79] build dylib for x86-64 mac Signed-off-by: ozkanonur --- .github/workflows/dev-build.yml | 42 +++++++++++++++++++++++++++++ .github/workflows/release-build.yml | 42 +++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index b79d2acb9f..7bbb5f5f92 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -149,6 +149,48 @@ jobs: local: ${{ env.BRANCH_NAME }} remote: "/uploads/${{ env.BRANCH_NAME }}" + mac-dylib-x86-64: + timeout-minutes: 30 + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + + - name: Build + run: | + rm -f ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION + cargo rustc --target x86_64-apple-darwin --lib --profile ci --package mm2_bin_lib --crate-type=staticlib + + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-mac-dylib-x86-64.zip" + mv target/x86_64-apple-darwin/ci/libmm2lib.a target/x86_64-apple-darwin/ci/libmm2.a + zip $NAME target/x86_64-apple-darwin/ci/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + wasm: timeout-minutes: 30 runs-on: ubuntu-latest diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 8ea5ad6102..21820340a1 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -144,6 +144,48 @@ jobs: local: ${{ env.BRANCH_NAME }} remote: "/uploads/${{ env.BRANCH_NAME }}" + mac-dylib-x86-64: + timeout-minutes: 60 + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install toolchain + run: | + rustup toolchain install nightly-2022-10-29 --no-self-update --profile=minimal + rustup default nightly-2022-10-29 + + - name: Calculate commit hash for PR commit + if: github.event_name == 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 ${{ github.event.pull_request.head.sha }})" >> $GITHUB_ENV + + - name: Calculate commit hash for merge commit + if: github.event_name != 'pull_request' + run: echo "COMMIT_HASH=$(git rev-parse --short=7 HEAD)" >> $GITHUB_ENV + + - name: Build + run: | + rm -f ./MM_VERSION + echo $COMMIT_HASH > ./MM_VERSION + cargo rustc --target x86_64-apple-darwin --lib --release --package mm2_bin_lib --crate-type=staticlib + + - name: Compress build output + run: | + NAME="mm2_$COMMIT_HASH-mac-dylib-x86-64.zip" + mv target/x86_64-apple-darwin/release/libmm2lib.a target/x86_64-apple-darwin/release/libmm2.a + zip $NAME target/x86_64-apple-darwin/release/libmm2.a -j + mkdir $BRANCH_NAME + mv $NAME ./$BRANCH_NAME/ + + - name: Upload output + uses: garygrossgarten/github-action-scp@release + with: + host: ${{ secrets.FILE_SERVER_HOST }} + username: ${{ secrets.FILE_SERVER_USERNAME }} + port: ${{ secrets.FILE_SERVER_PORT }} + privateKey: ${{ secrets.FILE_SERVER_KEY }} + local: ${{ env.BRANCH_NAME }} + remote: "/uploads/${{ env.BRANCH_NAME }}" + wasm: timeout-minutes: 60 runs-on: ubuntu-latest From e71e2cf087b516b92a809629f6f331e6fbb441d9 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Tue, 21 Mar 2023 12:27:04 +0300 Subject: [PATCH 71/79] statically link libusb Signed-off-by: ozkanonur --- mm2src/hw_common/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/hw_common/Cargo.toml b/mm2src/hw_common/Cargo.toml index 06e9383b73..33e3a4e27a 100644 --- a/mm2src/hw_common/Cargo.toml +++ b/mm2src/hw_common/Cargo.toml @@ -18,7 +18,7 @@ serde = "1.0" serde_derive = "1.0" [target.'cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))'.dependencies] -rusb = { version = "0.7.0" } +rusb = { version = "0.7.0", features = ["vendored"] } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = { version = "0.3.27" } From b0cc19eadf60ff3f4f979c60bcd681d1a0eae286 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Tue, 21 Mar 2023 19:43:02 +0200 Subject: [PATCH 72/79] fix: reduce evm wait_for_confirmations calls, fix endless loop in wait_for_htlc_tx_spend (#1724) * wait for confirmation of evm swap payment state initialization * remove unneeded todo related to checking payment state for QRC20 * remove another todo that is not needed * fix test_tx_history_tbtc_non_segwit * remove wait_for_payment_state_initialization check * reduce eth wait_for_confirmations calls * fix another endless loop issue introduced by the new code * log confirmation by adding status.append(" Confirmed.") * add entry for #1724 in change log * fix confirmation_block_number to be the right block number and add checks for overflow --------- Reviewed-by: cipig, laruh, caglaryucekaya --- CHANGELOG.md | 2 + mm2src/coins/coin_errors.rs | 4 +- mm2src/coins/eth.rs | 307 +++++++++++------- mm2src/coins/lightning.rs | 39 +-- mm2src/coins/lp_coins.rs | 18 +- mm2src/coins/qrc20.rs | 25 +- mm2src/coins/qrc20/qrc20_tests.rs | 35 +- mm2src/coins/solana.rs | 30 +- mm2src/coins/solana/spl.rs | 19 +- mm2src/coins/tendermint/tendermint_coin.rs | 61 ++-- mm2src/coins/tendermint/tendermint_token.rs | 34 +- mm2src/coins/test_coin.rs | 13 +- mm2src/coins/utxo/bch.rs | 36 +- mm2src/coins/utxo/qtum.rs | 37 +-- mm2src/coins/utxo/slp.rs | 35 +- mm2src/coins/utxo/utxo_common.rs | 20 +- mm2src/coins/utxo/utxo_standard.rs | 30 +- mm2src/coins/utxo/utxo_tests.rs | 15 +- mm2src/coins/z_coin.rs | 15 +- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 49 +-- mm2src/mm2_main/src/lp_swap/swap_watcher.rs | 20 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 36 +- .../tests/docker_tests/docker_tests_common.rs | 48 ++- .../tests/docker_tests/docker_tests_inner.rs | 116 +++++-- .../tests/docker_tests/qrc20_tests.rs | 225 +++++++++---- .../tests/docker_tests/swap_watcher_tests.rs | 129 +++++--- .../tests/mm2_tests/mm2_tests_inner.rs | 2 + 27 files changed, 799 insertions(+), 601 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fe9d77bd..b0bd0f2b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) - A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. - `OperationFailure::Other` error was expanded. New error variants were matched with `HwRpcError`, so error type will be `HwError`, not `InternalError` [#1719](https://github.com/KomodoPlatform/atomicDEX-API/pull/1719) +- RPC calls for evm chains was reduced in `wait_for_confirmations` function in [#1724](https://github.com/KomodoPlatform/atomicDEX-API/pull/1724) +- A possible endless loop in evm `wait_for_htlc_tx_spend` was fixed in [#1724](https://github.com/KomodoPlatform/atomicDEX-API/pull/1724) ## v1.0.0-beta - 2023-03-08 diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index a296f68adb..648e3428b4 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -60,7 +60,9 @@ impl From for ValidatePaymentError { match e { Web3RpcError::Transport(tr) => ValidatePaymentError::Transport(tr), Web3RpcError::InvalidResponse(resp) => ValidatePaymentError::InvalidRpcResponse(resp), - Web3RpcError::Internal(internal) => ValidatePaymentError::InternalError(internal), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { + ValidatePaymentError::InternalError(internal) + }, } } } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index ddb940e331..0db1807086 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -75,22 +75,22 @@ cfg_wasm32! { } use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, - CoinProtocol, CoinTransportMetrics, CoinsContext, EthValidateFeeArgs, FeeApproxStage, FoundSwapTxSpend, - HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, - NegotiateSwapContractAddrErr, NumConversError, NumConversResult, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, - RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, - RefundPaymentArgs, RefundResult, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, - SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, - TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG, - INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, - INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; + CoinProtocol, CoinTransportMetrics, CoinsContext, ConfirmPaymentInput, EthValidateFeeArgs, FeeApproxStage, + FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, + MyAddressError, MyWalletAddress, NegotiateSwapContractAddrErr, NumConversError, NumConversResult, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, + RefundError, RefundPaymentArgs, RefundResult, RpcClientType, RpcTransportEventHandler, + RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, + TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, + EARLY_CONFIRMATION_ERR_LOG, INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, + INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; pub use rlp; #[cfg(test)] mod eth_tests; @@ -100,7 +100,6 @@ mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; #[cfg(feature = "enable-nft-integration")] use crate::nft::WithdrawNftResult; -use crate::MyWalletAddress; #[cfg(feature = "enable-nft-integration")] use crate::{lp_coinfind_or_err, MmCoinEnum, TransactionType}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; @@ -197,6 +196,8 @@ pub enum Web3RpcError { Transport(String), #[display(fmt = "Invalid response: {}", _0)] InvalidResponse(String), + #[display(fmt = "Timeout: {}", _0)] + Timeout(String), #[display(fmt = "Internal: {}", _0)] Internal(String), } @@ -238,7 +239,9 @@ impl From for RawTransactionError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => RawTransactionError::Transport(tr), - Web3RpcError::Internal(internal) => RawTransactionError::InternalError(internal), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { + RawTransactionError::InternalError(internal) + }, } } } @@ -277,7 +280,9 @@ impl From for WithdrawError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(err) | Web3RpcError::InvalidResponse(err) => WithdrawError::Transport(err), - Web3RpcError::Internal(internal) => WithdrawError::InternalError(internal), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { + WithdrawError::InternalError(internal) + }, } } } @@ -290,7 +295,9 @@ impl From for TradePreimageError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(err) | Web3RpcError::InvalidResponse(err) => TradePreimageError::Transport(err), - Web3RpcError::Internal(internal) => TradePreimageError::InternalError(internal), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => { + TradePreimageError::InternalError(internal) + }, } } } @@ -319,7 +326,7 @@ impl From for BalanceError { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => BalanceError::Transport(tr), - Web3RpcError::Internal(internal) => BalanceError::Internal(internal), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => BalanceError::Internal(internal), } } } @@ -1726,114 +1733,90 @@ impl MarketCoinOps for EthCoin { ) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - _requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + macro_rules! update_status_with_error { + ($status: ident, $error: ident) => { + match $error.get_inner() { + Web3RpcError::Timeout(_) => $status.append(" Timed out."), + _ => $status.append(" Failed."), + } + }; + } + let ctx = try_fus!(MmArc::from_weak(&self.ctx).ok_or("No context")); let mut status = ctx.log.status_handle(); status.status(&[&self.ticker], "Waiting for confirmations…"); - status.deadline(wait_until * 1000); + status.deadline(input.wait_until * 1000); - let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(tx)); + let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(&input.payment_tx)); let tx = try_fus!(SignedEthTx::new(unsigned)); + let tx_hash = tx.hash(); - let required_confirms = U64::from(confirmations); + let required_confirms = U64::from(input.confirmations); + let check_every = input.check_every as f64; let selfi = self.clone(); let fut = async move { loop { - if status.ms2deadline().unwrap() < 0 { - status.append(" Timed out."); - return ERR!( - "Waited too long until {} for transaction {:?} confirmation ", - wait_until, - tx - ); - } - - let web3_receipt = match selfi.web3.eth().transaction_receipt(tx.hash()).await { - Ok(r) => r, + // Wait for one confirmation and return the transaction confirmation block number + let confirmed_at = match selfi + .transaction_confirmed_at(tx_hash, input.wait_until, check_every) + .compat() + .await + { + Ok(c) => c, Err(e) => { - error!( - "Error {:?} getting the {} transaction {:?}, retrying in 15 seconds", - e, - selfi.ticker(), - tx.tx_hash() - ); - Timer::sleep(check_every as f64).await; - continue; + update_status_with_error!(status, e); + return Err(e.to_string()); }, }; - if let Some(receipt) = web3_receipt { - if receipt.status != Some(1.into()) { - status.append(" Failed."); - return ERR!( - "Tx receipt {:?} status of {} tx {:?} is failed", - receipt, - selfi.ticker(), - tx.tx_hash() - ); - } - if let Some(confirmed_at) = receipt.block_number { - let current_block = match selfi.web3.eth().block_number().await { - Ok(b) => b, - Err(e) => { - error!( - "Error {:?} getting the {} block number retrying in 15 seconds", - e, - selfi.ticker() - ); - Timer::sleep(check_every as f64).await; - continue; - }, - }; - // checking if the current block is above the confirmed_at block prediction for pos chain to prevent overflow - if current_block >= confirmed_at && current_block - confirmed_at + 1 >= required_confirms { - loop { - if status.ms2deadline().unwrap() < 0 { - status.append(" Timed out."); - return ERR!( - "Waited too long until {} for transaction {:?} confirmation ", - wait_until, - tx - ); - } - - // Make sure that the transaction is returned by eth_getTransactionByHash too so that swaps don't fail at payment validation - // https://github.com/KomodoPlatform/atomicDEX-API/issues/1630#issuecomment-1401736168 - match selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).await { - Ok(Some(_)) => { - status.append(" Confirmed."); - return Ok(()); - }, - Ok(None) => error!( - "Didn't find tx: {:02x} for coin: {} on RPC node using eth_getTransactionByHash. Retrying in {} seconds", - tx.hash, - selfi.ticker(), - check_every - ), - Err(e) => error!( - "Error {} calling eth_getTransactionByHash for coin: {}, tx: {:02x}. Retrying in {} seconds", - e, - selfi.ticker(), - tx.hash, - check_every - ), - } - - Timer::sleep(check_every as f64).await; - } + // checking that confirmed_at is greater than zero to prevent overflow. + // untrusted RPC nodes might send a zero value to cause overflow if we didn't do this check. + // required_confirms should always be more than 0 anyways but we should keep this check nonetheless. + if confirmed_at <= U64::from(0) { + error!( + "confirmed_at: {}, for payment tx: {:02x}, for coin:{} should be greater than zero!", + confirmed_at, + tx_hash, + selfi.ticker() + ); + Timer::sleep(check_every).await; + continue; + } + + // Wait for a block that achieves the required confirmations + let confirmation_block_number = confirmed_at + required_confirms - 1; + if let Err(e) = selfi + .wait_for_block(confirmation_block_number, input.wait_until, check_every) + .compat() + .await + { + update_status_with_error!(status, e); + return Err(e.to_string()); + } + + // Make sure that there was no chain reorganization that led to transaction confirmation block to be changed + match selfi + .transaction_confirmed_at(tx_hash, input.wait_until, check_every) + .compat() + .await + { + Ok(conf) => { + if conf == confirmed_at { + status.append(" Confirmed."); + break Ok(()); } - } + }, + Err(e) => { + update_status_with_error!(status, e); + return Err(e.to_string()); + }, } - Timer::sleep(check_every as f64).await; + + Timer::sleep(check_every).await; } }; + Box::new(fut.boxed().compat()) } @@ -1881,6 +1864,14 @@ impl MarketCoinOps for EthCoin { let fut = async move { loop { + if now_ms() / 1000 > wait_until { + return TX_PLAIN_ERR!( + "Waited too long until {} for transaction {:?} to be spent ", + wait_until, + tx, + ); + } + let current_block = match selfi.current_block().compat().await { Ok(b) => b, Err(e) => { @@ -1925,15 +1916,7 @@ impl MarketCoinOps for EthCoin { } } - if now_ms() / 1000 > wait_until { - return TX_PLAIN_ERR!( - "Waited too long until {} for transaction {:?} to be spent ", - wait_until, - tx, - ); - } Timer::sleep(5.).await; - continue; } }; Box::new(fut.boxed().compat()) @@ -3563,7 +3546,7 @@ impl EthCoin { let fut = async move { loop { if now_ms() / 1000 > wait_until { - return MmError::err(Web3RpcError::Internal(ERRL!( + return MmError::err(Web3RpcError::Timeout(ERRL!( "Waited too long until {} for allowance to be updated to at least {}", wait_until, required_allowance @@ -4062,6 +4045,88 @@ impl EthCoin { ); Ok(None) } + + fn transaction_confirmed_at(&self, payment_hash: H256, wait_until: u64, check_every: f64) -> Web3RpcFut { + let selfi = self.clone(); + let fut = async move { + loop { + if now_ms() / 1000 > wait_until { + return MmError::err(Web3RpcError::Timeout(ERRL!( + "Waited too long until {} for payment tx: {:02x}, for coin:{}, to be confirmed!", + wait_until, + payment_hash, + selfi.ticker() + ))); + } + + let web3_receipt = match selfi.web3.eth().transaction_receipt(payment_hash).await { + Ok(r) => r, + Err(e) => { + error!( + "Error {:?} getting the {} transaction {:?}, retrying in 15 seconds", + e, + selfi.ticker(), + payment_hash + ); + Timer::sleep(check_every).await; + continue; + }, + }; + + if let Some(receipt) = web3_receipt { + if receipt.status != Some(1.into()) { + return MmError::err(Web3RpcError::Internal(ERRL!( + "Tx receipt {:?} status of {} tx {:?} is failed", + receipt, + selfi.ticker(), + payment_hash + ))); + } + + if let Some(confirmed_at) = receipt.block_number { + break Ok(confirmed_at); + } + } + + Timer::sleep(check_every).await; + } + }; + Box::new(fut.boxed().compat()) + } + + fn wait_for_block(&self, block_number: U64, wait_until: u64, check_every: f64) -> Web3RpcFut<()> { + let selfi = self.clone(); + let fut = async move { + loop { + if now_ms() / 1000 > wait_until { + return MmError::err(Web3RpcError::Timeout(ERRL!( + "Waited too long until {} for block number: {:02x} to appear on-chain, for coin:{}", + wait_until, + block_number, + selfi.ticker() + ))); + } + + match selfi.web3.eth().block_number().await { + Ok(current_block) => { + if current_block >= block_number { + break Ok(()); + } + }, + Err(e) => { + error!( + "Error {:?} getting the {} block number retrying in 15 seconds", + e, + selfi.ticker() + ); + }, + }; + + Timer::sleep(check_every).await; + } + }; + Box::new(fut.boxed().compat()) + } } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index a134d0bc53..f5ca99c625 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -15,17 +15,17 @@ 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, FeeApproxStage, FoundSwapTxSpend, - HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, - PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, - SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - Transaction, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, - UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest}; +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, + FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, + TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, TransactionFut, + TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::bech32::ToBase32; use bitcoin::hashes::Hash; @@ -1071,24 +1071,17 @@ impl MarketCoinOps for LightningCoin { // Todo: Add waiting for confirmations logic for the case of if the channel is closed and the htlc can be claimed on-chain // Todo: The above is postponed and might not be needed after this issue is resolved https://github.com/lightningdevkit/rust-lightning/issues/2017 - fn wait_for_confirmations( - &self, - tx: &[u8], - _confirmations: u64, - _requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - let payment_hash = try_f!(payment_hash_from_slice(tx).map_err(|e| e.to_string())); + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + let payment_hash = try_f!(payment_hash_from_slice(&input.payment_tx).map_err(|e| e.to_string())); let payment_hex = hex::encode(payment_hash.0); let coin = self.clone(); let fut = async move { loop { - if now_ms() / 1000 > wait_until { + if now_ms() / 1000 > input.wait_until { return ERR!( "Waited too long until {} for payment {} to be received", - wait_until, + input.wait_until, payment_hex ); } @@ -1124,7 +1117,7 @@ impl MarketCoinOps for LightningCoin { // note: When sleeping for only 1 second the test_send_payment_and_swaps unit test took 20 seconds to complete instead of 37 seconds when WAIT_CONFIRM_INTERVAL (15 seconds) is used // Todo: In next sprints, should add a mutex for lightning swap payments to avoid overloading the shared db connection with requests when the sleep time is reduced and multiple swaps are ran together // Todo: The aim is to make lightning swap payments as fast as possible. Running swap payments statuses should be loaded from db on restarts in this case. - Timer::sleep(check_every as f64).await; + Timer::sleep(input.check_every as f64).await; } }; Box::new(fut.boxed().compat()) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index da239d3550..f4dbd94905 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -543,6 +543,15 @@ pub enum ValidateOtherPubKeyErr { InvalidPubKey(String), } +#[derive(Clone, Debug)] +pub struct ConfirmPaymentInput { + pub payment_tx: Vec, + pub confirmations: u64, + pub requires_nota: bool, + pub wait_until: u64, + pub check_every: u64, +} + #[derive(Clone, Debug)] pub struct WatcherValidateTakerFeeInput { pub taker_fee_hash: Vec, @@ -941,14 +950,7 @@ pub trait MarketCoinOps { /// Receives raw transaction bytes as input and returns tx hash in hexadecimal format fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send>; - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send>; + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send>; fn wait_for_htlc_tx_spend( &self, diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index e8bf41228a..54219400cf 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -15,9 +15,9 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, Broadca GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, RecentlySpentOutPointsGuard, UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, UtxoFromLegacyReqErr, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom, UTXO_LOCK}; -use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, - FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, - NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, +use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, + FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, + MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, @@ -1220,19 +1220,18 @@ impl MarketCoinOps for Qrc20Coin { utxo_common::send_raw_tx_bytes(&self.utxo, tx) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - let tx: UtxoTx = try_fus!(deserialize(tx).map_err(|e| ERRL!("{:?}", e))); + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + let tx: UtxoTx = try_fus!(deserialize(input.payment_tx.as_slice()).map_err(|e| ERRL!("{:?}", e))); let selfi = self.clone(); let fut = async move { selfi - .wait_for_confirmations_and_check_result(tx, confirmations, requires_nota, wait_until, check_every) + .wait_for_confirmations_and_check_result( + tx, + input.confirmations, + input.requires_nota, + input.wait_until, + input.check_every, + ) .await }; Box::new(fut.boxed().compat()) diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 7354b5a103..f662f55ad3 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -260,27 +260,40 @@ fn test_wait_for_confirmations_excepted() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 1; // the transaction is mined already let check_every = 1; - coin.wait_for_confirmations(&payment_tx, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx, + confirmations, + requires_nota, + wait_until, + check_every, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); // tx_hash: ed53b97deb2ad76974c972cb084f6ba63bd9f16c91c4a39106a20c6d14599b2a // `erc20Payment` contract call excepted let payment_tx = hex::decode("01000000014c1411bac38ca25a2816342b019df81f503e1db75b25c6da618b08484dc2ff49010000006b483045022100da3e90fbcc45a94573c28213b36dc616630e3adfa42a7f16bdf917e8a76b954502206ad0830bb16e5c25466903ae7f749e291586726f1497ae9fc2e709c1b6cd1857012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff040000000000000000625403a08601012844095ea7b3000000000000000000000000ba8b71f3544b93e2f681f996da519a98ace0107a000000000000000000000000000000000000000000000000000000000000000014d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000625403a08601012844095ea7b3000000000000000000000000ba8b71f3544b93e2f681f996da519a98ace0107a000000000000000000000000000000000000000000000000000000000000000a14d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000e35403a0860101284cc49b415b2a0a1a8b4af2762154115ced87e2424b3cb940c0181cc3c850523702f1ec298fef0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc44000000000000000000000000783cf0be521101942da509846ea476e683aad8324b6b2e5444c2639cc0fb7bcea5afba3f3cdce239000000000000000000000000000000000000000000000000000000000000000000000000000000005fa0fffb14ba8b71f3544b93e2f681f996da519a98ace0107ac2493d4a03000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acae2ea15f").unwrap(); - let error = coin - .wait_for_confirmations(&payment_tx, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap_err(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx, + confirmations, + requires_nota, + wait_until, + check_every, + }; + let error = coin.wait_for_confirmations(confirm_payment_input).wait().unwrap_err(); log!("error: {:?}", error); assert!(error.contains("Contract call failed with an error: Revert")); // tx_hash: aa992c028c07e239dbd2ff32bf67251f026929c644b4d02a469e351cb44abab7 // `receiverSpend` contract call excepted let payment_tx = hex::decode("0100000007077ccb377a68fd6079503f856df4e553e337015f8419cd0f2a949c31db175df7050000006a473044022058097f54be31ae5af197f72e4410b33b22f29fad5b1a1cefb30ee45b3b3477dc02205c1098850fa2f2c1929c27af6261f83abce7682eb769f909dd09e9be5e0bd469012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559affffffffc191895a431db3dccbf4f9d4b8cd8301124343e66275194ad734a77ffe56b95e030000006a4730440220491fed7954c6f43acc7226c337bb16ac71b38df50f55a819441d9b2b9e4a04b502201f95be6941b6619c0ca246e15adb090b82cd908f7c85108a1dcc02eafb7cc725012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559afffffffff678de174fb81d3820df43a2c29945b08df4fb080deb8088ef11b3711c0fe8df020000006a473044022071d9c0ec57ab23360a4f73d0edfc2f67614b56f6d2e54387b39c3de1fa894c7d022030ea65d157784ff68cae9c9acb0dd626205073f478003b1cb1d0d581dcb27b1c012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559affffffffe1ef8740ce51ed3172efea91a5e559b5fe63dc6fede8a9037ad47fbc38560b51040000006a47304402203f056dff0be1f24ed96c72904c9aac3ac964913d0c3228bfab3fa4bef7f22c060220658a121bf8f29d86c18ec1aee4460f363c0704d2f05cc9d7923e978e917f48ca012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559affffffffe825dea61113bbd67dd35cbc9d88890ac222f55bf0201a7f9fb96592e0614d4d080000006b483045022100bb10f195c57c1eed9de3d9d9726484f839e25d83deb54cf2142df37099df6a8d02202a025182caaa5348350b410ee783180e9ce3ccac5e361eb50b162311e9d803f1012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559affffffffe1ef8740ce51ed3172efea91a5e559b5fe63dc6fede8a9037ad47fbc38560b51060000006a47304402205550e0b4e1425f2f7a8645c6fd408ba0603cca5ca408202729041f5eab0b0cd202205c98fc8e91a37960d38f0104e81d3d48f737c4000ef45e2372c84d857455da34012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559affffffffe825dea61113bbd67dd35cbc9d88890ac222f55bf0201a7f9fb96592e0614d4d060000006b483045022100b0d21cbb5d94b4995d9cb81e7440849dbe645416bca6d51bb5450e10753523220220299f105d573cdb785233699b5a9be8f907d9821a74cfd91fb72911a4a6e1bdb8012102aa32922f4b05cbc7384dd85b86021c98e4102f5da3df48bc516aa76f8119559affffffff020000000000000000c35403a0860101284ca402ed292be8b1d4904e8f1924bd7a2eb4d8085214c17af3d8d7574b2740a86b6296d343c00000000000000000000000000000000000000000000000000000000005f5e10028fcc0c5f6d9619d3c1f90af51e891d62333eb748c568f7da2a7734240d37d38000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc44000000000000000000000000d020b63f5a989776516bdc04d426ba118130c00214ba8b71f3544b93e2f681f996da519a98ace0107ac270630800000000001976a914fb7dad7ce97deecf50a4573a2bd7639c79bdc08588aca64aaa5f").unwrap(); - let error = coin - .wait_for_confirmations(&payment_tx, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap_err(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx, + confirmations, + requires_nota, + wait_until, + check_every, + }; + let error = coin.wait_for_confirmations(confirm_payment_input).wait().unwrap_err(); log!("error: {:?}", error); assert!(error.contains("Contract call failed with an error: Revert")); } diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 2443f31276..0da80b5968 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -2,16 +2,17 @@ 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, FeeApproxStage, FoundSwapTxSpend, - MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; +use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, + FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, + TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, + TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, + WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -441,14 +442,7 @@ impl MarketCoinOps for SolanaCoin { Box::new(fut.boxed().compat()) } - fn wait_for_confirmations( - &self, - _tx: &[u8], - _confirmations: u64, - _requires_nota: bool, - _wait_until: u64, - _check_every: u64, - ) -> Box + Send> { + fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { unimplemented!() } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index d23cb2301f..8aa917aed0 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -2,11 +2,11 @@ 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, FeeApproxStage, FoundSwapTxSpend, - MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, - RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, - SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, + FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, + PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, @@ -265,14 +265,7 @@ impl MarketCoinOps for SplToken { self.platform_coin.send_raw_tx_bytes(tx) } - fn wait_for_confirmations( - &self, - _tx: &[u8], - _confirmations: u64, - _requires_nota: bool, - _wait_until: u64, - _check_every: u64, - ) -> Box + Send> { + fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { unimplemented!() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 4eb75fdf23..eabffcf897 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -14,14 +14,14 @@ 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, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, - MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, - SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, + CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, + MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, + RawTransactionFut, RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, + RpcCommonOps, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, @@ -2127,26 +2127,19 @@ impl MarketCoinOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn wait_for_confirmations( - &self, - tx_bytes: &[u8], - _confirmations: u64, - _requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { // Sanity check - let _: TxRaw = try_fus!(Message::decode(tx_bytes)); + let _: TxRaw = try_fus!(Message::decode(input.payment_tx.as_slice())); - let tx_hash = hex::encode_upper(sha256(tx_bytes)); + let tx_hash = hex::encode_upper(sha256(&input.payment_tx)); let coin = self.clone(); let fut = async move { loop { - if now_ms() / 1000 > wait_until { + if now_ms() / 1000 > input.wait_until { return ERR!( "Waited too long until {} for payment {} to be received", - wait_until, + input.wait_until, tx_hash.clone() ); } @@ -2163,7 +2156,7 @@ impl MarketCoinOps for TendermintCoin { }; }; - Timer::sleep(check_every as f64).await; + Timer::sleep(input.check_every as f64).await; } }; @@ -3449,11 +3442,14 @@ pub mod tendermint_coin_tests { .unwrap() .encode_to_vec(); - block_on( - coin.wait_for_confirmations(&tx_bytes, 0, false, wait_until(), CHECK_INTERVAL) - .compat(), - ) - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx_bytes, + confirmations: 0, + requires_nota: false, + wait_until: wait_until(), + check_every: CHECK_INTERVAL, + }; + block_on(coin.wait_for_confirmations(confirm_payment_input).compat()).unwrap(); } for failed_tx_hash in FAILED_TX_HASH_SAMPLES { @@ -3461,11 +3457,14 @@ pub mod tendermint_coin_tests { .unwrap() .encode_to_vec(); - block_on( - coin.wait_for_confirmations(&tx_bytes, 0, false, wait_until(), CHECK_INTERVAL) - .compat(), - ) - .unwrap_err(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx_bytes, + confirmations: 0, + requires_nota: false, + wait_until: wait_until(), + check_every: CHECK_INTERVAL, + }; + block_on(coin.wait_for_confirmations(confirm_payment_input).compat()).unwrap_err(); } } } diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 2107c6a42a..54dc2dbc28 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -7,17 +7,17 @@ use super::{TendermintCoin, TendermintFeeDetails, GAS_LIMIT_DEFAULT, MIN_TX_SATO use crate::rpc_command::tendermint::IBCWithdrawRequest; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFut, BigDecimal, - CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, - MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, NegotiateSwapContractAddrErr, - PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; + CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, + FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -522,16 +522,8 @@ impl MarketCoinOps for TendermintToken { self.platform_coin.send_raw_tx_bytes(tx) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - self.platform_coin - .wait_for_confirmations(tx, confirmations, requires_nota, wait_until, check_every) + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + self.platform_coin.wait_for_confirmations(input) } fn wait_for_htlc_tx_spend( diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 7e3cb9e0eb..01e4dda0cc 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -3,8 +3,8 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, - FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, - PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, + PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, @@ -64,14 +64,7 @@ impl MarketCoinOps for TestCoin { fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { unimplemented!() } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { + fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { unimplemented!() } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 5758f269bd..896600d0a3 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -10,15 +10,15 @@ 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, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, - RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, - TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut}; + CoinWithDerivationMethod, ConfirmPaymentInput, IguanaPrivKey, MakerSwapTakerCoin, + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionType, + TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use derive_more::Display; @@ -1147,22 +1147,8 @@ impl MarketCoinOps for BchCoin { utxo_common::send_raw_tx_bytes(&self.utxo_arc, tx) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - utxo_common::wait_for_confirmations( - &self.utxo_arc, - tx, - confirmations, - requires_nota, - wait_until, - check_every, - ) + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + utxo_common::wait_for_confirmations(&self.utxo_arc, input) } fn wait_for_htlc_tx_spend( diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 657ecd56b9..8fa047cfa1 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -24,15 +24,16 @@ use crate::utxo::utxo_builder::{BlockHeaderUtxoArcOps, MergeUtxoArcOps, UtxoCoin UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaSecretBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, DelegationError, - DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, - TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; +use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, + DelegationError, DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, StakingInfosFut, + SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use ethereum_types::H160; @@ -831,22 +832,8 @@ impl MarketCoinOps for QtumCoin { utxo_common::send_raw_tx_bytes(&self.utxo_arc, tx) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - utxo_common::wait_for_confirmations( - &self.utxo_arc, - tx, - confirmations, - requires_nota, - wait_until, - check_every, - ) + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + utxo_common::wait_for_confirmations(&self.utxo_arc, input) } fn wait_for_htlc_tx_spend( diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 8beef7c130..ef2903c280 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -13,17 +13,18 @@ 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, FeeApproxStage, FoundSwapTxSpend, - HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, - NumConversError, PaymentInstructions, PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, - TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxFeeDetails, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentInput, VerificationError, - VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, + FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, + NegotiateSwapContractAddrErr, NumConversError, PaymentInstructions, PaymentInstructionsErr, + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, + WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; @@ -1154,16 +1155,8 @@ impl MarketCoinOps for SlpToken { Box::new(fut.boxed().compat()) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - self.platform_coin - .wait_for_confirmations(tx, confirmations, requires_nota, wait_until, check_every) + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + self.platform_coin.wait_for_confirmations(input) } fn wait_for_htlc_tx_spend( diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 1323a2a349..5709002ec2 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -12,8 +12,8 @@ use crate::utxo::rpc_clients::{electrum_script_hash, BlockHashOrHeight, UnspentI use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; -use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, HDAccountAddressId, - RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GetWithdrawSenderAddress, + HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TxFeeDetails, TxMarshalingErr, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, @@ -2323,21 +2323,17 @@ pub fn send_raw_tx_bytes( pub fn wait_for_confirmations( coin: &UtxoCoinFields, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, + input: ConfirmPaymentInput, ) -> Box + Send> { - let mut tx: UtxoTx = try_fus!(deserialize(tx).map_err(|e| ERRL!("{:?}", e))); + let mut tx: UtxoTx = try_fus!(deserialize(input.payment_tx.as_slice()).map_err(|e| ERRL!("{:?}", e))); tx.tx_hash_algo = coin.tx_hash_algo; coin.rpc_client.wait_for_confirmations( tx.hash().reversed().into(), tx.expiry_height, - confirmations as u32, - requires_nota, - wait_until, - check_every, + input.confirmations as u32, + input.requires_nota, + input.wait_until, + input.check_every, ) } diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index b3f5168b0c..b21b2fda80 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -22,12 +22,12 @@ use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, - IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, - SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, +use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, + GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, + TxMarshalingErr, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; @@ -596,22 +596,8 @@ impl MarketCoinOps for UtxoStandardCoin { utxo_common::send_raw_tx_bytes(&self.utxo_arc, tx) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - utxo_common::wait_for_confirmations( - &self.utxo_arc, - tx, - confirmations, - requires_nota, - wait_until, - check_every, - ) + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + utxo_common::wait_for_confirmations(&self.utxo_arc, input) } fn wait_for_htlc_tx_spend( diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index ae54d5fd57..bd2a901dcd 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -28,10 +28,10 @@ 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::INVALID_SENDER_ERR_LOG; use crate::{BlockHeightAndTime, CoinBalance, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, TxMarshalingErr, ValidateFeeArgs}; +use crate::{ConfirmPaymentInput, INVALID_SENDER_ERR_LOG}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; use common::executor::Timer; use common::{block_on, now_ms, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -4135,11 +4135,14 @@ fn test_for_non_existent_tx_hex_utxo_electrum() { ); // bad transaction hex let tx = hex::decode("0400008085202f8902bf17bf7d1daace52e08f732a6b8771743ca4b1cb765a187e72fd091a0aabfd52000000006a47304402203eaaa3c4da101240f80f9c5e9de716a22b1ec6d66080de6a0cca32011cd77223022040d9082b6242d6acf9a1a8e658779e1c655d708379862f235e8ba7b8ca4e69c6012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffffff023ca13c0e9e085dd13f481f193e8a3e8fd609020936e98b5587342d994f4d020000006b483045022100c0ba56adb8de923975052312467347d83238bd8d480ce66e8b709a7997373994022048507bcac921fdb2302fa5224ce86e41b7efc1a2e20ae63aa738dfa99b7be826012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0300e1f5050000000017a9141ee6d4c38a3c078eab87ad1a5e4b00f21259b10d87000000000000000016611400000000000000000000000000000000000000001b94d736000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac2d08e35e000000000000000000000000000000").unwrap(); - let actual = coin - .wait_for_confirmations(&tx, 1, false, timeout, 1) - .wait() - .err() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx, + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + let actual = coin.wait_for_confirmations(confirm_payment_input).wait().err().unwrap(); assert!(actual.contains( "Tx d342ff9da528a2e262bddf2b6f9a27d1beb7aeb03f0fc8d9eac2987266447e44 was not found on chain after 10 tries" )); diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 7d19ae13ee..416b6292c1 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -13,8 +13,8 @@ use crate::utxo::{sat_from_big_decimal, utxo_common, ActualTxFee, AdditionalTxDa RecentlySpentOutPointsGuard, UtxoActivationParams, UtxoAddressFormat, UtxoArc, UtxoCoinFields, UtxoCommonOps, UtxoFeeDetails, UtxoRpcMode, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom}; -use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, - FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, +use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, + FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructions, PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, @@ -1047,15 +1047,8 @@ impl MarketCoinOps for ZCoin { Box::new(fut.boxed().compat()) } - fn wait_for_confirmations( - &self, - tx: &[u8], - confirmations: u64, - requires_nota: bool, - wait_until: u64, - check_every: u64, - ) -> Box + Send> { - utxo_common::wait_for_confirmations(self.as_ref(), tx, confirmations, requires_nota, wait_until, check_every) + fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send> { + utxo_common::wait_for_confirmations(self.as_ref(), input) } fn wait_for_htlc_tx_spend( diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 0cd8affc40..ae1d8e7af2 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -16,8 +16,8 @@ use crate::mm2::lp_ordermatch::{MakerOrderBuilder, OrderConfirmationsSettings}; use crate::mm2::lp_price::fetch_swap_coins_price; use crate::mm2::lp_swap::{broadcast_swap_message, min_watcher_reward, taker_payment_spend_duration, watcher_reward_amount}; -use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MmCoinEnum, - PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, +use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, + MmCoinEnum, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput}; use common::log::{debug, error, info, warn}; @@ -893,13 +893,15 @@ impl MakerSwap { let maker_payment_wait_confirm = wait_for_maker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); - let f = self.maker_coin.wait_for_confirmations( - &self.r().maker_payment.clone().unwrap().tx_hex, - self.r().data.maker_payment_confirmations, - self.r().data.maker_payment_requires_nota.unwrap_or(false), - maker_payment_wait_confirm, - WAIT_CONFIRM_INTERVAL, - ); + let confirm_maker_payment_input = ConfirmPaymentInput { + payment_tx: self.r().maker_payment.clone().unwrap().tx_hex.0, + confirmations: self.r().data.maker_payment_confirmations, + requires_nota: self.r().data.maker_payment_requires_nota.unwrap_or(false), + wait_until: maker_payment_wait_confirm, + check_every: WAIT_CONFIRM_INTERVAL, + }; + + let f = self.maker_coin.wait_for_confirmations(confirm_maker_payment_input); if let Err(err) = f.compat().await { return Ok((Some(MakerSwapCommand::PrepareForMakerPaymentRefund), vec![ MakerSwapEvent::MakerPaymentWaitConfirmFailed( @@ -963,16 +965,18 @@ impl MakerSwap { async fn validate_taker_payment(&self) -> Result<(Option, Vec), String> { let wait_taker_payment = taker_payment_spend_deadline(self.r().data.started_at, self.r().data.lock_duration); let confirmations = self.r().data.taker_payment_confirmations; + let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); + let confirm_taker_payment_input = ConfirmPaymentInput { + payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, + confirmations, + requires_nota: self.r().data.taker_payment_requires_nota.unwrap_or(false), + wait_until: wait_taker_payment, + check_every: WAIT_CONFIRM_INTERVAL, + }; let wait_f = self .taker_coin - .wait_for_confirmations( - &self.r().taker_payment.clone().unwrap().tx_hex, - confirmations, - self.r().data.taker_payment_requires_nota.unwrap_or(false), - wait_taker_payment, - WAIT_CONFIRM_INTERVAL, - ) + .wait_for_confirmations(confirm_taker_payment_input) .compat(); if let Err(err) = wait_f.await { return Ok((Some(MakerSwapCommand::PrepareForMakerPaymentRefund), vec![ @@ -1007,7 +1011,7 @@ impl MakerSwap { unique_swap_data: self.unique_swap_data(), secret_hash: self.secret_hash(), amount: self.taker_amount.clone(), - swap_contract_address: self.r().data.taker_coin_swap_contract_address.clone(), + swap_contract_address: taker_coin_swap_contract_address, try_spv_proof_until: wait_taker_payment, confirmations, min_watcher_reward, @@ -1110,13 +1114,14 @@ impl MakerSwap { // we should wait for only one confirmation to make sure our spend transaction is not failed let confirmations = std::cmp::min(1, self.r().data.taker_payment_confirmations); let requires_nota = false; - let wait_fut = self.taker_coin.wait_for_confirmations( - &self.r().taker_payment_spend.clone().unwrap().tx_hex, + let confirm_taker_payment_input = ConfirmPaymentInput { + payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, confirmations, requires_nota, - self.wait_refund_until(), - WAIT_CONFIRM_INTERVAL, - ); + wait_until: self.wait_refund_until(), + check_every: WAIT_CONFIRM_INTERVAL, + }; + let wait_fut = self.taker_coin.wait_for_confirmations(confirm_taker_payment_input); if let Err(err) = wait_fut.compat().await { return Ok((Some(MakerSwapCommand::PrepareForMakerPaymentRefund), vec![ MakerSwapEvent::TakerPaymentSpendConfirmFailed( diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index 09b42f2863..7441a4a855 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -2,8 +2,9 @@ use super::{broadcast_p2p_tx_msg, get_payment_locktime, lp_coinfind, min_watcher tx_helper_topic, H256Json, SwapsContext, WAIT_CONFIRM_INTERVAL}; use crate::mm2::MmError; use async_trait::async_trait; -use coins::{CanRefundHtlc, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, SendMakerPaymentSpendPreimageInput, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput}; +use coins::{CanRefundHtlc, ConfirmPaymentInput, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, + SendMakerPaymentSpendPreimageInput, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput}; use common::executor::{AbortSettings, SpawnAbortable, Timer}; use common::log::{debug, error, info}; use common::state_machine::prelude::*; @@ -216,16 +217,17 @@ impl State for ValidateTakerPayment { }; let confirmations = min(watcher_ctx.data.taker_payment_confirmations, TAKER_SWAP_CONFIRMATIONS); + let confirm_taker_payment_input = ConfirmPaymentInput { + payment_tx: taker_payment_hex.clone(), + confirmations, + requires_nota: watcher_ctx.data.taker_payment_requires_nota.unwrap_or(false), + wait_until: taker_payment_spend_deadline, + check_every: WAIT_CONFIRM_INTERVAL, + }; let wait_fut = watcher_ctx .taker_coin - .wait_for_confirmations( - &taker_payment_hex, - confirmations, - watcher_ctx.data.taker_payment_requires_nota.unwrap_or(false), - taker_payment_spend_deadline, - WAIT_CONFIRM_INTERVAL, - ) + .wait_for_confirmations(confirm_taker_payment_input) .compat(); if let Err(err) = wait_fut.await { return Self::change_state(Stopped::from_reason(StopReason::Error( diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index f2bc7e0cb6..1a41f1128b 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -15,9 +15,10 @@ use crate::mm2::lp_ordermatch::{MatchBy, OrderConfirmationsSettings, TakerAction use crate::mm2::lp_price::fetch_swap_coins_price; use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, min_watcher_reward, tx_helper_topic, wait_for_maker_payment_conf_duration, watcher_reward_amount, TakerSwapWatcherData}; -use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MmCoinEnum, - PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, - SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, ValidatePaymentInput}; +use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, + FoundSwapTxSpend, MmCoinEnum, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, + ValidatePaymentInput}; use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -1338,13 +1339,15 @@ impl TakerSwap { async fn validate_maker_payment(&self) -> Result<(Option, Vec), String> { info!("Before wait confirm"); let confirmations = self.r().data.maker_payment_confirmations; - let f = self.maker_coin.wait_for_confirmations( - &self.r().maker_payment.clone().unwrap().tx_hex, + let confirm_maker_payment_input = ConfirmPaymentInput { + payment_tx: self.r().maker_payment.clone().unwrap().tx_hex.0, confirmations, - self.r().data.maker_payment_requires_nota.unwrap_or(false), - self.r().data.maker_payment_wait, - WAIT_CONFIRM_INTERVAL, - ); + requires_nota: self.r().data.maker_payment_requires_nota.unwrap_or(false), + wait_until: self.r().data.maker_payment_wait, + check_every: WAIT_CONFIRM_INTERVAL, + }; + + let f = self.maker_coin.wait_for_confirmations(confirm_maker_payment_input); if let Err(err) = f.compat().await { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::MakerPaymentWaitConfirmFailed( @@ -1614,15 +1617,16 @@ impl TakerSwap { self.p2p_privkey, ); + let confirm_taker_payment_input = ConfirmPaymentInput { + payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, + confirmations: self.r().data.taker_payment_confirmations, + requires_nota: self.r().data.taker_payment_requires_nota.unwrap_or(false), + wait_until: self.r().data.taker_payment_lock, + check_every: WAIT_CONFIRM_INTERVAL, + }; let wait_f = self .taker_coin - .wait_for_confirmations( - &self.r().taker_payment.clone().unwrap().tx_hex, - self.r().data.taker_payment_confirmations, - self.r().data.taker_payment_requires_nota.unwrap_or(false), - self.r().data.taker_payment_lock, - WAIT_CONFIRM_INTERVAL, - ) + .wait_for_confirmations(confirm_taker_payment_input) .compat(); if let Err(err) = wait_f.await { return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ 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 fe4145e002..40a6514c88 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -22,7 +22,7 @@ use coins::utxo::utxo_common::send_outputs_from_my_address; use coins::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use coins::utxo::{coin_daemon_data_dir, sat_from_big_decimal, zcash_params_path, UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps}; -use coins::{CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy, Transaction}; +use coins::{CoinProtocol, ConfirmPaymentInput, MarketCoinOps, PrivKeyBuildPolicy, Transaction}; use crypto::privkey::key_pair_from_seed; use crypto::Secp256k1Secret; use ethereum_types::H160 as H160Eth; @@ -277,10 +277,14 @@ impl BchDockerOps { let slp_genesis_tx = send_outputs_from_my_address(self.coin.clone(), bch_outputs) .wait() .unwrap(); - self.coin - .wait_for_confirmations(&slp_genesis_tx.tx_hex(), 1, false, now_ms() / 1000 + 30, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: slp_genesis_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_ms() / 1000 + 30, + check_every: 1, + }; + self.coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let adex_slp = SlpToken::new( 8, @@ -292,10 +296,14 @@ impl BchDockerOps { .unwrap(); let tx = block_on(adex_slp.send_slp_outputs(slp_outputs)).unwrap(); - self.coin - .wait_for_confirmations(&tx.tx_hex(), 1, false, now_ms() / 1000 + 30, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_ms() / 1000 + 30, + check_every: 1, + }; + self.coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); *SLP_TOKEN_OWNERS.lock().unwrap() = slp_privkeys; *SLP_TOKEN_ID.lock().unwrap() = slp_genesis_tx.tx_hash().as_slice().into(); } @@ -552,9 +560,14 @@ pub fn fill_qrc20_address(coin: &Qrc20Coin, amount: BigDecimal, timeout: u64) { let tx_bytes = client.get_transaction_bytes(&hash).wait().unwrap(); log!("{:02x}", tx_bytes); - coin.wait_for_confirmations(&tx_bytes, 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx_bytes.clone().0, + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); } /// Generate random privkey, create a QRC20 coin and fill it's address with the specified balance. @@ -665,9 +678,14 @@ where client.import_address(address, address, false).wait().unwrap(); let hash = client.send_to_address(address, &amount).wait().unwrap(); let tx_bytes = client.get_transaction_bytes(&hash).wait().unwrap(); - coin.wait_for_confirmations(&tx_bytes, 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx_bytes.clone().0, + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); log!("{:02x}", tx_bytes); loop { let unspents = client 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 c89f99528a..f6afbf7d1c 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -5,8 +5,8 @@ use bitcrypto::dhash160; use chain::OutPoint; use coins::utxo::rpc_clients::UnspentInfo; use coins::utxo::{GetUtxoListOps, UtxoCommonOps}; -use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, - SpendPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; +use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; use common::{block_on, now_ms}; use futures01::Future; use mm2_number::{BigDecimal, MmNumber}; @@ -41,9 +41,14 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -58,9 +63,14 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { .wait() .unwrap(); - coin.wait_for_confirmations(&refund_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -85,11 +95,14 @@ fn test_for_non_existent_tx_hex_utxo() { let (_ctx, coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); // bad transaction hex let tx = hex::decode("0400008085202f8902bf17bf7d1daace52e08f732a6b8771743ca4b1cb765a187e72fd091a0aabfd52000000006a47304402203eaaa3c4da101240f80f9c5e9de716a22b1ec6d66080de6a0cca32011cd77223022040d9082b6242d6acf9a1a8e658779e1c655d708379862f235e8ba7b8ca4e69c6012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffffff023ca13c0e9e085dd13f481f193e8a3e8fd609020936e98b5587342d994f4d020000006b483045022100c0ba56adb8de923975052312467347d83238bd8d480ce66e8b709a7997373994022048507bcac921fdb2302fa5224ce86e41b7efc1a2e20ae63aa738dfa99b7be826012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0300e1f5050000000017a9141ee6d4c38a3c078eab87ad1a5e4b00f21259b10d87000000000000000016611400000000000000000000000000000000000000001b94d736000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac2d08e35e000000000000000000000000000000").unwrap(); - let actual = coin - .wait_for_confirmations(&tx, 1, false, timeout, 1) - .wait() - .err() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx, + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + let actual = coin.wait_for_confirmations(confirm_payment_input).wait().err().unwrap(); assert!(actual.contains( "Tx d342ff9da528a2e262bddf2b6f9a27d1beb7aeb03f0fc8d9eac2987266447e44 was not found on chain after 10 tries" )); @@ -116,9 +129,14 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -133,9 +151,14 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { .wait() .unwrap(); - coin.wait_for_confirmations(&refund_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -176,9 +199,14 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, @@ -194,9 +222,14 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { .wait() .unwrap(); - coin.wait_for_confirmations(&spend_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: spend_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -237,9 +270,14 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, @@ -255,9 +293,14 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { .wait() .unwrap(); - coin.wait_for_confirmations(&spend_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: spend_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -2430,9 +2473,14 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ .wait() .unwrap(); coin.send_raw_tx(&hex::encode(&withdraw.tx_hex.0)).wait().unwrap(); - coin.wait_for_confirmations(&withdraw.tx_hex.0, 1, false, (now_ms() / 1000) + 10, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: withdraw.tx_hex.0, + confirmations: 1, + requires_nota: false, + wait_until: (now_ms() / 1000) + 10, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 6353005c9a..3601baa886 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -6,9 +6,9 @@ use coins::utxo::qtum::{qtum_coin_with_priv_key, QtumCoin}; use coins::utxo::rpc_clients::UtxoRpcClientEnum; use coins::utxo::utxo_common::big_decimal_from_sat; use coins::utxo::{UtxoActivationParams, UtxoCommonOps}; -use coins::{CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, - SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, TradePreimageValue, - TransactionEnum, ValidatePaymentInput}; +use coins::{CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, + RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, + TradePreimageValue, TransactionEnum, ValidatePaymentInput}; use common::log::debug; use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; @@ -202,10 +202,14 @@ fn test_taker_spends_maker_payment() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - taker_coin - .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let input = ValidatePaymentInput { payment_tx: payment_tx_hex.clone(), @@ -240,10 +244,14 @@ fn test_taker_spends_maker_payment() { log!("Taker spends tx: {:?}", spend_tx_hash); let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run - taker_coin - .wait_for_confirmations(&spend_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: spend_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_balance = maker_coin .my_spendable_balance() @@ -299,10 +307,14 @@ fn test_maker_spends_taker_payment() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - maker_coin - .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let input = ValidatePaymentInput { payment_tx: payment_tx_hex.clone(), @@ -337,10 +349,14 @@ fn test_maker_spends_taker_payment() { log!("Maker spends tx: {:?}", spend_tx_hash); let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run - maker_coin - .wait_for_confirmations(&spend_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: spend_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_balance = maker_coin .my_spendable_balance() @@ -385,9 +401,14 @@ fn test_maker_refunds_payment() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - coin.wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); @@ -409,9 +430,14 @@ fn test_maker_refunds_payment() { log!("Maker refunds payment: {:?}", refund_tx_hash); let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run - coin.wait_for_confirmations(&refund_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let balance_after_refund = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance, balance_after_refund); @@ -448,9 +474,14 @@ fn test_taker_refunds_payment() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - coin.wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); @@ -472,9 +503,14 @@ fn test_taker_refunds_payment() { log!("Taker refunds payment: {:?}", refund_tx_hash); let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run - coin.wait_for_confirmations(&refund_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let balance_after_refund = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance, balance_after_refund); @@ -508,9 +544,14 @@ fn test_check_if_my_payment_sent() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - coin.wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_from_block = coin.current_block().wait().expect("!current_block") - 10; let if_my_payment_sent_args = CheckIfMyPaymentSentArgs { @@ -560,10 +601,14 @@ fn test_search_for_swap_tx_spend_taker_spent() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - taker_coin - .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, @@ -583,10 +628,14 @@ fn test_search_for_swap_tx_spend_taker_spent() { log!("Taker spends tx: {:?}", spend_tx_hash); let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run - taker_coin - .wait_for_confirmations(&spend_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: spend_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -634,10 +683,14 @@ fn test_search_for_swap_tx_spend_maker_refunded() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - maker_coin - .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, @@ -656,10 +709,14 @@ fn test_search_for_swap_tx_spend_maker_refunded() { log!("Maker refunds tx: {:?}", refund_tx_hash); let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run - maker_coin - .wait_for_confirmations(&refund_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx_hex, + confirmations, + requires_nota, + wait_until, + check_every, + }; + maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -707,10 +764,14 @@ fn test_search_for_swap_tx_spend_not_spent() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - maker_coin - .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -760,10 +821,14 @@ fn test_wait_for_tx_spend() { let requires_nota = false; let wait_until = (now_ms() / 1000) + 40; // timeout if test takes more than 40 seconds to run let check_every = 1; - taker_coin - .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: payment_tx_hex.clone(), + confirmations, + requires_nota, + wait_until, + check_every, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); // first try to check if the wait_for_htlc_tx_spend() returns an error correctly let wait_until = (now_ms() / 1000) + 5; @@ -1471,9 +1536,14 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { }; let tx = coin.send_maker_payment(maker_payment).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -1488,9 +1558,14 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { .wait() .unwrap(); - coin.wait_for_confirmations(&refund_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -1530,9 +1605,14 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { }; let tx = coin.send_taker_payment(taker_payment).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -1547,9 +1627,14 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { .wait() .unwrap(); - coin.wait_for_confirmations(&refund_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, 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 af4156efcc..1f7b713808 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -4,11 +4,12 @@ use crate::{generate_utxo_coin_with_privkey, generate_utxo_coin_with_random_priv SecretKey}; use coins::coin_errors::ValidatePaymentError; use coins::utxo::{dhash160, UtxoCommonOps}; -use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoinEnum, RefundPaymentArgs, SearchForSwapTxSpendInput, - SendPaymentArgs, SwapOps, WatcherOps, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - EARLY_CONFIRMATION_ERR_LOG, INSUFFICIENT_WATCHER_REWARD_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 coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoinEnum, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendPaymentArgs, SwapOps, WatcherOps, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, EARLY_CONFIRMATION_ERR_LOG, INSUFFICIENT_WATCHER_REWARD_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_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use futures01::Future; @@ -470,10 +471,14 @@ fn test_watcher_validate_taker_fee_eth() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_fee.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_fee.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let validate_taker_fee_res = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { @@ -574,10 +579,14 @@ fn test_watcher_validate_taker_fee_erc20() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_fee.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_fee.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let validate_taker_fee_res = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { @@ -706,10 +715,14 @@ fn test_watcher_validate_taker_payment_eth() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) - .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 validate_taker_payment_res = taker_coin .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { @@ -838,10 +851,14 @@ fn test_watcher_validate_taker_payment_eth() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_payment_wrong_secret.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_payment_wrong_secret.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let error = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { @@ -971,10 +988,14 @@ fn test_watcher_validate_taker_payment_erc20() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) - .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 validate_taker_payment_res = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { @@ -1103,10 +1124,14 @@ fn test_watcher_validate_taker_payment_erc20() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_payment_wrong_secret.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_payment_wrong_secret.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let error = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { @@ -1434,10 +1459,14 @@ fn test_watcher_validate_taker_fee_utxo() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_fee.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: taker_fee.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let validate_taker_fee_res = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { @@ -1565,10 +1594,14 @@ fn test_watcher_validate_taker_payment_utxo() { .wait() .unwrap(); - taker_coin - .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) - .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( @@ -1737,9 +1770,14 @@ fn test_send_taker_payment_refund_preimage_utxo() { }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); - coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let refund_tx = coin .create_taker_payment_refund_preimage(&tx.tx_hex(), time_lock, my_public_key, &[0; 20], &None, &[]) @@ -1759,9 +1797,14 @@ fn test_send_taker_payment_refund_preimage_utxo() { .wait() .unwrap(); - coin.wait_for_confirmations(&refund_tx.tx_hex(), 1, false, timeout, 1) - .wait() - .unwrap(); + let confirm_payment_input = ConfirmPaymentInput { + payment_tx: refund_tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + }; + coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, 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 ca257a6118..163722d527 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -4533,6 +4533,8 @@ fn test_tx_history_tbtc_non_segwit() { ); let expected = vec![ + // https://live.blockcypher.com/btc-testnet/tx/a41b2e5f0741d1dcbc309ce4c43fde1ad44c5e61bb34778ab0bf9f3d9fd6fb6c/ + "a41b2e5f0741d1dcbc309ce4c43fde1ad44c5e61bb34778ab0bf9f3d9fd6fb6c", // https://live.blockcypher.com/btc-testnet/tx/9c1ca9de9f3a47d71c8113209123410f44048c67951bf49cdfb1a84c2cc6a55b/ "9c1ca9de9f3a47d71c8113209123410f44048c67951bf49cdfb1a84c2cc6a55b", // https://live.blockcypher.com/btc-testnet/tx/ac6218b33d02e069c4055af709bbb6ca92ce11e55450cde96bc17411e281e5e7/ From 53b2986bbbf7256cc2629f3b750e42eba883eab1 Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 22 Mar 2023 11:29:56 +0300 Subject: [PATCH 73/79] add changelog entry Signed-off-by: ozkanonur --- CHANGELOG.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d20374911..8d8a8017ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,19 +27,18 @@ - Maker/taker pubkeys were added to new columns in `stats_swaps` table in [#1665](https://github.com/KomodoPlatform/atomicDEX-API/pull/1665) and [#1717](https://github.com/KomodoPlatform/atomicDEX-API/pull/1717) - Get rid of unnecessary / old dependencies: `crossterm`, `crossterm_winapi`, `mio 0.7.13`, `miow`, `ntapi`, `signal-hook`, `signal-hook-mio` in [#1710](https://github.com/KomodoPlatform/atomicDEX-API/pull/1710) - A bug that caused EVM swap payments validation to fail because the tx was not available yet in the RPC node when calling `eth_getTransactionByHash` was fixed in [#1716](https://github.com/KomodoPlatform/atomicDEX-API/pull/1716). `eth_getTransactionByHash` in now retried in `wait_for_confirmations` until tx is found in the RPC node, this makes sure that the transaction is returned from `eth_getTransactionByHash` later when validating. -- CI/CD migrated from Azure to Github runners -- CI tests are much stabilized -- Integration and unit tests are seperated on CI stack -- Jemalloc configuration updated for optimization purposes -- Codebase is updated in linting rules at wasm and test stack -- `crossbeam` bumped to `0.8` from `0.7` -- Un-used/Unstable parts of mm2 excluded from build outputs which avoids mm2 - runtime from potential UB -- Build time optimizations applied such as sharing generics instead of - duplicating them in binary (which reduces output sizes) -- `RUSTSEC-2020-0036`, `RUSTSEC-2021-0139` and `RUSTSEC-2023-0018` resolved -- Enabled linting checks for wasm and test stack on CI -- Release container base image updated to debian stable from ubuntu bionic +- CI/CD migrated from Azure to Github runners [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- CI tests are much stabilized [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Integration and unit tests are seperated on CI stack [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Jemalloc configuration updated for optimization purposes [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Codebase is updated in linting rules at wasm and test stack [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- `crossbeam` bumped to `0.8` from `0.7` [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Un-used/Unstable parts of mm2 excluded from build outputs which avoids mm2 runtime from potential UB [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Build time optimizations applied such as sharing generics instead of duplicating them in binary (which reduces output sizes) [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- `RUSTSEC-2020-0036`, `RUSTSEC-2021-0139` and `RUSTSEC-2023-0018` resolved [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Enabled linting checks for wasm and test stack on CI [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Release container base image updated to debian stable from ubuntu bionic [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) +- Fix dylib linking error of rusb [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) - `OperationFailure::Other` error was expanded. New error variants were matched with `HwRpcError`, so error type will be `HwError`, not `InternalError` [#1719](https://github.com/KomodoPlatform/atomicDEX-API/pull/1719) ## v1.0.0-beta - 2023-03-08 From e031b29d3c734635e659a44d9666d4a1c1f533ee Mon Sep 17 00:00:00 2001 From: ozkanonur Date: Wed, 22 Mar 2023 21:36:41 +0300 Subject: [PATCH 74/79] fix `test_tendermint_activation_and_balance` test Signed-off-by: ozkanonur --- mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs index 31830de911..660e60c4e5 100644 --- a/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/tendermint_tests.rs @@ -33,7 +33,7 @@ fn test_tendermint_activation_and_balance() { let result: RpcV2Response = json::from_value(activation_result).unwrap(); assert_eq!(result.result.address, expected_address); - let expected_balance: BigDecimal = "0.0959".parse().unwrap(); + let expected_balance: BigDecimal = "8.0959".parse().unwrap(); assert_eq!(result.result.balance.spendable, expected_balance); let my_balance = block_on(my_balance(&mm, ATOM_TICKER)); From fba47236316f57b4b2cca47727aaa04c80938513 Mon Sep 17 00:00:00 2001 From: shamardy Date: Thu, 23 Mar 2023 14:37:58 +0200 Subject: [PATCH 75/79] add changelog for #1730 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0bd0f2b76..a0a7809323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - `OperationFailure::Other` error was expanded. New error variants were matched with `HwRpcError`, so error type will be `HwError`, not `InternalError` [#1719](https://github.com/KomodoPlatform/atomicDEX-API/pull/1719) - RPC calls for evm chains was reduced in `wait_for_confirmations` function in [#1724](https://github.com/KomodoPlatform/atomicDEX-API/pull/1724) - A possible endless loop in evm `wait_for_htlc_tx_spend` was fixed in [#1724](https://github.com/KomodoPlatform/atomicDEX-API/pull/1724) +- Wait time for taker fee validation was increased from 30 to 60 seconds in [#1730](https://github.com/KomodoPlatform/atomicDEX-API/pull/1730) to give the fee tx more time to appear in most nodes mempools. ## v1.0.0-beta - 2023-03-08 From c3a4185a5752ef363f67b377be4b5e552ecfd022 Mon Sep 17 00:00:00 2001 From: shamardy <39480341+shamardy@users.noreply.github.com> Date: Thu, 23 Mar 2023 14:40:02 +0200 Subject: [PATCH 76/79] fix: increase validate fee wait time (#1730) Reviewed-by: caglaryucekaya --- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index ae1d8e7af2..37cfe0d9b5 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -764,7 +764,7 @@ impl MakerSwap { { Ok(_) => break, Err(err) => { - if attempts >= 3 { + if attempts >= 6 { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::TakerFeeValidateFailed(ERRL!("{}", err).into()), ])); From 5f4a8bbc68773362d7a2e7ebab77ecdcb3479bbe Mon Sep 17 00:00:00 2001 From: Onur Date: Fri, 24 Mar 2023 14:17:10 +0300 Subject: [PATCH 77/79] initialize `env_logger` (#1725) Signed-off-by: ozkanonur --- CHANGELOG.md | 1 + Cargo.lock | 1 + mm2src/mm2_main/Cargo.toml | 1 + mm2src/mm2_main/src/mm2.rs | 2 ++ 4 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa68968c4..08907103bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - `mm2_git` crate was added to provide an abstraction layer on Git for doing query/parse operations over the repositories [#1636](https://github.com/KomodoPlatform/atomicDEX-API/pull/1636) **Enhancements/Fixes:** +- Use `env_logger` to achieve flexible log filtering [#1725](https://github.com/KomodoPlatform/atomicDEX-API/pull/1725) - IndexedDB Cursor can now iterate over the items step-by-step [#1678](https://github.com/KomodoPlatform/atomicDEX-API/pull/1678) - Uuid lib was update from v0.7.4 to v1.2.2 in [#1655](https://github.com/KomodoPlatform/atomicDEX-API/pull/1655) - A bug was fixed in [#1706](https://github.com/KomodoPlatform/atomicDEX-API/pull/1706) where EVM swap transactions were failing if sent before the approval transaction confirmation. diff --git a/Cargo.lock b/Cargo.lock index 0c63f27898..de68861053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4355,6 +4355,7 @@ dependencies = [ "either", "enum-primitive-derive", "enum_from", + "env_logger 0.7.1", "ethereum-types", "futures 0.1.29", "futures 0.3.15", diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 4d806ab933..443055e4d1 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -42,6 +42,7 @@ either = "1.6" ethereum-types = { version = "0.13", default-features = false, features = ["std", "serialize"] } enum_from = { path = "../derives/enum_from" } enum-primitive-derive = "0.2" +env_logger = "0.7.1" futures01 = { version = "0.1", package = "futures" } futures = { version = "0.3.1", package = "futures", features = ["compat", "async-await"] } gstuff = { version = "0.7", features = ["nightly"] } diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index 2281cd11a9..c31aff6439 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -456,6 +456,8 @@ pub fn run_lp_main( version: String, datetime: String, ) -> Result<(), String> { + env_logger::init(); + let conf = get_mm2config(first_arg)?; let log_filter = LogLevel::from_env(); From 4d691ca7e5906b3778972eb0c18075b9536f4c76 Mon Sep 17 00:00:00 2001 From: Alina Sharon <52405288+laruh@users.noreply.github.com> Date: Sat, 25 Mar 2023 01:04:17 +0700 Subject: [PATCH 78/79] feature: Withdraw ERC1155 and EVM based chains support added for NFT PoC (#1704) * wip * wip * wip some structures were added * wip * wip * wip * fn get_my_address was moved into lp_coins.rs * wip get_my_address * get_my_address works * send_moralis_request, errors, get_nft_list wip * add targets for send_moralis_request * wip * wip * wip use fn slurp_req_body in fn send_moralis_request * wip fix wasm * wip cursor in get_nft_list * wip impl Deserialize for Wrap * get_nft_list * remove unnecessary notes * wip get_nft_transfers * get_nft_transfers works * polish code * polish code * remove Option from some fields in Nft struct * wip get_nft_metadata * use NftWrapper in fn get_nft_metadata, add some doc comments * remove allow(dead_code) * change order in Chain enum * fix doc comment * beautify json * add from in withdraw requests * String::new(), serde UPPERCASE, pub(crate) SerdeStringWrap,line break * use ok_or_else, remove if cursor is null, remove !nfts_list.is_empty() * derive order, simplify match protocol * replace Chain::Bnb with Chain::Bsc * change status code matching, add derive Copy * fn withdraw_erc721 * move nft from eth to coin crate * remove memo * wip * add fn coins_conf_check * add from_stringify * add feature enable-nft-integration, fix coins_conf_check * add doc comment for withdraw_nft * fix coins_conf_check * fix deref which would be done by auto-deref * fix conflicts, add feature log file * fix wasm * find_wallet_amount, withdraw_erc1155 * add Avalanche, Fantom, Polygon chains * simplify code * amount_dec in tx details * add get_eth_nft_gas_details * add derive Clone, PartialEq * doc comments * fix merge conflict * TokenNotFoundInWallet * use eth_coin.my_address()? * add entry in the changelog file * use get_eth_gas_details for nft and fungible tokens * remove redundant attributes, count field, add type GasDetails, add iterator * fix merge conflicts * get_valid_nft_add_to_withdraw, use just bool for max * polish eth_value_for_estimate * remove nft: bool * add line spaces, add bold text --------- Reviewed-by: shamardy , ozkanonur --- CHANGELOG.md | 1 + mm2src/coins/eth.rs | 305 +++++++++++++++++++++--------- mm2src/coins/eth/v2_activation.rs | 2 +- mm2src/coins/lp_coins.rs | 71 ++++++- mm2src/coins/nft.rs | 58 ++++-- mm2src/coins/nft/nft_errors.rs | 18 +- mm2src/coins/nft/nft_structs.rs | 50 +++-- 7 files changed, 370 insertions(+), 135 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08907103bd..20713685a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ **Features:** - NFT integration `WIP` [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) - NFT integration PoC added. Includes ERC721 support for ETH and BSC [#1652](https://github.com/KomodoPlatform/atomicDEX-API/pull/1652) + - Withdraw ERC1155 and EVM based chains support added for NFT PoC [#1704](https://github.com/KomodoPlatform/atomicDEX-API/pull/1704) - Swap watcher nodes [#1431](https://github.com/KomodoPlatform/atomicDEX-API/issues/1431) - Watcher rewards for ETH swaps were added [#1658](https://github.com/KomodoPlatform/atomicDEX-API/pull/1658) - Cosmos integration `WIP` [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 70060976a8..8c55c5c366 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -22,7 +22,8 @@ // use super::eth::Action::{Call, Create}; #[cfg(feature = "enable-nft-integration")] -use crate::nft::nft_structs::{Chain, ContractType, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; +use crate::nft::nft_structs::{ContractType, ConvertChain, NftListReq, TransactionNftDetails, WithdrawErc1155, + WithdrawErc721}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry}; @@ -34,6 +35,7 @@ use common::{get_utc_timestamp, now_ms, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; use derive_more::Display; +use enum_from::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; use ethcore_transaction::{Action, Transaction as UnSignedEthTx, UnverifiedTransaction}; @@ -99,7 +101,7 @@ mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; #[cfg(feature = "enable-nft-integration")] -use crate::nft::WithdrawNftResult; +use crate::nft::{find_wallet_amount, WithdrawNftResult}; #[cfg(feature = "enable-nft-integration")] use crate::{lp_coinfind_or_err, MmCoinEnum, TransactionType}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; @@ -116,6 +118,8 @@ const SWAP_CONTRACT_ABI: &str = include_str!("eth/swap_contract_abi.json"); const ERC20_ABI: &str = include_str!("eth/erc20_abi.json"); /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); +/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md +const ERC1155_ABI: &str = include_str!("eth/erc1155_abi.json"); /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub const PAYMENT_STATE_UNINITIALIZED: u8 = 0; pub const PAYMENT_STATE_SENT: u8 = 1; @@ -155,11 +159,13 @@ lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); + pub static ref ERC1155_CONTRACT: Contract = Contract::load(ERC1155_ABI.as_bytes()).unwrap(); } pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; +type GasDetails = (U256, U256); #[derive(Debug, Display)] pub enum GasStationReqErr { @@ -750,40 +756,8 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }; let eth_value_dec = u256_to_big_decimal(eth_value, coin.decimals)?; - let (gas, gas_price) = match req.fee { - Some(WithdrawFee::EthGas { gas_price, gas }) => { - let gas_price = wei_from_big_decimal(&gas_price, 9)?; - (gas.into(), gas_price) - }, - Some(fee_policy) => { - let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - return MmError::err(WithdrawError::InvalidFeePolicy(error)); - }, - None => { - let gas_price = coin.get_gas_price().compat().await?; - // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH - let eth_value_for_estimate = if req.max && coin.coin_type == EthCoinType::Eth { - eth_value - gas_price * U256::from(21000) - } else { - eth_value - }; - let estimate_gas_req = CallRequest { - value: Some(eth_value_for_estimate), - data: Some(data.clone().into()), - from: Some(coin.my_address), - to: Some(call_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), - ..CallRequest::default() - }; - // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. - let gas_limit = coin.estimate_gas(estimate_gas_req).compat().await?; - (gas_limit, gas_price) - }, - }; + let (gas, gas_price) = + get_eth_gas_details(&coin, req.fee, eth_value, data.clone().into(), call_addr, req.max).await?; let total_fee = gas * gas_price; let total_fee_dec = u256_to_big_decimal(total_fee, coin.decimals)?; @@ -894,49 +868,50 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { } #[cfg(feature = "enable-nft-integration")] +/// `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, req: WithdrawErc1155) -> WithdrawNftResult { - let ticker = match req.chain { - Chain::Bsc => "BNB", - Chain::Eth => "ETH", + let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; + let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &req.to, &req.token_address)?; + let my_address = eth_coin.my_address()?; + + // todo check amount in nft cache, instead of sending new moralis req + // dont use `get_nft_metadata` for erc1155, it can return info related to other owner. + let nft_req = NftListReq { + chains: vec![req.chain], }; - let _coin = lp_coinfind_or_err(&ctx, ticker).await?; - unimplemented!() -} + let wallet_amount = find_wallet_amount(ctx, nft_req, req.token_address.clone(), req.token_id.clone()).await?; -#[cfg(feature = "enable-nft-integration")] -pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { - let ticker = match req.chain { - Chain::Bsc => "BNB", - Chain::Eth => "ETH", - }; - let coin = lp_coinfind_or_err(&ctx, ticker).await?; - let eth_coin = match coin { - MmCoinEnum::EthCoin(eth_coin) => eth_coin, - _ => { - return MmError::err(WithdrawError::CoinDoesntSupportNftWithdraw { - coin: coin.ticker().to_owned(), - }) - }, + let amount_dec = if req.max { + wallet_amount.clone() + } else { + req.amount.unwrap_or_else(|| 1.into()) }; - let from_addr = valid_addr_from_str(&req.from).map_to_mm(WithdrawError::InvalidAddress)?; - if eth_coin.my_address != from_addr { - return MmError::err(WithdrawError::AddressMismatchError { - my_address: eth_coin.my_address.to_string(), - from: req.from, + + if amount_dec > wallet_amount { + return MmError::err(WithdrawError::NotEnoughNftsAmount { + token_address: req.token_address, + token_id: req.token_id.to_string(), + available: wallet_amount, + required: amount_dec, }); } - let to_addr = valid_addr_from_str(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; - let token_addr = addr_from_str(&req.token_address).map_to_mm(WithdrawError::InvalidAddress)?; + let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { EthCoinType::Eth => { - let function = ERC721_CONTRACT.function("safeTransferFrom")?; + let function = ERC1155_CONTRACT.function("safeTransferFrom")?; let token_id_u256 = U256::from_dec_str(&req.token_id.to_string()) .map_err(|e| format!("{:?}", e)) .map_to_mm(NumConversError::new)?; + let amount_u256 = U256::from_dec_str(&amount_dec.to_string()) + .map_err(|e| format!("{:?}", e)) + .map_to_mm(NumConversError::new)?; let data = function.encode_input(&[ - Token::Address(from_addr), + Token::Address(eth_coin.my_address), Token::Address(to_addr), Token::Uint(token_id_u256), + Token::Uint(amount_u256), + Token::Bytes("0x".into()), ])?; (0.into(), data, token_addr, eth_coin.ticker()) }, @@ -946,34 +921,76 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu )) }, }; - let (gas, gas_price) = match req.fee { - Some(WithdrawFee::EthGas { gas_price, gas }) => { - let gas_price = wei_from_big_decimal(&gas_price, 9)?; - (gas.into(), gas_price) - }, - Some(fee_policy) => { - let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - return MmError::err(WithdrawError::InvalidFeePolicy(error)); + let (gas, gas_price) = + get_eth_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr, false).await?; + let _nonce_lock = eth_coin.nonce_lock.lock().await; + let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) + .compat() + .timeout_secs(30.) + .await? + .map_to_mm(WithdrawError::Transport)?; + + let tx = UnSignedEthTx { + nonce, + value: eth_value, + action: Action::Call(call_addr), + data, + gas, + gas_price, + }; + + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); + let signed = tx.sign(secret, eth_coin.chain_id); + let signed_bytes = rlp::encode(&signed); + let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + + Ok(TransactionNftDetails { + tx_hex: BytesJson::from(signed_bytes.to_vec()), + tx_hash: format!("{:02x}", signed.tx_hash()), + from: vec![my_address], + to: vec![req.to], + contract_type: ContractType::Erc1155, + token_address: req.token_address, + token_id: req.token_id, + amount: amount_dec, + fee_details: Some(fee_details.into()), + coin: eth_coin.ticker.clone(), + block_height: 0, + timestamp: now_ms() / 1000, + internal_id: 0, + transaction_type: TransactionType::NftTransfer, + }) +} + +#[cfg(feature = "enable-nft-integration")] +/// `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, req: WithdrawErc721) -> WithdrawNftResult { + let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; + let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &req.to, &req.token_address)?; + let my_address = eth_coin.my_address()?; + + let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { + EthCoinType::Eth => { + let function = ERC721_CONTRACT.function("safeTransferFrom")?; + let token_id_u256 = U256::from_dec_str(&req.token_id.to_string()) + .map_err(|e| format!("{:?}", e)) + .map_to_mm(NumConversError::new)?; + let data = function.encode_input(&[ + Token::Address(eth_coin.my_address), + Token::Address(to_addr), + Token::Uint(token_id_u256), + ])?; + (0.into(), data, token_addr, eth_coin.ticker()) }, - None => { - let gas_price = eth_coin.get_gas_price().compat().await?; - let estimate_gas_req = CallRequest { - value: Some(eth_value), - data: Some(data.clone().into()), - from: Some(eth_coin.my_address), - to: Some(call_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), - ..CallRequest::default() - }; - // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. - let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; - (gas_limit, gas_price) + EthCoinType::Erc20 { .. } => { + return MmError::err(WithdrawError::InternalError( + "Erc20 coin type doesnt support withdraw nft".to_owned(), + )) }, }; + let (gas, gas_price) = + get_eth_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr, false).await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -989,14 +1006,16 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu gas, gas_price, }; + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), tx_hash: format!("{:02x}", signed.tx_hash()), - from: vec![req.from], + from: vec![my_address], to: vec![req.to], contract_type: ContractType::Erc721, token_address: req.token_address, @@ -5076,7 +5095,7 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 } } -#[derive(Debug, Deserialize, Serialize, Display)] +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum GetEthAddressError { PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), EthActivationV2Error(EthActivationV2Error), @@ -5109,3 +5128,105 @@ pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult MmResult<(Address, Address, EthCoin), GetValidEthWithdrawAddError> { + let eth_coin = match coin_enum { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(GetValidEthWithdrawAddError::CoinDoesntSupportNftWithdraw { + coin: coin_enum.ticker().to_owned(), + }) + }, + }; + let to_addr = valid_addr_from_str(to).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; + let token_addr = addr_from_str(token_add).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; + Ok((to_addr, token_addr, eth_coin)) +} + +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)] +pub enum EthGasDetailsErr { + #[display(fmt = "Invalid fee policy: {}", _0)] + InvalidFeePolicy(String), + #[from_stringify("NumConversError")] + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + #[display(fmt = "Transport: {}", _0)] + Transport(String), +} + +impl From for EthGasDetailsErr { + fn from(e: web3::Error) -> Self { EthGasDetailsErr::from(Web3RpcError::from(e)) } +} + +impl From for EthGasDetailsErr { + fn from(e: Web3RpcError) -> Self { + match e { + Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthGasDetailsErr::Transport(tr), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => EthGasDetailsErr::Internal(internal), + } + } +} + +async fn get_eth_gas_details( + eth_coin: &EthCoin, + fee: Option, + eth_value: U256, + data: Bytes, + call_addr: Address, + fungible_max: bool, +) -> MmResult { + match fee { + Some(WithdrawFee::EthGas { gas_price, gas }) => { + let gas_price = wei_from_big_decimal(&gas_price, 9)?; + Ok((gas.into(), gas_price)) + }, + Some(fee_policy) => { + let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); + MmError::err(EthGasDetailsErr::InvalidFeePolicy(error)) + }, + None => { + let gas_price = eth_coin.get_gas_price().compat().await?; + // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH + let eth_value_for_estimate = if fungible_max && eth_coin.coin_type == EthCoinType::Eth { + eth_value - gas_price * U256::from(21000) + } else { + eth_value + }; + let estimate_gas_req = CallRequest { + value: Some(eth_value_for_estimate), + data: Some(data), + from: Some(eth_coin.my_address), + to: Some(call_addr), + gas: None, + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + gas_price: Some(gas_price), + ..CallRequest::default() + }; + // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; + Ok((gas_limit, gas_price)) + }, + } +} diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index d2251a6686..f3eda4d4ff 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -6,7 +6,7 @@ use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; -#[derive(Debug, Deserialize, Display, EnumFromTrait, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Deserialize, Display, EnumFromTrait, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum EthActivationV2Error { InvalidPayload(String), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 3546277283..78118f54b7 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -208,7 +208,10 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut}; pub mod coins_tests; pub mod eth; -use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; +#[cfg(feature = "enable-nft-integration")] +use eth::GetValidEthWithdrawAddError; +use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, + GetEthAddressError, SignedEthTx}; pub mod hd_confirm_address; pub mod hd_pubkey; @@ -279,6 +282,8 @@ use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; #[cfg(feature = "enable-nft-integration")] pub mod nft; +#[cfg(feature = "enable-nft-integration")] +use nft::nft_errors::GetNftInfoError; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; @@ -427,7 +432,7 @@ pub enum TxHistoryError { InternalError(String), } -#[derive(Clone, Debug, Display, Deserialize)] +#[derive(Clone, Debug, Deserialize, Display, PartialEq)] pub enum PrivKeyPolicyNotAllowed { #[display(fmt = "Hardware Wallet is not supported")] HardwareWalletNotSupported, @@ -1770,7 +1775,7 @@ impl DelegationError { } } -#[derive(Clone, Debug, Display, EnumFromStringify, EnumFromTrait, Serialize, SerializeErrorType, PartialEq)] +#[derive(Clone, Debug, Display, EnumFromStringify, EnumFromTrait, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum WithdrawError { #[display( @@ -1833,14 +1838,38 @@ pub enum WithdrawError { #[from_stringify("NumConversError", "UnexpectedDerivationMethod", "PrivKeyPolicyNotAllowed")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[cfg(feature = "enable-nft-integration")] #[display(fmt = "{} coin doesn't support NFT withdrawing", coin)] CoinDoesntSupportNftWithdraw { coin: String }, + #[cfg(feature = "enable-nft-integration")] #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] AddressMismatchError { my_address: String, from: String }, + #[cfg(feature = "enable-nft-integration")] #[display(fmt = "Contract type {} doesnt support 'withdraw_nft' yet", _0)] ContractTypeDoesntSupportNftWithdrawing(String), #[display(fmt = "Action not allowed for coin: {}", _0)] ActionNotAllowed(String), + #[cfg(feature = "enable-nft-integration")] + GetNftInfoError(GetNftInfoError), + #[cfg(feature = "enable-nft-integration")] + #[display( + fmt = "Not enough NFTs amount with token_address: {} and token_id {}. Available {}, required {}", + token_address, + token_id, + available, + required + )] + NotEnoughNftsAmount { + token_address: String, + token_id: String, + available: BigDecimal, + required: BigDecimal, + }, +} + +#[cfg(feature = "enable-nft-integration")] +impl From for WithdrawError { + fn from(e: GetNftInfoError) -> Self { WithdrawError::GetNftInfoError(e) } } impl HttpStatusCode for WithdrawError { @@ -1860,14 +1889,17 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnexpectedFromAddress(_) | WithdrawError::UnknownAccount { .. } | WithdrawError::UnexpectedUserAction { .. } - | WithdrawError::CoinDoesntSupportNftWithdraw { .. } - | WithdrawError::AddressMismatchError { .. } - | WithdrawError::ActionNotAllowed(_) - | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) => StatusCode::BAD_REQUEST, + | WithdrawError::ActionNotAllowed(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, WithdrawError::Transport(_) | WithdrawError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, + #[cfg(feature = "enable-nft-integration")] + WithdrawError::GetNftInfoError(_) + | WithdrawError::AddressMismatchError { .. } + | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) + | WithdrawError::CoinDoesntSupportNftWithdraw { .. } + | WithdrawError::NotEnoughNftsAmount { .. } => StatusCode::BAD_REQUEST, } } } @@ -1902,6 +1934,31 @@ impl From for WithdrawError { fn from(e: TimeoutError) -> Self { WithdrawError::Timeout(e.duration) } } +#[cfg(feature = "enable-nft-integration")] +impl From for WithdrawError { + fn from(e: GetValidEthWithdrawAddError) -> Self { + match e { + GetValidEthWithdrawAddError::AddressMismatchError { my_address, from } => { + WithdrawError::AddressMismatchError { my_address, from } + }, + GetValidEthWithdrawAddError::CoinDoesntSupportNftWithdraw { coin } => { + WithdrawError::CoinDoesntSupportNftWithdraw { coin } + }, + GetValidEthWithdrawAddError::InvalidAddress(e) => WithdrawError::InvalidAddress(e), + } + } +} + +impl From for WithdrawError { + fn from(e: EthGasDetailsErr) -> Self { + match e { + EthGasDetailsErr::InvalidFeePolicy(e) => WithdrawError::InvalidFeePolicy(e), + EthGasDetailsErr::Internal(e) => WithdrawError::InternalError(e), + EthGasDetailsErr::Transport(e) => WithdrawError::Transport(e), + } + } +} + impl WithdrawError { /// Construct [`WithdrawError`] from [`GenerateTxError`] using additional `coin` and `decimals`. pub fn from_generate_tx_error(gen_tx_err: GenerateTxError, coin: String, decimals: u8) -> WithdrawError { diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 0dc9a0aef3..2b768738bd 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -6,12 +6,14 @@ pub(crate) mod nft_structs; use crate::WithdrawError; use nft_errors::GetNftInfoError; -use nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, - NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, WithdrawNftReq}; +use nft_structs::{Chain, ConvertChain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, + NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, + TransactionNftDetails, WithdrawNftReq}; -use crate::eth::{get_eth_address, withdraw_erc721}; +use crate::eth::{get_eth_address, withdraw_erc1155, withdraw_erc721}; use common::{APPLICATION_JSON, X_API_KEY}; use http::header::ACCEPT; +use mm2_number::BigDecimal; use serde_json::Value as Json; /// url for moralis requests @@ -23,7 +25,7 @@ const DIRECTION_BOTH_MORALIS: &str = "direction=both"; pub type WithdrawNftResult = Result>; -/// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. +/// `get_nft_list` function returns list of NFTs on requested chains owned by user. pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { let api_key = ctx.conf["api_key"] .as_str() @@ -32,11 +34,8 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("BNB", "bsc"), - Chain::Eth => ("ETH", "eth"), - }; - let my_address = get_eth_address(&ctx, coin_str).await?; + let (coin_str, chain_str) = chain.to_ticker_chain(); + let my_address = get_eth_address(&ctx, &coin_str).await?; let uri_without_cursor = format!( "{}{}/nft?chain={}&{}", URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS @@ -83,10 +82,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult MmResult { let api_key = ctx.conf["api_key"] .as_str() .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; let chain_str = match req.chain { + Chain::Avalanche => "avalanche", Chain::Bsc => "bsc", Chain::Eth => "eth", + Chain::Fantom => "fantom", + Chain::Polygon => "polygon", }; let uri = format!( "{}nft/{}/{}?chain={}&{}", @@ -129,7 +132,7 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult { let api_key = ctx.conf["api_key"] @@ -140,8 +143,11 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult ("AVAX", "avalanche"), Chain::Bsc => ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), + Chain::Fantom => ("FTM", "fantom"), + Chain::Polygon => ("MATIC", "polygon"), }; let my_address = get_eth_address(&ctx, coin_str).await?; let uri_without_cursor = format!( @@ -192,7 +198,6 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult MmResult WithdrawNftResult { match req_type { - WithdrawNftReq::WithdrawErc1155(_) => MmError::err(WithdrawError::ContractTypeDoesntSupportNftWithdrawing( - "ERC1155".to_owned(), - )), + WithdrawNftReq::WithdrawErc1155(erc1155_req) => withdraw_erc1155(ctx, erc1155_req).await, WithdrawNftReq::WithdrawErc721(erc721_req) => withdraw_erc721(ctx, erc721_req).await, } } @@ -265,3 +267,21 @@ async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult MmResult { + let nft_list = get_nft_list(ctx, nft_list).await?.nfts; + let nft = nft_list + .into_iter() + .find(|nft| nft.token_address == token_address_req && nft.token_id == token_id_req) + .ok_or_else(|| GetNftInfoError::TokenNotFoundInWallet { + token_address: token_address_req, + token_id: token_id_req.to_string(), + })?; + Ok(nft.amount) +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index d48753266b..bd510108ab 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -7,7 +7,7 @@ use mm2_net::transport::SlurpError; use serde::{Deserialize, Serialize}; use web3::Error; -#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetNftInfoError { /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. @@ -24,6 +24,15 @@ pub enum GetNftInfoError { GetEthAddressError(GetEthAddressError), #[display(fmt = "X-API-Key is missing")] ApiKeyError, + #[display( + fmt = "Token: token_address {}, token_id {} was not find in wallet", + token_address, + token_id + )] + TokenNotFoundInWallet { + token_address: String, + token_id: String, + }, } impl From for GetNftInfoError { @@ -60,9 +69,10 @@ impl HttpStatusCode for GetNftInfoError { GetNftInfoError::InvalidRequest(_) => StatusCode::BAD_REQUEST, GetNftInfoError::InvalidResponse(_) => StatusCode::FAILED_DEPENDENCY, GetNftInfoError::ApiKeyError => StatusCode::FORBIDDEN, - GetNftInfoError::Transport(_) | GetNftInfoError::Internal(_) | GetNftInfoError::GetEthAddressError(_) => { - StatusCode::INTERNAL_SERVER_ERROR - }, + GetNftInfoError::Transport(_) + | GetNftInfoError::Internal(_) + | GetNftInfoError::GetEthAddressError(_) + | GetNftInfoError::TokenNotFoundInWallet { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index efe89cfab4..09e247967c 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -19,8 +19,39 @@ pub struct NftMetadataReq { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] pub(crate) enum Chain { + Avalanche, Bsc, Eth, + Fantom, + Polygon, +} + +pub(crate) trait ConvertChain { + fn to_ticker(&self) -> String; + + fn to_ticker_chain(&self) -> (String, String); +} + +impl ConvertChain for Chain { + fn to_ticker(&self) -> String { + 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(), + } + } + + fn to_ticker_chain(&self) -> (String, String) { + match self { + Chain::Avalanche => ("AVAX".to_owned(), "avalanche".to_owned()), + Chain::Bsc => ("BNB".to_owned(), "bsc".to_owned()), + Chain::Eth => ("ETH".to_owned(), "eth".to_owned()), + Chain::Fantom => ("FTM".to_owned(), "fantom".to_owned()), + Chain::Polygon => ("MATIC".to_owned(), "polygon".to_owned()), + } + } } #[derive(Debug, Display)] @@ -114,28 +145,24 @@ impl std::ops::Deref for SerdeStringWrap { #[derive(Debug, Serialize)] pub struct NftList { - pub(crate) count: u64, pub(crate) nfts: Vec, } -#[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc1155 { pub(crate) chain: Chain, - from: String, - to: String, - token_address: String, - token_id: BigDecimal, - amount: BigDecimal, + pub(crate) to: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: Option, #[serde(default)] - max: bool, - fee: Option, + pub(crate) max: bool, + pub(crate) fee: Option, } #[derive(Clone, Deserialize)] pub struct WithdrawErc721 { pub(crate) chain: Chain, - pub(crate) from: String, pub(crate) to: String, pub(crate) token_address: String, pub(crate) token_id: BigDecimal, @@ -151,7 +178,7 @@ pub enum WithdrawNftReq { #[derive(Debug, Deserialize, Serialize)] pub struct TransactionNftDetails { - /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction + /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction` RPC to broadcast the transaction pub(crate) tx_hex: BytesJson, pub(crate) tx_hash: String, /// NFTs are sent from these addresses @@ -226,6 +253,5 @@ pub(crate) struct NftTransferHistoryWrapper { #[derive(Debug, Serialize)] pub struct NftsTransferHistoryList { - pub(crate) count: u64, pub(crate) transfer_history: Vec, } From 759d180f4fea5ce0d6c40006728d3c6eec87c783 Mon Sep 17 00:00:00 2001 From: Onur Date: Fri, 24 Mar 2023 21:17:24 +0300 Subject: [PATCH 79/79] fix: build linux x86-64 with reasonable glibc (#1733) * use `ubuntu-18.04` for dev and release builds Signed-off-by: ozkanonur * always cancel previous `in-progress` pipelines when new commit pushed Signed-off-by: ozkanonur * switch from `ubuntu-latest` to `ubuntu-18.04` for all builds Signed-off-by: ozkanonur * turn off share generics nightly feature Signed-off-by: ozkanonur * set JEMALLOC environments in CI Signed-off-by: ozkanonur * rollback jemalloc entry from changelog Signed-off-by: ozkanonur --------- Signed-off-by: ozkanonur Reviewed-by: shamardy --- .cargo/config | 2 +- .github/workflows/dev-build.yml | 15 ++++++++++----- .github/workflows/fmt-and-lint.yml | 4 ++++ .github/workflows/release-build.yml | 13 +++++++++---- .github/workflows/test.yml | 4 ++++ .github/workflows/virustotal_scan.yml | 4 ++++ CHANGELOG.md | 1 - 7 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.cargo/config b/.cargo/config index 564b1fb152..c81c668c82 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,5 +1,5 @@ [env] -JEMALLOC_SYS_WITH_MALLOC_CONF = "percpu_arena:percpu,oversize_threshold:0,background_thread:true,metadata_thp:auto,dirty_decay_ms:5000,muzzy_decay_ms:5000" +JEMALLOC_SYS_WITH_MALLOC_CONF = "background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto" [target.'cfg(all())'] rustflags = [ "-Zshare-generics=y" ] diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 7bbb5f5f92..900048e6ae 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -7,14 +7,19 @@ on: branches: - dev +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} MANUAL_MM_VERSION: true + JEMALLOC_SYS_WITH_MALLOC_CONF: 'background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto' jobs: linux-x86-64: timeout-minutes: 30 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -193,7 +198,7 @@ jobs: wasm: timeout-minutes: 30 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -281,7 +286,7 @@ jobs: android-aarch64: timeout-minutes: 30 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -329,7 +334,7 @@ jobs: android-armv7: timeout-minutes: 30 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -380,7 +385,7 @@ jobs: if: github.event_name != 'pull_request' needs: linux-x86-64 timeout-minutes: 15 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/fmt-and-lint.yml b/.github/workflows/fmt-and-lint.yml index 6ee8a2c1a2..b9c5c92701 100644 --- a/.github/workflows/fmt-and-lint.yml +++ b/.github/workflows/fmt-and-lint.yml @@ -9,6 +9,10 @@ on: - main - dev +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: fmt-and-lint: timeout-minutes: 45 diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 21820340a1..9cf009f810 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -4,14 +4,19 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} MANUAL_MM_VERSION: true + JEMALLOC_SYS_WITH_MALLOC_CONF: 'background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto' jobs: linux-x86-64: timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -188,7 +193,7 @@ jobs: wasm: timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -276,7 +281,7 @@ jobs: android-aarch64: timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain @@ -324,7 +329,7 @@ jobs: android-armv7: timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 - name: Install toolchain diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae9b84fe07..5cd82b5311 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,10 @@ on: - main - dev +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + env: FROM_SHARED_RUNNER: true diff --git a/.github/workflows/virustotal_scan.yml b/.github/workflows/virustotal_scan.yml index 551990ddc0..b292e47050 100644 --- a/.github/workflows/virustotal_scan.yml +++ b/.github/workflows/virustotal_scan.yml @@ -4,6 +4,10 @@ on: release: types: [created, edited, released, published] +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: virustotal: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 20713685a6..0ae1785eb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,7 +32,6 @@ - CI/CD migrated from Azure to Github runners [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) - CI tests are much stabilized [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) - Integration and unit tests are seperated on CI stack [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) -- Jemalloc configuration updated for optimization purposes [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) - Codebase is updated in linting rules at wasm and test stack [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) - `crossbeam` bumped to `0.8` from `0.7` [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699) - Un-used/Unstable parts of mm2 excluded from build outputs which avoids mm2 runtime from potential UB [#1699](https://github.com/KomodoPlatform/atomicDEX-API/pull/1699)