diff --git a/Cargo.lock b/Cargo.lock index 559f45feb0..398dcb182c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1548,6 +1548,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" name = "crypto" version = "1.0.0" dependencies = [ + "arrayref", "async-trait", "bip32 0.2.2", "bitcrypto", @@ -1574,6 +1575,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "tiny-bip39", "trezor", ] @@ -4397,14 +4399,12 @@ dependencies = [ "futures 0.3.15", "gstuff", "hex 0.4.2", - "keys", "lazy_static", "mm2_metrics", "mm2_rpc", "primitives", "rand 0.7.3", "serde", - "serde_bytes", "serde_json", "shared_ref_counter", "uuid 0.7.4", @@ -4665,6 +4665,7 @@ dependencies = [ "chrono", "common", "crossterm", + "crypto", "db_common", "futures 0.3.15", "gstuff", diff --git a/mm2src/coins/coin_balance.rs b/mm2src/coins/coin_balance.rs index 305ead0ce7..e1e2bceb07 100644 --- a/mm2src/coins/coin_balance.rs +++ b/mm2src/coins/coin_balance.rs @@ -128,7 +128,7 @@ where { async fn coin_balance_report(&self) -> BalanceResult { match self.derivation_method() { - DerivationMethod::Iguana(my_address) => self + DerivationMethod::SingleAddress(my_address) => self .my_balance() .compat() .await @@ -176,7 +176,7 @@ where XPubExtractor: HDXPubExtractor + Sync, { match self.derivation_method() { - DerivationMethod::Iguana(my_address) => self + DerivationMethod::SingleAddress(my_address) => self .my_balance() .compat() .await diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cb66673a7b..a69fa8e391 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -62,10 +62,10 @@ use web3_transport::{EthFeeHistoryNamespace, Web3Transport, Web3TransportNode}; use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, CoinProtocol, CoinTransportMetrics, CoinsContext, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, MyAddressError, NegotiateSwapContractAddrErr, NumConversError, NumConversResult, - PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionRes, RawTransactionResult, RpcClientType, RpcTransportEventHandler, - RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, + RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, + SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureError, SignatureResult, SwapOps, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, @@ -80,6 +80,7 @@ pub use rlp; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; +use v2_activation::key_pair_from_priv_key_policy; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.0.6:8565) contract address: 0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd @@ -3579,8 +3580,8 @@ pub async fn eth_coin_from_conf_and_request( ticker: &str, conf: &Json, req: &Json, - priv_key: &[u8], protocol: CoinProtocol, + priv_key_policy: PrivKeyBuildPolicy, ) -> Result { let mut urls: Vec = try_s!(json::from_value(req["urls"].clone())); if urls.is_empty() { @@ -3610,7 +3611,7 @@ pub async fn eth_coin_from_conf_and_request( } } - let key_pair: KeyPair = try_s!(KeyPair::from_secret_slice(priv_key)); + let key_pair = try_s!(key_pair_from_priv_key_policy(conf, priv_key_policy)); let my_address = key_pair.address(); let mut web3_instances = vec![]; diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 81438d41fc..f8588032d8 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,4 +1,5 @@ use super::*; +use crate::IguanaPrivKey; use common::block_on; use mm2_core::mm_ctx::{MmArc, MmCtxBuilder}; use mm2_test_helpers::for_tests::{ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT}; @@ -1230,14 +1231,14 @@ fn polygon_check_if_my_payment_sent() { "swap_contract_address": "0x9130b257d37a52e52f21054c4da3450c72f595ce", }); - let priv_key = [1; 32]; + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(IguanaPrivKey::from([1; 32])); let coin = block_on(eth_coin_from_conf_and_request( &ctx, "MATIC", &conf, &request, - &priv_key, CoinProtocol::ETH, + priv_key_policy, )) .unwrap(); @@ -1445,13 +1446,14 @@ fn test_eth_validate_valid_and_invalid_pubkey() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(IguanaPrivKey::from(priv_key)); let coin = block_on(eth_coin_from_conf_and_request( &ctx, "MATIC", &conf, &request, - &priv_key, CoinProtocol::ETH, + priv_key_policy, )) .unwrap(); // Test expected to pass at this point as we're using a valid pubkey to validate against a valid pubkey diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 4247cc97f4..1aeb7ff1dd 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,5 +1,6 @@ use super::*; use common::executor::AbortedError; +use crypto::StandardHDPathToCoin; #[derive(Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] @@ -16,6 +17,11 @@ pub enum EthActivationV2Error { UnreachableNodes(String), #[display(fmt = "Enable request for ETH coin must have at least 1 node")] AtLeastOneNodeRequired, + #[display(fmt = "'derivation_path' field is not found in config")] + DerivationPathIsNotSet, + #[display(fmt = "Error deserializing 'derivation_path': {}", _0)] + ErrorDeserializingDerivationPath(String), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), InternalError(String), } @@ -141,8 +147,8 @@ pub async fn eth_coin_from_conf_and_request_v2( ticker: &str, conf: &Json, req: EthActivationV2Request, - priv_key: &[u8], -) -> Result> { + priv_key_policy: PrivKeyBuildPolicy, +) -> MmResult { if req.nodes.is_empty() { return Err(EthActivationV2Error::AtLeastOneNodeRequired.into()); } @@ -182,8 +188,7 @@ pub async fn eth_coin_from_conf_and_request_v2( } } - let key_pair: KeyPair = - KeyPair::from_secret_slice(priv_key).map_err(|e| EthActivationV2Error::InternalError(e.to_string()))?; + let key_pair = key_pair_from_priv_key_policy(conf, priv_key_policy)?; let my_address = checksum_address(&format!("{:02x}", key_pair.address())); let mut web3_instances = vec![]; @@ -272,3 +277,29 @@ pub async fn eth_coin_from_conf_and_request_v2( Ok(EthCoin(Arc::new(coin))) } + +/// Processes the given `priv_key_policy` and generates corresponding `KeyPair`. +/// This function expects either [`PrivKeyBuildPolicy::IguanaPrivKey`] +/// or [`PrivKeyBuildPolicy::GlobalHDAccount`], otherwise returns `PrivKeyPolicyNotAllowed` error. +pub(crate) fn key_pair_from_priv_key_policy( + conf: &Json, + priv_key_policy: PrivKeyBuildPolicy, +) -> MmResult { + let priv_key = match priv_key_policy { + PrivKeyBuildPolicy::IguanaPrivKey(iguana) => iguana, + PrivKeyBuildPolicy::GlobalHDAccount(global_hd_ctx) => { + // Consider storing `derivation_path` at `EthCoinImpl`. + let derivation_path: Option = json::from_value(conf["derivation_path"].clone()) + .map_to_mm(|e| EthActivationV2Error::ErrorDeserializingDerivationPath(e.to_string()))?; + let derivation_path = derivation_path.or_mm_err(|| EthActivationV2Error::DerivationPathIsNotSet)?; + global_hd_ctx + .derive_secp256k1_secret(&derivation_path) + .mm_err(|e| EthActivationV2Error::InternalError(e.to_string()))? + }, + PrivKeyBuildPolicy::Trezor => { + let priv_key_err = PrivKeyPolicyNotAllowed::HardwareWalletNotSupported; + return MmError::err(EthActivationV2Error::PrivKeyPolicyNotAllowed(priv_key_err)); + }, + }; + KeyPair::from_secret_slice(priv_key.as_slice()).map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string())) +} diff --git a/mm2src/coins/hd_pubkey.rs b/mm2src/coins/hd_pubkey.rs index d0c2fce6df..0d2ad49ea2 100644 --- a/mm2src/coins/hd_pubkey.rs +++ b/mm2src/coins/hd_pubkey.rs @@ -4,7 +4,7 @@ use crypto::hw_rpc_task::{HwConnectStatuses, TrezorRpcTaskConnectProcessor}; use crypto::trezor::trezor_rpc_task::{TrezorRpcTaskProcessor, TryIntoUserAction}; use crypto::trezor::utxo::IGNORE_XPUB_MAGIC; use crypto::trezor::{ProcessTrezorResponse, TrezorError, TrezorProcessingError}; -use crypto::{CryptoCtx, CryptoInitError, DerivationPath, EcdsaCurve, HardwareWalletArc, HwError, HwProcessingError, +use crypto::{CryptoCtx, CryptoCtxError, DerivationPath, EcdsaCurve, HardwareWalletArc, HwError, HwProcessingError, XPub, XPubConverter, XpubError}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -22,8 +22,8 @@ pub enum HDExtractPubkeyError { Internal(String), } -impl From for HDExtractPubkeyError { - fn from(e: CryptoInitError) -> Self { HDExtractPubkeyError::Internal(e.to_string()) } +impl From for HDExtractPubkeyError { + fn from(e: CryptoCtxError) -> Self { HDExtractPubkeyError::Internal(e.to_string()) } } impl From for HDExtractPubkeyError { diff --git a/mm2src/coins/hd_wallet.rs b/mm2src/coins/hd_wallet.rs index 84be4564f9..795af06d03 100644 --- a/mm2src/coins/hd_wallet.rs +++ b/mm2src/coins/hd_wallet.rs @@ -2,8 +2,8 @@ use crate::hd_pubkey::HDXPubExtractor; use crate::hd_wallet_storage::HDWalletStorageError; use crate::{BalanceError, WithdrawError}; use async_trait::async_trait; -use crypto::{Bip32DerPathError, Bip32Error, Bip44Chain, Bip44DerPathError, Bip44DerivationPath, ChildNumber, - DerivationPath, HwError}; +use crypto::{Bip32DerPathError, Bip32Error, Bip44Chain, ChildNumber, DerivationPath, HwError, StandardHDPath, + StandardHDPathError}; use derive_more::Display; use itertools::Itertools; use mm2_err_handle::prelude::*; @@ -125,7 +125,9 @@ pub enum NewAccountCreatingError { } impl From for NewAccountCreatingError { - fn from(e: Bip32DerPathError) -> Self { NewAccountCreatingError::Internal(Bip44DerPathError::from(e).to_string()) } + fn from(e: Bip32DerPathError) -> Self { + NewAccountCreatingError::Internal(StandardHDPathError::from(e).to_string()) + } } impl From for NewAccountCreatingError { @@ -190,8 +192,8 @@ pub struct HDAccountAddressId { pub address_id: u32, } -impl From for HDAccountAddressId { - fn from(der_path: Bip44DerivationPath) -> Self { +impl From for HDAccountAddressId { + fn from(der_path: StandardHDPath) -> Self { HDAccountAddressId { account_id: der_path.account_id(), chain: der_path.chain(), diff --git a/mm2src/coins/hd_wallet_storage/mod.rs b/mm2src/coins/hd_wallet_storage/mod.rs index 7d8582d782..ce94973d2c 100644 --- a/mm2src/coins/hd_wallet_storage/mod.rs +++ b/mm2src/coins/hd_wallet_storage/mod.rs @@ -1,6 +1,6 @@ use crate::hd_wallet::HDWalletCoinOps; use async_trait::async_trait; -use crypto::{CryptoCtx, CryptoInitError, XPub}; +use crypto::{CryptoCtx, CryptoCtxError, XPub}; use derive_more::Display; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -49,8 +49,8 @@ pub enum HDWalletStorageError { Internal(String), } -impl From for HDWalletStorageError { - fn from(e: CryptoInitError) -> Self { HDWalletStorageError::Internal(e.to_string()) } +impl From for HDWalletStorageError { + fn from(e: CryptoCtxError) -> Self { HDWalletStorageError::Internal(e.to_string()) } } impl HDWalletStorageError { @@ -226,7 +226,7 @@ impl HDWalletCoinStorage { let inner = Box::new(HDWalletStorageInstance::init(ctx).await?); let crypto_ctx = CryptoCtx::from_ctx(ctx)?; let hd_wallet_rmd160 = crypto_ctx - .hd_wallet_rmd160() + .hw_wallet_rmd160() .or_mm_err(|| HDWalletStorageError::HDWalletUnavailable)?; Ok(HDWalletCoinStorage { coin, diff --git a/mm2src/coins/lightning/ln_errors.rs b/mm2src/coins/lightning/ln_errors.rs index a9dd9f8034..5b349fd43a 100644 --- a/mm2src/coins/lightning/ln_errors.rs +++ b/mm2src/coins/lightning/ln_errors.rs @@ -1,4 +1,5 @@ use crate::utxo::rpc_clients::UtxoRpcError; +use crate::PrivKeyPolicyNotAllowed; use common::executor::AbortedError; use common::HttpStatusCode; use db_common::sqlite::rusqlite::Error as SqlError; @@ -11,7 +12,7 @@ use std::num::TryFromIntError; pub type EnableLightningResult = Result>; pub type SaveChannelClosingResult = Result>; -#[derive(Clone, Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum EnableLightningError { #[display(fmt = "Invalid request: {}", _0)] @@ -26,6 +27,8 @@ pub enum EnableLightningError { InvalidAddress(String), #[display(fmt = "Invalid path: {}", _0)] InvalidPath(String), + #[display(fmt = "Private key policy is not allowed: {}", _0)] + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), #[display(fmt = "System time error {}", _0)] SystemTimeError(String), #[display(fmt = "RPC error {}", _0)] @@ -42,7 +45,9 @@ pub enum EnableLightningError { impl HttpStatusCode for EnableLightningError { fn status_code(&self) -> StatusCode { match self { - EnableLightningError::InvalidRequest(_) | EnableLightningError::RpcError(_) => StatusCode::BAD_REQUEST, + EnableLightningError::InvalidRequest(_) + | EnableLightningError::RpcError(_) + | EnableLightningError::PrivKeyPolicyNotAllowed(_) => StatusCode::BAD_REQUEST, EnableLightningError::UnsupportedMode(_, _) => StatusCode::NOT_IMPLEMENTED, EnableLightningError::InvalidAddress(_) | EnableLightningError::InvalidPath(_) @@ -69,6 +74,10 @@ impl From for EnableLightningError { fn from(e: UtxoRpcError) -> Self { EnableLightningError::RpcError(e.to_string()) } } +impl From for EnableLightningError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { EnableLightningError::PrivKeyPolicyNotAllowed(e) } +} + impl From for EnableLightningError { fn from(e: RpcTaskError) -> Self { EnableLightningError::RpcTaskError(e.to_string()) } } diff --git a/mm2src/coins/lightning/ln_events.rs b/mm2src/coins/lightning/ln_events.rs index eb889c8273..728796593e 100644 --- a/mm2src/coins/lightning/ln_events.rs +++ b/mm2src/coins/lightning/ln_events.rs @@ -208,7 +208,7 @@ fn sign_funding_transaction( let my_address = coin .as_ref() .derivation_method - .iguana_or_err() + .single_addr_or_err() .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; let key_pair = coin .as_ref() @@ -526,7 +526,7 @@ impl LightningEventHandler { } // Todo: add support for Hardware wallets for funding transactions and spending spendable outputs (channel closing transactions) - let my_address = match self.platform.coin.as_ref().derivation_method.iguana_or_err() { + let my_address = match self.platform.coin.as_ref().derivation_method.single_addr_or_err() { Ok(addr) => addr.clone(), Err(e) => { error!("{}", e); diff --git a/mm2src/coins/lightning/ln_utils.rs b/mm2src/coins/lightning/ln_utils.rs index 651f7271f1..49bc23d666 100644 --- a/mm2src/coins/lightning/ln_utils.rs +++ b/mm2src/coins/lightning/ln_utils.rs @@ -79,9 +79,17 @@ pub async fn init_db(ctx: &MmArc, ticker: String) -> EnableLightningResult EnableLightningResult> { +pub fn init_keys_manager(platform: &Platform) -> EnableLightningResult> { // The current time is used to derive random numbers from the seed where required, to ensure all random generation is unique across restarts. - let seed: [u8; 32] = ctx.secp256k1_key_pair().private().secret.into(); + // TODO validate that this is right + let seed: [u8; 32] = platform + .coin + .as_ref() + .priv_key_policy + .key_pair_or_err()? + .private() + .secret + .into(); let cur = get_local_duration_since_epoch().map_to_mm(|e| EnableLightningError::SystemTimeError(e.to_string()))?; Ok(Arc::new(KeysManager::new(&seed, cur.as_secs(), cur.subsec_nanos()))) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 4e925313b2..c2466f7ecd 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -38,7 +38,8 @@ use base58::FromBase58Error; use common::executor::{abortable_queue::{AbortableQueue, WeakSpawner}, AbortSettings, SpawnAbortable, SpawnFuture}; use common::{calc_total_pages, now_ms, ten, HttpStatusCode}; -use crypto::{Bip32Error, CryptoCtx, DerivationPath, HwRpcError, WithHwRpcError}; +use crypto::{Bip32Error, CryptoCtx, DerivationPath, GlobalHDAccountArc, HwRpcError, KeyPairPolicy, Secp256k1Secret, + WithHwRpcError}; use derive_more::Display; use enum_from::EnumFromTrait; use futures::compat::Future01CompatExt; @@ -53,7 +54,7 @@ use mm2_metrics::MetricsWeak; use mm2_number::{bigdecimal::{BigDecimal, ParseBigDecimalError, Zero}, MmNumber}; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; -use serde::{Deserialize, Deserializer, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{self as json, Value as Json}; use std::cmp::Ordering; use std::collections::hash_map::{HashMap, RawEntryMut}; @@ -207,7 +208,7 @@ pub mod hd_wallet_storage; pub mod my_tx_history_v2; pub mod qrc20; -use qrc20::{qrc20_coin_from_conf_and_params, Qrc20ActivationParams, Qrc20Coin, Qrc20FeeDetails}; +use qrc20::{qrc20_coin_with_policy, Qrc20ActivationParams, Qrc20Coin, Qrc20FeeDetails}; pub mod rpc_command; use rpc_command::{init_account_balance::{AccountBalanceTaskManager, AccountBalanceTaskManagerShared}, @@ -233,23 +234,24 @@ pub mod solana; #[cfg(all(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")))] -pub use solana::{solana_coin_from_conf_and_params, SolanaActivationParams, SolanaCoin, SolanaFeeDetails}; +pub use solana::{SolanaActivationParams, SolanaCoin, SolanaFeeDetails}; pub mod utxo; -use utxo::bch::{bch_coin_from_conf_and_params, BchActivationRequest, BchCoin}; -use utxo::qtum::{self, qtum_coin_with_priv_key, Qrc20AddressError, QtumCoin, QtumDelegationOps, QtumDelegationRequest, +use utxo::bch::{bch_coin_with_policy, BchActivationRequest, BchCoin}; +use utxo::qtum::{self, qtum_coin_with_policy, Qrc20AddressError, QtumCoin, QtumDelegationOps, QtumDelegationRequest, QtumStakingInfosDetails, ScriptHashTypeNotSupported}; use utxo::rpc_clients::UtxoRpcError; use utxo::slp::SlpToken; use utxo::slp::{slp_addr_from_pubkey_str, SlpFeeDetails}; use utxo::utxo_common::big_decimal_from_sat_unsigned; -use utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; +use utxo::utxo_standard::{utxo_standard_coin_with_policy, UtxoStandardCoin}; use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; +pub type TransactionFut = Box + Send>; pub type BalanceResult = Result>; pub type BalanceFut = Box> + Send>; pub type NonZeroBalanceFut = Box> + Send>; @@ -275,6 +277,8 @@ pub type SendTakerSpendsMakerPaymentArgs<'a> = SendSpendPaymentArgs<'a>; pub type SendTakerRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; pub type SendMakerRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; +pub type IguanaPrivKey = Secp256k1Secret; + #[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum RawTransactionError { @@ -341,18 +345,27 @@ pub enum TxHistoryError { InternalError(String), } -#[derive(Debug, Display)] -pub enum PrivKeyNotAllowed { +#[derive(Clone, Debug, Display)] +pub enum PrivKeyPolicyNotAllowed { #[display(fmt = "Hardware Wallet is not supported")] HardwareWalletNotSupported, } +impl Serialize for PrivKeyPolicyNotAllowed { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + #[derive(Clone, Debug, Display, PartialEq, Serialize)] pub enum UnexpectedDerivationMethod { - #[display(fmt = "Iguana private key is unavailable")] - IguanaPrivKeyUnavailable, - #[display(fmt = "HD wallet is unavailable")] - HDWalletUnavailable, + #[display(fmt = "Expected 'SingleAddress' derivation method")] + ExpectedSingleAddress, + #[display(fmt = "Expected 'HDWallet' derivationMethod")] + ExpectedHDWallet, } pub trait Transaction: fmt::Debug + 'static { @@ -443,8 +456,6 @@ impl TransactionErr { } } -pub type TransactionFut = Box + Send>; - #[derive(Debug, PartialEq)] pub enum FoundSwapTxSpend { Spent(TransactionEnum), @@ -484,16 +495,6 @@ pub struct WatcherValidatePaymentInput { pub confirmations: u64, } -pub struct WatcherSearchForSwapTxSpendInput<'a> { - pub time_lock: u32, - pub taker_pub: &'a [u8], - pub maker_pub: &'a [u8], - pub secret_hash: &'a [u8], - pub tx: &'a [u8], - pub search_from_block: u64, - pub swap_contract_address: &'a Option, -} - #[derive(Clone, Debug)] pub struct ValidatePaymentInput { pub payment_tx: Vec, @@ -1480,8 +1481,8 @@ impl From for DelegationError { } } -impl From for DelegationError { - fn from(e: PrivKeyNotAllowed) -> Self { DelegationError::DelegationOpsNotSupported { reason: e.to_string() } } +impl From for DelegationError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { DelegationError::DelegationOpsNotSupported { reason: e.to_string() } } } impl From for DelegationError { @@ -1663,8 +1664,8 @@ impl From for WithdrawError { fn from(e: UnexpectedDerivationMethod) -> Self { WithdrawError::InternalError(e.to_string()) } } -impl From for WithdrawError { - fn from(e: PrivKeyNotAllowed) -> Self { WithdrawError::InternalError(e.to_string()) } +impl From for WithdrawError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { WithdrawError::InternalError(e.to_string()) } } impl WithdrawError { @@ -1743,8 +1744,8 @@ impl From for SignatureError { fn from(e: ethkey::Error) -> Self { SignatureError::InternalError(e.to_string()) } } -impl From for SignatureError { - fn from(e: PrivKeyNotAllowed) -> Self { SignatureError::InternalError(e.to_string()) } +impl From for SignatureError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { SignatureError::InternalError(e.to_string()) } } impl From for SignatureError { @@ -2191,14 +2192,14 @@ impl CoinsContext { /// This enum is used in coin activation requests. #[derive(Copy, Clone, Debug, Deserialize, Serialize)] pub enum PrivKeyActivationPolicy { - IguanaPrivKey, + ContextPrivKey, Trezor, } impl PrivKeyActivationPolicy { /// The function can be used as a default deserialization constructor: - /// `#[serde(default = "PrivKeyActivationPolicy::iguana_priv_key")]` - pub fn iguana_priv_key() -> PrivKeyActivationPolicy { PrivKeyActivationPolicy::IguanaPrivKey } + /// `#[serde(default = "PrivKeyActivationPolicy::context_priv_key")]` + pub fn context_priv_key() -> PrivKeyActivationPolicy { PrivKeyActivationPolicy::ContextPrivKey } /// The function can be used as a default deserialization constructor: /// `#[serde(default = "PrivKeyActivationPolicy::trezor")]` @@ -2219,59 +2220,66 @@ impl PrivKeyPolicy { } } - pub fn key_pair_or_err(&self) -> Result<&T, MmError> { + pub fn key_pair_or_err(&self) -> Result<&T, MmError> { self.key_pair() - .or_mm_err(|| PrivKeyNotAllowed::HardwareWalletNotSupported) + .or_mm_err(|| PrivKeyPolicyNotAllowed::HardwareWalletNotSupported) } } #[derive(Clone)] -pub enum PrivKeyBuildPolicy<'a> { - IguanaPrivKey(&'a [u8]), +pub enum PrivKeyBuildPolicy { + IguanaPrivKey(IguanaPrivKey), + GlobalHDAccount(GlobalHDAccountArc), Trezor, } -impl<'a> PrivKeyBuildPolicy<'a> { - pub fn iguana_priv_key(crypto_ctx: &'a CryptoCtx) -> Self { - PrivKeyBuildPolicy::IguanaPrivKey(crypto_ctx.iguana_ctx().secp256k1_privkey_bytes()) +impl PrivKeyBuildPolicy { + /// Detects the `PrivKeyBuildPolicy` with which the given `CryptoCtx` is initialized. + /// Later it will be used to detect `MetaMask` or `Trezor` policy. + pub fn detect_priv_key_policy(crypto_ctx: &CryptoCtx) -> PrivKeyBuildPolicy { + match crypto_ctx.key_pair_policy() { + // Use the internal private key as the coin secret. + KeyPairPolicy::Iguana => PrivKeyBuildPolicy::IguanaPrivKey(crypto_ctx.mm2_internal_privkey_secret()), + KeyPairPolicy::GlobalHDAccount(global_hd) => PrivKeyBuildPolicy::GlobalHDAccount(global_hd.clone()), + } } } #[derive(Debug)] pub enum DerivationMethod { - Iguana(Address), + SingleAddress(Address), HDWallet(HDWallet), } impl DerivationMethod { - pub fn iguana(&self) -> Option<&Address> { + pub fn single_addr(&self) -> Option<&Address> { match self { - DerivationMethod::Iguana(my_address) => Some(my_address), + DerivationMethod::SingleAddress(my_address) => Some(my_address), DerivationMethod::HDWallet(_) => None, } } - pub fn iguana_or_err(&self) -> MmResult<&Address, UnexpectedDerivationMethod> { - self.iguana() - .or_mm_err(|| UnexpectedDerivationMethod::IguanaPrivKeyUnavailable) + pub fn single_addr_or_err(&self) -> MmResult<&Address, UnexpectedDerivationMethod> { + self.single_addr() + .or_mm_err(|| UnexpectedDerivationMethod::ExpectedSingleAddress) } pub fn hd_wallet(&self) -> Option<&HDWallet> { match self { - DerivationMethod::Iguana(_) => None, + DerivationMethod::SingleAddress(_) => None, DerivationMethod::HDWallet(hd_wallet) => Some(hd_wallet), } } pub fn hd_wallet_or_err(&self) -> MmResult<&HDWallet, UnexpectedDerivationMethod> { self.hd_wallet() - .or_mm_err(|| UnexpectedDerivationMethod::HDWalletUnavailable) + .or_mm_err(|| UnexpectedDerivationMethod::ExpectedHDWallet) } /// # Panic /// /// Panic if the address mode is [`DerivationMethod::HDWallet`]. - pub fn unwrap_iguana(&self) -> &Address { self.iguana_or_err().unwrap() } + pub fn unwrap_single_addr(&self) -> &Address { self.single_addr_or_err().unwrap() } } #[async_trait] @@ -2509,10 +2517,10 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result Result { let params = try_s!(UtxoActivationParams::from_legacy_req(req)); - try_s!(utxo_standard_coin_with_priv_key(ctx, ticker, &coins_en, ¶ms, &secret).await).into() + try_s!(utxo_standard_coin_with_policy(ctx, ticker, &coins_en, ¶ms, priv_key_policy).await).into() }, CoinProtocol::QTUM => { let params = try_s!(UtxoActivationParams::from_legacy_req(req)); - try_s!(qtum_coin_with_priv_key(ctx, ticker, &coins_en, ¶ms, &secret).await).into() + try_s!(qtum_coin_with_policy(ctx, ticker, &coins_en, ¶ms, priv_key_policy).await).into() }, CoinProtocol::ETH | CoinProtocol::ERC20 { .. } => { - try_s!(eth_coin_from_conf_and_request(ctx, ticker, &coins_en, req, &secret, protocol).await).into() + try_s!(eth_coin_from_conf_and_request(ctx, ticker, &coins_en, req, protocol, priv_key_policy).await).into() }, CoinProtocol::QRC20 { platform, @@ -2541,8 +2549,16 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result Result = Result>; pub enum Qrc20GenTxError { ErrorGeneratingUtxoTx(GenerateTxError), ErrorSigningTx(UtxoSignWithKeyPairError), - PrivKeyNotAllowed(PrivKeyNotAllowed), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), UnexpectedDerivationMethod(UnexpectedDerivationMethod), } @@ -93,8 +94,8 @@ impl From for Qrc20GenTxError { fn from(e: UtxoSignWithKeyPairError) -> Self { Qrc20GenTxError::ErrorSigningTx(e) } } -impl From for Qrc20GenTxError { - fn from(e: PrivKeyNotAllowed) -> Self { Qrc20GenTxError::PrivKeyNotAllowed(e) } +impl From for Qrc20GenTxError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { Qrc20GenTxError::PrivKeyPolicyNotAllowed(e) } } impl From for Qrc20GenTxError { @@ -112,7 +113,7 @@ impl Qrc20GenTxError { WithdrawError::from_generate_tx_error(gen_err, coin, decimals) }, Qrc20GenTxError::ErrorSigningTx(sign_err) => WithdrawError::InternalError(sign_err.to_string()), - Qrc20GenTxError::PrivKeyNotAllowed(priv_err) => WithdrawError::InternalError(priv_err.to_string()), + Qrc20GenTxError::PrivKeyPolicyNotAllowed(priv_err) => WithdrawError::InternalError(priv_err.to_string()), Qrc20GenTxError::UnexpectedDerivationMethod(addr_err) => WithdrawError::InternalError(addr_err.to_string()), } } @@ -157,7 +158,7 @@ struct Qrc20CoinBuilder<'a> { ticker: &'a str, conf: &'a Json, activation_params: &'a Qrc20ActivationParams, - priv_key: &'a [u8], + priv_key_policy: PrivKeyBuildPolicy, platform: String, token_contract_address: H160, } @@ -168,7 +169,7 @@ impl<'a> Qrc20CoinBuilder<'a> { ticker: &'a str, conf: &'a Json, activation_params: &'a Qrc20ActivationParams, - priv_key: &'a [u8], + priv_key_policy: PrivKeyBuildPolicy, platform: String, token_contract_address: H160, ) -> Qrc20CoinBuilder<'a> { @@ -177,7 +178,7 @@ impl<'a> Qrc20CoinBuilder<'a> { ticker, conf, activation_params, - priv_key, + priv_key_policy, platform, token_contract_address, } @@ -259,18 +260,33 @@ impl<'a> UtxoCoinBuilderCommonOps for Qrc20CoinBuilder<'a> { } } -#[async_trait] -impl<'a> UtxoFieldsWithIguanaPrivKeyBuilder for Qrc20CoinBuilder<'a> {} +impl<'a> UtxoFieldsWithIguanaSecretBuilder for Qrc20CoinBuilder<'a> {} + +impl<'a> UtxoFieldsWithGlobalHDBuilder for Qrc20CoinBuilder<'a> {} + +/// Although, `Qrc20Coin` doesn't support [`PrivKeyBuildPolicy::Trezor`] yet, +/// `UtxoCoinBuilder` trait requires `UtxoFieldsWithHardwareWalletBuilder` to be implemented. +impl<'a> UtxoFieldsWithHardwareWalletBuilder for Qrc20CoinBuilder<'a> {} #[async_trait] -impl<'a> UtxoCoinWithIguanaPrivKeyBuilder for Qrc20CoinBuilder<'a> { +impl<'a> UtxoCoinBuilder for Qrc20CoinBuilder<'a> { type ResultCoin = Qrc20Coin; type Error = UtxoCoinBuildError; - fn priv_key(&self) -> &[u8] { self.priv_key } + fn priv_key_policy(&self) -> PrivKeyBuildPolicy { self.priv_key_policy.clone() } async fn build(self) -> MmResult { - let utxo = self.build_utxo_fields_with_iguana_priv_key(self.priv_key()).await?; + let utxo = match self.priv_key_policy() { + PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => self.build_utxo_fields_with_iguana_secret(priv_key).await?, + PrivKeyBuildPolicy::GlobalHDAccount(global_hd_ctx) => { + self.build_utxo_fields_with_global_hd(global_hd_ctx).await? + }, + PrivKeyBuildPolicy::Trezor => { + let priv_key_err = PrivKeyPolicyNotAllowed::HardwareWalletNotSupported; + return MmError::err(UtxoCoinBuildError::PrivKeyPolicyNotAllowed(priv_key_err)); + }, + }; + let inner = Qrc20CoinFields { utxo, platform: self.platform, @@ -282,13 +298,13 @@ impl<'a> UtxoCoinWithIguanaPrivKeyBuilder for Qrc20CoinBuilder<'a> { } } -pub async fn qrc20_coin_from_conf_and_params( +pub async fn qrc20_coin_with_policy( ctx: &MmArc, ticker: &str, platform: &str, conf: &Json, params: &Qrc20ActivationParams, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, contract_address: H160, ) -> Result { let builder = Qrc20CoinBuilder::new( @@ -296,13 +312,26 @@ pub async fn qrc20_coin_from_conf_and_params( ticker, conf, params, - priv_key, + priv_key_policy, platform.to_owned(), contract_address, ); Ok(try_s!(builder.build().await)) } +pub async fn qrc20_coin_with_priv_key( + ctx: &MmArc, + ticker: &str, + platform: &str, + conf: &Json, + params: &Qrc20ActivationParams, + priv_key: IguanaPrivKey, + contract_address: H160, +) -> Result { + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); + qrc20_coin_with_policy(ctx, ticker, platform, conf, params, priv_key_policy, contract_address).await +} + pub struct Qrc20CoinFields { pub utxo: UtxoCoinFields, pub platform: String, @@ -485,7 +514,7 @@ impl Qrc20Coin { &self, contract_outputs: Vec, ) -> Result> { - let my_address = self.utxo.derivation_method.iguana_or_err()?; + let my_address = self.utxo.derivation_method.single_addr_or_err()?; let (unspents, _) = self.get_unspent_ordered_list(my_address).await?; let mut gas_fee = 0; @@ -502,7 +531,7 @@ impl Qrc20Coin { .build() .await?; - let my_address = self.utxo.derivation_method.iguana_or_err()?; + let my_address = self.utxo.derivation_method.single_addr_or_err()?; let key_pair = self.utxo.priv_key_policy.key_pair_or_err()?; let prev_script = ScriptBuilder::build_p2pkh(&my_address.hash); @@ -1445,7 +1474,7 @@ async fn qrc20_withdraw(coin: Qrc20Coin, req: WithdrawRequest) -> WithdrawResult .await .mm_err(|gen_tx_error| gen_tx_error.into_withdraw_error(coin.platform.clone(), coin.utxo.decimals))?; - let my_address = coin.utxo.derivation_method.iguana_or_err()?; + let my_address = coin.utxo.derivation_method.single_addr_or_err()?; let received_by_me = if to_addr == *my_address { qrc20_amount.clone() } else { diff --git a/mm2src/coins/qrc20/history.rs b/mm2src/coins/qrc20/history.rs index 1edbced178..0e61ce0a79 100644 --- a/mm2src/coins/qrc20/history.rs +++ b/mm2src/coins/qrc20/history.rs @@ -224,7 +224,7 @@ impl Qrc20Coin { receipt: TxReceipt, miner_fee: BigDecimal, ) -> Result { - let my_address = try_s!(self.utxo.derivation_method.iguana_or_err()); + let my_address = try_s!(self.utxo.derivation_method.single_addr_or_err()); let tx_hash: H256Json = try_s!(H256Json::from_str(&qtum_details.tx_hash)); if qtum_tx.outputs.len() <= (receipt.output_index as usize) { return ERR!( @@ -621,7 +621,7 @@ impl TransferHistoryBuilder { .coin .utxo .derivation_method - .iguana_or_err() + .single_addr_or_err() .mm_err(|e| UtxoRpcError::Internal(e.to_string()))?; qtum::contract_addr_from_utxo_addr(my_address.clone()) .mm_err(|e| UtxoRpcError::Internal(e.to_string()))? @@ -840,7 +840,7 @@ mod tests { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (ctx, coin) = qrc20_coin_for_test(priv_key, None); ctx.metrics.init(); let tx_hash: H256Json = hex::decode("85ede12ccc12fb1709c4d9e403e96c0c394b0916f2f6098d41d8dfa00013fcdb") @@ -872,7 +872,7 @@ mod tests { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (ctx, coin) = qrc20_coin_for_test(priv_key, None); ctx.metrics.init(); let tx_hash: H256Json = hex::decode("85ede12ccc12fb1709c4d9e403e96c0c394b0916f2f6098d41d8dfa00013fcdb") @@ -914,7 +914,7 @@ mod tests { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (ctx, coin) = qrc20_coin_for_test(priv_key, None); ctx.metrics.init(); let tx_hash: H256Json = hex::decode("85ede12ccc12fb1709c4d9e403e96c0c394b0916f2f6098d41d8dfa00013fcdb") @@ -956,7 +956,7 @@ mod tests { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (ctx, coin) = qrc20_coin_for_test(priv_key, None); let tx_hash: H256Json = hex::decode("35e03bc529528a853ee75dde28f27eec8ed7b152b6af7ab6dfa5d55ea46f25ac") .unwrap() @@ -981,7 +981,7 @@ mod tests { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (ctx, coin) = qrc20_coin_for_test(priv_key, None); let tx_hash: H256Json = hex::decode("85ede12ccc12fb1709c4d9e403e96c0c394b0916f2f6098d41d8dfa00013fcdb") .unwrap() @@ -1006,7 +1006,7 @@ mod tests { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (ctx, coin) = qrc20_coin_for_test(priv_key, None); let metrics = MetricsArc::new(); metrics.init(); diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 7a10a76797..bbe24b857c 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -3,6 +3,7 @@ use crate::utxo::rpc_clients::UnspentInfo; use crate::TxFeeDetails; use chain::OutPoint; use common::{block_on, DEX_FEE_ADDR_RAW_PUBKEY}; +use crypto::Secp256k1Secret; use itertools::Itertools; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_number::bigdecimal::Zero; @@ -15,7 +16,7 @@ const EXPECTED_TX_FEE: i64 = 1000; const CONTRACT_CALL_GAS_FEE: i64 = (QRC20_GAS_LIMIT_DEFAULT * QRC20_GAS_PRICE_DEFAULT) as i64; const SWAP_PAYMENT_GAS_FEE: i64 = (QRC20_PAYMENT_GAS_LIMIT * QRC20_GAS_PRICE_DEFAULT) as i64; -pub fn qrc20_coin_for_test(priv_key: &[u8], fallback_swap: Option<&str>) -> (MmArc, Qrc20Coin) { +pub fn qrc20_coin_for_test(priv_key: [u8; 32], fallback_swap: Option<&str>) -> (MmArc, Qrc20Coin) { let conf = json!({ "coin":"QRC20", "decimals": 8, @@ -37,13 +38,13 @@ pub fn qrc20_coin_for_test(priv_key: &[u8], fallback_swap: Option<&str>) -> (MmA let ctx = MmCtxBuilder::new().into_mm_arc(); let params = Qrc20ActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qrc20_coin_from_conf_and_params( + let coin = block_on(qrc20_coin_with_priv_key( &ctx, "QRC20", "QTUM", &conf, ¶ms, - priv_key, + Secp256k1Secret::from(priv_key), contract_address, )) .unwrap(); @@ -61,13 +62,13 @@ fn test_withdraw_to_p2sh_address_should_fail() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_, coin) = qrc20_coin_for_test(&priv_key, None); + let (_, coin) = qrc20_coin_for_test(priv_key, None); let p2sh_address = Address { prefix: coin.as_ref().conf.p2sh_addr_prefix, - hash: coin.as_ref().derivation_method.unwrap_iguana().hash.clone(), + hash: coin.as_ref().derivation_method.unwrap_single_addr().hash.clone(), t_addr_prefix: coin.as_ref().conf.p2sh_t_addr_prefix, - checksum_type: coin.as_ref().derivation_method.unwrap_iguana().checksum_type, + checksum_type: coin.as_ref().derivation_method.unwrap_single_addr().checksum_type, hrp: coin.as_ref().conf.bech32_hrp.clone(), addr_format: UtxoAddressFormat::Standard, }; @@ -106,7 +107,7 @@ fn test_withdraw_impl_fee_details() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); let withdraw_req = WithdrawRequest { amount: 10.into(), @@ -144,10 +145,10 @@ fn test_validate_maker_payment() { 24, 181, 194, 193, 18, 152, 142, 168, 71, 73, 70, 244, 9, 101, 92, 168, 243, 61, 132, 48, 25, 39, 103, 92, 29, 17, 11, 29, 113, 235, 48, 70, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); assert_eq!( - *coin.utxo.derivation_method.unwrap_iguana(), + *coin.utxo.derivation_method.unwrap_single_addr(), "qUX9FGHubczidVjWPCUWuwCUJWpkAtGCgf".into() ); @@ -242,10 +243,10 @@ fn test_wait_for_confirmations_excepted() { 24, 181, 194, 193, 18, 152, 142, 168, 71, 73, 70, 244, 9, 101, 92, 168, 243, 61, 132, 48, 25, 39, 103, 92, 29, 17, 11, 29, 113, 235, 48, 70, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); assert_eq!( - *coin.utxo.derivation_method.unwrap_iguana(), + *coin.utxo.derivation_method.unwrap_single_addr(), "qUX9FGHubczidVjWPCUWuwCUJWpkAtGCgf".into() ); @@ -289,7 +290,7 @@ fn test_send_taker_fee() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); let amount = BigDecimal::from_str("0.01").unwrap(); let tx = coin @@ -322,7 +323,7 @@ fn test_validate_fee() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // QRC20 transfer tx "f97d3a43dbea0993f1b7a6a299377d4ee164c84935a1eb7d835f70c9429e6a1d" let tx = TransactionEnum::UtxoTx("010000000160fd74b5714172f285db2b36f0b391cd6883e7291441631c8b18f165b0a4635d020000006a47304402205d409e141111adbc4f185ae856997730de935ac30a0d2b1ccb5a6c4903db8171022024fc59bbcfdbba283556d7eeee4832167301dc8e8ad9739b7865f67b9676b226012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff020000000000000000625403a08601012844a9059cbb000000000000000000000000ca1e04745e8ca0c60d8c5881531d51bec470743f00000000000000000000000000000000000000000000000000000000000f424014d362e096e873eb7907e205fadc6175c6fec7bc44c200ada205000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acfe967d5f".into()); @@ -430,7 +431,7 @@ fn test_wait_for_tx_spend_malicious() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // f94d79f89e9ec785db40bb8bb8dca9bc01b7761429618d4c843bbebbc31836b7 // the transaction has two outputs: @@ -462,7 +463,7 @@ fn test_extract_secret() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); let expected_secret = &[1; 32]; let secret_hash = &*dhash160(expected_secret); @@ -481,7 +482,7 @@ fn test_extract_secret_malicious() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // f94d79f89e9ec785db40bb8bb8dca9bc01b7761429618d4c843bbebbc31836b7 // the transaction has two outputs: @@ -501,7 +502,7 @@ fn test_generate_token_transfer_script_pubkey() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); let gas_limit = 2_500_000; let gas_price = 40; @@ -550,7 +551,7 @@ fn test_transfer_details_by_hash() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); let tx_hash_bytes = hex::decode("85ede12ccc12fb1709c4d9e403e96c0c394b0916f2f6098d41d8dfa00013fcdb").unwrap(); let tx_hash: H256Json = tx_hash_bytes.as_slice().into(); let tx_hex:BytesJson = hex::decode("0100000001426d27fde82e12e1ce84e73ca41e2a30420f4c94aaa37b30d4c5b8b4f762c042040000006a473044022032665891693ee732571cefaa6d322ec5114c78259f2adbe03a0d7e6b65fbf40d022035c9319ca41e5423e09a8a613ac749a20b8f5ad6ba4ad6bb60e4a020b085d009012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff050000000000000000625403a08601012844095ea7b30000000000000000000000001549128bbfb33b997949b4105b6a6371c998e212000000000000000000000000000000000000000000000000000000000000000014d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000625403a08601012844095ea7b30000000000000000000000001549128bbfb33b997949b4105b6a6371c998e21200000000000000000000000000000000000000000000000000000000000927c014d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000835403a0860101284c640c565ae300000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000000000000000000000000000000000000000000000141549128bbfb33b997949b4105b6a6371c998e212c20000000000000000835403a0860101284c640c565ae300000000000000000000000000000000000000000000000000000000000493e0000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000000000000000000000000000000000000000000001141549128bbfb33b997949b4105b6a6371c998e212c231754b04000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acf7cd8b5f").unwrap().into(); @@ -699,7 +700,7 @@ fn test_get_trade_fee() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); @@ -726,7 +727,7 @@ fn test_sender_trade_preimage_zero_allowance() { 222, 243, 64, 156, 9, 153, 78, 253, 85, 119, 62, 117, 230, 140, 75, 69, 171, 21, 243, 19, 119, 29, 97, 174, 63, 231, 153, 202, 20, 238, 120, 64, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); @@ -762,7 +763,7 @@ fn test_sender_trade_preimage_with_allowance() { 32, 192, 195, 65, 165, 53, 21, 68, 180, 241, 67, 147, 54, 54, 41, 117, 174, 253, 139, 155, 56, 101, 69, 39, 32, 143, 221, 19, 47, 74, 175, 100, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); @@ -837,13 +838,13 @@ fn test_get_sender_trade_fee_preimage_for_correct_ticker() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = Qrc20ActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qrc20_coin_from_conf_and_params( + let coin = block_on(qrc20_coin_with_priv_key( &ctx, "QRC20", "tQTUM", &conf, ¶ms, - &priv_key, + Secp256k1Secret::from(priv_key), contract_address, )) .unwrap(); @@ -870,7 +871,7 @@ fn test_receiver_trade_preimage() { 32, 192, 195, 65, 165, 53, 21, 68, 180, 241, 67, 147, 54, 54, 41, 117, 174, 253, 139, 155, 56, 101, 69, 39, 32, 143, 221, 19, 47, 74, 175, 100, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); @@ -897,7 +898,7 @@ fn test_taker_fee_tx_fee() { 32, 192, 195, 65, 165, 53, 21, 68, 180, 241, 67, 147, 54, 54, 41, 117, 174, 253, 139, 155, 56, 101, 69, 39, 32, 143, 221, 19, 47, 74, 175, 100, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); let expected_balance = CoinBalance { @@ -945,13 +946,13 @@ fn test_coin_from_conf_without_decimals() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = Qrc20ActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qrc20_coin_from_conf_and_params( + let coin = block_on(qrc20_coin_with_priv_key( &ctx, "QRC20", "QTUM", &conf, ¶ms, - &priv_key, + Secp256k1Secret::from(priv_key), contract_address, )) .unwrap(); @@ -1009,7 +1010,7 @@ fn test_validate_maker_payment_malicious() { 24, 181, 194, 193, 18, 152, 142, 168, 71, 73, 70, 244, 9, 101, 92, 168, 243, 61, 132, 48, 25, 39, 103, 92, 29, 17, 11, 29, 113, 235, 48, 70, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); // Malicious tx 81540dc6abe59cf1e301a97a7e1c9b66d5f475da916faa3f0ef7ea896c0b3e5a let payment_tx = hex::decode("01000000010144e2b8b5e6da0666faf1db95075653ef49e2acaa8924e1ec595f6b89a6f715050000006a4730440220415adec5e24148db8e9654a6beda4b1af8aded596ab1cd8667af32187853e8f5022007a91d44ee13046194aafc07ca46ec44f770e75b41187acaa4e38e17d4eccb5d012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff030000000000000000625403a08601012844095ea7b300000000000000000000000085a4df739bbb2d247746bea611d5d365204725830000000000000000000000000000000000000000000000000000000005f5e10014d362e096e873eb7907e205fadc6175c6fec7bc44c20000000000000000e35403a0860101284cc49b415b2a0a1a8b4af2762154115ced87e2424b3cb940c0181cc3c850523702f1ec298fef0000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc44000000000000000000000000783cf0be521101942da509846ea476e683aad8324b6b2e5444c2639cc0fb7bcea5afba3f3cdce239000000000000000000000000000000000000000000000000000000000000000000000000000000005fa0fffb1485a4df739bbb2d247746bea611d5d36520472583c208535c01000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acc700a15f").unwrap(); @@ -1047,7 +1048,7 @@ fn test_validate_maker_payment_malicious() { #[test] fn test_negotiate_swap_contract_addr_no_fallback() { - let (_, coin) = qrc20_coin_for_test(&[1; 32], None); + let (_, coin) = qrc20_coin_for_test([1; 32], None); let input = None; let error = coin.negotiate_swap_contract_addr(input).unwrap_err().into_inner(); @@ -1077,7 +1078,7 @@ fn test_negotiate_swap_contract_addr_has_fallback() { let fallback = "0x8500AFc0bc5214728082163326C2FF0C73f4a871"; let fallback_addr = qtum::contract_addr_from_str(fallback).unwrap(); - let (_, coin) = qrc20_coin_for_test(&[1; 32], Some(fallback)); + let (_, coin) = qrc20_coin_for_test([1; 32], Some(fallback)); let input = None; let result = coin.negotiate_swap_contract_addr(input).unwrap(); @@ -1112,7 +1113,7 @@ fn test_send_contract_calls_recoverable_tx() { 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, ]; - let (_ctx, coin) = qrc20_coin_for_test(&priv_key, None); + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); let tx = TransactionEnum::UtxoTx("010000000160fd74b5714172f285db2b36f0b391cd6883e7291441631c8b18f165b0a4635d020000006a47304402205d409e141111adbc4f185ae856997730de935ac30a0d2b1ccb5a6c4903db8171022024fc59bbcfdbba283556d7eeee4832167301dc8e8ad9739b7865f67b9676b226012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff020000000000000000625403a08601012844a9059cbb000000000000000000000000ca1e04745e8ca0c60d8c5881531d51bec470743f00000000000000000000000000000000000000000000000000000000000f424014d362e096e873eb7907e205fadc6175c6fec7bc44c200ada205000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acfe967d5f".into()); diff --git a/mm2src/coins/qrc20/swap.rs b/mm2src/coins/qrc20/swap.rs index bdc93a8399..3b0072d404 100644 --- a/mm2src/coins/qrc20/swap.rs +++ b/mm2src/coins/qrc20/swap.rs @@ -134,7 +134,7 @@ impl Qrc20Coin { let expected_call_bytes = { let expected_value = wei_from_big_decimal(&amount, self.utxo.decimals)?; - let my_address = self.utxo.derivation_method.iguana_or_err()?.clone(); + let my_address = self.utxo.derivation_method.single_addr_or_err()?.clone(); let expected_receiver = qtum::contract_addr_from_utxo_addr(my_address) .mm_err(|err| ValidatePaymentError::InternalError(err.to_string()))?; self.erc20_payment_call_bytes( @@ -262,7 +262,7 @@ impl Qrc20Coin { } // Else try to find a 'senderRefund' contract call. - let my_address = try_s!(self.utxo.derivation_method.iguana_or_err()).clone(); + let my_address = try_s!(self.utxo.derivation_method.single_addr_or_err()).clone(); let sender = try_s!(qtum::contract_addr_from_utxo_addr(my_address)); let refund_txs = try_s!(self.sender_refund_transactions(sender, search_from_block).await); let found = refund_txs.into_iter().find(|tx| { @@ -286,7 +286,7 @@ impl Qrc20Coin { return Ok(None); }; - let my_address = try_s!(self.utxo.derivation_method.iguana_or_err()).clone(); + let my_address = try_s!(self.utxo.derivation_method.single_addr_or_err()).clone(); let sender = try_s!(qtum::contract_addr_from_utxo_addr(my_address)); let erc20_payment_txs = try_s!(self.erc20_payment_transactions(sender, search_from_block).await); let found = erc20_payment_txs @@ -457,7 +457,7 @@ impl Qrc20Coin { let my_address = self .utxo .derivation_method - .iguana_or_err() + .single_addr_or_err() .mm_err(|e| UtxoRpcError::Internal(e.to_string()))?; let tokens = self .utxo diff --git a/mm2src/coins/rpc_command/get_new_address.rs b/mm2src/coins/rpc_command/get_new_address.rs index cda996c345..2b36b2d05d 100644 --- a/mm2src/coins/rpc_command/get_new_address.rs +++ b/mm2src/coins/rpc_command/get_new_address.rs @@ -52,7 +52,7 @@ impl From for GetNewAddressRpcError { impl From for GetNewAddressRpcError { fn from(e: UnexpectedDerivationMethod) -> Self { match e { - UnexpectedDerivationMethod::HDWalletUnavailable => GetNewAddressRpcError::CoinIsActivatedNotWithHDWallet, + UnexpectedDerivationMethod::ExpectedHDWallet => GetNewAddressRpcError::CoinIsActivatedNotWithHDWallet, unexpected_error => GetNewAddressRpcError::Internal(unexpected_error.to_string()), } } diff --git a/mm2src/coins/rpc_command/hd_account_balance_rpc_error.rs b/mm2src/coins/rpc_command/hd_account_balance_rpc_error.rs index 4f0e836ffe..37c4598c9c 100644 --- a/mm2src/coins/rpc_command/hd_account_balance_rpc_error.rs +++ b/mm2src/coins/rpc_command/hd_account_balance_rpc_error.rs @@ -60,7 +60,7 @@ impl From for HDAccountBalanceRpcError { impl From for HDAccountBalanceRpcError { fn from(e: UnexpectedDerivationMethod) -> Self { match e { - UnexpectedDerivationMethod::HDWalletUnavailable => HDAccountBalanceRpcError::CoinIsActivatedNotWithHDWallet, + UnexpectedDerivationMethod::ExpectedHDWallet => HDAccountBalanceRpcError::CoinIsActivatedNotWithHDWallet, unexpected_error => HDAccountBalanceRpcError::Internal(unexpected_error.to_string()), } } diff --git a/mm2src/coins/rpc_command/init_create_account.rs b/mm2src/coins/rpc_command/init_create_account.rs index e5200b3fe6..fc50a357cb 100644 --- a/mm2src/coins/rpc_command/init_create_account.rs +++ b/mm2src/coins/rpc_command/init_create_account.rs @@ -72,7 +72,7 @@ impl From for CreateAccountRpcError { impl From for CreateAccountRpcError { fn from(e: UnexpectedDerivationMethod) -> Self { match e { - UnexpectedDerivationMethod::HDWalletUnavailable => CreateAccountRpcError::CoinIsActivatedNotWithHDWallet, + UnexpectedDerivationMethod::ExpectedHDWallet => CreateAccountRpcError::CoinIsActivatedNotWithHDWallet, unexpected_error => CreateAccountRpcError::Internal(unexpected_error.to_string()), } } diff --git a/mm2src/coins/rpc_command/lightning/open_channel.rs b/mm2src/coins/rpc_command/lightning/open_channel.rs index bf9ccec931..cb03396af4 100644 --- a/mm2src/coins/rpc_command/lightning/open_channel.rs +++ b/mm2src/coins/rpc_command/lightning/open_channel.rs @@ -145,7 +145,7 @@ pub async fn open_channel(ctx: MmArc, req: OpenChannelRequest) -> OpenChannelRes let platform_coin = ln_coin.platform_coin().clone(); let decimals = platform_coin.as_ref().decimals; - let my_address = platform_coin.as_ref().derivation_method.iguana_or_err()?; + let my_address = platform_coin.as_ref().derivation_method.single_addr_or_err()?; let (unspents, _) = platform_coin.get_unspent_ordered_list(my_address).await?; let (value, fee_policy) = match req.amount.clone() { ChannelOpenAmount::Max => ( diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 423c99fbad..e0a329e4fc 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -3,19 +3,21 @@ 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, - NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, - RawTransactionRequest, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SignatureResult, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, SearchForSwapTxSpendInput, + SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, + SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureResult, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest, + WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem}; use common::{async_blocking, now_ms}; +use crypto::StandardHDPathToCoin; use derive_more::Display; use futures::{FutureExt, TryFutureExt}; use futures01::Future; @@ -166,18 +168,28 @@ fn generate_keypair_from_slice(priv_key: &[u8]) -> Result Result { let client = RpcClient::new_with_commitment(params.client_url.clone(), CommitmentConfig { commitment: params.confirmation_commitment, }); let decimals = conf["decimals"].as_u64().unwrap_or(SOLANA_DEFAULT_DECIMALS) as u8; - let key_pair = try_s!(generate_keypair_from_slice(priv_key)); + + let priv_key = match priv_key_policy { + PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => priv_key, + PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { + let derivation_path: StandardHDPathToCoin = try_s!(json::from_value(conf["derivation_path"].clone())); + try_s!(global_hd.derive_secp256k1_secret(&derivation_path)) + }, + PrivKeyBuildPolicy::Trezor => return ERR!("{}", PrivKeyPolicyNotAllowed::HardwareWalletNotSupported), + }; + + let key_pair = try_s!(generate_keypair_from_slice(priv_key.as_slice())); let my_address = key_pair.pubkey().to_string(); let spl_tokens_infos = Arc::new(Mutex::new(HashMap::new())); diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index d9a1d658a1..54540e73b7 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -9,15 +9,16 @@ 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, MarketCoinOps, MmCoin, - NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, - RawTransactionFut, RawTransactionRequest, RawTransactionRes, SearchForSwapTxSpendInput, - SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureError, SignatureResult, SwapOps, - TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WatcherOps, WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; + NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, + SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, + SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, + SendTakerSpendsMakerPaymentArgs, SignatureError, SignatureResult, SwapOps, TradeFee, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, + TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, + WatcherValidatePaymentInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_std::prelude::FutureExt as AsyncStdFutureExt; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; @@ -39,7 +40,7 @@ use cosmrs::tendermint::chain::Id as ChainId; use cosmrs::tendermint::PublicKey; use cosmrs::tx::{self, Fee, Msg, Raw, SignDoc, SignerInfo}; use cosmrs::{AccountId, Any, Coin, Denom, ErrorReport}; -use crypto::privkey::key_pair_from_secret; +use crypto::{privkey::key_pair_from_secret, Secp256k1Secret, StandardHDPathToCoin}; use derive_more::Display; use futures::lock::Mutex as AsyncMutex; use futures::{FutureExt, TryFutureExt}; @@ -52,7 +53,7 @@ use mm2_number::MmNumber; use parking_lot::Mutex as PaMutex; use prost::{DecodeError, Message}; use rpc::v1::types::Bytes as BytesJson; -use serde_json::Value as Json; +use serde_json::{self as json, Value as Json}; use std::collections::HashMap; use std::convert::TryFrom; use std::ops::Deref; @@ -109,6 +110,39 @@ pub struct ActivatedTokenInfo { denom: Denom, } +pub struct TendermintConf { + avg_block_time: u8, + /// Derivation path of the coin. + /// This derivation path consists of `purpose` and `coin_type` only + /// where the full `BIP44` address has the following structure: + /// `m/purpose'/coin_type'/account'/change/address_index`. + derivation_path: Option, +} + +impl TendermintConf { + pub fn try_from_json(ticker: &str, conf: &Json) -> MmResult { + let avg_block_time = conf["avg_block_time"].as_i64().unwrap_or(0); + + // `avg_block_time` can not be less than 1 OR bigger than 255(u8::MAX) + if avg_block_time < 1 || avg_block_time > std::u8::MAX as i64 { + return MmError::err(TendermintInitError { + ticker: ticker.to_string(), + kind: TendermintInitErrorKind::AvgBlockTimeMissingOrInvalid, + }); + } + + let derivation_path = json::from_value(conf["derivation_path"].clone()).map_to_mm(|e| TendermintInitError { + ticker: ticker.to_string(), + kind: TendermintInitErrorKind::ErrorDeserializingDerivationPath(e.to_string()), + })?; + + Ok(TendermintConf { + avg_block_time: avg_block_time as u8, + derivation_path, + }) + } +} + pub struct TendermintCoinImpl { ticker: String, /// TODO @@ -155,6 +189,11 @@ pub enum TendermintInitErrorKind { RpcClientInitError(String), InvalidChainId(String), InvalidDenom(String), + #[display(fmt = "'derivation_path' field is not found in config")] + DerivationPathIsNotSet, + #[display(fmt = "Error deserializing 'derivation_path': {}", _0)] + ErrorDeserializingDerivationPath(String), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), RpcError(String), #[display(fmt = "avg_block_time missing or invalid. Please provide it with min 1 or max 255 value.")] AvgBlockTimeMissingOrInvalid, @@ -285,10 +324,10 @@ impl TendermintCoin { pub async fn init( ctx: &MmArc, ticker: String, - avg_block_time: u8, + conf: TendermintConf, protocol_info: TendermintProtocolInfo, rpc_urls: Vec, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> MmResult { if rpc_urls.is_empty() { return MmError::err(TendermintInitError { @@ -297,10 +336,14 @@ impl TendermintCoin { }); } + let priv_key = secret_from_priv_key_policy(&conf, &ticker, priv_key_policy)?; + let account_id = - account_id_from_privkey(priv_key, &protocol_info.account_prefix).mm_err(|kind| TendermintInitError { - ticker: ticker.clone(), - kind, + account_id_from_privkey(priv_key.as_slice(), &protocol_info.account_prefix).mm_err(|kind| { + TendermintInitError { + ticker: ticker.clone(), + kind, + } })?; let rpc_clients: Result, _> = rpc_urls @@ -345,7 +388,7 @@ impl TendermintCoin { denom, chain_id, gas_price: protocol_info.gas_price, - avg_block_time, + avg_block_time: conf.avg_block_time, sequence_lock: AsyncMutex::new(()), tokens_info: PaMutex::new(HashMap::new()), abortable_system, @@ -1865,12 +1908,46 @@ impl WatcherOps for TendermintCoin { } } +/// Processes the given `priv_key_policy` and returns corresponding `Secp256k1Secret`. +/// This function expects either [`PrivKeyBuildPolicy::IguanaPrivKey`] +/// or [`PrivKeyBuildPolicy::GlobalHDAccount`], otherwise returns `PrivKeyPolicyNotAllowed` error. +pub(crate) fn secret_from_priv_key_policy( + conf: &TendermintConf, + ticker: &str, + priv_key_policy: PrivKeyBuildPolicy, +) -> MmResult { + match priv_key_policy { + PrivKeyBuildPolicy::IguanaPrivKey(iguana) => Ok(iguana), + PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { + let derivation_path = conf.derivation_path.as_ref().or_mm_err(|| TendermintInitError { + ticker: ticker.to_string(), + kind: TendermintInitErrorKind::DerivationPathIsNotSet, + })?; + global_hd + .derive_secp256k1_secret(derivation_path) + .mm_err(|e| TendermintInitError { + ticker: ticker.to_string(), + kind: TendermintInitErrorKind::InvalidPrivKey(e.to_string()), + }) + }, + PrivKeyBuildPolicy::Trezor => { + let kind = + TendermintInitErrorKind::PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed::HardwareWalletNotSupported); + MmError::err(TendermintInitError { + ticker: ticker.to_string(), + kind, + }) + }, + } +} + #[cfg(test)] pub mod tendermint_coin_tests { use super::*; use crate::tendermint::htlc_proto::ClaimHtlcProtoRep; use common::{block_on, DEX_FEE_ADDR_RAW_PUBKEY}; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse, GetTxsEventResponse}; + use crypto::privkey::key_pair_from_seed; use rand::{thread_rng, Rng}; pub const IRIS_TESTNET_HTLC_PAIR1_SEED: &str = "iris test seed"; @@ -1925,19 +2002,23 @@ pub mod tendermint_coin_tests { let protocol_conf = get_iris_usdc_ibc_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; + + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); - let coin = common::block_on(TendermintCoin::init( + let coin = block_on(TendermintCoin::init( &ctx, "USDC-IBC".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); @@ -2045,25 +2126,31 @@ pub mod tendermint_coin_tests { // >> END HTLC CLAIMING } + /// TODO ignore it for a while. #[test] + #[ignore] fn try_query_claim_htlc_txs_and_get_secret() { let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; let protocol_conf = get_iris_usdc_ibc_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); + + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, "USDC-IBC".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); @@ -2097,25 +2184,31 @@ pub mod tendermint_coin_tests { assert_eq!(actual_secret, expected_secret); } + /// TODO ignore it for a while. #[test] + #[ignore] fn wait_for_tx_spend_test() { let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; let protocol_conf = get_iris_usdc_ibc_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; + + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, "USDC-IBC".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); @@ -2162,19 +2255,23 @@ pub mod tendermint_coin_tests { let protocol_conf = get_iris_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; + + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, "IRIS-TEST".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); @@ -2326,19 +2423,23 @@ pub mod tendermint_coin_tests { let protocol_conf = get_iris_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); + + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, "IRIS-TEST".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); @@ -2397,25 +2498,31 @@ pub mod tendermint_coin_tests { }; } + /// TODO ignore it for a while. #[test] + #[ignore] fn test_search_for_swap_tx_spend_spent() { let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; let protocol_conf = get_iris_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); + + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, "IRIS-TEST".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); @@ -2471,19 +2578,23 @@ pub mod tendermint_coin_tests { let protocol_conf = get_iris_protocol(); - let ctx = mm2_core::mm_ctx::MmCtxBuilder::default() - .with_secp256k1_key_pair(crypto::privkey::key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap()) - .into_mm_arc(); + let ctx = mm2_core::mm_ctx::MmCtxBuilder::default().into_mm_arc(); + + let conf = TendermintConf { + avg_block_time: 5, + derivation_path: None, + }; - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let key_pair = key_pair_from_seed(IRIS_TESTNET_HTLC_PAIR1_SEED).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(key_pair.private().secret); let coin = block_on(TendermintCoin::init( &ctx, "IRIS-TEST".to_string(), - 5, + conf, protocol_conf, rpc_urls, - priv_key, + priv_key_policy, )) .unwrap(); diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 5687938735..5752bb206d 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -50,8 +50,8 @@ use common::first_char_to_upper; use common::jsonrpc_client::JsonRpcError; use common::log::LogOnError; use common::now_ms; -use crypto::{Bip32DerPathOps, Bip32Error, Bip44Chain, Bip44DerPathError, Bip44PathToAccount, Bip44PathToCoin, - ChildNumber, DerivationPath, Secp256k1ExtendedPublicKey}; +use crypto::{Bip32DerPathOps, Bip32Error, Bip44Chain, ChildNumber, DerivationPath, Secp256k1ExtendedPublicKey, + StandardHDPathError, StandardHDPathToAccount, StandardHDPathToCoin}; use derive_more::Display; #[cfg(not(target_arch = "wasm32"))] use dirs::home_dir; use futures::channel::mpsc::{Receiver as AsyncReceiver, Sender as AsyncSender, UnboundedSender}; @@ -97,11 +97,11 @@ use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumRpcRequest UtxoRpcResult}; use super::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BalanceResult, CoinBalance, CoinFutSpawner, CoinsContext, DerivationMethod, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, KmdRewardsDetails, - MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, PrivKeyNotAllowed, - PrivKeyPolicy, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RpcTransportEventHandler, - RpcTransportEventHandlerShared, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - Transaction, TransactionDetails, TransactionEnum, TransactionErr, UnexpectedDerivationMethod, - VerificationError, WithdrawError, WithdrawRequest}; + MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, PrivKeyPolicy, + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RawTransactionResult, + RpcTransportEventHandler, RpcTransportEventHandlerShared, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, Transaction, TransactionDetails, TransactionEnum, TransactionErr, + UnexpectedDerivationMethod, VerificationError, WithdrawError, WithdrawRequest}; use crate::coin_balance::{EnableCoinScanPolicy, EnabledCoinBalanceParams, HDAddressBalanceScanner}; use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDAddressId, HDWalletCoinOps, HDWalletOps, InvalidBip44ChainError}; @@ -229,8 +229,8 @@ impl From for TxProviderError { } } -impl From for HDWalletStorageError { - fn from(e: Bip44DerPathError) -> Self { HDWalletStorageError::ErrorDeserializing(e.to_string()) } +impl From for HDWalletStorageError { + fn from(e: StandardHDPathError) -> Self { HDWalletStorageError::ErrorDeserializing(e.to_string()) } } impl From for HDWalletStorageError { @@ -561,6 +561,11 @@ pub struct UtxoCoinConf { /// The parameters that specify how the coin block headers should be verified. If None and enable_spv_proof is true, /// headers will be saved in DB without verification, can be none if the coin's RPC server is trusted. pub block_headers_verification_params: Option, + /// Derivation path of the coin. + /// This derivation path consists of `purpose` and `coin_type` only + /// where the full `BIP44` address has the following structure: + /// `m/purpose'/coin_type'/account'/change/address_index`. + pub derivation_path: Option, } pub struct UtxoCoinFields { @@ -1349,7 +1354,7 @@ pub struct UtxoActivationParams { pub gap_limit: Option, #[serde(flatten)] pub enable_params: EnabledCoinBalanceParams, - #[serde(default = "PrivKeyActivationPolicy::iguana_priv_key")] + #[serde(default = "PrivKeyActivationPolicy::context_priv_key")] pub priv_key_policy: PrivKeyActivationPolicy, /// The flag determines whether to use mature unspent outputs *only* to generate transactions. /// https://github.com/KomodoPlatform/atomicDEX-API/issues/1181 @@ -1405,7 +1410,7 @@ impl UtxoActivationParams { }; let priv_key_policy = json::from_value::>(req["priv_key_policy"].clone()) .map_to_mm(UtxoFromLegacyReqErr::InvalidPrivKeyPolicy)? - .unwrap_or(PrivKeyActivationPolicy::IguanaPrivKey); + .unwrap_or(PrivKeyActivationPolicy::ContextPrivKey); Ok(UtxoActivationParams { mode, @@ -1460,7 +1465,7 @@ pub struct UtxoHDWallet { /// This derivation path consists of `purpose` and `coin_type` only /// where the full `BIP44` address has the following structure: /// `m/purpose'/coin_type'/account'/change/address_index`. - pub derivation_path: Bip44PathToCoin, + pub derivation_path: StandardHDPathToCoin, /// User accounts. pub accounts: HDAccountsMutex, // The max number of empty addresses in a row. @@ -1500,7 +1505,7 @@ pub struct UtxoHDAccount { /// `m/purpose'/coin_type'/account'`. pub extended_pubkey: Secp256k1ExtendedPublicKey, /// [`UtxoHDWallet::derivation_path`] derived by [`UtxoHDAccount::account_id`]. - pub account_derivation_path: Bip44PathToAccount, + pub account_derivation_path: StandardHDPathToAccount, /// The number of addresses that we know have been used by the user. /// This is used in order not to check the transaction history for each address, /// but to request the balance of addresses whose index is less than `address_number`. @@ -1526,7 +1531,7 @@ impl HDAccountOps for UtxoHDAccount { impl UtxoHDAccount { pub fn try_from_storage_item( - wallet_der_path: &Bip44PathToCoin, + wallet_der_path: &StandardHDPathToCoin, account_info: &HDAccountStorageItem, ) -> HDWalletStorageResult { const ACCOUNT_CHILD_HARDENED: bool = true; @@ -1534,7 +1539,7 @@ impl UtxoHDAccount { let account_child = ChildNumber::new(account_info.account_id, ACCOUNT_CHILD_HARDENED)?; let account_derivation_path = wallet_der_path .derive(account_child) - .map_to_mm(Bip44DerPathError::from)?; + .map_to_mm(StandardHDPathError::from)?; let extended_pubkey = Secp256k1ExtendedPublicKey::from_str(&account_info.account_xpub)?; let capacity = account_info.external_addresses_number + account_info.internal_addresses_number + DEFAULT_GAP_LIMIT; @@ -1679,7 +1684,7 @@ pub async fn kmd_rewards_info(coin: &T) -> Result( where T: UtxoCommonOps + GetUtxoListOps, { - let my_address = try_tx_s!(coin.as_ref().derivation_method.iguana_or_err()); + let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()); let (unspents, recently_sent_txs) = try_tx_s!(coin.get_unspent_ordered_list(my_address).await); generate_and_send_tx(&coin, unspents, None, FeePolicy::SendExact, recently_sent_txs, outputs).await } @@ -1763,7 +1768,7 @@ async fn generate_and_send_tx( where T: AsRef + UtxoTxGenerationOps + UtxoTxBroadcastOps, { - let my_address = try_tx_s!(coin.as_ref().derivation_method.iguana_or_err()); + let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()); let key_pair = try_tx_s!(coin.as_ref().priv_key_policy.key_pair_or_err()); let mut builder = UtxoTxBuilder::new(coin) @@ -1834,7 +1839,7 @@ pub fn address_by_conf_and_pubkey_str( address_format: None, gap_limit: None, enable_params: EnabledCoinBalanceParams::default(), - priv_key_policy: PrivKeyActivationPolicy::IguanaPrivKey, + priv_key_policy: PrivKeyActivationPolicy::ContextPrivKey, check_utxo_maturity: None, }; let conf_builder = UtxoConfBuilder::new(conf, ¶ms, coin); diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 6df266754d..7bf2328d00 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -10,14 +10,14 @@ 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, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, SearchForSwapTxSpendInput, - SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureResult, SwapOps, - TradePreimageValue, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, - WatcherValidatePaymentInput, WithdrawFut}; + CoinWithDerivationMethod, IguanaPrivKey, NegotiateSwapContractAddrErr, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, + SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, + SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, + SendTakerSpendsMakerPaymentArgs, SignatureResult, SwapOps, TradePreimageValue, TransactionFut, + TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WatcherOps, WatcherValidatePaymentInput, WithdrawFut}; use common::log::warn; use derive_more::Display; use futures::{FutureExt, TryFutureExt}; @@ -303,7 +303,7 @@ impl BchCoin { let my_address = self .as_ref() .derivation_method - .iguana_or_err() + .single_addr_or_err() .mm_err(|e| UtxoRpcError::Internal(e.to_string()))?; let (mut bch_unspents, recently_spent) = self.bch_unspents_for_spend(my_address).await?; let (mut slp_unspents, standard_utxos) = ( @@ -322,7 +322,7 @@ impl BchCoin { let my_address = self .as_ref() .derivation_method - .iguana_or_err() + .single_addr_or_err() .mm_err(|e| UtxoRpcError::Internal(e.to_string()))?; let mut bch_unspents = self.bch_unspents_for_display(my_address).await?; let (mut slp_unspents, standard_utxos) = ( @@ -343,7 +343,7 @@ impl BchCoin { } pub fn get_my_slp_address(&self) -> Result { - let my_address = try_s!(self.as_ref().derivation_method.iguana_or_err()); + let my_address = try_s!(self.as_ref().derivation_method.single_addr_or_err()); let slp_address = my_address.to_cashaddress( &self.slp_prefix().to_string(), self.as_ref().conf.pub_addr_prefix, @@ -601,13 +601,13 @@ impl AsRef for BchCoin { fn as_ref(&self) -> &UtxoCoinFields { &self.utxo_arc } } -pub async fn bch_coin_from_conf_and_params( +pub async fn bch_coin_with_policy( ctx: &MmArc, ticker: &str, conf: &Json, params: BchActivationRequest, slp_addr_prefix: CashAddrPrefix, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result { if params.bchd_urls.is_empty() && !params.allow_slp_unsafe_conf { return Err("Using empty bchd_urls is unsafe for SLP users!".into()); @@ -624,7 +624,6 @@ pub async fn bch_coin_from_conf_and_params( } }; - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); let coin = try_s!( UtxoArcBuilder::new(ctx, ticker, conf, ¶ms.utxo_params, priv_key_policy, constructor) .build() @@ -633,6 +632,18 @@ pub async fn bch_coin_from_conf_and_params( Ok(coin) } +pub async fn bch_coin_with_priv_key( + ctx: &MmArc, + ticker: &str, + conf: &Json, + params: BchActivationRequest, + slp_addr_prefix: CashAddrPrefix, + priv_key: IguanaPrivKey, +) -> Result { + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); + bch_coin_with_policy(ctx, ticker, conf, params, slp_addr_prefix, priv_key_policy).await +} + #[derive(Debug)] pub enum BchActivationError { CoinInitError(String), @@ -1105,7 +1116,7 @@ impl MarketCoinOps for BchCoin { fn my_balance(&self) -> BalanceFut { let coin = self.clone(); let fut = async move { - let my_address = coin.as_ref().derivation_method.iguana_or_err()?; + let my_address = coin.as_ref().derivation_method.single_addr_or_err()?; let bch_unspents = coin.bch_unspents_for_display(my_address).await?; Ok(bch_unspents.platform_balance(coin.as_ref().decimals)) }; @@ -1288,7 +1299,7 @@ impl CoinWithTxHistoryV2 for BchCoin { #[async_trait] impl UtxoTxHistoryOps for BchCoin { async fn my_addresses(&self) -> MmResult, UtxoMyAddressesHistoryError> { - let my_address = self.as_ref().derivation_method.iguana_or_err()?; + let my_address = self.as_ref().derivation_method.single_addr_or_err()?; Ok(std::iter::once(my_address.clone()).collect()) } @@ -1360,13 +1371,13 @@ pub fn tbch_coin_for_test() -> (MmArc, BchCoin) { }); let params = BchActivationRequest::from_legacy_req(&req).unwrap(); - let coin = block_on(bch_coin_from_conf_and_params( + let coin = block_on(bch_coin_with_priv_key( &ctx, "BCH", &conf, params, CashAddrPrefix::SlpTest, - &*keypair.private().secret, + keypair.private().secret, )) .unwrap(); (ctx, coin) @@ -1393,13 +1404,13 @@ pub fn bch_coin_for_test() -> BchCoin { }); let params = BchActivationRequest::from_legacy_req(&req).unwrap(); - block_on(bch_coin_from_conf_and_params( + block_on(bch_coin_with_priv_key( &ctx, "BCH", &conf, params, CashAddrPrefix::SimpleLedger, - &*keypair.private().secret, + keypair.private().secret, )) .unwrap() } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index e3a048867a..b7ae14a63d 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -18,12 +18,12 @@ use crate::rpc_command::init_scan_for_new_addresses::{self, InitScanAddressesRpc use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawTaskHandle}; use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::utxo_builder::{BlockHeaderUtxoArcOps, MergeUtxoArcOps, UtxoCoinBuildError, UtxoCoinBuilder, - UtxoCoinBuilderCommonOps, UtxoFieldsWithHardwareWalletBuilder, - UtxoFieldsWithIguanaPrivKeyBuilder}; + UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, + UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaSecretBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, DelegationError, - DelegationFut, GetWithdrawSenderAddress, NegotiateSwapContractAddrErr, PaymentInstructions, + DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureResult, StakingInfosFut, SwapOps, @@ -158,7 +158,7 @@ pub trait QtumBasedCoin: UtxoCommonOps + MarketCoinOps { } fn my_addr_as_contract_addr(&self) -> MmResult { - let my_address = self.as_ref().derivation_method.iguana_or_err()?.clone(); + let my_address = self.as_ref().derivation_method.single_addr_or_err()?.clone(); contract_addr_from_utxo_addr(my_address).mm_err(Qrc20AddressError::from) } @@ -200,7 +200,7 @@ pub struct QtumCoinBuilder<'a> { ticker: &'a str, conf: &'a Json, activation_params: &'a UtxoActivationParams, - priv_key_policy: PrivKeyBuildPolicy<'a>, + priv_key_policy: PrivKeyBuildPolicy, } #[async_trait] @@ -216,7 +216,9 @@ impl<'a> UtxoCoinBuilderCommonOps for QtumCoinBuilder<'a> { fn check_utxo_maturity(&self) -> bool { self.activation_params().check_utxo_maturity.unwrap_or(true) } } -impl<'a> UtxoFieldsWithIguanaPrivKeyBuilder for QtumCoinBuilder<'a> {} +impl<'a> UtxoFieldsWithIguanaSecretBuilder for QtumCoinBuilder<'a> {} + +impl<'a> UtxoFieldsWithGlobalHDBuilder for QtumCoinBuilder<'a> {} impl<'a> UtxoFieldsWithHardwareWalletBuilder for QtumCoinBuilder<'a> {} @@ -225,7 +227,7 @@ impl<'a> UtxoCoinBuilder for QtumCoinBuilder<'a> { type ResultCoin = QtumCoin; type Error = UtxoCoinBuildError; - fn priv_key_policy(&self) -> PrivKeyBuildPolicy<'_> { self.priv_key_policy.clone() } + fn priv_key_policy(&self) -> PrivKeyBuildPolicy { self.priv_key_policy.clone() } async fn build(self) -> MmResult { let utxo = self.build_utxo_fields().await?; @@ -246,7 +248,7 @@ impl<'a> QtumCoinBuilder<'a> { ticker: &'a str, conf: &'a Json, activation_params: &'a UtxoActivationParams, - priv_key_policy: PrivKeyBuildPolicy<'a>, + priv_key_policy: PrivKeyBuildPolicy, ) -> Self { QtumCoinBuilder { ctx, @@ -275,14 +277,13 @@ impl From for UtxoArc { fn from(coin: QtumCoin) -> Self { coin.utxo_arc } } -pub async fn qtum_coin_with_priv_key( +pub async fn qtum_coin_with_policy( ctx: &MmArc, ticker: &str, conf: &Json, activation_params: &UtxoActivationParams, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result { - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); let coin = try_s!( QtumCoinBuilder::new(ctx, ticker, conf, activation_params, priv_key_policy) .build() @@ -291,6 +292,17 @@ pub async fn qtum_coin_with_priv_key( Ok(coin) } +pub async fn qtum_coin_with_priv_key( + ctx: &MmArc, + ticker: &str, + conf: &Json, + activation_params: &UtxoActivationParams, + priv_key: IguanaPrivKey, +) -> Result { + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); + qtum_coin_with_policy(ctx, ticker, conf, activation_params, priv_key_policy).await +} + impl QtumBasedCoin for QtumCoin {} #[derive(Clone, Debug, Deserialize)] diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index 61e813fd01..5765e482b0 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -6,7 +6,7 @@ use crate::utxo::qtum::{QtumBasedCoin, QtumCoin, QtumDelegationOps, QtumDelegati use crate::utxo::rpc_clients::UtxoRpcClientEnum; use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, UtxoTxBuilder}; use crate::utxo::{qtum, utxo_common, Address, GetUtxoListOps, UtxoCommonOps}; -use crate::utxo::{PrivKeyNotAllowed, UTXO_LOCK}; +use crate::utxo::{PrivKeyPolicyNotAllowed, UTXO_LOCK}; use crate::{DelegationError, DelegationFut, DelegationResult, MarketCoinOps, StakingInfos, StakingInfosError, StakingInfosFut, StakingInfosResult, TransactionDetails, TransactionType}; use bitcrypto::dhash256; @@ -78,8 +78,8 @@ impl From for DelegationError { fn from(e: Qrc20AbiError) -> Self { DelegationError::from(QtumStakingAbiError::from(e)) } } -impl From for QtumStakingAbiError { - fn from(e: PrivKeyNotAllowed) -> Self { QtumStakingAbiError::Internal(e.to_string()) } +impl From for QtumStakingAbiError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { QtumStakingAbiError::Internal(e.to_string()) } } impl QtumDelegationOps for QtumCoin { @@ -201,7 +201,7 @@ impl QtumCoin { async fn get_delegation_infos_impl(&self) -> StakingInfosResult { let coin = self.as_ref(); - let my_address = coin.derivation_method.iguana_or_err()?; + let my_address = coin.derivation_method.single_addr_or_err()?; let staker = self.am_i_currently_staking().await?; let (unspents, _) = self.get_unspent_ordered_list(my_address).await?; @@ -272,7 +272,7 @@ impl QtumCoin { let utxo = self.as_ref(); let key_pair = utxo.priv_key_policy.key_pair_or_err()?; - let my_address = utxo.derivation_method.iguana_or_err()?; + let my_address = utxo.derivation_method.single_addr_or_err()?; let (unspents, _) = self.get_unspent_ordered_list(my_address).await?; let mut gas_fee = 0; diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index b1fdc21526..d1e24bd6d1 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -15,8 +15,8 @@ use crate::utxo::{generate_and_send_tx, sat_from_big_decimal, ActualTxFee, Addit UtxoCommonOps, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, - PaymentInstructions, PaymentInstructionsErr, PrivKeyNotAllowed, RawTransactionFut, RawTransactionRequest, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, + RawTransactionRequest, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureResult, SwapOps, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, @@ -151,7 +151,7 @@ pub enum SpendP2SHError { GenerateTxErr(GenerateTxError), Rpc(UtxoRpcError), SignTxErr(UtxoSignWithKeyPairError), - PrivKeyNotAllowed(PrivKeyNotAllowed), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), UnexpectedDerivationMethod(UnexpectedDerivationMethod), String(String), } @@ -168,8 +168,8 @@ impl From for SpendP2SHError { fn from(sign: UtxoSignWithKeyPairError) -> SpendP2SHError { SpendP2SHError::SignTxErr(sign) } } -impl From for SpendP2SHError { - fn from(e: PrivKeyNotAllowed) -> Self { SpendP2SHError::PrivKeyNotAllowed(e) } +impl From for SpendP2SHError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { SpendP2SHError::PrivKeyPolicyNotAllowed(e) } } impl From for SpendP2SHError { @@ -1079,7 +1079,7 @@ impl MarketCoinOps for SlpToken { fn ticker(&self) -> &str { &self.conf.ticker } fn my_address(&self) -> MmResult { - let my_address = self.as_ref().derivation_method.iguana_or_err()?; + let my_address = self.as_ref().derivation_method.single_addr_or_err()?; let slp_address = self .platform_coin .slp_address(my_address) @@ -1543,7 +1543,7 @@ impl MmCoin for SlpToken { fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { let coin = self.clone(); let fut = async move { - let my_address = coin.platform_coin.as_ref().derivation_method.iguana_or_err()?; + let my_address = coin.platform_coin.as_ref().derivation_method.single_addr_or_err()?; let key_pair = coin.platform_coin.as_ref().priv_key_policy.key_pair_or_err()?; let address = CashAddress::decode(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; @@ -2058,7 +2058,7 @@ mod slp_tests { let token_id = H256::from("bb309e48930671582bea508f9a1d9b491e49b69be3d6f372dc08da2ac6e90eb7"); let fusd = SlpToken::new(4, "FUSD".into(), token_id, bch.clone(), 0).unwrap(); - let bch_address = bch.as_ref().derivation_method.unwrap_iguana(); + let bch_address = bch.as_ref().derivation_method.unwrap_single_addr(); let (unspents, recently_spent) = block_on(bch.get_unspent_ordered_list(bch_address)).unwrap(); let secret_hash = hex::decode("5d9e149ad9ccb20e9f931a69b605df2ffde60242").unwrap(); diff --git a/mm2src/coins/utxo/utxo_builder/mod.rs b/mm2src/coins/utxo/utxo_builder/mod.rs index cd48444513..1870f04a58 100644 --- a/mm2src/coins/utxo/utxo_builder/mod.rs +++ b/mm2src/coins/utxo/utxo_builder/mod.rs @@ -4,6 +4,6 @@ mod utxo_conf_builder; pub use utxo_arc_builder::{BlockHeaderUtxoArcOps, MergeUtxoArcOps, UtxoArcBuilder}; pub use utxo_coin_builder::{UtxoCoinBuildError, UtxoCoinBuildResult, UtxoCoinBuilder, UtxoCoinBuilderCommonOps, - UtxoCoinWithIguanaPrivKeyBuilder, UtxoFieldsWithHardwareWalletBuilder, - UtxoFieldsWithIguanaPrivKeyBuilder}; + UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, + UtxoFieldsWithIguanaSecretBuilder}; pub use utxo_conf_builder::{UtxoConfBuilder, UtxoConfError, UtxoConfResult}; diff --git a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs index 42830667c0..95edfe0764 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs @@ -1,6 +1,7 @@ use crate::utxo::rpc_clients::UtxoRpcClientEnum; use crate::utxo::utxo_builder::{UtxoCoinBuildError, UtxoCoinBuilder, UtxoCoinBuilderCommonOps, - UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaPrivKeyBuilder}; + UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, + UtxoFieldsWithIguanaSecretBuilder}; use crate::utxo::{generate_and_send_tx, FeePolicy, GetUtxoListOps, UtxoArc, UtxoCommonOps, UtxoSyncStatusLoopHandle, UtxoWeak}; use crate::{DerivationMethod, PrivKeyBuildPolicy, UtxoActivationParams}; @@ -30,7 +31,7 @@ where ticker: &'a str, conf: &'a Json, activation_params: &'a UtxoActivationParams, - priv_key_policy: PrivKeyBuildPolicy<'a>, + priv_key_policy: PrivKeyBuildPolicy, constructor: F, } @@ -43,7 +44,7 @@ where ticker: &'a str, conf: &'a Json, activation_params: &'a UtxoActivationParams, - priv_key_policy: PrivKeyBuildPolicy<'a>, + priv_key_policy: PrivKeyBuildPolicy, constructor: F, ) -> UtxoArcBuilder<'a, F, T> { UtxoArcBuilder { @@ -71,7 +72,12 @@ where fn ticker(&self) -> &str { self.ticker } } -impl<'a, F, T> UtxoFieldsWithIguanaPrivKeyBuilder for UtxoArcBuilder<'a, F, T> where +impl<'a, F, T> UtxoFieldsWithIguanaSecretBuilder for UtxoArcBuilder<'a, F, T> where + F: Fn(UtxoArc) -> T + Send + Sync + 'static +{ +} + +impl<'a, F, T> UtxoFieldsWithGlobalHDBuilder for UtxoArcBuilder<'a, F, T> where F: Fn(UtxoArc) -> T + Send + Sync + 'static { } @@ -90,7 +96,7 @@ where type ResultCoin = T; type Error = UtxoCoinBuildError; - fn priv_key_policy(&self) -> PrivKeyBuildPolicy<'_> { self.priv_key_policy.clone() } + fn priv_key_policy(&self) -> PrivKeyBuildPolicy { self.priv_key_policy.clone() } async fn build(self) -> MmResult { let utxo = self.build_utxo_fields().await?; @@ -152,7 +158,7 @@ async fn merge_utxo_loop( }; let my_address = match coin.as_ref().derivation_method { - DerivationMethod::Iguana(ref my_address) => my_address, + DerivationMethod::SingleAddress(ref my_address) => my_address, DerivationMethod::HDWallet(_) => { warn!("'merge_utxo_loop' is currently not used for HD wallets"); return; diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 8b64ddc6a4..6a62189426 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -4,19 +4,20 @@ use crate::utxo::rpc_clients::{ElectrumClient, ElectrumClientImpl, ElectrumRpcRe UtxoRpcClientEnum}; use crate::utxo::tx_cache::{UtxoVerboseCacheOps, UtxoVerboseCacheShared}; use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; -use crate::utxo::utxo_builder::utxo_conf_builder::{UtxoConfBuilder, UtxoConfError, UtxoConfResult}; +use crate::utxo::utxo_builder::utxo_conf_builder::{UtxoConfBuilder, UtxoConfError}; use crate::utxo::{output_script, utxo_common, ElectrumBuilderArgs, ElectrumProtoVerifier, RecentlySpentOutPoints, TxFee, UtxoCoinConf, UtxoCoinFields, UtxoHDAccount, UtxoHDWallet, UtxoRpcMode, UtxoSyncStatus, UtxoSyncStatusLoopHandle, DEFAULT_GAP_LIMIT, UTXO_DUST_AMOUNT}; -use crate::{BlockchainNetwork, CoinTransportMetrics, DerivationMethod, HistorySyncState, PrivKeyBuildPolicy, - PrivKeyPolicy, RpcClientType, UtxoActivationParams}; +use crate::{BlockchainNetwork, CoinTransportMetrics, DerivationMethod, HistorySyncState, IguanaPrivKey, + PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, UtxoActivationParams}; use async_trait::async_trait; use chain::TxHashAlgo; use common::executor::{abortable_queue::AbortableQueue, AbortSettings, AbortableSystem, AbortedError, SpawnAbortable, Timer}; use common::log::{error, info}; use common::small_rng; -use crypto::{Bip32DerPathError, Bip44DerPathError, Bip44PathToCoin, CryptoCtx, CryptoInitError, HwWalletType}; +use crypto::{Bip32DerPathError, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, HwWalletType, Secp256k1Secret, + StandardHDPathError, StandardHDPathToCoin}; use derive_more::Display; use futures::channel::mpsc::{unbounded, Receiver as AsyncReceiver, UnboundedReceiver}; use futures::compat::Future01CompatExt; @@ -27,7 +28,7 @@ pub use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHashEnum, Key Type as ScriptType}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use primitives::hash::{H160, H256}; +use primitives::hash::H160; use rand::seq::SliceRandom; use serde_json::{self as json, Value as Json}; use spv_validation::storage::{BlockHeaderStorageError, BlockHeaderStorageOps}; @@ -66,8 +67,8 @@ pub enum UtxoCoinBuildError { ElectrumProtocolVersionCheckError(String), #[display(fmt = "Can not detect the user home directory")] CantDetectUserHome, - #[display(fmt = "Unexpected derivation method: {}", _0)] - UnexpectedDerivationMethod(String), + #[display(fmt = "Private key policy is not allowed: {}", _0)] + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), #[display(fmt = "Hardware Wallet context is not initialized")] HwContextNotInitialized, HDWalletStorageError(HDWalletStorageError), @@ -86,13 +87,13 @@ impl From for UtxoCoinBuildError { fn from(e: UtxoConfError) -> Self { UtxoCoinBuildError::ConfError(e) } } -impl From for UtxoCoinBuildError { +impl From for UtxoCoinBuildError { /// `CryptoCtx` is expected to be initialized already. - fn from(crypto_err: CryptoInitError) -> Self { UtxoCoinBuildError::Internal(crypto_err.to_string()) } + fn from(crypto_err: CryptoCtxError) -> Self { UtxoCoinBuildError::Internal(crypto_err.to_string()) } } impl From for UtxoCoinBuildError { - fn from(e: Bip32DerPathError) -> Self { UtxoCoinBuildError::Internal(Bip44DerPathError::from(e).to_string()) } + fn from(e: Bip32DerPathError) -> Self { UtxoCoinBuildError::Internal(StandardHDPathError::from(e).to_string()) } } impl From for UtxoCoinBuildError { @@ -108,92 +109,119 @@ impl From for UtxoCoinBuildError { } #[async_trait] -pub trait UtxoCoinBuilder: UtxoFieldsWithIguanaPrivKeyBuilder + UtxoFieldsWithHardwareWalletBuilder { +pub trait UtxoCoinBuilder: + UtxoFieldsWithIguanaSecretBuilder + UtxoFieldsWithGlobalHDBuilder + UtxoFieldsWithHardwareWalletBuilder +{ type ResultCoin; type Error: NotMmError; - fn priv_key_policy(&self) -> PrivKeyBuildPolicy<'_>; + fn priv_key_policy(&self) -> PrivKeyBuildPolicy; async fn build(self) -> MmResult; async fn build_utxo_fields(&self) -> UtxoCoinBuildResult { match self.priv_key_policy() { - PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => self.build_utxo_fields_with_iguana_priv_key(priv_key).await, + PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => self.build_utxo_fields_with_iguana_secret(priv_key).await, + PrivKeyBuildPolicy::GlobalHDAccount(global_hd_ctx) => { + self.build_utxo_fields_with_global_hd(global_hd_ctx).await + }, PrivKeyBuildPolicy::Trezor => self.build_utxo_fields_with_trezor().await, } } } #[async_trait] -pub trait UtxoCoinWithIguanaPrivKeyBuilder: UtxoFieldsWithIguanaPrivKeyBuilder { - type ResultCoin; - type Error: NotMmError; - - fn priv_key(&self) -> &[u8]; - - async fn build(self) -> MmResult; +pub trait UtxoFieldsWithIguanaSecretBuilder: UtxoCoinBuilderCommonOps { + async fn build_utxo_fields_with_iguana_secret( + &self, + priv_key: IguanaPrivKey, + ) -> UtxoCoinBuildResult { + let conf = UtxoConfBuilder::new(self.conf(), self.activation_params(), self.ticker()).build()?; + build_utxo_coin_fields_with_conf_and_secret(self, conf, priv_key).await + } } #[async_trait] -pub trait UtxoFieldsWithIguanaPrivKeyBuilder: UtxoCoinBuilderCommonOps { - async fn build_utxo_fields_with_iguana_priv_key(&self, priv_key: &[u8]) -> UtxoCoinBuildResult { +pub trait UtxoFieldsWithGlobalHDBuilder: UtxoCoinBuilderCommonOps { + async fn build_utxo_fields_with_global_hd( + &self, + global_hd_ctx: GlobalHDAccountArc, + ) -> UtxoCoinBuildResult { let conf = UtxoConfBuilder::new(self.conf(), self.activation_params(), self.ticker()).build()?; - let private = Private { - prefix: conf.wif_prefix, - secret: H256::from(priv_key), - compressed: true, - checksum_type: conf.checksum_type, - }; - let key_pair = KeyPair::from_private(private).map_to_mm(|e| UtxoCoinBuildError::Internal(e.to_string()))?; - let addr_format = self.address_format()?; - let my_address = Address { - prefix: conf.pub_addr_prefix, - t_addr_prefix: conf.pub_t_addr_prefix, - hash: AddressHashEnum::AddressHash(key_pair.public().address_hash()), - checksum_type: conf.checksum_type, - hrp: conf.bech32_hrp.clone(), - addr_format, - }; - - let my_script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); - let derivation_method = DerivationMethod::Iguana(my_address); - let priv_key_policy = PrivKeyPolicy::KeyPair(key_pair); - - // Create an abortable system linked to the `MmCtx` so if the context is stopped via `MmArc::stop`, - // all spawned futures related to this `UTXO` coin will be aborted as well. - let abortable_system: AbortableQueue = self.ctx().abortable_system.create_subsystem()?; - - let rpc_client = self.rpc_client(abortable_system.create_subsystem()?).await?; - let tx_fee = self.tx_fee(&rpc_client).await?; - let decimals = self.decimals(&rpc_client).await?; - let dust_amount = self.dust_amount(); + let derivation_path = conf + .derivation_path + .as_ref() + .or_mm_err(|| UtxoConfError::DerivationPathIsNotSet)?; + let secret = global_hd_ctx + .derive_secp256k1_secret(derivation_path) + .mm_err(|e| UtxoCoinBuildError::Internal(e.to_string()))?; + build_utxo_coin_fields_with_conf_and_secret(self, conf, secret).await + } +} - let initial_history_state = self.initial_history_state(); - let tx_hash_algo = self.tx_hash_algo(); - let check_utxo_maturity = self.check_utxo_maturity(); - let tx_cache = self.tx_cache(); - let (block_headers_status_notifier, block_headers_status_watcher) = self.block_header_status_channel(); +async fn build_utxo_coin_fields_with_conf_and_secret( + builder: &Builder, + conf: UtxoCoinConf, + secret: Secp256k1Secret, +) -> UtxoCoinBuildResult +where + Builder: UtxoCoinBuilderCommonOps + Sync + ?Sized, +{ + let private = Private { + prefix: conf.wif_prefix, + secret, + compressed: true, + checksum_type: conf.checksum_type, + }; + let key_pair = KeyPair::from_private(private).map_to_mm(|e| UtxoCoinBuildError::Internal(e.to_string()))?; + let addr_format = builder.address_format()?; + let my_address = Address { + prefix: conf.pub_addr_prefix, + t_addr_prefix: conf.pub_t_addr_prefix, + hash: AddressHashEnum::AddressHash(key_pair.public().address_hash()), + checksum_type: conf.checksum_type, + hrp: conf.bech32_hrp.clone(), + addr_format, + }; - let coin = UtxoCoinFields { - conf, - decimals, - dust_amount, - rpc_client, - priv_key_policy, - derivation_method, - history_sync_state: Mutex::new(initial_history_state), - tx_cache, - recently_spent_outpoints: AsyncMutex::new(RecentlySpentOutPoints::new(my_script_pubkey)), - tx_fee, - tx_hash_algo, - check_utxo_maturity, - block_headers_status_notifier, - block_headers_status_watcher, - abortable_system, - }; - Ok(coin) - } + let my_script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); + let derivation_method = DerivationMethod::SingleAddress(my_address); + let priv_key_policy = PrivKeyPolicy::KeyPair(key_pair); + + // Create an abortable system linked to the `MmCtx` so if the context is stopped via `MmArc::stop`, + // all spawned futures related to this `UTXO` coin will be aborted as well. + let abortable_system: AbortableQueue = builder.ctx().abortable_system.create_subsystem()?; + + let rpc_client = builder.rpc_client(abortable_system.create_subsystem()?).await?; + let tx_fee = builder.tx_fee(&rpc_client).await?; + let decimals = builder.decimals(&rpc_client).await?; + let dust_amount = builder.dust_amount(); + + let initial_history_state = builder.initial_history_state(); + let tx_hash_algo = builder.tx_hash_algo(); + let check_utxo_maturity = builder.check_utxo_maturity(); + let tx_cache = builder.tx_cache(); + let (block_headers_status_notifier, block_headers_status_watcher) = builder.block_header_status_channel(); + + let coin = UtxoCoinFields { + conf, + decimals, + dust_amount, + rpc_client, + priv_key_policy, + derivation_method, + history_sync_state: Mutex::new(initial_history_state), + tx_cache, + recently_spent_outpoints: AsyncMutex::new(RecentlySpentOutPoints::new(my_script_pubkey)), + tx_fee, + tx_hash_algo, + check_utxo_maturity, + block_headers_status_notifier, + block_headers_status_watcher, + abortable_system, + }; + Ok(coin) } #[async_trait] @@ -213,7 +241,10 @@ pub trait UtxoFieldsWithHardwareWalletBuilder: UtxoCoinBuilderCommonOps { let recently_spent_outpoints = AsyncMutex::new(RecentlySpentOutPoints::new(my_script_pubkey)); let address_format = self.address_format()?; - let derivation_path = self.derivation_path()?; + let derivation_path = conf + .derivation_path + .clone() + .or_mm_err(|| UtxoConfError::DerivationPathIsNotSet)?; let hd_wallet_storage = HDWalletCoinStorage::init(self.ctx(), ticker).await?; @@ -268,21 +299,13 @@ pub trait UtxoFieldsWithHardwareWalletBuilder: UtxoCoinBuilderCommonOps { async fn load_hd_wallet_accounts( &self, hd_wallet_storage: &HDWalletCoinStorage, - derivation_path: &Bip44PathToCoin, + derivation_path: &StandardHDPathToCoin, ) -> UtxoCoinBuildResult> { utxo_common::load_hd_accounts_from_storage(hd_wallet_storage, derivation_path) .await .mm_err(UtxoCoinBuildError::from) } - fn derivation_path(&self) -> UtxoConfResult { - if self.conf()["derivation_path"].is_null() { - return MmError::err(UtxoConfError::DerivationPathIsNotSet); - } - json::from_value(self.conf()["derivation_path"].clone()) - .map_to_mm(|e| UtxoConfError::ErrorDeserializingDerivationPath(e.to_string())) - } - fn gap_limit(&self) -> u32 { self.activation_params().gap_limit.unwrap_or(DEFAULT_GAP_LIMIT) } fn supports_trezor(&self, conf: &UtxoCoinConf) -> bool { conf.trezor_coin.is_some() } diff --git a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs index 20cc29371d..f341b8df74 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs @@ -3,7 +3,7 @@ use crate::utxo::{parse_hex_encoded_u32, UtxoCoinConf, DEFAULT_DYNAMIC_FEE_VOLAT MATURE_CONFIRMATIONS_DEFAULT}; use crate::UtxoActivationParams; use bitcrypto::ChecksumType; -use crypto::{Bip32Error, ChildNumber}; +use crypto::{Bip32Error, StandardHDPathToCoin}; use derive_more::Display; pub use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHashEnum, KeyPair, Private, Public, Secret, Type as ScriptType}; @@ -24,17 +24,6 @@ pub enum UtxoConfError { DerivationPathIsNotSet, #[display(fmt = "'trezor_coin' field is not found in config")] TrezorCoinIsNotSet, - #[display(fmt = "Invalid 'derivation_path' purpose {}. BIP44 is supported only", found)] - InvalidDerivationPathPurpose { - found: ChildNumber, - }, - #[display( - fmt = "Invalid length '{}' of 'derivation_path'. Expected \"m/purpose'/coin_type'/\" path, i.e 2 children", - found_children - )] - InvalidDerivationPathLen { - found_children: usize, - }, #[display(fmt = "Error deserializing 'derivation_path': {}", _0)] ErrorDeserializingDerivationPath(String), InvalidConsensusBranchId(String), @@ -99,6 +88,7 @@ impl<'a> UtxoConfBuilder<'a> { let trezor_coin = self.trezor_coin(); let enable_spv_proof = self.enable_spv_proof(); let block_headers_verification_params = self.block_headers_verification_params(); + let derivation_path = self.derivation_path()?; Ok(UtxoCoinConf { ticker: self.ticker.to_owned(), @@ -132,6 +122,7 @@ impl<'a> UtxoConfBuilder<'a> { trezor_coin, enable_spv_proof, block_headers_verification_params, + derivation_path, }) } @@ -297,4 +288,9 @@ impl<'a> UtxoConfBuilder<'a> { fn block_headers_verification_params(&self) -> Option { json::from_value(self.conf["block_headers_verification_params"].clone()).unwrap_or(None) } + + fn derivation_path(&self) -> UtxoConfResult> { + json::from_value(self.conf["derivation_path"].clone()) + .map_to_mm(|e| UtxoConfError::ErrorDeserializingDerivationPath(e.to_string())) + } } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 99ef2ae9d5..c1dcdf4696 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -25,7 +25,7 @@ use common::executor::Timer; use common::jsonrpc_client::JsonRpcErrorType; use common::log::{error, warn}; use common::{now_ms, one_hundred, ten_f64}; -use crypto::{Bip32DerPathOps, Bip44Chain, Bip44DerPathError, Bip44DerivationPath, RpcDerivationPath}; +use crypto::{Bip32DerPathOps, Bip44Chain, RpcDerivationPath, StandardHDPath, StandardHDPathError}; use futures::compat::Future01CompatExt; use futures::future::{FutureExt, TryFutureExt}; use futures01::future::Either; @@ -224,7 +224,7 @@ where let account_child = ChildNumber::new(new_account_id, account_child_hardened) .map_to_mm(|e| NewAccountCreatingError::Internal(e.to_string()))?; - let account_derivation_path: Bip44PathToAccount = hd_wallet.derivation_path.derive(account_child)?; + let account_derivation_path: StandardHDPathToAccount = hd_wallet.derivation_path.derive(account_child)?; let account_pubkey = coin .extract_extended_pubkey(xpub_extractor, account_derivation_path.to_derivation_path()) .await?; @@ -440,7 +440,7 @@ where pub async fn load_hd_accounts_from_storage( hd_wallet_storage: &HDWalletCoinStorage, - derivation_path: &Bip44PathToCoin, + derivation_path: &StandardHDPathToCoin, ) -> HDWalletStorageResult> { let accounts = hd_wallet_storage.load_all_accounts().await?; let res: HDWalletStorageResult> = accounts @@ -655,7 +655,7 @@ pub fn my_public_key(coin: &UtxoCoinFields) -> Result<&Public, MmError Ok(key_pair.public()), // Hardware Wallets requires BIP39/BIP44 derivation path to extract a public key. - PrivKeyPolicy::Trezor => MmError::err(UnexpectedDerivationMethod::IguanaPrivKeyUnavailable), + PrivKeyPolicy::Trezor => MmError::err(UnexpectedDerivationMethod::ExpectedSingleAddress), } } @@ -721,7 +721,7 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { UtxoTxBuilder { tx: coin.as_ref().transaction_preimage(), coin, - from: coin.as_ref().derivation_method.iguana().cloned(), + from: coin.as_ref().derivation_method.single_addr().cloned(), available_inputs: vec![], fee_policy: FeePolicy::SendExact, fee: None, @@ -1229,7 +1229,7 @@ pub fn send_maker_spends_taker_payment( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + 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))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); @@ -1338,7 +1338,7 @@ pub fn create_taker_spends_maker_payment_preimage( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + 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))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); @@ -1399,7 +1399,7 @@ pub fn create_taker_refunds_payment_preimage( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + 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)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; @@ -1460,7 +1460,7 @@ pub fn send_taker_spends_maker_payment( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + 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))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); @@ -1526,7 +1526,7 @@ pub fn send_taker_refunds_payment( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + 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)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; @@ -1606,7 +1606,7 @@ pub fn send_maker_refunds_payment( secret_hash: &[u8], swap_unique_data: &[u8], ) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.iguana_or_err()).clone(); + 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))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); @@ -2097,7 +2097,7 @@ pub fn extract_secret(secret_hash: &[u8], spend_tx: &[u8]) -> Result, St pub fn my_address(coin: &T) -> MmResult { match coin.as_ref().derivation_method { - DerivationMethod::Iguana(ref my_address) => { + DerivationMethod::SingleAddress(ref my_address) => { my_address.display_address().map_to_mm(MyAddressError::InternalError) }, DerivationMethod::HDWallet(_) => MmError::err(MyAddressError::UnexpectedDerivationMethod( @@ -2147,7 +2147,7 @@ where let my_address = try_f!(coin .as_ref() .derivation_method - .iguana_or_err() + .single_addr_or_err() .mm_err(BalanceError::from)) .clone(); let fut = async move { address_balance(&coin, &my_address).await }; @@ -2329,7 +2329,7 @@ where + UtxoCommonOps, { match coin.derivation_method() { - DerivationMethod::Iguana(my_address) => get_withdraw_iguana_sender(coin, req, my_address), + DerivationMethod::SingleAddress(my_address) => get_withdraw_iguana_sender(coin, req, my_address), DerivationMethod::HDWallet(hd_wallet) => get_withdraw_hd_sender(coin, req, hd_wallet).await, } } @@ -2369,8 +2369,8 @@ where } = match req.from.clone().or_mm_err(|| WithdrawError::FromAddressNotFound)? { WithdrawFrom::AddressId(id) => id, WithdrawFrom::DerivationPath { derivation_path } => { - let derivation_path = Bip44DerivationPath::from_str(&derivation_path) - .map_to_mm(Bip44DerPathError::from) + let derivation_path = StandardHDPath::from_str(&derivation_path) + .map_to_mm(StandardHDPathError::from) .mm_err(|e| WithdrawError::UnexpectedFromAddress(e.to_string()))?; let coin_type = derivation_path.coin_type(); let expected_coin_type = hd_wallet.coin_type(); @@ -2812,7 +2812,7 @@ where .collect() }, UtxoRpcClientEnum::Electrum(client) => { - let my_address = match coin.as_ref().derivation_method.iguana_or_err() { + let my_address = match coin.as_ref().derivation_method.single_addr_or_err() { Ok(my_address) => my_address, Err(e) => return RequestTxHistoryResult::CriticalError(e.to_string()), }; @@ -2875,7 +2875,7 @@ pub async fn tx_details_by_hash( let verbose_tx = try_s!(coin.as_ref().rpc_client.get_verbose_transaction(&hash).compat().await); let mut tx: UtxoTx = try_s!(deserialize(verbose_tx.hex.as_slice()).map_err(|e| ERRL!("{:?}", e))); tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - let my_address = try_s!(coin.as_ref().derivation_method.iguana_or_err()); + let my_address = try_s!(coin.as_ref().derivation_method.single_addr_or_err()); input_transactions.insert(hash, HistoryUtxoTx { tx: tx.clone(), @@ -3161,7 +3161,7 @@ where let tx_fee = coin.get_tx_fee().await?; // [`FeePolicy::DeductFromOutput`] is used if the value is [`TradePreimageValue::UpperBound`] only let is_amount_upper_bound = matches!(fee_policy, FeePolicy::DeductFromOutput(_)); - let my_address = coin.as_ref().derivation_method.iguana_or_err()?; + let my_address = coin.as_ref().derivation_method.single_addr_or_err()?; match tx_fee { // if it's a dynamic fee, we should generate a swap transaction to get an actual trade fee @@ -3986,7 +3986,7 @@ where pub fn addr_format(coin: &dyn AsRef) -> &UtxoAddressFormat { match coin.as_ref().derivation_method { - DerivationMethod::Iguana(ref my_address) => &my_address.addr_format, + DerivationMethod::SingleAddress(ref my_address) => &my_address.addr_format, DerivationMethod::HDWallet(UtxoHDWallet { ref address_format, .. }) => address_format, } } diff --git a/mm2src/coins/utxo/utxo_common/utxo_tx_history_v2_common.rs b/mm2src/coins/utxo/utxo_common/utxo_tx_history_v2_common.rs index ad293aabfd..8e6c8dcdd3 100644 --- a/mm2src/coins/utxo/utxo_common/utxo_tx_history_v2_common.rs +++ b/mm2src/coins/utxo/utxo_common/utxo_tx_history_v2_common.rs @@ -42,11 +42,11 @@ where ::Address: DisplayAddress, { match (coin.derivation_method(), target) { - (DerivationMethod::Iguana(_), MyTxHistoryTarget::Iguana) => { + (DerivationMethod::SingleAddress(_), MyTxHistoryTarget::Iguana) => { let my_address = coin.my_address()?; Ok(GetTxHistoryFilters::for_address(my_address)) }, - (DerivationMethod::Iguana(_), target) => { + (DerivationMethod::SingleAddress(_), target) => { MmError::err(MyTxHistoryErrorV2::with_expected_target(target, "Iguana")) }, (DerivationMethod::HDWallet(hd_wallet), MyTxHistoryTarget::AccountId { account_id }) => { @@ -129,7 +129,7 @@ where const ADDRESSES_CAPACITY: usize = 60; match coin.as_ref().derivation_method { - DerivationMethod::Iguana(ref my_address) => Ok(iter::once(my_address.clone()).collect()), + DerivationMethod::SingleAddress(ref my_address) => Ok(iter::once(my_address.clone()).collect()), DerivationMethod::HDWallet(ref hd_wallet) => { let hd_accounts = hd_wallet.get_accounts().await; diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index cf26c41a5e..712a51f127 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -79,7 +79,7 @@ pub(super) fn utxo_coin_fields_for_test( let my_script_pubkey = Builder::build_p2pkh(&my_address.hash).to_bytes(); let priv_key_policy = PrivKeyPolicy::KeyPair(key_pair); - let derivation_method = DerivationMethod::Iguana(my_address); + let derivation_method = DerivationMethod::SingleAddress(my_address); let bech32_hrp = if is_segwit_coin { Some(TEST_COIN_HRP.to_string()) @@ -120,6 +120,7 @@ pub(super) fn utxo_coin_fields_for_test( trezor_coin: None, enable_spv_proof: false, block_headers_verification_params: None, + derivation_path: None, }, decimals: TEST_COIN_DECIMALS, dust_amount: UTXO_DUST_AMOUNT, @@ -242,7 +243,7 @@ pub(super) async fn test_hd_utxo_tx_history_impl(rpc_client: ElectrumClient) { let hd_account_for_test = UtxoHDAccount { account_id: 0, extended_pubkey: Secp256k1ExtendedPublicKey::from_str("xpub6DEHSksajpRPM59RPw7Eg6PKdU7E2ehxJWtYdrfQ6JFmMGBsrR6jA78ANCLgzKYm4s5UqQ4ydLEYPbh3TRVvn5oAZVtWfi4qJLMntpZ8uGJ").unwrap(), - account_derivation_path: Bip44PathToAccount::from_str("m/44'/141'/0'").unwrap(), + account_derivation_path: StandardHDPathToAccount::from_str("m/44'/141'/0'").unwrap(), external_addresses_number: 11, internal_addresses_number: 3, derived_addresses: HDAddressesCache::default(), @@ -256,7 +257,7 @@ pub(super) async fn test_hd_utxo_tx_history_impl(rpc_client: ElectrumClient) { hd_wallet_rmd160: "6d9d2b554d768232320587df75c4338ecc8bf37d".into(), hd_wallet_storage: HDWalletCoinStorage::default(), address_format: UtxoAddressFormat::Standard, - derivation_path: Bip44PathToCoin::from_str("m/44'/141'").unwrap(), + derivation_path: StandardHDPathToCoin::from_str("m/44'/141'").unwrap(), accounts: HDAccountsMutex::new(hd_accounts), gap_limit: 20, }); diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 010e9e8162..c4b7dab062 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -21,8 +21,8 @@ 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, - NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, + IguanaPrivKey, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, + PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureResult, SwapOps, TradePreimageValue, TransactionFut, TxMarshalingErr, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, @@ -51,14 +51,13 @@ impl From for UtxoArc { fn from(coin: UtxoStandardCoin) -> Self { coin.utxo_arc } } -pub async fn utxo_standard_coin_with_priv_key( +pub async fn utxo_standard_coin_with_policy( ctx: &MmArc, ticker: &str, conf: &Json, activation_params: &UtxoActivationParams, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result { - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); let coin = try_s!( UtxoArcBuilder::new( ctx, @@ -74,6 +73,17 @@ pub async fn utxo_standard_coin_with_priv_key( Ok(coin) } +pub async fn utxo_standard_coin_with_priv_key( + ctx: &MmArc, + ticker: &str, + conf: &Json, + activation_params: &UtxoActivationParams, + priv_key: IguanaPrivKey, +) -> Result { + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); + utxo_standard_coin_with_policy(ctx, ticker, conf, activation_params, priv_key_policy).await +} + // if mockable is placed before async_trait there is `munmap_chunk(): invalid pointer` error on async fn mocking attempt #[async_trait] #[cfg_attr(test, mockable)] diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index b2a4ffc5a2..25184be619 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -22,13 +22,13 @@ use crate::utxo::utxo_sql_block_header_storage::SqliteBlockHeadersStorage; use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps}; #[cfg(not(target_arch = "wasm32"))] use crate::WithdrawFee; -use crate::{BlockHeightAndTime, CoinBalance, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, +use crate::{BlockHeightAndTime, CoinBalance, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SendMakerSpendsTakerPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, TxMarshalingErr, ValidateFeeArgs}; use chain::{BlockHeader, OutPoint}; use common::executor::Timer; use common::{block_on, now_ms, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; -use crypto::{privkey::key_pair_from_seed, Bip44Chain, RpcDerivationPath}; +use crypto::{privkey::key_pair_from_seed, Bip44Chain, RpcDerivationPath, Secp256k1Secret}; use db_common::sqlite::rusqlite::Connection; use futures::future::join_all; use futures::TryFutureExt; @@ -52,7 +52,7 @@ pub fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { "servers": servers, }); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(&[]); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(IguanaPrivKey::default()); let builder = UtxoArcBuilder::new( &ctx, TEST_COIN_NAME, @@ -223,7 +223,7 @@ fn test_generate_transaction() { }]; let outputs = vec![TransactionOutput { - script_pubkey: Builder::build_p2pkh(&coin.as_ref().derivation_method.unwrap_iguana().hash).to_bytes(), + script_pubkey: Builder::build_p2pkh(&coin.as_ref().derivation_method.unwrap_single_addr().hash).to_bytes(), value: 100000, }]; @@ -908,7 +908,7 @@ fn test_utxo_lock() { let coin = utxo_coin_for_test(client.into(), None, false); let output = TransactionOutput { value: 1000000, - script_pubkey: Builder::build_p2pkh(&coin.as_ref().derivation_method.unwrap_iguana().hash).to_bytes(), + script_pubkey: Builder::build_p2pkh(&coin.as_ref().derivation_method.unwrap_single_addr().hash).to_bytes(), }; let mut futures = vec![]; for _ in 0..5 { @@ -1335,10 +1335,8 @@ fn test_cashaddresses_in_tx_details_by_hash() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "BCH", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "BCH", &conf, ¶ms, priv_key)).unwrap(); let tx_details = get_tx_details_eq_for_both_versions(&coin, TX_HASH); log!("{:?}", tx_details); @@ -1376,10 +1374,8 @@ fn test_address_from_str_with_cashaddress_activated() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "BCH", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "BCH", &conf, ¶ms, priv_key)).unwrap(); // other error on parse let error = UtxoCommonOps::address_from_str(&coin, "bitcoincash:000000000000000000000000000000000000000000") @@ -1416,10 +1412,8 @@ fn test_address_from_str_with_legacy_address_activated() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "BCH", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "BCH", &conf, ¶ms, priv_key)).unwrap(); let error = UtxoCommonOps::address_from_str(&coin, "bitcoincash:qzxqqt9lh4feptf0mplnk58gnajfepzwcq9f2rxk55") .err() @@ -1477,11 +1471,10 @@ fn test_unavailable_electrum_proto_version() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let error = block_on(utxo_standard_coin_with_priv_key( - &ctx, "RICK", &conf, ¶ms, &[1u8; 32], - )) - .err() - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let error = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)) + .err() + .unwrap(); log!("Error: {}", error); assert!(error.contains("There are no Electrums with the required protocol version")); } @@ -1505,13 +1498,13 @@ fn test_spam_rick() { "RICK", &conf, ¶ms, - &*key_pair.private().secret, + key_pair.private().secret, )) .unwrap(); let output = TransactionOutput { value: 1000000, - script_pubkey: Builder::build_p2pkh(&coin.as_ref().derivation_method.unwrap_iguana().hash).to_bytes(), + script_pubkey: Builder::build_p2pkh(&coin.as_ref().derivation_method.unwrap_single_addr().hash).to_bytes(), }; let mut futures = vec![]; for _ in 0..5 { @@ -1553,10 +1546,8 @@ fn test_one_unavailable_electrum_proto_version() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "BTC", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "BTC", &conf, ¶ms, priv_key)).unwrap(); block_on(async { Timer::sleep(0.5).await }); @@ -1565,10 +1556,10 @@ fn test_one_unavailable_electrum_proto_version() { #[test] fn test_qtum_generate_pod() { - let priv_key = [ + let priv_key = Secp256k1Secret::from([ 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, - ]; + ]); let conf = json!({"coin":"tQTUM","rpcport":13889,"pubtype":120,"p2shtype":110}); let req = json!({ "method": "electrum", @@ -1578,7 +1569,7 @@ fn test_qtum_generate_pod() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, &priv_key)).unwrap(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); let expected_res = "20086d757b34c01deacfef97a391f8ed2ca761c72a08d5000adc3d187b1007aca86a03bc5131b1f99b66873a12b51f8603213cdc1aa74c05ca5d48fe164b82152b"; let address = Address::from_str("qcyBHeSct7Wr4mAw18iuQ1zW5mMFYmtmBE").unwrap(); let res = coin.generate_pod(address.hash).unwrap(); @@ -1601,7 +1592,7 @@ fn test_qtum_add_delegation() { "tQTUM", &conf, ¶ms, - keypair.private().secret.as_slice(), + keypair.private().secret, )) .unwrap(); let address = Address::from_str("qcyBHeSct7Wr4mAw18iuQ1zW5mMFYmtmBE").unwrap(); @@ -1640,7 +1631,7 @@ fn test_qtum_add_delegation_on_already_delegating() { "tQTUM", &conf, ¶ms, - keypair.private().secret.as_slice(), + keypair.private().secret, )) .unwrap(); let address = Address::from_str("qcyBHeSct7Wr4mAw18iuQ1zW5mMFYmtmBE").unwrap(); @@ -1671,7 +1662,7 @@ fn test_qtum_get_delegation_infos() { "tQTUM", &conf, ¶ms, - keypair.private().secret.as_slice(), + keypair.private().secret, )) .unwrap(); let staking_infos = coin.get_delegation_infos().wait().unwrap(); @@ -1701,7 +1692,7 @@ fn test_qtum_remove_delegation() { "tQTUM", &conf, ¶ms, - keypair.private().secret.as_slice(), + keypair.private().secret, )) .unwrap(); let res = coin.remove_delegation().wait(); @@ -1754,13 +1745,13 @@ fn test_qtum_my_balance() { let ctx = MmCtxBuilder::new().into_mm_arc(); - let priv_key = [ + let priv_key = Secp256k1Secret::from([ 184, 199, 116, 240, 113, 222, 8, 199, 253, 143, 98, 185, 127, 26, 87, 38, 246, 206, 159, 27, 207, 20, 27, 112, 184, 102, 137, 37, 78, 214, 113, 78, - ]; + ]); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, &priv_key)).unwrap(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); let CoinBalance { spendable, unspendable } = coin.my_balance().wait().unwrap(); let expected_spendable = BigDecimal::from(66); @@ -1790,13 +1781,13 @@ fn test_qtum_my_balance_with_check_utxo_maturity_false() { let ctx = MmCtxBuilder::new().into_mm_arc(); - let priv_key = [ + let priv_key = Secp256k1Secret::from([ 184, 199, 116, 240, 113, 222, 8, 199, 253, 143, 98, 185, 127, 26, 87, 38, 246, 206, 159, 27, 207, 20, 27, 112, 184, 102, 137, 37, 78, 214, 113, 78, - ]; + ]); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, &priv_key)).unwrap(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); let CoinBalance { spendable, unspendable } = coin.my_balance().wait().unwrap(); let expected_spendable = BigDecimal::from(DISPLAY_BALANCE); @@ -2685,8 +2676,9 @@ fn test_generate_tx_doge_fee() { let ctx = MmCtxBuilder::default().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&request).unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); let doge = block_on(utxo_standard_coin_with_priv_key( - &ctx, "DOGE", &config, ¶ms, &[1; 32], + &ctx, "DOGE", &config, ¶ms, priv_key, )) .unwrap(); @@ -2867,7 +2859,7 @@ fn test_tx_details_kmd_rewards() { ]); let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); - fields.derivation_method = DerivationMethod::Iguana(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); + fields.derivation_method = DerivationMethod::SingleAddress(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); let coin = utxo_coin_from_fields(fields); let tx_details = get_tx_details_eq_for_both_versions( @@ -2904,7 +2896,7 @@ fn test_tx_details_kmd_rewards_claimed_by_other() { ]); let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); - fields.derivation_method = DerivationMethod::Iguana(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); + fields.derivation_method = DerivationMethod::SingleAddress(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); let coin = utxo_coin_from_fields(fields); let tx_details = get_tx_details_eq_for_both_versions(&coin, TX_HASH); @@ -2956,7 +2948,7 @@ fn test_update_kmd_rewards() { ]); let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); - fields.derivation_method = DerivationMethod::Iguana(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); + fields.derivation_method = DerivationMethod::SingleAddress(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); let coin = utxo_coin_from_fields(fields); let mut input_transactions = HistoryUtxoTxMap::default(); @@ -2988,7 +2980,7 @@ fn test_update_kmd_rewards_claimed_not_by_me() { ]); let mut fields = utxo_coin_fields_for_test(electrum.into(), None, false); fields.conf.ticker = "KMD".to_owned(); - fields.derivation_method = DerivationMethod::Iguana(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); + fields.derivation_method = DerivationMethod::SingleAddress(Address::from("RMGJ9tRST45RnwEKHPGgBLuY3moSYP7Mhk")); let coin = utxo_coin_from_fields(fields); let mut input_transactions = HistoryUtxoTxMap::default(); @@ -3051,9 +3043,9 @@ fn test_withdraw_to_p2pkh() { // Create a p2pkh address for the test coin let p2pkh_address = Address { prefix: coin.as_ref().conf.pub_addr_prefix, - hash: coin.as_ref().derivation_method.unwrap_iguana().hash.clone(), + hash: coin.as_ref().derivation_method.unwrap_single_addr().hash.clone(), t_addr_prefix: coin.as_ref().conf.pub_t_addr_prefix, - checksum_type: coin.as_ref().derivation_method.unwrap_iguana().checksum_type, + checksum_type: coin.as_ref().derivation_method.unwrap_single_addr().checksum_type, hrp: coin.as_ref().conf.bech32_hrp.clone(), addr_format: UtxoAddressFormat::Standard, }; @@ -3099,9 +3091,9 @@ fn test_withdraw_to_p2sh() { // Create a p2sh address for the test coin let p2sh_address = Address { prefix: coin.as_ref().conf.p2sh_addr_prefix, - hash: coin.as_ref().derivation_method.unwrap_iguana().hash.clone(), + hash: coin.as_ref().derivation_method.unwrap_single_addr().hash.clone(), t_addr_prefix: coin.as_ref().conf.p2sh_t_addr_prefix, - checksum_type: coin.as_ref().derivation_method.unwrap_iguana().checksum_type, + checksum_type: coin.as_ref().derivation_method.unwrap_single_addr().checksum_type, hrp: coin.as_ref().conf.bech32_hrp.clone(), addr_format: UtxoAddressFormat::Standard, }; @@ -3147,9 +3139,9 @@ fn test_withdraw_to_p2wpkh() { // Create a p2wpkh address for the test coin let p2wpkh_address = Address { prefix: coin.as_ref().conf.pub_addr_prefix, - hash: coin.as_ref().derivation_method.unwrap_iguana().hash.clone(), + hash: coin.as_ref().derivation_method.unwrap_single_addr().hash.clone(), t_addr_prefix: coin.as_ref().conf.pub_t_addr_prefix, - checksum_type: coin.as_ref().derivation_method.unwrap_iguana().checksum_type, + checksum_type: coin.as_ref().derivation_method.unwrap_single_addr().checksum_type, hrp: coin.as_ref().conf.bech32_hrp.clone(), addr_format: UtxoAddressFormat::Segwit, }; @@ -3199,10 +3191,8 @@ fn test_utxo_standard_with_check_utxo_maturity_true() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "RICK", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)).unwrap(); let address = Address::from("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW"); // Don't use `block_on` here because it's used within a mock of [`GetUtxoListOps::get_mature_unspent_ordered_list`]. @@ -3241,10 +3231,8 @@ fn test_utxo_standard_without_check_utxo_maturity() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "RICK", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)).unwrap(); let address = Address::from("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW"); // Don't use `block_on` here because it's used within a mock of [`UtxoStandardCoin::get_all_unspent_ordered_list`]. @@ -3278,7 +3266,8 @@ fn test_qtum_without_check_utxo_maturity() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, &[1u8; 32])).unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); let address = Address::from("qcyBHeSct7Wr4mAw18iuQ1zW5mMFYmtmBE"); // Don't use `block_on` here because it's used within a mock of [`QtumCoin::get_mature_unspent_ordered_list`]. @@ -3290,10 +3279,10 @@ fn test_qtum_without_check_utxo_maturity() { #[test] #[ignore] fn test_split_qtum() { - let priv_key = [ + let priv_key = Secp256k1Secret::from([ 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, 172, 110, 180, 13, 123, 179, 10, 49, - ]; + ]); let conf = json!({ "coin": "tQTUM", "name": "qtumtest", @@ -3321,8 +3310,8 @@ fn test_split_qtum() { }); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, &priv_key)).unwrap(); - let p2pkh_address = coin.as_ref().derivation_method.unwrap_iguana(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); + let p2pkh_address = coin.as_ref().derivation_method.unwrap_single_addr(); let script: Script = output_script(p2pkh_address, ScriptType::P2PKH); let key_pair = coin.as_ref().priv_key_policy.key_pair_or_err().unwrap(); let (unspents, _) = block_on(coin.get_mature_unspent_ordered_list(p2pkh_address)).expect("Unspent list is empty"); @@ -3392,7 +3381,8 @@ fn test_qtum_with_check_utxo_maturity_false() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, &[1u8; 32])).unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); let address = Address::from("qcyBHeSct7Wr4mAw18iuQ1zW5mMFYmtmBE"); // Don't use `block_on` here because it's used within a mock of [`QtumCoin::get_all_unspent_ordered_list`]. @@ -3463,7 +3453,7 @@ fn test_account_balance_rpc() { hd_accounts.insert(0, UtxoHDAccount { account_id: 0, extended_pubkey: Secp256k1ExtendedPublicKey::from_str("xpub6DEHSksajpRPM59RPw7Eg6PKdU7E2ehxJWtYdrfQ6JFmMGBsrR6jA78ANCLgzKYm4s5UqQ4ydLEYPbh3TRVvn5oAZVtWfi4qJLMntpZ8uGJ").unwrap(), - account_derivation_path: Bip44PathToAccount::from_str("m/44'/141'/0'").unwrap(), + account_derivation_path: StandardHDPathToAccount::from_str("m/44'/141'/0'").unwrap(), external_addresses_number: 7, internal_addresses_number: 3, derived_addresses: HDAddressesCache::default(), @@ -3471,7 +3461,7 @@ fn test_account_balance_rpc() { hd_accounts.insert(1, UtxoHDAccount { account_id: 1, extended_pubkey: Secp256k1ExtendedPublicKey::from_str("xpub6DEHSksajpRPQq2FdGT6JoieiQZUpTZ3WZn8fcuLJhFVmtCpXbuXxp5aPzaokwcLV2V9LE55Dwt8JYkpuMv7jXKwmyD28WbHYjBH2zhbW2p").unwrap(), - account_derivation_path: Bip44PathToAccount::from_str("m/44'/141'/1'").unwrap(), + account_derivation_path: StandardHDPathToAccount::from_str("m/44'/141'/1'").unwrap(), external_addresses_number: 0, internal_addresses_number: 1, derived_addresses: HDAddressesCache::default(), @@ -3480,7 +3470,7 @@ fn test_account_balance_rpc() { hd_wallet_rmd160: "21605444b36ec72780bdf52a5ffbc18288893664".into(), hd_wallet_storage: HDWalletCoinStorage::default(), address_format: UtxoAddressFormat::Standard, - derivation_path: Bip44PathToCoin::from_str("m/44'/141'").unwrap(), + derivation_path: StandardHDPathToCoin::from_str("m/44'/141'").unwrap(), accounts: HDAccountsMutex::new(hd_accounts), gap_limit: 3, }); @@ -3790,7 +3780,7 @@ fn test_scan_for_new_addresses() { hd_accounts.insert(0, UtxoHDAccount { account_id: 0, extended_pubkey: Secp256k1ExtendedPublicKey::from_str("xpub6DEHSksajpRPM59RPw7Eg6PKdU7E2ehxJWtYdrfQ6JFmMGBsrR6jA78ANCLgzKYm4s5UqQ4ydLEYPbh3TRVvn5oAZVtWfi4qJLMntpZ8uGJ").unwrap(), - account_derivation_path: Bip44PathToAccount::from_str("m/44'/141'/0'").unwrap(), + account_derivation_path: StandardHDPathToAccount::from_str("m/44'/141'/0'").unwrap(), external_addresses_number: 3, internal_addresses_number: 1, derived_addresses: HDAddressesCache::default(), @@ -3798,7 +3788,7 @@ fn test_scan_for_new_addresses() { hd_accounts.insert(1, UtxoHDAccount { account_id: 1, extended_pubkey: Secp256k1ExtendedPublicKey::from_str("xpub6DEHSksajpRPQq2FdGT6JoieiQZUpTZ3WZn8fcuLJhFVmtCpXbuXxp5aPzaokwcLV2V9LE55Dwt8JYkpuMv7jXKwmyD28WbHYjBH2zhbW2p").unwrap(), - account_derivation_path: Bip44PathToAccount::from_str("m/44'/141'/1'").unwrap(), + account_derivation_path: StandardHDPathToAccount::from_str("m/44'/141'/1'").unwrap(), external_addresses_number: 0, internal_addresses_number: 2, derived_addresses: HDAddressesCache::default(), @@ -3807,7 +3797,7 @@ fn test_scan_for_new_addresses() { hd_wallet_rmd160: "21605444b36ec72780bdf52a5ffbc18288893664".into(), hd_wallet_storage: HDWalletCoinStorage::default(), address_format: UtxoAddressFormat::Standard, - derivation_path: Bip44PathToCoin::from_str("m/44'/141'").unwrap(), + derivation_path: StandardHDPathToCoin::from_str("m/44'/141'").unwrap(), accounts: HDAccountsMutex::new(hd_accounts), gap_limit: 3, }); @@ -3926,7 +3916,7 @@ fn test_get_new_address() { let hd_account_for_test = UtxoHDAccount { account_id: 0, extended_pubkey: Secp256k1ExtendedPublicKey::from_str("xpub6DEHSksajpRPM59RPw7Eg6PKdU7E2ehxJWtYdrfQ6JFmMGBsrR6jA78ANCLgzKYm4s5UqQ4ydLEYPbh3TRVvn5oAZVtWfi4qJLMntpZ8uGJ").unwrap(), - account_derivation_path: Bip44PathToAccount::from_str("m/44'/141'/0'").unwrap(), + account_derivation_path: StandardHDPathToAccount::from_str("m/44'/141'/0'").unwrap(), external_addresses_number: 4, internal_addresses_number: 0, derived_addresses: HDAddressesCache::default(), @@ -3941,7 +3931,7 @@ fn test_get_new_address() { hd_wallet_rmd160: "21605444b36ec72780bdf52a5ffbc18288893664".into(), hd_wallet_storage: HDWalletCoinStorage::default(), address_format: UtxoAddressFormat::Standard, - derivation_path: Bip44PathToCoin::from_str("m/44'/141'").unwrap(), + derivation_path: StandardHDPathToCoin::from_str("m/44'/141'").unwrap(), accounts: HDAccountsMutex::new(hd_accounts), gap_limit: 2, }); @@ -4253,10 +4243,8 @@ fn test_utxo_validate_valid_and_invalid_pubkey() { let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, "RICK", &conf, ¶ms, &[1u8; 32], - )) - .unwrap(); + let priv_key = Secp256k1Secret::from([1; 32]); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)).unwrap(); // Test expected to pass at this point as we're using a valid pubkey to validate against a valid pubkey assert!(coin .validate_other_pubkey(&[ diff --git a/mm2src/coins/utxo/utxo_withdraw.rs b/mm2src/coins/utxo/utxo_withdraw.rs index 3bd9845af5..5e5842c954 100644 --- a/mm2src/coins/utxo/utxo_withdraw.rs +++ b/mm2src/coins/utxo/utxo_withdraw.rs @@ -11,7 +11,7 @@ use common::now_ms; use crypto::hw_rpc_task::{HwConnectStatuses, TrezorRpcTaskConnectProcessor}; use crypto::trezor::client::TrezorClient; use crypto::trezor::{TrezorError, TrezorProcessingError}; -use crypto::{from_hw_error, CryptoCtx, CryptoInitError, DerivationPath, HwError, HwProcessingError, HwRpcError}; +use crypto::{from_hw_error, CryptoCtx, CryptoCtxError, DerivationPath, HwError, HwProcessingError, HwRpcError}; use keys::{Public as PublicKey, Type as ScriptType}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -70,8 +70,8 @@ impl From for WithdrawError { } } -impl From for WithdrawError { - fn from(e: CryptoInitError) -> Self { WithdrawError::InternalError(e.to_string()) } +impl From for WithdrawError { + fn from(e: CryptoCtxError) -> Self { WithdrawError::InternalError(e.to_string()) } } impl From for WithdrawError { @@ -431,7 +431,7 @@ where { #[allow(clippy::result_large_err)] pub fn new(coin: Coin, req: WithdrawRequest) -> Result> { - let my_address = coin.as_ref().derivation_method.iguana_or_err()?.clone(); + let my_address = coin.as_ref().derivation_method.single_addr_or_err()?.clone(); let my_address_string = coin.my_address()?; Ok(StandardUtxoWithdraw { coin, diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 75169f1038..abba509815 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -3,8 +3,9 @@ use crate::my_tx_history_v2::{MyTxHistoryErrorV2, MyTxHistoryRequestV2, MyTxHist use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawInProgressStatus, WithdrawTaskHandle}; use crate::utxo::rpc_clients::{ElectrumRpcRequest, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, UtxoRpcResult}; -use crate::utxo::utxo_builder::{UtxoCoinBuilderCommonOps, UtxoCoinWithIguanaPrivKeyBuilder, - UtxoFieldsWithIguanaPrivKeyBuilder}; +use crate::utxo::utxo_builder::{UtxoCoinBuildError, UtxoCoinBuilder, UtxoCoinBuilderCommonOps, + UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, + UtxoFieldsWithIguanaSecretBuilder}; use crate::utxo::utxo_common::{addresses_from_script, big_decimal_from_sat, big_decimal_from_sat_unsigned, payment_script}; use crate::utxo::{sat_from_big_decimal, utxo_common, ActualTxFee, AdditionalTxData, AddrFromStrError, Address, @@ -14,15 +15,15 @@ use crate::utxo::{sat_from_big_decimal, utxo_common, ActualTxFee, AdditionalTxDa VerboseTransactionFrom}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, - PaymentInstructions, PaymentInstructionsErr, PrivKeyActivationPolicy, RawTransactionFut, - RawTransactionRequest, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SignatureError, SignatureResult, SwapOps, TradeFee, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionFut, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationError, VerificationResult, WatcherOps, WatcherValidatePaymentInput, WithdrawFut, - WithdrawRequest}; + PaymentInstructions, PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, SearchForSwapTxSpendInput, + SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, + SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SignatureError, SignatureResult, SwapOps, + TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, + ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherValidatePaymentInput, + WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -30,6 +31,7 @@ use chain::constants::SEQUENCE_FINAL; use chain::{Transaction as UtxoTx, TransactionOutput}; use common::{async_blocking, calc_total_pages, log, PagingOptionsEnum}; use crypto::privkey::{key_pair_from_secret, secp_privkey_from_hash}; +use crypto::{Bip32DerPathOps, GlobalHDAccountArc, StandardHDPathToCoin}; use db_common::sqlite::offset_by_id; use db_common::sqlite::rusqlite::{Error as SqlError, Row, NO_PARAMS}; use db_common::sqlite::sql_builder::{name, SqlBuilder, SqlName}; @@ -50,6 +52,7 @@ use script::{Builder as ScriptBuilder, Opcode, Script, TransactionInputSigner}; use serde_json::Value as Json; use serialization::CoinVariant; use std::collections::{HashMap, HashSet}; +use std::iter; use std::path::PathBuf; use std::sync::Arc; use zcash_client_backend::data_api::WalletRead; @@ -66,6 +69,7 @@ use zcash_primitives::sapling::note_encryption::try_sapling_output_recovery; use zcash_primitives::transaction::builder::Builder as ZTxBuilder; use zcash_primitives::transaction::components::{Amount, TxOut}; use zcash_primitives::transaction::Transaction as ZTransaction; +use zcash_primitives::zip32::ChildIndex as Zip32Child; use zcash_primitives::{consensus, constants::mainnet as z_mainnet_constants, sapling::PaymentAddress, zip32::ExtendedFullViewingKey, zip32::ExtendedSpendingKey}; use zcash_proofs::prover::LocalTxProver; @@ -139,6 +143,8 @@ pub struct CheckPointBlockInfo { pub struct ZcoinProtocolInfo { consensus_params: ZcoinConsensusParams, check_point_block: Option, + // `z_derivation_path` can be the same or different from [`UtxoCoinFields::derivation_path`]. + z_derivation_path: Option, } impl Parameters for ZcoinConsensusParams { @@ -711,11 +717,21 @@ pub async fn z_coin_from_conf_and_params( conf: &Json, params: &ZcoinActivationParams, protocol_info: ZcoinProtocolInfo, - secp_priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result> { - let z_key = ExtendedSpendingKey::master(secp_priv_key); - let db_dir = ctx.dbdir(); - z_coin_from_conf_and_params_with_z_key(ctx, ticker, conf, params, secp_priv_key, db_dir, z_key, protocol_info).await + let db_dir_path = ctx.dbdir(); + let z_spending_key = None; + let builder = ZCoinBuilder::new( + ctx, + ticker, + conf, + params, + priv_key_policy, + db_dir_path, + z_spending_key, + protocol_info, + ); + builder.build().await } pub struct ZCoinBuilder<'a> { @@ -724,9 +740,10 @@ pub struct ZCoinBuilder<'a> { conf: &'a Json, z_coin_params: &'a ZcoinActivationParams, utxo_params: UtxoActivationParams, - secp_priv_key: &'a [u8], + priv_key_policy: PrivKeyBuildPolicy, db_dir_path: PathBuf, - z_spending_key: ExtendedSpendingKey, + /// `Some` if `ZCoin` should be initialized with a forced spending key. + z_spending_key: Option, protocol_info: ZcoinProtocolInfo, } @@ -740,22 +757,31 @@ impl<'a> UtxoCoinBuilderCommonOps for ZCoinBuilder<'a> { fn ticker(&self) -> &str { self.ticker } } -#[async_trait] -impl<'a> UtxoFieldsWithIguanaPrivKeyBuilder for ZCoinBuilder<'a> {} +impl<'a> UtxoFieldsWithIguanaSecretBuilder for ZCoinBuilder<'a> {} + +impl<'a> UtxoFieldsWithGlobalHDBuilder for ZCoinBuilder<'a> {} + +/// Although, `ZCoin` doesn't support [`PrivKeyBuildPolicy::Trezor`] yet, +/// `UtxoCoinBuilder` trait requires `UtxoFieldsWithHardwareWalletBuilder` to be implemented. +impl<'a> UtxoFieldsWithHardwareWalletBuilder for ZCoinBuilder<'a> {} #[async_trait] -impl<'a> UtxoCoinWithIguanaPrivKeyBuilder for ZCoinBuilder<'a> { +impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { type ResultCoin = ZCoin; type Error = ZCoinBuildError; - fn priv_key(&self) -> &[u8] { self.secp_priv_key } + fn priv_key_policy(&self) -> PrivKeyBuildPolicy { self.priv_key_policy.clone() } async fn build(self) -> MmResult { - let utxo = self.build_utxo_fields_with_iguana_priv_key(self.priv_key()).await?; + let utxo = self.build_utxo_fields().await?; let utxo_arc = UtxoArc::new(utxo); - let (_, my_z_addr) = self - .z_spending_key + let z_spending_key = match self.z_spending_key { + Some(ref z_spending_key) => z_spending_key.clone(), + None => extended_spending_key_from_protocol_info_and_policy(&self.protocol_info, &self.priv_key_policy)?, + }; + + let (_, my_z_addr) = z_spending_key .default_address() .map_err(|_| MmError::new(ZCoinBuildError::GetAddressError))?; @@ -792,7 +818,7 @@ impl<'a> UtxoCoinWithIguanaPrivKeyBuilder for ZCoinBuilder<'a> { &my_z_addr, ); - let evk = ExtendedFullViewingKey::from(&self.z_spending_key); + let evk = ExtendedFullViewingKey::from(&z_spending_key); let cache_db_path = self.db_dir_path.join(format!("{}_cache.db", self.ticker)); let wallet_db_path = self.db_dir_path.join(format!("{}_wallet.db", self.ticker)); let blocks_db = @@ -834,8 +860,8 @@ impl<'a> UtxoCoinWithIguanaPrivKeyBuilder for ZCoinBuilder<'a> { dex_fee_addr, my_z_addr, my_z_addr_encoded, - evk: ExtendedFullViewingKey::from(&self.z_spending_key), - z_spending_key: self.z_spending_key, + evk: ExtendedFullViewingKey::from(&z_spending_key), + z_spending_key, z_tx_prover: Arc::new(z_tx_prover), light_wallet_db, consensus_params: self.protocol_info.consensus_params, @@ -858,9 +884,9 @@ impl<'a> ZCoinBuilder<'a> { ticker: &'a str, conf: &'a Json, z_coin_params: &'a ZcoinActivationParams, - secp_priv_key: &'a [u8], + priv_key_policy: PrivKeyBuildPolicy, db_dir_path: PathBuf, - z_spending_key: ExtendedSpendingKey, + z_spending_key: Option, protocol_info: ZcoinProtocolInfo, ) -> ZCoinBuilder<'a> { let utxo_mode = match &z_coin_params.mode { @@ -878,7 +904,7 @@ impl<'a> ZCoinBuilder<'a> { address_format: None, gap_limit: None, enable_params: Default::default(), - priv_key_policy: PrivKeyActivationPolicy::IguanaPrivKey, + priv_key_policy: PrivKeyActivationPolicy::ContextPrivKey, check_utxo_maturity: None, }; ZCoinBuilder { @@ -887,7 +913,7 @@ impl<'a> ZCoinBuilder<'a> { conf, z_coin_params, utxo_params, - secp_priv_key, + priv_key_policy, db_dir_path, z_spending_key, protocol_info, @@ -895,13 +921,15 @@ impl<'a> ZCoinBuilder<'a> { } } +/// Initialize `ZCoin` with a forced `z_spending_key`. +#[cfg(all(test, feature = "zhtlc-native-tests"))] #[allow(clippy::too_many_arguments)] async fn z_coin_from_conf_and_params_with_z_key( ctx: &MmArc, ticker: &str, conf: &Json, params: &ZcoinActivationParams, - secp_priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, db_dir_path: PathBuf, z_spending_key: ExtendedSpendingKey, protocol_info: ZcoinProtocolInfo, @@ -911,9 +939,9 @@ async fn z_coin_from_conf_and_params_with_z_key( ticker, conf, params, - secp_priv_key, + priv_key_policy, db_dir_path, - z_spending_key, + Some(z_spending_key), protocol_info, ); builder.build().await @@ -1770,6 +1798,50 @@ impl InitWithdrawCoin for ZCoin { } } +fn extended_spending_key_from_protocol_info_and_policy( + protocol_info: &ZcoinProtocolInfo, + priv_key_policy: &PrivKeyBuildPolicy, +) -> MmResult { + match priv_key_policy { + PrivKeyBuildPolicy::IguanaPrivKey(iguana) => Ok(ExtendedSpendingKey::master(iguana.as_slice())), + PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { + extended_spending_key_from_global_hd_account(protocol_info, global_hd) + }, + PrivKeyBuildPolicy::Trezor => { + let priv_key_err = PrivKeyPolicyNotAllowed::HardwareWalletNotSupported; + MmError::err(ZCoinBuildError::UtxoBuilderError( + UtxoCoinBuildError::PrivKeyPolicyNotAllowed(priv_key_err), + )) + }, + } +} + +fn extended_spending_key_from_global_hd_account( + protocol_info: &ZcoinProtocolInfo, + global_hd: &GlobalHDAccountArc, +) -> MmResult { + let path_to_coin = protocol_info + .z_derivation_path + .clone() + .or_mm_err(|| ZCoinBuildError::ZDerivationPathNotSet)?; + + let path_to_account = path_to_coin + .to_derivation_path() + .into_iter() + // Map `bip32::ChildNumber` to `zip32::Zip32Child`. + .map(|child| Zip32Child::from_index(child.0)) + // Push the hardened `account` index, so the derivation path looks like: + // `m/purpose'/coin'/account'`. + .chain(iter::once(Zip32Child::Hardened(global_hd.account_id()))); + + let mut spending_key = ExtendedSpendingKey::master(global_hd.root_seed_bytes()); + for zip32_child in path_to_account { + spending_key = spending_key.derive_child(zip32_child); + } + + Ok(spending_key) +} + #[test] fn derive_z_key_from_mm_seed() { use crypto::privkey::key_pair_from_seed; diff --git a/mm2src/coins/z_coin/z_coin_errors.rs b/mm2src/coins/z_coin/z_coin_errors.rs index cd0b0846b5..53bb425190 100644 --- a/mm2src/coins/z_coin/z_coin_errors.rs +++ b/mm2src/coins/z_coin/z_coin_errors.rs @@ -2,7 +2,7 @@ use crate::my_tx_history_v2::MyTxHistoryErrorV2; use crate::utxo::rpc_clients::UtxoRpcError; use crate::utxo::utxo_builder::UtxoCoinBuildError; use crate::WithdrawError; -use crate::{NumConversError, PrivKeyNotAllowed}; +use crate::{NumConversError, PrivKeyPolicyNotAllowed}; use common::jsonrpc_client::JsonRpcError; use db_common::sqlite::rusqlite::Error as SqliteError; use db_common::sqlite::rusqlite::Error as SqlError; @@ -162,11 +162,11 @@ pub enum SendOutputsErr { NumConversion(NumConversError), Rpc(UtxoRpcError), TxNotMined(String), - PrivKeyNotAllowed(PrivKeyNotAllowed), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), } -impl From for SendOutputsErr { - fn from(err: PrivKeyNotAllowed) -> Self { SendOutputsErr::PrivKeyNotAllowed(err) } +impl From for SendOutputsErr { + fn from(err: PrivKeyPolicyNotAllowed) -> Self { SendOutputsErr::PrivKeyPolicyNotAllowed(err) } } impl From for SendOutputsErr { @@ -206,6 +206,7 @@ pub enum ZCoinBuildError { Io(std::io::Error), RpcClientInitErr(ZcoinClientInitError), ZCashParamsNotFound, + ZDerivationPathNotSet, } impl From for ZCoinBuildError { diff --git a/mm2src/coins/z_coin/z_htlc.rs b/mm2src/coins/z_coin/z_htlc.rs index ee286b90ab..4914c0002d 100644 --- a/mm2src/coins/z_coin/z_htlc.rs +++ b/mm2src/coins/z_coin/z_htlc.rs @@ -10,7 +10,7 @@ use crate::utxo::rpc_clients::{UtxoRpcClientEnum, UtxoRpcError}; use crate::utxo::utxo_common::payment_script; use crate::utxo::{sat_from_big_decimal, UtxoAddressFormat}; use crate::z_coin::{SendOutputsErr, ZOutput, DEX_FEE_OVK}; -use crate::{NumConversError, PrivKeyNotAllowed, TransactionEnum}; +use crate::{NumConversError, PrivKeyPolicyNotAllowed, TransactionEnum}; use bitcrypto::dhash160; use common::async_blocking; use derive_more::Display; @@ -96,7 +96,7 @@ pub async fn z_send_dex_fee( #[allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] pub enum ZP2SHSpendError { ZTxBuilderError(ZTxBuilderError), - PrivKeyNotAllowed(PrivKeyNotAllowed), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), Rpc(UtxoRpcError), #[display(fmt = "{:?} {}", _0, _1)] TxRecoverable(TransactionEnum, String), @@ -107,8 +107,8 @@ impl From for ZP2SHSpendError { fn from(tx_builder: ZTxBuilderError) -> ZP2SHSpendError { ZP2SHSpendError::ZTxBuilderError(tx_builder) } } -impl From for ZP2SHSpendError { - fn from(err: PrivKeyNotAllowed) -> Self { ZP2SHSpendError::PrivKeyNotAllowed(err) } +impl From for ZP2SHSpendError { + fn from(err: PrivKeyPolicyNotAllowed) -> Self { ZP2SHSpendError::PrivKeyPolicyNotAllowed(err) } } impl From for ZP2SHSpendError { diff --git a/mm2src/coins_activation/src/bch_with_tokens_activation.rs b/mm2src/coins_activation/src/bch_with_tokens_activation.rs index 36b5f68337..5a7e6435ff 100644 --- a/mm2src/coins_activation/src/bch_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/bch_with_tokens_activation.rs @@ -3,12 +3,13 @@ use crate::prelude::*; use crate::slp_token_activation::SlpActivationRequest; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; -use coins::utxo::bch::{bch_coin_from_conf_and_params, BchActivationRequest, BchCoin, CashAddrPrefix}; +use coins::utxo::bch::{bch_coin_with_policy, BchActivationRequest, BchCoin, CashAddrPrefix}; use coins::utxo::rpc_clients::UtxoRpcError; use coins::utxo::slp::{EnableSlpError, SlpProtocolConf, SlpToken}; use coins::utxo::utxo_tx_history_v2::bch_and_slp_history_loop; use coins::utxo::UtxoCommonOps; -use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, PrivKeyNotAllowed, UnexpectedDerivationMethod}; +use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + UnexpectedDerivationMethod}; use common::executor::{AbortSettings, SpawnAbortable}; use common::Future01CompatExt; use mm2_core::mm_ctx::MmArc; @@ -101,8 +102,8 @@ impl From for EnablePlatformCoinWithTokensError { prefix, ticker, error )) }, - BchWithTokensActivationError::PrivKeyNotAllowed(e) => { - EnablePlatformCoinWithTokensError::PrivKeyNotAllowed(e) + BchWithTokensActivationError::PrivKeyPolicyNotAllowed(e) => { + EnablePlatformCoinWithTokensError::PrivKeyPolicyNotAllowed(e) }, BchWithTokensActivationError::UnexpectedDerivationMethod(e) => { EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(e) @@ -172,7 +173,7 @@ pub enum BchWithTokensActivationError { prefix: String, error: String, }, - PrivKeyNotAllowed(String), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), UnexpectedDerivationMethod(String), Transport(String), Internal(String), @@ -188,8 +189,8 @@ impl From for BchWithTokensActivationError { } } -impl From for BchWithTokensActivationError { - fn from(e: PrivKeyNotAllowed) -> Self { BchWithTokensActivationError::PrivKeyNotAllowed(e.to_string()) } +impl From for BchWithTokensActivationError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { BchWithTokensActivationError::PrivKeyPolicyNotAllowed(e) } } #[async_trait] @@ -205,7 +206,7 @@ impl PlatformWithTokensActivationOps for BchCoin { platform_conf: Json, activation_request: Self::ActivationRequest, protocol_conf: Self::PlatformProtocolInfo, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result> { let slp_prefix = CashAddrPrefix::from_str(&protocol_conf.slp_prefix).map_to_mm(|error| { BchWithTokensActivationError::InvalidSlpPrefix { @@ -215,13 +216,13 @@ impl PlatformWithTokensActivationOps for BchCoin { } })?; - let platform_coin = bch_coin_from_conf_and_params( + let platform_coin = bch_coin_with_policy( &ctx, &ticker, &platform_conf, activation_request.platform_request, slp_prefix, - priv_key, + priv_key_policy, ) .await .map_to_mm(|error| BchWithTokensActivationError::PlatformCoinCreationError { ticker, error })?; @@ -239,7 +240,7 @@ impl PlatformWithTokensActivationOps for BchCoin { async fn get_activation_result( &self, ) -> Result> { - let my_address = self.as_ref().derivation_method.iguana_or_err()?; + let my_address = self.as_ref().derivation_method.single_addr_or_err()?; let my_slp_address = self .get_my_slp_address() .map_to_mm(BchWithTokensActivationError::Internal)? diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index a1d619487c..5056dfe9ef 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -8,7 +8,7 @@ use coins::{eth::{v2_activation::{eth_coin_from_conf_and_request_v2, Erc20Protoc Erc20TokenActivationRequest, EthActivationV2Error, EthActivationV2Request}, Erc20TokenInfo, EthCoin, EthCoinType}, my_tx_history_v2::TxHistoryStorage, - CoinBalance, CoinProtocol, MarketCoinOps, MmCoin}; + CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, PrivKeyBuildPolicy}; use common::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -35,6 +35,15 @@ impl From for EnablePlatformCoinWithTokensError { EthActivationV2Error::CouldNotFetchBalance(e) | EthActivationV2Error::UnreachableNodes(e) => { EnablePlatformCoinWithTokensError::Transport(e) }, + EthActivationV2Error::DerivationPathIsNotSet => EnablePlatformCoinWithTokensError::InvalidPayload( + "'derivation_path' field is not found in config".to_string(), + ), + EthActivationV2Error::ErrorDeserializingDerivationPath(e) => { + EnablePlatformCoinWithTokensError::InvalidPayload(e) + }, + EthActivationV2Error::PrivKeyPolicyNotAllowed(e) => { + EnablePlatformCoinWithTokensError::PrivKeyPolicyNotAllowed(e) + }, EthActivationV2Error::InternalError(e) => EnablePlatformCoinWithTokensError::Internal(e), } } @@ -155,14 +164,14 @@ impl PlatformWithTokensActivationOps for EthCoin { platform_conf: Json, activation_request: Self::ActivationRequest, _protocol: Self::PlatformProtocolInfo, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result> { let platform_coin = eth_coin_from_conf_and_request_v2( &ctx, &ticker, &platform_conf, activation_request.platform_request, - priv_key, + priv_key_policy, ) .await?; diff --git a/mm2src/coins_activation/src/lightning_activation.rs b/mm2src/coins_activation/src/lightning_activation.rs index 0fcb093ca2..e6aae97ea8 100644 --- a/mm2src/coins_activation/src/lightning_activation.rs +++ b/mm2src/coins_activation/src/lightning_activation.rs @@ -333,7 +333,7 @@ async fn start_lightning( let persister = init_persister(ctx, conf.ticker.clone(), params.backup_path).await?; // Initialize the KeysManager - let keys_manager = init_keys_manager(ctx)?; + let keys_manager = init_keys_manager(&platform)?; // Initialize the P2PGossipSync. This is used for providing routes to send payments over task_handle.update_in_progress_status(LightningInProgressStatus::ReadingNetworkGraphFromFile)?; diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 016a5061d1..e800da5d62 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -2,8 +2,9 @@ use crate::prelude::*; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tx_history_storage::{CreateTxHistoryStorageError, TxHistoryStorageBuilder}; -use coins::{lp_coinfind, CoinProtocol, CoinsContext, MmCoinEnum}; +use coins::{lp_coinfind, CoinProtocol, CoinsContext, MmCoinEnum, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed}; use common::{log, HttpStatusCode, StatusCode}; +use crypto::{CryptoCtx, CryptoCtxError}; use derive_more::Display; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -144,7 +145,7 @@ pub trait PlatformWithTokensActivationOps: Into { coin_conf: Json, activation_request: Self::ActivationRequest, protocol_conf: Self::PlatformProtocolInfo, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result>; fn token_initializers( @@ -202,7 +203,7 @@ pub enum EnablePlatformCoinWithTokensError { error: String, }, #[display(fmt = "Private key is not allowed: {}", _0)] - PrivKeyNotAllowed(String), + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), #[display(fmt = "Unexpected derivation method: {}", _0)] UnexpectedDerivationMethod(String), Transport(String), @@ -256,13 +257,17 @@ impl From for EnablePlatformCoinWithTokensError { } } +impl From for EnablePlatformCoinWithTokensError { + fn from(e: CryptoCtxError) -> Self { EnablePlatformCoinWithTokensError::Internal(e.to_string()) } +} + impl HttpStatusCode for EnablePlatformCoinWithTokensError { fn status_code(&self) -> StatusCode { match self { EnablePlatformCoinWithTokensError::CoinProtocolParseError { .. } | EnablePlatformCoinWithTokensError::TokenProtocolParseError { .. } | EnablePlatformCoinWithTokensError::PlatformCoinCreationError { .. } - | EnablePlatformCoinWithTokensError::PrivKeyNotAllowed(_) + | EnablePlatformCoinWithTokensError::PrivKeyPolicyNotAllowed(_) | EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(_) | EnablePlatformCoinWithTokensError::Transport(_) | EnablePlatformCoinWithTokensError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, @@ -294,7 +299,8 @@ where let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; - let priv_key = &*ctx.secp256k1_key_pair().private().secret; + let crypto_ctx = CryptoCtx::from_ctx(&ctx)?; + let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(&crypto_ctx); let platform_coin = Platform::enable_platform_coin( ctx.clone(), @@ -302,7 +308,7 @@ where platform_conf, req.request.clone(), platform_protocol, - priv_key, + priv_key_policy, ) .await?; let mut mm_tokens = Vec::new(); diff --git a/mm2src/coins_activation/src/solana_with_tokens_activation.rs b/mm2src/coins_activation/src/solana_with_tokens_activation.rs index 58b46700e6..0f4cc53478 100644 --- a/mm2src/coins_activation/src/solana_with_tokens_activation.rs +++ b/mm2src/coins_activation/src/solana_with_tokens_activation.rs @@ -8,9 +8,10 @@ use crate::spl_token_activation::SplActivationRequest; use async_trait::async_trait; use coins::coin_errors::MyAddressError; use coins::my_tx_history_v2::TxHistoryStorage; +use coins::solana::solana_coin_with_policy; use coins::solana::spl::{SplProtocolConf, SplTokenCreationError}; -use coins::{solana_coin_from_conf_and_params, BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, - SolanaActivationParams, SolanaCoin, SplToken}; +use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy, SolanaActivationParams, + SolanaCoin, SplToken}; use common::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -179,14 +180,14 @@ impl PlatformWithTokensActivationOps for SolanaCoin { platform_conf: Json, activation_request: Self::ActivationRequest, _protocol_conf: Self::PlatformProtocolInfo, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result> { - let platform_coin = solana_coin_from_conf_and_params( + let platform_coin = solana_coin_with_policy( &ctx, &ticker, &platform_conf, activation_request.platform_request, - priv_key, + priv_key_policy, ) .await .map_to_mm(|error| SolanaWithTokensActivationError::PlatformCoinCreationError { ticker, error })?; diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index 428d2dd8a5..5fb8e7e978 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -5,10 +5,10 @@ use crate::platform_coin_with_tokens::{EnablePlatformCoinWithTokensError, GetPla use crate::prelude::*; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; -use coins::tendermint::{TendermintCoin, TendermintInitError, TendermintInitErrorKind, TendermintProtocolInfo, - TendermintToken, TendermintTokenActivationParams, TendermintTokenInitError, - TendermintTokenProtocolInfo}; -use coins::{CoinBalance, CoinProtocol, MarketCoinOps}; +use coins::tendermint::{TendermintCoin, TendermintConf, TendermintInitError, TendermintInitErrorKind, + TendermintProtocolInfo, TendermintToken, TendermintTokenActivationParams, + TendermintTokenInitError, TendermintTokenProtocolInfo}; +use coins::{CoinBalance, CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy}; use common::Future01CompatExt; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -156,25 +156,16 @@ impl PlatformWithTokensActivationOps for TendermintCoin { coin_conf: Json, activation_request: Self::ActivationRequest, protocol_conf: Self::PlatformProtocolInfo, - priv_key: &[u8], + priv_key_policy: PrivKeyBuildPolicy, ) -> Result> { - let avg_block_time = coin_conf["avg_block_time"].as_i64().unwrap_or(0); - - // `avg_block_time` can not be less than 1 OR bigger than 255(u8::MAX) - if avg_block_time < 1 || avg_block_time > std::u8::MAX as i64 { - return MmError::err(TendermintInitError { - ticker, - kind: TendermintInitErrorKind::AvgBlockTimeMissingOrInvalid, - }); - } - + let conf = TendermintConf::try_from_json(&ticker, &coin_conf)?; TendermintCoin::init( &ctx, ticker, - avg_block_time as u8, + conf, protocol_conf, activation_request.rpc_urls, - priv_key, + priv_key_policy, ) .await } diff --git a/mm2src/coins_activation/src/utxo_activation/common_impl.rs b/mm2src/coins_activation/src/utxo_activation/common_impl.rs index 77db8549b0..10c687b026 100644 --- a/mm2src/coins_activation/src/utxo_activation/common_impl.rs +++ b/mm2src/coins_activation/src/utxo_activation/common_impl.rs @@ -78,7 +78,7 @@ pub(crate) fn priv_key_build_policy( activation_policy: PrivKeyActivationPolicy, ) -> PrivKeyBuildPolicy { match activation_policy { - PrivKeyActivationPolicy::IguanaPrivKey => PrivKeyBuildPolicy::iguana_priv_key(crypto_ctx), + PrivKeyActivationPolicy::ContextPrivKey => PrivKeyBuildPolicy::detect_priv_key_policy(crypto_ctx), PrivKeyActivationPolicy::Trezor => PrivKeyBuildPolicy::Trezor, } } 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 0a4e355954..8087b047d8 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 @@ -4,7 +4,7 @@ use coins::hd_wallet::{NewAccountCreatingError, NewAddressDerivingError}; use coins::tx_history_storage::CreateTxHistoryStorageError; use coins::utxo::utxo_builder::UtxoCoinBuildError; use coins::{BalanceError, RegisterCoinError}; -use crypto::{CryptoInitError, HwError, HwRpcError}; +use crypto::{CryptoCtxError, HwError, HwRpcError}; use derive_more::Display; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; @@ -37,9 +37,9 @@ impl From for InitUtxoStandardError { } } -impl From for InitUtxoStandardError { +impl From for InitUtxoStandardError { /// `CryptoCtx` is expected to be initialized already. - fn from(crypto_err: CryptoInitError) -> Self { InitUtxoStandardError::Internal(crypto_err.to_string()) } + fn from(crypto_err: CryptoCtxError) -> Self { InitUtxoStandardError::Internal(crypto_err.to_string()) } } impl From for InitUtxoStandardError { diff --git a/mm2src/coins_activation/src/z_coin_activation.rs b/mm2src/coins_activation/src/z_coin_activation.rs index 9b406bb360..8c5aa39d89 100644 --- a/mm2src/coins_activation/src/z_coin_activation.rs +++ b/mm2src/coins_activation/src/z_coin_activation.rs @@ -9,9 +9,9 @@ use coins::my_tx_history_v2::TxHistoryStorage; use coins::tx_history_storage::CreateTxHistoryStorageError; use coins::z_coin::{z_coin_from_conf_and_params, BlockchainScanStopped, SyncStatus, ZCoin, ZCoinBuildError, ZcoinActivationParams, ZcoinProtocolInfo}; -use coins::{BalanceError, CoinProtocol, MarketCoinOps, RegisterCoinError}; +use coins::{BalanceError, CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy, RegisterCoinError}; use crypto::hw_rpc_task::{HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; -use crypto::CryptoInitError; +use crypto::{CryptoCtx, CryptoCtxError}; use derive_more::Display; use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; @@ -127,8 +127,8 @@ impl From for ZcoinInitError { } } -impl From for ZcoinInitError { - fn from(err: CryptoInitError) -> Self { ZcoinInitError::Internal(err.to_string()) } +impl From for ZcoinInitError { + fn from(err: CryptoCtxError) -> Self { ZcoinInitError::Internal(err.to_string()) } } impl From for ZcoinInitError { @@ -164,6 +164,10 @@ impl From for InitStandaloneCoinError { } } +impl From for InitStandaloneCoinError { + fn from(e: CryptoCtxError) -> Self { InitStandaloneCoinError::Internal(e.to_string()) } +} + impl TryFromCoinProtocol for ZcoinProtocolInfo { fn try_from_coin_protocol(proto: CoinProtocol) -> Result> where @@ -198,14 +202,18 @@ impl InitStandaloneCoinActivationOps for ZCoin { protocol_info: ZcoinProtocolInfo, task_handle: &ZcoinRpcTaskHandle, ) -> MmResult { - let secp_privkey = ctx.secp256k1_key_pair().private().secret; + let crypto_ctx = CryptoCtx::from_ctx(&ctx)?; + // When `ZCoin` supports Trezor, we'll need to check [`ZcoinActivationParams::priv_key_policy`] + // instead of using [`PrivKeyBuildPolicy::detect_priv_key_policy`]. + let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(&crypto_ctx); + let coin = z_coin_from_conf_and_params( &ctx, &ticker, &coin_conf, activation_request, protocol_info, - secp_privkey.as_slice(), + priv_key_policy, ) .await .mm_err(|e| ZcoinInitError::from_build_err(e, ticker))?; diff --git a/mm2src/common/executor/wasm_executor.rs b/mm2src/common/executor/wasm_executor.rs index 243a1dee0f..422f564623 100644 --- a/mm2src/common/executor/wasm_executor.rs +++ b/mm2src/common/executor/wasm_executor.rs @@ -12,10 +12,10 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { - /// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout + // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout fn setTimeout(closure: &Closure, delay_ms: u32) -> i32; - /// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout + // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout fn clearTimeout(id: i32); } diff --git a/mm2src/crypto/Cargo.toml b/mm2src/crypto/Cargo.toml index c2e4726092..25cb6ccb04 100644 --- a/mm2src/crypto/Cargo.toml +++ b/mm2src/crypto/Cargo.toml @@ -7,30 +7,32 @@ edition = "2018" doctest = false [dependencies] -rustc-hex = "2" -mm2_err_handle = { path = "../mm2_err_handle" } +arrayref = "0.3" async-trait = "0.1" bip32 = { version = "0.2.2", default-features = false, features = ["alloc", "secp256k1-ffi"] } bitcrypto = { path = "../mm2_bitcoin/crypto" } bs58 = "0.4.0" common = { path = "../common" } -mm2_core = { path = "../mm2_core" } derive_more = "0.99" enum-primitive-derive = "0.2" futures = "0.3" hex = "0.4.2" http = "0.2" hw_common = { path = "../hw_common" } -parking_lot = { version = "0.12.0", features = ["nightly"] } keys = { path = "../mm2_bitcoin/keys" } +mm2_core = { path = "../mm2_core" } +mm2_err_handle = { path = "../mm2_err_handle" } num-traits = "0.2" +parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } rpc = { path = "../mm2_bitcoin/rpc" } rpc_task = { path = "../rpc_task" } +rustc-hex = "2" secp256k1 = "0.20" ser_error = { path = "../derives/ser_error" } ser_error_derive = { path = "../derives/ser_error_derive" } serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +tiny-bip39 = "0.8.0" trezor = { path = "../trezor" } diff --git a/mm2src/crypto/src/crypto_ctx.rs b/mm2src/crypto/src/crypto_ctx.rs index 78b4c87ce7..089db82a4d 100644 --- a/mm2src/crypto/src/crypto_ctx.rs +++ b/mm2src/crypto/src/crypto_ctx.rs @@ -1,11 +1,14 @@ +use crate::global_hd_ctx::{GlobalHDAccountArc, GlobalHDAccountCtx}; use crate::hw_client::{HwDeviceInfo, HwProcessingError, HwPubkey, TrezorConnectProcessor}; use crate::hw_ctx::{HardwareWalletArc, HardwareWalletCtx}; use crate::hw_error::HwError; -use crate::key_pair_ctx::IguanaArc; use crate::privkey::{key_pair_from_seed, PrivKeyError}; +use arrayref::array_ref; +use common::bits256; use derive_more::Display; -use keys::Public as PublicKey; +use keys::{KeyPair, Public as PublicKey, Secret as Secp256k1Secret}; use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::common_errors::InternalError; use mm2_err_handle::prelude::*; use parking_lot::RwLock; use primitives::hash::H160; @@ -22,6 +25,11 @@ pub enum CryptoInitError { NullStringPassphrase, #[display(fmt = "Invalid passphrase: '{}'", _0)] InvalidPassphrase(PrivKeyError), + #[display(fmt = "Invalid 'hd_account_id' = {}: {}", hd_account_id, error)] + InvalidHdAccount { + hd_account_id: u64, + error: String, + }, Internal(String), } @@ -29,6 +37,14 @@ impl From for CryptoInitError { fn from(e: PrivKeyError) -> Self { CryptoInitError::InvalidPassphrase(e) } } +#[derive(Debug, Display)] +pub enum CryptoCtxError { + #[display(fmt = "'CryptoCtx' is not initialized")] + NotInitialized, + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + #[derive(Debug)] pub enum HwCtxInitError { InitializingAlready, @@ -53,68 +69,137 @@ impl From> for HwCtxInitError< impl NotEqual for HwCtxInitError {} pub struct CryptoCtx { - iguana_ctx: IguanaArc, + /// secp256k1 key pair derived from either: + /// * Iguana passphrase, + /// cf. `key_pair_from_seed`; + /// * BIP39 passphrase at `mm2_internal_der_path`, + /// cf. [`GlobalHDAccountCtx::new`]. + secp256k1_key_pair: KeyPair, + key_pair_policy: KeyPairPolicy, /// Can be initialized on [`CryptoCtx::init_hw_ctx_with_trezor`]. hw_ctx: RwLock, } impl CryptoCtx { - pub fn from_ctx(ctx: &MmArc) -> CryptoInitResult> { + pub fn is_init(ctx: &MmArc) -> MmResult { + match CryptoCtx::from_ctx(ctx).split_mm() { + Ok(_) => Ok(true), + Err((CryptoCtxError::NotInitialized, _trace)) => Ok(false), + Err((other, trace)) => MmError::err_with_trace(InternalError(other.to_string()), trace), + } + } + + pub fn from_ctx(ctx: &MmArc) -> MmResult, CryptoCtxError> { let ctx_field = ctx .crypto_ctx .lock() - .map_to_mm(|poison| CryptoInitError::Internal(poison.to_string()))?; + .map_to_mm(|poison| CryptoCtxError::Internal(poison.to_string()))?; let ctx = match ctx_field.deref() { Some(ctx) => ctx, - None => return MmError::err(CryptoInitError::NotInitialized), + None => return MmError::err(CryptoCtxError::NotInitialized), }; ctx.clone() .downcast() - .map_err(|_| MmError::new(CryptoInitError::Internal("Error casting the context field".to_owned()))) + .map_err(|_| MmError::new(CryptoCtxError::Internal("Error casting the context field".to_owned()))) } - pub fn iguana_ctx(&self) -> &IguanaArc { &self.iguana_ctx } + #[inline] + pub fn key_pair_policy(&self) -> &KeyPairPolicy { &self.key_pair_policy } - pub fn secp256k1_pubkey(&self) -> PublicKey { self.iguana_ctx.secp256k1_pubkey() } + /// This is our public ID, allowing us to be different from other peers. + /// This should also be our public key which we'd use for P2P message verification. + #[inline] + pub fn mm2_internal_public_id(&self) -> bits256 { + // Compressed public key is going to be 33 bytes. + let public = self.mm2_internal_pubkey(); + // First byte is a prefix, https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/. + bits256 { + bytes: *array_ref!(public, 1, 32), + } + } - pub fn secp256k1_pubkey_hex(&self) -> String { hex::encode(&*self.secp256k1_pubkey()) } + /// Returns `secp256k1` key-pair. + /// It can be used for mm2 internal purposes such as signing P2P messages. + /// + /// To activate coins, consider matching [`CryptoCtx::key_pair_ctx`] manually. + /// + /// # Security + /// + /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning key-pair is used to activate coins. + /// Please use this method carefully. + #[inline] + pub fn mm2_internal_key_pair(&self) -> &KeyPair { &self.secp256k1_key_pair } - pub fn hw_ctx(&self) -> Option { self.hw_ctx.read().to_option().cloned() } + /// Returns `secp256k1` public key. + /// It can be used for mm2 internal purposes such as P2P peer ID. + /// + /// To activate coins, consider matching [`CryptoCtx::key_pair_ctx`] manually. + /// + /// # Security + /// + /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning key-pair can be also used + /// at the activated coins. + /// Please use this method carefully. + #[inline] + pub fn mm2_internal_pubkey(&self) -> PublicKey { *self.secp256k1_key_pair.public() } - /// Returns an `RIPEMD160(SHA256(x))` where x is secp256k1 pubkey that identifies a Hardware Wallet device or an HD master private key. - pub fn hd_wallet_rmd160(&self) -> Option { self.hw_ctx.read().to_option().map(|hw_ctx| hw_ctx.rmd160()) } + /// Returns `secp256k1` public key hex. + /// It can be used for mm2 internal purposes such as P2P peer ID. + /// + /// To activate coins, consider matching [`CryptoCtx::key_pair_ctx`] manually. + /// + /// # Security + /// + /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning public key can be also used + /// at the activated coins. + /// Please use this method carefully. + #[inline] + pub fn mm2_internal_pubkey_hex(&self) -> String { hex::encode(&*self.mm2_internal_pubkey()) } - pub fn init_with_iguana_passphrase(ctx: MmArc, passphrase: &str) -> CryptoInitResult<()> { - let mut ctx_field = ctx - .crypto_ctx - .lock() - .map_to_mm(|poison| CryptoInitError::Internal(poison.to_string()))?; - if ctx_field.is_some() { - return MmError::err(CryptoInitError::InitializedAlready); - } + /// Returns `secp256k1` private key as `H256` bytes. + /// It can be used for mm2 internal purposes such as signing P2P messages. + /// + /// To activate coins, consider matching [`CryptoCtx::key_pair_ctx`] manually. + /// + /// # Security + /// + /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. + /// Please use this method carefully. + #[inline] + pub fn mm2_internal_privkey_secret(&self) -> Secp256k1Secret { self.secp256k1_key_pair.private().secret } - if passphrase.is_empty() { - return MmError::err(CryptoInitError::NullStringPassphrase); - } + /// Returns `secp256k1` private key as `[u8]` slice. + /// It can be used for mm2 internal purposes such as signing P2P messages. + /// Please consider using [`CryptoCtx::mm2_internal_privkey_bytes`] instead. + /// + /// If you don't need to borrow the secret bytes, consider using [`CryptoCtx::mm2_internal_privkey_bytes`] instead. + /// To activate coins, consider matching [`CryptoCtx::key_pair_ctx`] manually. + /// + /// # Security + /// + /// If [`CryptoCtx::key_pair_ctx`] is `Iguana`, then the returning private is used to activate coins. + /// Please use this method carefully. + #[inline] + pub fn mm2_internal_privkey_slice(&self) -> &[u8] { self.secp256k1_key_pair.private().secret.as_slice() } - let secp256k1_key_pair = key_pair_from_seed(passphrase)?; - // We can't clone `secp256k1_key_pair`, but it's used later to initialize legacy `MmCtx` fields. - let secp256k1_key_pair_for_legacy = key_pair_from_seed(passphrase)?; + #[inline] + pub fn hw_ctx(&self) -> Option { self.hw_ctx.read().to_option().cloned() } - let rmd160 = secp256k1_key_pair.public().address_hash(); - let crypto_ctx = CryptoCtx { - iguana_ctx: IguanaArc::from(secp256k1_key_pair), - hw_ctx: RwLock::new(HardwareWalletCtxState::NotInitialized), - }; - *ctx_field = Some(Arc::new(crypto_ctx)); + /// Returns an `RIPEMD160(SHA256(x))` where x is secp256k1 pubkey that identifies a Hardware Wallet device or an HD master private key. + #[inline] + pub fn hw_wallet_rmd160(&self) -> Option { self.hw_ctx.read().to_option().map(|hw_ctx| hw_ctx.rmd160()) } - // TODO remove initializing legacy fields when lp_swap and lp_ordermatch support CryptoCtx. - ctx.secp256k1_key_pair - .pin(secp256k1_key_pair_for_legacy) - .map_to_mm(CryptoInitError::Internal)?; - ctx.rmd160.pin(rmd160).map_to_mm(CryptoInitError::Internal)?; + pub fn init_with_iguana_passphrase(ctx: MmArc, passphrase: &str) -> CryptoInitResult> { + Self::init_crypto_ctx_with_policy_builder(ctx, passphrase, KeyPairPolicyBuilder::Iguana) + } - Ok(()) + pub fn init_with_global_hd_account( + ctx: MmArc, + passphrase: &str, + hd_account_id: u64, + ) -> CryptoInitResult> { + let builder = KeyPairPolicyBuilder::GlobalHDAccount { hd_account_id }; + Self::init_crypto_ctx_with_policy_builder(ctx, passphrase, builder) } pub async fn init_hw_ctx_with_trezor( @@ -148,6 +233,69 @@ impl CryptoCtx { let mut state = self.hw_ctx.write(); *state = HardwareWalletCtxState::NotInitialized; } + + fn init_crypto_ctx_with_policy_builder( + ctx: MmArc, + passphrase: &str, + policy_builder: KeyPairPolicyBuilder, + ) -> CryptoInitResult> { + let mut ctx_field = ctx + .crypto_ctx + .lock() + .map_to_mm(|poison| CryptoInitError::Internal(poison.to_string()))?; + if ctx_field.is_some() { + return MmError::err(CryptoInitError::InitializedAlready); + } + + if passphrase.is_empty() { + return MmError::err(CryptoInitError::NullStringPassphrase); + } + + let (secp256k1_key_pair, key_pair_policy) = policy_builder.build(passphrase)?; + let rmd160 = secp256k1_key_pair.public().address_hash(); + + let crypto_ctx = CryptoCtx { + secp256k1_key_pair, + key_pair_policy, + hw_ctx: RwLock::new(HardwareWalletCtxState::NotInitialized), + }; + + let result = Arc::new(crypto_ctx); + *ctx_field = Some(result.clone()); + drop(ctx_field); + + ctx.rmd160.pin(rmd160).map_to_mm(CryptoInitError::Internal)?; + + Ok(result) + } +} + +enum KeyPairPolicyBuilder { + Iguana, + GlobalHDAccount { hd_account_id: u64 }, +} + +impl KeyPairPolicyBuilder { + /// [`KeyPairPolicyBuilder::build`] is fired if all checks pass **only**. + fn build(self, passphrase: &str) -> CryptoInitResult<(KeyPair, KeyPairPolicy)> { + match self { + KeyPairPolicyBuilder::Iguana => { + let secp256k1_key_pair = key_pair_from_seed(passphrase)?; + Ok((secp256k1_key_pair, KeyPairPolicy::Iguana)) + }, + KeyPairPolicyBuilder::GlobalHDAccount { hd_account_id } => { + let (mm2_internal_key_pair, global_hd_ctx) = GlobalHDAccountCtx::new(passphrase, hd_account_id)?; + let key_pair_policy = KeyPairPolicy::GlobalHDAccount(global_hd_ctx.into_arc()); + Ok((mm2_internal_key_pair, key_pair_policy)) + }, + } + } +} + +#[derive(Clone)] +pub enum KeyPairPolicy { + Iguana, + GlobalHDAccount(GlobalHDAccountArc), } async fn init_check_hw_ctx_with_trezor( diff --git a/mm2src/crypto/src/global_hd_ctx.rs b/mm2src/crypto/src/global_hd_ctx.rs new file mode 100644 index 0000000000..13b2736f41 --- /dev/null +++ b/mm2src/crypto/src/global_hd_ctx.rs @@ -0,0 +1,112 @@ +use crate::privkey::{bip39_seed_from_passphrase, key_pair_from_secret, PrivKeyError}; +use crate::{mm2_internal_der_path, Bip32DerPathOps, Bip32Error, CryptoInitError, CryptoInitResult, + StandardHDPathToCoin}; +use bip32::{ChildNumber, ExtendedPrivateKey}; +use keys::{KeyPair, Secret as Secp256k1Secret}; +use mm2_err_handle::prelude::*; +use std::convert::TryInto; +use std::num::TryFromIntError; +use std::ops::Deref; +use std::sync::Arc; + +const HARDENED: bool = true; +const NON_HARDENED: bool = false; + +pub(super) type Mm2InternalKeyPair = KeyPair; + +#[derive(Clone)] +pub struct GlobalHDAccountArc(Arc); + +impl Deref for GlobalHDAccountArc { + type Target = GlobalHDAccountCtx; + + fn deref(&self) -> &Self::Target { &self.0 } +} + +pub struct GlobalHDAccountCtx { + bip39_seed: bip39::Seed, + bip39_secp_priv_key: ExtendedPrivateKey, + /// This account is set globally for every activated coin. + hd_account: ChildNumber, +} + +impl GlobalHDAccountCtx { + pub fn new(passphrase: &str, hd_account_id: u64) -> CryptoInitResult<(Mm2InternalKeyPair, GlobalHDAccountCtx)> { + let bip39_seed = bip39_seed_from_passphrase(passphrase)?; + let bip39_secp_priv_key: ExtendedPrivateKey = + ExtendedPrivateKey::new(bip39_seed.as_bytes()) + .map_to_mm(|e| PrivKeyError::InvalidPrivKey(e.to_string()))?; + + let hd_account_id = + hd_account_id + .try_into() + .map_to_mm(|e: TryFromIntError| CryptoInitError::InvalidHdAccount { + hd_account_id, + error: e.to_string(), + })?; + let hd_account = + ChildNumber::new(hd_account_id, NON_HARDENED).map_to_mm(|e| CryptoInitError::InvalidHdAccount { + hd_account_id: hd_account_id as u64, + error: e.to_string(), + })?; + + let derivation_path = mm2_internal_der_path(Some(hd_account)); + + let mut internal_priv_key = bip39_secp_priv_key.clone(); + for child in derivation_path { + internal_priv_key = internal_priv_key + .derive_child(child) + .map_to_mm(|e| CryptoInitError::InvalidPassphrase(PrivKeyError::InvalidPrivKey(e.to_string())))?; + } + + let mm2_internal_key_pair = key_pair_from_secret(internal_priv_key.private_key().as_ref())?; + + let global_hd_ctx = GlobalHDAccountCtx { + bip39_seed, + bip39_secp_priv_key, + hd_account, + }; + Ok((mm2_internal_key_pair, global_hd_ctx)) + } + + #[inline] + pub fn into_arc(self) -> GlobalHDAccountArc { GlobalHDAccountArc(Arc::new(self)) } + + /// Returns an identifier of the selected HD account. + pub fn account_id(&self) -> u32 { self.hd_account.index() } + + /// Returns the root BIP39 seed. + pub fn root_seed(&self) -> &bip39::Seed { &self.bip39_seed } + + /// Returns the root BIP39 seed as bytes. + pub fn root_seed_bytes(&self) -> &[u8] { self.bip39_seed.as_bytes() } + + /// Derives a `secp256k1::SecretKey` from [`HDAccountCtx::bip39_secp_priv_key`] + /// at the given `m/purpose'/coin_type'/account_id'/chain/address_id` derivation path, + /// where: + /// * `m/purpose'/coin_type'` is specified by `derivation_path`. + /// * `account_id = 0`, `chain = 0`. + /// * `address_id = HDAccountCtx::hd_account`. + /// + /// Returns the `secp256k1::Private` Secret 256-bit key + pub fn derive_secp256k1_secret( + &self, + derivation_path: &StandardHDPathToCoin, + ) -> MmResult { + const ACCOUNT_ID: u32 = 0; + const CHAIN_ID: u32 = 0; + + let mut account_der_path = derivation_path.to_derivation_path(); + account_der_path.push(ChildNumber::new(ACCOUNT_ID, HARDENED).unwrap()); + account_der_path.push(ChildNumber::new(CHAIN_ID, NON_HARDENED).unwrap()); + account_der_path.push(self.hd_account); + + let mut priv_key = self.bip39_secp_priv_key.clone(); + for child in account_der_path { + priv_key = priv_key.derive_child(child)?; + } + + let secret = *priv_key.private_key().as_ref(); + Ok(Secp256k1Secret::from(secret)) + } +} diff --git a/mm2src/crypto/src/hw_ctx.rs b/mm2src/crypto/src/hw_ctx.rs index cd2ae15759..8db21ff5f6 100644 --- a/mm2src/crypto/src/hw_ctx.rs +++ b/mm2src/crypto/src/hw_ctx.rs @@ -1,11 +1,12 @@ use crate::hw_client::{HwClient, HwDeviceInfo, HwProcessingError, HwPubkey, TrezorConnectProcessor}; use crate::hw_error::HwError; use crate::trezor::TrezorSession; -use crate::HwWalletType; +use crate::{mm2_internal_der_path, HwWalletType}; +use bip32::ChildNumber; use bitcrypto::dhash160; use common::log::warn; use futures::lock::Mutex as AsyncMutex; -use hw_common::primitives::{DerivationPath, EcdsaCurve, Secp256k1ExtendedPublicKey}; +use hw_common::primitives::{EcdsaCurve, Secp256k1ExtendedPublicKey}; use keys::Public as PublicKey; use mm2_err_handle::prelude::*; use primitives::hash::{H160, H264}; @@ -16,14 +17,6 @@ use trezor::client::TrezorClient; use trezor::utxo::IGNORE_XPUB_MAGIC; use trezor::{ProcessTrezorResponse, TrezorRequestProcessor}; -/// The derivation path generally consists of: -/// `m/purpose'/coin_type'/account'/change/address_index`. -/// For MarketMaker internal purposes, we decided to use a pubkey derived from the following path, where: -/// * `coin_type = 141` - KMD coin; -/// * `account = (2 ^ 31 - 1) = 2147483647` - latest available account index. -/// This number is chosen so that it does not cross with real accounts; -/// * `change = 0`, `address_index = 0` - nothing special. -const MM2_INTERNAL_DERIVATION_PATH: &str = "m/44'/141'/2147483647/0/0"; const MM2_INTERNAL_ECDSA_CURVE: EcdsaCurve = EcdsaCurve::Secp256k1; const MM2_TREZOR_INTERNAL_COIN: &str = "Komodo"; const SHOW_PUBKEY_ON_DISPLAY: bool = false; @@ -42,7 +35,7 @@ impl HardwareWalletArc { } pub struct HardwareWalletCtx { - /// The pubkey derived from `MM2_INTERNAL_DERIVATION_PATH`. + /// The pubkey derived from `mm2_internal_der_path`. pub(crate) hw_internal_pubkey: H264, pub(crate) hw_wallet_type: HwWalletType, /// Please avoid locking multiple mutexes. @@ -121,8 +114,9 @@ impl HardwareWalletCtx { where Processor: TrezorRequestProcessor + Sync, { - let path = DerivationPath::from_str(MM2_INTERNAL_DERIVATION_PATH) - .expect("'MM2_INTERNAL_DERIVATION_PATH' is expected to be valid derivation path"); + const ADDRESS_INDEX: Option = None; + + let path = mm2_internal_der_path(ADDRESS_INDEX); let mm2_internal_xpub = trezor .get_public_key( path, diff --git a/mm2src/crypto/src/key_pair_ctx.rs b/mm2src/crypto/src/key_pair_ctx.rs deleted file mode 100644 index 8026b6b9a8..0000000000 --- a/mm2src/crypto/src/key_pair_ctx.rs +++ /dev/null @@ -1,34 +0,0 @@ -use keys::{KeyPair, Private, Public as PublicKey}; -use std::ops::Deref; -use std::sync::Arc; - -#[derive(Clone)] -pub struct IguanaArc(Arc); - -impl Deref for IguanaArc { - type Target = IguanaCtx; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl From for IguanaArc { - fn from(secp256k1_key_pair: KeyPair) -> Self { IguanaArc::new(IguanaCtx { secp256k1_key_pair }) } -} - -impl IguanaArc { - pub fn new(ctx: IguanaCtx) -> IguanaArc { IguanaArc(Arc::new(ctx)) } -} - -pub struct IguanaCtx { - /// secp256k1 key pair derived from passphrase. - /// cf. `key_pair_from_seed`. - pub(crate) secp256k1_key_pair: KeyPair, -} - -impl IguanaCtx { - pub fn secp256k1_pubkey(&self) -> PublicKey { *self.secp256k1_key_pair.public() } - - pub fn secp256k1_privkey(&self) -> &Private { self.secp256k1_key_pair.private() } - - pub fn secp256k1_privkey_bytes(&self) -> &[u8] { self.secp256k1_privkey().secret.as_slice() } -} diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index c744af8fa5..4712e22619 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -1,26 +1,27 @@ #[macro_use] extern crate serde_derive; mod bip32_child; -mod bip44; mod crypto_ctx; +mod global_hd_ctx; mod hw_client; mod hw_ctx; mod hw_error; pub mod hw_rpc_task; -mod key_pair_ctx; pub mod privkey; +mod standard_hd_path; mod xpub; pub use bip32_child::{Bip32Child, Bip32DerPathError, Bip32DerPathOps, Bip44Tail}; -pub use bip44::{Bip44Chain, Bip44DerPathError, Bip44DerivationPath, Bip44PathToAccount, Bip44PathToCoin, - UnkownBip44ChainError, BIP44_PURPOSE}; -pub use crypto_ctx::{CryptoCtx, CryptoInitError, CryptoInitResult, HwCtxInitError}; +pub use crypto_ctx::{CryptoCtx, CryptoCtxError, CryptoInitError, CryptoInitResult, HwCtxInitError, KeyPairPolicy}; +pub use global_hd_ctx::GlobalHDAccountArc; pub use hw_client::{HwClient, HwDeviceInfo, HwProcessingError, HwPubkey, HwWalletType, TrezorConnectProcessor}; pub use hw_common::primitives::{Bip32Error, ChildNumber, DerivationPath, EcdsaCurve, ExtendedPublicKey, Secp256k1ExtendedPublicKey, XPub}; pub use hw_ctx::{HardwareWalletArc, HardwareWalletCtx}; pub use hw_error::{from_hw_error, HwError, HwResult, HwRpcError, WithHwRpcError}; -pub use key_pair_ctx::{IguanaArc, IguanaCtx}; +pub use keys::Secret as Secp256k1Secret; +pub use standard_hd_path::{Bip44Chain, StandardHDPath, StandardHDPathError, StandardHDPathToAccount, + StandardHDPathToCoin, UnknownChainError}; pub use trezor; pub use xpub::{XPubConverter, XpubError}; @@ -28,6 +29,20 @@ use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; +/// The derivation path generally consists of: +/// `m/purpose'/coin_type'/account'/change/address_index`. +/// For MarketMaker internal purposes, we decided to use a pubkey derived from the following path, where: +/// * `coin_type = 141` - KMD coin; +/// * `account = (2 ^ 31 - 1) = 2147483647` - latest available account index. +/// This number is chosen so that it does not cross with real accounts; +/// * `change = 0` - nothing special. +/// * `address_index` - is ether specified by the config or default `0`. +pub(crate) fn mm2_internal_der_path(address_index: Option) -> DerivationPath { + let mut der_path = DerivationPath::from_str("m/44'/141'/2147483647/0").expect("valid derivation path"); + der_path.push(address_index.unwrap_or_default()); + der_path +} + #[derive(Clone, Debug, PartialEq)] pub struct RpcDerivationPath(pub DerivationPath); diff --git a/mm2src/crypto/src/privkey.rs b/mm2src/crypto/src/privkey.rs index cfbc82c8a6..88686f809f 100644 --- a/mm2src/crypto/src/privkey.rs +++ b/mm2src/crypto/src/privkey.rs @@ -21,9 +21,8 @@ use bitcrypto::{sha256, ChecksumType}; use derive_more::Display; -use keys::{Error as KeysError, KeyPair, Private}; +use keys::{Error as KeysError, KeyPair, Private, Secret as Secp256k1Secret}; use mm2_err_handle::prelude::*; -use primitives::hash::H256; use rustc_hex::FromHexError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -36,7 +35,7 @@ pub enum PrivKeyError { #[display(fmt = "Error parsing passphrase: {}", _0)] ErrorParsingPassphrase(String), #[display(fmt = "Invalid private key: {}", _0)] - InvalidPrivKey(KeysError), + InvalidPrivKey(String), #[display(fmt = "We only support compressed keys at the moment")] ExpectedCompressedKeys, } @@ -46,7 +45,7 @@ impl From for PrivKeyError { } impl From for PrivKeyError { - fn from(e: KeysError) -> Self { PrivKeyError::InvalidPrivKey(e) } + fn from(e: KeysError) -> Self { PrivKeyError::InvalidPrivKey(e.to_string()) } } fn private_from_seed(seed: &str) -> PrivKeyResult { @@ -61,7 +60,7 @@ fn private_from_seed(seed: &str) -> PrivKeyResult { match seed.strip_prefix("0x") { Some(stripped) => { - let hash: H256 = stripped.parse()?; + let hash: Secp256k1Secret = stripped.parse()?; Ok(Private { prefix: 0, secret: hash, @@ -83,7 +82,7 @@ fn private_from_seed(seed: &str) -> PrivKeyResult { } /// Mutates the arbitrary hash to become a valid secp256k1 private key -pub fn secp_privkey_from_hash(mut hash: H256) -> H256 { +pub fn secp_privkey_from_hash(mut hash: Secp256k1Secret) -> Secp256k1Secret { hash[0] &= 248; hash[31] &= 127; hash[31] |= 64; @@ -103,7 +102,7 @@ pub fn key_pair_from_seed(seed: &str) -> PrivKeyResult { pub fn key_pair_from_secret(secret: &[u8]) -> PrivKeyResult { if secret.len() != 32 { - return MmError::err(PrivKeyError::InvalidPrivKey(KeysError::InvalidPrivate)); + return MmError::err(PrivKeyError::InvalidPrivKey(KeysError::InvalidPrivate.to_string())); } let private = Private { @@ -115,6 +114,12 @@ pub fn key_pair_from_secret(secret: &[u8]) -> PrivKeyResult { Ok(KeyPair::from_private(private)?) } +pub fn bip39_seed_from_passphrase(passphrase: &str) -> PrivKeyResult { + let mnemonic = bip39::Mnemonic::from_phrase(passphrase, bip39::Language::English) + .map_to_mm(|e| PrivKeyError::ErrorParsingPassphrase(e.to_string()))?; + Ok(bip39::Seed::new(&mnemonic, "")) +} + #[derive(Clone, Copy, Debug)] pub struct SerializableSecp256k1Keypair { inner: KeyPair, diff --git a/mm2src/crypto/src/bip44.rs b/mm2src/crypto/src/standard_hd_path.rs similarity index 58% rename from mm2src/crypto/src/bip44.rs rename to mm2src/crypto/src/standard_hd_path.rs index d7220c1e93..4d13456b1a 100644 --- a/mm2src/crypto/src/bip44.rs +++ b/mm2src/crypto/src/standard_hd_path.rs @@ -6,29 +6,34 @@ use hw_common::primitives::Bip32Error; use num_traits::FromPrimitive; use std::convert::TryFrom; -pub const BIP44_PURPOSE: u32 = 44; - +/// Standard HD Path for [BIP-44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki), +/// [BIP-49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki), +/// [BIP-84](https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki) +/// and similar. +/// For path as `m/purpose'/coin_type'/account'/change/address_index`. #[rustfmt::skip] -pub type Bip44DerivationPath = - Bip32Child>>>>; #[rustfmt::skip] -pub type Bip44PathToCoin = - Bip32Child>; #[rustfmt::skip] -pub type Bip44PathToAccount = - Bip32Child>>; -impl Bip44DerivationPath { +impl StandardHDPath { + pub fn purpose(&self) -> Bip43Purpose { self.value() } + pub fn coin_type(&self) -> u32 { self.child().value() } pub fn account_id(&self) -> u32 { self.child().child().value() } @@ -38,23 +43,27 @@ impl Bip44DerivationPath { pub fn address_id(&self) -> u32 { self.child().child().child().child().value() } } -impl Bip44PathToCoin { +impl StandardHDPathToCoin { + pub fn purpose(&self) -> Bip43Purpose { self.value() } + pub fn coin_type(&self) -> u32 { self.child().value() } } -impl Bip44PathToAccount { +impl StandardHDPathToAccount { + pub fn purpose(&self) -> Bip43Purpose { self.value() } + pub fn coin_type(&self) -> u32 { self.child().value() } pub fn account_id(&self) -> u32 { self.child().child().value() } } #[derive(Debug)] -pub struct UnkownBip44ChainError { +pub struct UnknownChainError { pub chain: u32, } #[derive(Debug, Display, Eq, PartialEq)] -pub enum Bip44DerPathError { +pub enum StandardHDPathError { #[display(fmt = "Invalid derivation path length '{}', expected '{}'", found, expected)] InvalidDerivationPathLength { expected: usize, found: usize }, #[display(fmt = "Child '{}' is expected to be hardened", child)] @@ -71,42 +80,42 @@ pub enum Bip44DerPathError { Bip32Error(Bip32Error), } -impl From for Bip44DerPathError { +impl From for StandardHDPathError { fn from(e: Bip32DerPathError) -> Self { fn display_child_at(child_at: usize) -> String { - Bip44Index::from_usize(child_at) + StandardHDIndex::from_usize(child_at) .map(|index| format!("{:?}", index)) .unwrap_or_else(|| "UNKNOWN".to_owned()) } match e { Bip32DerPathError::InvalidDerivationPathLength { expected, found } => { - Bip44DerPathError::InvalidDerivationPathLength { expected, found } + StandardHDPathError::InvalidDerivationPathLength { expected, found } }, - Bip32DerPathError::ChildIsNotHardened { child_at } => Bip44DerPathError::ChildIsNotHardened { + Bip32DerPathError::ChildIsNotHardened { child_at } => StandardHDPathError::ChildIsNotHardened { child: display_child_at(child_at), }, - Bip32DerPathError::ChildIsHardened { child_at } => Bip44DerPathError::ChildIsHardened { + Bip32DerPathError::ChildIsHardened { child_at } => StandardHDPathError::ChildIsHardened { child: display_child_at(child_at), }, Bip32DerPathError::UnexpectedChildValue { child_at, actual, expected, - } => Bip44DerPathError::UnexpectedChildValue { + } => StandardHDPathError::UnexpectedChildValue { child: display_child_at(child_at), value: actual, expected, }, - Bip32DerPathError::Bip32Error(bip32) => Bip44DerPathError::Bip32Error(bip32), + Bip32DerPathError::Bip32Error(bip32) => StandardHDPathError::Bip32Error(bip32), } } } -impl From for Bip32DerPathError { - fn from(e: UnkownBip44ChainError) -> Self { +impl From for Bip32DerPathError { + fn from(e: UnknownChainError) -> Self { Bip32DerPathError::UnexpectedChildValue { - child_at: Bip44Index::Chain as usize, + child_at: StandardHDIndex::Chain as usize, actual: e.chain, expected: "0 or 1 chain".to_owned(), } @@ -114,7 +123,7 @@ impl From for Bip32DerPathError { } #[derive(Clone, Copy, Debug, Eq, PartialEq, Primitive)] -pub enum Bip44Index { +pub enum StandardHDIndex { Purpose = 0, CoinType = 1, AccountId = 2, @@ -130,13 +139,13 @@ pub enum Bip44Chain { } impl TryFrom for Bip44Chain { - type Error = UnkownBip44ChainError; + type Error = UnknownChainError; fn try_from(value: u32) -> Result { match value { 0 => Ok(Bip44Chain::External), 1 => Ok(Bip44Chain::Internal), - chain => Err(UnkownBip44ChainError { chain }), + chain => Err(UnknownChainError { chain }), } } } @@ -170,32 +179,50 @@ impl Bip32ChildValue for Bip44ChainValue { } } +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u32)] +pub enum Bip43Purpose { + Bip32 = 32, + Bip44 = 44, + Bip49 = 49, + Bip84 = 84, +} + #[derive(Clone, PartialEq)] -pub struct Bip44PurposeValue; +pub struct Bip32PurposeValue { + purpose: Bip43Purpose, +} -impl Bip32ChildValue for Bip44PurposeValue { - type Value = u32; +impl Bip32ChildValue for Bip32PurposeValue { + type Value = Bip43Purpose; - /// `purpose` is always a hardened child as it's described in the BIP44 standard. + /// `purpose` is always a hardened child as it's described in the BIP44/BIP49/BIP84 standards. fn hardened() -> bool { true } - fn number(&self) -> u32 { BIP44_PURPOSE } + fn number(&self) -> u32 { self.purpose as u32 } - fn value(&self) -> u32 { BIP44_PURPOSE } + fn value(&self) -> Bip43Purpose { self.purpose } fn from_bip32_number(child_number: ChildNumber, child_at: usize) -> Result { - let purpose_child_hardened = true; - let expected_purpose = ChildNumber::new(BIP44_PURPOSE, purpose_child_hardened) - .expect("'BIP44_PURPOSE' is expected to be a valid index"); - - if child_number != expected_purpose { - return Err(Bip32DerPathError::UnexpectedChildValue { - child_at, - actual: child_number.0, - expected: format!("'{}' BIP44 purpose", expected_purpose), - }); + if !child_number.is_hardened() { + return Err(Bip32DerPathError::ChildIsNotHardened { child_at }); } - Ok(Bip44PurposeValue) + + let purpose = match child_number.index() { + 32 => Bip43Purpose::Bip32, + 44 => Bip43Purpose::Bip44, + 49 => Bip43Purpose::Bip49, + 84 => Bip43Purpose::Bip84, + _chain => { + return Err(Bip32DerPathError::UnexpectedChildValue { + child_at, + actual: child_number.0, + expected: "one of the following: 32, 44, 49, 84".to_string(), + }) + }, + }; + + Ok(Bip32PurposeValue { purpose }) } } @@ -208,7 +235,7 @@ mod tests { #[test] fn test_from_str() { - let der_path = Bip44DerivationPath::from_str("m/44'/141'/1'/0/10").unwrap(); + let der_path = StandardHDPath::from_str("m/44'/141'/1'/0/10").unwrap(); assert_eq!(der_path.coin_type(), 141); assert_eq!(der_path.account_id(), 1); assert_eq!(der_path.chain(), Bip44Chain::External); @@ -217,15 +244,15 @@ mod tests { #[test] fn test_display() { - let der_path = Bip44PathToAccount::from_str("m/44'/141'/1'").unwrap(); + let der_path = StandardHDPathToAccount::from_str("m/44'/141'/1'").unwrap(); let actual = format!("{}", der_path); assert_eq!(actual, "m/44'/141'/1'"); } #[test] fn test_derive() { - let der_path_to_coin = Bip44PathToCoin::from_str("m/44'/141'").unwrap(); - let der_path_to_account: Bip44PathToAccount = + let der_path_to_coin = StandardHDPathToCoin::from_str("m/44'/141'").unwrap(); + let der_path_to_account: StandardHDPathToAccount = der_path_to_coin.derive(ChildNumber::new(10, true).unwrap()).unwrap(); assert_eq!( der_path_to_account.to_derivation_path(), @@ -235,14 +262,14 @@ mod tests { #[test] fn test_from_invalid_length() { - let error = Bip44DerivationPath::from_str("m/44'/141'/0'").expect_err("derivation path is too short"); + let error = StandardHDPath::from_str("m/44'/141'/0'").expect_err("derivation path is too short"); assert_eq!(error, Bip32DerPathError::InvalidDerivationPathLength { expected: 5, found: 3 }); - let error = Bip44DerivationPath::from_str("m/44'/141'/0'/1/2/3") - .expect_err("max number of children is 5, but 6 passes"); + let error = + StandardHDPath::from_str("m/44'/141'/0'/1/2/3").expect_err("max number of children is 5, but 6 passes"); assert_eq!(error, Bip32DerPathError::InvalidDerivationPathLength { expected: 5, found: 6 @@ -251,11 +278,26 @@ mod tests { #[test] fn test_from_unexpected_child_value() { - let error = Bip44PathToAccount::from_str("m/44'/141'/0").expect_err("'account_id' is not hardened"); + let error = StandardHDPathToAccount::from_str("m/44'/141'/0").expect_err("'account_id' is not hardened"); assert_eq!(error, Bip32DerPathError::ChildIsNotHardened { child_at: 2 }); - let error = Bip44DerPathError::from(error); - assert_eq!(error, Bip44DerPathError::ChildIsNotHardened { + let error = StandardHDPathError::from(error); + assert_eq!(error, StandardHDPathError::ChildIsNotHardened { child: "AccountId".to_owned() }); } + + #[test] + fn test_purposes() { + let path = StandardHDPath::from_str("m/32'/141'/0'/0/0").unwrap(); + assert_eq!(path.purpose(), Bip43Purpose::Bip32); + + let path = StandardHDPath::from_str("m/44'/141'/0'/0/0").unwrap(); + assert_eq!(path.purpose(), Bip43Purpose::Bip44); + + let path = StandardHDPath::from_str("m/49'/141'/0'/0/0").unwrap(); + assert_eq!(path.purpose(), Bip43Purpose::Bip49); + + let path = StandardHDPath::from_str("m/84'/141'/0'/0/0").unwrap(); + assert_eq!(path.purpose(), Bip43Purpose::Bip84); + } } diff --git a/mm2src/mm2_bitcoin/primitives/src/hash.rs b/mm2src/mm2_bitcoin/primitives/src/hash.rs index e7c10aa4e8..46f86df4d4 100644 --- a/mm2src/mm2_bitcoin/primitives/src/hash.rs +++ b/mm2src/mm2_bitcoin/primitives/src/hash.rs @@ -19,6 +19,10 @@ macro_rules! impl_hash { fn as_ref(&self) -> &$name { self } } + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { &self.0 } + } + impl Clone for $name { fn clone(&self) -> Self { let mut result = Self::default(); diff --git a/mm2src/mm2_core/Cargo.toml b/mm2src/mm2_core/Cargo.toml index b260692076..2af51b32dc 100644 --- a/mm2src/mm2_core/Cargo.toml +++ b/mm2src/mm2_core/Cargo.toml @@ -15,13 +15,11 @@ db_common = { path = "../db_common" } derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } hex = "0.4.2" -keys = { path = "../mm2_bitcoin/keys" } lazy_static = "1.4" mm2_metrics = { path = "../mm2_metrics" } primitives = { path = "../mm2_bitcoin/primitives" } rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } serde = "1" -serde_bytes = "0.11" serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } shared_ref_counter = { path = "../common/shared_ref_counter" } uuid = { version = "0.7", features = ["serde", "v4"] } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index d337b880d3..ec9388f64f 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -1,18 +1,14 @@ -use arrayref::array_ref; #[cfg(feature = "track-ctx-pointer")] use common::executor::Timer; use common::executor::{abortable_queue::{AbortableQueue, WeakSpawner}, graceful_shutdown, AbortSettings, AbortableSystem, SpawnAbortable, SpawnFuture}; use common::log::{self, LogLevel, LogOnError, LogState}; -use common::{bits256, cfg_native, cfg_wasm32, small_rng}; +use common::{cfg_native, cfg_wasm32, small_rng}; use gstuff::{try_s, Constructible, ERR, ERRL}; -use keys::KeyPair; use lazy_static::lazy_static; use mm2_metrics::{MetricsArc, MetricsOps}; use primitives::hash::H160; use rand::Rng; -use serde::{Deserialize, Serialize}; -use serde_bytes::ByteBuf; use serde_json::{self as json, Value as Json}; use shared_ref_counter::{SharedRc, WeakRc}; use std::any::Any; @@ -94,9 +90,6 @@ pub struct MmCtx { pub crypto_ctx: Mutex>>, /// RIPEMD160(SHA256(x)) where x is secp256k1 pubkey derived from passphrase. pub rmd160: Constructible, - /// secp256k1 key pair derived from passphrase. - /// cf. `key_pair_from_seed`. - pub secp256k1_key_pair: Constructible, /// Coins that should be enabled to kick start the interrupted swaps and orders. pub coins_needed_for_kick_start: Mutex>, /// The context belonging to the `lp_swap` mod: `SwapsContext`. @@ -143,7 +136,6 @@ impl MmCtx { coins_activation_ctx: Mutex::new(None), crypto_ctx: Mutex::new(None), rmd160: Constructible::default(), - secp256k1_key_pair: Constructible::default(), coins_needed_for_kick_start: Mutex::new(HashSet::new()), swaps_ctx: Mutex::new(None), stats_ctx: Mutex::new(None), @@ -231,33 +223,6 @@ impl MmCtx { /// True if the MarketMaker instance needs to stop. pub fn is_stopping(&self) -> bool { self.stop.copy_or(false) } - /// Get a reference to the secp256k1 key pair. - /// Panics if the key pair is not available. - pub fn secp256k1_key_pair(&self) -> &KeyPair { - match self.secp256k1_key_pair.as_option() { - Some(pair) => pair, - None => panic!("secp256k1_key_pair not available"), - } - } - - /// Get a reference to the secp256k1 key pair as option. - /// Can be used in no-login functions to check if the passphrase is set - pub fn secp256k1_key_pair_as_option(&self) -> Option<&KeyPair> { self.secp256k1_key_pair.as_option() } - - /// This is our public ID, allowing us to be different from other peers. - /// This should also be our public key which we'd use for message verification. - pub fn public_id(&self) -> Result { - self.secp256k1_key_pair - .ok_or(ERRL!("Public ID is not yet available")) - .map(|keypair| { - let public = keypair.public(); // Compressed public key is going to be 33 bytes. - // First byte is a prefix, https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/. - bits256 { - bytes: *array_ref!(public, 1, 32), - } - }) - } - pub fn gui(&self) -> Option<&str> { self.conf["gui"].as_str() } pub fn mm_version(&self) -> &str { &self.mm_version } @@ -358,22 +323,6 @@ lazy_static! { pub static ref MM_CTX_FFI: Mutex> = Mutex::new (HashMap::default()); } -/// Portable core sharing its context with the native helpers. -/// -/// In the integration tests we're using this to create new native contexts. -#[derive(Serialize, Deserialize)] -struct PortableCtx { - // Sending the `conf` as a string in order for bencode not to mess with JSON, and for wire readability. - conf: String, - secp256k1_key_pair: ByteBuf, - ffi_handle: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -struct NativeCtx { - ffi_handle: u32, -} - impl MmArc { pub fn new(ctx: MmCtx) -> MmArc { MmArc(SharedRc::new(ctx)) } @@ -579,7 +528,6 @@ where pub struct MmCtxBuilder { conf: Option, log_level: LogLevel, - key_pair: Option, version: String, #[cfg(target_arch = "wasm32")] db_namespace: DbNamespaceId, @@ -598,11 +546,6 @@ impl MmCtxBuilder { self } - pub fn with_secp256k1_key_pair(mut self, key_pair: KeyPair) -> Self { - self.key_pair = Some(key_pair); - self - } - pub fn with_version(mut self, version: String) -> Self { self.version = version; self @@ -629,11 +572,6 @@ impl MmCtxBuilder { ctx.conf = conf } - if let Some(key_pair) = self.key_pair { - ctx.rmd160.pin(key_pair.public().address_hash()).unwrap(); - ctx.secp256k1_key_pair.pin(key_pair).unwrap(); - } - #[cfg(target_arch = "wasm32")] { ctx.db_namespace = self.db_namespace; diff --git a/mm2src/mm2_err_handle/src/common_errors.rs b/mm2src/mm2_err_handle/src/common_errors.rs index 83f699932f..24e3b5238a 100644 --- a/mm2src/mm2_err_handle/src/common_errors.rs +++ b/mm2src/mm2_err_handle/src/common_errors.rs @@ -9,3 +9,9 @@ pub trait WithInternal { pub trait WithTimeout { fn timeout(duration: Duration) -> Self; } + +pub struct InternalError(pub String); + +impl InternalError { + pub fn take(self) -> String { self.0 } +} diff --git a/mm2src/mm2_err_handle/src/discard_mm_trace.rs b/mm2src/mm2_err_handle/src/discard_mm_trace.rs new file mode 100644 index 0000000000..112a66ec16 --- /dev/null +++ b/mm2src/mm2_err_handle/src/discard_mm_trace.rs @@ -0,0 +1,29 @@ +use crate::mm_error::{MmError, NotMmError}; + +pub trait DiscardMmTrace +where + E: NotMmError, +{ + fn discard_mm_trace(self) -> Result; +} + +impl DiscardMmTrace for Result> +where + E: NotMmError, +{ + /// Discards the error trace and maps `Err(MmError)` into `Err(E)`. + /// + /// This method can be used to match the inner `E` error and at the same time not to loose the trace. + /// + /// # Examples + /// + /// ```rust + /// let res: Result<(), _> = MmError::err("Not found"); + /// match res.discard_mm_trace() { + /// Ok(_) => (), + /// Err("Internal error") => return Err(NewErrorType {}), + /// Err("Not found") => return Ok(None), + /// } + /// ``` + fn discard_mm_trace(self) -> Result { self.map_err(MmError::into_inner) } +} diff --git a/mm2src/mm2_err_handle/src/lib.rs b/mm2src/mm2_err_handle/src/lib.rs index c1e8c572c7..93b5030af3 100644 --- a/mm2src/mm2_err_handle/src/lib.rs +++ b/mm2src/mm2_err_handle/src/lib.rs @@ -3,20 +3,24 @@ #![feature(allocator_api)] pub mod common_errors; +pub mod discard_mm_trace; pub mod map_mm_error; pub mod map_to_mm; pub mod map_to_mm_fut; pub mod mm_error; pub mod mm_json_error; pub mod or_mm_error; +pub mod split_mm; pub mod prelude { pub use crate::common_errors::{WithInternal, WithTimeout}; + pub use crate::discard_mm_trace::DiscardMmTrace; pub use crate::map_mm_error::MapMmError; pub use crate::map_to_mm::MapToMmResult; pub use crate::map_to_mm_fut::MapToMmFutureExt; pub use crate::mm_error::{MmError, MmResult, NotEqual, NotMmError, SerMmErrorType}; pub use crate::mm_json_error::MmJsonError; pub use crate::or_mm_error::OrMmError; + pub use crate::split_mm::SplitMmResult; pub use ser_error::SerializeErrorType; } diff --git a/mm2src/mm2_err_handle/src/split_mm.rs b/mm2src/mm2_err_handle/src/split_mm.rs new file mode 100644 index 0000000000..13eb5ab683 --- /dev/null +++ b/mm2src/mm2_err_handle/src/split_mm.rs @@ -0,0 +1,29 @@ +use crate::mm_error::{MmError, MmErrorTrace, NotMmError}; + +pub trait SplitMmResult +where + E: NotMmError, +{ + fn split_mm(self) -> Result; +} + +impl SplitMmResult for Result> +where + E: NotMmError, +{ + /// Splits the inner `Err(MmError)` into inner `E` and the error trace. + /// + /// This method can be used to match the inner `E` error and at the same time not to loose the trace. + /// + /// # Examples + /// + /// ```rust + /// let res: Result<(), _> = MmError::err("Not found"); + /// match res.split_mm() { + /// Ok(_) => (), + /// Err("Internal", trace) => return MmError::err_with_trace(NewErrorType {}, trace), + /// Err("Not found", _trace) => return Ok(None), + /// } + /// ``` + fn split_mm(self) -> Result { self.map_err(MmError::split) } +} diff --git a/mm2src/mm2_main/src/lp_init/init_hw.rs b/mm2src/mm2_main/src/lp_init/init_hw.rs index 0b92fd5e81..5a1d4b02f2 100644 --- a/mm2src/mm2_main/src/lp_init/init_hw.rs +++ b/mm2src/mm2_main/src/lp_init/init_hw.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use common::{HttpStatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction, HwRpcTaskUserActionRequest, TrezorRpcTaskConnectProcessor}; -use crypto::{from_hw_error, CryptoCtx, CryptoInitError, HwCtxInitError, HwDeviceInfo, HwError, HwPubkey, HwRpcError, +use crypto::{from_hw_error, CryptoCtx, CryptoCtxError, HwCtxInitError, HwDeviceInfo, HwError, HwPubkey, HwRpcError, HwWalletType, WithHwRpcError}; use derive_more::Display; use enum_from::EnumFromTrait; @@ -47,8 +47,8 @@ impl From for InitHwError { fn from(hw_error: HwError) -> Self { from_hw_error(hw_error) } } -impl From for InitHwError { - fn from(e: CryptoInitError) -> Self { InitHwError::Internal(e.to_string()) } +impl From for InitHwError { + fn from(e: CryptoCtxError) -> Self { InitHwError::Internal(e.to_string()) } } impl From> for InitHwError { diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index 5daa09d607..d04966bed0 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -26,6 +26,7 @@ use crypto::{from_hw_error, CryptoCtx, CryptoInitError, HwError, HwProcessingErr use derive_more::Display; use enum_from::EnumFromTrait; use mm2_core::mm_ctx::{MmArc, MmCtx}; +use mm2_err_handle::common_errors::InternalError; use mm2_err_handle::prelude::*; use mm2_libp2p::{spawn_gossipsub, AdexBehaviourError, NodeType, RelayAddress, RelayAddressError, SwarmRuntime, WssCerts}; @@ -118,6 +119,11 @@ pub enum MmInitError { FieldNotFoundInConfig { field: String, }, + #[display(fmt = "The '{}' field has wrong value in the config: {}", field, error)] + FieldWrongValueInConfig { + field: String, + error: String, + }, #[display(fmt = "P2P initializing error: '{}'", _0)] P2PError(P2PInitError), #[display(fmt = "Error creating DB director '{:?}': {}", path, error)] @@ -199,6 +205,10 @@ impl From for MmInitError { }, CryptoInitError::NullStringPassphrase => MmInitError::NullStringPassphrase, CryptoInitError::InvalidPassphrase(pass) => MmInitError::InvalidPassphrase(pass.to_string()), + CryptoInitError::InvalidHdAccount { error, .. } => MmInitError::FieldWrongValueInConfig { + field: "hd_account".to_string(), + error, + }, CryptoInitError::Internal(internal) => MmInitError::Internal(internal), } } @@ -231,6 +241,10 @@ impl From> for MmInitError { } } +impl From for MmInitError { + fn from(e: InternalError) -> Self { MmInitError::Internal(e.take()) } +} + impl MmInitError { pub fn db_directory_is_not_writable(path: &str) -> MmInitError { MmInitError::DbDirectoryIsNotWritable { path: path.to_owned() } @@ -354,7 +368,7 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { init_ordermatch_context(&ctx)?; init_p2p(ctx.clone()).await?; - if ctx.secp256k1_key_pair_as_option().is_none() { + if !CryptoCtx::is_init(&ctx)? { return Ok(()); } @@ -397,7 +411,11 @@ pub async fn lp_init(ctx: MmArc) -> MmInitResult<()> { field: "passphrase".to_owned(), error: e.to_string(), })?; - CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?; + + match ctx.conf["hd_account_id"].as_u64() { + Some(hd_account_id) => CryptoCtx::init_with_global_hd_account(ctx.clone(), &passphrase, hd_account_id)?, + None => CryptoCtx::init_with_iguana_passphrase(ctx.clone(), &passphrase)?, + }; } lp_init_continue(ctx.clone()).await?; @@ -454,7 +472,8 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { let ctx_on_poll = ctx.clone(); let force_p2p_key = if i_am_seed { - let key = sha256(&*ctx.secp256k1_key_pair().private().secret); + let crypto_ctx = CryptoCtx::from_ctx(&ctx).mm_err(|e| P2PInitError::Internal(e.to_string()))?; + let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); Some(key.take()) } else { None diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 0fb0ed4f91..d1d6b1311c 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -33,7 +33,7 @@ use common::log::{error, warn, LogOnError}; use common::time_cache::TimeCache; use common::{bits256, log, new_uuid, now_ms}; use crypto::privkey::SerializableSecp256k1Keypair; -use crypto::CryptoCtx; +use crypto::{CryptoCtx, CryptoCtxError}; use derive_more::Display; use futures::{compat::Future01CompatExt, lock::Mutex as AsyncMutex, TryFutureExt}; use hash256_std_hasher::Hash256StdHasher; @@ -61,12 +61,12 @@ use std::time::Duration; use trie_db::NodeCodec as NodeCodecT; use uuid::Uuid; -use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, Libp2pPeerId, - P2PRequest}; +use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest}; use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, check_other_coin_balance_for_swap, insert_new_swap_to_db, is_pubkey_banned, - lp_atomic_locktime, run_maker_swap, run_taker_swap, AtomicLocktimeVersion, MakerSwap, - RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap}; + lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast, + p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, AtomicLocktimeVersion, + MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap}; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; use my_orders_storage::{delete_my_maker_order, delete_my_taker_order, save_maker_order_on_update, @@ -319,6 +319,14 @@ fn process_maker_order_updated(ctx: MmArc, from_pubkey: String, updated_msg: new // Ok(()) // } +// ZHTLC protocol coin uses random keypair to sign P2P messages per every order. +// So, each ZHTLC order has unique «pubkey» field that doesn’t match node persistent pubkey derived from passphrase. +// We can compare pubkeys from maker_orders and from asks or bids, to find our order. +#[inline(always)] +fn is_my_order(my_orders_pubkeys: &HashSet, my_pub: &Option, order_pubkey: &str) -> bool { + my_pub.as_deref() == Some(order_pubkey) || my_orders_pubkeys.contains(order_pubkey) +} + /// Request best asks and bids for the given `base` and `rel` coins from relays. /// Set `asks_num` and/or `bids_num` to get corresponding number of best asks and bids or None to get all of the available orders. /// @@ -347,24 +355,28 @@ async fn request_and_fill_orderbook(ctx: &MmArc, base: &str, rel: &str) -> Resul let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).unwrap(); let mut orderbook = ordermatch_ctx.orderbook.lock(); - let keypair = ctx.secp256k1_key_pair_as_option(); + let my_pubsecp = match CryptoCtx::from_ctx(ctx).discard_mm_trace() { + Ok(crypto_ctx) => Some(crypto_ctx.mm2_internal_pubkey_hex()), + Err(CryptoCtxError::NotInitialized) => None, + Err(other) => return ERR!("{}", other), + }; + let alb_pair = alb_ordered_pair(base, rel); for (pubkey, GetOrderbookPubkeyItem { orders, .. }) in pubkey_orders { let pubkey_bytes = match hex::decode(&pubkey) { Ok(b) => b, Err(e) => { - log::warn!("Error {} decoding pubkey {}", e, pubkey); + warn!("Error {} decoding pubkey {}", e, pubkey); continue; }, }; - if let Some(keypair) = keypair { - if pubkey_bytes.as_slice() == keypair.public().as_ref() { - continue; - } + + if is_my_order(&orderbook.my_p2p_pubkeys, &my_pubsecp, &pubkey) { + continue; } if is_pubkey_banned(ctx, &pubkey_bytes[1..].into()) { - log::warn!("Pubkey {} is banned", pubkey); + warn!("Pubkey {} is banned", pubkey); continue; } let params = ProcessTrieParams { @@ -962,10 +974,7 @@ fn maker_order_created_p2p_notify( }; let to_broadcast = new_protocol::OrdermatchMessage::MakerOrderCreated(message.clone()); - let (key_pair, peer_id) = match order.p2p_keypair() { - Some(k) => (k, Some(k.libp2p_peer_id())), - None => (ctx.secp256k1_key_pair(), None), - }; + let (key_pair, peer_id) = p2p_keypair_and_peer_id_to_broadcast(&ctx, order.p2p_keypair()); let encoded_msg = encode_and_sign(&to_broadcast, key_pair.private_ref()).unwrap(); let item: OrderbookItem = (message, hex::encode(key_pair.public_slice())).into(); @@ -991,10 +1000,7 @@ fn maker_order_updated_p2p_notify( p2p_privkey: Option<&KeyPair>, ) { let msg: new_protocol::OrdermatchMessage = message.clone().into(); - let (secret, peer_id) = match p2p_privkey { - Some(k) => (k.private_bytes(), Some(k.libp2p_peer_id())), - None => (ctx.secp256k1_key_pair().private_bytes(), None), - }; + let (secret, peer_id) = p2p_private_and_peer_id_to_broadcast(&ctx, p2p_privkey); let encoded_msg = encode_and_sign(&msg, &secret).unwrap(); process_my_maker_order_updated(&ctx, &message); broadcast_p2p_msg(&ctx, vec![topic], encoded_msg, peer_id); @@ -2220,7 +2226,7 @@ fn broadcast_keep_alive_for_pub(ctx: &MmArc, pubkey: &str, orderbook: &Orderbook pub async fn broadcast_maker_orders_keep_alive_loop(ctx: MmArc) { let persistent_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") - .secp256k1_pubkey_hex(); + .mm2_internal_pubkey_hex(); while !ctx.is_stopping() { Timer::sleep(MIN_ORDER_KEEP_ALIVE_INTERVAL as f64).await; @@ -2251,10 +2257,7 @@ fn broadcast_ordermatch_message( msg: new_protocol::OrdermatchMessage, p2p_privkey: Option<&KeyPair>, ) { - let (secret, peer_id) = match p2p_privkey { - Some(k) => (k.private_bytes(), Some(k.libp2p_peer_id())), - None => (ctx.secp256k1_key_pair().private_bytes(), None), - }; + let (secret, peer_id) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey); let encoded_msg = encode_and_sign(&msg, &secret).unwrap(); broadcast_p2p_msg(ctx, topics.into_iter().collect(), encoded_msg, peer_id); } @@ -2852,8 +2855,11 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO let alice = bits256::from(maker_match.request.sender_pubkey.0); let maker_amount = maker_match.reserved.get_base_amount().to_decimal(); let taker_amount = maker_match.reserved.get_rel_amount().to_decimal(); - let privkey = &ctx.secp256k1_key_pair().private().secret; - let my_persistent_pub = compressed_pub_key_from_priv_raw(&privkey[..], ChecksumType::DSHA256).unwrap(); + + let crypto_ctx = CryptoCtx::from_ctx(&ctx).expect("'CryptoCtx' must be initialized already"); + let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); + let my_persistent_pub = compressed_pub_key_from_priv_raw(raw_priv.as_slice(), ChecksumType::DSHA256).unwrap(); + let my_conf_settings = choose_maker_confs_and_notas( maker_order.conf_settings, &maker_match.request, @@ -2941,8 +2947,10 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat }, }; - let privkey = &ctx.secp256k1_key_pair().private().secret; - let my_persistent_pub = compressed_pub_key_from_priv_raw(&privkey[..], ChecksumType::DSHA256).unwrap(); + let crypto_ctx = CryptoCtx::from_ctx(&ctx).expect("'CryptoCtx' must be initialized already"); + let raw_priv = crypto_ctx.mm2_internal_privkey_secret(); + let my_persistent_pub = compressed_pub_key_from_priv_raw(raw_priv.as_slice(), ChecksumType::DSHA256).unwrap(); + let maker_amount = taker_match.reserved.get_base_amount().clone(); let taker_amount = taker_match.reserved.get_rel_amount().clone(); @@ -3002,7 +3010,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat pub async fn lp_ordermatch_loop(ctx: MmArc) { let my_pubsecp = CryptoCtx::from_ctx(&ctx) .expect("CryptoCtx not available") - .secp256k1_pubkey_hex(); + .mm2_internal_pubkey_hex(); let maker_order_timeout = ctx.conf["maker_order_timeout"].as_u64().unwrap_or(MAKER_ORDER_TIMEOUT); loop { @@ -3269,7 +3277,9 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: } } - let our_public_id = ctx.public_id().unwrap(); + let our_public_id = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .mm2_internal_public_id(); if our_public_id.bytes == from_pubkey.0 { log::warn!("Skip maker reserved from our pubkey"); return; @@ -3343,7 +3353,10 @@ async fn process_maker_reserved(ctx: MmArc, from_pubkey: H256Json, reserved_msg: async fn process_maker_connected(ctx: MmArc, from_pubkey: H256Json, connected: MakerConnected) { log::debug!("Processing MakerConnected {:?}", connected); let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); - let our_public_id = ctx.public_id().unwrap(); + + let our_public_id = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .mm2_internal_public_id(); if our_public_id.bytes == from_pubkey.0 { log::warn!("Skip maker connected from our pubkey"); return; @@ -3380,7 +3393,12 @@ async fn process_maker_connected(ctx: MmArc, from_pubkey: H256Json, connected: M } async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: TakerRequest) { - let our_public_id: H256Json = ctx.public_id().unwrap().bytes.into(); + let our_public_id: H256Json = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .mm2_internal_public_id() + .bytes + .into(); + if our_public_id == from_pubkey { log::warn!("Skip the request originating from our pubkey"); return; @@ -3454,7 +3472,11 @@ async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: async fn process_taker_connect(ctx: MmArc, sender_pubkey: H256Json, connect_msg: TakerConnect) { log::debug!("Processing TakerConnect {:?}", connect_msg); let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); - let our_public_id = ctx.public_id().unwrap(); + + let our_public_id = CryptoCtx::from_ctx(&ctx) + .expect("'CryptoCtx' must be initialized already") + .mm2_internal_public_id(); + if our_public_id.bytes == sender_pubkey.0 { log::warn!("Skip taker connect from our pubkey"); return; @@ -3697,7 +3719,7 @@ pub async fn lp_auto_buy( }; let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(ctx)); let mut my_taker_orders = ordermatch_ctx.my_taker_orders.lock().await; - let our_public_id = try_s!(ctx.public_id()); + let our_public_id = try_s!(CryptoCtx::from_ctx(&ctx)).mm2_internal_public_id(); let rel_volume = &input.volume * &input.price; let conf_settings = OrderConfirmationsSettings { base_confs: input.base_confs.unwrap_or_else(|| base_coin.required_confirmations()), diff --git a/mm2src/mm2_main/src/lp_ordermatch/orderbook_rpc.rs b/mm2src/mm2_main/src/lp_ordermatch/orderbook_rpc.rs index 2b8fb46f62..38278d88e9 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/orderbook_rpc.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/orderbook_rpc.rs @@ -1,9 +1,9 @@ -use super::{orderbook_address, subscribe_to_orderbook_topic, OrdermatchContext, RpcOrderbookEntry}; -use crate::mm2::lp_ordermatch::{addr_format_from_protocol_info, RpcOrderbookEntryV2}; +use super::{addr_format_from_protocol_info, is_my_order, orderbook_address, subscribe_to_orderbook_topic, + OrdermatchContext, RpcOrderbookEntry, RpcOrderbookEntryV2}; use coins::{address_by_coin_conf_and_pubkey_str, coin_conf, is_wallet_only_conf}; use common::log::warn; use common::{now_ms, HttpStatusCode}; -use crypto::CryptoCtx; +use crypto::{CryptoCtx, CryptoCtxError}; use derive_more::Display; use http::{Response, StatusCode}; use mm2_core::mm_ctx::MmArc; @@ -11,7 +11,6 @@ use mm2_err_handle::prelude::*; use mm2_number::{construct_detailed, BigRational, MmNumber, MmNumberMultiRepr}; use num_traits::Zero; use serde_json::{self as json, Value as Json}; -use std::collections::HashSet; #[derive(Deserialize)] pub struct OrderbookReq { @@ -108,14 +107,6 @@ fn build_aggregated_entries_v2( (aggregated, total_base.into(), total_rel.into()) } -// ZHTLC protocol coin uses random keypair to sign P2P messages per every order. -// So, each ZHTLC order has unique «pubkey» field that doesn’t match node persistent pubkey derived from passphrase. -// We can compare pubkeys from maker_orders and from asks or bids, to find our order. -#[inline(always)] -fn is_my_order(my_orders_pubkeys: &HashSet, my_pub: &Option, order_pubkey: &str) -> bool { - my_pub.as_deref() == Some(order_pubkey) || my_orders_pubkeys.contains(order_pubkey) -} - pub async fn orderbook_rpc(ctx: MmArc, req: Json) -> Result>, String> { let req: OrderbookReq = try_s!(json::from_value(req)); if req.base == req.rel { @@ -144,13 +135,14 @@ pub async fn orderbook_rpc(ctx: MmArc, req: Json) -> Result>, S } try_s!(subscribe_to_orderbook_topic(&ctx, &base_ticker, &rel_ticker, request_orderbook).await); - let orderbook = ordermatch_ctx.orderbook.lock(); - let my_pubsecp = ctx.secp256k1_key_pair_as_option().map(|_| { - CryptoCtx::from_ctx(&ctx) - .expect("ctx is available") - .secp256k1_pubkey_hex() - }); + let my_pubsecp = match CryptoCtx::from_ctx(&ctx).discard_mm_trace() { + Ok(crypto_ctx) => Some(crypto_ctx.mm2_internal_pubkey_hex()), + Err(CryptoCtxError::NotInitialized) => None, + Err(other) => return ERR!("{}", other), + }; + + let orderbook = ordermatch_ctx.orderbook.lock(); let mut asks = match orderbook.unordered.get(&(base_ticker.clone(), rel_ticker.clone())) { Some(uuids) => { let mut orderbook_entries = Vec::new(); @@ -232,6 +224,7 @@ pub enum OrderbookRpcError { CoinConfigNotFound(String), CoinIsWalletOnly(String), P2PSubscribeError(String), + Internal(String), } impl HttpStatusCode for OrderbookRpcError { @@ -241,7 +234,9 @@ impl HttpStatusCode for OrderbookRpcError { | OrderbookRpcError::BaseRelSameOrderbookTickersAndProtocols | OrderbookRpcError::CoinConfigNotFound(_) | OrderbookRpcError::CoinIsWalletOnly(_) => StatusCode::BAD_REQUEST, - OrderbookRpcError::P2PSubscribeError(_) => StatusCode::INTERNAL_SERVER_ERROR, + OrderbookRpcError::P2PSubscribeError(_) | OrderbookRpcError::Internal(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, } } } @@ -311,11 +306,14 @@ pub async fn orderbook_rpc_v2( .map_to_mm(OrderbookRpcError::P2PSubscribeError)?; let orderbook = ordermatch_ctx.orderbook.lock(); - let my_pubsecp = ctx.secp256k1_key_pair_as_option().map(|_| { - CryptoCtx::from_ctx(&ctx) - .expect("ctx is available") - .secp256k1_pubkey_hex() - }); + + let my_pubsecp = match CryptoCtx::from_ctx(&ctx).split_mm() { + Ok(crypto_ctx) => Some(crypto_ctx.mm2_internal_pubkey_hex()), + Err((CryptoCtxError::NotInitialized, _trace)) => None, + Err((CryptoCtxError::Internal(e), trace)) => { + return MmError::err_with_trace(OrderbookRpcError::Internal(e), trace) + }, + }; let mut asks = match orderbook.unordered.get(&(base_ticker.clone(), rel_ticker.clone())) { Some(uuids) => { diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs index 23f87cf5dc..a531eb48e8 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs @@ -756,18 +756,14 @@ pub async fn stop_simple_market_maker_bot(ctx: MmArc, _req: Json) -> StopSimpleM mod tests { use super::{start_simple_market_maker_bot, stop_simple_market_maker_bot, StartSimpleMakerBotRequest}; use common::block_on; - use crypto::privkey::key_pair_from_seed; - use mm2_core::mm_ctx::MmCtxBuilder; + use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use serde_json::Value as Json; #[test] fn test_start_and_stop_simple_market_maker_bot_from_ctx() { - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair( - key_pair_from_seed("also shoot benefit prefer juice shell elder veteran woman mimic image kidney") - .unwrap(), - ) - .into_mm_arc(); + let ctx = mm_ctx_with_iguana(Some( + "also shoot benefit prefer juice shell elder veteran woman mimic image kidney", + )); let cloned_ctx = ctx.clone(); let another_cloned_ctx = ctx.clone(); diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 9dea4e7ff1..cc2c5058c6 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -70,7 +70,7 @@ use derive_more::Display; use http::Response; use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_err_handle::prelude::*; -use mm2_libp2p::{decode_signed, encode_and_sign, pub_sub_topic, TopicPrefix}; +use mm2_libp2p::{decode_signed, encode_and_sign, pub_sub_topic, PeerId, TopicPrefix}; use mm2_number::{BigDecimal, BigRational, MmNumber}; use parking_lot::Mutex as PaMutex; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; @@ -103,6 +103,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; mod swap_wasm_db; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError}; +use crypto::CryptoCtx; use keys::KeyPair; use maker_swap::MakerSwapEvent; pub use maker_swap::{calc_max_maker_vol, check_balance_for_maker_swap, maker_swap_trade_preimage, run_maker_swap, @@ -162,6 +163,38 @@ impl SwapMsgStore { } } +/// Returns key-pair for signing P2P messages and an optional `PeerId` if it should be used forcibly +/// instead of local peer ID. +/// +/// # Panic +/// +/// This function panics if `CryptoCtx` hasn't been initialized yet. +pub fn p2p_keypair_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&KeyPair>) -> (KeyPair, Option) { + match p2p_privkey { + Some(keypair) => (*keypair, Some(keypair.libp2p_peer_id())), + None => { + let crypto_ctx = CryptoCtx::from_ctx(ctx).expect("CryptoCtx must be initialized already"); + (*crypto_ctx.mm2_internal_key_pair(), None) + }, + } +} + +/// Returns private key for signing P2P messages and an optional `PeerId` if it should be used forcibly +/// instead of local peer ID. +/// +/// # Panic +/// +/// This function panics if `CryptoCtx` hasn't been initialized yet. +pub fn p2p_private_and_peer_id_to_broadcast(ctx: &MmArc, p2p_privkey: Option<&KeyPair>) -> ([u8; 32], Option) { + match p2p_privkey { + Some(keypair) => (keypair.private_bytes(), Some(keypair.libp2p_peer_id())), + None => { + let crypto_ctx = CryptoCtx::from_ctx(ctx).expect("CryptoCtx must be initialized already"); + (crypto_ctx.mm2_internal_privkey_secret().take(), None) + }, + } +} + /// Spawns the loop that broadcasts message every `interval` seconds returning the AbortOnDropHandle /// to stop it pub fn broadcast_swap_message_every( @@ -182,10 +215,7 @@ pub fn broadcast_swap_message_every( /// Broadcast the swap message once pub fn broadcast_swap_message(ctx: &MmArc, topic: String, msg: T, p2p_privkey: &Option) { - let (p2p_private, from) = match p2p_privkey { - Some(keypair) => (keypair.private_bytes(), Some(keypair.libp2p_peer_id())), - None => (ctx.secp256k1_key_pair().private().secret.take(), None), - }; + let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); let encoded_msg = encode_and_sign(&msg, &p2p_private).unwrap(); broadcast_p2p_msg(ctx, vec![topic], encoded_msg, from); } @@ -196,11 +226,7 @@ pub fn broadcast_p2p_tx_msg(ctx: &MmArc, topic: String, msg: &TransactionEnum, p return; } - let (p2p_private, from) = match p2p_privkey { - Some(keypair) => (keypair.private_bytes(), Some(keypair.libp2p_peer_id())), - None => (ctx.secp256k1_key_pair().private().secret.take(), None), - }; - + let (p2p_private, from) = p2p_private_and_peer_id_to_broadcast(ctx, p2p_privkey.as_ref()); let encoded_msg = encode_and_sign(&msg.tx_hex(), &p2p_private).unwrap(); broadcast_p2p_msg(ctx, vec![topic], encoded_msg, from); } @@ -1304,7 +1330,6 @@ mod lp_swap_tests { use coins::MarketCoinOps; use coins::PrivKeyActivationPolicy; use common::block_on; - use crypto::privkey::key_pair_from_seed; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_test_helpers::for_tests::{morty_conf, rick_conf, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; @@ -1681,7 +1706,7 @@ mod lp_swap_tests { address_format: None, gap_limit: None, enable_params: Default::default(), - priv_key_policy: PrivKeyActivationPolicy::IguanaPrivKey, + priv_key_policy: PrivKeyActivationPolicy::ContextPrivKey, check_utxo_maturity: None, } } @@ -1705,11 +1730,11 @@ mod lp_swap_tests { "i_am_seed": true, }); - let maker_key_pair = key_pair_from_seed(&maker_passphrase).unwrap(); - let maker_ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair(maker_key_pair.clone()) - .with_conf(maker_ctx_conf) - .into_mm_arc(); + let maker_ctx = MmCtxBuilder::default().with_conf(maker_ctx_conf).into_mm_arc(); + let maker_key_pair = *CryptoCtx::init_with_iguana_passphrase(maker_ctx.clone(), &maker_passphrase) + .unwrap() + .mm2_internal_key_pair(); + fix_directories(&maker_ctx).unwrap(); block_on(init_p2p(maker_ctx.clone())).unwrap(); @@ -1721,7 +1746,7 @@ mod lp_swap_tests { "RICK", &rick_conf(), &rick_activation_params, - maker_key_pair.private_ref(), + maker_key_pair.private().secret, )) .unwrap(); @@ -1732,21 +1757,21 @@ mod lp_swap_tests { "MORTY", &morty_conf(), &morty_activation_params, - maker_key_pair.private_ref(), + maker_key_pair.private().secret, )) .unwrap(); - let taker_key_pair = key_pair_from_seed(&taker_passphrase).unwrap(); - let taker_ctx_conf = json!({ "netid": 1234, "p2p_in_memory": true, "seednodes": vec!["/memory/777"] }); - let taker_ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair(taker_key_pair.clone()) - .with_conf(taker_ctx_conf) - .into_mm_arc(); + + let taker_ctx = MmCtxBuilder::default().with_conf(taker_ctx_conf).into_mm_arc(); + let taker_key_pair = *CryptoCtx::init_with_iguana_passphrase(taker_ctx.clone(), &taker_passphrase) + .unwrap() + .mm2_internal_key_pair(); + fix_directories(&taker_ctx).unwrap(); block_on(init_p2p(taker_ctx.clone())).unwrap(); @@ -1755,7 +1780,7 @@ mod lp_swap_tests { "RICK", &rick_conf(), &rick_activation_params, - taker_key_pair.private_ref(), + taker_key_pair.private().secret, )) .unwrap(); @@ -1764,7 +1789,7 @@ mod lp_swap_tests { "MORTY", &morty_conf(), &morty_activation_params, - taker_key_pair.private_ref(), + taker_key_pair.private().secret, )) .unwrap(); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 3276acecb5..f4431313af 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -23,6 +23,7 @@ use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTx use common::log::{debug, error, info, warn}; use common::{bits256, executor::Timer, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::SerializableSecp256k1Keypair; +use crypto::CryptoCtx; use futures::{compat::Future01CompatExt, select, FutureExt}; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; @@ -391,24 +392,27 @@ impl MakerSwap { .swap_contract_address() .map_or_else(Vec::new, |addr| addr.0); - if r.data.maker_coin_htlc_pubkey != r.data.taker_coin_htlc_pubkey { - NegotiationDataMsg::V3(NegotiationDataV3 { + let equal = r.data.maker_coin_htlc_pubkey == r.data.taker_coin_htlc_pubkey; + let same_as_persistent = r.data.maker_coin_htlc_pubkey == Some(r.data.my_persistent_pub); + + if equal && same_as_persistent { + NegotiationDataMsg::V2(NegotiationDataV2 { started_at: r.data.started_at, payment_locktime: r.data.maker_payment_lock, + persistent_pubkey: r.data.my_persistent_pub.0.to_vec(), secret_hash, maker_coin_swap_contract, taker_coin_swap_contract, - maker_coin_htlc_pub: self.my_maker_coin_htlc_pub().into(), - taker_coin_htlc_pub: self.my_taker_coin_htlc_pub().into(), }) } else { - NegotiationDataMsg::V2(NegotiationDataV2 { + NegotiationDataMsg::V3(NegotiationDataV3 { started_at: r.data.started_at, payment_locktime: r.data.maker_payment_lock, - persistent_pubkey: r.data.my_persistent_pub.0.to_vec(), secret_hash, maker_coin_swap_contract, taker_coin_swap_contract, + maker_coin_htlc_pub: self.my_maker_coin_htlc_pub().into(), + taker_coin_htlc_pub: self.my_taker_coin_htlc_pub().into(), }) } } @@ -1173,7 +1177,10 @@ impl MakerSwap { let mut taker = bits256::from([0; 32]); taker.bytes = data.taker.0; - let my_persistent_pub = H264::from(&**ctx.secp256k1_key_pair().public()); + + let crypto_ctx = try_s!(CryptoCtx::from_ctx(&ctx)); + let my_persistent_pub = H264::from(&**crypto_ctx.mm2_internal_key_pair().public()); + let conf_settings = SwapConfirmationsSettings { maker_coin_confs: data.maker_payment_confirmations, maker_coin_nota: data @@ -1913,6 +1920,12 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { event.clone().into(), ) } + + #[cfg(target_arch = "wasm32")] + if event.is_error() { + error!("[swap uuid={uuid_str}] {event:?}"); + } + status.status(swap_tags!(), &event.status_str()); running_swap.apply_event(event); } @@ -2114,11 +2127,13 @@ mod maker_swap_tests { use coins::eth::{addr_from_str, signed_eth_tx_from_bytes, SignedEthTx}; use coins::{MarketCoinOps, MmCoin, SwapOps, TestCoin}; use common::block_on; - use crypto::privkey::key_pair_from_seed; - use mm2_core::mm_ctx::MmCtxBuilder; + use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use mocktopus::mocking::*; use serde_json as json; + const PASSPHRASE: Option<&str> = + Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + fn eth_tx_for_test() -> SignedEthTx { // raw transaction bytes of https://etherscan.io/tx/0x0869be3e5d4456a29d488a533ad6c118620fef450f36778aecf31d356ff8b41f let tx_bytes = [ @@ -2138,15 +2153,13 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_swap_payment_errored_but_sent() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the swap ends up with MakerPaymentTransactionFailed error but the transaction is actually // sent, need to find it and refund // TODO remove TransactionDetails from json let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_lock":1563759539,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563743939,"taker":"101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743939211},{"event":{"data":{"taker_payment_locktime":1563751737,"taker_pubkey":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9"},"type":"Negotiated"},"timestamp":1563743979835},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeValidated"},"timestamp":1563744052878},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"MakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563763243350}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2182,13 +2195,11 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_payment_refund_errored() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the swap ends up with MakerPaymentRefundFailed error let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_lock":1563636475,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563620875,"taker":"14a96292bfcd7762ece8eb08ead915da927c2619277363853572f30880d5155e","taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875062},{"event":{"data":{"taker_payment_locktime":1563628675,"taker_pubkey":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91"},"type":"Negotiated"},"timestamp":1563620915497},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeValidated"},"timestamp":1563620976060},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentSent"},"timestamp":1563620976189},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentReceived"},"timestamp":1563621268320},{"event":{"type":"TakerPaymentWaitConfirmStarted"},"timestamp":1563621268321},{"event":{"type":"TakerPaymentValidatedAndConfirmed"},"timestamp":1563621778471},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentSpendFailed"},"timestamp":1563638060583},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"MakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563621778483}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2218,13 +2229,11 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_payment_refund_errored_already_refunded() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the swap ends up with MakerPaymentRefundFailed error let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_lock":1563636475,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563620875,"taker":"14a96292bfcd7762ece8eb08ead915da927c2619277363853572f30880d5155e","taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875062},{"event":{"data":{"taker_payment_locktime":1563628675,"taker_pubkey":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91"},"type":"Negotiated"},"timestamp":1563620915497},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeValidated"},"timestamp":1563620976060},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentSent"},"timestamp":1563620976189},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentReceived"},"timestamp":1563621268320},{"event":{"type":"TakerPaymentWaitConfirmStarted"},"timestamp":1563621268321},{"event":{"type":"TakerPaymentValidatedAndConfirmed"},"timestamp":1563621778471},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentSpendFailed"},"timestamp":1563638060583},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"MakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563621778483}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2242,13 +2251,11 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_payment_refund_errored_already_spent() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the swap ends up with MakerPaymentRefundFailed error let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_lock":1563636475,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563620875,"taker":"14a96292bfcd7762ece8eb08ead915da927c2619277363853572f30880d5155e","taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875062},{"event":{"data":{"taker_payment_locktime":1563628675,"taker_pubkey":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91"},"type":"Negotiated"},"timestamp":1563620915497},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeValidated"},"timestamp":1563620976060},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentSent"},"timestamp":1563620976189},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentReceived"},"timestamp":1563621268320},{"event":{"type":"TakerPaymentWaitConfirmStarted"},"timestamp":1563621268321},{"event":{"type":"TakerPaymentValidatedAndConfirmed"},"timestamp":1563621778471},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentSpendFailed"},"timestamp":1563638060583},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"MakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563621778483}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2280,14 +2287,12 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_swap_payment_errored_but_too_early_to_refund() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the swap ends up with MakerPaymentTransactionFailed error but the transaction is actually // sent, need to find it and refund, prevent refund if payment is not spendable due to locktime restrictions let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_lock":1563759539,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563743939,"taker":"101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743939211},{"event":{"data":{"taker_payment_locktime":1563751737,"taker_pubkey":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9"},"type":"Negotiated"},"timestamp":1563743979835},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeValidated"},"timestamp":1563744052878},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"MakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563763243350}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2311,14 +2316,12 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_swap_payment_errored_and_not_sent() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the swap ends up with MakerPaymentTransactionFailed error and transaction is not sent, // recover must return error in this case let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_lock":1563759539,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563743939,"taker":"101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743939211},{"event":{"data":{"taker_payment_locktime":1563751737,"taker_pubkey":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9"},"type":"Negotiated"},"timestamp":1563743979835},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeValidated"},"timestamp":1563744052878},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"MakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563763243350}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2337,13 +2340,11 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_swap_not_finished() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // return error if swap is not finished let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_lock":1563759539,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563743939,"taker":"101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743939211},{"event":{"data":{"taker_payment_locktime":1563751737,"taker_pubkey":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9"},"type":"Negotiated"},"timestamp":1563743979835},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeValidated"},"timestamp":1563744052878}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2355,13 +2356,11 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_swap_taker_payment_spent() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // return error if taker payment was spent by us let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"1","maker_coin":"BEER","maker_coin_start_block":154892,"maker_payment_confirmations":1,"maker_payment_lock":1563444026,"my_persistent_pub":"02631dcf1d4b1b693aa8c2751afc68e4794b1e5996566cfc701a663f8b7bbbe640","secret":"e1c9bd12a83f810813dc078ac398069b63d56bf1e94657def995c43cd1975302","started_at":1563428426,"taker":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","taker_amount":"1","taker_coin":"ETOMIC","taker_coin_start_block":150282,"taker_payment_confirmations":1,"uuid":"983ce732-62a8-4a44-b4ac-7e4271adc977"},"type":"Started"},"timestamp":1563428426510},{"event":{"data":{"taker_payment_locktime":1563436226,"taker_pubkey":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3"},"type":"Negotiated"},"timestamp":1563428466880},{"event":{"data":{"tx_hash":"32f5bec2106dd3778dc32e3d856398ed0fa10b71c688672906a4fa0345cc4135","tx_hex":"0400008085202f89015ba9c8f0aec5b409bc824bcddc1a5a40148d4bd065c10169249e44ec44d62db2010000006a473044022050a213db7486e34871b9e7ef850845d55e0d53431350c16fa14fb60b81b1858302204f1042761f84e5f8d22948358b3c4103861adf5293d1d9e7f58f3b7491470b19012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff02bcf60100000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac764d12ac010000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac8806305d000000000000000000000000000000"},"type":"TakerFeeValidated"},"timestamp":1563428507723},{"event":{"data":{"tx_hash":"1619d10a51925d2f3d0ef92d81cb6449b77d5dbe1f3ef5e7ae6c8bc19080cb5a","tx_hex":"0400008085202f890176ead03820bc0c4e92dba39b5d7e7a1e176b165f6cfc7a5e2c000ed62e8a8134010000006b48304502210086ca9a6ea5e787f4c3001c4ddb7b2f4732d8bb2642e9e43d0f39df4b736a4aa402206dbd17753f728d70c9631b6c2d1bba125745a5bc9be6112febf0e0c8ada786b1012102631dcf1d4b1b693aa8c2751afc68e4794b1e5996566cfc701a663f8b7bbbe640ffffffff0200e1f5050000000017a91410503cfea67f03f025c5e1eeb18524464adf77ee877f360c18c00000001976a91464ae8510aac9546d5e7704e31ce177451386455588ac9b06305d000000000000000000000000000000"},"type":"MakerPaymentSent"},"timestamp":1563428512925},{"event":{"data":{"tx_hash":"ee8b904efdee0d3bf0215d14a236489cde0b0efa92f7fa49faaa5fd97ed38ac0","tx_hex":"0400008085202f89013541cc4503faa406296788c6710ba10fed9863853d2ec38d77d36d10c2bef532010000006b483045022100a32e290d3a047ad75a512f9fd581c561c5153aa1b6be2b36915a9dd452cd0d4102204d1838b3cd15698ab424d15651d50983f0196e59b0b34abaad9cb792c97b527a012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0200e1f5050000000017a91424fc6f967eaa2751adbeb42a97c3497fbd9ddcce878e681ca6010000001976a91405aab5342166f8594baf17a7d9bef5d56744332788acbf06305d000000000000000000000000000000"},"type":"TakerPaymentReceived"},"timestamp":1563428664418},{"event":{"type":"TakerPaymentWaitConfirmStarted"},"timestamp":1563428664420},{"event":{"type":"TakerPaymentValidatedAndConfirmed"},"timestamp":1563428664824},{"event":{"data":{"tx_hash":"8b48d7452a2a1c6b1128aa83ab946e5a624037c5327b527b18c3dcadb404f139","tx_hex":"0400008085202f8901c08ad37ed95faafa49faf792fa0e0bde9c4836a2145d21f03b0deefd4e908bee00000000d747304402206ac1f2b5b856b86585b4d2147309e3a7ef9dd4c35ffd85a49c409a4acd11602902204be03e2114888fae460eaf99675bae0c834ff80be8531a5bd30ee14baf0a52e30120e1c9bd12a83f810813dc078ac398069b63d56bf1e94657def995c43cd1975302004c6b6304c224305db1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9143501575fb9a12a689bb94adad33cc78c13b0688c882102631dcf1d4b1b693aa8c2751afc68e4794b1e5996566cfc701a663f8b7bbbe640ac68ffffffff0118ddf505000000001976a91464ae8510aac9546d5e7704e31ce177451386455588ac28f92f5d000000000000000000000000000000"},"type":"TakerPaymentSpent"},"timestamp":1563428666150},{"event":{"type":"Finished"},"timestamp":1563428666152}],"my_info":{"my_amount":"1","my_coin":"BEER","other_amount":"1","other_coin":"ETOMIC","started_at":1563428426},"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"type":"Maker","uuid":"983ce732-62a8-4a44-b4ac-7e4271adc977"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2394,13 +2393,11 @@ mod maker_swap_tests { #[test] fn test_recover_funds_maker_swap_maker_payment_refunded() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // return error if maker payment was refunded let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"9.38455187130897","maker_coin":"VRSC","maker_coin_start_block":604407,"maker_payment_confirmations":1,"maker_payment_lock":1564317372,"my_persistent_pub":"03c2e08e48e6541b3265ccd430c5ecec7efc7d0d9fc4e310a9b052f9642673fb0a","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1564301772,"taker":"39c4bcdb1e6bbb29a3b131c2b82eba2552f4f8a804021b2064114ab857f00848","taker_amount":"0.999999999999999880468812552729","taker_coin":"KMD","taker_coin_start_block":1462209,"taker_payment_confirmations":1,"uuid":"8f5b267a-efa8-49d6-a92d-ec0523cca891"},"type":"Started"},"timestamp":1564301773193},{"event":{"data":{"taker_payment_locktime":1564309572,"taker_pubkey":"0339c4bcdb1e6bbb29a3b131c2b82eba2552f4f8a804021b2064114ab857f00848"},"type":"Negotiated"},"timestamp":1564301813664},{"event":{"data":{"tx_hash":"cf54a5f5dfdf2eb404855eaba6a05b41f893a20327d43770c0138bb9ed2cf9eb","tx_hex":"0400008085202f89018f03a4d46831ec541279d01998be6092a98ee0f103b69ab84697cdc3eea7e93c000000006a473044022046eb76ecf610832ef063a6d210b5d07bc90fd0f3b68550fd2945ce86b317252a02202d3438d2e83df49f1c8ab741553af65a0d97e6edccbb6c4d0c769b05426c637001210339c4bcdb1e6bbb29a3b131c2b82eba2552f4f8a804021b2064114ab857f00848ffffffff0276c40100000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88acddf7bd54000000001976a9144df806990ae0197402aeaa6d9b1ec60078d9eadf88ac01573d5d000000000000000000000000000000"},"type":"TakerFeeValidated"},"timestamp":1564301864738},{"event":{"data":{"tx_hash":"2252c9929707995aff6dbb03d23b7e7eb786611d26b6ae748ca13007e71d1de6","tx_hex":"0400008085202f8901f63aed15c53b794df1a9446755f452e9fd9db250e1f608636f6172b7d795358c010000006b483045022100b5adb583fbb4b1a628b9c58ec292bb7b1319bb881c2cf018af6fe33b7a182854022020d89a2d6cbf15a117e2e1122046941f95466af7507883c4fa05955f0dfb81f2012103c2e08e48e6541b3265ccd430c5ecec7efc7d0d9fc4e310a9b052f9642673fb0affffffff0293b0ef370000000017a914ca41def369fc07d8aea10ba26cf3e64a12470d4087163149f61c0000001976a914f4f89313803d610fa472a5849d2389ca6df3b90088ac285a3d5d000000000000000000000000000000"},"type":"MakerPaymentSent"},"timestamp":1564301867675},{"event":{"data":{"error":"timeout (2690.6 > 2690.0)"},"type":"TakerPaymentValidateFailed"},"timestamp":1564304558269},{"event":{"data":{"tx_hash":"96d0b50bc2371ab88052bc4d656f1b91b3e3e64eba650eac28ebce9387d234cb","tx_hex":"0400008085202f8901e61d1de70730a18c74aeb6261d6186b77e7e3bd203bb6dff5a99079792c9522200000000b647304402207d36206295eee6c936d0204552cc5a001d4de4bbc0c5ae1c6218cf8548b4f08b02204c2a6470e06a6caf407ea8f2704fdc1b1dee39f89d145f8c0460130cb1875b2b01514c6b6304bc963d5db1752103c2e08e48e6541b3265ccd430c5ecec7efc7d0d9fc4e310a9b052f9642673fb0aac6782012088a9145f5598259da7c0c0beffcc3e9da35e553bac727388210339c4bcdb1e6bbb29a3b131c2b82eba2552f4f8a804021b2064114ab857f00848ac68feffffff01abacef37000000001976a914f4f89313803d610fa472a5849d2389ca6df3b90088ac26973d5d000000000000000000000000000000"},"type":"MakerPaymentRefunded"},"timestamp":1564321080407},{"event":{"type":"Finished"},"timestamp":1564321080409}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"8f5b267a-efa8-49d6-a92d-ec0523cca891"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2413,14 +2410,12 @@ mod maker_swap_tests { #[test] /// https://github.com/KomodoPlatform/atomicDEX-API/issues/774 fn test_recover_funds_my_payment_spent_other_not() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // The swap ends up with TakerPaymentSpendConfirmFailed error because the TakerPaymentSpend transaction was in mempool long time and finally not mined. // sent, need to find it and refund, prevent refund if payment is not spendable due to locktime restrictions let maker_saved_json = r#"{"uuid":"7f95db1d-2ea5-4cce-b056-400e8b288042","events":[{"timestamp":1607887364672,"event":{"type":"Started","data":{"taker_coin":"KMD","maker_coin":"EMC2","taker":"ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900d","secret":"0000000000000000000000000000000000000000000000000000000000000000","secret_hash":"4a40a42a7d7192e5cbeaa3871f734612acfeaf76","my_persistent_pub":"03005e349c71a17334a3d7b712ebeb593c692e2401e611bbd829b6948c3acc15e5","lock_duration":31200,"maker_amount":"24.69126200952912480678056188200573124484152642245967528226624133800833910867311611640128371388479323","taker_amount":"3.094308955034189920785740015052958239603540091262646506373605364479205057098914911707408875024042288","maker_payment_confirmations":1,"maker_payment_requires_nota":true,"taker_payment_confirmations":2,"taker_payment_requires_nota":true,"maker_payment_lock":1607949764,"uuid":"7f95db1d-2ea5-4cce-b056-400e8b288042","started_at":1607887364,"maker_coin_start_block":3526364,"taker_coin_start_block":2178701}}},{"timestamp":1607887366684,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1607918567,"taker_pubkey":"03ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900d"}}},{"timestamp":1607887367745,"event":{"type":"TakerFeeValidated","data":{"tx_hex":"0400008085202f8902f02f23931783009e01b7f250234eb7b3a96bd7e7e16dd61f21988bbc7600b6f7020000006b483045022100d4610ef1f147417476877aa09b1f110f7e6773355d6dc8cae7af429707f9da4d02203f5d1890da9d6efffee55869761f9353dbd3bcafb2a560f4564eba658ae2807b012103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dffffffffb7599816287b72f939b8e6b59fe4706d7b6826e5f6c04db18e3689cb76e846ce000000006a473044022021a486a9920ff8b3d892c00c10abaf58b4509fe9d3a6f8320e198e312e220db402203fab57d7ccfda1eded606ab3de6a32f626b5904ecf9f41e4a7a4800376952d67012103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dffffffff020e780500000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac5bdb0c00000000001976a914851bf1c11fb48beecc0a0e50982b9d43357743e688ac0c62d65f000000000000000000000000000000","tx_hash":"8a7c0ddbc2a0e94e1f58c920780563eb71266d932fe9435cf66def905db91efb"}}},{"timestamp":1607887367887,"event":{"type":"MakerPaymentSent","data":{"tx_hex":"010000000118c570eab3ec0f07a33640aff42a7b3565f4fa72561473b9f55d23b7a5360351090000006a4730440220047c5a917e7ac72b55357c657581ebf60b7281466be3dd00a92f68b85e03ae2e02204b07643faf3ae88a1775794c5afc406be4aaec87d0a98fb49a2125e02b9ed21d012103005e349c71a17334a3d7b712ebeb593c692e2401e611bbd829b6948c3acc15e5ffffffff0338e02b930000000017a914e0ddc80814f50249d097c3242e355a5d6fae462b870000000000000000166a144a40a42a7d7192e5cbeaa3871f734612acfeaf76284bcb93000000001976a914b86cb58669cc65e2f880e1df5d6e11c3dcb7230988ac076ad65f","tx_hash":"aa06c1647cb0418ed6ca7666dc517bfe8de92bc163b6765f9370bb316af1c1ff"}}},{"timestamp":1607887416097,"event":{"type":"TakerPaymentReceived","data":{"tx_hex":"0400008085202f8903fb1eb95d90ef6df65c43e92f936d2671eb63057820c9581f4ee9a0c2db0d7c8a010000006b483045022100d55c02f8536f0c1e5f10833b901adc3d2a77d7f0701371a29dcc155426b8f280022028f540607c349f9a73489801c45445f594f2552d165b2bd004af00c0297ce494012103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dffffffffe83e674cb46d0862cbc3ade7e363c5eb3a73a9ad977fff60544093efc6a2682b000000006a47304402200a324d95e7e7193a479aed48c608068f6d81ee8d7dd9b13735427e18f53bb25b0220606e075b56c675f10f6cb3332e4a28011d88428f54e3f09b016deec16c1ac41b012103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dffffffff987b47469a16aac4adaf0a47d8fbf813f8b20cf28eef82e14b74d732a263584e000000006a473044022062badf692bea3f13b5adb5cd66ff87f8f3224624762a75caaffa6fd856a0cfa602204d2477941cb781bba824d20e562f2d52a85c6c1103dccf28ddbfcadead87925b012103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dffffffff036f8a71120000000017a91415d9f7c7ad4e88b91d92c1480902fedfea92b981870000000000000000166a144a40a42a7d7192e5cbeaa3871f734612acfeaf76967e1500000000001976a914851bf1c11fb48beecc0a0e50982b9d43357743e688ac3c62d65f000000000000000000000000000000","tx_hash":"3e01651b399901192067e24f60371f640e840d240956676a416d26dec6f051f4"}}},{"timestamp":1607887416115,"event":{"type":"TakerPaymentWaitConfirmStarted"}},{"timestamp":1607901459795,"event":{"type":"TakerPaymentValidatedAndConfirmed"}},{"timestamp":1607901459843,"event":{"type":"TakerPaymentSpent","data":{"tx_hex":"0400008085202f8901f451f0c6de266d416a675609240d840e641f37604fe26720190199391b65013e00000000d8483045022100f8a8dade217e2595d3aaa287adc6cbf895b3d9c13f28aa707873943c1412c36d022053efe8c35fefbab2b298e0e4e8b93ad05b1d98d872b656616520dee15d79ea6801202e3d520b3d396cd2fc4aaac03257d13b2c82772ffe4479b7e0841987f8f673a7004c6b6304e7e3d65fb1752103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dac6782012088a9144a40a42a7d7192e5cbeaa3871f734612acfeaf76882103005e349c71a17334a3d7b712ebeb593c692e2401e611bbd829b6948c3acc15e5ac68ffffffff0187867112000000001976a914b86cb58669cc65e2f880e1df5d6e11c3dcb7230988ac1599d65f000000000000000000000000000000","tx_hash":"21cb40785f3e768c38c502be378448f33634430277360dc1c20fcdc238ebf806"}}},{"timestamp":1607901459850,"event":{"type":"TakerPaymentSpendConfirmStarted"}},{"timestamp":1607957899314,"event":{"type":"TakerPaymentSpendConfirmFailed","data":{"error":"maker_swap:714] !wait for taker payment spend confirmations: rpc_clients:123] Waited too long until 1607953464 for transaction Transaction { version: 4, n_time: None, overwintered: true, version_group_id: 2301567109, inputs: [TransactionInput { previous_output: OutPoint { hash: f451f0c6de266d416a675609240d840e641f37604fe26720190199391b65013e, index: 0 }, script_sig: 483045022100f8a8dade217e2595d3aaa287adc6cbf895b3d9c13f28aa707873943c1412c36d022053efe8c35fefbab2b298e0e4e8b93ad05b1d98d872b656616520dee15d79ea6801202e3d520b3d396cd2fc4aaac03257d13b2c82772ffe4479b7e0841987f8f673a7004c6b6304e7e3d65fb1752103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dac6782012088a9144a40a42a7d7192e5cbeaa3871f734612acfeaf76882103005e349c71a17334a3d7b712ebeb593c692e2401e611bbd829b6948c3acc15e5ac68, sequence: 4294967295, script_witness: [] }], outputs: [TransactionOutput { value: 309429895, script_pubkey: 76a914b86cb58669cc65e2f880e1df5d6e11c3dcb7230988ac }], lock_time: 1607899413, expiry_height: 0, shielded_spends: [], shielded_outputs: [], join_splits: [], value_balance: 0, join_split_pubkey: 0000000000000000000000000000000000000000000000000000000000000000, join_split_sig: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, binding_sig: 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, zcash: true, str_d_zeel: None } to be confirmed 1 times"}}},{"timestamp":1607957899319,"event":{"type":"MakerPaymentWaitRefundStarted","data":{"wait_until":1607953464}}},{"timestamp":1607957899367,"event":{"type":"MakerPaymentRefundFailed","data":{"error":"maker_swap:746] !maker_coin.send_maker_refunds_payment: utxo_common:791] rpc_clients:1440] JsonRpcError { client_info: \"coin: EMC2\", request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"8\", method: \"blockchain.transaction.broadcast\", params: [String(\"0100000001ffc1f16a31bb70935f76b663c12be98dfe7b51dc6676cad68e41b07c64c106aa00000000b6473044022029d1626dde413ecb7af09c1609a0f1f3791539aef1a5f972787db39c6b8178f302202052b6c37f2334cbee21dc8c6ceeb368cc0bb69ab254da3a8b48a27d4542d84b01514c6b6304c45dd75fb1752103005e349c71a17334a3d7b712ebeb593c692e2401e611bbd829b6948c3acc15e5ac6782012088a9144a40a42a7d7192e5cbeaa3871f734612acfeaf76882103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dac68feffffff0198592a93000000001976a914b86cb58669cc65e2f880e1df5d6e11c3dcb7230988ac7b6fd75f\")] }, error: Response(electrum2.cipig.net:10062, Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\n18: bad-txns-inputs-spent\\n[0100000001ffc1f16a31bb70935f76b663c12be98dfe7b51dc6676cad68e41b07c64c106aa00000000b6473044022029d1626dde413ecb7af09c1609a0f1f3791539aef1a5f972787db39c6b8178f302202052b6c37f2334cbee21dc8c6ceeb368cc0bb69ab254da3a8b48a27d4542d84b01514c6b6304c45dd75fb1752103005e349c71a17334a3d7b712ebeb593c692e2401e611bbd829b6948c3acc15e5ac6782012088a9144a40a42a7d7192e5cbeaa3871f734612acfeaf76882103ae3cc37d2a7cc9077fb5b1baa962d1539e1ffe5fb318c99dcba43059ed97900dac68feffffff0198592a93000000001976a914b86cb58669cc65e2f880e1df5d6e11c3dcb7230988ac7b6fd75f]\")})) }"}}},{"timestamp":1607957899372,"event":{"type":"Finished"}}],"maker_amount":"24.69126200952912480678056188200573124484152642245967528226624133800833910867311611640128371388479323","maker_coin":"EMC2","taker_amount":"3.094308955034189920785740015052958239603540091262646506373605364479205057098914911707408875024042288","taker_coin":"KMD","gui":"AtomicDex Desktop 0.3.1-beta","mm_version":"2.1.2793_mm2.1_19701cc87_Windows_NT_Release","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2461,12 +2456,10 @@ mod maker_swap_tests { #[test] fn test_recover_funds_should_not_refund_on_the_successful_swap() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let maker_saved_json = r#"{"type":"Maker","uuid":"12456076-58dd-4772-9d88-167d5fa103d2","my_order_uuid":"5ae22bf5-09cf-4828-87a7-c3aa7339ba10","events":[{"timestamp":1631695364907,"event":{"type":"Started","data":{"taker_coin":"KMD","maker_coin":"TKL","taker":"2b20b92e19e9e11b07f8309cebb1fcd1cce1606be8ab0de2c1b91f979c937996","secret":"0000000000000000000000000000000000000000000000000000000000000000","secret_hash":"65a10bd6dbdf6ebf7ec1f3bfb7451cde0582f9cb","my_persistent_pub":"03789c206e830f9e0083571f79e80eb58601d37bde8abb0c380d81127613060b74","lock_duration":31200,"maker_amount":"500","taker_amount":"140.7","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":2,"taker_payment_requires_nota":true,"maker_payment_lock":1631757764,"uuid":"12456076-58dd-4772-9d88-167d5fa103d2","started_at":1631695364,"maker_coin_start_block":61066,"taker_coin_start_block":2569118,"maker_payment_trade_fee":{"coin":"TKL","amount":"0.00001","paid_from_trading_vol":false},"taker_payment_spend_trade_fee":{"coin":"KMD","amount":"0.00001","paid_from_trading_vol":true}}}},{"timestamp":1631695366908,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1631726564,"taker_pubkey":"032b20b92e19e9e11b07f8309cebb1fcd1cce1606be8ab0de2c1b91f979c937996","maker_coin_swap_contract_addr":null,"taker_coin_swap_contract_addr":null}}},{"timestamp":1631695367917,"event":{"type":"TakerFeeValidated","data":{"tx_hex":"0400008085202f8901562fdec6bbdac4c5c3212394e1fd439d3647ff04bdd79d51b9bbf697c9a925e7000000006a473044022074c71fcdc12654e3aa01c780b10d6c84b1d6ba28f0db476010002a1ed00e75cf022018e115923b1c1b5e872893fd6a1f270c0e8e3e84a869181c349aa78553e1423b0121032b20b92e19e9e11b07f8309cebb1fcd1cce1606be8ab0de2c1b91f979c937996ffffffff0251adf800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac72731e4c080000001976a914dc1bea5367613f189da622e9bc5bdb2d61667e5b88ac08aa4161000000000000000000000000000000","tx_hash":"f315170aba20ff4d432b8a2d0a8fa0211444c8d27b56fc0d4fc2058e9f3c6e08"}}},{"timestamp":1631695368024,"event":{"type":"MakerPaymentSent","data":{"tx_hex":"0400008085202f8901bc488c4e0f9a3fe9d7f5dbcc17f61e7711a75c7ed277843988f3be4d236b9a02020000006a473044022027ac57a4a34b0d8561afc1ad63f9e1fb271d58577a80f26ba519017d65d882f802200b5617f32427b86b423de6740cd134fdb8b86c511943e778c65781573224cf4a012103789c206e830f9e0083571f79e80eb58601d37bde8abb0c380d81127613060b74ffffffff0300743ba40b00000017a914022be92579878d04c80d128cdfdcba4ed29a9f9a870000000000000000166a1465a10bd6dbdf6ebf7ec1f3bfb7451cde0582f9cb6494f93503c801001976a914bde146a76acf122caf5e460d01ddaf3be714247e88ac07b24161000000000000000000000000000000","tx_hash":"8693723462ef5ee6c3014230fd4a4aefe6bcd0eaeb727e1e5b33fe1105e9f8ad"}}},{"timestamp":1631696319310,"event":{"type":"TakerPaymentReceived","data":{"tx_hex":"0400008085202f8901086e3c9f8e05c24f0dfc567bd2c8441421a08f0a2d8a2b434dff20ba0a1715f3010000006a47304402207190691940b4834394c2a9e08a32b775f1c62a47ab76737c96c08e2937173988022040229094d51acb3d948413c349e36795b888a9b425b29ed7a96ed8eb97407d050121032b20b92e19e9e11b07f8309cebb1fcd1cce1606be8ab0de2c1b91f979c937996ffffffff038029a3460300000017a9146d0db00d111fcd0b83505cb805a3255cbaa8c747870000000000000000166a1465a10bd6dbdf6ebf7ec1f3bfb7451cde0582f9cb0a467b05050000001976a914dc1bea5367613f189da622e9bc5bdb2d61667e5b88acbfad4161000000000000000000000000000000","tx_hash":"a9b97c4c12c8eb637a7016459de644eae9e307efd2d051601d7d9f615fd62461"}}},{"timestamp":1631696319310,"event":{"type":"TakerPaymentWaitConfirmStarted"}},{"timestamp":1631697459816,"event":{"type":"TakerPaymentValidatedAndConfirmed"}},{"timestamp":1631697459821,"event":{"type":"TakerPaymentSpent","data":{"tx_hex":"0400008085202f89016124d65f619f7d1d6051d0d2ef07e3e9ea44e69d4516707a63ebc8124c7cb9a900000000d84830450221008c50c144382346247d7052a32e12f4d839fa22c12064b199d589cc62ead00c99022017e88f543e181fd92ebf32e1313ca6fb12f93226fd294c808b0904601102424f012068e659c506d57d94369ca520158d641ea997b0db39fdafb1e59b07867ad4be9d004c6b6304e42b4261b17521032b20b92e19e9e11b07f8309cebb1fcd1cce1606be8ab0de2c1b91f979c937996ac6782012088a91465a10bd6dbdf6ebf7ec1f3bfb7451cde0582f9cb882103789c206e830f9e0083571f79e80eb58601d37bde8abb0c380d81127613060b74ac68ffffffff019825a346030000001976a914bde146a76acf122caf5e460d01ddaf3be714247e88ace42b4261000000000000000000000000000000","tx_hash":"8a6d65518d3a01f6f659f11e0667373052ebfc2e600f80c6592dec556bee4a39"}}},{"timestamp":1631697459822,"event":{"type":"TakerPaymentSpendConfirmStarted"}},{"timestamp":1631697489840,"event":{"type":"TakerPaymentSpendConfirmed"}},{"timestamp":1631697489841,"event":{"type":"Finished"}}],"maker_amount":"500","maker_coin":"TKL","taker_amount":"140.7","taker_coin":"KMD","gui":"TOKEL-IDO","mm_version":"41170748d","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2505,12 +2498,10 @@ mod maker_swap_tests { fn swap_must_not_lock_funds_by_default() { use crate::mm2::lp_swap::get_locked_amount; + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_lock":1563759539,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563743939,"taker":"101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743939211},{"event":{"data":{"taker_payment_locktime":1563751737,"taker_pubkey":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9"},"type":"Negotiated"},"timestamp":1563743979835},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeValidated"},"timestamp":1563744052878}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2525,13 +2516,11 @@ mod maker_swap_tests { #[test] fn test_recheck_swap_contract_address_if_none() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // swap file contains neither maker_coin_swap_contract_address nor taker_coin_swap_contract_address let maker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","TakerPaymentValidateFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentRefunded","MakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_lock":1563759539,"my_persistent_pub":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret":"0000000000000000000000000000000000000000000000000000000000000000","started_at":1563743939,"taker":"101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743939211},{"event":{"data":{"taker_payment_locktime":1563751737,"taker_pubkey":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9"},"type":"Negotiated"},"timestamp":1563743979835},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeValidated"},"timestamp":1563744052878}],"success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); static mut SWAP_CONTRACT_ADDRESS_CALLED: usize = 0; @@ -2556,13 +2545,11 @@ mod maker_swap_tests { #[test] fn test_recheck_only_one_swap_contract_address() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // swap file contains only maker_coin_swap_contract_address let maker_saved_json = r#"{"type":"Maker","uuid":"c52659d7-4e13-41f5-9c1a-30cc2f646033","events":[{"timestamp":1608541830095,"event":{"type":"Started","data":{"taker_coin":"JST","maker_coin":"ETH","taker":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","secret":"dc45c1d22028970d8d30d1ddacbfc50eb92403b0d6076c94f2216c4c44512b41","secret_hash":"943e11f7c74e2d6493ef8ad01a06ef2ce9bd1fb3","my_persistent_pub":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"maker_payment_lock":1608557429,"uuid":"c52659d7-4e13-41f5-9c1a-30cc2f646033","started_at":1608541829,"maker_coin_start_block":14353,"taker_coin_start_block":14353,"maker_coin_swap_contract_address":"a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd"}}},{"timestamp":1608541830399,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1608549629,"taker_pubkey":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3"}}},{"timestamp":1608541831810,"event":{"type":"TakerFeeValidated","data":{"tx_hex":"f8a7821fb58083033450942b294f029fde858b2c62184e8390591755521d8e80b844a9059cbb000000000000000000000000d8997941dd1346e9231118d5685d866294f59e5b0000000000000000000000000000000000000000000000000000750d557426e01ba06ddad2dfe6933b8d70d5739beb3005c8f367bc72eac4e5609b81c2f8e5843cd9a07fa695cc42f8c6b6a7b10f6ae9e4dca3e750e37f64a85b54dec736236790f05e","tx_hash":"b13c3428f70b46d8c1d7f5863af020a27c380a8ede0927554beabf234998bcc8"}}},{"timestamp":1608541832884,"event":{"type":"MakerPaymentSent","data":{"tx_hex":"f8ef82021980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af7c7ce37fac65bd995eae3d58ccdc367d79f3a10e6ca55f609e6dcefac960982b000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa29943e11f7c74e2d6493ef8ad01a06ef2ce9bd1fb3000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a3751ca03ab6306b8b8875c7d2cbaa71a3991eb8e7ae44e192dc9974cecc1f9dcfe5e4d6a04ec2808db06fe7b246134997fcce81ca201ced1257f1f8e93cacadd6554ca653","tx_hash":"ceba36dff0b2c7aec69cb2d5be7055858e09889959ba63f7957b45a15dceade4"}}},{"timestamp":1608541835207,"event":{"type":"TakerPaymentReceived","data":{"tx_hex":"f90127821fb680830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8c49b415b2a64bdf61f195a1767f547bb0886ed697f3c1a063ce928ff9a47222c0b5d099200000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000002b294f029fde858b2c62184e8390591755521d8e0000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e9943e11f7c74e2d6493ef8ad01a06ef2ce9bd1fb3000000000000000000000000000000000000000000000000000000000000000000000000000000005fe084fd1ba0a5b6ef54217c5a03a588d01410ef1187ce6107bdb075306ced06a06e25a50984a03f541f1f392079ae2590d0f48f2065f8721a8b46c44a060ae53f00bfb5160118","tx_hash":"1247a1be3da89f3612ca33d83d493808388775e2897036f640c0efe69c3b162f"}}},{"timestamp":1608541835208,"event":{"type":"TakerPaymentWaitConfirmStarted"}},{"timestamp":1608541836196,"event":{"type":"TakerPaymentValidatedAndConfirmed"}},{"timestamp":1608541837173,"event":{"type":"TakerPaymentSpent","data":{"tx_hex":"f9010782021a80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b64bdf61f195a1767f547bb0886ed697f3c1a063ce928ff9a47222c0b5d099200000000000000000000000000000000000000000000000000016345785d8a0000dc45c1d22028970d8d30d1ddacbfc50eb92403b0d6076c94f2216c4c44512b410000000000000000000000002b294f029fde858b2c62184e8390591755521d8e000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa291ba053af89feb4ab066b26e76de9788c85ec1bf14ae6dcbdd7ff53e561e48e1b822ca043796d45bd4233500a120a1571b3fee95a34e8cc6b616c69552da4352c0d8e39","tx_hash":"d9a839c6eead3fbf538eca0a4ec39e28647104920a5c8b9c107524287dd90165"}}},{"timestamp":1608541837175,"event":{"type":"TakerPaymentSpendConfirmStarted"}},{"timestamp":1608541837612,"event":{"type":"TakerPaymentSpendConfirmed"}},{"timestamp":1608541837614,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"JST","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","TakerPaymentSpendConfirmStarted","TakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","TakerPaymentSpendConfirmFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; let maker_saved_swap: MakerSavedSwap = json::from_str(maker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); static mut SWAP_CONTRACT_ADDRESS_CALLED: usize = 0; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 3a5876f79e..df4cd8e2bf 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -23,7 +23,7 @@ use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; -use crypto::privkey::SerializableSecp256k1Keypair; +use crypto::{privkey::SerializableSecp256k1Keypair, CryptoCtx}; use futures::{compat::Future01CompatExt, future::try_join, select, FutureExt}; use http::Response; use keys::KeyPair; @@ -410,6 +410,12 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { event.clone().into(), ) } + + #[cfg(target_arch = "wasm32")] + if event.is_error() { + error!("[swap uuid={uuid}] {event:?}"); + } + status.status(&[&"swap", &("uuid", uuid.as_str())], &event.status_str()); running_swap.apply_event(event); } @@ -828,24 +834,28 @@ impl TakerSwap { taker_coin_swap_contract: Vec, ) -> NegotiationDataMsg { let r = self.r(); - if r.data.maker_coin_htlc_pubkey != r.data.taker_coin_htlc_pubkey { - NegotiationDataMsg::V3(NegotiationDataV3 { + + let equal = r.data.maker_coin_htlc_pubkey == r.data.taker_coin_htlc_pubkey; + let same_as_persistent = r.data.maker_coin_htlc_pubkey == Some(r.data.my_persistent_pub); + + if equal && same_as_persistent { + NegotiationDataMsg::V2(NegotiationDataV2 { started_at: r.data.started_at, - payment_locktime: r.data.taker_payment_lock, secret_hash, + payment_locktime: r.data.taker_payment_lock, + persistent_pubkey: self.my_persistent_pub.to_vec(), maker_coin_swap_contract, taker_coin_swap_contract, - maker_coin_htlc_pub: self.my_maker_coin_htlc_pub().into(), - taker_coin_htlc_pub: self.my_taker_coin_htlc_pub().into(), }) } else { - NegotiationDataMsg::V2(NegotiationDataV2 { + NegotiationDataMsg::V3(NegotiationDataV3 { started_at: r.data.started_at, - secret_hash, payment_locktime: r.data.taker_payment_lock, - persistent_pubkey: self.my_persistent_pub.to_vec(), + secret_hash, maker_coin_swap_contract, taker_coin_swap_contract, + maker_coin_htlc_pub: self.my_maker_coin_htlc_pub().into(), + taker_coin_htlc_pub: self.my_taker_coin_htlc_pub().into(), }) } } @@ -1711,9 +1721,11 @@ impl TakerSwap { data.taker_coin_swap_contract_address = taker_coin.swap_contract_address(); } + let crypto_ctx = try_s!(CryptoCtx::from_ctx(&ctx)); + let my_persistent_pub = H264::from(&**crypto_ctx.mm2_internal_key_pair().public()); + let mut maker = bits256::from([0; 32]); maker.bytes = data.maker.0; - let my_persistent_pub = H264::from(&**ctx.secp256k1_key_pair().public()); let conf_settings = SwapConfirmationsSettings { maker_coin_confs: data.maker_payment_confirmations, maker_coin_nota: data @@ -2188,7 +2200,7 @@ pub async fn taker_swap_trade_preimage( rel_confs: rel_coin.required_confirmations(), rel_nota: rel_coin.requires_notarization(), }; - let our_public_id = ctx.public_id().expect("!ctx.public_id()"); + let our_public_id = CryptoCtx::from_ctx(ctx)?.mm2_internal_public_id(); let order_builder = TakerOrderBuilder::new(&base_coin, &rel_coin) .with_base_amount(base_amount) .with_rel_amount(rel_amount) @@ -2359,10 +2371,12 @@ mod taker_swap_tests { use coins::utxo::UtxoTx; use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SwapOps, TestCoin}; use common::{block_on, new_uuid}; - use crypto::privkey::key_pair_from_seed; - use mm2_core::mm_ctx::MmCtxBuilder; + use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use mocktopus::mocking::*; + const PASSPHRASE: Option<&str> = + Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + fn eth_tx_for_test() -> SignedEthTx { // raw transaction bytes of https://etherscan.io/tx/0x0869be3e5d4456a29d488a533ad6c118620fef450f36778aecf31d356ff8b41f let tx_bytes = [ @@ -2382,12 +2396,10 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_maker_payment_spend_errored() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2414,12 +2426,10 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_not_spent() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2460,12 +2470,10 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_and_spent_by_maker() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2509,12 +2517,10 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2548,12 +2554,10 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent_too_early_to_refund() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2575,12 +2579,10 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_taker_payment_refund_failed_spent_by_maker() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2617,13 +2619,11 @@ mod taker_swap_tests { #[test] fn test_recover_funds_taker_swap_not_finished() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // the json doesn't have Finished event at the end let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); @@ -2650,13 +2650,11 @@ mod taker_swap_tests { #[test] fn test_recheck_swap_contract_address_if_none() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // swap file contains neither maker_coin_swap_contract_address nor taker_coin_swap_contract_address let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); static mut SWAP_CONTRACT_ADDRESS_CALLED: usize = 0; @@ -2681,13 +2679,11 @@ mod taker_swap_tests { #[test] fn test_recheck_only_one_swap_contract_address() { + let ctx = mm_ctx_with_iguana(PASSPHRASE); + // swap file contains only maker_coin_swap_contract_address let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); static mut SWAP_CONTRACT_ADDRESS_CALLED: usize = 0; @@ -2853,10 +2849,8 @@ mod taker_swap_tests { "error_events": [] }"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); - let key_pair = - key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") - .unwrap(); - let ctx = MmCtxBuilder::default().with_secp256k1_key_pair(key_pair).into_mm_arc(); + + let ctx = mm_ctx_with_iguana(PASSPHRASE); let maker_coin = MmCoinEnum::Test(TestCoin::new("RICK")); let taker_coin = MmCoinEnum::Test(TestCoin::new("MORTY")); diff --git a/mm2src/mm2_main/src/lp_swap/trade_preimage.rs b/mm2src/mm2_main/src/lp_swap/trade_preimage.rs index 56c6b374ca..c7ab265190 100644 --- a/mm2src/mm2_main/src/lp_swap/trade_preimage.rs +++ b/mm2src/mm2_main/src/lp_swap/trade_preimage.rs @@ -3,6 +3,7 @@ use super::{maker_swap_trade_preimage, taker_swap_trade_preimage, MakerTradePrei use crate::mm2::lp_ordermatch::{MakerOrderBuildError, TakerAction, TakerOrderBuildError}; use coins::{is_wallet_only_ticker, lp_coinfind_or_err, BalanceError, CoinFindError, TradeFee, TradePreimageError}; use common::HttpStatusCode; +use crypto::CryptoCtxError; use derive_more::Display; use http::StatusCode; use mm2_core::mm_ctx::MmArc; @@ -314,6 +315,10 @@ impl From for TradePreimageRpcError { } } +impl From for TradePreimageRpcError { + fn from(e: CryptoCtxError) -> Self { TradePreimageRpcError::InternalError(e.to_string()) } +} + impl TradePreimageRpcError { /// Construct [`TradePreimageRpcError`] from [`coins::TradePreimageError`] using the additional `ticker` argument. /// `ticker` is used to identify whether the `NotSufficientBalance` or `NotSufficientBaseCoinBalance` has occurred. diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 48d2cd0354..9b35c5bb47 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -6,9 +6,10 @@ use common::{block_on, executor::spawn}; use crypto::privkey::key_pair_from_seed; use db_common::sqlite::rusqlite::Connection; use futures::{channel::mpsc, StreamExt}; -use mm2_core::mm_ctx::{MmArc, MmCtx, MmCtxBuilder}; +use mm2_core::mm_ctx::{MmArc, MmCtx}; use mm2_libp2p::atomicdex_behaviour::AdexBehaviourCmd; use mm2_libp2p::{decode_message, PeerId}; +use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use mocktopus::mocking::*; use rand::{seq::SliceRandom, thread_rng, Rng}; use std::collections::HashSet; @@ -1039,9 +1040,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { #[test] fn test_cancel_by_single_coin() { - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair(key_pair_from_seed("123").unwrap()) - .into_mm_arc(); + let ctx = mm_ctx_with_iguana(None); let rx = prepare_for_cancel_by(&ctx); let connection = Connection::open_in_memory().unwrap(); @@ -1060,9 +1059,7 @@ fn test_cancel_by_single_coin() { #[test] fn test_cancel_by_pair() { - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair(key_pair_from_seed("123").unwrap()) - .into_mm_arc(); + let ctx = mm_ctx_with_iguana(None); let rx = prepare_for_cancel_by(&ctx); let connection = Connection::open_in_memory().unwrap(); @@ -1085,9 +1082,7 @@ fn test_cancel_by_pair() { #[test] fn test_cancel_by_all() { - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair(key_pair_from_seed("123").unwrap()) - .into_mm_arc(); + let ctx = mm_ctx_with_iguana(None); let rx = prepare_for_cancel_by(&ctx); let connection = Connection::open_in_memory().unwrap(); @@ -1203,13 +1198,13 @@ fn test_maker_order_was_updated() { #[test] fn lp_connect_start_bob_should_not_be_invoked_if_order_match_already_connected() { + let ctx = mm_ctx_with_iguana(Some( + "also shoot benefit prefer juice shell elder veteran woman mimic image kidney", + )); + let order_json = r#"{"max_base_vol":"1","max_base_vol_rat":[[1,[1]],[1,[1]]],"min_base_vol":"0","min_base_vol_rat":[[0,[]],[1,[1]]],"price":"1","price_rat":[[1,[1]],[1,[1]]],"created_at":1589265312093,"updated_at":1589265312093,"base":"ETH","rel":"JST","matches":{"2f9afe84-7a89-4194-8947-45fba563118f":{"request":{"base":"ETH","rel":"JST","base_amount":"0.1","base_amount_rat":[[1,[1]],[1,[10]]],"rel_amount":"0.2","rel_amount_rat":[[1,[1]],[1,[5]]],"action":"Buy","uuid":"2f9afe84-7a89-4194-8947-45fba563118f","method":"request","sender_pubkey":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","dest_pub_key":"0000000000000000000000000000000000000000000000000000000000000000","match_by":{"type":"Any"}},"reserved":{"base":"ETH","rel":"JST","base_amount":"0.1","base_amount_rat":[[1,[1]],[1,[10]]],"rel_amount":"0.1","rel_amount_rat":[[1,[1]],[1,[10]]],"taker_order_uuid":"2f9afe84-7a89-4194-8947-45fba563118f","maker_order_uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3","method":"reserved","sender_pubkey":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","dest_pub_key":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3"},"connect":{"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"},"connected":{"taker_order_uuid":"2f9afe84-7a89-4194-8947-45fba563118f","maker_order_uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3","method":"connected","sender_pubkey":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","dest_pub_key":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3"},"last_updated":1589265314408}},"started_swaps":["2f9afe84-7a89-4194-8947-45fba563118f"],"uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3"}"#; let maker_order: MakerOrder = json::from_str(order_json).unwrap(); - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair( - key_pair_from_seed("also shoot benefit prefer juice shell elder veteran woman mimic image kidney").unwrap(), - ) - .into_mm_arc(); + let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); ordermatch_ctx .maker_orders_ctx @@ -1230,14 +1225,13 @@ fn lp_connect_start_bob_should_not_be_invoked_if_order_match_already_connected() #[test] fn should_process_request_only_once() { + let ctx = mm_ctx_with_iguana(Some( + "also shoot benefit prefer juice shell elder veteran woman mimic image kidney", + )); + let order_json = r#"{"max_base_vol":"1","max_base_vol_rat":[[1,[1]],[1,[1]]],"min_base_vol":"0","min_base_vol_rat":[[0,[]],[1,[1]]],"price":"1","price_rat":[[1,[1]],[1,[1]]],"created_at":1589265312093,"updated_at":1589265312093,"base":"ETH","rel":"JST","matches":{"2f9afe84-7a89-4194-8947-45fba563118f":{"request":{"base":"ETH","rel":"JST","base_amount":"0.1","base_amount_rat":[[1,[1]],[1,[10]]],"rel_amount":"0.2","rel_amount_rat":[[1,[1]],[1,[5]]],"action":"Buy","uuid":"2f9afe84-7a89-4194-8947-45fba563118f","method":"request","sender_pubkey":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","dest_pub_key":"0000000000000000000000000000000000000000000000000000000000000000","match_by":{"type":"Any"}},"reserved":{"base":"ETH","rel":"JST","base_amount":"0.1","base_amount_rat":[[1,[1]],[1,[10]]],"rel_amount":"0.1","rel_amount_rat":[[1,[1]],[1,[10]]],"taker_order_uuid":"2f9afe84-7a89-4194-8947-45fba563118f","maker_order_uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3","method":"reserved","sender_pubkey":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","dest_pub_key":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3"},"connect":{"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"},"connected":{"taker_order_uuid":"2f9afe84-7a89-4194-8947-45fba563118f","maker_order_uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3","method":"connected","sender_pubkey":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","dest_pub_key":"031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3"},"last_updated":1589265314408}},"started_swaps":["2f9afe84-7a89-4194-8947-45fba563118f"],"uuid":"5f6516ea-ccaa-453a-9e37-e1c2c0d527e3"}"#; let maker_order: MakerOrder = json::from_str(order_json).unwrap(); let uuid = maker_order.uuid; - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair( - key_pair_from_seed("also shoot benefit prefer juice shell elder veteran woman mimic image kidney").unwrap(), - ) - .into_mm_arc(); let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); ordermatch_ctx .maker_orders_ctx @@ -1620,11 +1614,10 @@ fn test_choose_taker_confs_settings_sell_action() { fn make_ctx_for_tests() -> (MmArc, String, [u8; 32]) { let ctx = MmArc::new(MmCtx::default()); ctx.init_metrics().unwrap(); - ctx.secp256k1_key_pair - .pin(key_pair_from_seed("passphrase").unwrap()) - .unwrap(); - let secret = *(&*ctx.secp256k1_key_pair().private().secret); - let pubkey = hex::encode(&**ctx.secp256k1_key_pair().public()); + let crypto_ctx = CryptoCtx::init_with_iguana_passphrase(ctx.clone(), "passphrase").unwrap(); + + let secret = crypto_ctx.mm2_internal_privkey_secret().take(); + let pubkey = crypto_ctx.mm2_internal_pubkey_hex(); (ctx, pubkey, secret) } @@ -3173,9 +3166,7 @@ fn check_order_serde() { #[test] fn test_maker_order_balance_loops() { - let ctx = MmCtxBuilder::default() - .with_secp256k1_key_pair(key_pair_from_seed("123").unwrap()) - .into_mm_arc(); + let ctx = mm_ctx_with_iguana(None); let ordermatch_ctx = OrdermatchContext::from_ctx(&ctx).unwrap(); let mut maker_orders_ctx = ordermatch_ctx.maker_orders_ctx.lock(); diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs index 6cad4b4dee..f5a5a95063 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs @@ -1,5 +1,5 @@ use common::HttpStatusCode; -use crypto::{CryptoCtx, CryptoInitError}; +use crypto::{CryptoCtx, CryptoCtxError}; use derive_more::Display; use http::StatusCode; use mm2_core::mm_ctx::MmArc; @@ -15,8 +15,8 @@ pub enum GetPublicKeyError { Internal(String), } -impl From for GetPublicKeyError { - fn from(_: CryptoInitError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } +impl From for GetPublicKeyError { + fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } } #[derive(Serialize)] @@ -33,7 +33,7 @@ impl HttpStatusCode for GetPublicKeyError { } pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key = CryptoCtx::from_ctx(&ctx)?.secp256k1_pubkey().to_string(); + let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); Ok(GetPublicKeyResponse { public_key }) } diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index 0767f07ea1..13448d8445 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -5,7 +5,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_test_helpers::electrums::{morty_electrums, rick_electrums}; use mm2_test_helpers::for_tests::{check_recent_swaps, enable_electrum_json, morty_conf, rick_conf, start_swaps, test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, MarketMakerIt, - Mm2TestConf, MORTY, RICK}; + Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, MORTY, RICK}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::OrderbookResponse; use serde_json::json; @@ -83,56 +83,27 @@ async fn test_mm2_stops_immediately() { async fn test_qrc20_tx_history() { test_qrc20_history_impl(Some(wasm_start)).await } async fn trade_base_rel_electrum( + bob_priv_key_policy: Mm2InitPrivKeyPolicy, + alice_priv_key_policy: Mm2InitPrivKeyPolicy, pairs: &[(&'static str, &'static str)], maker_price: i32, taker_price: i32, volume: f64, ) { - let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); - let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); - let coins = json!([rick_conf(), morty_conf(),]); - let mut mm_bob = MarketMakerIt::start_async( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - Some(wasm_start), - ) - .await - .unwrap(); + let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(bob_priv_key_policy, &coins); + let mut mm_bob = MarketMakerIt::start_async(bob_conf.conf, bob_conf.rpc_password, Some(wasm_start)) + .await + .unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); Timer::sleep(1.).await; - let mut mm_alice = MarketMakerIt::start_async( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), - "rpcip": env::var ("ALICE_TRADE_IP") .ok(), - "passphrase": alice_passphrase, - "coins": coins, - "seednodes": [mm_bob.my_seed_addr()], - "rpc_password": "password", - "skip_startup_checks": true, - }), - "password".into(), - Some(wasm_start), - ) - .await - .unwrap(); + let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); + let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, Some(wasm_start)) + .await + .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); @@ -189,6 +160,8 @@ async fn trade_base_rel_electrum( #[wasm_bindgen_test] async fn trade_test_rick_and_morty() { + let bob_policy = Mm2InitPrivKeyPolicy::Iguana; + let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount(0); let pairs: &[_] = &[("RICK", "MORTY")]; - trade_base_rel_electrum(pairs, 1, 1, 0.0001).await; + trade_base_rel_electrum(bob_policy, alice_policy, pairs, 1, 1, 0.0001).await; } 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 ad10635611..7ecf542be5 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -11,8 +11,8 @@ use bitcrypto::{dhash160, ChecksumType}; use chain::TransactionOutput; use coins::eth::{eth_coin_from_conf_and_request, EthCoin}; use coins::qrc20::rpc_clients::for_tests::Qrc20NativeWalletOps; -use coins::qrc20::{qrc20_coin_from_conf_and_params, Qrc20ActivationParams, Qrc20Coin}; -use coins::utxo::bch::{bch_coin_from_conf_and_params, BchActivationRequest, BchCoin}; +use coins::qrc20::{qrc20_coin_with_priv_key, Qrc20ActivationParams, Qrc20Coin}; +use coins::utxo::bch::{bch_coin_with_priv_key, BchActivationRequest, BchCoin}; use coins::utxo::qtum::{qtum_coin_with_priv_key, QtumBasedCoin, QtumCoin}; use coins::utxo::rpc_clients::{NativeClient, UtxoRpcClientEnum, UtxoRpcClientOps}; use coins::utxo::slp::{slp_genesis_output, SlpOutput, SlpToken}; @@ -20,12 +20,13 @@ 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, Transaction}; -use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; +use coins::{CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy, Transaction}; +use crypto::privkey::key_pair_from_seed; +use crypto::Secp256k1Secret; use ethereum_types::H160 as H160Eth; use futures01::Future; use http::StatusCode; -use keys::{Address, AddressHashEnum, NetworkPrefix as CashAddrPrefix}; +use keys::{Address, AddressHashEnum, KeyPair, NetworkPrefix as CashAddrPrefix}; use mm2_core::mm_ctx::{MmArc, MmCtxBuilder}; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::enable_native; @@ -114,14 +115,11 @@ impl UtxoAssetDockerOps { pub fn from_ticker(ticker: &str) -> UtxoAssetDockerOps { let conf = json!({"asset": ticker, "txfee": 1000, "network": "regtest"}); let req = json!({"method":"enable"}); - let priv_key = hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(); + let priv_key = Secp256k1Secret::from("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f"); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(utxo_standard_coin_with_priv_key( - &ctx, ticker, &conf, ¶ms, &priv_key, - )) - .unwrap(); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, ticker, &conf, ¶ms, priv_key)).unwrap(); UtxoAssetDockerOps { ctx, coin } } } @@ -145,13 +143,14 @@ fn eth_distributor() -> EthCoin { }); let keypair = key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid").unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(keypair.private().secret); block_on(eth_coin_from_conf_and_request( &MM_CTX, "ETH", &conf, &req, - &*keypair.private().secret, CoinProtocol::ETH, + priv_key_policy, )) .unwrap() } @@ -168,17 +167,17 @@ impl BchDockerOps { pub fn from_ticker(ticker: &str) -> BchDockerOps { let conf = json!({"asset": ticker,"txfee":1000,"network": "regtest","txversion":4,"overwintered":1}); let req = json!({"method":"enable", "bchd_urls": [], "allow_slp_unsafe_conf": true}); - let priv_key = hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(); + let priv_key = Secp256k1Secret::from("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f"); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = BchActivationRequest::from_legacy_req(&req).unwrap(); - let coin = block_on(bch_coin_from_conf_and_params( + let coin = block_on(bch_coin_with_priv_key( &ctx, ticker, &conf, params, CashAddrPrefix::SlpTest, - &priv_key, + priv_key, )) .unwrap(); BchDockerOps { ctx, coin } @@ -198,8 +197,7 @@ impl BchDockerOps { let mut slp_outputs = vec![]; for _ in 0..18 { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let key_pair = key_pair_from_secret(priv_key.as_ref()).unwrap(); + let key_pair = KeyPair::random_compressed(); let address_hash = key_pair.public().address_hash(); let address = Address { prefix: self.coin.as_ref().conf.pub_addr_prefix, @@ -226,7 +224,7 @@ impl BchDockerOps { amount: 1000_00000000, script_pubkey: script_pubkey.to_bytes(), }); - slp_privkeys.push(*priv_key.as_ref()); + slp_privkeys.push(*key_pair.private_ref()); } let slp_genesis_tx = send_outputs_from_my_address(self.coin.clone(), bch_outputs) @@ -269,6 +267,11 @@ pub struct UtxoDockerNode<'a> { pub port: u16, } +pub fn random_secp256k1_secret() -> Secp256k1Secret { + let priv_key = SecretKey::new(&mut rand6::thread_rng()); + Secp256k1Secret::from(*priv_key.as_ref()) +} + pub fn utxo_asset_docker_node<'a>(docker: &'a Cli, ticker: &'static str, port: u16) -> UtxoDockerNode<'a> { let args = vec![ "-v".into(), @@ -314,8 +317,8 @@ pub fn utxo_asset_docker_node<'a>(docker: &'a Cli, ticker: &'static str, port: u } } -pub fn rmd160_from_priv(privkey: [u8; 32]) -> H160 { - let secret = SecretKey::from_slice(&privkey).unwrap(); +pub fn rmd160_from_priv(privkey: Secp256k1Secret) -> H160 { + let secret = SecretKey::from_slice(privkey.as_slice()).unwrap(); let public = PublicKey::from_secret_key(&Secp256k1::new(), &secret); dhash160(&public.serialize()) } @@ -338,7 +341,7 @@ where } /// Build `Qrc20Coin` from ticker and privkey without filling the balance. -pub fn qrc20_coin_from_privkey(ticker: &str, priv_key: &[u8]) -> (MmArc, Qrc20Coin) { +pub fn qrc20_coin_from_privkey(ticker: &str, priv_key: Secp256k1Secret) -> (MmArc, Qrc20Coin) { let (contract_address, swap_contract_address) = unsafe { let contract_address = match ticker { "QICK" => QICK_TOKEN_ADDRESS @@ -378,13 +381,13 @@ pub fn qrc20_coin_from_privkey(ticker: &str, priv_key: &[u8]) -> (MmArc, Qrc20Co }); let params = Qrc20ActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qrc20_coin_from_conf_and_params( + let coin = block_on(qrc20_coin_with_priv_key( &ctx, ticker, platform, &conf, ¶ms, - &priv_key, + priv_key, contract_address, )) .unwrap(); @@ -421,7 +424,7 @@ fn qrc20_coin_conf_item(ticker: &str) -> Json { } /// Build asset `UtxoStandardCoin` from ticker and privkey without filling the balance. -pub fn utxo_coin_from_privkey(ticker: &str, priv_key: &[u8]) -> (MmArc, UtxoStandardCoin) { +pub fn utxo_coin_from_privkey(ticker: &str, priv_key: Secp256k1Secret) -> (MmArc, UtxoStandardCoin) { let ctx = MmCtxBuilder::new().into_mm_arc(); let conf = json!({"asset":ticker,"txversion":4,"overwintered":1,"txfee":1000,"network":"regtest"}); let req = json!({"method":"enable"}); @@ -432,7 +435,7 @@ pub fn utxo_coin_from_privkey(ticker: &str, priv_key: &[u8]) -> (MmArc, UtxoStan } /// Create a UTXO coin for the given privkey and fill it's address with the specified balance. -pub fn generate_utxo_coin_with_privkey(ticker: &str, balance: BigDecimal, priv_key: &[u8]) { +pub fn generate_utxo_coin_with_privkey(ticker: &str, balance: BigDecimal, priv_key: Secp256k1Secret) { let (_, coin) = utxo_coin_from_privkey(ticker, priv_key); let timeout = 30; // timeout if test takes more than 30 seconds to run let my_address = coin.my_address().expect("!my_address"); @@ -443,13 +446,13 @@ pub fn generate_utxo_coin_with_privkey(ticker: &str, balance: BigDecimal, priv_k pub fn generate_utxo_coin_with_random_privkey( ticker: &str, balance: BigDecimal, -) -> (MmArc, UtxoStandardCoin, [u8; 32]) { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let (ctx, coin) = utxo_coin_from_privkey(ticker, priv_key.as_ref()); +) -> (MmArc, UtxoStandardCoin, Secp256k1Secret) { + let priv_key = random_secp256k1_secret(); + let (ctx, coin) = utxo_coin_from_privkey(ticker, priv_key); let timeout = 30; // timeout if test takes more than 30 seconds to run let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, balance, timeout); - (ctx, coin, *priv_key.as_ref()) + (ctx, coin, priv_key) } /// Get only one address assigned the specified label. @@ -512,15 +515,15 @@ pub fn generate_qrc20_coin_with_random_privkey( ticker: &str, qtum_balance: BigDecimal, qrc20_balance: BigDecimal, -) -> (MmArc, Qrc20Coin, [u8; 32]) { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let (ctx, coin) = qrc20_coin_from_privkey(ticker, priv_key.as_ref()); +) -> (MmArc, Qrc20Coin, Secp256k1Secret) { + let priv_key = random_secp256k1_secret(); + let (ctx, coin) = qrc20_coin_from_privkey(ticker, priv_key); let timeout = 30; // timeout if test takes more than 30 seconds to run let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, qtum_balance, timeout); fill_qrc20_address(&coin, qrc20_balance, timeout); - (ctx, coin, *priv_key.as_ref()) + (ctx, coin, priv_key) } pub fn generate_qtum_coin_with_random_privkey( @@ -545,22 +548,22 @@ pub fn generate_qtum_coin_with_random_privkey( "dust": 72800, }); let req = json!({"method": "enable"}); - let priv_key = SecretKey::new(&mut rand6::thread_rng()); + let priv_key = random_secp256k1_secret(); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key.as_ref())).unwrap(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); let timeout = 30; // timeout if test takes more than 30 seconds to run let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, balance, timeout); - (ctx, coin, *priv_key.as_ref()) + (ctx, coin, priv_key.take()) } pub fn generate_segwit_qtum_coin_with_random_privkey( ticker: &str, balance: BigDecimal, txfee: Option, -) -> (MmArc, QtumCoin, [u8; 32]) { +) -> (MmArc, QtumCoin, Secp256k1Secret) { let confpath = unsafe { QTUM_CONF_PATH.as_ref().expect("Qtum config is not set yet") }; let conf = json!({ "coin":ticker, @@ -583,15 +586,15 @@ pub fn generate_segwit_qtum_coin_with_random_privkey( }, }); let req = json!({"method": "enable"}); - let priv_key = SecretKey::new(&mut rand6::thread_rng()); + let priv_key = random_secp256k1_secret(); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key.as_ref())).unwrap(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); let timeout = 30; // timeout if test takes more than 30 seconds to run let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, balance, timeout); - (ctx, coin, *priv_key.as_ref()) + (ctx, coin, priv_key) } pub fn fill_address(coin: &T, address: &str, amount: BigDecimal, timeout: u64) @@ -651,8 +654,8 @@ pub fn wait_for_estimate_smart_fee(timeout: u64) -> Result<(), String> { EstimateSmartFeeState::Idle => log!("Start wait_for_estimate_smart_fee"), } - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let (_ctx, coin) = qrc20_coin_from_privkey("QICK", priv_key.as_ref()); + let priv_key = random_secp256k1_secret(); + let (_ctx, coin) = qrc20_coin_from_privkey("QICK", priv_key); let timeout = now_ms() / 1000 + timeout; let client = match coin.as_ref().rpc_client { UtxoRpcClientEnum::Native(ref client) => client, @@ -695,7 +698,7 @@ pub async fn enable_qrc20_native(mm: &MarketMakerIt, coin: &str) -> Json { pub fn trade_base_rel((base, rel): (&str, &str)) { /// Generate a wallet with the random private key and fill the wallet with Qtum (required by gas_fee) and specified in `ticker` coin. - fn generate_and_fill_priv_key(ticker: &str) -> [u8; 32] { + fn generate_and_fill_priv_key(ticker: &str) -> Secp256k1Secret { let timeout = 30; // timeout if test takes more than 30 seconds to run match ticker { @@ -707,28 +710,27 @@ pub fn trade_base_rel((base, rel): (&str, &str)) { priv_key }, "QICK" | "QORTY" => { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let (_ctx, coin) = qrc20_coin_from_privkey(ticker, priv_key.as_ref()); + let priv_key = random_secp256k1_secret(); + let (_ctx, coin) = qrc20_coin_from_privkey(ticker, priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, 10.into(), timeout); fill_qrc20_address(&coin, 10.into(), timeout); - *priv_key.as_ref() + priv_key }, "MYCOIN" | "MYCOIN1" => { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let (_ctx, coin) = utxo_coin_from_privkey(ticker, priv_key.as_ref()); + let priv_key = random_secp256k1_secret(); + let (_ctx, coin) = utxo_coin_from_privkey(ticker, priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, 10.into(), timeout); // also fill the Qtum - let (_ctx, coin) = qrc20_coin_from_privkey("QICK", priv_key.as_ref()); + let (_ctx, coin) = qrc20_coin_from_privkey("QICK", priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, 10.into(), timeout); - *priv_key.as_ref() + priv_key }, - "ADEXSLP" => get_prefilled_slp_privkey(), - "FORSLP" => get_prefilled_slp_privkey(), + "ADEXSLP" | "FORSLP" => Secp256k1Secret::from(get_prefilled_slp_privkey()), _ => panic!("Expected either QICK or QORTY or MYCOIN or MYCOIN1, found {}", ticker), } } 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 6bd7c6c2eb..fc6141ea0a 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -1,6 +1,6 @@ use crate::integration_tests_common::*; use crate::{fill_address, fill_eth, generate_utxo_coin_with_privkey, generate_utxo_coin_with_random_privkey, - rmd160_from_priv, utxo_coin_from_privkey}; + random_secp256k1_secret, rmd160_from_priv, utxo_coin_from_privkey}; use bitcrypto::dhash160; use chain::OutPoint; use coins::utxo::rpc_clients::UnspentInfo; @@ -13,7 +13,6 @@ use futures01::Future; use mm2_number::{BigDecimal, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, mm_dump, MarketMakerIt, Mm2TestConf}; use mm2_test_helpers::structs::*; -use secp256k1::SecretKey; use serde_json::Value as Json; use std::collections::HashMap; use std::env; @@ -831,10 +830,11 @@ fn test_order_should_be_updated_when_matched_partially() { #[test] fn test_watcher_spends_taker_spends_maker_payment() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 100.into()); - generate_utxo_coin_with_privkey("MYCOIN1", 100.into(), &bob_priv_key); + generate_utxo_coin_with_privkey("MYCOIN1", 100.into(), bob_priv_key); let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN1", 100.into()); - generate_utxo_coin_with_privkey("MYCOIN", 100.into(), &alice_priv_key); - let watcher_priv_key = *SecretKey::new(&mut rand6::thread_rng()).as_ref(); + generate_utxo_coin_with_privkey("MYCOIN", 100.into(), alice_priv_key); + + let watcher_priv_key = random_secp256k1_secret(); let coins = json!([ {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, @@ -1485,13 +1485,13 @@ fn test_buy_max() { #[test] fn test_maker_trade_preimage() { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); + let priv_key = random_secp256k1_secret(); - let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", &priv_key[..]); + let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key); let my_address = mycoin.my_address().expect("!my_address"); fill_address(&mycoin, &my_address, 10.into(), 30); - let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", &priv_key[..]); + let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key); let my_address = mycoin1.my_address().expect("!my_address"); fill_address(&mycoin1, &my_address, 20.into(), 30); @@ -1504,7 +1504,7 @@ fn test_maker_trade_preimage() { "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(&priv_key[..])), + "passphrase": format!("0x{}", hex::encode(priv_key)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -1625,13 +1625,13 @@ fn test_maker_trade_preimage() { #[test] fn test_taker_trade_preimage() { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); + let priv_key = random_secp256k1_secret(); - let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key.as_ref()); + let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key); let my_address = mycoin.my_address().expect("!my_address"); fill_address(&mycoin, &my_address, 10.into(), 30); - let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key.as_ref()); + let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key); let my_address = mycoin1.my_address().expect("!my_address"); fill_address(&mycoin1, &my_address, 20.into(), 30); @@ -1644,7 +1644,7 @@ fn test_taker_trade_preimage() { "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(priv_key.as_ref())), + "passphrase": format!("0x{}", hex::encode(priv_key)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -1772,9 +1772,9 @@ fn test_trade_preimage_not_sufficient_balance() { assert_eq!(actual.error_data, Some(expected)); } - let priv_key = SecretKey::new(&mut rand6::thread_rng()); + let priv_key = random_secp256k1_secret(); let fill_balance_functor = |amount: BigDecimal| { - let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key.as_ref()); + let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key); let my_address = mycoin.my_address().expect("!my_address"); fill_address(&mycoin, &my_address, amount, 30); }; @@ -1788,7 +1788,7 @@ fn test_trade_preimage_not_sufficient_balance() { "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(priv_key.as_ref())), + "passphrase": format!("0x{}", hex::encode(priv_key)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -1890,13 +1890,13 @@ fn test_trade_preimage_not_sufficient_balance() { /// https://github.com/KomodoPlatform/atomicDEX-API/issues/902 #[test] fn test_trade_preimage_additional_validation() { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); + let priv_key = random_secp256k1_secret(); - let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key.as_ref()); + let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key); let my_address = mycoin1.my_address().expect("!my_address"); fill_address(&mycoin1, &my_address, 20.into(), 30); - let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key.as_ref()); + let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key); let my_address = mycoin.my_address().expect("!my_address"); fill_address(&mycoin, &my_address, 10.into(), 30); @@ -1909,7 +1909,7 @@ fn test_trade_preimage_additional_validation() { "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(priv_key.as_ref())), + "passphrase": format!("0x{}", hex::encode(priv_key)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -2035,11 +2035,11 @@ fn test_trade_preimage_additional_validation() { #[test] fn test_trade_preimage_legacy() { - let priv_key = SecretKey::new(&mut rand6::thread_rng()); - let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key.as_ref()); + let priv_key = random_secp256k1_secret(); + let (_ctx, mycoin) = utxo_coin_from_privkey("MYCOIN", priv_key); let my_address = mycoin.my_address().expect("!my_address"); fill_address(&mycoin, &my_address, 10.into(), 30); - let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key.as_ref()); + let (_ctx, mycoin1) = utxo_coin_from_privkey("MYCOIN1", priv_key); let my_address = mycoin1.my_address().expect("!my_address"); fill_address(&mycoin1, &my_address, 20.into(), 30); @@ -2052,7 +2052,7 @@ fn test_trade_preimage_legacy() { "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(priv_key.as_ref())), + "passphrase": format!("0x{}", hex::encode(priv_key)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -3096,7 +3096,7 @@ fn test_utxo_merge() { thread::sleep(Duration::from_secs(2)); let (unspents, _) = - block_on(coin.get_unspent_ordered_list(&coin.as_ref().derivation_method.unwrap_iguana())).unwrap(); + block_on(coin.get_unspent_ordered_list(&coin.as_ref().derivation_method.unwrap_single_addr())).unwrap(); assert_eq!(unspents.len(), 1); } @@ -3153,14 +3153,14 @@ fn test_utxo_merge_max_merge_at_once() { thread::sleep(Duration::from_secs(2)); let (unspents, _) = - block_on(coin.get_unspent_ordered_list(&coin.as_ref().derivation_method.unwrap_iguana())).unwrap(); + block_on(coin.get_unspent_ordered_list(&coin.as_ref().derivation_method.unwrap_single_addr())).unwrap(); // 4 utxos are merged of 5 so the resulting unspents len must be 2 assert_eq!(unspents.len(), 2); } #[test] fn test_withdraw_not_sufficient_balance() { - let privkey = SecretKey::new(&mut rand6::thread_rng()); + let privkey = random_secp256k1_secret(); let coins = json!([ {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, @@ -3170,7 +3170,7 @@ fn test_withdraw_not_sufficient_balance() { "gui": "nogui", "netid": 9000, "dht": "on", // Enable DHT without delay. - "passphrase": format!("0x{}", hex::encode(privkey.as_ref())), + "passphrase": format!("0x{}", hex::encode(privkey)), "coins": coins, "rpc_password": "pass", "i_am_seed": true, @@ -3211,7 +3211,7 @@ fn test_withdraw_not_sufficient_balance() { // fill the MYCOIN balance let balance = BigDecimal::from(1) / BigDecimal::from(2); - let (_ctx, coin) = utxo_coin_from_privkey("MYCOIN", privkey.as_ref()); + let (_ctx, coin) = utxo_coin_from_privkey("MYCOIN", privkey); fill_address(&coin, &coin.my_address().unwrap(), balance.clone(), 30); // txfee = 0.00001, amount = 0.5 => required = 0.50001 diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 1141655346..01bae44849 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -11,7 +11,8 @@ use coins::{CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MarketCo SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SwapOps, TradePreimageValue, TransactionEnum, ValidatePaymentInput}; use common::log::debug; -use common::{log, temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; +use crypto::Secp256k1Secret; use ethereum_types::H160; use futures01::Future; use http::StatusCode; @@ -53,9 +54,9 @@ impl QtumDockerOps { let req = json!({ "method": "enable", }); - let priv_key = hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(); + let priv_key = Secp256k1Secret::from("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f"); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); - let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, &priv_key)).unwrap(); + let coin = block_on(qtum_coin_with_priv_key(&ctx, "QTUM", &conf, ¶ms, priv_key)).unwrap(); QtumDockerOps { ctx, coin } } @@ -791,26 +792,26 @@ fn test_wait_for_tx_spend() { #[test] fn test_check_balance_on_order_post_base_coin_locked() { - let bob_priv_key = SecretKey::new(&mut rand6::thread_rng()); - let alice_priv_key = SecretKey::new(&mut rand6::thread_rng()); + let bob_priv_key = random_secp256k1_secret(); + let alice_priv_key = random_secp256k1_secret(); let timeout = 30; // timeout if test takes more than 80 seconds to run // fill the Bob address by 0.05 Qtum - let (_ctx, coin) = qrc20_coin_from_privkey("QICK", bob_priv_key.as_ref()); + let (_ctx, coin) = qrc20_coin_from_privkey("QICK", bob_priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, BigDecimal::try_from(0.05).unwrap(), timeout); // fill the Bob address by 10 MYCOIN - let (_ctx, coin) = utxo_coin_from_privkey("MYCOIN", bob_priv_key.as_ref()); + let (_ctx, coin) = utxo_coin_from_privkey("MYCOIN", bob_priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, 10.into(), timeout); // fill the Alice address by 10 Qtum and 10 QICK - let (_ctx, coin) = qrc20_coin_from_privkey("QICK", alice_priv_key.as_ref()); + let (_ctx, coin) = qrc20_coin_from_privkey("QICK", alice_priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, 10.into(), timeout); fill_qrc20_address(&coin, 10.into(), timeout); // fill the Alice address by 10 MYCOIN - let (_ctx, coin) = utxo_coin_from_privkey("MYCOIN", alice_priv_key.as_ref()); + let (_ctx, coin) = utxo_coin_from_privkey("MYCOIN", alice_priv_key); let my_address = coin.my_address().expect("!my_address"); fill_address(&coin, &my_address, 10.into(), timeout); @@ -829,7 +830,7 @@ fn test_check_balance_on_order_post_base_coin_locked() { "myipaddr": env::var ("BOB_TRADE_IP") .ok(), "rpcip": env::var ("BOB_TRADE_IP") .ok(), "canbind": env::var ("BOB_TRADE_PORT") .ok().map(|s| s.parse::().unwrap()), - "passphrase": format!("0x{}", hex::encode(bob_priv_key.as_ref())), + "passphrase": format!("0x{}", hex::encode(bob_priv_key)), "coins": coins, "i_am_seed": true, "rpc_password": "pass", @@ -852,7 +853,7 @@ fn test_check_balance_on_order_post_base_coin_locked() { "myipaddr": env::var ("BOB_TRADE_IP") .ok(), "rpcip": env::var ("BOB_TRADE_IP") .ok(), "canbind": env::var ("BOB_TRADE_PORT") .ok().map(|s| s.parse::().unwrap()), - "passphrase": format!("0x{}", hex::encode(alice_priv_key.as_ref())), + "passphrase": format!("0x{}", hex::encode(alice_priv_key)), "coins": coins, "seednodes": [mm_bob.ip.to_string()], "rpc_password": "pass", diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs index 25399fd9f4..1ef18a8ed3 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs @@ -2,6 +2,7 @@ use crate::generate_utxo_coin_with_random_privkey; use crate::integration_tests_common::enable_native; use bitcrypto::ChecksumType; use common::block_on; +use crypto::Secp256k1Secret; use keys::{KeyPair, Private}; use mm2_io::file_lock::FileLock; use mm2_test_helpers::for_tests::{mm_dump, new_mm2_temp_folder_path, MarketMakerIt}; @@ -172,7 +173,7 @@ fn test_swaps_should_kick_start_if_process_was_killed() { block_on(mm_alice_dup.wait_for_log(50., |log| log.contains(&format!("Swap {} kick started.", uuid)))).unwrap(); } -fn addr_hash_for_privkey(priv_key: [u8; 32]) -> String { +fn addr_hash_for_privkey(priv_key: Secp256k1Secret) -> String { let private = Private { prefix: 1, secret: priv_key.into(), 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 8d6e8df128..381a7cad7c 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 @@ -1,10 +1,12 @@ use common::executor::Timer; use common::{block_on, log, now_ms}; use http::StatusCode; -use mm2_test_helpers::for_tests::{enable_bch_with_tokens, enable_slp, my_tx_history_v2, sign_message, verify_message, - MarketMakerIt, Mm2TestConf, UtxoRpcMode}; -use mm2_test_helpers::structs::{RpcV2Response, SignatureResponse, StandardHistoryV2Res, UtxoFeeDetails, - VerificationResponse}; +use itertools::Itertools; +use mm2_test_helpers::for_tests::{enable_bch_with_tokens, enable_slp, my_tx_history_v2, sign_message, + tbch_for_slp_conf, tbch_usdf_conf, verify_message, MarketMakerIt, Mm2TestConf, + UtxoRpcMode}; +use mm2_test_helpers::structs::{EnableBchWithTokensResponse, RpcV2Response, SignatureResponse, StandardHistoryV2Res, + UtxoFeeDetails, VerificationResponse}; use serde_json::{self as json, json, Value as Json}; use std::env; use std::thread; @@ -26,6 +28,8 @@ const T_BCH_ELECTRUMS: &[&str] = &[ "blackie.c3-soft.com:60004", ]; +const BIP39_PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + fn t_bch_electrums_legacy_json() -> Vec { T_BCH_ELECTRUMS.into_iter().map(|url| json!({ "url": url })).collect() } #[test] @@ -631,3 +635,76 @@ fn test_sign_verify_message_slp() { assert!(response.is_valid); } + +/// Tested via [Electron-Cash-SLP](https://github.com/simpleledger/Electron-Cash-SLP). +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_bch_and_slp_with_hd_account_id() { + const TX_HISTORY: bool = false; + + let coins = json!([tbch_for_slp_conf(), tbch_usdf_conf()]); + + // HD account 0 + + let hd_account_id = 0; + let conf_0 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, hd_account_id, &coins); + let mm_hd_0 = MarketMakerIt::start(conf_0.conf, conf_0.rpc_password, None).unwrap(); + + let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); + let activation_result = block_on(enable_bch_with_tokens( + &mm_hd_0, + "tBCH", + &["USDF"], + rpc_mode, + TX_HISTORY, + )); + + let activation_result: RpcV2Response = json::from_value(activation_result).unwrap(); + let (bch_addr, _) = activation_result + .result + .bch_addresses_infos + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(bch_addr, "bchtest:qpylzql7gzh6yctm7uslsz5qufl44gk2tsj8c9pjw0"); + + let (slp_addr, _) = activation_result + .result + .slp_addresses_infos + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(slp_addr, "slptest:qpylzql7gzh6yctm7uslsz5qufl44gk2tsfnl7m9uj"); + + // HD account 1 + + let hd_account_id = 1; + let conf_1 = Mm2TestConf::seednode_with_hd_account(BIP39_PASSPHRASE, hd_account_id, &coins); + let mm_hd_1 = MarketMakerIt::start(conf_1.conf, conf_1.rpc_password, None).unwrap(); + + let rpc_mode = UtxoRpcMode::electrum(T_BCH_ELECTRUMS); + let activation_result = block_on(enable_bch_with_tokens( + &mm_hd_1, + "tBCH", + &["USDF"], + rpc_mode, + TX_HISTORY, + )); + + let activation_result: RpcV2Response = json::from_value(activation_result).unwrap(); + let (bch_addr, _) = activation_result + .result + .bch_addresses_infos + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(bch_addr, "bchtest:qpyhwc7shd5hlul8zg0snmaptaa9q9yc4q7g9khpkj"); + + let (slp_addr, _) = activation_result + .result + .slp_addresses_infos + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(slp_addr, "slptest:qpyhwc7shd5hlul8zg0snmaptaa9q9yc4q9uzddky0"); +} 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 b978ba7687..8f56390a8c 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -9,14 +9,16 @@ use mm2_number::{BigDecimal, BigRational, Fraction, MmNumber}; use mm2_test_helpers::electrums::*; #[cfg(all(feature = "zhtlc-native-tests", not(target_arch = "wasm32")))] use mm2_test_helpers::for_tests::init_z_coin_native; -use mm2_test_helpers::for_tests::{btc_with_spv_conf, check_recent_swaps, check_stats_swap_status, enable_eth_coin, - enable_qrc20, find_metrics_in_json, from_env_file, mm_spat, morty_conf, rick_conf, - sign_message, start_swaps, tbtc_with_spv_conf, test_qrc20_history_impl, +use mm2_test_helpers::for_tests::{btc_segwit_conf, btc_with_spv_conf, check_recent_swaps, check_stats_swap_status, + enable_eth_coin, enable_qrc20, eth_jst_testnet_conf, eth_testnet_conf, + find_metrics_in_json, from_env_file, mm_spat, morty_conf, rick_conf, sign_message, + start_swaps, tbtc_with_spv_conf, test_qrc20_history_impl, tqrc20_conf, verify_message, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, wait_for_swaps_finish_and_check_status, - wait_till_history_has_records, MarketMakerIt, Mm2TestConf, RaiiDump, ETH_DEV_NODES, - ETH_DEV_SWAP_CONTRACT, ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, - MAKER_SUCCESS_EVENTS, MORTY, RICK, TAKER_SUCCESS_EVENTS}; + wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, + Mm2TestConfForSwap, RaiiDump, ETH_DEV_NODES, ETH_DEV_SWAP_CONTRACT, + ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, MAKER_SUCCESS_EVENTS, MORTY, + QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, TAKER_SUCCESS_EVENTS}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -741,41 +743,28 @@ fn test_rpc_password_from_json_no_userpass() { /// Trading test using coins with remote RPC (Electrum, ETH nodes), it needs only ENV variables to be set, coins daemons are not required. /// Trades few pairs concurrently to speed up the process and also act like "load" test +/// +/// Please note that it async fn trade_base_rel_electrum( + bob_priv_key_policy: Mm2InitPrivKeyPolicy, + alice_priv_key_policy: Mm2InitPrivKeyPolicy, pairs: &[(&'static str, &'static str)], maker_price: i32, taker_price: i32, volume: f64, ) { - let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); - let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); - let coins = json!([ rick_conf(), morty_conf(), - {"coin":"ETH","name":"ethereum","protocol":{"type":"ETH"}}, - {"coin":"JST","name":"jst","protocol":{"type":"ERC20","protocol_data":{"platform":"ETH","contract_address":"0x2b294F029Fde858b2c62184e8390591755521d8E"}}}, + eth_testnet_conf(), + eth_jst_testnet_conf(), {"coin":"ZOMBIE","asset":"ZOMBIE","fname":"ZOMBIE (TESTCOIN)","txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"ZHTLC"},"required_confirmations":0}, ]); - let mut mm_bob = MarketMakerIt::start_async( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("BOB_TRADE_IP") .ok(), - "rpcip": env::var ("BOB_TRADE_IP") .ok(), - "canbind": env::var ("BOB_TRADE_PORT") .ok().map (|s| s.parse::().unwrap()), - "passphrase": bob_passphrase, - "coins": coins, - "rpc_password": "password", - "i_am_seed": true, - }), - "password".into(), - None, - ) - .await - .unwrap(); + let bob_conf = Mm2TestConfForSwap::bob_conf_with_policy(bob_priv_key_policy, &coins); + let mut mm_bob = MarketMakerIt::start_async(bob_conf.conf, bob_conf.rpc_password, None) + .await + .unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); #[cfg(not(target_arch = "wasm32"))] @@ -785,24 +774,10 @@ async fn trade_base_rel_electrum( Timer::sleep(1.).await; - let mut mm_alice = MarketMakerIt::start_async( - json! ({ - "gui": "nogui", - "netid": 8999, - "dht": "on", // Enable DHT without delay. - "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), - "rpcip": env::var ("ALICE_TRADE_IP") .ok(), - "passphrase": alice_passphrase, - "coins": coins, - "seednodes": [mm_bob.my_seed_addr()], - "rpc_password": "password", - "skip_startup_checks": true, - }), - "password".into(), - None, - ) - .await - .unwrap(); + let alice_conf = Mm2TestConfForSwap::alice_conf_with_policy(alice_priv_key_policy, &coins, &mm_bob.my_seed_addr()); + let mut mm_alice = MarketMakerIt::start_async(alice_conf.conf, alice_conf.rpc_password, None) + .await + .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); #[cfg(not(target_arch = "wasm32"))] @@ -914,11 +889,21 @@ async fn trade_base_rel_electrum( #[test] #[cfg(not(target_arch = "wasm32"))] -fn trade_test_electrum_and_eth_coins() { block_on(trade_base_rel_electrum(&[("ETH", "JST")], 1, 2, 0.1)); } +fn trade_test_electrum_and_eth_coins() { + let bob_policy = Mm2InitPrivKeyPolicy::Iguana; + let alice_policy = Mm2InitPrivKeyPolicy::GlobalHDAccount(0); + let pairs = &[("ETH", "JST")]; + block_on(trade_base_rel_electrum(bob_policy, alice_policy, pairs, 1, 2, 0.1)); +} #[test] #[cfg(all(not(target_arch = "wasm32"), feature = "zhtlc-native-tests"))] -fn trade_test_electrum_rick_zombie() { block_on(trade_base_rel_electrum(&[("RICK", "ZOMBIE")], 1, 2, 0.1)); } +fn trade_test_electrum_rick_zombie() { + let bob_policy = Mm2InitPrivKeyPolicy::Iguana; + let alice_policy = Mm2InitPrivKeyPolicy::Iguana; + let pairs = &[("RICK", "ZOMBIE")]; + block_on(trade_base_rel_electrum(bob_policy, alice_policy, pairs, 1, 2, 0.1)); +} #[cfg(not(target_arch = "wasm32"))] fn withdraw_and_send( @@ -1400,7 +1385,7 @@ fn test_withdraw_segwit() { assert!(withdraw_error["error"] .as_str() .expect("Expected 'error' field") - .contains("Invalid address: tb1p6h5fuzmnvpdthf5shf0qqjzwy7wsqc5rhmgq2ks9xrak4ry6mtrscsqvzp")); + .contains("address variant/format Bech32m is not supported yet")); block_on(mm_alice.stop()).unwrap(); } @@ -7312,6 +7297,65 @@ fn test_tbtc_block_header_sync() { block_on(mm_bob.stop()).unwrap(); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_enable_coins_with_hd_account_id() { + const TX_HISTORY: bool = false; + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([ + eth_testnet_conf(), + eth_jst_testnet_conf(), + rick_conf(), + tqrc20_conf(), + btc_segwit_conf(), + ]); + + let hd_account_id = 0; + let conf_0 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, hd_account_id, &coins); + let mm_hd_0 = MarketMakerIt::start(conf_0.conf, conf_0.rpc_password, None).unwrap(); + let (_dump_log, _dump_dashboard) = mm_hd_0.mm_dump(); + log!("log path: {}", mm_hd_0.log_path.display()); + + let eth = block_on(enable_native(&mm_hd_0, "ETH", &["http://195.201.0.6:8565"])); + assert_eq!(eth.address, "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93"); + let jst = block_on(enable_native(&mm_hd_0, "JST", &["http://195.201.0.6:8565"])); + assert_eq!(jst.address, "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93"); + let rick = block_on(enable_electrum(&mm_hd_0, "RICK", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + assert_eq!(rick.address, "RXNtAyDSsY3DS3VxTpJegzoHU9bUX54j56"); + let qrc20 = block_on(enable_qrc20( + &mm_hd_0, + "QRC20", + QRC20_ELECTRUMS, + "0xd362e096e873eb7907e205fadc6175c6fec7bc44", + )); + assert_eq!(qrc20["address"].as_str(), Some("qRtCTiPHW9e6zH9NcRhjMVfq7sG37SvgrL")); + let btc_segwit = block_on(enable_electrum(&mm_hd_0, "BTC-segwit", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + assert_eq!(btc_segwit.address, "bc1q6vyur5hjul2m0979aadd6u7ptuj9ac4gt0ha0c"); + + let hd_account_id = 1; + let conf_1 = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, hd_account_id, &coins); + let mm_hd_1 = MarketMakerIt::start(conf_1.conf, conf_1.rpc_password, None).unwrap(); + let (_dump_log, _dump_dashboard) = mm_hd_1.mm_dump(); + log!("log path: {}", mm_hd_1.log_path.display()); + + let eth = block_on(enable_native(&mm_hd_1, "ETH", &["http://195.201.0.6:8565"])); + assert_eq!(eth.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); + let jst = block_on(enable_native(&mm_hd_1, "JST", &["http://195.201.0.6:8565"])); + assert_eq!(jst.address, "0xDe841899aB4A22E23dB21634e54920aDec402397"); + let rick = block_on(enable_electrum(&mm_hd_1, "RICK", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + assert_eq!(rick.address, "RVyndZp3ZrhGKSwHryyM3Kcz9aq2EJrW1z"); + let qrc20 = block_on(enable_qrc20( + &mm_hd_1, + "QRC20", + QRC20_ELECTRUMS, + "0xd362e096e873eb7907e205fadc6175c6fec7bc44", + )); + assert_eq!(qrc20["address"].as_str(), Some("qY8FNq2ZDUh52BjNvaroFoeHdr3AAhqsxW")); + let btc_segwit = block_on(enable_electrum(&mm_hd_1, "BTC-segwit", TX_HISTORY, RICK_ELECTRUM_ADDRS)); + assert_eq!(btc_segwit.address, "bc1q6kxcwcrsm5z8pe940xxu294q7588mqvarttxcx"); +} + #[test] fn test_eth_swap_contract_addr_negotiation_same_fallback() { let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs index eed897287d..0c0f149a80 100644 --- a/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/z_coin_tests.rs @@ -17,6 +17,7 @@ use std::str::FromStr; use std::thread; use std::time::Duration; +const ZOMBIE_TEST_BIP39_ACTIVATION_SEED: &str = "course flock lucky cereal hamster novel team never metal bean behind cute cruel matrix symptom fault harsh fashion impact prison glove then tree chef"; const ZOMBIE_TEST_BALANCE_SEED: &str = "zombie test seed"; const ARRR_TEST_ACTIVATION_SEED: &str = "arrr test activation seed"; const ZOMBIE_TEST_HISTORY_SEED: &str = "zombie test history seed"; @@ -68,6 +69,32 @@ fn activate_z_coin_light() { assert_eq!(balance.balance.spendable, BigDecimal::from_str("3.1").unwrap()); } +#[test] +#[ignore] +fn activate_z_coin_with_hd_account() { + let coins = json!([zombie_conf()]); + + let hd_account_id = 0; + let conf = Mm2TestConf::seednode_with_hd_account(ZOMBIE_TEST_BIP39_ACTIVATION_SEED, hd_account_id, &coins); + let mm = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + + let activation_result = block_on(enable_z_coin_light( + &mm, + ZOMBIE_TICKER, + ZOMBIE_ELECTRUMS, + ZOMBIE_LIGHTWALLETD_URLS, + )); + + let actual = match activation_result.wallet_balance { + EnableCoinBalance::Iguana(iguana) => iguana.address, + EnableCoinBalance::HD(_) => panic!("Expected 'Iguana' wallet balance, found HD"), + }; + assert_eq!( + actual, + "zs1p4xfnqmqa4aq5rrnfldafxcggsqhg0wph3elzrzwls9ks95lrg0gtlktjr0t5gg9lj657jyr8m6" + ); +} + // ignored because it requires a long-running Zcoin initialization process #[test] #[ignore] diff --git a/mm2src/mm2_test_helpers/Cargo.toml b/mm2src/mm2_test_helpers/Cargo.toml index 9eb34f2e4b..7666820f66 100644 --- a/mm2src/mm2_test_helpers/Cargo.toml +++ b/mm2src/mm2_test_helpers/Cargo.toml @@ -10,6 +10,7 @@ doctest = false bytes = "1.1" cfg-if = "1.0" common = { path = "../common" } +crypto = { path = "../crypto" } db_common = { path = "../db_common" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } http = "0.2" diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 8e03fc08cf..ec25b13582 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -5,6 +5,7 @@ use common::executor::Timer; use common::log::debug; use common::{cfg_native, now_float, now_ms, PagingOptionsEnum}; use common::{get_utc_timestamp, log}; +use crypto::CryptoCtx; use gstuff::{try_s, ERR, ERRL}; use http::{HeaderMap, StatusCode}; use lazy_static::lazy_static; @@ -120,6 +121,11 @@ 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"]; const DEFAULT_RPC_PASSWORD: &str = "pass"; +pub const QRC20_ELECTRUMS: &[&str] = &[ + "electrum1.cipig.net:10071", + "electrum2.cipig.net:10071", + "electrum3.cipig.net:10071", +]; pub const ETH_MAINNET_NODE: &str = "https://mainnet.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b"; pub const ETH_MAINNET_SWAP_CONTRACT: &str = "0x24abe4c71fc658c91313b6552cd40cd808b3ea80"; @@ -162,6 +168,21 @@ impl Mm2TestConf { } } + pub fn seednode_with_hd_account(passphrase: &str, hd_account_id: u32, coins: &Json) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "passphrase": passphrase, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "i_am_seed": true, + "hd_account_id": hd_account_id, + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn light_node(passphrase: &str, coins: &Json, seednodes: &[&str]) -> Self { Mm2TestConf { conf: json!({ @@ -191,6 +212,21 @@ impl Mm2TestConf { } } + pub fn light_node_with_hd_account(passphrase: &str, hd_account_id: u32, coins: &Json, seednodes: &[&str]) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "passphrase": passphrase, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "seednodes": seednodes, + "hd_account_id": hd_account_id, + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn no_login_node(coins: &Json, seednodes: &[&str]) -> Self { Mm2TestConf { conf: json!({ @@ -205,6 +241,46 @@ impl Mm2TestConf { } } +pub struct Mm2TestConfForSwap; + +impl Mm2TestConfForSwap { + /// TODO consider moving it to read it from a env file. + const BOB_HD_PASSPHRASE: &'static str = + "involve work eager scene give acoustic tooth mimic dance smoke hold foster"; + /// TODO consider moving it to read it from a env file. + const ALICE_HD_PASSPHRASE: &'static str = + "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + pub fn bob_conf_with_policy(priv_key_policy: Mm2InitPrivKeyPolicy, coins: &Json) -> Mm2TestConf { + match priv_key_policy { + Mm2InitPrivKeyPolicy::Iguana => { + let bob_passphrase = crate::get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); + Mm2TestConf::seednode(&bob_passphrase, coins) + }, + Mm2InitPrivKeyPolicy::GlobalHDAccount(hd_account_id) => { + Mm2TestConf::seednode_with_hd_account(Self::BOB_HD_PASSPHRASE, hd_account_id, coins) + }, + } + } + + pub fn alice_conf_with_policy(priv_key_policy: Mm2InitPrivKeyPolicy, coins: &Json, bob_ip: &str) -> Mm2TestConf { + match priv_key_policy { + Mm2InitPrivKeyPolicy::Iguana => { + let alice_passphrase = crate::get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); + Mm2TestConf::light_node(&alice_passphrase, coins, &[bob_ip]) + }, + Mm2InitPrivKeyPolicy::GlobalHDAccount(hd_account_id) => { + Mm2TestConf::light_node_with_hd_account(Self::ALICE_HD_PASSPHRASE, hd_account_id, coins, &[bob_ip]) + }, + } + } +} + +pub enum Mm2InitPrivKeyPolicy { + Iguana, + GlobalHDAccount(u32), +} + pub fn zombie_conf() -> Json { json!({ "coin":"ZOMBIE", @@ -234,9 +310,11 @@ pub fn zombie_conf() -> Json { "hash": "106BAA72C53E7FA52E30E6D3D15B37001207E3CF3B9FCE9BAB6C6D4AF9ED9200", "sapling_tree": "017797D05B070D29A47EFEBE3FAD3F29345D31BE608C46A5131CD55D201A631C13000D000119CE6220D0CB0F82AD6466B677828A0B4C2983662DAB181A86F913F7E9FB9C28000139C4399E4CA741CBABBDDAEB6DCC3541BA902343E394160EEECCDF20C289BA65011823D28B592E9612A6C3CF4778F174E10B1B714B4FF85E6E58EE19DD4A0D5734016FA4682B0007E61B63A0442B85E0B8C0CE2409E665F219013B5E24E385F6066B00000001A325043E11CD6A431A0BD99141C4C6E9632A156185EB9B0DBEF665EEC803DD6F00000103C11FCCC90C2EC1A126635F708311EDEF9B93D3E752E053D3AA9EFA0AF9D526" }, + "z_derivation_path": "m/32'/133'", } }, - "required_confirmations":0 + "required_confirmations":0, + "derivation_path": "m/44'/133'", }) } @@ -282,6 +360,7 @@ pub fn rick_conf() -> Json { "required_confirmations":0, "txversion":4, "overwintered":1, + "derivation_path": "m/44'/141'", "protocol":{ "type":"UTXO" } @@ -295,6 +374,7 @@ pub fn morty_conf() -> Json { "required_confirmations":0, "txversion":4, "overwintered":1, + "derivation_path": "m/44'/141'", "protocol":{ "type":"UTXO" } @@ -317,6 +397,40 @@ pub fn atom_testnet_conf() -> Json { }) } +pub fn btc_segwit_conf() -> Json { + json!({ + "coin": "BTC-segwit", + "name": "bitcoin", + "fname": "Bitcoin", + "rpcport": 8332, + "pubtype": 0, + "p2shtype": 5, + "wiftype": 128, + "segwit": true, + "bech32_hrp": "bc", + "address_format": { + "format": "segwit" + }, + "orderbook_ticker": "BTC", + "txfee": 0, + "estimate_fee_mode": "ECONOMICAL", + "mm2": 1, + "enable_spv_proof": true, + "block_headers_verification_params": { + "difficulty_check": true, + "constant_difficulty": false, + "difficulty_algorithm": "Bitcoin Mainnet", + "genesis_block_header": "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299" + }, + "required_confirmations": 1, + "avg_blocktime": 10, + "derivation_path": "m/84'/0'", + "protocol": { + "type": "UTXO" + } + }) +} + pub fn btc_with_spv_conf() -> Json { json!({ "coin": "BTC", @@ -367,10 +481,37 @@ pub fn tbtc_with_spv_conf() -> Json { }) } +pub fn eth_testnet_conf() -> Json { + json!({ + "coin": "ETH", + "name": "ethereum", + "derivation_path": "m/44'/60'", + "protocol": { + "type": "ETH" + } + }) +} + +pub fn eth_jst_testnet_conf() -> Json { + json!({ + "coin": "JST", + "name": "jst", + "derivation_path": "m/44'/60'", + "protocol": { + "type": "ERC20", + "protocol_data": { + "platform": "ETH", + "contract_address": "0x2b294F029Fde858b2c62184e8390591755521d8E" + } + } + }) +} + pub fn iris_testnet_conf() -> Json { json!({ - "coin":"IRIS-TEST", + "coin": "IRIS-TEST", "avg_block_time": 5, + "derivation_path": "m/44'/566'", "protocol":{ "type":"TENDERMINT", "protocol_data": { @@ -384,9 +525,11 @@ pub fn iris_testnet_conf() -> Json { } pub fn iris_nimda_testnet_conf() -> Json { - json!({"coin":"IRIS-NIMDA", - "protocol":{ - "type":"TENDERMINTTOKEN", + json!({ + "coin": "IRIS-NIMDA", + "derivation_path": "m/44'/566'", + "protocol": { + "type": "TENDERMINTTOKEN", "protocol_data": { "platform": "IRIS-TEST", "decimals": 6, @@ -410,6 +553,42 @@ pub fn usdc_ibc_iris_testnet_conf() -> Json { }) } +/// `245` is SLP coin type within the derivation path. +pub fn tbch_for_slp_conf() -> Json { + json!({ + "coin": "tBCH", + "pubtype": 0, + "p2shtype": 5, + "mm2": 1, + "derivation_path": "m/44'/245'", + "protocol": { + "type": "BCH", + "protocol_data": { + "slp_prefix": "slptest" + } + }, + "address_format": { + "format": "cashaddress", + "network": "bchtest" + } + }) +} + +pub fn tbch_usdf_conf() -> Json { + json!({ + "coin": "USDF", + "protocol": { + "type": "SLPTOKEN", + "protocol_data": { + "decimals": 4, + "token_id": "bb309e48930671582bea508f9a1d9b491e49b69be3d6f372dc08da2ac6e90eb7", + "platform": "tBCH", + "required_confirmations": 1 + } + } + }) +} + pub fn tbnb_conf() -> Json { json!({ "coin": "tBNB", @@ -424,6 +603,35 @@ pub fn tbnb_conf() -> Json { }) } +pub fn tqrc20_conf() -> Json { + json!({ + "coin": "QRC20", + "required_confirmations": 0, + "pubtype": 120, + "p2shtype": 50, + "wiftype": 128, + "txfee": 0, + "mm2": 1, + "mature_confirmations": 2000, + "derivation_path": "m/44'/2301'", + "protocol": { + "type": "QRC20", + "protocol_data": { + "platform": "QTUM", + "contract_address": "0xd362e096e873eb7907e205fadc6175c6fec7bc44" + } + } + }) +} + +pub fn mm_ctx_with_iguana(passphrase: Option<&str>) -> MmArc { + const DEFAULT_IGUANA_PASSPHRASE: &str = "123"; + + let ctx = MmCtxBuilder::default().into_mm_arc(); + CryptoCtx::init_with_iguana_passphrase(ctx.clone(), passphrase.unwrap_or(DEFAULT_IGUANA_PASSPHRASE)).unwrap(); + ctx +} + #[cfg(target_arch = "wasm32")] pub fn mm_ctx_with_custom_db() -> MmArc { MmCtxBuilder::new().with_test_db_namespace().into_mm_arc() } @@ -1158,7 +1366,9 @@ pub fn from_env_file(env: Vec) -> (Option, Option) { #[cfg(target_arch = "wasm32")] macro_rules! get_passphrase { ($_env_file:literal, $env:literal) => { - option_env!($env).ok_or_else(|| ERRL!("No such '{}' environment variable", $env)) + option_env!($env) + .map(|pass| pass.to_string()) + .ok_or_else(|| ERRL!("No such '{}' environment variable", $env)) }; }