diff --git a/Cargo.lock b/Cargo.lock index 431959e5c5..5754ab423a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4439,6 +4439,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", + "tokio", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index a6cd170627..40071058a1 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -20,36 +20,17 @@ // // Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // -use super::eth::Action::{Call, Create}; -use super::watcher_common::{validate_watcher_reward, REWARD_GAS_AMOUNT}; -use super::*; -use crate::coin_balance::{EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, - HDWalletBalance, HDWalletBalanceOps}; -use crate::eth::eth_rpc::ETH_RPC_REQUEST_TIMEOUT; -use crate::eth::web3_transport::websocket_transport::{WebsocketTransport, WebsocketTransportNode}; -use crate::hd_wallet::{HDAccountOps, HDCoinAddress, HDCoinWithdrawOps, HDConfirmAddress, HDPathAccountToAddressId, - HDWalletCoinOps, HDXPubExtractor}; -use crate::lp_price::get_base_price_in_rel; -use crate::nft::nft_errors::ParseContractTypeError; -use crate::nft::nft_structs::{ContractType, ConvertChain, NftInfo, TransactionNftDetails, WithdrawErc1155, - WithdrawErc721}; -use crate::nft::WithdrawNftResult; -use crate::rpc_command::account_balance::{AccountBalanceParams, AccountBalanceRpcOps, HDAccountBalanceResponse}; -use crate::rpc_command::get_new_address::{GetNewAddressParams, GetNewAddressResponse, GetNewAddressRpcError, - GetNewAddressRpcOps}; -use crate::rpc_command::hd_account_balance_rpc_error::HDAccountBalanceRpcError; -use crate::rpc_command::init_account_balance::{InitAccountBalanceParams, InitAccountBalanceRpcOps}; -use crate::rpc_command::init_create_account::{CreateAccountRpcError, CreateAccountState, CreateNewAccountParams, - InitCreateAccountRpcOps}; -use crate::rpc_command::init_scan_for_new_addresses::{InitScanAddressesRpcOps, ScanAddressesParams, - ScanAddressesResponse}; -use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawTaskHandleShared}; -use crate::rpc_command::{account_balance, get_new_address, init_account_balance, init_create_account, - init_scan_for_new_addresses}; -use crate::{coin_balance, scan_for_new_addresses_impl, BalanceResult, CoinWithDerivationMethod, DerivationMethod, - DexFee, Eip1559Ops, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, - PrivKeyPolicy, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, - ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; + +// 1. Standard library imports +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; +use std::ops::Deref; +use std::str::{from_utf8, FromStr}; +use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +// 2. External crates imports use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -62,13 +43,17 @@ use common::{now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{Bip44Chain, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; use derive_more::Display; +use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, + InfuraGasApiCaller}; use enum_derives::EnumFromStringify; +use eth_hd_wallet::EthHDWallet; +use eth_rpc::ETH_RPC_REQUEST_TIMEOUT; +use eth_withdraw::{EthWithdraw, InitEthWithdraw, StandardEthWithdraw}; use ethabi::{Contract, Function, Token}; use ethcore_transaction::tx_builders::TxBuilderError; use ethcore_transaction::{Action, TransactionWrapper, TransactionWrapperBuilder as UnSignedEthTxBuilder, UnverifiedEip1559Transaction, UnverifiedEip2930Transaction, UnverifiedLegacyTransaction, UnverifiedTransactionWrapper}; -pub use ethcore_transaction::{SignedTransaction as SignedEthTx, TxType}; use ethereum_types::{Address, H160, H256, U256}; use ethkey::{public_to_address, sign, verify_address, KeyPair, Public, Signature}; use futures::compat::Future01CompatExt; @@ -80,7 +65,7 @@ use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; use mm2_number::bigdecimal_custom::CheckedDivision; use mm2_number::{BigDecimal, BigUint, MmNumber}; -#[cfg(test)] use mocktopus::macros::*; +use nonce::ParityNonce; use rand::seq::SliceRandom; use rlp::{DecoderError, Encodable, RlpStream}; use rpc::v1::types::Bytes as BytesJson; @@ -88,17 +73,48 @@ use secp256k1::PublicKey; use serde_json::{self as json, Value as Json}; use serialization::{CompactInteger, Serializable, Stream}; use sha3::{Digest, Keccak256}; -use std::collections::HashMap; -use std::convert::{TryFrom, TryInto}; -use std::ops::Deref; -use std::str::from_utf8; -use std::str::FromStr; -use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; -use std::sync::{Arc, Mutex}; -use std::time::Duration; +use v2_activation::{build_address_and_priv_key_policy, eth_account_db_id, eth_shared_db_id, EthActivationV2Error}; use web3::types::{Action as TraceAction, BlockId, BlockNumber, Bytes, CallRequest, FilterBuilder, Log, Trace, TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, U64}; use web3::{self, Web3}; +use web3_transport::http_transport::HttpTransportNode; +use web3_transport::websocket_transport::{WebsocketTransport, WebsocketTransportNode}; +use web3_transport::Web3Transport; + +// 3. Internal crate imports +use crate::coin_balance::{EnableCoinBalanceError, EnabledCoinBalanceParams, HDAccountBalance, HDAddressBalance, + HDWalletBalance, HDWalletBalanceOps}; +use crate::hd_wallet::{HDAccountOps, HDCoinAddress, HDCoinWithdrawOps, HDConfirmAddress, HDPathAccountToAddressId, + HDWalletCoinOps, HDXPubExtractor}; +use crate::lp_price::get_base_price_in_rel; +use crate::nft::nft_errors::ParseContractTypeError; +use crate::nft::nft_structs::{ContractType, ConvertChain, NftInfo, TransactionNftDetails, WithdrawErc1155, + WithdrawErc721}; +use crate::nft::WithdrawNftResult; +use crate::rpc_command::account_balance::{AccountBalanceParams, AccountBalanceRpcOps, HDAccountBalanceResponse}; +use crate::rpc_command::get_new_address::{GetNewAddressParams, GetNewAddressResponse, GetNewAddressRpcError, + GetNewAddressRpcOps}; +use crate::rpc_command::hd_account_balance_rpc_error::HDAccountBalanceRpcError; +use crate::rpc_command::init_account_balance::{InitAccountBalanceParams, InitAccountBalanceRpcOps}; +use crate::rpc_command::init_create_account::{CreateAccountRpcError, CreateAccountState, CreateNewAccountParams, + InitCreateAccountRpcOps}; +use crate::rpc_command::init_scan_for_new_addresses::{InitScanAddressesRpcOps, ScanAddressesParams, + ScanAddressesResponse}; +use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawTaskHandleShared}; +use crate::rpc_command::{account_balance, get_new_address, init_account_balance, init_create_account, + init_scan_for_new_addresses}; +use crate::{coin_balance, scan_for_new_addresses_impl, BalanceResult, CoinWithDerivationMethod, DerivationMethod, + DexFee, Eip1559Ops, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, + PrivKeyPolicy, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, + ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; + +// 4. Local module imports +use super::eth::Action::{Call, Create}; +use super::watcher_common::{validate_watcher_reward, REWARD_GAS_AMOUNT}; +use super::*; + +// 5. Conditionally compiled imports +#[cfg(test)] use mocktopus::macros::*; cfg_wasm32! { use common::{now_ms, wait_until_ms}; @@ -108,56 +124,30 @@ cfg_wasm32! { use web3::types::TransactionRequest; } -use super::{coin_conf, lp_coinfind_or_err, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, - CoinBalance, CoinFutSpawner, CoinProtocol, CoinTransportMetrics, CoinsContext, ConfirmPaymentInput, - EthValidateFeeArgs, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, - MarketCoinOps, MmCoin, MmCoinEnum, MyAddressError, MyWalletAddress, NegotiateSwapContractAddrErr, - NumConversError, NumConversResult, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, RefundPaymentArgs, - RefundResult, RewardTarget, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignEthTransactionParams, - SignRawTransactionEnum, SignRawTransactionRequest, SignatureError, SignatureResult, SpendPaymentArgs, - SwapOps, SwapTxFeePolicy, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, - TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, TransactionEnum, TransactionErr, - TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, - INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; -pub use rlp; cfg_native! { use std::path::PathBuf; } +// 6. Re-exports +pub use ethcore_transaction::{SignedTransaction as SignedEthTx, TxType}; +pub use rlp; + +// 7. Local modules declarations +mod eip1559_gas_fee; +pub(crate) use eip1559_gas_fee::FeePerGasEstimated; + mod eth_balance_events; +pub mod eth_hd_wallet; mod eth_rpc; +pub(crate) mod eth_swap_v2; #[cfg(test)] mod eth_tests; #[cfg(target_arch = "wasm32")] mod eth_wasm_tests; +mod eth_withdraw; #[cfg(any(test, target_arch = "wasm32"))] mod for_tests; pub(crate) mod nft_swap_v2; -mod web3_transport; -use web3_transport::{http_transport::HttpTransportNode, Web3Transport}; - -pub mod eth_hd_wallet; -use eth_hd_wallet::EthHDWallet; - -#[path = "eth/v2_activation.rs"] pub mod v2_activation; -use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; - -mod eth_withdraw; -use eth_withdraw::{EthWithdraw, InitEthWithdraw, StandardEthWithdraw}; - mod nonce; -use nonce::ParityNonce; - -mod eip1559_gas_fee; -pub(crate) use eip1559_gas_fee::FeePerGasEstimated; -use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, - InfuraGasApiCaller}; -pub(crate) mod eth_swap_v2; +#[path = "eth/v2_activation.rs"] pub mod v2_activation; +mod web3_transport; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 @@ -737,16 +727,16 @@ macro_rules! tx_type_from_pay_for_gas_option { impl EthCoinImpl { #[cfg(not(target_arch = "wasm32"))] - fn eth_traces_path(&self, ctx: &MmArc, my_address: Address) -> PathBuf { - ctx.dbdir() + fn eth_traces_path(&self, ctx: &MmArc, my_address: Address, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id) .join("TRANSACTIONS") .join(format!("{}_{:#02x}_trace.json", self.ticker, my_address)) } /// Load saved ETH traces from local DB #[cfg(not(target_arch = "wasm32"))] - fn load_saved_traces(&self, ctx: &MmArc, my_address: Address) -> Option { - let content = gstuff::slurp(&self.eth_traces_path(ctx, my_address)); + fn load_saved_traces(&self, ctx: &MmArc, my_address: Address, db_id: Option<&str>) -> Option { + let content = gstuff::slurp(&self.eth_traces_path(ctx, my_address, db_id)); if content.is_empty() { None } else { @@ -759,54 +749,59 @@ impl EthCoinImpl { /// Load saved ETH traces from local DB #[cfg(target_arch = "wasm32")] - fn load_saved_traces(&self, _ctx: &MmArc, _my_address: Address) -> Option { + fn load_saved_traces(&self, _ctx: &MmArc, _my_address: Address, _db_id: Option<&str>) -> Option { common::panic_w("'load_saved_traces' is not implemented in WASM"); unreachable!() } /// Store ETH traces to local DB #[cfg(not(target_arch = "wasm32"))] - fn store_eth_traces(&self, ctx: &MmArc, my_address: Address, traces: &SavedTraces) { + fn store_eth_traces(&self, ctx: &MmArc, my_address: Address, traces: &SavedTraces, db_id: Option<&str>) { let content = json::to_vec(traces).unwrap(); - let tmp_file = format!("{}.tmp", self.eth_traces_path(ctx, my_address).display()); + let tmp_file = format!("{}.tmp", self.eth_traces_path(ctx, my_address, db_id).display()); std::fs::write(&tmp_file, content).unwrap(); - std::fs::rename(tmp_file, self.eth_traces_path(ctx, my_address)).unwrap(); + std::fs::rename(tmp_file, self.eth_traces_path(ctx, my_address, db_id)).unwrap(); } /// Store ETH traces to local DB #[cfg(target_arch = "wasm32")] - fn store_eth_traces(&self, _ctx: &MmArc, _my_address: Address, _traces: &SavedTraces) { + fn store_eth_traces(&self, _ctx: &MmArc, _my_address: Address, _traces: &SavedTraces, _db_id: Option<&str>) { common::panic_w("'store_eth_traces' is not implemented in WASM"); unreachable!() } #[cfg(not(target_arch = "wasm32"))] - fn erc20_events_path(&self, ctx: &MmArc, my_address: Address) -> PathBuf { - ctx.dbdir() + fn erc20_events_path(&self, ctx: &MmArc, my_address: Address, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id) .join("TRANSACTIONS") .join(format!("{}_{:#02x}_events.json", self.ticker, my_address)) } /// Store ERC20 events to local DB #[cfg(not(target_arch = "wasm32"))] - fn store_erc20_events(&self, ctx: &MmArc, my_address: Address, events: &SavedErc20Events) { + fn store_erc20_events(&self, ctx: &MmArc, my_address: Address, events: &SavedErc20Events, db_id: Option<&str>) { let content = json::to_vec(events).unwrap(); - let tmp_file = format!("{}.tmp", self.erc20_events_path(ctx, my_address).display()); + let tmp_file = format!("{}.tmp", self.erc20_events_path(ctx, my_address, db_id).display()); std::fs::write(&tmp_file, content).unwrap(); - std::fs::rename(tmp_file, self.erc20_events_path(ctx, my_address)).unwrap(); + std::fs::rename(tmp_file, self.erc20_events_path(ctx, my_address, db_id)).unwrap(); } /// Store ERC20 events to local DB #[cfg(target_arch = "wasm32")] - fn store_erc20_events(&self, _ctx: &MmArc, _my_address: Address, _events: &SavedErc20Events) { + fn store_erc20_events(&self, _ctx: &MmArc, _my_address: Address, _events: &SavedErc20Events, _db_id: Option<&str>) { common::panic_w("'store_erc20_events' is not implemented in WASM"); unreachable!() } /// Load saved ERC20 events from local DB #[cfg(not(target_arch = "wasm32"))] - fn load_saved_erc20_events(&self, ctx: &MmArc, my_address: Address) -> Option { - let content = gstuff::slurp(&self.erc20_events_path(ctx, my_address)); + fn load_saved_erc20_events( + &self, + ctx: &MmArc, + my_address: Address, + db_id: Option<&str>, + ) -> Option { + let content = gstuff::slurp(&self.erc20_events_path(ctx, my_address, db_id)); if content.is_empty() { None } else { @@ -819,7 +814,12 @@ impl EthCoinImpl { /// Load saved ERC20 events from local DB #[cfg(target_arch = "wasm32")] - fn load_saved_erc20_events(&self, _ctx: &MmArc, _my_address: Address) -> Option { + fn load_saved_erc20_events( + &self, + _ctx: &MmArc, + _my_address: Address, + _db_id: Option<&str>, + ) -> Option { common::panic_w("'load_saved_erc20_events' is not implemented in WASM"); unreachable!() } @@ -949,7 +949,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit EthCoinType::Erc20 { .. } => { return MmError::err(WithdrawError::InternalError( "Erc20 coin type doesnt support withdraw nft".to_owned(), - )) + )); }, EthCoinType::Nft { .. } => return MmError::err(WithdrawError::NftProtocolNotSupported), }; @@ -1098,6 +1098,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd #[derive(Clone)] pub struct EthCoin(Arc); + impl Deref for EthCoin { type Target = EthCoinImpl; fn deref(&self) -> &EthCoinImpl { &self.0 } @@ -2871,7 +2872,8 @@ impl EthCoin { }, }; - let mut saved_traces = match self.load_saved_traces(ctx, my_address) { + let mut saved_traces = match self.load_saved_traces(ctx, my_address, self.account_db_id().await.as_deref()) + { Some(traces) => traces, None => SavedTraces { traces: vec![], @@ -2883,7 +2885,10 @@ impl EthCoin { "blocks_left": saved_traces.earliest_block.as_u64(), })); - let mut existing_history = match self.load_history_from_file(ctx).compat().await { + let mut existing_history = match self + .load_history_from_file(ctx, self.account_db_id().await.as_deref()) + .await + { Ok(history) => history, Err(e) => { ctx.log.log( @@ -2961,7 +2966,7 @@ impl EthCoin { } else { 0.into() }; - self.store_eth_traces(ctx, my_address, &saved_traces); + self.store_eth_traces(ctx, my_address, &saved_traces, self.account_db_id().await.as_deref()); } if current_block > saved_traces.latest_block { @@ -3017,7 +3022,7 @@ impl EthCoin { saved_traces.traces.extend(to_traces_after_latest); saved_traces.latest_block = current_block; - self.store_eth_traces(ctx, my_address, &saved_traces); + self.store_eth_traces(ctx, my_address, &saved_traces, self.account_db_id().await.as_deref()); } saved_traces.traces.sort_by(|a, b| b.block_number.cmp(&a.block_number)); for trace in saved_traces.traces { @@ -3174,7 +3179,7 @@ impl EthCoin { existing_history.push(details); - if let Err(e) = self.save_history_to_file(ctx, existing_history.clone()).compat().await { + if let Err(e) = self.save_history_to_file(ctx, existing_history.clone()).await { ctx.log.log( "", &[&"tx_history", &self.ticker], @@ -3245,14 +3250,15 @@ impl EthCoin { }, }; - let mut saved_events = match self.load_saved_erc20_events(ctx, my_address) { - Some(events) => events, - None => SavedErc20Events { - events: vec![], - earliest_block: current_block, - latest_block: current_block, - }, - }; + let mut saved_events = + match self.load_saved_erc20_events(ctx, my_address, self.account_db_id().await.as_deref()) { + Some(events) => events, + None => SavedErc20Events { + events: vec![], + earliest_block: current_block, + latest_block: current_block, + }, + }; *self.history_sync_state.lock().unwrap() = HistorySyncState::InProgress(json!({ "blocks_left": saved_events.earliest_block, })); @@ -3324,7 +3330,7 @@ impl EthCoin { } else { 0.into() }; - self.store_erc20_events(ctx, my_address, &saved_events); + self.store_erc20_events(ctx, my_address, &saved_events, self.account_db_id().await.as_deref()); } if current_block > saved_events.latest_block { @@ -3381,7 +3387,7 @@ impl EthCoin { saved_events.events.extend(from_events_after_latest); saved_events.events.extend(to_events_after_latest); saved_events.latest_block = current_block; - self.store_erc20_events(ctx, my_address, &saved_events); + self.store_erc20_events(ctx, my_address, &saved_events, self.account_db_id().await.as_deref()); } let all_events: HashMap<_, _> = saved_events @@ -3394,7 +3400,10 @@ impl EthCoin { all_events.sort_by(|a, b| b.block_number.unwrap().cmp(&a.block_number.unwrap())); for event in all_events { - let mut existing_history = match self.load_history_from_file(ctx).compat().await { + let mut existing_history = match self + .load_history_from_file(ctx, self.account_db_id().await.as_deref()) + .await + { Ok(history) => history, Err(e) => { ctx.log.log( @@ -3554,7 +3563,7 @@ impl EthCoin { existing_history.push(details); - if let Err(e) = self.save_history_to_file(ctx, existing_history).compat().await { + if let Err(e) = self.save_history_to_file(ctx, existing_history).await { ctx.log.log( "", &[&"tx_history", &self.ticker], @@ -3796,21 +3805,21 @@ impl EthCoin { amount, wait_for_required_allowance_until, ) - .map_err(move |e| { - TransactionErr::Plain(ERRL!( + .map_err(move |e| { + TransactionErr::Plain(ERRL!( "Allowed value was not updated in time after sending approve transaction {:02x}: {}", approved.tx_hash_as_bytes(), e )) - }) - .and_then(move |_| { - arc.sign_and_send_transaction( - value, - Call(swap_contract_address), - data, - gas, - ) - }) + }) + .and_then(move |_| { + arc.sign_and_send_transaction( + value, + Call(swap_contract_address), + data, + gas, + ) + }) }), ) } else { @@ -5759,6 +5768,12 @@ impl MmCoin for EthCoin { tokens.remove(ticker); }; } + + async fn account_db_id(&self) -> Option { eth_account_db_id(self).await } + + async fn shared_db_id(&self, ctx: &MmArc) -> Option { + eth_shared_db_id(self, ctx).await.or(eth_account_db_id(self).await) + } } pub trait TryToAddress { diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 15af41b3c2..2080f40950 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,5 +1,7 @@ use super::*; -use crate::eth::web3_transport::http_transport::HttpTransport; +use crate::eth::eth_hd_wallet::EthHDWallet; +use crate::eth::web3_transport::http_transport::{HttpTransport, HttpTransportNode}; +use crate::eth::web3_transport::Web3Transport; use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDPathAccountToAddressId, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; use crate::nft::get_nfts_for_activation; @@ -10,6 +12,8 @@ use common::executor::AbortedError; use crypto::{trezor::TrezorError, Bip32Error, CryptoCtxError, HwError}; use enum_derives::EnumFromTrait; use instant::Instant; +#[cfg(not(target_arch = "wasm32"))] +use mm2_core::sql_connection_pool::run_db_migration_for_new_pubkey; use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; @@ -701,17 +705,32 @@ pub(crate) async fn build_address_and_priv_key_policy( // Consider storing `derivation_path` at `EthCoinImpl`. let path_to_coin = json::from_value(conf["derivation_path"].clone()) .map_to_mm(|e| EthActivationV2Error::ErrorDeserializingDerivationPath(e.to_string()))?; - let raw_priv_key = global_hd_ctx - .derive_secp256k1_secret( - &path_to_address - .to_derivation_path(&path_to_coin) - .mm_err(|e| EthActivationV2Error::InvalidPathToAddress(e.to_string()))?, - ) - .mm_err(|e| EthActivationV2Error::InternalError(e.to_string()))?; - let activated_key = KeyPair::from_secret_slice(raw_priv_key.as_slice()) - .map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string()))?; + let activated_key = { + let raw_priv_key = global_hd_ctx + .derive_secp256k1_secret( + &path_to_address + .to_derivation_path(&path_to_coin) + .mm_err(|e| EthActivationV2Error::InvalidPathToAddress(e.to_string()))?, + ) + .mm_err(|e| EthActivationV2Error::InternalError(e.to_string()))?; + KeyPair::from_secret_slice(raw_priv_key.as_slice()) + .map_to_mm(|e| EthActivationV2Error::InternalError(e.to_string()))? + }; let bip39_secp_priv_key = global_hd_ctx.root_priv_key().clone(); + #[cfg(not(target_arch = "wasm32"))] + { + let pubkey = { + let pubkey = Public::from_slice(activated_key.public().as_bytes()); + let addr = display_eth_address(&public_to_address(&pubkey)); + addr.trim_start_matches("0x").to_string() + }; + + run_db_migration_for_new_pubkey(ctx, pubkey) + .await + .map_to_mm(EthActivationV2Error::InternalError)?; + } + let hd_wallet_rmd160 = *ctx.rmd160(); let hd_wallet_storage = HDWalletCoinStorage::init_with_rmd160(ctx, ticker.to_string(), hd_wallet_rmd160) .await @@ -726,6 +745,7 @@ pub(crate) async fn build_address_and_priv_key_policy( enabled_address: *path_to_address, gap_limit, }; + let derivation_method = DerivationMethod::HDWallet(hd_wallet); Ok(( EthPrivKeyPolicy::HDWallet { @@ -956,3 +976,19 @@ fn compress_public_key(uncompressed: H520) -> MmResult Option { + // Use the hd_wallet_rmd160 as the db_id in HD mode only since it's unique to a device and not tied to a single address + coin.derivation_method().hd_wallet().map(|_| ctx.default_shared_db_id()) +} + +pub(super) async fn eth_account_db_id(coin: &EthCoin) -> Option { + match coin.derivation_method() { + DerivationMethod::HDWallet(hd_wallet) => hd_wallet.get_enabled_address().await.map(|addr| { + let pubkey = Public::from_slice(addr.pubkey().as_bytes()); + let addr = display_eth_address(&public_to_address(&pubkey)); + addr.trim_start_matches("0x").to_string() + }), + _ => None, + } +} diff --git a/mm2src/coins/hd_wallet/storage/mod.rs b/mm2src/coins/hd_wallet/storage/mod.rs index cb8f11b01d..1453603047 100644 --- a/mm2src/coins/hd_wallet/storage/mod.rs +++ b/mm2src/coins/hd_wallet/storage/mod.rs @@ -14,6 +14,7 @@ use std::ops::Deref; #[cfg(target_arch = "wasm32")] mod wasm_storage; #[cfg(any(test, target_arch = "wasm32"))] mod mock_storage; + #[cfg(any(test, target_arch = "wasm32"))] pub(crate) use mock_storage::HDWalletMockStorage; @@ -228,11 +229,11 @@ impl Default for HDWalletCoinStorage { impl HDWalletCoinStorage { pub async fn init(ctx: &MmArc, coin: String) -> HDWalletStorageResult { - let inner = Box::new(HDWalletStorageInstance::init(ctx).await?); let crypto_ctx = CryptoCtx::from_ctx(ctx)?; let hd_wallet_rmd160 = crypto_ctx .hw_wallet_rmd160() .or_mm_err(|| HDWalletStorageError::HDWalletUnavailable)?; + let inner = Box::new(HDWalletStorageInstance::init(ctx).await?); Ok(HDWalletCoinStorage { coin, hd_wallet_rmd160, diff --git a/mm2src/coins/hd_wallet/storage/sqlite_storage.rs b/mm2src/coins/hd_wallet/storage/sqlite_storage.rs index 898f4c8823..274e9255b7 100644 --- a/mm2src/coins/hd_wallet/storage/sqlite_storage.rs +++ b/mm2src/coins/hd_wallet/storage/sqlite_storage.rs @@ -101,11 +101,11 @@ impl HDWalletStorageInternalOps for HDWalletSqliteStorage { where Self: Sized, { - let shared = ctx.shared_sqlite_conn.as_option().or_mm_err(|| { + let shared = ctx.shared_sqlite_conn_opt().or_mm_err(|| { HDWalletStorageError::Internal("'MmCtx::shared_sqlite_conn' is not initialized".to_owned()) })?; let storage = HDWalletSqliteStorage { - conn: SqliteConnShared::downgrade(shared), + conn: SqliteConnShared::downgrade(&shared), }; storage.init_tables().await?; Ok(storage) @@ -279,7 +279,8 @@ pub(crate) async fn get_all_storage_items(ctx: &MmArc) -> Vec| HDAccountStorageItem::try_from(row)) diff --git a/mm2src/coins/hd_wallet/storage/wasm_storage.rs b/mm2src/coins/hd_wallet/storage/wasm_storage.rs index 4654474236..f87fcb05a4 100644 --- a/mm2src/coins/hd_wallet/storage/wasm_storage.rs +++ b/mm2src/coins/hd_wallet/storage/wasm_storage.rs @@ -21,7 +21,7 @@ const WALLET_ID_INDEX: &str = "wallet_id"; /// * account_id - HD account id const WALLET_ACCOUNT_ID_INDEX: &str = "wallet_account_id"; -type HDWalletDbLocked<'a> = DbLocked<'a, HDWalletDb>; +type HDWalletDbLocked = DbLocked; impl From for HDWalletStorageError { fn from(e: DbTransactionError) -> Self { @@ -53,17 +53,17 @@ impl From for HDWalletStorageError { let stringified_error = e.to_string(); match e { // We don't expect that the `String` and `u32` types serialization to fail. - CursorError::ErrorSerializingIndexFieldValue {..} + CursorError::ErrorSerializingIndexFieldValue { .. } // We don't expect that the `String` and `u32` types deserialization to fail. - | CursorError::ErrorDeserializingIndexValue {..} - | CursorError::ErrorOpeningCursor {..} - | CursorError::AdvanceError {..} - | CursorError::InvalidKeyRange {..} - | CursorError::TypeMismatch {..} - | CursorError::IncorrectNumberOfKeysPerIndex {..} + | CursorError::ErrorDeserializingIndexValue { .. } + | CursorError::ErrorOpeningCursor { .. } + | CursorError::AdvanceError { .. } + | CursorError::InvalidKeyRange { .. } + | CursorError::TypeMismatch { .. } + | CursorError::IncorrectNumberOfKeysPerIndex { .. } | CursorError::UnexpectedState(..) - | CursorError::IncorrectUsage {..} => HDWalletStorageError::Internal(stringified_error), - CursorError::ErrorDeserializingItem {..} => HDWalletStorageError::ErrorDeserializing(stringified_error), + | CursorError::IncorrectUsage { .. } => HDWalletStorageError::Internal(stringified_error), + CursorError::ErrorDeserializingItem { .. } => HDWalletStorageError::ErrorDeserializing(stringified_error), } } } @@ -270,8 +270,8 @@ impl HDWalletIndexedDbStorage { .or_mm_err(|| HDWalletStorageError::Internal("'HDWalletIndexedDbStorage::db' doesn't exist".to_owned())) } - async fn lock_db_mutex(db: &SharedDb) -> HDWalletStorageResult> { - db.get_or_initialize().await.mm_err(HDWalletStorageError::from) + async fn lock_db_mutex(db: &SharedDb) -> HDWalletStorageResult { + db.get_or_initialize_shared().await.mm_err(HDWalletStorageError::from) } async fn find_account( @@ -314,9 +314,10 @@ impl HDWalletIndexedDbStorage { } /// This function is used in `hd_wallet_storage::tests`. +#[cfg(any(test, target_arch = "wasm32"))] pub(super) async fn get_all_storage_items(ctx: &MmArc) -> Vec { let coins_ctx = CoinsContext::from_ctx(ctx).unwrap(); - let db = coins_ctx.hd_wallet_db.get_or_initialize().await.unwrap(); + let db = coins_ctx.hd_wallet_db.get_or_initialize_shared().await.unwrap(); let transaction = db.inner.transaction().await.unwrap(); let table = transaction.table::().await.unwrap(); table diff --git a/mm2src/coins/lightning/ln_utils.rs b/mm2src/coins/lightning/ln_utils.rs index 693b7c3a4f..88918f776c 100644 --- a/mm2src/coins/lightning/ln_utils.rs +++ b/mm2src/coins/lightning/ln_utils.rs @@ -39,7 +39,7 @@ pub type ChannelManager = SimpleArcChannelManager, Arc, Arc>; #[inline] -fn ln_data_dir(ctx: &MmArc, ticker: &str) -> PathBuf { ctx.dbdir().join("LIGHTNING").join(ticker) } +fn ln_data_dir(ctx: &MmArc, ticker: &str) -> PathBuf { ctx.dbdir(None).join("LIGHTNING").join(ticker) } #[inline] fn ln_data_backup_dir(ctx: &MmArc, path: Option, ticker: &str) -> Option { @@ -68,15 +68,11 @@ pub async fn init_persister( Ok(persister) } -pub async fn init_db(ctx: &MmArc, ticker: String) -> EnableLightningResult { - let db = SqliteLightningDB::new( - ticker, - ctx.sqlite_connection - .ok_or(MmError::new(EnableLightningError::DbError( - "sqlite_connection is not initialized".into(), - )))? - .clone(), - )?; +pub async fn init_db(ctx: &MmArc, ticker: String, db_id: Option<&str>) -> EnableLightningResult { + let db = ctx.sqlite_conn_opt(db_id).or_mm_err(|| { + EnableLightningError::DbError("'MmCtx::sqlite_connection' is not found or initialized".to_owned()) + })?; + let db = SqliteLightningDB::new(ticker, db)?; if !db.is_db_initialized().await? { db.init_db().await?; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 1af6027e30..1e1681602a 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -57,7 +57,7 @@ use enum_derives::{EnumFromStringify, EnumFromTrait}; use ethereum_types::H256; use futures::compat::Future01CompatExt; use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; -use futures::{FutureExt, TryFutureExt}; +use futures::TryFutureExt; use futures01::Future; use hex::FromHexError; use http::{Response, StatusCode}; @@ -91,7 +91,7 @@ cfg_native! { use crate::lightning::ln_conf::PlatformCoinConfirmationTargets; use ::lightning::ln::PaymentHash as LightningPayment; use async_std::fs; - use futures::AsyncWriteExt; + use futures::{FutureExt, AsyncWriteExt}; use lightning_invoice::{Invoice, ParseOrSemanticError}; use std::io; use std::path::PathBuf; @@ -102,7 +102,7 @@ cfg_wasm32! { use hd_wallet::HDWalletDb; use mm2_db::indexed_db::{ConstructibleDb, DbLocked, SharedDb}; use tx_history_storage::wasm::{clear_tx_history, load_tx_history, save_tx_history, TxHistoryDb}; - pub type TxHistoryDbLocked<'a> = DbLocked<'a, TxHistoryDb>; + pub type TxHistoryDbLocked = DbLocked; } // using custom copy of try_fus as futures crate was renamed to futures01 @@ -129,7 +129,7 @@ macro_rules! try_tx_fus_err { ($err: expr) => { return Box::new(futures01::future::err(crate::TransactionErr::Plain(ERRL!( "{:?}", $err - )))) + )))); }; } @@ -141,7 +141,7 @@ macro_rules! try_tx_fus_opt { None => { return Box::new(futures01::future::err(crate::TransactionErr::Plain(ERRL!( "{:?}", $err - )))) + )))); }, } }; @@ -162,7 +162,7 @@ macro_rules! try_tx_fus { return Box::new(futures01::future::err(crate::TransactionErr::TxRecoverable( TransactionEnum::from($tx), ERRL!("{:?}", err), - ))) + ))); }, } }; @@ -179,7 +179,7 @@ macro_rules! try_tx_s { file!(), line!(), err - ))) + ))); }, } }; @@ -190,7 +190,7 @@ macro_rules! try_tx_s { return Err(crate::TransactionErr::TxRecoverable( TransactionEnum::from($tx), format!("{}:{}] {:?}", file!(), line!(), err), - )) + )); }, } }; @@ -227,59 +227,20 @@ macro_rules! ok_or_continue_after_sleep { } pub mod coin_balance; -use coin_balance::{AddressBalanceStatus, HDAddressBalance, HDWalletBalanceOps}; - -pub mod lp_price; -pub mod watcher_common; - pub mod coin_errors; -use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentResult}; - #[doc(hidden)] #[cfg(test)] pub mod coins_tests; - pub mod eth; -use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err}; -use eth::GetValidEthWithdrawAddError; -use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, - GetEthAddressError, SignedEthTx}; -use ethereum_types::U256; - pub mod hd_wallet; -use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress, - HDCoinHDAccount, HDExtractPubkeyError, HDPathAccountToAddressId, HDWalletAddress, HDWalletCoinOps, - HDWalletOps, HDWithdrawError, HDXPubExtractor, WithdrawFrom, WithdrawSenderAddress}; - #[cfg(not(target_arch = "wasm32"))] pub mod lightning; +pub mod lp_price; #[cfg_attr(target_arch = "wasm32", allow(dead_code, unused_imports))] pub mod my_tx_history_v2; - +pub mod nft; pub mod qrc20; -use qrc20::{qrc20_coin_with_policy, Qrc20ActivationParams, Qrc20Coin, Qrc20FeeDetails}; - pub mod rpc_command; -use rpc_command::{get_new_address::{GetNewAddressTaskManager, GetNewAddressTaskManagerShared}, - init_account_balance::{AccountBalanceTaskManager, AccountBalanceTaskManagerShared}, - init_create_account::{CreateAccountTaskManager, CreateAccountTaskManagerShared}, - init_scan_for_new_addresses::{ScanAddressesTaskManager, ScanAddressesTaskManagerShared}, - init_withdraw::{WithdrawTaskManager, WithdrawTaskManagerShared}}; - -pub mod tendermint; -use tendermint::htlc::CustomTendermintMsgType; -use tendermint::{CosmosTransaction, TendermintCoin, TendermintFeeDetails, TendermintProtocolInfo, TendermintToken, - TendermintTokenProtocolInfo}; - -#[doc(hidden)] -#[allow(unused_variables)] -pub mod test_coin; -pub use test_coin::TestCoin; - -pub mod tx_history_storage; - #[cfg(feature = "enable-sia")] pub mod siacoin; -#[cfg(feature = "enable-sia")] use siacoin::SiaCoin; - #[doc(hidden)] #[allow(unused_variables)] #[cfg(all( @@ -289,6 +250,15 @@ pub mod tx_history_storage; not(target_arch = "wasm32") ))] pub mod solana; +pub mod tendermint; +#[doc(hidden)] +#[allow(unused_variables)] +pub mod test_coin; +pub mod tx_history_storage; +pub mod utxo; +pub mod watcher_common; +pub mod z_coin; + #[cfg(all( feature = "enable-solana", not(target_os = "ios"), @@ -303,24 +273,37 @@ pub use solana::spl::SplToken; not(target_arch = "wasm32") ))] pub use solana::{SolTransaction, SolanaActivationParams, SolanaCoin, SolanaFeeDetails}; +pub use test_coin::TestCoin; -pub mod utxo; +use coin_balance::{AddressBalanceStatus, BalanceObjectOps, HDAddressBalance, HDWalletBalanceObject, HDWalletBalanceOps}; +use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentResult}; +use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err}; +use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, + GetEthAddressError, GetValidEthWithdrawAddError, SignedEthTx}; +use ethereum_types::U256; +use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress, + HDCoinHDAccount, HDExtractPubkeyError, HDPathAccountToAddressId, HDWalletAddress, HDWalletCoinOps, + HDWalletOps, HDWithdrawError, HDXPubExtractor, WithdrawFrom, WithdrawSenderAddress}; +use nft::nft_errors::GetNftInfoError; +use qrc20::{qrc20_coin_with_policy, Qrc20ActivationParams, Qrc20Coin, Qrc20FeeDetails}; +use rpc_command::{get_new_address::{GetNewAddressTaskManager, GetNewAddressTaskManagerShared}, + init_account_balance::{AccountBalanceTaskManager, AccountBalanceTaskManagerShared}, + init_create_account::{CreateAccountTaskManager, CreateAccountTaskManagerShared}, + init_scan_for_new_addresses::{ScanAddressesTaskManager, ScanAddressesTaskManagerShared}, + init_withdraw::{WithdrawTaskManager, WithdrawTaskManagerShared}}; +use script::Script; +#[cfg(feature = "enable-sia")] use siacoin::SiaCoin; +use tendermint::htlc::CustomTendermintMsgType; +use tendermint::{CosmosTransaction, TendermintCoin, TendermintFeeDetails, TendermintProtocolInfo, TendermintToken, + TendermintTokenProtocolInfo}; 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::slp::{slp_addr_from_pubkey_str, SlpFeeDetails, SlpToken}; use utxo::utxo_common::{big_decimal_from_sat_unsigned, payment_script, WaitForOutputSpendErr}; use utxo::utxo_standard::{utxo_standard_coin_with_policy, UtxoStandardCoin}; use utxo::{swap_proto_v2_scripts, BlockchainNetwork, GenerateTxError, UtxoActivationParams, UtxoFeeDetails, UtxoTx}; - -pub mod nft; -use nft::nft_errors::GetNftInfoError; -use script::Script; - -pub mod z_coin; -use crate::coin_balance::{BalanceObjectOps, HDWalletBalanceObject}; use z_coin::{ZCoin, ZcoinProtocolInfo}; pub type TransactionFut = Box + Send>; @@ -3388,46 +3371,61 @@ pub trait MmCoin: /// Loop collecting coin transaction history and saving it to local DB fn process_history_loop(&self, ctx: MmArc) -> Box + Send>; + /// Retrieves a unique identifier for the account based on the coin's derivation method. + /// E.g, If the coin is derived from an HD wallet, it uses the public key hash of the enabled address as the database ID. + /// If the coin is not derived from an HD wallet, it returns `None`. + async fn account_db_id(&self) -> Option { None } + + // Retrieves db_id for derivation methods (HD wallet vs. non-HD wallet) + // NOTE: this function only needs special handling for coins that supports HD wallet + async fn shared_db_id(&self, _ctx: &MmArc) -> Option { None } + /// Path to tx history file #[cfg(not(target_arch = "wasm32"))] - fn tx_history_path(&self, ctx: &MmArc) -> PathBuf { + fn tx_history_path(&self, ctx: &MmArc, db_id: Option<&str>) -> PathBuf { let my_address = self.my_address().unwrap_or_default(); // BCH cash address format has colon after prefix, e.g. bitcoincash: // Colon can't be used in file names on Windows so it should be escaped let my_address = my_address.replace(':', "_"); - ctx.dbdir() + ctx.dbdir(db_id) .join("TRANSACTIONS") .join(format!("{}_{}.json", self.ticker(), my_address)) } /// Path to tx history migration file #[cfg(not(target_arch = "wasm32"))] - fn tx_migration_path(&self, ctx: &MmArc) -> PathBuf { + fn tx_migration_path(&self, ctx: &MmArc, db_id: Option<&str>) -> PathBuf { let my_address = self.my_address().unwrap_or_default(); // BCH cash address format has colon after prefix, e.g. bitcoincash: // Colon can't be used in file names on Windows so it should be escaped let my_address = my_address.replace(':', "_"); - ctx.dbdir() + ctx.dbdir(db_id) .join("TRANSACTIONS") .join(format!("{}_{}_migration", self.ticker(), my_address)) } /// Loads existing tx history from file, returns empty vector if file is not found /// Cleans the existing file if deserialization fails - fn load_history_from_file(&self, ctx: &MmArc) -> TxHistoryFut> { - load_history_from_file_impl(self, ctx) + async fn load_history_from_file( + &self, + ctx: &MmArc, + db_id: Option<&str>, + ) -> TxHistoryResult> { + load_history_from_file_impl(self, ctx, db_id).await } - fn save_history_to_file(&self, ctx: &MmArc, history: Vec) -> TxHistoryFut<()> { - save_history_to_file_impl(self, ctx, history) + async fn save_history_to_file(&self, ctx: &MmArc, history: Vec) -> TxHistoryResult<()> { + save_history_to_file_impl(self, ctx, history).await } #[cfg(not(target_arch = "wasm32"))] - fn get_tx_history_migration(&self, ctx: &MmArc) -> TxHistoryFut { get_tx_history_migration_impl(self, ctx) } + fn get_tx_history_migration(&self, ctx: &MmArc, db_id: Option<&str>) -> TxHistoryFut { + get_tx_history_migration_impl(self, ctx, db_id) + } #[cfg(not(target_arch = "wasm32"))] - fn update_migration_file(&self, ctx: &MmArc, migration_number: u64) -> TxHistoryFut<()> { - update_migration_file_impl(self, ctx, migration_number) + fn update_migration_file(&self, ctx: &MmArc, migration_number: u64, db_id: Option<&str>) -> TxHistoryFut<()> { + update_migration_file_impl(self, ctx, migration_number, db_id) } /// Transaction history background sync status @@ -3839,7 +3837,7 @@ impl CoinsContext { scan_addresses_manager: ScanAddressesTaskManager::new_shared(), withdraw_task_manager: WithdrawTaskManager::new_shared(), #[cfg(target_arch = "wasm32")] - tx_history_db: ConstructibleDb::new(ctx).into_shared(), + tx_history_db: ConstructibleDb::new(ctx, None).into_shared(), #[cfg(target_arch = "wasm32")] hd_wallet_db: ConstructibleDb::new_shared_db(ctx).into_shared(), }) @@ -3973,8 +3971,8 @@ impl CoinsContext { } #[cfg(target_arch = "wasm32")] - async fn tx_history_db(&self) -> TxHistoryResult> { - Ok(self.tx_history_db.get_or_initialize().await?) + async fn tx_history_db(&self, db_id: Option<&str>) -> TxHistoryResult { + Ok(self.tx_history_db.get_or_initialize(db_id).await?) } #[inline(always)] @@ -4648,11 +4646,11 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result return ERR!("Lightning protocol is not supported by lp_coininit"), #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinProtocol::SOLANA => { - return ERR!("Solana protocol is not supported by lp_coininit - use enable_solana_with_tokens instead") + return ERR!("Solana protocol is not supported by lp_coininit - use enable_solana_with_tokens instead"); }, #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] CoinProtocol::SPLTOKEN { .. } => { - return ERR!("SplToken protocol is not supported by lp_coininit - use enable_spl instead") + return ERR!("SplToken protocol is not supported by lp_coininit - use enable_spl instead"); }, #[cfg(feature = "enable-sia")] CoinProtocol::SIA { .. } => { @@ -4700,7 +4698,7 @@ pub async fn lp_register_coin( let mut coins = cctx.coins.lock().await; match coins.raw_entry_mut().from_key(&ticker) { RawEntryMut::Occupied(_oe) => { - return MmError::err(RegisterCoinError::CoinIsInitializedAlready { coin: ticker.clone() }) + return MmError::err(RegisterCoinError::CoinIsInitializedAlready { coin: ticker.clone() }); }, RawEntryMut::Vacant(ve) => ve.insert(ticker.clone(), MmCoinStruct::new(coin.clone())), }; @@ -4745,6 +4743,36 @@ pub async fn lp_coinfind_any(ctx: &MmArc, ticker: &str) -> Result Result, String> { + find_unique_account_ids(ctx, false).await +} + +/// Finds unique account IDs for active accounts. +pub async fn find_unique_account_ids_active(ctx: &MmArc) -> Result, String> { + find_unique_account_ids(ctx, true).await +} + +/// Finds unique account IDs based on the given context and active status. +async fn find_unique_account_ids(ctx: &MmArc, active_only: bool) -> Result, String> { + // Using a HashSet to ensure uniqueness efficiently + // Initialize with default wallet pubkey as coin.account_db_id() will return None by default. + let mut account_ids = HashSet::from([ctx.rmd160.to_string()]); + + let coin_ctx = try_s!(CoinsContext::from_ctx(ctx)); + let coins = coin_ctx.coins.lock().await; + + for (_, coin) in coins.iter() { + if let Some(account) = coin.inner.account_db_id().await { + if !active_only || coin.is_available() { + account_ids.insert(account.clone()); + } + } + } + + Ok(account_ids) +} + /// Attempts to find a pair of active coins returning None if one is not enabled pub async fn find_pair(ctx: &MmArc, base: &str, rel: &str) -> Result, String> { let fut_base = lp_coinfind(ctx, base); @@ -4879,7 +4907,7 @@ pub async fn remove_delegation(ctx: MmArc, req: RemoveDelegateRequest) -> Delega _ => { return MmError::err(DelegationError::CoinDoesntSupportDelegation { coin: coin.ticker().to_string(), - }) + }); }, } } @@ -4891,7 +4919,7 @@ pub async fn get_staking_infos(ctx: MmArc, req: GetStakingInfosRequest) -> Staki _ => { return MmError::err(StakingInfosError::CoinDoesntSupportStakingInfos { coin: coin.ticker().to_string(), - }) + }); }, } } @@ -4904,7 +4932,7 @@ pub async fn add_delegation(ctx: MmArc, req: AddDelegateRequest) -> DelegationRe _ => { return MmError::err(DelegationError::CoinDoesntSupportDelegation { coin: coin.ticker().to_string(), - }) + }); }, }; match req.staking_details { @@ -4957,7 +4985,10 @@ pub async fn my_tx_history(ctx: MmArc, req: Json) -> Result>, S Err(err) => return ERR!("!lp_coinfind({}): {}", request.coin, err), }; - let history = try_s!(coin.load_history_from_file(&ctx).compat().await); + let history = try_s!( + coin.load_history_from_file(&ctx, coin.account_db_id().await.as_deref()) + .await + ); let total_records = history.len(); let limit = if request.max { total_records } else { request.limit }; @@ -5249,47 +5280,52 @@ pub fn address_by_coin_conf_and_pubkey_str( } #[cfg(target_arch = "wasm32")] -fn load_history_from_file_impl(coin: &T, ctx: &MmArc) -> TxHistoryFut> +async fn load_history_from_file_impl( + coin: &T, + ctx: &MmArc, + db_id: Option<&str>, +) -> TxHistoryResult> where T: MmCoin + ?Sized, { let ctx = ctx.clone(); let ticker = coin.ticker().to_owned(); - let my_address = try_f!(coin.my_address()); + let my_address = coin.my_address()?; + let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); - let fut = async move { - let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); - let db = coins_ctx.tx_history_db().await?; - let err = match load_tx_history(&db, &ticker, &my_address).await { - Ok(history) => return Ok(history), - Err(e) => e, - }; + let db = coins_ctx.tx_history_db(db_id).await?; + let err = match load_tx_history(&db, &ticker, &my_address).await { + Ok(history) => return Ok(history), + Err(e) => e, + }; - if let TxHistoryError::ErrorDeserializing(e) = err.get_inner() { - ctx.log.log( - "🌋", - &[&"tx_history", &ticker.to_owned()], - &ERRL!("Error {} on history deserialization, resetting the cache.", e), - ); - clear_tx_history(&db, &ticker, &my_address).await?; - return Ok(Vec::new()); - } + if let TxHistoryError::ErrorDeserializing(e) = err.get_inner() { + ctx.log.log( + "🌋", + &[&"tx_history", &ticker.to_owned()], + &ERRL!("Error {} on history deserialization, resetting the cache.", e), + ); + clear_tx_history(&db, &ticker, &my_address).await?; + return Ok(Vec::new()); + } - Err(err) - }; - Box::new(fut.boxed().compat()) + Err(err) } #[cfg(not(target_arch = "wasm32"))] -fn load_history_from_file_impl(coin: &T, ctx: &MmArc) -> TxHistoryFut> +async fn load_history_from_file_impl( + coin: &T, + ctx: &MmArc, + db_id: Option<&str>, +) -> TxHistoryResult> where T: MmCoin + ?Sized, { let ticker = coin.ticker().to_owned(); - let history_path = coin.tx_history_path(ctx); + let history_path = coin.tx_history_path(ctx, db_id); let ctx = ctx.clone(); - let fut = async move { + async move { let content = match fs::read(&history_path).await { Ok(content) => content, Err(err) if err.kind() == io::ErrorKind::NotFound => { @@ -5318,36 +5354,39 @@ where .await .map_to_mm(|e| TxHistoryError::ErrorClearing(e.to_string()))?; Ok(Vec::new()) - }; - Box::new(fut.boxed().compat()) + } + .await } #[cfg(target_arch = "wasm32")] -fn save_history_to_file_impl(coin: &T, ctx: &MmArc, mut history: Vec) -> TxHistoryFut<()> +async fn save_history_to_file_impl( + coin: &T, + ctx: &MmArc, + mut history: Vec, +) -> TxHistoryResult<()> where T: MmCoin + MarketCoinOps + ?Sized, { let ctx = ctx.clone(); let ticker = coin.ticker().to_owned(); - let my_address = try_f!(coin.my_address()); + let my_address = coin.my_address()?; history.sort_unstable_by(compare_transaction_details); - let fut = async move { - let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); - let db = coins_ctx.tx_history_db().await?; - save_tx_history(&db, &ticker, &my_address, history).await?; - Ok(()) - }; - Box::new(fut.boxed().compat()) + let db_id = coin.account_db_id().await; + let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); + let db = coins_ctx.tx_history_db(db_id.as_deref()).await?; + save_tx_history(&db, &ticker, &my_address, history).await?; + + Ok(()) } #[cfg(not(target_arch = "wasm32"))] -fn get_tx_history_migration_impl(coin: &T, ctx: &MmArc) -> TxHistoryFut +fn get_tx_history_migration_impl(coin: &T, ctx: &MmArc, db_id: Option<&str>) -> TxHistoryFut where T: MmCoin + MarketCoinOps + ?Sized, { - let migration_path = coin.tx_migration_path(ctx); + let migration_path = coin.tx_migration_path(ctx, db_id); let fut = async move { let current_migration = match fs::read(&migration_path).await { @@ -5370,11 +5409,11 @@ where } #[cfg(not(target_arch = "wasm32"))] -fn update_migration_file_impl(coin: &T, ctx: &MmArc, migration_number: u64) -> TxHistoryFut<()> +fn update_migration_file_impl(coin: &T, ctx: &MmArc, migration_number: u64, db_id: Option<&str>) -> TxHistoryFut<()> where T: MmCoin + MarketCoinOps + ?Sized, { - let migration_path = coin.tx_migration_path(ctx); + let migration_path = coin.tx_migration_path(ctx, db_id); let tmp_file = format!("{}.tmp", migration_path.display()); let fut = async move { @@ -5398,16 +5437,20 @@ where } #[cfg(not(target_arch = "wasm32"))] -fn save_history_to_file_impl(coin: &T, ctx: &MmArc, mut history: Vec) -> TxHistoryFut<()> +async fn save_history_to_file_impl( + coin: &T, + ctx: &MmArc, + mut history: Vec, +) -> TxHistoryResult<()> where T: MmCoin + MarketCoinOps + ?Sized, { - let history_path = coin.tx_history_path(ctx); + let history_path = coin.tx_history_path(ctx, coin.account_db_id().await.as_deref()); let tmp_file = format!("{}.tmp", history_path.display()); history.sort_unstable_by(compare_transaction_details); - let fut = async move { + async move { let content = json::to_vec(&history).map_to_mm(|e| TxHistoryError::ErrorSerializing(e.to_string()))?; let fs_fut = async { @@ -5423,9 +5466,10 @@ where let error = format!("Error '{}' creating/writing/renaming the tmp file {}", e, tmp_file); return MmError::err(TxHistoryError::ErrorSaving(error)); } + Ok(()) - }; - Box::new(fut.boxed().compat()) + } + .await } pub(crate) fn compare_transaction_details(a: &TransactionDetails, b: &TransactionDetails) -> Ordering { diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index f7348a5e78..6954b81f23 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -402,7 +402,7 @@ pub(crate) async fn my_tx_history_v2_impl( where Coin: CoinWithTxHistoryV2 + MmCoin, { - let tx_history_storage = TxHistoryStorageBuilder::new(&ctx).build()?; + let tx_history_storage = TxHistoryStorageBuilder::new(&ctx, coin.shared_db_id(&ctx).await).build()?; let wallet_id = coin.history_wallet_id(); let is_storage_init = tx_history_storage.is_initialized_for(&wallet_id).await?; @@ -529,7 +529,7 @@ pub(crate) mod for_tests { pub fn init_storage_for(coin: &Coin) -> (MmArc, impl TxHistoryStorage) { let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); block_on(storage.init(&coin.history_wallet_id())).unwrap(); (ctx, storage) } diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 2298ae9648..46ac69a89b 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -14,7 +14,7 @@ pub(crate) mod storage; use crate::{coin_conf, get_my_address, lp_coinfind_or_err, CoinsContext, HDPathAccountToAddressId, MarketCoinOps, MmCoinEnum, MmCoinStruct, MyAddressReq, WithdrawError}; use nft_errors::{GetNftInfoError, UpdateNftError}; -use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq, +use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList, TransactionNftDetails, UpdateNftReq, WithdrawNftReq}; @@ -23,8 +23,9 @@ use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_er use crate::nft::nft_errors::{ClearNftDbError, MetaFromUrlError, ProtectFromSpamError, TransferConfirmationsError, UpdateSpamPhishingError}; use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, ClearNftDbReq, NftCommon, NftCtx, NftInfo, - NftTransferCommon, PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq, - SpamContractReq, SpamContractRes, TransferMeta, TransferStatus, UriMeta}; + NftLists, NftTransferCommon, NftsTransferHistoryLists, PhishingDomainReq, + PhishingDomainRes, RefreshMetadataReq, SpamContractReq, SpamContractRes, TransferMeta, + TransferStatus, UriMeta}; use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; use common::log::error; use common::parse_rfc3339_to_timestamp; @@ -39,6 +40,8 @@ use serde::Deserialize; use serde_json::Value as Json; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; +use std::future::Future; +use std::pin::Pin; use std::str::FromStr; use web3::types::TransactionId; @@ -83,24 +86,60 @@ pub type WithdrawNftResult = Result MmResult { - let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; +pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult, GetNftInfoError> { + let db_ids = find_unique_nft_account_ids(&ctx, req.chains.clone()) + .await + .map_to_mm(GetNftInfoError::Internal)?; + + let get_nfts = + |id: String, chains: Vec| -> Pin> + Send>> { + let ctx_clone = ctx.clone(); + let req = req.clone(); + + let res = async move { + let nft_ctx = NftCtx::from_ctx(&ctx_clone).map_to_mm(GetNftInfoError::Internal)?; + + let chains = req + .chains + .clone() + .into_iter() + .filter(|c| chains.contains(c)) + .collect::>(); + let storage = nft_ctx.lock_db(Some(&id)).await?; + for chain in req.chains.iter() { + if !NftListStorageOps::is_initialized(&storage, chain).await? { + NftListStorageOps::init(&storage, chain).await?; + } + } + + let mut nft_list = storage + .get_nft_list(chains, req.max, req.limit, req.page_number, req.filters) + .await?; - let storage = nft_ctx.lock_db().await?; - for chain in req.chains.iter() { - if !NftListStorageOps::is_initialized(&storage, chain).await? { - NftListStorageOps::init(&storage, chain).await?; - } - } - let mut nft_list = storage - .get_nft_list(req.chains, req.max, req.limit, req.page_number, req.filters) - .await?; - if req.protect_from_spam { - for nft in &mut nft_list.nfts { - protect_from_nft_spam_links(nft, true)?; - } - } - Ok(nft_list) + if req.protect_from_spam { + for nft in &mut nft_list.nfts { + protect_from_nft_spam_links(nft, true)?; + } + } + + Ok(NftLists { nft_list, pubkey: id }) + }; + + Box::pin(res) + }; + + let future_list = db_ids + .into_iter() + .filter_map(|(id, chains)| { + if !chains.is_empty() { + Some(get_nfts(id, chains)) + } else { + None + } + }) + .collect::>(); + + try_join_all(future_list).await } /// Retrieves detailed metadata for a specified NFT. @@ -109,9 +148,13 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult { + let db_id = find_nft_account_id_for_chain(&ctx, req.chain) + .await + .map_to_mm(GetNftInfoError::Internal)?; + let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; - let storage = nft_ctx.lock_db().await?; + let storage = nft_ctx.lock_db(db_id.map(|(key, _)| key).as_deref()).await?; if !NftListStorageOps::is_initialized(&storage, &req.chain).await? { NftListStorageOps::init(&storage, &req.chain).await?; } @@ -125,6 +168,7 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult MmResult { - let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; +pub async fn get_nft_transfers( + ctx: MmArc, + req: NftTransfersReq, +) -> MmResult, GetNftInfoError> { + let db_ids = find_unique_nft_account_ids(&ctx, req.chains.clone()) + .await + .map_to_mm(GetNftInfoError::Internal)?; + + let get_nft_transfers = + |db_id: String, + chains: Vec| + -> Pin> + Send>> { + let ctx = ctx.clone(); + let req = req.clone(); + + let res = async move { + let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + + let storage = nft_ctx.lock_db(Some(&db_id)).await?; + for chain in req.chains.iter() { + if !NftTransferHistoryStorageOps::is_initialized(&storage, chain).await? { + NftTransferHistoryStorageOps::init(&storage, chain).await?; + } + } + let mut transfer_history = storage + .get_transfer_history(chains, req.max, req.limit, req.page_number, req.filters) + .await?; + if req.protect_from_spam { + for transfer in &mut transfer_history.transfer_history { + protect_from_history_spam_links(transfer, true)?; + } + } + process_transfers_confirmations(&ctx, req.chains, &mut transfer_history).await?; + + Ok(NftsTransferHistoryLists { + transfer_history, + pubkey: db_id, + }) + }; - let storage = nft_ctx.lock_db().await?; - for chain in req.chains.iter() { - if !NftTransferHistoryStorageOps::is_initialized(&storage, chain).await? { - NftTransferHistoryStorageOps::init(&storage, chain).await?; - } - } - let mut transfer_history_list = storage - .get_transfer_history(req.chains.clone(), req.max, req.limit, req.page_number, req.filters) - .await?; - if req.protect_from_spam { - for transfer in &mut transfer_history_list.transfer_history { - protect_from_history_spam_links(transfer, true)?; - } - } - process_transfers_confirmations(&ctx, req.chains, &mut transfer_history_list).await?; - Ok(transfer_history_list) + Box::pin(res) + }; + + let future_list = db_ids + .into_iter() + .filter_map(|(id, chains)| { + if !chains.is_empty() { + Some(get_nft_transfers(id, chains)) + } else { + None + } + }) + .collect::>(); + + try_join_all(future_list).await } async fn process_transfers_confirmations( @@ -216,92 +296,120 @@ async fn process_transfers_confirmations( /// data fetched from the provided `url`. The function ensures the local cache is in /// sync with the latest data from the source, validates against spam contract addresses and phishing domains. pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNftError> { - let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + let db_ids = find_unique_nft_account_ids(&ctx, req.chains.clone()) + .await + .map_to_mm(UpdateNftError::Internal)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); - let storage = nft_ctx.lock_db().await?; - for chain in req.chains.iter() { - let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; + let futures = |db_id: String, + chains: Vec| + -> Pin> + Send>> { + let ctx = ctx.clone(); + let req = req.clone(); + let p2p_ctx = p2p_ctx.clone(); + Box::pin(async move { + let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + + let storage = nft_ctx.lock_db(Some(&db_id)).await?; + for chain in chains.iter() { + let transfer_history_initialized = + NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; + + let from_block = if transfer_history_initialized { + let last_transfer_block = + NftTransferHistoryStorageOps::get_last_block_number(&storage, chain).await?; + last_transfer_block.map(|b| b + 1) + } else { + NftTransferHistoryStorageOps::init(&storage, chain).await?; + None + }; + // TODO activate and use global NFT instead of ETH coin after adding enable nft using coin conf support + let coin_enum = lp_coinfind_or_err(&ctx, chain.to_ticker()).await?; + let eth_coin = match coin_enum { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(UpdateNftError::CoinDoesntSupportNft { + coin: coin_enum.ticker().to_owned(), + }); + }, + }; + let proxy_sign = if req.komodo_proxy { + let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| UpdateNftError::Internal(e.to_string()))?; + Some(proxy_sign) + } else { + None + }; + let wrapper = UrlSignWrapper { + chain, + orig_url: &req.url, + url_antispam: &req.url_antispam, + proxy_sign, + }; + let nft_transfers = get_moralis_nft_transfers(&ctx, from_block, eth_coin, &wrapper).await?; + storage.add_transfers_to_history(*chain, nft_transfers).await?; + + let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { + Ok(Some(block)) => block, + Ok(None) => { + // if there are no rows in NFT LIST table we can try to get nft list from moralis. + let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?; + update_meta_in_transfers(&storage, chain, nft_list).await?; + update_transfers_with_empty_meta(&storage, &wrapper).await?; + update_spam(&storage, *chain, &req.url_antispam).await?; + update_phishing(&storage, chain, &req.url_antispam).await?; + continue; + }, + Err(_) => { + // if there is an error, then NFT LIST table doesn't exist, so we need to cache nft list from moralis. + NftListStorageOps::init(&storage, chain).await?; + let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?; + update_meta_in_transfers(&storage, chain, nft_list).await?; + update_transfers_with_empty_meta(&storage, &wrapper).await?; + update_spam(&storage, *chain, &req.url_antispam).await?; + update_phishing(&storage, chain, &req.url_antispam).await?; + continue; + }, + }; + let scanned_block = storage.get_last_scanned_block(chain).await?.ok_or_else(|| { + UpdateNftError::LastScannedBlockNotFound { + last_nft_block: nft_block.to_string(), + } + })?; + // if both block numbers exist, last scanned block should be equal + // or higher than last block number from NFT LIST table. + if scanned_block < nft_block { + return MmError::err(UpdateNftError::InvalidBlockOrder { + last_scanned_block: scanned_block.to_string(), + last_nft_block: nft_block.to_string(), + }); + } + update_nft_list(ctx.clone(), &storage, scanned_block + 1, &wrapper).await?; + update_nft_global_in_coins_ctx(&ctx, &storage, *chain).await?; + update_transfers_with_empty_meta(&storage, &wrapper).await?; + update_spam(&storage, *chain, &req.url_antispam).await?; + update_phishing(&storage, chain, &req.url_antispam).await?; + } - let from_block = if transfer_history_initialized { - let last_transfer_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain).await?; - last_transfer_block.map(|b| b + 1) - } else { - NftTransferHistoryStorageOps::init(&storage, chain).await?; - None - }; - // TODO activate and use global NFT instead of ETH coin after adding enable nft using coin conf support - let coin_enum = lp_coinfind_or_err(&ctx, chain.to_ticker()).await?; - let eth_coin = match coin_enum { - MmCoinEnum::EthCoin(eth_coin) => eth_coin, - _ => { - return MmError::err(UpdateNftError::CoinDoesntSupportNft { - coin: coin_enum.ticker().to_owned(), - }) - }, - }; - let proxy_sign = if req.komodo_proxy { - let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; - let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) - .map_err(|e| UpdateNftError::Internal(e.to_string()))?; - Some(proxy_sign) - } else { - None - }; + Ok(()) + }) + }; - let wrapper = UrlSignWrapper { - chain, - orig_url: &req.url, - url_antispam: &req.url_antispam, - proxy_sign, - }; + let future_list = db_ids + .into_iter() + .filter_map(|(id, chains)| { + if !chains.is_empty() { + Some(futures(id, chains)) + } else { + None + } + }) + .collect::>(); - let nft_transfers = get_moralis_nft_transfers(&ctx, from_block, eth_coin, &wrapper).await?; - storage.add_transfers_to_history(*chain, nft_transfers).await?; + try_join_all(future_list).await?; - let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { - Ok(Some(block)) => block, - Ok(None) => { - // if there are no rows in NFT LIST table we can try to get nft list from moralis. - let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?; - update_meta_in_transfers(&storage, chain, nft_list).await?; - update_transfers_with_empty_meta(&storage, &wrapper).await?; - update_spam(&storage, *chain, &req.url_antispam).await?; - update_phishing(&storage, chain, &req.url_antispam).await?; - continue; - }, - Err(_) => { - // if there is an error, then NFT LIST table doesn't exist, so we need to cache nft list from moralis. - NftListStorageOps::init(&storage, chain).await?; - let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?; - update_meta_in_transfers(&storage, chain, nft_list).await?; - update_transfers_with_empty_meta(&storage, &wrapper).await?; - update_spam(&storage, *chain, &req.url_antispam).await?; - update_phishing(&storage, chain, &req.url_antispam).await?; - continue; - }, - }; - let scanned_block = - storage - .get_last_scanned_block(chain) - .await? - .ok_or_else(|| UpdateNftError::LastScannedBlockNotFound { - last_nft_block: nft_block.to_string(), - })?; - // if both block numbers exist, last scanned block should be equal - // or higher than last block number from NFT LIST table. - if scanned_block < nft_block { - return MmError::err(UpdateNftError::InvalidBlockOrder { - last_scanned_block: scanned_block.to_string(), - last_nft_block: nft_block.to_string(), - }); - } - update_nft_list(ctx.clone(), &storage, scanned_block + 1, &wrapper).await?; - update_nft_global_in_coins_ctx(&ctx, &storage, *chain).await?; - update_transfers_with_empty_meta(&storage, &wrapper).await?; - update_spam(&storage, *chain, &req.url_antispam).await?; - update_phishing(&storage, chain, &req.url_antispam).await?; - } Ok(()) } @@ -468,10 +576,14 @@ fn prepare_uri_for_blocklist_endpoint( /// is identified as spam or matches with any phishing domains, the NFT's `possible_spam` and/or /// `possible_phishing` flags are set to true. pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResult<(), UpdateNftError> { + let db_id = find_nft_account_id_for_chain(&ctx, req.chain) + .await + .map_to_mm(UpdateNftError::Internal)? + .map(|(key, _)| key); let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); - let storage = nft_ctx.lock_db().await?; + let storage = nft_ctx.lock_db(db_id.as_deref()).await?; let proxy_sign = if req.komodo_proxy { let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; @@ -1505,30 +1617,47 @@ pub(crate) fn get_domain_from_url(url: Option<&str>) -> Option { /// Clears NFT data from the database for specified chains. pub async fn clear_nft_db(ctx: MmArc, req: ClearNftDbReq) -> MmResult<(), ClearNftDbError> { - if req.clear_all { - let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(ClearNftDbError::Internal)?; - let storage = nft_ctx.lock_db().await?; - storage.clear_all_nft_data().await?; - storage.clear_all_history_data().await?; - return Ok(()); - } + let db_ids = find_unique_nft_account_ids(&ctx, req.chains.clone()) + .await + .map_to_mm(ClearNftDbError::Internal)?; + + for (idx, (db_id, chains)) in db_ids.iter().enumerate() { + if req.clear_all { + let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(ClearNftDbError::Internal)?; + let storage = nft_ctx.lock_db(None).await?; + storage.clear_all_nft_data().await?; + storage.clear_all_history_data().await?; + if idx == db_ids.len() - 1 { + return Ok(()); + } + } - if req.chains.is_empty() { - return MmError::err(ClearNftDbError::InvalidRequest( - "Nothing to clear was specified".to_string(), - )); - } + let filtered_chains = chains + .iter() + .filter_map(|c| req.chains.contains(c).then_some(c)) + .collect::>(); + if filtered_chains.is_empty() { + if idx == db_ids.len() - 1 { + return MmError::err(ClearNftDbError::InvalidRequest( + "Nothing to clear was specified".to_string(), + )); + } else { + continue; + } + } - let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(ClearNftDbError::Internal)?; - let storage = nft_ctx.lock_db().await?; - let mut errors = Vec::new(); - for chain in req.chains.iter() { - if let Err(e) = clear_data_for_chain(&storage, chain).await { - errors.push(e); + let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(ClearNftDbError::Internal)?; + let storage = nft_ctx.lock_db(Some(db_id)).await?; + let mut errors = Vec::new(); + for chain in filtered_chains { + if let Err(e) = clear_data_for_chain(&storage, chain).await { + errors.push(e); + } + } + + if !errors.is_empty() && idx == db_ids.len() - 1 { + return MmError::err(ClearNftDbError::DbError(format!("{:?}", errors))); } - } - if !errors.is_empty() { - return MmError::err(ClearNftDbError::DbError(format!("{:?}", errors))); } Ok(()) @@ -1578,3 +1707,56 @@ async fn build_and_send_request(uri: &str, proxy_sign: &Option) -> Mm let response = send_request_to_uri(uri, payload.as_deref()).await?; Ok(response) } + +pub async fn find_unique_nft_account_ids( + ctx: &MmArc, + chains: Vec, +) -> Result>, String> { + let cctx = try_s!(CoinsContext::from_ctx(ctx)); + let coins = cctx.coins.lock().await; + let coins = coins.values().collect::>(); + + let mut active_id_chains = HashMap::new(); + for coin in coins.iter() { + if coin.is_available() { + // Use default if no db_id + let db_id = coin + .inner + .account_db_id() + .await + .unwrap_or_else(|| ctx.rmd160.to_string()); + let entry = active_id_chains.entry(db_id).or_insert_with(Vec::new); + if let Ok(chain) = Chain::from_ticker(coin.inner.ticker()) { + if chains.contains(&chain) { + entry.push(chain); + } + } + } + } + + Ok(active_id_chains) +} + +pub async fn find_nft_account_id_for_chain(ctx: &MmArc, chains: Chain) -> Result, String> { + let cctx = try_s!(CoinsContext::from_ctx(ctx)); + let coins = cctx.coins.lock().await; + let coins = coins.values().collect::>(); + + for coin in coins.iter() { + if coin.is_available() { + // Use default if no db_id + let db_id = coin + .inner + .account_db_id() + .await + .unwrap_or_else(|| ctx.rmd160.to_string()); + if let Ok(chain) = Chain::from_ticker(coin.inner.ticker()) { + if chains == chain { + return Ok(Some((db_id, chain))); + } + } + } + } + + Ok(None) +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 12e8d326a0..1b34e11e21 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -368,6 +368,7 @@ pub enum LockDBError { /// Errors related to SQL operations in non-WASM environments. #[cfg(not(target_arch = "wasm32"))] SqlError(SqlError), + InternalError(String), } #[cfg(not(target_arch = "wasm32"))] diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index f772b92f56..204884b6ce 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -2,6 +2,8 @@ use common::ten; use enum_derives::EnumVariantList; use ethereum_types::Address; use mm2_core::mm_ctx::{from_ctx, MmArc}; +#[cfg(not(target_arch = "wasm32"))] +use mm2_core::sql_connection_pool::AsyncSqliteConnPool; use mm2_err_handle::prelude::*; use mm2_number::{BigDecimal, BigUint}; use rpc::v1::types::Bytes as BytesJson; @@ -21,11 +23,6 @@ use crate::nft::nft_errors::{LockDBError, ParseChainTypeError, ParseContractType use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; use crate::{TransactionType, TxFeeDetails, WithdrawFee}; -cfg_native! { - use db_common::async_sql_conn::AsyncConnection; - use futures::lock::Mutex as AsyncMutex; -} - cfg_wasm32! { use mm2_db::indexed_db::{ConstructibleDb, SharedDb}; use crate::nft::storage::wasm::WasmNftCacheError; @@ -36,7 +33,7 @@ cfg_wasm32! { /// /// The request provides options such as pagination, limiting the number of results, /// and applying specific filters to the list. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct NftListReq { /// List of chains to fetch the NFTs from. pub(crate) chains: Vec, @@ -84,7 +81,7 @@ pub struct NftMetadataReq { } /// Contains parameters required to refresh metadata for a specified NFT. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct RefreshMetadataReq { /// The address of the NFT token whose metadata needs to be refreshed. pub(crate) token_address: Address, @@ -418,6 +415,12 @@ impl std::ops::Deref for SerdeStringWrap { /// Represents a detailed list of NFTs, including the total number of NFTs and the number of skipped NFTs. /// It is used as response of `get_nft_list` if it is successful. +#[derive(Debug, Serialize)] +pub struct NftLists { + pub(crate) nft_list: NftList, + pub(crate) pubkey: String, +} + #[derive(Debug, Serialize)] pub struct NftList { pub(crate) nfts: Vec, @@ -508,7 +511,7 @@ pub struct TransactionNftDetails { /// /// The request provides options such as pagination, limiting the number of results, /// and applying specific filters to the history. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct NftTransfersReq { /// List of chains to fetch the NFT transfer history from. pub(crate) chains: Vec, @@ -634,6 +637,12 @@ pub struct NftsTransferHistoryList { pub(crate) total: usize, } +#[derive(Debug, Serialize)] +pub struct NftsTransferHistoryLists { + pub(crate) transfer_history: NftsTransferHistoryList, + pub(crate) pubkey: String, +} + /// Filters that can be applied to the NFT transfer history. /// /// Allows filtering based on transaction type (send/receive), date range, @@ -653,7 +662,7 @@ pub struct NftTransferHistoryFilters { } /// Contains parameters required to update NFT transfer history and NFT list. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct UpdateNftReq { /// A list of blockchains for which the NFTs need to be updated. pub(crate) chains: Vec, @@ -721,7 +730,7 @@ pub(crate) struct NftCtx { #[cfg(target_arch = "wasm32")] pub(crate) nft_cache_db: SharedDb, #[cfg(not(target_arch = "wasm32"))] - pub(crate) nft_cache_db: Arc>, + pub(crate) nft_cache_dbs: AsyncSqliteConnPool, } impl NftCtx { @@ -732,10 +741,10 @@ impl NftCtx { pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { Ok(try_s!(from_ctx(&ctx.nft_ctx, move || { let async_sqlite_connection = ctx - .async_sqlite_connection + .async_sqlite_conn_pool .ok_or("async_sqlite_connection is not initialized".to_owned())?; Ok(NftCtx { - nft_cache_db: async_sqlite_connection.clone(), + nft_cache_dbs: async_sqlite_connection.clone(), }) }))) } @@ -744,7 +753,7 @@ impl NftCtx { pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { Ok(try_s!(from_ctx(&ctx.nft_ctx, move || { Ok(NftCtx { - nft_cache_db: ConstructibleDb::new(ctx).into_shared(), + nft_cache_db: ConstructibleDb::new(ctx, None).into_shared(), }) }))) } @@ -753,16 +762,19 @@ impl NftCtx { #[cfg(not(target_arch = "wasm32"))] pub(crate) async fn lock_db( &self, + db_id: Option<&str>, ) -> MmResult { - Ok(self.nft_cache_db.lock().await) + let locked = self.nft_cache_dbs.async_sqlite_conn(db_id).await; + Ok(locked.lock_owned().await) } #[cfg(target_arch = "wasm32")] pub(crate) async fn lock_db( &self, + db_id: Option<&str>, ) -> MmResult { self.nft_cache_db - .get_or_initialize() + .get_or_initialize(db_id) .await .mm_err(WasmNftCacheError::from) .mm_err(LockDBError::from) diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index 05f732a9ee..529e6336a4 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -166,7 +166,7 @@ cross_test!(test_camo, { cross_test!(test_add_get_nfts, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -183,7 +183,7 @@ cross_test!(test_add_get_nfts, { cross_test!(test_last_nft_block, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -195,10 +195,43 @@ cross_test!(test_last_nft_block, { assert_eq!(last_block, 28056726); }); +cross_test!(test_last_nft_block_with_multikey_db_lock, { + #[cfg(target_arch = "wasm32")] + common::log::wasm_log::register_wasm_log(); + let chain = Chain::Bsc; + let nft_ctx = get_nft_ctx(&chain).await; + let storage_1 = nft_ctx.lock_db(None).await.unwrap(); + let storage_2 = nft_ctx.lock_db(Some("TEST_DB_ID")).await.unwrap(); + NftListStorageOps::init(&storage_1, &chain).await.unwrap(); + NftListStorageOps::init(&storage_2, &chain).await.unwrap(); + + // insert nft into storage_1 and query for last block(should return 28056726 and assert_eq should pass!) + let nft_list = nft_list(); + storage_1.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); + let last_block = NftListStorageOps::get_last_block_number(&storage_1, &chain) + .await + .unwrap() + .unwrap(); + assert_eq!(last_block, 28056726); + + // Since we didn't insert nft into storage_2 instance yet, last_block should return None and assert_eq should pass! + let last_block = NftListStorageOps::get_last_block_number(&storage_2, &chain) + .await + .unwrap(); + assert_eq!(last_block, None); + + // storage_1 last block should pass again + let last_block = NftListStorageOps::get_last_block_number(&storage_1, &chain) + .await + .unwrap() + .unwrap(); + assert_eq!(last_block, 28056726); +}); + cross_test!(test_nft_list, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -207,6 +240,7 @@ cross_test!(test_nft_list, { .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap()), None) .await .unwrap(); + assert_eq!(nft_list.nfts.len(), 1); let nft = nft_list.nfts.get(0).unwrap(); assert_eq!(nft.block_number, 28056721); @@ -217,7 +251,7 @@ cross_test!(test_nft_list, { cross_test!(test_remove_nft, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -242,7 +276,7 @@ cross_test!(test_remove_nft, { cross_test!(test_nft_amount, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let mut nft = nft(); storage @@ -280,7 +314,7 @@ cross_test!(test_nft_amount, { cross_test!(test_refresh_metadata, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let new_symbol = "NEW_SYMBOL"; let mut nft = nft(); @@ -300,7 +334,7 @@ cross_test!(test_refresh_metadata, { cross_test!(test_update_nft_spam_by_token_address, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -321,7 +355,7 @@ cross_test!(test_update_nft_spam_by_token_address, { cross_test!(test_exclude_nft_spam, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -340,7 +374,7 @@ cross_test!(test_exclude_nft_spam, { cross_test!(test_get_animation_external_domains, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -354,7 +388,7 @@ cross_test!(test_get_animation_external_domains, { cross_test!(test_update_nft_phishing_by_domain, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -382,7 +416,7 @@ cross_test!(test_update_nft_phishing_by_domain, { cross_test!(test_exclude_nft_phishing_spam, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft_list = nft_list(); storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap(); @@ -406,7 +440,7 @@ cross_test!(test_exclude_nft_phishing_spam, { cross_test!(test_clear_nft, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft = nft(); storage.add_nfts_to_list(chain, vec![nft], 28056726).await.unwrap(); @@ -418,7 +452,7 @@ cross_test!(test_clear_nft, { cross_test!(test_clear_all_nft, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftListStorageOps::init(&storage, &chain).await.unwrap(); let nft = nft(); storage.add_nfts_to_list(chain, vec![nft], 28056726).await.unwrap(); @@ -448,7 +482,7 @@ async fn test_clear_nft_target(storage: &S, chain: &Chain) cross_test!(test_add_get_transfers, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -475,7 +509,7 @@ cross_test!(test_add_get_transfers, { cross_test!(test_last_transfer_block, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -490,7 +524,7 @@ cross_test!(test_last_transfer_block, { cross_test!(test_transfer_history, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -509,7 +543,7 @@ cross_test!(test_transfer_history, { cross_test!(test_transfer_history_filters, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -571,7 +605,7 @@ cross_test!(test_transfer_history_filters, { cross_test!(test_get_update_transfer_meta, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -606,7 +640,7 @@ cross_test!(test_get_update_transfer_meta, { cross_test!(test_update_transfer_spam_by_token_address, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -627,7 +661,7 @@ cross_test!(test_update_transfer_spam_by_token_address, { cross_test!(test_get_token_addresses, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -639,7 +673,7 @@ cross_test!(test_get_token_addresses, { cross_test!(test_exclude_transfer_spam, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -662,7 +696,7 @@ cross_test!(test_exclude_transfer_spam, { cross_test!(test_get_domains, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -676,7 +710,7 @@ cross_test!(test_get_domains, { cross_test!(test_update_transfer_phishing_by_domain, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -704,7 +738,7 @@ cross_test!(test_update_transfer_phishing_by_domain, { cross_test!(test_exclude_transfer_phishing_spam, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -747,7 +781,7 @@ cross_test!(test_exclude_transfer_phishing_spam, { cross_test!(test_clear_history, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); @@ -759,7 +793,7 @@ cross_test!(test_clear_history, { cross_test!(test_clear_all_history, { let chain = Chain::Bsc; let nft_ctx = get_nft_ctx(&chain).await; - let storage = nft_ctx.lock_db().await.unwrap(); + let storage = nft_ctx.lock_db(None).await.unwrap(); NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap(); let transfers = nft_transfer_history(); storage.add_transfers_to_history(chain, transfers).await.unwrap(); diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index 6844b261d9..a59c07a6c3 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -12,7 +12,7 @@ use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlRe use db_common::sqlite::sql_builder::SqlBuilder; use db_common::sqlite::{query_single_row, string_from_row, SafeTableName, CHECK_TABLE_EXISTS_SQL}; use ethereum_types::Address; -use futures::lock::MutexGuard as AsyncMutexGuard; +use futures_util::lock::OwnedMutexGuard; use mm2_err_handle::prelude::*; use mm2_number::{BigDecimal, BigUint}; use serde_json::Value as Json; @@ -547,7 +547,7 @@ fn is_table_empty(conn: &Connection, safe_table_name: SafeTableName) -> Result { +impl NftListStorageOps for OwnedMutexGuard { type Error = AsyncConnError; async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { @@ -964,7 +964,7 @@ impl NftListStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } #[async_trait] -impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { +impl NftTransferHistoryStorageOps for OwnedMutexGuard { type Error = AsyncConnError; async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { diff --git a/mm2src/coins/nft/storage/wasm/nft_idb.rs b/mm2src/coins/nft/storage/wasm/nft_idb.rs index 054f1c058e..ed9a957fab 100644 --- a/mm2src/coins/nft/storage/wasm/nft_idb.rs +++ b/mm2src/coins/nft/storage/wasm/nft_idb.rs @@ -9,7 +9,7 @@ const DB_VERSION: u32 = 1; /// /// This type ensures that while the database is being accessed or modified, /// no other operations can interfere, maintaining data integrity. -pub type NftCacheIDBLocked<'a> = DbLocked<'a, NftCacheIDB>; +pub type NftCacheIDBLocked = DbLocked; /// Represents the IndexedDB instance specifically designed for caching NFT data. /// @@ -33,6 +33,7 @@ impl DbInstance for NftCacheIDB { .with_table::() .build() .await?; + Ok(NftCacheIDB { inner }) } } diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index e5ea955918..1b31696051 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -130,7 +130,7 @@ impl NftTransferHistoryFilters { } #[async_trait] -impl NftListStorageOps for NftCacheIDBLocked<'_> { +impl NftListStorageOps for NftCacheIDBLocked { type Error = WasmNftCacheError; async fn init(&self, _chain: &Chain) -> MmResult<(), Self::Error> { Ok(()) } @@ -447,7 +447,7 @@ impl NftListStorageOps for NftCacheIDBLocked<'_> { } #[async_trait] -impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { +impl NftTransferHistoryStorageOps for NftCacheIDBLocked { type Error = WasmNftCacheError; async fn init(&self, _chain: &Chain) -> MmResult<(), Self::Error> { Ok(()) } diff --git a/mm2src/coins/qrc20/history.rs b/mm2src/coins/qrc20/history.rs index af3c41f078..652610aa44 100644 --- a/mm2src/coins/qrc20/history.rs +++ b/mm2src/coins/qrc20/history.rs @@ -177,7 +177,7 @@ impl Qrc20Coin { .flat_map(|(_, value)| value) .map(|(_tx_id, tx)| tx.clone()) .collect(); - if let Err(e) = self.save_history_to_file(&ctx, to_write).compat().await { + if let Err(e) = self.save_history_to_file(&ctx, to_write).await { ctx.log.log( "", &[&"tx_history", &self.as_ref().conf.ticker], @@ -357,7 +357,7 @@ impl Qrc20Coin { RequestTxHistoryResult::Retry { error: ERRL!("Error {:?} on blockchain_contract_event_get_history", err), } - } + }; }, JsonRpcErrorType::InvalidRequest(err) | JsonRpcErrorType::Transport(err) @@ -372,7 +372,7 @@ impl Qrc20Coin { UtxoRpcError::InvalidResponse(e) | UtxoRpcError::Internal(e) => { return RequestTxHistoryResult::Retry { error: ERRL!("Error {} on blockchain_contract_event_get_history", e), - } + }; }, }, }; @@ -541,7 +541,9 @@ impl Qrc20Coin { } async fn try_load_history_from_file(&self, ctx: &MmArc) -> TxHistoryResult { - let history = self.load_history_from_file(ctx).compat().await?; + let history = self + .load_history_from_file(ctx, self.account_db_id().await.as_deref()) + .await?; let mut history_map: HistoryMapByHash = HashMap::default(); for tx in history { diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index bc5e12f6ff..1392cbc950 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -120,6 +120,7 @@ pub struct SerializedUnsignedTx { type TendermintPrivKeyPolicy = PrivKeyPolicy; +#[derive(Clone)] pub struct TendermintKeyPair { private_key_secret: Secp256k1Secret, public_key: Public, @@ -258,7 +259,7 @@ impl TendermintActivationPolicy { } } - fn public_key(&self) -> Result { + pub fn public_key(&self) -> Result { match self { Self::PrivateKey(private_key_policy) => match private_key_policy { PrivKeyPolicy::Iguana(pair) => PublicKey::from_raw_secp256k1(&pair.public_key.to_bytes()) @@ -2465,6 +2466,26 @@ impl MmCoin for TendermintCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + async fn account_db_id(&self) -> Option { + if let Ok(public_key) = self.activation_policy.public_key() { + let address_hash = dhash160(&public_key.to_bytes()); + let address_rmd160_hex = address_hash.to_string(); + + return Some(address_rmd160_hex); + }; + + None + } + + async fn shared_db_id(&self, ctx: &MmArc) -> Option { + if let TendermintActivationPolicy::PrivateKey(PrivKeyPolicy::HDWallet { .. }) = self.activation_policy { + return Some(ctx.default_shared_db_id()); + }; + + // Fallback to the account db_id for non-HD wallets + self.account_db_id().await + } } #[async_trait] @@ -3092,7 +3113,7 @@ impl WatcherOps for TendermintCoin { pub fn tendermint_priv_key_policy( conf: &TendermintConf, ticker: &str, - priv_key_build_policy: PrivKeyBuildPolicy, + priv_key_build_policy: &PrivKeyBuildPolicy, path_to_address: HDPathAccountToAddressId, ) -> MmResult { match priv_key_build_policy { @@ -3102,7 +3123,7 @@ pub fn tendermint_priv_key_policy( kind: TendermintInitErrorKind::Internal(e.to_string()), })?; - let tendermint_pair = TendermintKeyPair::new(iguana, *mm2_internal_key_pair.public()); + let tendermint_pair = TendermintKeyPair::new(*iguana, *mm2_internal_key_pair.public()); Ok(TendermintPrivKeyPolicy::Iguana(tendermint_pair)) }, @@ -3123,14 +3144,11 @@ pub fn tendermint_priv_key_policy( kind: TendermintInitErrorKind::InvalidPrivKey(e.to_string()), })?; let bip39_secp_priv_key = global_hd.root_priv_key().clone(); - let pubkey = Public::from_slice(&bip39_secp_priv_key.public_key().to_bytes()).map_to_mm(|e| { - TendermintInitError { - ticker: ticker.to_string(), - kind: TendermintInitErrorKind::Internal(e.to_string()), - } + let keypair = key_pair_from_secret(activated_priv_key.as_ref()).mm_err(|e| TendermintInitError { + ticker: ticker.to_string(), + kind: TendermintInitErrorKind::Internal(e.to_string()), })?; - - let tendermint_pair = TendermintKeyPair::new(activated_priv_key, pubkey); + let tendermint_pair = TendermintKeyPair::new(activated_priv_key, *keypair.public()); Ok(TendermintPrivKeyPolicy::HDWallet { path_to_coin: path_to_coin.clone(), diff --git a/mm2src/coins/tx_history_storage/mod.rs b/mm2src/coins/tx_history_storage/mod.rs index 1f0ca4f8d8..d32bd183d8 100644 --- a/mm2src/coins/tx_history_storage/mod.rs +++ b/mm2src/coins/tx_history_storage/mod.rs @@ -45,18 +45,21 @@ pub enum CreateTxHistoryStorageError { /// `TxHistoryStorageBuilder` is used to create an instance that implements the `TxHistoryStorage` trait. pub struct TxHistoryStorageBuilder<'a> { ctx: &'a MmArc, + db_id: Option, } impl<'a> TxHistoryStorageBuilder<'a> { #[inline] - pub fn new(ctx: &MmArc) -> TxHistoryStorageBuilder<'_> { TxHistoryStorageBuilder { ctx } } + pub fn new(ctx: &MmArc, db_id: Option) -> TxHistoryStorageBuilder<'_> { + TxHistoryStorageBuilder { ctx, db_id } + } #[inline] pub fn build(self) -> MmResult { #[cfg(target_arch = "wasm32")] - return wasm::IndexedDbTxHistoryStorage::new(self.ctx); + return wasm::IndexedDbTxHistoryStorage::new(self.ctx, self.db_id.as_deref()); #[cfg(not(target_arch = "wasm32"))] - sql_tx_history_storage_v2::SqliteTxHistoryStorage::new(self.ctx) + sql_tx_history_storage_v2::SqliteTxHistoryStorage::new(self.ctx, self.db_id.as_deref()) } } diff --git a/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs b/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs index cf0575f973..cad17cfbcb 100644 --- a/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs +++ b/mm2src/coins/tx_history_storage/sql_tx_history_storage_v2.rs @@ -375,13 +375,14 @@ impl WalletId { pub struct SqliteTxHistoryStorage(Arc>); impl SqliteTxHistoryStorage { - pub fn new(ctx: &MmArc) -> Result> { - let sqlite_connection = ctx - .sqlite_connection - .ok_or(MmError::new(CreateTxHistoryStorageError::Internal( - "sqlite_connection is not initialized".to_owned(), - )))?; - Ok(SqliteTxHistoryStorage(sqlite_connection.clone())) + pub fn new(ctx: &MmArc, db_id: Option<&str>) -> Result> { + let sqlite_connection = ctx.sqlite_conn_opt(db_id).ok_or_else(|| { + MmError::new(CreateTxHistoryStorageError::Internal( + "'MmCtx::sqlite_connection' is not found or initialized".to_owned(), + )) + })?; + + Ok(SqliteTxHistoryStorage(sqlite_connection)) } } diff --git a/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs b/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs index 2ffcc9760d..8118fc31a3 100644 --- a/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs +++ b/mm2src/coins/tx_history_storage/tx_history_v2_tests.rs @@ -43,7 +43,7 @@ async fn test_add_transactions_impl() { let wallet_id = wallet_id_for_test("TEST_ADD_TRANSACTIONS"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -84,7 +84,7 @@ async fn test_remove_transaction_impl() { let wallet_id = wallet_id_for_test("TEST_REMOVE_TRANSACTION"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); let tx_details = get_bch_tx_details("6686ee013620d31ba645b27d581fed85437ce00f46b595a576718afac4dd5b69"); @@ -116,7 +116,7 @@ async fn test_get_transaction_impl() { let wallet_id = wallet_id_for_test("TEST_GET_TRANSACTION"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -158,7 +158,7 @@ async fn test_update_transaction_impl() { let wallet_id = wallet_id_for_test("TEST_UPDATE_TRANSACTION"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -185,7 +185,7 @@ async fn test_contains_and_get_unconfirmed_transaction_impl() { let wallet_id = wallet_id_for_test("TEST_CONTAINS_AND_GET_UNCONFIRMED_TRANSACTION"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -274,7 +274,7 @@ async fn test_has_transactions_with_hash_impl() { let wallet_id = wallet_id_for_test("TEST_HAS_TRANSACTIONS_WITH_HASH"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -307,7 +307,7 @@ async fn test_unique_tx_hashes_num_impl() { let wallet_id = wallet_id_for_test("TEST_UNIQUE_TX_HASHES_NUM"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -355,7 +355,7 @@ async fn test_add_and_get_tx_from_cache_impl() { .with_hd_wallet_rmd160("108f07b8382412612c048d07d13f814118445acd".into()); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id_1).await.unwrap(); storage.init(&wallet_id_2).await.unwrap(); @@ -387,7 +387,7 @@ async fn test_get_raw_tx_bytes_on_add_transactions_impl() { let wallet_id = wallet_id_for_test("TEST_GET_RAW_TX_BYTES_ON_ADD_TRANSACTIONS"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -418,7 +418,7 @@ async fn test_get_history_page_number_impl() { let wallet_id = wallet_id_for_test("TEST_GET_HISTORY_PAGE_NUMBER"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -460,7 +460,7 @@ async fn test_get_history_from_id_impl() { let wallet_id = wallet_id_for_test("TEST_GET_HISTORY_FROM_ID"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -502,7 +502,7 @@ async fn test_get_history_for_addresses_impl() { let wallet_id = wallet_id_for_test("TEST_GET_HISTORY_FROM_ID"); let ctx = mm_ctx_with_custom_db(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, None).build().unwrap(); storage.init(&wallet_id).await.unwrap(); @@ -591,7 +591,7 @@ mod native_tests { let wallet_id = wallet_id_for_test("TEST_INIT_COLLECTION"); let ctx = mm_ctx_with_custom_db(); - let storage = SqliteTxHistoryStorage::new(&ctx).unwrap(); + let storage = SqliteTxHistoryStorage::new(&ctx, None).unwrap(); let initialized = block_on(storage.is_initialized_for(&wallet_id)).unwrap(); assert!(!initialized); @@ -660,7 +660,7 @@ mod wasm_tests { let wallet_id = wallet_id_for_test("TEST_INIT_COLLECTION"); let ctx = mm_ctx_with_custom_db(); - let storage = IndexedDbTxHistoryStorage::new(&ctx).unwrap(); + let storage = IndexedDbTxHistoryStorage::new(&ctx, None).unwrap(); // Please note this is the `IndexedDbTxHistoryStorage` specific: // [`IndexedDbTxHistoryStorage::is_initialized_for`] always returns `true`. diff --git a/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs b/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs index b646e7cefc..8602479f40 100644 --- a/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs +++ b/mm2src/coins/tx_history_storage/wasm/tx_history_db.rs @@ -5,7 +5,7 @@ use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedD const DB_VERSION: u32 = 1; -pub type TxHistoryDbLocked<'a> = DbLocked<'a, TxHistoryDb>; +pub type TxHistoryDbLocked = DbLocked; pub struct TxHistoryDb { inner: IndexedDb, diff --git a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs index c52fefd76d..78639b2f04 100644 --- a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs +++ b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v1.rs @@ -95,7 +95,7 @@ mod tests { #[wasm_bindgen_test] async fn test_tx_history() { const DB_NAME: &str = "TEST_TX_HISTORY"; - let db = TxHistoryDb::init(DbIdentifier::for_test(DB_NAME)) + let db = TxHistoryDb::init(DbIdentifier::for_test(DB_NAME, None)) .await .expect("!TxHistoryDb::init_with_fs_path"); diff --git a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs index acc6d338e2..f5c7811ebd 100644 --- a/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs +++ b/mm2src/coins/tx_history_storage/wasm/tx_history_storage_v2.rs @@ -27,7 +27,7 @@ pub struct IndexedDbTxHistoryStorage { } impl IndexedDbTxHistoryStorage { - pub fn new(ctx: &MmArc) -> MmResult + pub fn new(ctx: &MmArc, _db_id: Option<&str>) -> MmResult where Self: Sized, { @@ -344,7 +344,7 @@ impl IndexedDbTxHistoryStorage { transactions: Vec::new(), skipped: 0, total: total_count, - }) + }); }, } }, @@ -358,8 +358,8 @@ impl IndexedDbTxHistoryStorage { }) } - async fn lock_db(&self) -> WasmTxHistoryResult> { - self.db.get_or_initialize().await.mm_err(WasmTxHistoryError::from) + async fn lock_db(&self) -> WasmTxHistoryResult { + self.db.get_or_initialize(None).await.mm_err(WasmTxHistoryError::from) } } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index f2cf7b4251..96c33b68ab 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1358,6 +1358,10 @@ impl MmCoin for BchCoin { tokens.remove(ticker); }; } + + async fn account_db_id(&self) -> Option { utxo_common::account_db_id(self).await } + + async fn shared_db_id(&self, _ctx: &MmArc) -> Option { utxo_common::shared_db_id(self).await } } #[async_trait] diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index d3442d6690..ace093b9c7 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -74,6 +74,7 @@ impl From for WithdrawError { } #[path = "qtum_delegation.rs"] mod qtum_delegation; + #[derive(Debug, Serialize, Deserialize)] #[serde(tag = "format")] pub enum QtumAddressFormat { @@ -979,6 +980,10 @@ impl MmCoin for QtumCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + async fn account_db_id(&self) -> Option { utxo_common::account_db_id(self).await } + + async fn shared_db_id(&self, _ctx: &MmArc) -> Option { utxo_common::shared_db_id(self).await } } #[async_trait] diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index c559449c22..494777bb0b 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -493,7 +493,7 @@ impl SlpToken { _ => { return MmError::err(ValidatePaymentError::WrongPaymentTx( "Invalid Slp tx details".to_string(), - )) + )); }, } @@ -1102,7 +1102,7 @@ impl MarketCoinOps for SlpToken { DerivationMethod::HDWallet(_) => { return MmError::err(MyAddressError::UnexpectedDerivationMethod( "'my_address' is deprecated for HD wallets".to_string(), - )) + )); }, }; let slp_address = self @@ -1655,7 +1655,7 @@ impl MmCoin for SlpToken { CashAddrType::P2SH => { return MmError::err(WithdrawError::InvalidAddress( "Withdrawal to P2SH is not supported".into(), - )) + )); }, }; let slp_output = SlpOutput { amount, script_pubkey }; @@ -1747,7 +1747,7 @@ impl MmCoin for SlpToken { return ValidateAddressResult { is_valid: false, reason: Some(format!("Error {} on parsing the {} as cash address", e, address)), - } + }; }, }; @@ -2227,7 +2227,7 @@ mod slp_tests { discriminant(&tx_err), discriminant(&TransactionErr::TxRecoverable( TransactionEnum::from(utxo_tx), - String::new() + String::new(), )) ); } diff --git a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs index 89266af2f6..5c9ad0bc2a 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/mod.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/mod.rs @@ -1,8 +1,10 @@ #[cfg(not(target_arch = "wasm32"))] mod sql_block_header_storage; + #[cfg(not(target_arch = "wasm32"))] pub use sql_block_header_storage::SqliteBlockHeadersStorage; #[cfg(target_arch = "wasm32")] mod wasm; + #[cfg(target_arch = "wasm32")] pub use wasm::IDBBlockHeadersStorage; @@ -26,32 +28,44 @@ impl Debug for BlockHeaderStorage { impl BlockHeaderStorage { #[cfg(all(not(test), not(target_arch = "wasm32")))] - pub(crate) fn new_from_ctx(ctx: MmArc, ticker: String) -> Result { - let sqlite_connection = ctx.sqlite_connection.ok_or(BlockHeaderStorageError::Internal( - "sqlite_connection is not initialized".to_owned(), - ))?; + pub(crate) fn new_from_ctx( + ctx: &MmArc, + ticker: String, + db_id: Option<&str>, + ) -> Result { + let sqlite_connection = ctx + .sqlite_conn_opt(db_id) + .ok_or_else(|| BlockHeaderStorageError::Internal("sqlite_connection is not initialized".to_owned()))?; + Ok(BlockHeaderStorage { inner: Box::new(SqliteBlockHeadersStorage { ticker, - conn: sqlite_connection.clone(), + conn: sqlite_connection, }), }) } #[cfg(target_arch = "wasm32")] - pub(crate) fn new_from_ctx(ctx: MmArc, ticker: String) -> Result { + pub(crate) fn new_from_ctx( + ctx: &MmArc, + ticker: String, + db_id: Option<&str>, + ) -> Result { Ok(BlockHeaderStorage { - inner: Box::new(IDBBlockHeadersStorage::new(&ctx, ticker)), + inner: Box::new(IDBBlockHeadersStorage::new(ctx, ticker, db_id)), }) } #[cfg(all(test, not(target_arch = "wasm32")))] - pub(crate) fn new_from_ctx(ctx: MmArc, ticker: String) -> Result { + pub(crate) fn new_from_ctx( + _ctx: &MmArc, + ticker: String, + _db_id: Option<&str>, + ) -> Result { use db_common::sqlite::rusqlite::Connection; use std::sync::{Arc, Mutex}; let conn = Arc::new(Mutex::new(Connection::open_in_memory().unwrap())); - let conn = ctx.sqlite_connection.clone_or(conn); Ok(BlockHeaderStorage { inner: Box::new(SqliteBlockHeadersStorage { ticker, conn }), @@ -132,7 +146,7 @@ mod block_headers_storage_tests { pub(crate) async fn test_add_block_headers_impl(for_coin: &str) { let ctx = mm_ctx_with_custom_db(); - let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string()) + let storage = BlockHeaderStorage::new_from_ctx(&ctx, for_coin.to_string(), None) .unwrap() .into_inner(); storage.init().await.unwrap(); @@ -146,7 +160,7 @@ mod block_headers_storage_tests { pub(crate) async fn test_get_block_header_impl(for_coin: &str) { let ctx = mm_ctx_with_custom_db(); - let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string()) + let storage = BlockHeaderStorage::new_from_ctx(&ctx, for_coin.to_string(), None) .unwrap() .into_inner(); storage.init().await.unwrap(); @@ -171,7 +185,7 @@ mod block_headers_storage_tests { pub(crate) async fn test_get_last_block_header_with_non_max_bits_impl(for_coin: &str) { let ctx = mm_ctx_with_custom_db(); - let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string()) + let storage = BlockHeaderStorage::new_from_ctx(&ctx, for_coin.to_string(), None) .unwrap() .into_inner(); storage.init().await.unwrap(); @@ -206,7 +220,7 @@ mod block_headers_storage_tests { pub(crate) async fn test_get_last_block_height_impl(for_coin: &str) { let ctx = mm_ctx_with_custom_db(); - let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string()) + let storage = BlockHeaderStorage::new_from_ctx(&ctx, for_coin.to_string(), None) .unwrap() .into_inner(); storage.init().await.unwrap(); @@ -234,7 +248,7 @@ mod block_headers_storage_tests { pub(crate) async fn test_remove_headers_from_storage_impl(for_coin: &str) { let ctx = mm_ctx_with_custom_db(); - let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string()) + let storage = BlockHeaderStorage::new_from_ctx(&ctx, for_coin.to_string(), None) .unwrap() .into_inner(); storage.init().await.unwrap(); @@ -288,7 +302,7 @@ mod native_tests { fn test_init_collection() { let for_coin = "init_collection"; let ctx = mm_ctx_with_custom_db(); - let storage = BlockHeaderStorage::new_from_ctx(ctx, for_coin.to_string()) + let storage = BlockHeaderStorage::new_from_ctx(&ctx, for_coin.to_string(), None) .unwrap() .into_inner(); @@ -305,6 +319,7 @@ mod native_tests { const FOR_COIN_GET: &str = "get"; const FOR_COIN_INSERT: &str = "insert"; + #[test] fn test_add_block_headers() { block_on(test_add_block_headers_impl(FOR_COIN_INSERT)) } @@ -338,7 +353,7 @@ mod wasm_test { #[wasm_bindgen_test] async fn test_storage_init() { let ctx = mm_ctx_with_custom_db(); - let storage = IDBBlockHeadersStorage::new(&ctx, "RICK".to_string()); + let storage = IDBBlockHeadersStorage::new(&ctx, "RICK".to_string(), None); register_wasm_log(); diff --git a/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs b/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs index 08e1a962c8..22cd70e929 100644 --- a/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs +++ b/mm2src/coins/utxo/utxo_block_header_storage/wasm/indexeddb_block_header_storage.rs @@ -16,7 +16,7 @@ use std::collections::HashMap; const DB_VERSION: u32 = 1; pub type IDBBlockHeadersStorageRes = MmResult; -pub type IDBBlockHeadersInnerLocked<'a> = DbLocked<'a, IDBBlockHeadersInner>; +pub type IDBBlockHeadersInnerLocked = DbLocked; pub struct IDBBlockHeadersInner { pub inner: IndexedDb, @@ -44,19 +44,21 @@ impl IDBBlockHeadersInner { pub struct IDBBlockHeadersStorage { pub db: SharedDb, pub ticker: String, + pub db_id: Option, } impl IDBBlockHeadersStorage { - pub fn new(ctx: &MmArc, ticker: String) -> Self { + pub fn new(ctx: &MmArc, ticker: String, db_id: Option<&str>) -> Self { Self { - db: ConstructibleDb::new(ctx).into_shared(), + db: ConstructibleDb::new(ctx, db_id).into_shared(), ticker, + db_id: db_id.map(|e| e.to_string()), } } - async fn lock_db(&self) -> IDBBlockHeadersStorageRes> { + async fn lock_db(&self) -> IDBBlockHeadersStorageRes { self.db - .get_or_initialize() + .get_or_initialize(None) .await .mm_err(|err| BlockHeaderStorageError::init_err(&self.ticker, err.to_string())) } diff --git a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs index 60b4d75ff0..d0f2a3a19a 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs @@ -127,7 +127,7 @@ where } if let EventInitStatus::Failed(err) = - EventBehaviour::spawn_if_active(UtxoStandardCoin::from(utxo_arc), stream_config).await + EventBehaviour::spawn_if_active(UtxoStandardCoin::from(utxo_arc.clone()), stream_config).await { return MmError::err(UtxoCoinBuildError::FailedSpawningBalanceEvents(err)); } diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 446cadf2bb..68b0304b6f 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -9,8 +9,9 @@ use crate::utxo::{output_script, ElectrumBuilderArgs, ElectrumProtoVerifier, Ele RecentlySpentOutPoints, ScripthashNotification, ScripthashNotificationSender, TxFee, UtxoCoinConf, UtxoCoinFields, UtxoHDWallet, UtxoRpcMode, UtxoSyncStatus, UtxoSyncStatusLoopHandle, UTXO_DUST_AMOUNT}; -use crate::{BlockchainNetwork, CoinTransportMetrics, DerivationMethod, HistorySyncState, IguanaPrivKey, - PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, UtxoActivationParams}; +use crate::{lp_coinfind_any, BlockchainNetwork, CoinTransportMetrics, DerivationMethod, HistorySyncState, + IguanaPrivKey, PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, + UtxoActivationParams}; use async_trait::async_trait; use chain::TxHashAlgo; use common::custom_futures::repeatable::{Ready, Retry}; @@ -28,6 +29,8 @@ use keys::bytes::Bytes; pub use keys::{Address, AddressBuilder, AddressFormat as UtxoAddressFormat, AddressHashEnum, AddressScriptType, KeyPair, Private, Public, Secret}; use mm2_core::mm_ctx::MmArc; +#[cfg(not(target_arch = "wasm32"))] +use mm2_core::sql_connection_pool::run_db_migration_for_new_pubkey; use mm2_err_handle::prelude::*; use primitives::hash::H160; use rand::seq::SliceRandom; @@ -217,6 +220,15 @@ pub trait UtxoFieldsWithGlobalHDBuilder: UtxoCoinBuilderCommonOps { bip39_secp_priv_key: global_hd_ctx.root_priv_key().clone(), }; + #[cfg(not(target_arch = "wasm32"))] + { + // db_id should be the current activated key for this hd wallet + let pubkey = activated_key_pair.public().address_hash().to_string(); + run_db_migration_for_new_pubkey(self.ctx(), pubkey) + .await + .map_to_mm(UtxoCoinBuildError::Internal)? + } + let address_format = self.address_format()?; let hd_wallet_rmd160 = *self.ctx().rmd160(); let hd_wallet_storage = @@ -342,7 +354,6 @@ pub trait UtxoFieldsWithHardwareWalletBuilder: UtxoCoinBuilderCommonOps { return MmError::err(UtxoCoinBuildError::CoinDoesntSupportTrezor); } let hd_wallet_rmd160 = self.trezor_wallet_rmd160()?; - // For now, use a default script pubkey. // TODO change the type of `recently_spent_outpoints` to `AsyncMutex>` let my_script_pubkey = Bytes::new(); @@ -602,8 +613,15 @@ pub trait UtxoCoinBuilderCommonOps { event_handlers.push(ElectrumProtoVerifier { on_event_tx }.into_shared()); } + let db_id = match lp_coinfind_any(ctx, self.ticker()) + .await + .map_to_mm(UtxoCoinBuildError::Internal)? + { + Some(coin) => coin.inner.account_db_id().await, + None => None, + }; let storage_ticker = self.ticker().replace('-', "_"); - let block_headers_storage = BlockHeaderStorage::new_from_ctx(self.ctx().clone(), storage_ticker) + let block_headers_storage = BlockHeaderStorage::new_from_ctx(self.ctx(), storage_ticker, db_id.as_deref()) .map_to_mm(|e| UtxoCoinBuildError::Internal(e.to_string()))?; if !block_headers_storage.is_initialized_for().await? { block_headers_storage.init().await?; @@ -767,7 +785,7 @@ pub trait UtxoCoinBuilderCommonOps { } #[cfg(not(target_arch = "wasm32"))] - fn tx_cache_path(&self) -> PathBuf { self.ctx().dbdir().join("TX_CACHE") } + fn tx_cache_path(&self) -> PathBuf { self.ctx().dbdir(None).join("TX_CACHE") } fn block_header_status_channel( &self, @@ -820,9 +838,7 @@ pub trait UtxoCoinBuilderCommonOps { return Ok(None); } - let block_to_sync_from = current_block_height - blocks_to_sync; - - Ok(Some(block_to_sync_from)) + Ok(Some(current_block_height - blocks_to_sync)) } } @@ -852,7 +868,7 @@ fn read_native_mode_conf( "Error parsing the native wallet configuration '{}': {}", filename.as_ref().display(), err - ) + ); }, }; let rpc_port = match read_property(&conf, network, "rpcport") { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 175454e1da..cf9fef14c6 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -2119,7 +2119,7 @@ pub fn watcher_validate_taker_fee( return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx {:?} does not have output {}", taker_fee_tx, output_index - ))) + ))); }, } @@ -2207,7 +2207,7 @@ pub fn validate_fee( return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx {:?} does not have output {}", tx, output_index - ))) + ))); }, } @@ -2234,7 +2234,7 @@ pub fn validate_fee( return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided burn tx output {:?} does not have output {}", tx, output_index - ))) + ))); }, } } @@ -2308,7 +2308,7 @@ pub fn watcher_validate_taker_payment( None => { return MmError::err(ValidatePaymentError::WrongPaymentTx( "Payment tx has no outputs".to_string(), - )) + )); }, }; @@ -2323,7 +2323,7 @@ pub fn watcher_validate_taker_payment( None => { return MmError::err(ValidatePaymentError::WrongPaymentTx( "Taker payment refund tx has no inputs".to_string(), - )) + )); }, }; @@ -2546,7 +2546,7 @@ pub async fn get_taker_watcher_reward(coin: &T, address: &str) -> ValidateAd return ValidateAddressResult { is_valid: false, reason: Some(e.to_string()), - } + }; }, }; @@ -3123,7 +3123,10 @@ where T: UtxoStandardOps + UtxoCommonOps + MmCoin + MarketCoinOps, { const MIGRATION_NUMBER: u64 = 1; - let history = match coin.load_history_from_file(ctx).compat().await { + let history = match coin + .load_history_from_file(ctx, coin.account_db_id().await.as_deref()) + .await + { Ok(history) => history, Err(e) => { log_tag!( @@ -3154,7 +3157,7 @@ where .collect(); if updated { - if let Err(e) = coin.save_history_to_file(ctx, to_write).compat().await { + if let Err(e) = coin.save_history_to_file(ctx, to_write).await { log_tag!( ctx, "", @@ -3165,7 +3168,11 @@ where return; }; } - if let Err(e) = coin.update_migration_file(ctx, MIGRATION_NUMBER).compat().await { + if let Err(e) = coin + .update_migration_file(ctx, MIGRATION_NUMBER, coin.account_db_id().await.as_deref()) + .compat() + .await + { log_tag!( ctx, "", @@ -3181,7 +3188,11 @@ async fn migrate_tx_history(coin: &T, ctx: &MmArc) where T: UtxoStandardOps + UtxoCommonOps + MmCoin + MarketCoinOps, { - let current_migration = coin.get_tx_history_migration(ctx).compat().await.unwrap_or(0); + let current_migration = coin + .get_tx_history_migration(ctx, coin.account_db_id().await.as_deref()) + .compat() + .await + .unwrap_or(0); if current_migration < 1 { tx_history_migration_1(coin, ctx).await; } @@ -3196,7 +3207,10 @@ where migrate_tx_history(&coin, &ctx).await; let mut my_balance: Option = None; - let history = match coin.load_history_from_file(&ctx).compat().await { + let history = match coin + .load_history_from_file(&ctx, coin.account_db_id().await.as_deref()) + .await + { Ok(history) => history, Err(e) => { log_tag!( @@ -3303,7 +3317,7 @@ where if history_map.len() < history_length { let to_write: Vec = history_map.values().cloned().collect(); - if let Err(e) = coin.save_history_to_file(&ctx, to_write).compat().await { + if let Err(e) = coin.save_history_to_file(&ctx, to_write).await { log_tag!( ctx, "", @@ -3389,7 +3403,7 @@ where } if updated { let to_write: Vec = history_map.values().cloned().collect(); - if let Err(e) = coin.save_history_to_file(&ctx, to_write).compat().await { + if let Err(e) = coin.save_history_to_file(&ctx, to_write).await { log_tag!( ctx, "", @@ -3429,7 +3443,7 @@ where return RequestTxHistoryResult::CriticalError(ERRL!( "Error on getting self address: {}. Stop tx history", e - )) + )); }, }; @@ -4307,7 +4321,7 @@ pub async fn validate_payment<'a, T: UtxoCommonOps>( None => { return MmError::err(ValidatePaymentError::WrongPaymentTx( "Payment tx has no outputs".to_string(), - )) + )); }, }; @@ -5126,6 +5140,34 @@ where Ok(transaction) } +pub async fn account_db_id(coin: &Coin) -> Option +where + Coin: CoinWithDerivationMethod + HDWalletCoinOps + HDCoinWithdrawOps + UtxoCommonOps, +{ + if let Some(hd_wallet) = coin.derivation_method().hd_wallet() { + // we can use hd_wallet_rmd160 as our shared_db_id since it's unique to a device + return hd_wallet + .get_enabled_address() + .await + .map(|addr| hex::encode(addr.pubkey().address_hash().as_slice())); + } + + None +} + +/// In normal wallet mode, this function returns the regular `db_id`, which is the RMD160 hash of the public key. +/// In HD wallet mode, it returns `hd_wallet_rmd160`, which is the RMD160 hash unique to the HD wallet/device. +pub async fn shared_db_id(coin: &Coin) -> Option +where + Coin: CoinWithDerivationMethod + HDWalletCoinOps + HDCoinWithdrawOps + UtxoCommonOps, +{ + // Use the hd_wallet_rmd160 as the db_id since it's unique to a device and not tied to a single address + // Fallback to the account db_id for non-HD wallets. + coin.derivation_method() + .hd_wallet() + .map(|hd| hd.inner.hd_wallet_rmd160.to_string()) +} + #[test] fn test_increase_by_percent() { assert_eq!(increase_by_percent(4300, 1.), 4343); diff --git a/mm2src/coins/utxo/utxo_common_tests.rs b/mm2src/coins/utxo/utxo_common_tests.rs index 03fea53699..0bc1f920e1 100644 --- a/mm2src/coins/utxo/utxo_common_tests.rs +++ b/mm2src/coins/utxo/utxo_common_tests.rs @@ -285,8 +285,15 @@ pub(super) async fn test_hd_utxo_tx_history_impl(rpc_client: ElectrumClient) { }); let coin = utxo_coin_from_fields(fields); + #[cfg(not(target_arch = "wasm32"))] + { + let dbs = ctx.sqlite_conn_pool.as_option().unwrap(); + dbs.add_test_db(coin.shared_db_id(&ctx).await.unwrap()); + } let current_balances = coin.my_addresses_balances().await.unwrap(); - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, coin.shared_db_id(&ctx).await) + .build() + .unwrap(); spawn(utxo_history_loop( coin.clone(), storage, @@ -311,7 +318,9 @@ pub(super) async fn test_hd_utxo_tx_history_impl(rpc_client: ElectrumClient) { _ => unimplemented!(), } - let storage = TxHistoryStorageBuilder::new(&ctx).build().unwrap(); + let storage = TxHistoryStorageBuilder::new(&ctx, coin.shared_db_id(&ctx).await) + .build() + .unwrap(); spawn(utxo_history_loop( coin.clone(), storage, diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index d55ce40420..28c0310b82 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -1050,6 +1050,10 @@ impl MmCoin for UtxoStandardCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + async fn account_db_id(&self) -> Option { utxo_common::account_db_id(self).await } + + async fn shared_db_id(&self, _ctx: &MmArc) -> Option { utxo_common::shared_db_id(self).await } } #[async_trait] diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 81d0aca85c..37b3d7dead 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -786,7 +786,7 @@ pub async fn z_coin_from_conf_and_params( #[cfg(target_arch = "wasm32")] let db_dir_path = PathBuf::new(); #[cfg(not(target_arch = "wasm32"))] - let db_dir_path = ctx.dbdir(); + let db_dir_path = ctx.dbdir(None); let z_spending_key = None; let builder = ZCoinBuilder::new( ctx, @@ -863,6 +863,10 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { async fn build(self) -> MmResult { let utxo = self.build_utxo_fields().await?; let utxo_arc = UtxoArc::new(utxo); + let db_id = utxo_arc + .priv_key_policy + .activated_key() + .map(|activated_key| hex::encode(activated_key.public().address_hash().as_slice())); let z_spending_key = match self.z_spending_key { Some(ref z_spending_key) => z_spending_key.clone(), @@ -884,13 +888,14 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { .expect("DEX_FEE_Z_ADDR is a valid z-address") .expect("DEX_FEE_Z_ADDR is a valid z-address"); - let z_tx_prover = self.z_tx_prover().await?; + let z_tx_prover = self.z_tx_prover(db_id.as_deref()).await?; let my_z_addr_encoded = encode_payment_address( self.protocol_info.consensus_params.hrp_sapling_payment_address(), &my_z_addr, ); - let blocks_db = self.init_blocks_db().await?; + let blocks_db = self.init_blocks_db(db_id.as_deref()).await?; + let (z_balance_event_sender, z_balance_event_handler) = if self.ctx.event_stream_configuration.is_some() { let (sender, receiver) = futures::channel::mpsc::unbounded(); (Some(sender), Some(Arc::new(AsyncMutex::new(receiver)))) @@ -907,6 +912,7 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { blocks_db, &z_spending_key, z_balance_event_sender, + db_id.as_deref(), ) .await? }, @@ -924,6 +930,7 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> { skip_sync_params.unwrap_or_default(), &z_spending_key, z_balance_event_sender, + db_id.as_deref(), ) .await? }, @@ -998,18 +1005,18 @@ impl<'a> ZCoinBuilder<'a> { } } - async fn init_blocks_db(&self) -> Result> { + async fn init_blocks_db(&self, db_id: Option<&str>) -> Result> { let cache_db_path = self.db_dir_path.join(format!("{}_cache.db", self.ticker)); let ctx = self.ctx.clone(); let ticker = self.ticker.to_string(); - BlockDbImpl::new(&ctx, ticker, cache_db_path) + BlockDbImpl::new(&ctx, ticker, cache_db_path, db_id) .map_err(|err| MmError::new(ZcoinClientInitError::ZcoinStorageError(err.to_string()))) .await } #[cfg(not(target_arch = "wasm32"))] - async fn z_tx_prover(&self) -> Result> { + async fn z_tx_prover(&self, _db_id: Option<&str>) -> Result> { let params_dir = match &self.z_coin_params.zcash_params_path { None => default_params_folder().or_mm_err(|| ZCoinBuildError::ZCashParamsNotFound)?, Some(file_path) => PathBuf::from(file_path), @@ -1028,8 +1035,8 @@ impl<'a> ZCoinBuilder<'a> { } #[cfg(target_arch = "wasm32")] - async fn z_tx_prover(&self) -> Result> { - let params_db = ZcashParamsWasmImpl::new(self.ctx) + async fn z_tx_prover(&self, db_id: Option<&str>) -> Result> { + let params_db = ZcashParamsWasmImpl::new(self.ctx, db_id) .await .mm_err(|err| ZCoinBuildError::ZCashParamsError(err.to_string()))?; let (sapling_spend, sapling_output) = if !params_db @@ -1783,6 +1790,13 @@ impl MmCoin for ZCoin { fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.as_ref().abortable_system) } fn on_token_deactivated(&self, _ticker: &str) {} + + async fn account_db_id(&self) -> Option { + self.utxo_arc + .priv_key_policy + .activated_key() + .map(|activated_key| hex::encode(activated_key.public().address_hash().as_slice())) + } } #[async_trait] diff --git a/mm2src/coins/z_coin/storage/blockdb/blockdb_idb_storage.rs b/mm2src/coins/z_coin/storage/blockdb/blockdb_idb_storage.rs index cccf8cc0a9..88dfa6c517 100644 --- a/mm2src/coins/z_coin/storage/blockdb/blockdb_idb_storage.rs +++ b/mm2src/coins/z_coin/storage/blockdb/blockdb_idb_storage.rs @@ -18,7 +18,7 @@ use zcash_primitives::consensus::BlockHeight; const DB_NAME: &str = "z_compactblocks_cache"; const DB_VERSION: u32 = 1; -pub type BlockDbInnerLocked<'a> = DbLocked<'a, BlockDbInner>; +pub type BlockDbInnerLocked = DbLocked; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct BlockDbTable { @@ -67,16 +67,17 @@ impl BlockDbInner { } impl BlockDbImpl { - pub async fn new(ctx: &MmArc, ticker: String, _path: PathBuf) -> ZcoinStorageRes { + pub async fn new(ctx: &MmArc, ticker: String, _path: PathBuf, db_id: Option<&str>) -> ZcoinStorageRes { Ok(Self { - db: ConstructibleDb::new(ctx).into_shared(), + db: ConstructibleDb::new(ctx, db_id).into_shared(), ticker, + db_id: db_id.map(|e| e.to_string()), }) } - async fn lock_db(&self) -> ZcoinStorageRes> { + async fn lock_db(&self) -> ZcoinStorageRes { self.db - .get_or_initialize() + .get_or_initialize(self.db_id.as_deref()) .await .mm_err(|err| ZcoinStorageError::DbError(err.to_string())) } diff --git a/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs b/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs index 7523360807..b17a5a5cf4 100644 --- a/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs +++ b/mm2src/coins/z_coin/storage/blockdb/blockdb_sql_storage.rs @@ -45,7 +45,8 @@ impl From> for ZcoinStorageError { impl BlockDbImpl { #[cfg(not(test))] - pub async fn new(_ctx: &MmArc, ticker: String, path: PathBuf) -> ZcoinStorageRes { + pub async fn new(_ctx: &MmArc, ticker: String, path: PathBuf, db_id: Option<&str>) -> ZcoinStorageRes { + let db_id = db_id.map(|e| e.to_string()); async_blocking(move || { let conn = Connection::open(path).map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; let conn = Arc::new(Mutex::new(conn)); @@ -62,22 +63,29 @@ impl BlockDbImpl { ) .map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; - Ok(Self { db: conn, ticker }) + Ok(Self { + db: conn, + ticker, + db_id, + }) }) .await } #[cfg(test)] - pub(crate) async fn new(ctx: &MmArc, ticker: String, _path: PathBuf) -> ZcoinStorageRes { - let ctx = ctx.clone(); + pub(crate) async fn new( + _ctx: &MmArc, + ticker: String, + _path: PathBuf, + db_id: Option<&str>, + ) -> ZcoinStorageRes { + let db_id = db_id.map(|e| e.to_string()); + let conn = Arc::new(Mutex::new(Connection::open_in_memory().unwrap())); + let conn_clone = conn.clone(); async_blocking(move || { - let conn = ctx - .sqlite_connection - .clone_or(Arc::new(Mutex::new(Connection::open_in_memory().unwrap()))); - let conn_clone = conn.clone(); - let conn_clone = conn_clone.lock().unwrap(); - run_optimization_pragmas(&conn_clone).map_err(|err| ZcoinStorageError::DbError(err.to_string()))?; - conn_clone + let conn_lock = conn_clone.lock().unwrap(); + run_optimization_pragmas(&conn_lock).map_err(|err| ZcoinStorageError::DbError(err.to_string()))?; + conn_lock .execute( "CREATE TABLE IF NOT EXISTS compactblocks ( height INTEGER PRIMARY KEY, @@ -87,7 +95,11 @@ impl BlockDbImpl { ) .map_to_mm(|err| ZcoinStorageError::DbError(err.to_string()))?; - Ok(BlockDbImpl { db: conn, ticker }) + Ok(BlockDbImpl { + db: conn, + ticker, + db_id, + }) }) .await } diff --git a/mm2src/coins/z_coin/storage/blockdb/mod.rs b/mm2src/coins/z_coin/storage/blockdb/mod.rs index 7e2ef49fe7..1ad6eeca56 100644 --- a/mm2src/coins/z_coin/storage/blockdb/mod.rs +++ b/mm2src/coins/z_coin/storage/blockdb/mod.rs @@ -7,11 +7,13 @@ use db_common::sqlite::rusqlite::Connection; #[cfg(target_arch = "wasm32")] pub(crate) mod blockdb_idb_storage; + #[cfg(target_arch = "wasm32")] use blockdb_idb_storage::BlockDbInner; #[cfg(target_arch = "wasm32")] use mm2_db::indexed_db::SharedDb; /// A wrapper for the db connection to the block cache database in native and browser. +#[allow(unused)] #[derive(Clone)] pub struct BlockDbImpl { #[cfg(not(target_arch = "wasm32"))] @@ -20,6 +22,7 @@ pub struct BlockDbImpl { pub db: SharedDb, #[allow(unused)] ticker: String, + db_id: Option, } #[cfg(any(test, target_arch = "wasm32"))] @@ -32,13 +35,13 @@ mod block_db_storage_tests { const TICKER: &str = "ARRR"; const HEADERS: &[(u32, &str)] = &[(1900000, - "10E0FB731A2044797F3BB78323A7717007F1E289A3689E0B5B3433385DBD8E6F6A17000000002220735484676853C744A8CA0FEA105081C54A8C50A151E42E31EC7E20040000000028EBACFD9306"), (1900001, - "10E1FB731A20A261B624D0E42238255A69F96E45EEA341B5E4125A7DD710118D150B00000000222044797F3BB78323A7717007F1E289A3689E0B5B3433385DBD8E6F6A170000000028FEACFD9306"), (1900002,"10E2FB731A208747587DE8DDED766591FA6C9859D77BFC9C293B054F3D38A9BC5E08000000002220A261B624D0E42238255A69F96E45EEA341B5E4125A7DD710118D150B0000000028F7ADFD93063AC002080212201D7165BCACD3245EED7324367EB34199EA2ED502726933484FEFA6A220AA330F22220A208DD3C9362FBCF766BEF2DFA3A3B186BBB43CA456DB9690EFD06978FC822056D22A7A0A20245E73ED6EB4B73805D3929F841CCD7E01523E2B8A0F29D721CD82547A470C711220D6BAF6AF4783FF265451B8A7A5E4271EA72F034890DA234427082F84F08256DD1A34EAABEE115A1FCDED194189F586C6DC2099E8C5F47BD68B210146EDFFCB39649EB55504910EC590E6E9908B6114ED3DDFD5861FDC2A7A0A2079E70D202FEE537011284A30F1531BCF627613CBBAAFABBB24CE56600FE94B6C122041E9FBA0E6197A58532F61BD7617CACEC8C2F10C77AA8B99B2E535EE1D3C36171A341B6A04C5EC9A2AE8CDF0433C9AAD36C647139C9542759E2758FD4A10ED0C78F8087BE5AEE92EA8834E6CE116C8A5737B7607BD523AC002080312202790606A461DA171221480A3FC414CCF9C273FE6F0C2E3CFA6C85D6CDE8EFE5C22220A201767E6E3B390FAB4C79E46131C54ED91A987EEA2286DB80F240D431AC07A750C2A7A0A20E86C11A660EB72F1449BA0CEB57FFB313A4047880C33ADED93945ED9C477581B12201752816751ABAB19398A4A5CFE429724D820588BCFEDC7D88B399D9B24FB4C111A34DB38AE57231FBE768063E08D8EC70E3486FF89A74E0840B6F5D8412F1C7E2C5D884AA08E2F7EDA42836B80B4433C83CDDC8B51DE2A7A0A20E2FEF897A286A8D5AD9E0485F287CE1A73970EADA899DBE3FC77043846E06B1E1220F0A046829B17CC8B5B750281CD20A1E28F983E599AA2A1C8F3BD97BE49C55CEB1A3488DCDA1444CBACE213100507FC83627D83624EF2AD47C25160F5E604595158C98EBC3549C0A07359FB42D8437A70AB472FB64AA13AC002080412201EDD399E68128B97F6F98E31C1965361528AC07665114D09F9D119C089791E9222220A20B9471453950609CF8C2EDF721FE7D0D2D211BBD158283E8D6B80EAAB312968EF2A7A0A201FF6F7D74ABBAC9D4E5A95F63861C19FE3D18083ABE2EACE7B8A70E7E5FCB51812206753F2992061EF3FC0C37FC0D1352A386514B2CC1AEB39AC835A8D9BFBD022D91A34BA41719ECF19520BD7D6EFB08AAF5018282026781D0FE5697811B34E0DEFE4D4691585D4994056E109DC19FFE63CAB29CA4F26682A7A0A200E570E832326625C9D8536DBAC389529A090FC54C3F378E25431405751BBFF391220D27A030843C93522B2D232644E7AC7CF235494B126FDAEA9F5980FA1AECE746E1A34EF8BD98D7DD39659714E7851E47F57A52741F564F0275CE8A82F2665C70EA5887B0CE8501CF509A8265ECB155A00A0629B463C253AC00208051220E1F375AD9EC6A774E444ECC5EB6F07237B1DE9EAA1A9FD7AEF392D6F40BA705822220A20D8298A06C9657E042DC69473B23A74C94E51AF684DA6281CE7F797791F486AD42A7A0A209216A5DBC616291688CDFB075A5E639FA8000ADD006438C4BCE98D000AE0DF3512202C20533A17279C46EC995DBF819673039E5810DCD2DA024DAEF64053CD7B562D1A346928F93BB25B03519AC83B297F77E2F54F62B1E722E6F8D886ADF709455C2C0B930CE429EA24ECD15354085F7FA3F2A4077DE76D2A7A0A203AE3F07AB8AB4C76B246A0D7CA9321F84081144E9B7E3AE0CEC0139B392E443812200791064E9E188BF1D1373BEEFAE7458F12F976B15896CD69970019B4560A5F721A3428ADC7816F15528F65372E585E07D1CD6C0DFB3F3BA7BD263BB4E5A3ADAAFD84CD55FFBDD23787163F52711A22935EB52A30EB37") + "10E0FB731A2044797F3BB78323A7717007F1E289A3689E0B5B3433385DBD8E6F6A17000000002220735484676853C744A8CA0FEA105081C54A8C50A151E42E31EC7E20040000000028EBACFD9306"), (1900001, + "10E1FB731A20A261B624D0E42238255A69F96E45EEA341B5E4125A7DD710118D150B00000000222044797F3BB78323A7717007F1E289A3689E0B5B3433385DBD8E6F6A170000000028FEACFD9306"), (1900002, "10E2FB731A208747587DE8DDED766591FA6C9859D77BFC9C293B054F3D38A9BC5E08000000002220A261B624D0E42238255A69F96E45EEA341B5E4125A7DD710118D150B0000000028F7ADFD93063AC002080212201D7165BCACD3245EED7324367EB34199EA2ED502726933484FEFA6A220AA330F22220A208DD3C9362FBCF766BEF2DFA3A3B186BBB43CA456DB9690EFD06978FC822056D22A7A0A20245E73ED6EB4B73805D3929F841CCD7E01523E2B8A0F29D721CD82547A470C711220D6BAF6AF4783FF265451B8A7A5E4271EA72F034890DA234427082F84F08256DD1A34EAABEE115A1FCDED194189F586C6DC2099E8C5F47BD68B210146EDFFCB39649EB55504910EC590E6E9908B6114ED3DDFD5861FDC2A7A0A2079E70D202FEE537011284A30F1531BCF627613CBBAAFABBB24CE56600FE94B6C122041E9FBA0E6197A58532F61BD7617CACEC8C2F10C77AA8B99B2E535EE1D3C36171A341B6A04C5EC9A2AE8CDF0433C9AAD36C647139C9542759E2758FD4A10ED0C78F8087BE5AEE92EA8834E6CE116C8A5737B7607BD523AC002080312202790606A461DA171221480A3FC414CCF9C273FE6F0C2E3CFA6C85D6CDE8EFE5C22220A201767E6E3B390FAB4C79E46131C54ED91A987EEA2286DB80F240D431AC07A750C2A7A0A20E86C11A660EB72F1449BA0CEB57FFB313A4047880C33ADED93945ED9C477581B12201752816751ABAB19398A4A5CFE429724D820588BCFEDC7D88B399D9B24FB4C111A34DB38AE57231FBE768063E08D8EC70E3486FF89A74E0840B6F5D8412F1C7E2C5D884AA08E2F7EDA42836B80B4433C83CDDC8B51DE2A7A0A20E2FEF897A286A8D5AD9E0485F287CE1A73970EADA899DBE3FC77043846E06B1E1220F0A046829B17CC8B5B750281CD20A1E28F983E599AA2A1C8F3BD97BE49C55CEB1A3488DCDA1444CBACE213100507FC83627D83624EF2AD47C25160F5E604595158C98EBC3549C0A07359FB42D8437A70AB472FB64AA13AC002080412201EDD399E68128B97F6F98E31C1965361528AC07665114D09F9D119C089791E9222220A20B9471453950609CF8C2EDF721FE7D0D2D211BBD158283E8D6B80EAAB312968EF2A7A0A201FF6F7D74ABBAC9D4E5A95F63861C19FE3D18083ABE2EACE7B8A70E7E5FCB51812206753F2992061EF3FC0C37FC0D1352A386514B2CC1AEB39AC835A8D9BFBD022D91A34BA41719ECF19520BD7D6EFB08AAF5018282026781D0FE5697811B34E0DEFE4D4691585D4994056E109DC19FFE63CAB29CA4F26682A7A0A200E570E832326625C9D8536DBAC389529A090FC54C3F378E25431405751BBFF391220D27A030843C93522B2D232644E7AC7CF235494B126FDAEA9F5980FA1AECE746E1A34EF8BD98D7DD39659714E7851E47F57A52741F564F0275CE8A82F2665C70EA5887B0CE8501CF509A8265ECB155A00A0629B463C253AC00208051220E1F375AD9EC6A774E444ECC5EB6F07237B1DE9EAA1A9FD7AEF392D6F40BA705822220A20D8298A06C9657E042DC69473B23A74C94E51AF684DA6281CE7F797791F486AD42A7A0A209216A5DBC616291688CDFB075A5E639FA8000ADD006438C4BCE98D000AE0DF3512202C20533A17279C46EC995DBF819673039E5810DCD2DA024DAEF64053CD7B562D1A346928F93BB25B03519AC83B297F77E2F54F62B1E722E6F8D886ADF709455C2C0B930CE429EA24ECD15354085F7FA3F2A4077DE76D2A7A0A203AE3F07AB8AB4C76B246A0D7CA9321F84081144E9B7E3AE0CEC0139B392E443812200791064E9E188BF1D1373BEEFAE7458F12F976B15896CD69970019B4560A5F721A3428ADC7816F15528F65372E585E07D1CD6C0DFB3F3BA7BD263BB4E5A3ADAAFD84CD55FFBDD23787163F52711A22935EB52A30EB37") ]; pub(crate) async fn test_insert_block_and_get_latest_block_impl() { let ctx = mm_ctx_with_custom_db(); - let db = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let db = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); // insert block @@ -53,7 +56,7 @@ mod block_db_storage_tests { pub(crate) async fn test_rewind_to_height_impl() { let ctx = mm_ctx_with_custom_db(); - let db = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let db = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); // insert block @@ -78,7 +81,7 @@ mod block_db_storage_tests { #[allow(unused)] pub(crate) async fn test_process_blocks_with_mode_impl() { let ctx = mm_ctx_with_custom_db(); - let db = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let db = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); // insert block diff --git a/mm2src/coins/z_coin/storage/walletdb/wallet_sql_storage.rs b/mm2src/coins/z_coin/storage/walletdb/wallet_sql_storage.rs index 3a957d375f..4cd1c95187 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wallet_sql_storage.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wallet_sql_storage.rs @@ -23,6 +23,7 @@ pub async fn create_wallet_db( checkpoint_block: Option, evk: ExtendedFullViewingKey, continue_from_prev_sync: bool, + _db_id: Option<&str>, ) -> Result, MmError> { let db = async_blocking(move || { WalletDbAsync::for_path(wallet_db_path, consensus_params) @@ -83,6 +84,7 @@ impl<'a> WalletDbShared { checkpoint_block: Option, z_spending_key: &ExtendedSpendingKey, continue_from_prev_sync: bool, + db_id: Option<&str>, ) -> ZcoinStorageRes { let ticker = builder.ticker; let consensus_params = builder.protocol_info.consensus_params.clone(); @@ -92,6 +94,7 @@ impl<'a> WalletDbShared { checkpoint_block, ExtendedFullViewingKey::from(z_spending_key), continue_from_prev_sync, + db_id, ) .await .map_err(|err| ZcoinStorageError::InitDbError { diff --git a/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs b/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs index 907a007c55..330b86be3d 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs @@ -127,7 +127,9 @@ mod wasm_test { } async fn wallet_db_from_zcoin_builder_for_test(ctx: &MmArc, ticker: &str) -> WalletIndexedDb { - WalletIndexedDb::new(ctx, ticker, consensus_params()).await.unwrap() + WalletIndexedDb::new(ctx, ticker, consensus_params(), None) + .await + .unwrap() } #[wasm_bindgen_test] @@ -205,7 +207,7 @@ mod wasm_test { async fn test_valid_chain_state() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -313,7 +315,7 @@ mod wasm_test { async fn invalid_chain_cache_disconnected() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -402,7 +404,7 @@ mod wasm_test { async fn test_invalid_chain_reorg() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -491,7 +493,7 @@ mod wasm_test { async fn test_data_db_rewinding() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -557,7 +559,7 @@ mod wasm_test { async fn test_scan_cached_blocks_requires_sequential_blocks() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -625,7 +627,7 @@ mod wasm_test { async fn test_scan_cached_blokcs_finds_received_notes() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -678,7 +680,7 @@ mod wasm_test { async fn test_scan_cached_blocks_finds_change_notes() { // init blocks_db let ctx = mm_ctx_with_custom_db(); - let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()) + let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None) .await .unwrap(); @@ -744,7 +746,7 @@ mod wasm_test { // async fn create_to_address_fails_on_unverified_notes() { // // init blocks_db // let ctx = mm_ctx_with_custom_db(); - // let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()).await.unwrap(); + // let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None).await.unwrap(); // // // init walletdb. // let mut walletdb = wallet_db_from_zcoin_builder_for_test(&ctx, TICKER).await; @@ -1013,7 +1015,7 @@ mod wasm_test { // // // init blocks_db // let ctx = mm_ctx_with_custom_db(); - // let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new()).await.unwrap(); + // let blockdb = BlockDbImpl::new(&ctx, TICKER.to_string(), PathBuf::new(), None).await.unwrap(); // // // init walletdb. // let mut walletdb = wallet_db_from_zcoin_builder_for_test(&ctx, TICKER).await; diff --git a/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs b/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs index 9dbaf389a3..e130f79754 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs @@ -38,7 +38,7 @@ use zcash_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; const DB_NAME: &str = "wallet_db_cache"; const DB_VERSION: u32 = 1; -pub type WalletDbInnerLocked<'a> = DbLocked<'a, WalletDbInner>; +pub type WalletDbInnerLocked = DbLocked; macro_rules! num_to_bigint { ($value: ident) => { @@ -56,10 +56,11 @@ impl<'a> WalletDbShared { checkpoint_block: Option, z_spending_key: &ExtendedSpendingKey, continue_from_prev_sync: bool, + db_id: Option<&str>, ) -> ZcoinStorageRes { let ticker = builder.ticker; let consensus_params = builder.protocol_info.consensus_params.clone(); - let db = WalletIndexedDb::new(builder.ctx, ticker, consensus_params).await?; + let db = WalletIndexedDb::new(builder.ctx, ticker, consensus_params, db_id).await?; let extrema = db.block_height_extrema().await?; let get_evk = db.get_extended_full_viewing_keys().await?; let evk = ExtendedFullViewingKey::from(z_spending_key); @@ -129,6 +130,7 @@ pub struct WalletIndexedDb { pub db: SharedDb, pub ticker: String, pub params: ZcoinConsensusParams, + pub db_id: Option, } impl<'a> WalletIndexedDb { @@ -136,19 +138,21 @@ impl<'a> WalletIndexedDb { ctx: &MmArc, ticker: &str, consensus_params: ZcoinConsensusParams, + db_id: Option<&str>, ) -> MmResult { let db = Self { - db: ConstructibleDb::new(ctx).into_shared(), + db: ConstructibleDb::new(ctx, db_id).into_shared(), ticker: ticker.to_string(), params: consensus_params, + db_id: db_id.map(|e| e.to_string()), }; Ok(db) } - pub(crate) async fn lock_db(&self) -> ZcoinStorageRes> { + pub(crate) async fn lock_db(&self) -> ZcoinStorageRes { self.db - .get_or_initialize() + .get_or_initialize(self.db_id.as_deref()) .await .mm_err(|err| ZcoinStorageError::DbError(err.to_string())) } @@ -697,7 +701,7 @@ impl WalletIndexedDb { } /// Asynchronously rewinds the storage to a specified block height, effectively - /// removing data beyond the specified height from the storage. + /// removing data beyond the specified height from the storage. pub async fn rewind_to_height(&self, block_height: BlockHeight) -> ZcoinStorageRes<()> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; diff --git a/mm2src/coins/z_coin/storage/z_params/indexeddb.rs b/mm2src/coins/z_coin/storage/z_params/indexeddb.rs index 91a2ec51b4..05ef81db1f 100644 --- a/mm2src/coins/z_coin/storage/z_params/indexeddb.rs +++ b/mm2src/coins/z_coin/storage/z_params/indexeddb.rs @@ -11,7 +11,7 @@ const DB_VERSION: u32 = 1; const TARGET_SPEND_CHUNKS: usize = 12; pub(crate) type ZcashParamsWasmRes = MmResult; -pub(crate) type ZcashParamsInnerLocked<'a> = DbLocked<'a, ZcashParamsWasmInner>; +pub(crate) type ZcashParamsInnerLocked = DbLocked; /// Since sapling_spend data way is greater than indexeddb max_data(267386880) bytes to save, we need to split /// sapling_spend and insert to db multiple times with index(sapling_spend_id) @@ -24,7 +24,7 @@ struct ZcashParamsWasmTable { } impl ZcashParamsWasmTable { - const SPEND_OUTPUT_INDEX: &str = "sapling_spend_sapling_output_index"; + const SPEND_OUTPUT_INDEX: &'static str = "sapling_spend_sapling_output_index"; } impl TableSignature for ZcashParamsWasmTable { @@ -69,13 +69,13 @@ impl ZcashParamsWasmInner { pub(crate) struct ZcashParamsWasmImpl(SharedDb); impl ZcashParamsWasmImpl { - pub(crate) async fn new(ctx: &MmArc) -> MmResult { - Ok(Self(ConstructibleDb::new(ctx).into_shared())) + pub(crate) async fn new(ctx: &MmArc, db_id: Option<&str>) -> MmResult { + Ok(Self(ConstructibleDb::new(ctx, db_id).into_shared())) } - async fn lock_db(&self) -> ZcashParamsWasmRes> { + async fn lock_db(&self) -> ZcashParamsWasmRes { self.0 - .get_or_initialize() + .get_or_initialize(None) .await .mm_err(|err| ZcoinStorageError::DbError(err.to_string())) } diff --git a/mm2src/coins/z_coin/storage/z_params/mod.rs b/mm2src/coins/z_coin/storage/z_params/mod.rs index d86a7181a0..1ca891a370 100644 --- a/mm2src/coins/z_coin/storage/z_params/mod.rs +++ b/mm2src/coins/z_coin/storage/z_params/mod.rs @@ -65,7 +65,7 @@ async fn test_download_save_and_get_params() { register_wasm_log(); info!("Testing download, save and get params"); let ctx = mm_ctx_with_custom_db(); - let db = ZcashParamsWasmImpl::new(&ctx).await.unwrap(); + let db = ZcashParamsWasmImpl::new(&ctx, None).await.unwrap(); // save params let (sapling_spend, sapling_output) = db.download_and_save_params().await.unwrap(); // get params @@ -79,7 +79,7 @@ async fn test_download_save_and_get_params() { async fn test_check_for_no_params() { register_wasm_log(); let ctx = mm_ctx_with_custom_db(); - let db = ZcashParamsWasmImpl::new(&ctx).await.unwrap(); + let db = ZcashParamsWasmImpl::new(&ctx, None).await.unwrap(); // check for no params let check_params = db.check_params().await.unwrap(); assert!(!check_params) diff --git a/mm2src/coins/z_coin/z_rpc.rs b/mm2src/coins/z_coin/z_rpc.rs index bd71d554c6..23a1a31ddf 100644 --- a/mm2src/coins/z_coin/z_rpc.rs +++ b/mm2src/coins/z_coin/z_rpc.rs @@ -502,6 +502,7 @@ impl ZRpcOps for NativeClient { } } +#[allow(clippy::too_many_arguments)] pub(super) async fn init_light_client<'a>( builder: &ZCoinBuilder<'a>, lightwalletd_urls: Vec, @@ -510,6 +511,7 @@ pub(super) async fn init_light_client<'a>( skip_sync_params: bool, z_spending_key: &ExtendedSpendingKey, z_balance_event_sender: Option, + db_id: Option<&str>, ) -> Result<(AsyncMutex, WalletDbShared), MmError> { let coin = builder.ticker.to_string(); let (sync_status_notifier, sync_watcher) = channel(1); @@ -542,8 +544,14 @@ pub(super) async fn init_light_client<'a>( // check if no sync_params was provided and continue syncing from last height in db if it's > 0 or skip_sync_params is true. let continue_from_prev_sync = (min_height > 0 && sync_params.is_none()) || (skip_sync_params && min_height < sapling_activation_height); - let wallet_db = - WalletDbShared::new(builder, maybe_checkpoint_block, z_spending_key, continue_from_prev_sync).await?; + let wallet_db = WalletDbShared::new( + builder, + maybe_checkpoint_block, + z_spending_key, + continue_from_prev_sync, + db_id, + ) + .await?; // Check min_height in blocks_db and rewind blocks_db to 0 if sync_height != min_height if !continue_from_prev_sync && (sync_height != min_height) { // let user know we're clearing cache and re-syncing from new provided height. @@ -583,12 +591,14 @@ pub(super) async fn init_light_client<'a>( } #[cfg(not(target_arch = "wasm32"))] +#[allow(clippy::too_many_arguments)] pub(super) async fn init_native_client<'a>( builder: &ZCoinBuilder<'a>, native_client: NativeClient, blocks_db: BlockDbImpl, z_spending_key: &ExtendedSpendingKey, z_balance_event_sender: Option, + db_id: Option<&str>, ) -> Result<(AsyncMutex, WalletDbShared), MmError> { let coin = builder.ticker.to_string(); let (sync_status_notifier, sync_watcher) = channel(1); @@ -602,7 +612,8 @@ pub(super) async fn init_native_client<'a>( is_pre_sapling: false, actual: checkpoint_height, }; - let wallet_db = WalletDbShared::new(builder, checkpoint_block, z_spending_key, true) + + let wallet_db = WalletDbShared::new(builder, checkpoint_block, z_spending_key, true, db_id) .await .mm_err(|err| ZcoinClientInitError::ZcoinStorageError(err.to_string()))?; diff --git a/mm2src/coins_activation/src/lightning_activation.rs b/mm2src/coins_activation/src/lightning_activation.rs index 1d2f9ec232..5992f5ff29 100644 --- a/mm2src/coins_activation/src/lightning_activation.rs +++ b/mm2src/coins_activation/src/lightning_activation.rs @@ -15,7 +15,7 @@ use coins::lightning::ln_utils::{get_open_channels_nodes_addresses, init_channel use coins::lightning::{InvoicePayer, LightningCoin}; use coins::utxo::utxo_standard::UtxoStandardCoin; use coins::utxo::UtxoCommonOps; -use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, MmCoinEnum, RegisterCoinError}; +use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, RegisterCoinError}; use common::executor::{SpawnFuture, Timer}; use crypto::hw_rpc_task::{HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use derive_more::Display; @@ -371,7 +371,7 @@ async fn start_lightning( )); // Initialize DB - let db = init_db(ctx, conf.ticker.clone()).await?; + let db = init_db(ctx, conf.ticker.clone(), platform_coin.account_db_id().await.as_deref()).await?; // Initialize the ChannelManager task_handle.update_in_progress_status(LightningInProgressStatus::InitializingChannelManager)?; diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 051dd22fc3..5692d86376 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -476,7 +476,7 @@ where if req.request.tx_history() { platform_coin.start_history_background_fetching( ctx.clone(), - TxHistoryStorageBuilder::new(&ctx).build()?, + TxHistoryStorageBuilder::new(&ctx, platform_coin.clone().into().shared_db_id(&ctx).await).build()?, activation_result.get_platform_balance(), ); } diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs index 314f3066b4..8adc178adb 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs @@ -6,7 +6,7 @@ use crate::standalone_coin::init_standalone_coin_error::{CancelInitStandaloneCoi use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tx_history_storage::{CreateTxHistoryStorageError, TxHistoryStorageBuilder}; -use coins::{lp_coinfind, lp_register_coin, CoinsContext, MmCoinEnum, RegisterCoinError, RegisterCoinParams}; +use coins::{lp_coinfind, lp_register_coin, CoinsContext, MmCoin, MmCoinEnum, RegisterCoinError, RegisterCoinParams}; use common::{log, SuccessResponse}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -31,7 +31,7 @@ pub struct InitStandaloneCoinReq { } #[async_trait] -pub trait InitStandaloneCoinActivationOps: Into + Send + Sync + 'static { +pub trait InitStandaloneCoinActivationOps: Into + MmCoin + Send + Sync + 'static { type ActivationRequest: TxHistory + Clone + Send + Sync; type StandaloneProtocol: TryFromCoinProtocol + Clone + Send + Sync; // The following types are related to `RpcTask` management. @@ -205,16 +205,26 @@ where .await?; let result = coin - .get_activation_result(self.ctx.clone(), task_handle, &self.request.activation_params) + .get_activation_result(self.ctx.clone(), task_handle.clone(), &self.request.activation_params) .await?; log::info!("{} current block {}", ticker, result.current_block()); let tx_history = self.request.activation_params.tx_history(); if tx_history { let current_balances = result.get_addresses_balances(); + let coin_clone = Standalone::init_standalone_coin( + self.ctx.clone(), + ticker.clone(), + self.coin_conf.clone(), + &self.request.activation_params, + self.protocol_info.clone(), + task_handle.clone(), + ) + .await?; + coin.start_history_background_fetching( self.ctx.metrics.clone(), - TxHistoryStorageBuilder::new(&self.ctx).build()?, + TxHistoryStorageBuilder::new(&self.ctx, coin_clone.shared_db_id(&self.ctx).await).build()?, current_balances, ); } diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index 349e37b23d..c148fbd5c1 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -15,10 +15,13 @@ use coins::tendermint::{tendermint_priv_key_policy, RpcNode, TendermintActivatio TendermintCommons, TendermintConf, TendermintInitError, TendermintInitErrorKind, TendermintProtocolInfo, TendermintPublicKey, TendermintToken, TendermintTokenActivationParams, TendermintTokenInitError, TendermintTokenProtocolInfo}; +#[cfg(not(target_arch = "wasm32"))] use coins::utxo::dhash160; use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, PrivKeyBuildPolicy}; use common::executor::{AbortSettings, SpawnAbortable}; use common::{true_f, Future01CompatExt}; use mm2_core::mm_ctx::MmArc; +#[cfg(not(target_arch = "wasm32"))] +use mm2_core::sql_connection_pool::run_db_migration_for_new_pubkey; use mm2_err_handle::prelude::*; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; use mm2_event_stream::EventStreamConfiguration; @@ -245,6 +248,15 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin { kind: TendermintInitErrorKind::CantUseWatchersWithPubkeyPolicy, }); } + #[cfg(not(target_arch = "wasm32"))] + { + run_db_migration_for_new_pubkey(&ctx, dhash160(&pubkey.to_bytes()).to_string()) + .await + .map_to_mm(|err| TendermintInitError { + ticker: ticker.clone(), + kind: TendermintInitErrorKind::Internal(err), + })?; + } TendermintActivationPolicy::with_public_key(pubkey) } else { @@ -255,7 +267,25 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin { })?; let tendermint_private_key_policy = - tendermint_priv_key_policy(&conf, &ticker, private_key_policy, activation_request.path_to_address)?; + tendermint_priv_key_policy(&conf, &ticker, &private_key_policy, activation_request.path_to_address)?; + + #[cfg(not(target_arch = "wasm32"))] + { + if let PrivKeyBuildPolicy::GlobalHDAccount(_) = &private_key_policy { + let result = + TendermintActivationPolicy::with_private_key_policy(tendermint_private_key_policy.clone()); + let pubkey = result.public_key().map_to_mm(|e| TendermintInitError { + ticker: ticker.clone(), + kind: TendermintInitErrorKind::Internal(e.to_string()), + })?; + run_db_migration_for_new_pubkey(&ctx, dhash160(&pubkey.to_bytes()).to_string()) + .await + .map_to_mm(|err| TendermintInitError { + ticker: ticker.clone(), + kind: TendermintInitErrorKind::Internal(err), + })?; + }; + } TendermintActivationPolicy::with_private_key_policy(tendermint_private_key_policy) }; diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index c98fe610d2..eba11e1c35 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -531,7 +531,7 @@ pub fn double_panic_crash() { drop(panicker) // Delays the drop. } -/// RPC response, returned by the RPC handlers. +/// RPC response, returned by the RPC handlers. /// NB: By default the future is executed on the shared asynchronous reactor (`CORE`), /// the handler is responsible for spawning the future on another reactor if it doesn't fit the `CORE` well. pub type HyRes = Box>, Error = String> + Send>; @@ -697,8 +697,8 @@ pub fn now_sec_i64() -> i64 { #[cfg(not(target_arch = "wasm32"))] pub fn temp_dir() -> PathBuf { env::temp_dir() } -/// If the `MM_LOG` variable is present then tries to open that file. -/// Prints a warning to `stdout` if there's a problem opening the file. +/// If the `MM_LOG` variable is present then tries to open that file. +/// Prints a warning to `stdout` if there's a problem opening the file. /// Returns `None` if `MM_LOG` variable is not present or if the specified path can't be opened. #[cfg(not(target_arch = "wasm32"))] pub(crate) fn open_log_file() -> Option { @@ -881,7 +881,7 @@ pub const fn sixty_f64() -> f64 { 60. } pub fn one() -> NonZeroUsize { NonZeroUsize::new(1).unwrap() } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct PagingOptions { #[serde(default = "ten")] pub limit: usize, @@ -1026,7 +1026,7 @@ fn test_is_acceptable_input_on_repeated_characters() { assert!(is_acceptable_input_on_repeated_characters("SuperStrongPassword123*", 3)); assert!(!is_acceptable_input_on_repeated_characters( "SuperStrongaaaPassword123*", - 3 + 3, )); } diff --git a/mm2src/common/custom_futures/repeatable.rs b/mm2src/common/custom_futures/repeatable.rs index 3aaba119c7..ba84f64cb6 100644 --- a/mm2src/common/custom_futures/repeatable.rs +++ b/mm2src/common/custom_futures/repeatable.rs @@ -443,7 +443,7 @@ mod tests { actual, Err(RepeatError::AttemptsExceed { attempts: ACTUAL_ATTEMPTS, - error: "Not ready" + error: "Not ready", }) ); @@ -616,7 +616,7 @@ mod tests { actual, Err(RepeatError::AttemptsExceed { attempts: 1, - error: "Not ready" + error: "Not ready", }) ); } @@ -646,7 +646,7 @@ mod tests { actual, Err(RepeatError::AttemptsExceed { attempts: 1, - error: "Not ready" + error: "Not ready", }) ); diff --git a/mm2src/crypto/src/lib.rs b/mm2src/crypto/src/lib.rs index e2651a54ea..0968ea36a3 100644 --- a/mm2src/crypto/src/lib.rs +++ b/mm2src/crypto/src/lib.rs @@ -12,7 +12,7 @@ pub mod hw_rpc_task; mod key_derivation; pub mod mnemonic; pub mod privkey; -mod shared_db_id; +pub mod shared_db_id; mod slip21; mod standard_hd_path; mod xpub; diff --git a/mm2src/crypto/src/shared_db_id.rs b/mm2src/crypto/src/shared_db_id.rs index 8c78baaaf3..611594e26f 100644 --- a/mm2src/crypto/src/shared_db_id.rs +++ b/mm2src/crypto/src/shared_db_id.rs @@ -11,7 +11,7 @@ const SHARED_DB_MAGIC_SALT: &str = "uVa*6pcnpc9ki+VBX.6_L."; pub type SharedDbId = H160; -#[derive(Display, EnumFromStringify)] +#[derive(Debug, Display, EnumFromStringify)] pub enum SharedDbIdError { #[display(fmt = "Passphrase cannot be an empty string")] EmptyPassphrase, diff --git a/mm2src/mm2_core/src/lib.rs b/mm2src/mm2_core/src/lib.rs index 62e4bef09c..dc27779f15 100644 --- a/mm2src/mm2_core/src/lib.rs +++ b/mm2src/mm2_core/src/lib.rs @@ -4,6 +4,7 @@ use rand::{thread_rng, Rng}; pub mod data_asker; pub mod event_dispatcher; pub mod mm_ctx; +#[cfg(not(target_arch = "wasm32"))] pub mod sql_connection_pool; #[derive(Clone, Copy, Display, PartialEq, Default)] pub enum DbNamespaceId { diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index ec7ceb036a..4925d39a86 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -28,9 +28,8 @@ cfg_wasm32! { } cfg_native! { - use db_common::async_sql_conn::AsyncConnection; use db_common::sqlite::rusqlite::Connection; - use futures::lock::Mutex as AsyncMutex; + use crate::sql_connection_pool::{AsyncSqliteConnPool, SqliteConnPool, DbMigrationWatcher, DbMigrationHandler}; use rustls::ServerName; use mm2_metrics::prometheus; use mm2_metrics::MmMetricsError; @@ -84,7 +83,7 @@ pub struct MmCtx { pub event_stream_configuration: Option, /// True if the MarketMaker instance needs to stop. pub stop: Constructible, - /// Unique context identifier, allowing us to more easily pass the context through the FFI boundaries. + /// Unique context identifier, allowing us to more easily pass the context through the FFI boundaries. /// 0 if the handler ID is allocated yet. pub ffi_handle: Constructible, /// The context belonging to the `ordermatch` mod: `OrdermatchContext`. @@ -119,12 +118,12 @@ pub struct MmCtx { /// The RPC sender forwarding requests to writing part of underlying stream. #[cfg(target_arch = "wasm32")] pub wasm_rpc: Constructible, - /// Deprecated, please use `async_sqlite_connection` for new implementations. + /// Deprecated, please use `async_sqlite_conn_pool` for new implementations. #[cfg(not(target_arch = "wasm32"))] - pub sqlite_connection: Constructible>>, - /// Deprecated, please create `shared_async_sqlite_conn` for new implementations and call db `KOMODEFI-shared.db`. + pub sqlite_conn_pool: Constructible, + /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] - pub shared_sqlite_conn: Constructible>>, + pub async_sqlite_conn_pool: Constructible, pub mm_version: String, pub datetime: String, pub mm_init_ctx: Mutex>>, @@ -139,9 +138,8 @@ pub struct MmCtx { pub db_namespace: DbNamespaceId, /// The context belonging to the `nft` mod: `NftCtx`. pub nft_ctx: Mutex>>, - /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] - pub async_sqlite_connection: Constructible>>, + pub db_migration_watcher: Constructible>, } impl MmCtx { @@ -178,9 +176,9 @@ impl MmCtx { #[cfg(target_arch = "wasm32")] wasm_rpc: Constructible::default(), #[cfg(not(target_arch = "wasm32"))] - sqlite_connection: Constructible::default(), + sqlite_conn_pool: Constructible::default(), #[cfg(not(target_arch = "wasm32"))] - shared_sqlite_conn: Constructible::default(), + async_sqlite_conn_pool: Constructible::default(), mm_version: "".into(), datetime: "".into(), mm_init_ctx: Mutex::new(None), @@ -190,7 +188,7 @@ impl MmCtx { db_namespace: DbNamespaceId::Main, nft_ctx: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] - async_sqlite_connection: Constructible::default(), + db_migration_watcher: Constructible::default(), } } @@ -201,6 +199,10 @@ impl MmCtx { self.rmd160.or(&|| &*DEFAULT) } + pub fn default_db_id(&self) -> String { self.rmd160().to_string() } + + pub fn db_id_or_default(&self, db_id: Option<&str>) -> String { db_id.unwrap_or(&self.default_db_id()).to_owned() } + pub fn shared_db_id(&self) -> &H160 { lazy_static! { static ref DEFAULT: H160 = [0; 20].into(); @@ -208,6 +210,8 @@ impl MmCtx { self.shared_db_id.or(&|| &*DEFAULT) } + pub fn default_shared_db_id(&self) -> String { self.shared_db_id().to_string() } + #[cfg(not(target_arch = "wasm32"))] pub fn rpc_ip_port(&self) -> Result { let port = match self.conf.get("rpcport") { @@ -296,7 +300,7 @@ impl MmCtx { self.db_root().join(wallet_name.to_string() + ".dat") } - /// MM database path. + /// MM database path. /// Defaults to a relative "DB". /// /// Can be changed via the "dbdir" configuration field, for example: @@ -305,7 +309,9 @@ impl MmCtx { /// /// No checks in this method, the paths should be checked in the `fn fix_directories` instead. #[cfg(not(target_arch = "wasm32"))] - pub fn dbdir(&self) -> PathBuf { path_to_dbdir(self.conf["dbdir"].as_str(), self.rmd160()) } + pub fn dbdir(&self, db_id: Option<&str>) -> PathBuf { + path_to_dbdir(self.conf["dbdir"].as_str(), &self.db_id_or_default(db_id)) + } /// MM shared database path. /// Defaults to a relative "DB". @@ -316,7 +322,9 @@ impl MmCtx { /// /// No checks in this method, the paths should be checked in the `fn fix_directories` instead. #[cfg(not(target_arch = "wasm32"))] - pub fn shared_dbdir(&self) -> PathBuf { path_to_dbdir(self.conf["dbdir"].as_str(), self.shared_db_id()) } + pub fn shared_dbdir(&self) -> PathBuf { + path_to_dbdir(self.conf["dbdir"].as_str(), &self.shared_db_id().to_string()) + } pub fn is_watcher(&self) -> bool { self.conf["is_watcher"].as_bool().unwrap_or_default() } @@ -347,53 +355,40 @@ impl MmCtx { pub fn mm_version(&self) -> &str { &self.mm_version } + /// Retrieves an optional shared connection from the pool for the specified database ID. + /// Returns `None` if the connection pool is not initialized. #[cfg(not(target_arch = "wasm32"))] - pub fn init_sqlite_connection(&self) -> Result<(), String> { - let sqlite_file_path = self.dbdir().join("MM2.db"); - log_sqlite_file_open_attempt(&sqlite_file_path); - let connection = try_s!(Connection::open(sqlite_file_path)); - try_s!(self.sqlite_connection.pin(Arc::new(Mutex::new(connection)))); - Ok(()) - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn init_shared_sqlite_conn(&self) -> Result<(), String> { - let sqlite_file_path = self.shared_dbdir().join("MM2-shared.db"); - log_sqlite_file_open_attempt(&sqlite_file_path); - let connection = try_s!(Connection::open(sqlite_file_path)); - try_s!(self.shared_sqlite_conn.pin(Arc::new(Mutex::new(connection)))); - Ok(()) - } - - #[cfg(not(target_arch = "wasm32"))] - pub async fn init_async_sqlite_connection(&self) -> Result<(), String> { - let sqlite_file_path = self.dbdir().join("KOMODEFI.db"); - log_sqlite_file_open_attempt(&sqlite_file_path); - let async_conn = try_s!(AsyncConnection::open(sqlite_file_path).await); - try_s!(self.async_sqlite_connection.pin(Arc::new(AsyncMutex::new(async_conn)))); - Ok(()) + pub fn shared_sqlite_conn_opt(&self) -> Option>> { + self.sqlite_conn_pool + .as_option() + .cloned() + .map(|pool| pool.sqlite_conn_shared(Some(&self.default_db_id()))) } + /// Retrieves an optional connection from the pool for the specified database ID. + /// Returns `None` if the connection pool is not initialized. #[cfg(not(target_arch = "wasm32"))] - pub fn sqlite_conn_opt(&self) -> Option> { - self.sqlite_connection.as_option().map(|conn| conn.lock().unwrap()) + pub fn sqlite_conn_opt(&self, db_id: Option<&str>) -> Option>> { + self.sqlite_conn_pool + .as_option() + .cloned() + .map(|pool| pool.sqlite_conn(db_id)) } #[cfg(not(target_arch = "wasm32"))] - pub fn sqlite_connection(&self) -> MutexGuard { - self.sqlite_connection + pub fn run_sql_query(&self, db_id: Option<&str>, f: F) -> R + where + F: FnOnce(MutexGuard) -> R + Send + 'static, + R: Send + 'static, + { + self.sqlite_conn_pool .or(&|| panic!("sqlite_connection is not initialized")) - .lock() - .unwrap() + .clone() + .run_sql_query(db_id, f) } #[cfg(not(target_arch = "wasm32"))] - pub fn shared_sqlite_conn(&self) -> MutexGuard { - self.shared_sqlite_conn - .or(&|| panic!("shared_sqlite_conn is not initialized")) - .lock() - .unwrap() - } + pub fn init_db_migration_watcher(&self) -> Result { DbMigrationWatcher::init(self) } } impl Default for MmCtx { @@ -436,11 +431,7 @@ fn path_to_db_root(db_root: Option<&str>) -> PathBuf { /// This function can be used later by an FFI function to open a GUI storage. #[cfg(not(target_arch = "wasm32"))] -pub fn path_to_dbdir(db_root: Option<&str>, db_id: &H160) -> PathBuf { - let path = path_to_db_root(db_root); - - path.join(hex::encode(db_id.as_slice())) -} +pub fn path_to_dbdir(db_root: Option<&str>, db_id: &str) -> PathBuf { path_to_db_root(db_root).join(db_id) } // We don't want to send `MmCtx` across threads, it will only obstruct the normal use case // (and might result in undefined behaviour if there's a C struct or value in the context that is aliased from the various MM threads). @@ -455,6 +446,7 @@ pub struct MmArc(pub SharedRc); // after we finish the initial port and replace the C values with the corresponding Rust alternatives. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for MmArc {} + unsafe impl Sync for MmArc {} impl Clone for MmArc { @@ -473,6 +465,7 @@ pub struct MmWeak(WeakRc); // Same as `MmArc`. #[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for MmWeak {} + unsafe impl Sync for MmWeak {} impl MmWeak { @@ -568,7 +561,7 @@ impl MmArc { } } - /// Tries getting access to the MM context. + /// Tries getting access to the MM context. /// Fails if an invalid MM context handler is passed (no such context or dropped context). #[track_caller] pub fn from_ffi_handle(ffi_handle: u32) -> Result { @@ -775,7 +768,7 @@ impl MmCtxBuilder { } #[cfg(not(target_arch = "wasm32"))] -fn log_sqlite_file_open_attempt(sqlite_file_path: &Path) { +pub fn log_sqlite_file_open_attempt(sqlite_file_path: &Path) { match sqlite_file_path.canonicalize() { Ok(absolute_path) => { log::debug!("Trying to open SQLite database file {}", absolute_path.display()); diff --git a/mm2src/mm2_core/src/sql_connection_pool.rs b/mm2src/mm2_core/src/sql_connection_pool.rs new file mode 100644 index 0000000000..bf67b22d7a --- /dev/null +++ b/mm2src/mm2_core/src/sql_connection_pool.rs @@ -0,0 +1,331 @@ +use crate::mm_ctx::MmCtx; +use crate::mm_ctx::{log_sqlite_file_open_attempt, path_to_dbdir, MmArc}; +use async_std::sync::RwLock as AsyncRwLock; +use common::log::error; +use common::log::info; +use db_common::async_sql_conn::AsyncConnection; +use db_common::sqlite::rusqlite::Connection; +use futures::channel::mpsc::{channel, Receiver, Sender}; +use futures::lock::Mutex as AsyncMutex; +use futures::SinkExt; +use gstuff::try_s; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::{Arc, Mutex, MutexGuard, RwLock}; + +pub const ASYNC_SQLITE_DB_ID: &str = "KOMODEFI.db"; +const SYNC_SQLITE_DB_ID: &str = "MM2.db"; +const SQLITE_SHARED_DB_ID: &str = "MM2-shared.db"; + +/// Represents the kind of database connection ID: either shared or single-db. +enum DbIdConnKind { + Shared, + Single, +} + +/// A pool for managing SQLite connections, where each connection is keyed by a unique string identifier. +#[derive(Clone)] +pub struct SqliteConnPool { + connections: Arc>>>>, + // default db_id + default_db_id: String, + // default shared_db_id + shared_db_id: String, + db_root: Option, +} + +impl SqliteConnPool { + /// Initializes a single-user database connection. + pub fn init(ctx: &MmArc, db_id: Option<&str>) -> Result<(), String> { + Self::init_impl(ctx, db_id, DbIdConnKind::Single) + } + + /// Initializes a shared database connection. + pub fn init_shared(ctx: &MmArc) -> Result<(), String> { Self::init_impl(ctx, None, DbIdConnKind::Shared) } + + /// Internal implementation to initialize a database connection. + fn init_impl(ctx: &MmArc, db_id: Option<&str>, kind: DbIdConnKind) -> Result<(), String> { + let db_id = Self::db_id_from_ctx(ctx, db_id, &kind); + let sqlite_file_path = match kind { + DbIdConnKind::Shared => ctx.shared_dbdir().join(SQLITE_SHARED_DB_ID), + DbIdConnKind::Single => ctx.dbdir(Some(&db_id)).join(SYNC_SQLITE_DB_ID), + }; + + // Connection pool is already initialized, insert new connection. + if let Some(pool) = ctx.sqlite_conn_pool.as_option() { + { + let conns = pool.connections.read().unwrap(); + if conns.get(&db_id).is_some() { + return Ok(()); + } + } + + let conn = Self::open_connection(sqlite_file_path); + let mut pool = pool.connections.write().unwrap(); + pool.insert(db_id, conn); + + return Ok(()); + } + + // Connection pool is not already initialized, create new connection pool. + let conn = Self::open_connection(sqlite_file_path); + let connections = Arc::new(RwLock::new(HashMap::from([(db_id, conn)]))); + let db_root = ctx.conf["dbdir"].as_str(); + try_s!(ctx.sqlite_conn_pool.pin(Self { + connections, + default_db_id: ctx.default_db_id(), + shared_db_id: ctx.default_shared_db_id(), + db_root: db_root.map(|d| d.to_owned()) + })); + + Ok(()) + } + + /// Test method for initializing a single-user database connection in-memory. + pub fn init_test(ctx: &MmArc) -> Result<(), String> { Self::init_impl_test(ctx, None, DbIdConnKind::Single) } + + /// Test method for initializing a shared database connection in-memory. + pub fn init_shared_test(ctx: &MmArc) -> Result<(), String> { Self::init_impl_test(ctx, None, DbIdConnKind::Shared) } + + /// Internal test implementation to initialize a database connection in-memory. + fn init_impl_test(ctx: &MmArc, db_id: Option<&str>, kind: DbIdConnKind) -> Result<(), String> { + let db_id = Self::db_id_from_ctx(ctx, db_id, &kind); + if let Some(pool) = ctx.sqlite_conn_pool.as_option() { + let connection = Arc::new(Mutex::new(Connection::open_in_memory().unwrap())); + let mut pool = pool.connections.write().unwrap(); + pool.insert(db_id, connection); + + return Ok(()); + } + + let connection = Arc::new(Mutex::new(Connection::open_in_memory().unwrap())); + let connections = Arc::new(RwLock::new(HashMap::from([(db_id, connection)]))); + let db_root = ctx.conf["dbdir"].as_str(); + try_s!(ctx.sqlite_conn_pool.pin(Self { + connections, + default_db_id: ctx.default_db_id(), + shared_db_id: ctx.default_shared_db_id(), + db_root: db_root.map(|d| d.to_owned()) + })); + + Ok(()) + } + + /// Retrieves a single-user connection from the pool. + pub fn sqlite_conn(&self, db_id: Option<&str>) -> Arc> { + self.sqlite_conn_impl(db_id, DbIdConnKind::Single) + } + + /// Retrieves a shared connection from the pool. + pub fn sqlite_conn_shared(&self, db_id: Option<&str>) -> Arc> { + self.sqlite_conn_impl(db_id, DbIdConnKind::Shared) + } + + /// Internal implementation to retrieve or create a connection. + fn sqlite_conn_impl(&self, db_id: Option<&str>, kind: DbIdConnKind) -> Arc> { + let db_id = self.db_id(db_id, &kind); + { + let connections = self.connections.read().unwrap(); + if let Some(connection) = connections.get(&db_id) { + return Arc::clone(connection); + } + } + + let mut connections = self.connections.write().unwrap(); + let sqlite_file_path = self.sqlite_file_path(&db_id, &kind); + let connection = Self::open_connection(sqlite_file_path); + connections.insert(db_id, Arc::clone(&connection)); + + connection + } + + /// Run a sql query for db. + pub fn run_sql_query(&self, db_id: Option<&str>, f: F) -> R + where + F: FnOnce(MutexGuard) -> R + Send + 'static, + R: Send + 'static, + { + f(self.sqlite_conn_impl(db_id, DbIdConnKind::Single).lock().unwrap()) + } + + pub fn add_test_db(&self, db_id: String) { + let mut connections = self.connections.write().unwrap(); + connections.insert(db_id, Arc::new(Mutex::new(Connection::open_in_memory().unwrap()))); + } + + /// Opens a database connection based on the database ID and connection kind. + fn open_connection(sqlite_file_path: PathBuf) -> Arc> { + log_sqlite_file_open_attempt(&sqlite_file_path); + Arc::new(Mutex::new( + Connection::open(sqlite_file_path).expect("failed to open db"), + )) + } + + fn db_dir(&self, db_id: &str) -> PathBuf { path_to_dbdir(self.db_root.as_deref(), db_id) } + fn db_id(&self, db_id: Option<&str>, kind: &DbIdConnKind) -> String { + match kind { + DbIdConnKind::Shared => db_id + .map(|e| e.to_owned()) + .unwrap_or_else(|| self.shared_db_id.to_owned()), + DbIdConnKind::Single => db_id + .map(|e| e.to_owned()) + .unwrap_or_else(|| self.default_db_id.to_owned()), + } + } + fn db_id_from_ctx(ctx: &MmArc, db_id: Option<&str>, kind: &DbIdConnKind) -> String { + match kind { + DbIdConnKind::Shared => db_id + .map(|e| e.to_owned()) + .unwrap_or_else(|| ctx.default_shared_db_id()), + DbIdConnKind::Single => db_id.map(|e| e.to_owned()).unwrap_or_else(|| ctx.default_db_id()), + } + } + fn sqlite_file_path(&self, db_id: &str, kind: &DbIdConnKind) -> PathBuf { + self.db_dir(db_id).join(match kind { + DbIdConnKind::Shared => SQLITE_SHARED_DB_ID, + DbIdConnKind::Single => SYNC_SQLITE_DB_ID, + }) + } +} + +/// A pool for managing async SQLite connections, where each connection is keyed by a unique string identifier. +#[derive(Clone)] +pub struct AsyncSqliteConnPool { + connections: Arc>>>>, + sqlite_file_path: PathBuf, + default_db_id: String, +} + +impl AsyncSqliteConnPool { + /// Initialize a database connection. + pub async fn init(ctx: &MmArc, db_id: Option<&str>) -> Result<(), String> { + let db_id = db_id.map(|e| e.to_owned()).unwrap_or_else(|| ctx.default_db_id()); + + if let Some(pool) = ctx.async_sqlite_conn_pool.as_option() { + { + let conns = pool.connections.read().await; + if conns.get(&db_id).is_some() { + return Ok(()); + } + } + + let conn = Self::open_connection(&pool.sqlite_file_path).await; + let mut pool = pool.connections.write().await; + pool.insert(db_id, conn); + + return Ok(()); + } + + let sqlite_file_path = ctx.dbdir(Some(&db_id)).join(ASYNC_SQLITE_DB_ID); + let conn = Self::open_connection(&sqlite_file_path).await; + let connections = Arc::new(AsyncRwLock::new(HashMap::from([(db_id, conn)]))); + try_s!(ctx.async_sqlite_conn_pool.pin(Self { + connections, + sqlite_file_path, + default_db_id: ctx.default_db_id(), + })); + + Ok(()) + } + + /// Initialize a database connection. + pub async fn init_test(ctx: &MmArc, db_id: Option<&str>) -> Result<(), String> { + let db_id = db_id.map(|e| e.to_owned()).unwrap_or_else(|| ctx.default_db_id()); + + if let Some(pool) = ctx.async_sqlite_conn_pool.as_option() { + { + let conns = pool.connections.read().await; + if conns.get(&db_id).is_some() { + return Ok(()); + } + } + + let mut pool = pool.connections.write().await; + let conn = Arc::new(AsyncMutex::new(AsyncConnection::open_in_memory().await.unwrap())); + pool.insert(db_id, conn); + + return Ok(()); + } + + let conn = Arc::new(AsyncMutex::new(AsyncConnection::open_in_memory().await.unwrap())); + // extra connection to test accessing different db test + let conn2 = Arc::new(AsyncMutex::new(AsyncConnection::open_in_memory().await.unwrap())); + let connections = HashMap::from([(db_id, conn), ("TEST_DB_ID".to_owned(), conn2)]); + let connections = Arc::new(AsyncRwLock::new(connections)); + try_s!(ctx.async_sqlite_conn_pool.pin(Self { + connections, + sqlite_file_path: PathBuf::new(), + default_db_id: ctx.default_db_id(), + })); + Ok(()) + } + + /// Retrieve or create a connection. + pub async fn async_sqlite_conn(&self, db_id: Option<&str>) -> Arc> { + let db_id = db_id.unwrap_or(&self.default_db_id); + + { + let connections = self.connections.read().await; + if let Some(connection) = connections.get(db_id) { + return Arc::clone(connection); + }; + } + + let mut connections = self.connections.write().await; + let connection = Self::open_connection(&self.sqlite_file_path).await; + connections.insert(db_id.to_owned(), Arc::clone(&connection)); + connection + } + + pub async fn close_connections(&self) { + let mut connections = self.connections.write().await; + for (id, connection) in connections.iter_mut() { + let mut connection = connection.lock().await; + if let Err(e) = connection.close().await { + error!("Error stopping AsyncConnection: {}, connection_id=({id})", e); + } + } + } + + async fn open_connection(sqlite_file_path: &PathBuf) -> Arc> { + log_sqlite_file_open_attempt(sqlite_file_path); + + Arc::new(AsyncMutex::new( + AsyncConnection::open(sqlite_file_path) + .await + .expect("failed to open db"), + )) + } +} + +pub type DbMigrationHandler = Receiver; +pub type DbMigrationSender = Sender; + +pub struct DbMigrationWatcher { + sender: DbMigrationSender, +} + +impl DbMigrationWatcher { + pub fn init(ctx: &MmCtx) -> Result { + let (sender, receiver) = channel(1); + + let selfi = Arc::new(Self { sender }); + try_s!(ctx.db_migration_watcher.pin(selfi)); + + Ok(receiver) + } + + pub fn get_sender(&self) -> DbMigrationSender { self.sender.clone() } +} + +pub async fn run_db_migration_for_new_pubkey(ctx: &MmArc, db_id: String) -> Result<(), String> { + info!("Public key hash: {db_id:?}"); + let mut db_migration_sender = ctx + .db_migration_watcher + .as_option() + .expect("Db migration watcher isn't intialized yet!") + .get_sender(); + db_migration_sender.send(db_id).await.map_err(|err| err.to_string())?; + + Ok(()) +} diff --git a/mm2src/mm2_db/Cargo.toml b/mm2src/mm2_db/Cargo.toml index 5f5374acad..b63752839b 100644 --- a/mm2src/mm2_db/Cargo.toml +++ b/mm2src/mm2_db/Cargo.toml @@ -24,6 +24,7 @@ primitives = { path = "../mm2_bitcoin/primitives" } rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } serde = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } +tokio = { version = "1.20", features = ["default", "sync"] } wasm-bindgen = "0.2.86" wasm-bindgen-futures = { version = "0.4.1" } wasm-bindgen-test = { version = "0.3.2" } diff --git a/mm2src/mm2_db/src/indexed_db/db_lock.rs b/mm2src/mm2_db/src/indexed_db/db_lock.rs index 1ca10262f9..a99886bc7e 100644 --- a/mm2src/mm2_db/src/indexed_db/db_lock.rs +++ b/mm2src/mm2_db/src/indexed_db/db_lock.rs @@ -1,20 +1,28 @@ use super::{DbIdentifier, DbInstance, InitDbResult}; -use futures::lock::{MappedMutexGuard as AsyncMappedMutexGuard, Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use mm2_core::{mm_ctx::MmArc, DbNamespaceId}; -use primitives::hash::H160; +use std::collections::hash_map::Entry; +use std::collections::HashMap; use std::sync::{Arc, Weak}; +use tokio::sync::{Mutex as AsyncMutex, OwnedMappedMutexGuard, OwnedMutexGuard, RwLock}; + +const GLOBAL_DB_ID: &str = "KOMODEFI"; /// The mapped mutex guard. /// This implements `Deref`. -pub type DbLocked<'a, Db> = AsyncMappedMutexGuard<'a, Option, Db>; +pub type DbLocked = OwnedMappedMutexGuard, Db>; pub type SharedDb = Arc>; pub type WeakDb = Weak>; +type ConnectionsDb = Arc>>>>>; +#[allow(clippy::type_complexity)] pub struct ConstructibleDb { /// It's better to use something like [`Constructible`], but it doesn't provide a method to get the inner value by the mutable reference. - mutex: AsyncMutex>, + locks: ConnectionsDb, db_namespace: DbNamespaceId, - wallet_rmd160: Option, + // Default mm2 db_id derive from passphrase rmd160 + db_id: String, + // Default mm2 shared_db_id derive from passphrase + shared_db_id: String, } impl ConstructibleDb { @@ -22,11 +30,17 @@ impl ConstructibleDb { /// Creates a new uninitialized `Db` instance from other Iguana and/or HD accounts. /// This can be initialized later using [`ConstructibleDb::get_or_initialize`]. - pub fn new(ctx: &MmArc) -> Self { + pub fn new(ctx: &MmArc, db_id: Option<&str>) -> Self { + let default_db_id = ctx.rmd160().to_string(); + let db_id = db_id.unwrap_or(&default_db_id).to_string(); + let shared_db_id = ctx.default_shared_db_id(); + let conns = HashMap::from([(db_id.to_owned(), Default::default())]); + ConstructibleDb { - mutex: AsyncMutex::new(None), + locks: Arc::new(RwLock::new(conns)), db_namespace: ctx.db_namespace, - wallet_rmd160: Some(*ctx.rmd160()), + db_id, + shared_db_id, } } @@ -34,45 +48,90 @@ impl ConstructibleDb { /// derived from the same passphrase. /// This can be initialized later using [`ConstructibleDb::get_or_initialize`]. pub fn new_shared_db(ctx: &MmArc) -> Self { + let db_id = ctx.default_db_id(); + let shared_db_id = ctx.default_shared_db_id(); + let conns = HashMap::from([(shared_db_id.clone(), Default::default())]); + ConstructibleDb { - mutex: AsyncMutex::new(None), + locks: Arc::new(RwLock::new(conns)), db_namespace: ctx.db_namespace, - wallet_rmd160: Some(*ctx.shared_db_id()), + db_id, + shared_db_id, } } /// Creates a new uninitialized `Db` instance shared between all wallets/seed. /// This can be initialized later using [`ConstructibleDb::get_or_initialize`]. pub fn new_global_db(ctx: &MmArc) -> Self { + let db_id = ctx.default_db_id(); + let shared_db_id = ctx.default_shared_db_id(); ConstructibleDb { - mutex: AsyncMutex::new(None), + locks: Arc::new(RwLock::new(HashMap::default())), db_namespace: ctx.db_namespace, - wallet_rmd160: None, + db_id, + shared_db_id, } } + // handle to get or initialize db + pub async fn get_or_initialize(&self, db_id: Option<&str>) -> InitDbResult> { + self.get_or_initialize_impl(db_id, false).await + } + + // handle to get or initialize shared db + pub async fn get_or_initialize_shared(&self) -> InitDbResult> { + self.get_or_initialize_impl(Some(&self.shared_db_id), true).await + } + + // handle to get or initialize global db + pub async fn get_or_intiailize_global(&self) -> InitDbResult> { + self.get_or_initialize_impl(Some(GLOBAL_DB_ID), false).await + } + /// Locks the given mutex and checks if the inner database is initialized already or not, /// initializes it if it's required, and returns the locked instance. - pub async fn get_or_initialize(&self) -> InitDbResult> { - let mut locked_db = self.mutex.lock().await; - // Db is initialized already - if locked_db.is_some() { - return Ok(unwrap_db_instance(locked_db)); - } + async fn get_or_initialize_impl(&self, db_id: Option<&str>, is_shared: bool) -> InitDbResult> { + let db_id = db_id + .unwrap_or(if is_shared { &self.shared_db_id } else { &self.db_id }) + .to_owned(); + + let mut connections = self.locks.write().await; + match connections.entry(db_id.clone()) { + Entry::Occupied(conn) => { + let mut locked_db = conn.get().clone().lock_owned().await; + drop(connections); + // check and return found connection if already initialized. + if locked_db.is_some() { + return Ok(unwrap_db_instance(locked_db)); + }; + // existing connection found but not initialized, hence, we initialize and return this connection. + let db = Db::init(DbIdentifier::new::(self.db_namespace, Some(db_id.clone()))).await?; + *locked_db = Some(db); + Ok(unwrap_db_instance(locked_db)) + }, + Entry::Vacant(entry) => { + // No connection found so we create a new connection with immediate initialization + let db = { + let db = Db::init(DbIdentifier::new::(self.db_namespace, Some(db_id.clone()))).await?; + let db = Arc::new(AsyncMutex::new(Some(db))); + entry.insert(db.clone()); + drop(connections); - let db_id = DbIdentifier::new::(self.db_namespace, self.wallet_rmd160); + db + }; - let db = Db::init(db_id).await?; - *locked_db = Some(db); - Ok(unwrap_db_instance(locked_db)) + let locked_db = db.lock_owned().await; + Ok(unwrap_db_instance(locked_db)) + }, + } } } /// # Panics /// /// This function will `panic!()` if the inner value of the `guard` is `None`. -fn unwrap_db_instance(guard: AsyncMutexGuard<'_, Option>) -> DbLocked<'_, Db> { - AsyncMutexGuard::map(guard, |wrapped_db| { +fn unwrap_db_instance(guard: OwnedMutexGuard>) -> DbLocked { + OwnedMutexGuard::map(guard, |wrapped_db| { wrapped_db .as_mut() .expect("The locked 'Option' must contain a value") diff --git a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs index 3f0c1c7b8c..133f435bde 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_cursor.rs @@ -387,7 +387,7 @@ mod tests { }) .collect(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -448,7 +448,7 @@ mod tests { swap_item!("uuid6", "QRC20", "RICK", 2, 2, 721), ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -500,7 +500,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -557,7 +557,7 @@ mod tests { swap_item!("uuid12", "tBTC", "RICK", 92, 6, 721), ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -632,7 +632,7 @@ mod tests { swap_item!("uuid25", "DOGE", "tBTC", 9, 10, 711), ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -691,7 +691,7 @@ mod tests { TimestampTable::new(u128::MAX, 2, u64::MAX as u128 + 1), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -742,7 +742,7 @@ mod tests { swap_item!("uuid4", "RICK", "MORTY", 8, 6, 92), ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -788,7 +788,7 @@ mod tests { const DB_NAME: &str = "TEST_REV_ITER_WITHOUT_CONSTRAINTS"; const DB_VERSION: u32 = 1; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -825,7 +825,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -876,7 +876,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -929,7 +929,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -977,7 +977,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1020,7 +1020,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1064,7 +1064,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1110,7 +1110,7 @@ mod tests { swap_item!("uuid6", "KMD", "MORTY", 12, 3124, 214), // + ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1157,7 +1157,7 @@ mod tests { swap_item!("uuid3", "RICK", "FTM", 12, 3124, 214), ]; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() diff --git a/mm2src/mm2_db/src/indexed_db/indexed_db.rs b/mm2src/mm2_db/src/indexed_db/indexed_db.rs index 1595662ad8..21672fbbf8 100644 --- a/mm2src/mm2_db/src/indexed_db/indexed_db.rs +++ b/mm2src/mm2_db/src/indexed_db/indexed_db.rs @@ -81,39 +81,36 @@ pub trait DbInstance: Sized { } #[derive(Clone, Display)] -#[display(fmt = "{}::{}::{}", namespace_id, "self.display_rmd160()", db_name)] +#[display(fmt = "{}::{}::{}", namespace_id, "self.display_db_id()", db_name)] pub struct DbIdentifier { namespace_id: DbNamespaceId, - /// The `RIPEMD160(SHA256(x))` where x is secp256k1 pubkey derived from passphrase. - /// This value is used to distinguish different databases corresponding to user's different seed phrases. - wallet_rmd160: Option, + /// The db_id derived from passphrase or coin. + /// This value is used to distinguish different databases corresponding to user's coin activation db_id or seedphrase. + db_id: Option, db_name: &'static str, } impl DbIdentifier { pub fn db_name(&self) -> &'static str { self.db_name } - pub fn new(namespace_id: DbNamespaceId, wallet_rmd160: Option) -> DbIdentifier { + pub fn new(namespace_id: DbNamespaceId, db_id: Option) -> DbIdentifier { DbIdentifier { namespace_id, - wallet_rmd160, + db_id, db_name: Db::DB_NAME, } } - pub fn for_test(db_name: &'static str) -> DbIdentifier { + pub fn for_test(db_name: &'static str, db_id: Option) -> DbIdentifier { + let db_id = Some(db_id.unwrap_or_else(|| hex::encode(H160::default().as_slice()))); DbIdentifier { namespace_id: DbNamespaceId::for_test(), - wallet_rmd160: Some(H160::default()), + db_id, db_name, } } - pub fn display_rmd160(&self) -> String { - self.wallet_rmd160 - .map(hex::encode) - .unwrap_or_else(|| "KOMODEFI".to_string()) - } + pub fn display_db_id(&self) -> String { self.db_id.clone().unwrap_or_else(|| "KOMODEFI".to_string()) } } pub struct IndexedDbBuilder { @@ -123,9 +120,9 @@ pub struct IndexedDbBuilder { } impl IndexedDbBuilder { - pub fn new(db_id: DbIdentifier) -> IndexedDbBuilder { + pub fn new(db_ident: DbIdentifier) -> IndexedDbBuilder { IndexedDbBuilder { - db_name: db_id.to_string(), + db_name: db_ident.to_string(), db_version: 1, tables: HashMap::new(), } @@ -982,7 +979,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1052,7 +1049,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1112,7 +1109,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1165,7 +1162,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1242,7 +1239,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1284,7 +1281,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1376,7 +1373,7 @@ mod tests { register_wasm_log(); - let db_identifier = DbIdentifier::for_test(DB_NAME); + let db_identifier = DbIdentifier::for_test(DB_NAME, None); init_and_check(db_identifier.clone(), 1, Some((0, 1))).await.unwrap(); init_and_check(db_identifier.clone(), 2, Some((1, 2))).await.unwrap(); @@ -1390,7 +1387,7 @@ mod tests { const DB_VERSION: u32 = 1; register_wasm_log(); - let db_identifier = DbIdentifier::for_test(DB_NAME); + let db_identifier = DbIdentifier::for_test(DB_NAME, None); let _db = IndexedDbBuilder::new(db_identifier.clone()) .with_version(DB_VERSION) @@ -1418,7 +1415,7 @@ mod tests { const DB_VERSION: u32 = 1; register_wasm_log(); - let db_identifier = DbIdentifier::for_test(DB_NAME); + let db_identifier = DbIdentifier::for_test(DB_NAME, None); let db = IndexedDbBuilder::new(db_identifier.clone()) .with_version(DB_VERSION) @@ -1479,7 +1476,7 @@ mod tests { some_data: "Some data 2".to_owned(), }; - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() @@ -1528,7 +1525,7 @@ mod tests { register_wasm_log(); - let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME)) + let db = IndexedDbBuilder::new(DbIdentifier::for_test(DB_NAME, None)) .with_version(DB_VERSION) .with_table::() .build() diff --git a/mm2src/mm2_gui_storage/src/account/mod.rs b/mm2src/mm2_gui_storage/src/account/mod.rs index 4350b0c939..70dd845c07 100644 --- a/mm2src/mm2_gui_storage/src/account/mod.rs +++ b/mm2src/mm2_gui_storage/src/account/mod.rs @@ -8,8 +8,11 @@ use std::collections::BTreeSet; pub(crate) mod storage; +#[cfg(not(target_arch = "wasm32"))] pub const MAX_ACCOUNT_NAME_LENGTH: usize = 255; +#[cfg(not(target_arch = "wasm32"))] pub const MAX_ACCOUNT_DESCRIPTION_LENGTH: usize = 600; +#[cfg(not(target_arch = "wasm32"))] pub const MAX_TICKER_LENGTH: usize = 255; pub(crate) type HwPubkey = H160Json; diff --git a/mm2src/mm2_gui_storage/src/account/storage/account_storage_tests.rs b/mm2src/mm2_gui_storage/src/account/storage/account_storage_tests.rs index b44e83190a..4612a83314 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/account_storage_tests.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/account_storage_tests.rs @@ -70,7 +70,7 @@ async fn fill_storage(storage: &dyn AccountStorage, accounts: Vec) async fn test_init_collection_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); // repetitive init must not fail @@ -79,7 +79,7 @@ async fn test_init_collection_impl() { async fn test_upload_account_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); for account in accounts_for_test() { @@ -99,7 +99,7 @@ async fn test_upload_account_impl() { async fn test_enable_account_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let error = storage @@ -151,7 +151,7 @@ async fn test_enable_account_impl() { async fn test_set_name_desc_balance_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let accounts = accounts_for_test(); @@ -195,7 +195,7 @@ async fn test_set_name_desc_balance_impl() { async fn test_activate_deactivate_coins_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let accounts = accounts_for_test(); @@ -287,7 +287,7 @@ async fn test_activate_deactivate_coins_impl() { async fn test_load_enabled_account_with_coins_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let accounts = accounts_for_test(); @@ -357,7 +357,7 @@ async fn test_load_enabled_account_with_coins_impl() { async fn test_load_accounts_with_enabled_flag_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let accounts = accounts_for_test(); @@ -406,7 +406,7 @@ async fn test_load_accounts_with_enabled_flag_impl() { async fn test_delete_account_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let accounts = accounts_for_test(); @@ -464,7 +464,7 @@ async fn test_delete_account_impl() { async fn test_delete_account_clears_coins_impl() { let ctx = mm_ctx_with_custom_db(); - let storage = AccountStorageBuilder::new(&ctx).build().unwrap(); + let storage = AccountStorageBuilder::new(&ctx, None).build().unwrap(); storage.init().await.unwrap(); let accounts = accounts_for_test(); diff --git a/mm2src/mm2_gui_storage/src/account/storage/mod.rs b/mm2src/mm2_gui_storage/src/account/storage/mod.rs index a2a186d490..b288a9a4b0 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/mod.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/mod.rs @@ -19,6 +19,7 @@ const DEFAULT_DEVICE_PUB: HwPubkey = HwPubkey::const_default(); pub(crate) type AccountStorageBoxed = Box; pub type AccountStorageResult = MmResult; +#[allow(unused)] #[derive(Debug, Display)] pub enum AccountStorageError { #[display(fmt = "No such account {:?}", _0)] @@ -123,19 +124,22 @@ impl EnabledAccountId { /// The implementation depends on the target architecture. pub(crate) struct AccountStorageBuilder<'a> { ctx: &'a MmArc, + db_id: Option<&'a str>, } +#[allow(unused)] impl<'a> AccountStorageBuilder<'a> { - pub fn new(ctx: &'a MmArc) -> Self { AccountStorageBuilder { ctx } } + pub fn new(ctx: &'a MmArc, db_id: Option<&'a str>) -> Self { AccountStorageBuilder { ctx, db_id } } #[cfg(not(target_arch = "wasm32"))] pub fn build(self) -> AccountStorageResult { - sqlite_storage::SqliteAccountStorage::new(self.ctx).map(|storage| -> AccountStorageBoxed { Box::new(storage) }) + sqlite_storage::SqliteAccountStorage::new(self.ctx, self.db_id) + .map(|storage| -> AccountStorageBoxed { Box::new(storage) }) } #[cfg(target_arch = "wasm32")] pub fn build(self) -> AccountStorageResult { - Ok(Box::new(wasm_storage::WasmAccountStorage::new(self.ctx))) + Ok(Box::new(wasm_storage::WasmAccountStorage::new(self.ctx, self.db_id))) } } diff --git a/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs b/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs index 916854de63..b6618cc896 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/sqlite_storage.rs @@ -14,7 +14,7 @@ use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; -use std::sync::{Arc, MutexGuard}; +use std::sync::MutexGuard; const DEVICE_PUBKEY_MAX_LENGTH: usize = 20; const BALANCE_MAX_LENGTH: usize = 255; @@ -115,14 +115,14 @@ pub(crate) struct SqliteAccountStorage { } impl SqliteAccountStorage { - pub(crate) fn new(ctx: &MmArc) -> AccountStorageResult { - let shared = ctx - .sqlite_connection - .as_option() - .or_mm_err(|| AccountStorageError::Internal("'MmCtx::sqlite_connection' is not initialized".to_owned()))?; - Ok(SqliteAccountStorage { - conn: Arc::clone(shared), - }) + pub(crate) fn new(ctx: &MmArc, db_id: Option<&str>) -> AccountStorageResult { + let conn = ctx.sqlite_conn_opt(db_id).ok_or_else(|| { + MmError::new(AccountStorageError::Internal( + "'MmCtx::sqlite_connection' is not found or initialized".to_owned(), + )) + })?; + + Ok(SqliteAccountStorage { conn }) } fn lock_conn_mutex(&self) -> AccountStorageResult> { diff --git a/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs b/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs index 1f05370c29..82036b71e0 100644 --- a/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs +++ b/mm2src/mm2_gui_storage/src/account/storage/wasm_storage.rs @@ -13,7 +13,7 @@ use std::collections::{BTreeMap, BTreeSet}; const DB_VERSION: u32 = 1; -type AccountDbLocked<'a> = DbLocked<'a, AccountDb>; +type AccountDbLocked = DbLocked; impl From for AccountStorageError { fn from(e: DbTransactionError) -> Self { @@ -61,15 +61,15 @@ pub(crate) struct WasmAccountStorage { } impl WasmAccountStorage { - pub fn new(ctx: &MmArc) -> Self { + pub fn new(ctx: &MmArc, _db_id: Option<&str>) -> Self { WasmAccountStorage { account_db: ConstructibleDb::new_shared_db(ctx).into_shared(), } } - async fn lock_db_mutex(&self) -> AccountStorageResult> { + async fn lock_db_mutex(&self) -> AccountStorageResult { self.account_db - .get_or_initialize() + .get_or_initialize_shared() .await .mm_err(AccountStorageError::from) } diff --git a/mm2src/mm2_gui_storage/src/context.rs b/mm2src/mm2_gui_storage/src/context.rs index 4a15f48541..55889101ff 100644 --- a/mm2src/mm2_gui_storage/src/context.rs +++ b/mm2src/mm2_gui_storage/src/context.rs @@ -2,16 +2,22 @@ use crate::account::storage::{AccountStorage, AccountStorageBoxed, AccountStorag use mm2_core::mm_ctx::{from_ctx, MmArc}; use std::sync::Arc; +#[allow(unused)] pub(crate) struct AccountContext { storage: AccountStorageBoxed, + db_id: Option, } +#[allow(unused)] impl AccountContext { /// Obtains a reference to this crate context, creating it if necessary. - pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> { + pub(crate) fn from_ctx(ctx: &MmArc, db_id: Option<&str>) -> Result, String> { from_ctx(&ctx.account_ctx, move || { Ok(AccountContext { - storage: AccountStorageBuilder::new(ctx).build().map_err(|e| e.to_string())?, + storage: AccountStorageBuilder::new(ctx, db_id) + .build() + .map_err(|e| e.to_string())?, + db_id: db_id.map(|e| e.to_string()), }) }) } diff --git a/mm2src/mm2_gui_storage/src/lib.rs b/mm2src/mm2_gui_storage/src/lib.rs index 16535b3782..a158fe0dd6 100644 --- a/mm2src/mm2_gui_storage/src/lib.rs +++ b/mm2src/mm2_gui_storage/src/lib.rs @@ -1,3 +1,2 @@ pub(crate) mod account; pub(crate) mod context; -pub mod rpc_commands; diff --git a/mm2src/mm2_gui_storage/src/rpc_commands.rs b/mm2src/mm2_gui_storage/src/rpc_commands.rs index 2c3363443c..2d2d85263b 100644 --- a/mm2src/mm2_gui_storage/src/rpc_commands.rs +++ b/mm2src/mm2_gui_storage/src/rpc_commands.rs @@ -172,7 +172,7 @@ pub struct SetBalanceRequest { /// /// This RPC affects the storage **only**. It doesn't affect MarketMaker. pub async fn enable_account(ctx: MmArc, req: EnableAccountRequest) -> MmResult { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; let account_id = match req.policy { EnableAccountPolicy::Existing(account_id) => account_id, EnableAccountPolicy::New(new_account) => { @@ -197,7 +197,7 @@ pub async fn enable_account(ctx: MmArc, req: EnableAccountRequest) -> MmResult MmResult { validate_new_account(&req.account)?; - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx .storage() .await? @@ -213,7 +213,7 @@ pub async fn add_account(ctx: MmArc, req: AddAccountRequest) -> MmResult MmResult { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx.storage().await?.delete_account(req.account_id).await?; Ok(SuccessResponse::new()) } @@ -228,7 +228,7 @@ pub async fn get_accounts( ctx: MmArc, _req: GetAccountsRequest, ) -> MmResult, AccountRpcError> { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; let accounts = account_ctx .storage() .await? @@ -249,7 +249,7 @@ pub async fn get_account_coins( ctx: MmArc, req: GetAccountCoinsRequest, ) -> MmResult { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; let coins = account_ctx .storage() .await? @@ -271,7 +271,7 @@ pub async fn get_enabled_account( ctx: MmArc, _req: GetEnabledAccountRequest, ) -> MmResult { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; let account = account_ctx.storage().await?.load_enabled_account_with_coins().await?; Ok(account) } @@ -279,7 +279,7 @@ pub async fn get_enabled_account( /// Sets the account name. pub async fn set_account_name(ctx: MmArc, req: SetAccountNameRequest) -> MmResult { validate_account_name(&req.name)?; - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx.storage().await?.set_name(req.account_id, req.name).await?; Ok(SuccessResponse::new()) } @@ -290,7 +290,7 @@ pub async fn set_account_description( req: SetAccountDescriptionRequest, ) -> MmResult { validate_account_desc(&req.description)?; - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx .storage() .await? @@ -305,7 +305,7 @@ pub async fn set_account_description( /// /// This RPC affects the storage **only**. It doesn't affect MarketMaker. pub async fn set_account_balance(ctx: MmArc, req: SetBalanceRequest) -> MmResult { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx .storage() .await? @@ -321,7 +321,7 @@ pub async fn set_account_balance(ctx: MmArc, req: SetBalanceRequest) -> MmResult /// This RPC affects the storage **only**. It doesn't affect MarketMaker. pub async fn activate_coins(ctx: MmArc, req: CoinRequest) -> MmResult { validate_tickers(&req.tickers)?; - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx .storage() .await? @@ -336,7 +336,7 @@ pub async fn activate_coins(ctx: MmArc, req: CoinRequest) -> MmResult MmResult { - let account_ctx = AccountContext::from_ctx(&ctx).map_to_mm(AccountRpcError::Internal)?; + let account_ctx = AccountContext::from_ctx(&ctx, None).map_to_mm(AccountRpcError::Internal)?; account_ctx .storage() .await? diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 60c3e9aa62..3b4c53ed70 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -68,7 +68,7 @@ mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net", features = ["event-stream", "p2p"] } mm2_number = { path = "../mm2_number" } -mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"]} +mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"] } mm2_state_machine = { path = "../mm2_state_machine" } num-traits = "0.2" parity-util-mem = "0.11" @@ -93,7 +93,9 @@ ser_error_derive = { path = "../derives/ser_error_derive" } serialization = { path = "../mm2_bitcoin/serialization" } serialization_derive = { path = "../mm2_bitcoin/serialization_derive" } spv_validation = { path = "../mm2_bitcoin/spv_validation" } -sp-runtime-interface = { version = "6.0.0", default-features = false, features = ["disable_target_static_assertions"] } +sp-runtime-interface = { version = "6.0.0", default-features = false, features = [ + "disable_target_static_assertions", +] } sp-trie = { version = "6.0", default-features = false } trie-db = { version = "0.23.1", default-features = false } trie-root = "0.16.0" diff --git a/mm2src/mm2_main/src/database.rs b/mm2src/mm2_main/src/database.rs index 8f278d3adc..436db196e8 100644 --- a/mm2src/mm2_main/src/database.rs +++ b/mm2src/mm2_main/src/database.rs @@ -16,65 +16,76 @@ use stats_swaps::create_and_fill_stats_swaps_from_json_statements; const SELECT_MIGRATION: &str = "SELECT * FROM migration ORDER BY current_migration DESC LIMIT 1;"; -fn get_current_migration(ctx: &MmArc) -> SqlResult { - let conn = ctx.sqlite_connection(); - conn.query_row(SELECT_MIGRATION, [], |row| row.get(0)) +fn get_current_migration(ctx: &MmArc, db_id: Option<&str>) -> SqlResult { + ctx.run_sql_query(db_id, move |conn| { + conn.query_row(SELECT_MIGRATION, [], |row| row.get(0)) + }) } -pub async fn init_and_migrate_sql_db(ctx: &MmArc) -> SqlResult<()> { +pub async fn init_and_migrate_sql_db(ctx: &MmArc, db_id: Option<&str>) -> SqlResult<()> { info!("Checking the current SQLite migration"); - match get_current_migration(ctx) { + match get_current_migration(ctx, db_id) { Ok(current_migration) => { if current_migration >= 1 { info!( "Current migration is {}, skipping the init, trying to migrate", current_migration ); - migrate_sqlite_database(ctx, current_migration).await?; + migrate_sqlite_database(ctx, current_migration, db_id).await?; return Ok(()); } }, Err(e) => { debug!("Error '{}' on getting current migration. The database is either empty or corrupted, trying to clean it first", e); - clean_db(ctx); + clean_db(ctx, db_id); }, }; - info!("Trying to initialize the SQLite database"); + info!( + "Trying to initialize the SQLite database for {}:db", + db_id.unwrap_or("default") + ); - init_db(ctx)?; - migrate_sqlite_database(ctx, 1).await?; - info!("SQLite database initialization is successful"); + init_db(ctx, db_id)?; + migrate_sqlite_database(ctx, 1, db_id).await?; + info!( + "SQLite {} database initialization is successful", + db_id.unwrap_or("default") + ); Ok(()) } -fn init_db(ctx: &MmArc) -> SqlResult<()> { - let conn = ctx.sqlite_connection(); - run_optimization_pragmas(&conn)?; - let init_batch = concat!( - "BEGIN; - CREATE TABLE IF NOT EXISTS migration (current_migration INTEGER NOT_NULL UNIQUE); - INSERT INTO migration (current_migration) VALUES (1);", - CREATE_MY_SWAPS_TABLE!(), - "COMMIT;" - ); - conn.execute_batch(init_batch) +fn init_db(ctx: &MmArc, db_id: Option<&str>) -> SqlResult<()> { + ctx.run_sql_query(db_id, move |conn| { + run_optimization_pragmas(&conn)?; + let init_batch = concat!( + "BEGIN; + CREATE TABLE IF NOT EXISTS migration (current_migration INTEGER NOT_NULL UNIQUE); + INSERT INTO migration (current_migration) VALUES (1);", + CREATE_MY_SWAPS_TABLE!(), + "COMMIT;" + ); + conn.execute_batch(init_batch) + }) } -fn clean_db(ctx: &MmArc) { - let conn = ctx.sqlite_connection(); - if let Err(e) = conn.execute_batch( - "DROP TABLE migration; - DROP TABLE my_swaps;", - ) { - error!("Error {} on SQLite database cleanup", e); - } +fn clean_db(ctx: &MmArc, db_id: Option<&str>) { + ctx.run_sql_query(db_id, move |conn| { + if let Err(e) = conn.execute_batch( + "DROP TABLE migration; + DROP TABLE my_swaps;", + ) { + error!("Error {} on SQLite database cleanup", e); + } + }) } -async fn migration_1(ctx: &MmArc) -> Vec<(&'static str, Vec)> { fill_my_swaps_from_json_statements(ctx).await } +async fn migration_1(ctx: &MmArc) -> Vec<(&'static str, Vec)> { + fill_my_swaps_from_json_statements(ctx, None).await +} async fn migration_2(ctx: &MmArc) -> Vec<(&'static str, Vec)> { - create_and_fill_stats_swaps_from_json_statements(ctx).await + create_and_fill_stats_swaps_from_json_statements(ctx, None).await } fn migration_3() -> Vec<(&'static str, Vec)> { vec![(stats_swaps::ADD_STARTED_AT_INDEX, vec![])] } @@ -105,7 +116,7 @@ fn migration_9() -> Vec<(&'static str, Vec)> { } async fn migration_10(ctx: &MmArc) -> Vec<(&'static str, Vec)> { - set_is_finished_for_legacy_swaps_statements(ctx).await + set_is_finished_for_legacy_swaps_statements(ctx, None).await } fn migration_11() -> Vec<(&'static str, Vec)> { @@ -119,6 +130,10 @@ fn migration_12() -> Vec<(&'static str, Vec)> { ] } +fn migration_13() -> Vec<(&'static str, Vec)> { + db_common::sqlite::execute_batch(my_swaps::ADD_COIN_DB_ID_FIELD) +} + async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option)>> { match current_migration { 1 => Some(migration_1(ctx).await), @@ -133,27 +148,41 @@ async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option 10 => Some(migration_10(ctx).await), 11 => Some(migration_11()), 12 => Some(migration_12()), + 13 => Some(migration_13()), _ => None, } } -pub async fn migrate_sqlite_database(ctx: &MmArc, mut current_migration: i64) -> SqlResult<()> { - info!("migrate_sqlite_database, current migration {}", current_migration); +pub async fn migrate_sqlite_database(ctx: &MmArc, mut current_migration: i64, db_id: Option<&str>) -> SqlResult<()> { + info!( + "{}:db migrate_sqlite_database current migration {current_migration}", + db_id.unwrap_or("default") + ); + while let Some(statements_with_params) = statements_for_migration(ctx, current_migration).await { // `statements_for_migration` locks the [`MmCtx::sqlite_connection`] mutex, // so we can't create a transaction outside of this loop. - let conn = ctx.sqlite_connection(); + let conn = ctx + .sqlite_conn_opt(db_id) + .expect("Connection should be initialized before we get here"); + let conn = conn.lock().unwrap(); let transaction = conn.unchecked_transaction()?; for (statement, params) in statements_with_params { - debug!("Executing SQL statement {:?} with params {:?}", statement, params); + debug!("Executing SQL statement {statement:?} with params {params:?}"); transaction.execute(statement, params_from_iter(params.iter()))?; } + info!("migrate_sqlite_database complete, migrated to {current_migration}"); current_migration += 1; transaction.execute("INSERT INTO migration (current_migration) VALUES (?1);", [ current_migration, ])?; transaction.commit()?; } - info!("migrate_sqlite_database complete, migrated to {}", current_migration); + + info!( + "{}:db migrate_sqlite_database complete migrated to {current_migration}", + db_id.unwrap_or("default") + ); + Ok(()) } diff --git a/mm2src/mm2_main/src/database/my_orders.rs b/mm2src/mm2_main/src/database/my_orders.rs index 7e38c4bfa9..8b3a633ea2 100644 --- a/mm2src/mm2_main/src/database/my_orders.rs +++ b/mm2src/mm2_main/src/database/my_orders.rs @@ -24,8 +24,8 @@ pub const CREATE_MY_ORDERS_TABLE: &str = "CREATE TABLE IF NOT EXISTS my_orders ( rel VARCHAR(255) NOT NULL, price DECIMAL NOT NULL, volume DECIMAL NOT NULL, - created_at INTEGER NOT NULL, - last_updated INTEGER NOT NULL, + created_at INTEGER NOT NULL, + last_updated INTEGER NOT NULL, was_taker INTEGER NOT NULL, status VARCHAR(255) NOT NULL );"; @@ -56,9 +56,10 @@ pub fn insert_maker_order(ctx: &MmArc, uuid: Uuid, order: &MakerOrder) -> SqlRes 0.to_string(), "Created".to_string(), ]; - let conn = ctx.sqlite_connection(); - conn.execute(INSERT_MY_ORDER, params_from_iter(params.iter())) - .map(|_| ()) + ctx.run_sql_query(order.account_id().as_deref(), move |conn| { + conn.execute(INSERT_MY_ORDER, params_from_iter(params.iter())) + .map(|_| ()) + }) } pub fn insert_taker_order(ctx: &MmArc, uuid: Uuid, order: &TakerOrder) -> SqlResult<()> { @@ -81,9 +82,11 @@ pub fn insert_taker_order(ctx: &MmArc, uuid: Uuid, order: &TakerOrder) -> SqlRes 0.to_string(), "Created".to_string(), ]; - let conn = ctx.sqlite_connection(); - conn.execute(INSERT_MY_ORDER, params_from_iter(params.iter())) - .map(|_| ()) + + ctx.run_sql_query(order.account_id().as_deref(), move |conn| { + conn.execute(INSERT_MY_ORDER, params_from_iter(params.iter())) + .map(|_| ()) + }) } pub fn update_maker_order(ctx: &MmArc, uuid: Uuid, order: &MakerOrder) -> SqlResult<()> { @@ -95,12 +98,13 @@ pub fn update_maker_order(ctx: &MmArc, uuid: Uuid, order: &MakerOrder) -> SqlRes order.updated_at.unwrap_or(0).to_string(), "Updated".to_string(), ]; - let conn = ctx.sqlite_connection(); - conn.execute(UPDATE_MY_ORDER, params_from_iter(params.iter())) - .map(|_| ()) + ctx.run_sql_query(order.account_id().as_deref(), move |conn| { + conn.execute(UPDATE_MY_ORDER, params_from_iter(params.iter())) + .map(|_| ()) + }) } -pub fn update_was_taker(ctx: &MmArc, uuid: Uuid) -> SqlResult<()> { +pub fn update_was_taker(ctx: &MmArc, uuid: Uuid, db_id: Option<&str>) -> SqlResult<()> { debug!("Updating order {} in the SQLite database", uuid); let params = vec![ uuid.to_string(), @@ -108,17 +112,21 @@ pub fn update_was_taker(ctx: &MmArc, uuid: Uuid) -> SqlResult<()> { now_ms().to_string(), 1.to_string(), ]; - let conn = ctx.sqlite_connection(); - conn.execute(UPDATE_WAS_TAKER, params_from_iter(params.iter())) - .map(|_| ()) + + ctx.run_sql_query(db_id, move |conn| { + conn.execute(UPDATE_WAS_TAKER, params_from_iter(params.iter())) + .map(|_| ()) + }) } -pub fn update_order_status(ctx: &MmArc, uuid: Uuid, status: String) -> SqlResult<()> { +pub fn update_order_status(ctx: &MmArc, uuid: Uuid, status: String, db_id: Option<&str>) -> SqlResult<()> { debug!("Updating order {} in the SQLite database", uuid); let params = vec![uuid.to_string(), now_ms().to_string(), status]; - let conn = ctx.sqlite_connection(); - conn.execute(UPDATE_ORDER_STATUS, params_from_iter(params.iter())) - .map(|_| ()) + + ctx.run_sql_query(db_id, move |conn| { + conn.execute(UPDATE_ORDER_STATUS, params_from_iter(params.iter())) + .map(|_| ()) + }) } /// Adds where clauses determined by MyOrdersFilter @@ -205,7 +213,7 @@ impl From for SelectRecentOrdersUuidsErr { pub fn select_orders_by_filter( conn: &Connection, filter: &MyOrdersFilter, - paging_options: Option<&PagingOptions>, + paging_options: Option, ) -> SqlResult { let mut query_builder = SqlBuilder::select_from(MY_ORDERS_TABLE); let mut params = vec![]; diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index 2fe1a85890..a8d81e3182 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -56,6 +56,11 @@ pub const ADD_OTHER_P2P_PUBKEY_FIELD: &str = "ALTER TABLE my_swaps ADD COLUMN ot // Storing rational numbers as text to maintain precision pub const ADD_DEX_FEE_BURN_FIELD: &str = "ALTER TABLE my_swaps ADD COLUMN dex_fee_burn TEXT;"; +pub const ADD_COIN_DB_ID_FIELD: &[&str] = &[ + "ALTER TABLE my_swaps ADD COLUMN taker_coin_db_id TEXT;", + "ALTER TABLE my_swaps ADD COLUMN maker_coin_db_id TEXT;", +]; + /// The query to insert swap on migration 1, during this migration swap_type column doesn't exist /// in my_swaps table yet. const INSERT_MY_SWAP_MIGRATION_1: &str = @@ -64,7 +69,7 @@ const INSERT_MY_SWAP: &str = "INSERT INTO my_swaps (my_coin, other_coin, uuid, started_at, swap_type) VALUES (?1, ?2, ?3, ?4, ?5)"; pub fn insert_new_swap( - ctx: &MmArc, + conn: &Connection, my_coin: &str, other_coin: &str, uuid: &str, @@ -72,7 +77,6 @@ pub fn insert_new_swap( swap_type: u8, ) -> SqlResult<()> { debug!("Inserting new swap {} to the SQLite database", uuid); - let conn = ctx.sqlite_connection(); let params = [my_coin, other_coin, uuid, started_at, &swap_type.to_string()]; conn.execute(INSERT_MY_SWAP, params).map(|_| ()) } @@ -97,7 +101,9 @@ const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( maker_coin_nota, taker_coin_confs, taker_coin_nota, - other_p2p_pub + other_p2p_pub, + taker_coin_db_id, + maker_coin_db_id ) VALUES ( :my_coin, :other_coin, @@ -118,18 +124,21 @@ const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( :maker_coin_nota, :taker_coin_confs, :taker_coin_nota, - :other_p2p_pub + :other_p2p_pub, + :taker_coin_db_id, + :maker_coin_db_id );"#; -pub fn insert_new_swap_v2(ctx: &MmArc, params: &[(&str, &dyn ToSql)]) -> SqlResult<()> { - let conn = ctx.sqlite_connection(); +pub fn insert_new_swap_v2(conn: &Connection, params: &[(&str, &dyn ToSql)]) -> SqlResult<()> { conn.execute(INSERT_MY_SWAP_V2, params).map(|_| ()) } /// Returns SQL statements to initially fill my_swaps table using existing DB with JSON files /// Use this only in migration code! -pub async fn fill_my_swaps_from_json_statements(ctx: &MmArc) -> Vec<(&'static str, Vec)> { - let swaps = SavedSwap::load_all_my_swaps_from_db(ctx).await.unwrap_or_default(); +pub async fn fill_my_swaps_from_json_statements(ctx: &MmArc, db_id: Option<&str>) -> Vec<(&'static str, Vec)> { + let swaps = SavedSwap::load_all_my_swaps_from_db(ctx, db_id) + .await + .unwrap_or_default(); swaps .into_iter() .filter_map(insert_saved_swap_sql_migration_1) @@ -196,7 +205,8 @@ fn apply_my_swaps_filter(builder: &mut SqlBuilder, params: &mut Vec<(&str, Strin pub fn select_uuids_by_my_swaps_filter( conn: &Connection, filter: &MySwapsFilter, - paging_options: Option<&PagingOptions>, + paging_options: Option, + db_id: String, ) -> SqlResult { let mut query_builder = SqlBuilder::select_from(MY_SWAPS_TABLE); let mut params = vec![]; @@ -213,7 +223,10 @@ pub fn select_uuids_by_my_swaps_filter( let total_count: isize = conn.query_row_named(&count_query, params_as_trait.as_slice(), |row| row.get(0))?; let total_count = total_count.try_into().expect("COUNT should always be >= 0"); if total_count == 0 { - return Ok(MyRecentSwapsUuids::default()); + return Ok(MyRecentSwapsUuids { + pubkey: db_id, + ..MyRecentSwapsUuids::default() + }); } // query the uuids and types finally @@ -251,6 +264,7 @@ pub fn select_uuids_by_my_swaps_filter( uuids_and_types, total_count, skipped, + pubkey: db_id, }) } @@ -278,6 +292,7 @@ pub fn update_swap_events(conn: &Connection, uuid: &str, events_json: &str) -> S } const UPDATE_SWAP_IS_FINISHED_BY_UUID: &str = "UPDATE my_swaps SET is_finished = 1 WHERE uuid = :uuid;"; + pub fn set_swap_is_finished(conn: &Connection, uuid: &str) -> SqlResult<()> { let mut stmt = conn.prepare(UPDATE_SWAP_IS_FINISHED_BY_UUID)?; stmt.execute(&[(":uuid", uuid)]).map(|_| ()) @@ -337,14 +352,21 @@ pub const SELECT_MY_SWAP_V2_BY_UUID: &str = r#"SELECT taker_coin_confs, taker_coin_nota, p2p_privkey, - other_p2p_pub + other_p2p_pub, + taker_coin_db_id, + maker_coin_db_id FROM my_swaps WHERE uuid = :uuid; "#; /// Returns SQL statements to set is_finished to 1 for completed legacy swaps -pub async fn set_is_finished_for_legacy_swaps_statements(ctx: &MmArc) -> Vec<(&'static str, Vec)> { - let swaps = SavedSwap::load_all_my_swaps_from_db(ctx).await.unwrap_or_default(); +pub async fn set_is_finished_for_legacy_swaps_statements( + ctx: &MmArc, + db_id: Option<&str>, +) -> Vec<(&'static str, Vec)> { + let swaps = SavedSwap::load_all_my_swaps_from_db(ctx, db_id) + .await + .unwrap_or_default(); swaps .into_iter() .filter_map(|swap| { diff --git a/mm2src/mm2_main/src/database/stats_nodes.rs b/mm2src/mm2_main/src/database/stats_nodes.rs index 458f4159bc..91e80b1bd7 100644 --- a/mm2src/mm2_main/src/database/stats_nodes.rs +++ b/mm2src/mm2_main/src/database/stats_nodes.rs @@ -30,52 +30,54 @@ const SELECT_PEERS_NAMES: &str = "SELECT peer_id, name FROM nodes"; const INSERT_STAT: &str = "INSERT INTO stats_nodes (name, version, timestamp, error) VALUES (?1, ?2, ?3, ?4)"; -pub fn insert_node_info(ctx: &MmArc, node_info: &NodeInfo) -> SqlResult<()> { - debug!("Inserting info about node {} to the SQLite database", node_info.name); - let params = vec![ - node_info.name.clone(), - node_info.address.clone(), - node_info.peer_id.clone(), - ]; - let conn = ctx.sqlite_connection(); - conn.execute(INSERT_NODE, params_from_iter(params.iter())).map(|_| ()) +pub fn insert_node_info(ctx: &MmArc, node_info: NodeInfo, db_id: Option<&str>) -> SqlResult<()> { + ctx.run_sql_query(db_id, move |conn| { + debug!("Inserting info about node {} to the SQLite database", node_info.name); + let params = vec![node_info.name.clone(), node_info.address.clone(), node_info.peer_id]; + conn.execute(INSERT_NODE, params_from_iter(params.iter())).map(|_| ()) + }) } -pub fn delete_node_info(ctx: &MmArc, name: String) -> SqlResult<()> { - debug!("Deleting info about node {} from the SQLite database", name); - let params = vec![name]; - let conn = ctx.sqlite_connection(); - conn.execute(DELETE_NODE, params_from_iter(params.iter())).map(|_| ()) +pub fn delete_node_info(ctx: &MmArc, name: String, db_id: Option<&str>) -> SqlResult<()> { + ctx.run_sql_query(db_id, move |conn| { + debug!("Deleting info about node {} from the SQLite database", name); + let params = vec![name]; + conn.execute(DELETE_NODE, params_from_iter(params.iter())).map(|_| ()) + }) } -pub fn select_peers_addresses(ctx: &MmArc) -> SqlResult, SqlError> { - let conn = ctx.sqlite_connection(); - let mut stmt = conn.prepare(SELECT_PEERS_ADDRESSES)?; - let peers_addresses = stmt - .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? - .collect::>>()?; - - Ok(peers_addresses) +pub fn select_peers_addresses(ctx: &MmArc, db_id: Option<&str>) -> SqlResult, SqlError> { + ctx.run_sql_query(db_id, move |conn| { + let mut stmt = conn.prepare(SELECT_PEERS_ADDRESSES)?; + let peers_addresses = stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? + .collect::>>()?; + Ok(peers_addresses) + }) } -pub fn select_peers_names(ctx: &MmArc) -> SqlResult, SqlError> { - ctx.sqlite_connection() - .prepare(SELECT_PEERS_NAMES)? - .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? - .collect::>>() +pub fn select_peers_names(ctx: &MmArc, db_id: Option<&str>) -> SqlResult, SqlError> { + ctx.run_sql_query(db_id, move |conn| { + let mut stmt = conn.prepare(SELECT_PEERS_NAMES)?; + let peers_names = stmt + .query_map([], |row| Ok((row.get(0)?, row.get(1)?)))? + .collect::>>()?; + Ok(peers_names) + }) } -pub fn insert_node_version_stat(ctx: &MmArc, node_version_stat: NodeVersionStat) -> SqlResult<()> { - debug!( - "Inserting new version stat for node {} to the SQLite database", - node_version_stat.name - ); - let params = vec![ - node_version_stat.name, - node_version_stat.version.unwrap_or_default(), - node_version_stat.timestamp.to_string(), - node_version_stat.error.unwrap_or_default(), - ]; - let conn = ctx.sqlite_connection(); - conn.execute(INSERT_STAT, params_from_iter(params.iter())).map(|_| ()) +pub fn insert_node_version_stat(ctx: &MmArc, node_version_stat: NodeVersionStat, db_id: Option<&str>) -> SqlResult<()> { + ctx.run_sql_query(db_id, move |conn| { + debug!( + "Inserting new version stat for node {} to the SQLite database", + node_version_stat.name + ); + let params = vec![ + node_version_stat.name, + node_version_stat.version.unwrap_or_default(), + node_version_stat.timestamp.to_string(), + node_version_stat.error.unwrap_or_default(), + ]; + conn.execute(INSERT_STAT, params_from_iter(params.iter())).map(|_| ()) + }) } diff --git a/mm2src/mm2_main/src/database/stats_swaps.rs b/mm2src/mm2_main/src/database/stats_swaps.rs index 3322245f01..55a93138af 100644 --- a/mm2src/mm2_main/src/database/stats_swaps.rs +++ b/mm2src/mm2_main/src/database/stats_swaps.rs @@ -48,8 +48,8 @@ const INSERT_STATS_SWAP: &str = "INSERT INTO stats_swaps ( taker_coin_usd_price, maker_pubkey, taker_pubkey -) VALUES (:maker_coin, :maker_coin_ticker, :maker_coin_platform, :taker_coin, :taker_coin_ticker, -:taker_coin_platform, :uuid, :started_at, :finished_at, :maker_amount, :taker_amount, :is_success, +) VALUES (:maker_coin, :maker_coin_ticker, :maker_coin_platform, :taker_coin, :taker_coin_ticker, +:taker_coin_platform, :uuid, :started_at, :finished_at, :maker_amount, :taker_amount, :is_success, :maker_coin_usd_price, :taker_coin_usd_price, :maker_pubkey, :taker_pubkey)"; pub const ADD_STARTED_AT_INDEX: &str = "CREATE INDEX timestamp_index ON stats_swaps (started_at);"; @@ -97,9 +97,16 @@ pub const ADD_MAKER_TAKER_GUI_AND_VERSION: &[&str] = &[ pub const SELECT_ID_BY_UUID: &str = "SELECT id FROM stats_swaps WHERE uuid = ?1"; /// Returns SQL statements to initially fill stats_swaps table using existing DB with JSON files -pub async fn create_and_fill_stats_swaps_from_json_statements(ctx: &MmArc) -> Vec<(&'static str, Vec)> { - let maker_swaps = SavedSwap::load_all_from_maker_stats_db(ctx).await.unwrap_or_default(); - let taker_swaps = SavedSwap::load_all_from_taker_stats_db(ctx).await.unwrap_or_default(); +pub async fn create_and_fill_stats_swaps_from_json_statements( + ctx: &MmArc, + db_id: Option<&str>, +) -> Vec<(&'static str, Vec)> { + let maker_swaps = SavedSwap::load_all_from_maker_stats_db(ctx, db_id) + .await + .unwrap_or_default(); + let taker_swaps = SavedSwap::load_all_from_taker_stats_db(ctx, db_id) + .await + .unwrap_or_default(); let mut result = vec![(CREATE_STATS_SWAPS_TABLE, vec![])]; let mut inserted_maker_uuids = HashSet::with_capacity(maker_swaps.len()); diff --git a/mm2src/mm2_main/src/lp_native_dex.rs b/mm2src/mm2_main/src/lp_native_dex.rs index bd875511b0..4f7a8c23ea 100644 --- a/mm2src/mm2_main/src/lp_native_dex.rs +++ b/mm2src/mm2_main/src/lp_native_dex.rs @@ -18,13 +18,22 @@ // marketmaker // +use crate::heartbeat_event::HeartbeatEvent; +use crate::lp_message_service::{init_message_service, InitMessageServiceError}; +use crate::lp_network::{lp_network_ports, p2p_event_process_loop, NetIdError}; +use crate::lp_ordermatch::{broadcast_maker_orders_keep_alive_loop, clean_memory_loop, init_ordermatch_context, + lp_ordermatch_loop, orders_kick_start, BalanceUpdateOrdermatchHandler, OrdermatchInitError}; +use crate::lp_swap::{running_swaps_num, swap_kick_starts}; +use crate::lp_wallet::{initialize_wallet_passphrase, WalletInitError}; +use crate::rpc::spawn_rpc; use bitcrypto::sha256; use coins::register_balance_update_handler; use common::executor::{SpawnFuture, Timer}; -use common::log::{info, warn}; +use common::log::{debug, error, info, warn}; use crypto::{from_hw_error, CryptoCtx, HwError, HwProcessingError, HwRpcError, WithHwRpcError}; use derive_more::Display; use enum_derives::EnumFromTrait; +use futures::StreamExt; use mm2_core::mm_ctx::{MmArc, MmCtx}; use mm2_err_handle::common_errors::InternalError; use mm2_err_handle::prelude::*; @@ -44,19 +53,10 @@ use std::str; use std::time::Duration; use std::{fs, usize}; -#[cfg(not(target_arch = "wasm32"))] -use crate::database::init_and_migrate_sql_db; -use crate::heartbeat_event::HeartbeatEvent; -use crate::lp_message_service::{init_message_service, InitMessageServiceError}; -use crate::lp_network::{lp_network_ports, p2p_event_process_loop, NetIdError}; -use crate::lp_ordermatch::{broadcast_maker_orders_keep_alive_loop, clean_memory_loop, init_ordermatch_context, - lp_ordermatch_loop, orders_kick_start, BalanceUpdateOrdermatchHandler, OrdermatchInitError}; -use crate::lp_swap::{running_swaps_num, swap_kick_starts}; -use crate::lp_wallet::{initialize_wallet_passphrase, WalletInitError}; -use crate::rpc::spawn_rpc; - cfg_native! { + use crate::database::init_and_migrate_sql_db; use db_common::sqlite::rusqlite::Error as SqlError; + use mm2_core::sql_connection_pool::{AsyncSqliteConnPool, SqliteConnPool}; use mm2_io::fs::{ensure_dir_is_writable, ensure_file_is_writable}; use mm2_net::ip_addr::myipaddr; use rustls_pemfile as pemfile; @@ -66,10 +66,10 @@ cfg_native! { #[path = "lp_init/init_hw.rs"] pub mod init_hw; cfg_wasm32! { - use mm2_net::wasm_event_stream::handle_worker_stream; - #[path = "lp_init/init_metamask.rs"] pub mod init_metamask; + + use mm2_net::wasm_event_stream::handle_worker_stream; } const DEFAULT_NETID_SEEDNODES: &[SeedNodeInfo] = &[ @@ -331,10 +331,16 @@ fn default_seednodes(netid: u16) -> Vec { } #[cfg(not(target_arch = "wasm32"))] -pub fn fix_directories(ctx: &MmCtx) -> MmInitResult<()> { - fix_shared_dbdir(ctx)?; +pub fn fix_directories(ctx: &MmCtx, db_id: Option<&str>, fix_shared: bool) -> MmInitResult<()> { + if fix_shared { + fix_shared_dbdir(ctx)?; + }; - let dbdir = ctx.dbdir(); + let dbdir = ctx.dbdir(db_id); + fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { + path: dbdir.clone(), + error: e.to_string(), + })?; if !ensure_dir_is_writable(&dbdir.join("SWAPS")) { return MmError::err(MmInitError::db_directory_is_not_writable("SWAPS")); @@ -399,9 +405,9 @@ fn fix_shared_dbdir(ctx: &MmCtx) -> MmInitResult<()> { } #[cfg(not(target_arch = "wasm32"))] -fn migrate_db(ctx: &MmArc) -> MmInitResult<()> { - let migration_num_path = ctx.dbdir().join(".migration"); - let mut current_migration = match std::fs::read(&migration_num_path) { +fn migrate_db(ctx: &MmArc, db_id: Option<&str>) -> MmInitResult<()> { + let migration_num_path = ctx.dbdir(db_id).join(".migration"); + let mut current_migration = match fs::read(&migration_num_path) { Ok(bytes) => { let mut num_bytes = [0; 8]; if bytes.len() == 8 { @@ -448,49 +454,52 @@ fn init_wasm_event_streaming(ctx: &MmArc) { } } -pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { - init_ordermatch_context(&ctx)?; - init_p2p(ctx.clone()).await?; - - if !CryptoCtx::is_init(&ctx)? { - return Ok(()); - } - - #[cfg(not(target_arch = "wasm32"))] - { - fix_directories(&ctx)?; - ctx.init_sqlite_connection() - .map_to_mm(MmInitError::ErrorSqliteInitializing)?; - ctx.init_shared_sqlite_conn() - .map_to_mm(MmInitError::ErrorSqliteInitializing)?; - ctx.init_async_sqlite_connection() - .await - .map_to_mm(MmInitError::ErrorSqliteInitializing)?; - init_and_migrate_sql_db(&ctx).await?; - migrate_db(&ctx)?; - } - - init_message_service(&ctx).await?; - - let balance_update_ordermatch_handler = BalanceUpdateOrdermatchHandler::new(ctx.clone()); - register_balance_update_handler(ctx.clone(), Box::new(balance_update_ordermatch_handler)).await; - - ctx.initialized.pin(true).map_to_mm(MmInitError::Internal)?; - - // launch kickstart threads before RPC is available, this will prevent the API user to place - // an order and start new swap that might get started 2 times because of kick-start - kick_start(ctx.clone()).await?; +#[cfg(not(target_arch = "wasm32"))] +async fn init_db_migration_watcher_loop(ctx: MmArc) { + use std::collections::HashSet; + + let mut migrations = HashSet::new(); + let mut receiver = ctx + .init_db_migration_watcher() + .expect("db_migration_watcher initialization failed"); + + while let Some(db_id) = receiver.next().await { + if migrations.contains(&db_id) { + debug!("{} migrated, skipping migration..", db_id); + continue; + } - init_event_streaming(&ctx).await?; + // fix directories for new db_id. + if let Err(err) = fix_directories(&ctx, Some(&db_id), false) { + error!("{err:?}"); + continue; + }; + // run db migration for new db_id. + if let Err(err) = run_db_migration_impl(&ctx, Some(&db_id)).await { + error!("{err:?}"); + continue; + }; - ctx.spawner().spawn(lp_ordermatch_loop(ctx.clone())); + // insert new db_id to migrated list + migrations.insert(db_id.to_owned()); - ctx.spawner().spawn(broadcast_maker_orders_keep_alive_loop(ctx.clone())); + // Fetch and extend ctx.coins_needed_for_kick_start from new intialized db. + if let Err(err) = kick_start(ctx.clone(), Some(&db_id)).await { + error!("{err:?}"); + continue; + }; + } +} - #[cfg(target_arch = "wasm32")] - init_wasm_event_streaming(&ctx); +#[cfg(not(target_arch = "wasm32"))] +async fn run_db_migration_impl(ctx: &MmArc, db_id: Option<&str>) -> MmInitResult<()> { + AsyncSqliteConnPool::init(ctx, db_id) + .await + .map_to_mm(MmInitError::ErrorSqliteInitializing)?; + SqliteConnPool::init(ctx, db_id).map_to_mm(MmInitError::ErrorSqliteInitializing)?; - ctx.spawner().spawn(clean_memory_loop(ctx.weak())); + init_and_migrate_sql_db(ctx, db_id).await?; + migrate_db(ctx, db_id)?; Ok(()) } @@ -517,8 +526,8 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes let ctx_id = ctx.ffi_handle().map_to_mm(MmInitError::Internal)?; spawn_rpc(ctx_id); - let ctx_c = ctx.clone(); + let ctx_c = ctx.clone(); ctx.spawner().spawn(async move { if let Err(err) = ctx_c.init_metrics() { warn!("Couldn't initialize metrics system: {}", err); @@ -543,20 +552,72 @@ pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitRes Ok(()) } -async fn kick_start(ctx: MmArc) -> MmInitResult<()> { - let mut coins_needed_for_kick_start = swap_kick_starts(ctx.clone()) - .await - .map_to_mm(MmInitError::SwapsKickStartError)?; - coins_needed_for_kick_start.extend( - orders_kick_start(&ctx) +pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { + init_ordermatch_context(&ctx)?; + init_p2p(ctx.clone()).await?; + + if !CryptoCtx::is_init(&ctx)? { + return Ok(()); + } + + #[cfg(not(target_arch = "wasm32"))] + { + // fix directory for shared and global db. + fix_directories(&ctx, None, true)?; + // init shared_db once. + SqliteConnPool::init_shared(&ctx).map_to_mm(MmInitError::ErrorSqliteInitializing)?; + run_db_migration_impl(&ctx, None).await?; + ctx.spawner().spawn(init_db_migration_watcher_loop(ctx.clone())); + } + + init_message_service(&ctx).await?; + + let balance_update_ordermatch_handler = BalanceUpdateOrdermatchHandler::new(ctx.clone()); + register_balance_update_handler(ctx.clone(), Box::new(balance_update_ordermatch_handler)).await; + + ctx.initialized.pin(true).map_to_mm(MmInitError::Internal)?; + + // launch kickstart threads before RPC is available, this will prevent the API user to place + // an order and start new swap that might get started 2 times because of kick-start + kick_start(ctx.clone(), None).await?; + + init_event_streaming(&ctx).await?; + + ctx.spawner().spawn(lp_ordermatch_loop(ctx.clone())); + + ctx.spawner().spawn(broadcast_maker_orders_keep_alive_loop(ctx.clone())); + + #[cfg(target_arch = "wasm32")] + init_wasm_event_streaming(&ctx); + + ctx.spawner().spawn(clean_memory_loop(ctx.weak())); + + Ok(()) +} + +// kick_start calls swap_kick_starts to get list of coins needed for swap kick start, +// additionally calls orders_kick_start to get list of coins needed for orders and +// then extend Mmctx::coins_needed_for_kick_start list +async fn kick_start(ctx: MmArc, db_id: Option<&str>) -> MmInitResult<()> { + let coins_needed_for_kick_start = { + let mut coins_needed_for_kick_start = swap_kick_starts(ctx.clone(), db_id) .await - .map_to_mm(MmInitError::OrdersKickStartError)?, - ); + .map_to_mm(MmInitError::SwapsKickStartError)?; + coins_needed_for_kick_start.extend( + orders_kick_start(&ctx, db_id) + .await + .map_to_mm(MmInitError::OrdersKickStartError)?, + ); + + coins_needed_for_kick_start + }; + let mut lock = ctx .coins_needed_for_kick_start .lock() .map_to_mm(|poison| MmInitError::Internal(poison.to_string()))?; - *lock = coins_needed_for_kick_start; + // extend existing coins list needed for kickstart when there's a new pubkey activation + lock.extend(coins_needed_for_kick_start); Ok(()) } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 9829fed75a..e51090a727 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -25,11 +25,11 @@ use best_orders::BestOrdersAction; use blake2::digest::{Update, VariableOutput}; use blake2::Blake2bVar; use coins::utxo::{compressed_pub_key_from_priv_raw, ChecksumType, UtxoAddressFormat}; -use coins::{coin_conf, find_pair, lp_coinfind, BalanceTradeFeeUpdatedHandler, CoinProtocol, CoinsContext, - FeeApproxStage, MarketCoinOps, MmCoinEnum}; +use coins::{coin_conf, find_pair, find_unique_account_ids_active, lp_coinfind, lp_coinfind_any, + BalanceTradeFeeUpdatedHandler, CoinProtocol, CoinsContext, FeeApproxStage, MarketCoinOps, MmCoinEnum}; use common::executor::{simple_map::AbortableSimpleMap, AbortSettings, AbortableSystem, AbortedError, SpawnAbortable, SpawnFuture, Timer}; -use common::log::{error, warn, LogOnError}; +use common::log::{error, info, warn, LogOnError}; use common::time_cache::TimeCache; use common::{bits256, log, new_uuid, now_ms, now_sec}; use crypto::privkey::SerializableSecp256k1Keypair; @@ -92,7 +92,7 @@ cfg_wasm32! { use mm2_db::indexed_db::{ConstructibleDb, DbLocked}; use ordermatch_wasm_db::{InitDbResult, OrdermatchDb}; - pub type OrdermatchDbLocked<'a> = DbLocked<'a, OrdermatchDb>; + pub type OrdermatchDbLocked = DbLocked; } mod best_orders; @@ -122,6 +122,9 @@ const TAKER_ORDER_TIMEOUT: u64 = 30; const ORDER_MATCH_TIMEOUT: u64 = 30; const ORDERBOOK_REQUESTING_TIMEOUT: u64 = MIN_ORDER_KEEP_ALIVE_INTERVAL * 2; const MAX_ORDERS_NUMBER_IN_ORDERBOOK_RESPONSE: usize = 1000; +/// How many times we'll try to check if the other coin is ready before giving up. +/// We don't want to wait forever, so we'll give it 5 shots. +const VALIDATE_OTHER_COIN_TIMEOUT: u64 = 5; #[cfg(not(test))] const TRIE_STATE_HISTORY_TIMEOUT: u64 = 14400; #[cfg(test)] @@ -1417,7 +1420,7 @@ impl<'a> TakerOrderBuilder<'a> { /// Validate fields and build #[allow(clippy::result_large_err)] - pub fn build(self) -> Result { + pub async fn build(self) -> Result { let min_base_amount = self.base_coin.min_trading_vol(); let min_rel_amount = self.rel_coin.min_trading_vol(); @@ -1512,12 +1515,14 @@ impl<'a> TakerOrderBuilder<'a> { base_orderbook_ticker: self.base_orderbook_ticker, rel_orderbook_ticker: self.rel_orderbook_ticker, p2p_privkey, + base_coin_account_id: self.base_coin.account_db_id().await, + rel_coin_account_id: self.rel_coin.account_db_id().await, }) } #[cfg(test)] /// skip validation for tests - fn build_unchecked(self) -> TakerOrder { + async fn build_unchecked(self) -> TakerOrder { let base_protocol_info = match &self.action { TakerAction::Buy => self.base_coin.coin_protocol_info(Some(self.base_amount.clone())), TakerAction::Sell => self.base_coin.coin_protocol_info(None), @@ -1552,6 +1557,8 @@ impl<'a> TakerOrderBuilder<'a> { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: self.base_coin.account_db_id().await, + rel_coin_account_id: self.rel_coin.account_db_id().await, } } } @@ -1573,6 +1580,12 @@ pub struct TakerOrder { /// A custom priv key for more privacy to prevent linking orders of the same node between each other /// Commonly used with privacy coins (ARRR, ZCash, etc.) p2p_privkey: Option, + /// The pubkey rmd160 for the coin we're offering to trade. + /// It's optional as we'll fallback to default account pubkey. + base_coin_account_id: Option, + /// The pubkey rmd160 for the coin we want in return. + /// Also optional, just like the base coin's + rel_coin_account_id: Option, } /// Result of match_reserved function @@ -1673,6 +1686,36 @@ impl TakerOrder { } fn p2p_keypair(&self) -> Option<&KeyPair> { self.p2p_privkey.as_ref().map(|key| key.key_pair()) } + + /// Gets the account ID for the coin we're trading. + /// If we're buying, it's the base coin; if we're selling, it's the rel coin. + pub fn account_id(&self) -> &Option { + match self.request.action { + TakerAction::Buy => &self.base_coin_account_id, + TakerAction::Sell => &self.rel_coin_account_id, + } + } + + /// Gets the account ID for the 'other' coin in the trade. + /// If we're buying, it's the rel coin; if we're selling, it's the base coin. + pub fn other_coin_account_id(&self) -> &Option { + match self.request.action { + TakerAction::Buy => &self.rel_coin_account_id, + TakerAction::Sell => &self.base_coin_account_id, + } + } + + /// Makes sure the 'other' coin in our trade is ready to go. + /// It'll keep trying for a bit if it doesn't work right away. + pub async fn validate_other_coin_account_id(&self, ctx: &MmArc) -> Result<(), String> { + validate_other_coin_account_id_impl( + ctx, + self.taker_coin_ticker(), + self.other_coin_account_id(), + "TakerOrder", + ) + .await + } } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -1704,6 +1747,12 @@ pub struct MakerOrder { /// A custom priv key for more privacy to prevent linking orders of the same node between each other /// Commonly used with privacy coins (ARRR, ZCash, etc.) p2p_privkey: Option, + /// The pubkey rmd160 for the coin we're offering to trade. + /// It's optional as we'll fallback to default account pubkey. + base_coin_account_id: Option, + /// The pubkey rmd160 for the coin we want in return. + /// Also optional, just like the base coin's + rel_coin_account_id: Option, } pub struct MakerOrderBuilder<'a> { @@ -1905,7 +1954,7 @@ impl<'a> MakerOrderBuilder<'a> { /// Build MakerOrder #[allow(clippy::result_large_err)] - pub fn build(self) -> Result { + pub async fn build(self) -> Result { if self.base_coin.ticker() == self.rel_coin.ticker() { return Err(MakerOrderBuildError::BaseEqualRel); } @@ -1959,11 +2008,13 @@ impl<'a> MakerOrderBuilder<'a> { base_orderbook_ticker: self.base_orderbook_ticker, rel_orderbook_ticker: self.rel_orderbook_ticker, p2p_privkey, + base_coin_account_id: self.base_coin.account_db_id().await, + rel_coin_account_id: self.rel_coin.account_db_id().await, }) } #[cfg(test)] - fn build_unchecked(self) -> MakerOrder { + async fn build_unchecked(self) -> MakerOrder { let created_at = now_ms(); #[allow(clippy::or_fun_call)] MakerOrder { @@ -1983,6 +2034,8 @@ impl<'a> MakerOrderBuilder<'a> { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: self.base_coin.account_db_id().await, + rel_coin_account_id: self.rel_coin.account_db_id().await, } } } @@ -2090,6 +2143,16 @@ impl MakerOrder { fn was_updated(&self) -> bool { self.updated_at != Some(self.created_at) } fn p2p_keypair(&self) -> Option<&KeyPair> { self.p2p_privkey.as_ref().map(|key| key.key_pair()) } + + /// Gets the account ID for the base coin. + /// For maker orders, this is always the coin we're offering to trade. + pub fn account_id(&self) -> &Option { &self.base_coin_account_id } + + /// Checks if the other coin (the one we want in exchange) is good to go. + /// It'll give it a few tries if it doesn't work right away. + pub async fn validate_other_coin_account_id(&self, ctx: &MmArc) -> Result<(), String> { + validate_other_coin_account_id_impl(ctx, &self.rel, &self.rel_coin_account_id, "MakerOrder").await + } } impl From for MakerOrder { @@ -2113,6 +2176,8 @@ impl From for MakerOrder { base_orderbook_ticker: taker_order.base_orderbook_ticker, rel_orderbook_ticker: taker_order.rel_orderbook_ticker, p2p_privkey: taker_order.p2p_privkey, + base_coin_account_id: taker_order.base_coin_account_id, + rel_coin_account_id: taker_order.rel_coin_account_id, }, // The "buy" taker order is recreated with reversed pair as Maker order is always considered as "sell" TakerAction::Buy => { @@ -2135,6 +2200,8 @@ impl From for MakerOrder { base_orderbook_ticker: taker_order.rel_orderbook_ticker, rel_orderbook_ticker: taker_order.base_orderbook_ticker, p2p_privkey: taker_order.p2p_privkey, + base_coin_account_id: taker_order.rel_coin_account_id, + rel_coin_account_id: taker_order.base_coin_account_id, } }, } @@ -2722,6 +2789,7 @@ struct OrdermatchContext { /// Pending MakerReserved messages for a specific TakerOrder UUID /// Used to select a trade with the best price upon matching pending_maker_reserved: AsyncMutex>>, + // Using None for db_id here won't matter much since calling `OrdermatchContext::ordermatch_db(db_id)` will use the provided db_id. #[cfg(target_arch = "wasm32")] ordermatch_db: ConstructibleDb, } @@ -2759,7 +2827,7 @@ pub fn init_ordermatch_context(ctx: &MmArc) -> OrdermatchInitResult<()> { orderbook_tickers, original_tickers, #[cfg(target_arch = "wasm32")] - ordermatch_db: ConstructibleDb::new(ctx), + ordermatch_db: ConstructibleDb::new(ctx, None), }; from_ctx(&ctx.ordermatch_ctx, move || Ok(ordermatch_context)) @@ -2788,8 +2856,9 @@ impl OrdermatchContext { pending_maker_reserved: Default::default(), orderbook_tickers: Default::default(), original_tickers: Default::default(), + // Using None for db_id here won't matter much since calling `OrdermatchContext::ordermatch_db(db_id)` will use the provided db_id. #[cfg(target_arch = "wasm32")] - ordermatch_db: ConstructibleDb::new(ctx), + ordermatch_db: ConstructibleDb::new(ctx, None), }) }))) } @@ -2808,8 +2877,8 @@ impl OrdermatchContext { } #[cfg(target_arch = "wasm32")] - pub async fn ordermatch_db(&self) -> InitDbResult> { - self.ordermatch_db.get_or_initialize().await + pub async fn ordermatch_db(&self, db_id: Option<&str>) -> InitDbResult { + self.ordermatch_db.get_or_initialize(db_id).await } } @@ -2966,12 +3035,13 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }, }; + let account_db_id = maker_order.account_id(); if ctx.use_trading_proto_v2() { let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { let mut maker_swap_state_machine = MakerSwapStateMachine { - storage: MakerSwapStorage::new(ctx.clone()), + storage: MakerSwapStorage::new(ctx.clone(), account_db_id.as_deref()), abortable_system: ctx .abortable_system .create_subsystem() @@ -3011,6 +3081,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO uuid, now, LEGACY_SWAP_TYPE, + account_db_id.as_deref(), ) .await { @@ -3030,7 +3101,8 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO lock_time, maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), secret, - ); + ) + .await; run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), ctx).await; } }; @@ -3117,6 +3189,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat ); let now = now_sec(); + let account_db_id = taker_order.account_id(); if ctx.use_trading_proto_v2() { let taker_secret = match generate_secret() { Ok(s) => s.into(), @@ -3129,7 +3202,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { let mut taker_swap_state_machine = TakerSwapStateMachine { - storage: TakerSwapStorage::new(ctx.clone()), + storage: TakerSwapStorage::new(ctx.clone(), account_db_id.as_deref()), abortable_system: ctx .abortable_system .create_subsystem() @@ -3173,6 +3246,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat uuid, now, LEGACY_SWAP_TYPE, + account_db_id.as_deref(), ) .await { @@ -3194,7 +3268,8 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), #[cfg(any(test, feature = "run-docker-tests"))] fail_at, - ); + ) + .await; run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx).await } }; @@ -3374,27 +3449,7 @@ async fn handle_timed_out_taker_orders(ctx: MmArc, ordermatch_ctx: &OrdermatchCo } // transform the timed out taker order to maker - - delete_my_taker_order(ctx.clone(), order.clone(), TakerOrderCancellationReason::ToMaker) - .compat() - .await - .ok(); - let maker_order: MakerOrder = order.into(); - ordermatch_ctx - .maker_orders_ctx - .lock() - .add_order(ctx.weak(), maker_order.clone(), None); - - storage - .save_new_active_maker_order(&maker_order) - .await - .error_log_with_msg("!save_new_active_maker_order"); - if maker_order.save_in_history { - storage - .update_was_taker_in_filtering_history(uuid) - .await - .error_log_with_msg("!update_was_taker_in_filtering_history"); - } + let maker_order = handle_transform_my_taker_order(&ctx, uuid, order, ordermatch_ctx, &storage).await; // notify other peers if let Ok(Some((base, rel))) = find_pair(&ctx, &maker_order.base, &maker_order.rel).await { @@ -3410,6 +3465,39 @@ async fn handle_timed_out_taker_orders(ctx: MmArc, ordermatch_ctx: &OrdermatchCo *my_taker_orders = my_actual_taker_orders; } +/// Transforms a taker order into a maker order, +/// updating relevant contexts and storage. +async fn handle_transform_my_taker_order( + ctx: &MmArc, + uuid: Uuid, + order: TakerOrder, + ordermatch_ctx: &OrdermatchContext, + storage: &MyOrdersStorage, +) -> MakerOrder { + delete_my_taker_order(ctx.clone(), order.clone(), TakerOrderCancellationReason::ToMaker) + .compat() + .await + .ok(); + let maker_order: MakerOrder = order.into(); + ordermatch_ctx + .maker_orders_ctx + .lock() + .add_order(ctx.weak(), maker_order.clone(), None); + + storage + .save_new_active_maker_order(&maker_order) + .await + .error_log_with_msg("!save_new_active_maker_order"); + if maker_order.save_in_history { + storage + .update_was_taker_in_filtering_history(uuid, maker_order.account_id().as_deref()) + .await + .error_log_with_msg("!update_was_taker_in_filtering_history"); + }; + + maker_order +} + /// # Safety /// /// The function locks the [`OrdermatchContext::my_maker_orders`] mutex. @@ -3418,6 +3506,26 @@ async fn check_balance_for_maker_orders(ctx: MmArc, ordermatch_ctx: &OrdermatchC for (uuid, order) in my_maker_orders { let order = order.lock().await; + // validate other coin's account id + if let Err(err) = order.validate_other_coin_account_id(&ctx).await { + warn!("{err:?} while validating other_coin account id for maker order: {uuid}"); + let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&uuid); + + // This checks that the order hasn't been removed by another process + if removed_order_mutex.is_some() { + maker_order_cancelled_p2p_notify(ctx.clone(), &order); + delete_my_maker_order( + ctx.clone(), + order.clone(), + MakerOrderCancellationReason::OtherCoinIdMisMatch, + ) + .compat() + .await + .ok(); + } + continue; + }; + if order.available_amount() >= order.min_base_vol || order.has_ongoing_matches() { continue; } @@ -3936,7 +4044,7 @@ pub async fn lp_auto_buy( if let Some(timeout) = input.timeout { order_builder = order_builder.with_timeout(timeout); } - let order = try_s!(order_builder.build()); + let order = try_s!(order_builder.build().await); let request_orderbook = false; try_s!( @@ -4679,7 +4787,7 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result { pub async fn order_status(ctx: MmArc, req: Json) -> Result>, String> { let req: OrderStatusReq = try_s!(json::from_value(req)); - + let db_id: Option = None; // TODO let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(&ctx)); - let storage = MyOrdersStorage::new(ctx.clone()); let maybe_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().get_order(&req.uuid).cloned(); if let Some(order_mutex) = maybe_order_mutex { @@ -4938,22 +5045,29 @@ pub async fn order_status(ctx: MmArc, req: Json) -> Result>, St .map_err(|e| ERRL!("{}", e)); } - let order = try_s!(storage.load_order_from_history(req.uuid).await); - let cancellation_reason = &try_s!(storage.select_order_status(req.uuid).await); + let storage = MyOrdersStorage::new(ctx.clone()); + if let (Ok(order), Ok(cancellation_reason)) = ( + storage.load_order_from_history(req.uuid, db_id.as_deref()).await, + &storage.select_order_status(req.uuid, db_id.as_deref()).await, + ) { + let res = json!(OrderForRpcWithCancellationReason { + order: OrderForRpc::from(&order), + cancellation_reason, + }); - let res = json!(OrderForRpcWithCancellationReason { - order: OrderForRpc::from(&order), - cancellation_reason, - }); - Response::builder() - .body(json::to_vec(&res).expect("Serialization failed")) - .map_err(|e| ERRL!("{}", e)) + return Response::builder() + .body(json::to_vec(&res).expect("Serialization failed")) + .map_err(|e| ERRL!("{}", e)); + }; + + Err("No orders found across databases".to_string()) } #[derive(Display)] pub enum MakerOrderCancellationReason { Fulfilled, InsufficientBalance, + OtherCoinIdMisMatch, Cancelled, } @@ -4965,7 +5079,7 @@ pub enum TakerOrderCancellationReason { Cancelled, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct MyOrdersFilter { pub order_type: Option, pub initial_action: Option, @@ -5006,6 +5120,13 @@ impl Order { Order::Taker(taker) => taker.request.uuid, } } + + pub fn account_id(&self) -> &Option { + match self { + Order::Maker(maker) => maker.account_id(), + Order::Taker(taker) => taker.account_id(), + } + } } #[derive(Serialize)] @@ -5041,69 +5162,68 @@ pub struct FilteringOrder { /// Returns *all* uuids of swaps, which match the selected filter. pub async fn orders_history_by_filter(ctx: MmArc, req: Json) -> Result>, String> { - let storage = MyOrdersStorage::new(ctx.clone()); - + let mut results = vec![]; + let db_ids = try_s!(find_unique_account_ids_active(&ctx).await); let filter: MyOrdersFilter = try_s!(json::from_value(req)); - let db_result = try_s!(storage.select_orders_by_filter(&filter, None).await); - - let mut warnings = vec![]; - let rpc_orders = if filter.include_details { - let mut vec = Vec::with_capacity(db_result.orders.len()); - for order in db_result.orders.iter() { - let uuid = match Uuid::parse_str(order.uuid.as_str()) { - Ok(uuid) => uuid, - Err(e) => { - let warning = format!( - "Order details for Uuid {} were skipped because uuid could not be parsed", - order.uuid - ); - log::warn!("{}, error {}", warning, e); - warnings.push(UuidParseError { - uuid: order.uuid.clone(), - warning, - }); + for db_id in db_ids { + let storage = MyOrdersStorage::new(ctx.clone()); + let db_result = try_s!(storage.select_orders_by_filter(&filter, None, Some(&db_id)).await); + let mut warnings = vec![]; + + if filter.include_details { + let mut vec = Vec::with_capacity(db_result.orders.len()); + for order in db_result.orders.iter() { + let uuid = match Uuid::parse_str(order.uuid.as_str()) { + Ok(uuid) => uuid, + Err(e) => { + let warning = format!( + "Order details for Uuid {} were skipped because uuid could not be parsed", + order.uuid + ); + warn!("{}, error {}", warning, e); + warnings.push(UuidParseError { + uuid: order.uuid.clone(), + warning, + }); + continue; + }, + }; + + if let Ok(order) = storage.load_order_from_history(uuid, Some(&db_id)).await { + vec.push(order); continue; - }, - }; + } - if let Ok(order) = storage.load_order_from_history(uuid).await { - vec.push(order); - continue; - } + let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(&ctx)); + if order.order_type == "Maker" { + let maybe_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().get_order(&uuid).cloned(); + if let Some(maker_order_mutex) = maybe_order_mutex { + let maker_order = maker_order_mutex.lock().await.clone(); + vec.push(Order::Maker(maker_order)); + } + continue; + } - let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(&ctx)); - if order.order_type == "Maker" { - let maybe_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().get_order(&uuid).cloned(); - if let Some(maker_order_mutex) = maybe_order_mutex { - let maker_order = maker_order_mutex.lock().await.clone(); - vec.push(Order::Maker(maker_order)); + let taker_orders = ordermatch_ctx.my_taker_orders.lock().await; + if let Some(taker_order) = taker_orders.get(&uuid) { + vec.push(Order::Taker(taker_order.to_owned())); } - continue; } - let taker_orders = ordermatch_ctx.my_taker_orders.lock().await; - if let Some(taker_order) = taker_orders.get(&uuid) { - vec.push(Order::Taker(taker_order.to_owned())); - } + let details: Vec<_> = vec.iter().map(OrderForRpc::from).collect(); + results.push(json!({ + "result": { + "orders": db_result.orders, + "details": details, + "found_records": db_result.total_count, + "warnings": warnings, + "db_id": db_id + } + })); } - vec - } else { - vec![] - }; - - let details: Vec<_> = rpc_orders.iter().map(OrderForRpc::from).collect(); - - let json = json!({ - "result": { - "orders": db_result.orders, - "details": details, - "found_records": db_result.total_count, - "warnings": warnings, - }}); - - let res = try_s!(json::to_vec(&json)); + } - Ok(try_s!(Response::builder().body(res))) + Ok(try_s!(Response::builder().body(try_s!(json::to_vec(&results))))) } #[derive(Deserialize)] @@ -5337,27 +5457,33 @@ pub async fn my_orders(ctx: MmArc) -> Result>, String> { } #[cfg(not(target_arch = "wasm32"))] -pub fn my_maker_orders_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("ORDERS").join("MY").join("MAKER") } +pub fn my_maker_orders_dir(ctx: &MmArc, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id).join("ORDERS").join("MY").join("MAKER") +} #[cfg(not(target_arch = "wasm32"))] -fn my_taker_orders_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("ORDERS").join("MY").join("TAKER") } +fn my_taker_orders_dir(ctx: &MmArc, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id).join("ORDERS").join("MY").join("TAKER") +} #[cfg(not(target_arch = "wasm32"))] -fn my_orders_history_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("ORDERS").join("MY").join("HISTORY") } +fn my_orders_history_dir(ctx: &MmArc, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id).join("ORDERS").join("MY").join("HISTORY") +} #[cfg(not(target_arch = "wasm32"))] -pub fn my_maker_order_file_path(ctx: &MmArc, uuid: &Uuid) -> PathBuf { - my_maker_orders_dir(ctx).join(format!("{}.json", uuid)) +pub fn my_maker_order_file_path(ctx: &MmArc, uuid: &Uuid, db_id: Option<&str>) -> PathBuf { + my_maker_orders_dir(ctx, db_id).join(format!("{}.json", uuid)) } #[cfg(not(target_arch = "wasm32"))] -fn my_taker_order_file_path(ctx: &MmArc, uuid: &Uuid) -> PathBuf { - my_taker_orders_dir(ctx).join(format!("{}.json", uuid)) +fn my_taker_order_file_path(ctx: &MmArc, uuid: &Uuid, db_id: Option<&str>) -> PathBuf { + my_taker_orders_dir(ctx, db_id).join(format!("{}.json", uuid)) } #[cfg(not(target_arch = "wasm32"))] -fn my_order_history_file_path(ctx: &MmArc, uuid: &Uuid) -> PathBuf { - my_orders_history_dir(ctx).join(format!("{}.json", uuid)) +fn my_order_history_file_path(ctx: &MmArc, uuid: &Uuid, db_id: Option<&str>) -> PathBuf { + my_orders_history_dir(ctx, db_id).join(format!("{}.json", uuid)) } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -5374,12 +5500,19 @@ pub struct HistoricalOrder { conf_settings: Option, } -pub async fn orders_kick_start(ctx: &MmArc) -> Result, String> { +/// Initializes and restarts the order matching system by loading saved orders from storage +/// and populating the order contexts. +/// +/// # Notes +/// - For maker orders, only those with matching `rel_coin_account_id` are processed. +/// - For taker orders, only those with matching `base_coin_account_id` are processed. +/// - This ensures account consistency for both maker and taker orders. +/// - Invalid orders are silently skipped and not added to the order contexts. +pub async fn orders_kick_start(ctx: &MmArc, db_id: Option<&str>) -> Result, String> { let ordermatch_ctx = try_s!(OrdermatchContext::from_ctx(ctx)); - let storage = MyOrdersStorage::new(ctx.clone()); - let saved_maker_orders = try_s!(storage.load_active_maker_orders().await); - let saved_taker_orders = try_s!(storage.load_active_taker_orders().await); + let saved_maker_orders = try_s!(storage.load_active_maker_orders(db_id).await); + let saved_taker_orders = try_s!(storage.load_active_taker_orders(db_id).await); let mut coins = HashSet::with_capacity((saved_maker_orders.len() * 2) + (saved_taker_orders.len() * 2)); { @@ -5393,10 +5526,28 @@ pub async fn orders_kick_start(ctx: &MmArc) -> Result, String> { let mut taker_orders = ordermatch_ctx.my_taker_orders.lock().await; for order in saved_taker_orders { + if let Err(err) = order.validate_other_coin_account_id(ctx).await { + warn!( + "{err:?} while validating other_coin account id for TakerOrder: {:?}", + order.request.uuid + ); + + if !order.matches.is_empty() || order.order_type != OrderType::GoodTillCancelled { + delete_my_taker_order(ctx.clone(), order, TakerOrderCancellationReason::TimedOut) + .compat() + .await + .ok(); + continue; + } + + handle_transform_my_taker_order(ctx, order.request.uuid, order, &ordermatch_ctx, &storage).await; + continue; + } coins.insert(order.request.base.clone()); coins.insert(order.request.rel.clone()); taker_orders.insert(order.request.uuid, order); } + Ok(coins) } @@ -5859,3 +6010,37 @@ fn orderbook_address( CoinProtocol::SIA { .. } => MmError::err(OrderbookAddrErr::CoinIsNotSupported(coin.to_owned())), } } + +/// Tries to find a coin and make sure it's activated with the right account ID. +/// If it doesn't work at first, it'll keep trying for a bit. +async fn validate_other_coin_account_id_impl( + ctx: &MmArc, + other_coin_ticker: &str, + other_coin_account_id: &Option, + order_type: &str, +) -> Result<(), String> { + for attempt in 0..=VALIDATE_OTHER_COIN_TIMEOUT { + if let Some(coin) = try_s!(lp_coinfind_any(ctx, other_coin_ticker).await) { + if &coin.inner.account_db_id().await == other_coin_account_id { + info!("Correct {order_type} coin found: {}", coin.inner.ticker()); + return Ok(()); + } + } + + if attempt < VALIDATE_OTHER_COIN_TIMEOUT { + info!( + "{order_type}: validate_other_coin_account_id attempt {} failed: Coin {} not found or not activated with pubkey: {}", + attempt + 1, + other_coin_ticker, + other_coin_account_id.as_deref().unwrap_or(&ctx.default_db_id()) + ); + Timer::sleep(2.).await + } + } + + Err(format!( + "{order_type}: Coin {} not found or not activated with the expected account key after {} attempts", + other_coin_ticker, + VALIDATE_OTHER_COIN_TIMEOUT + 1 + )) +} diff --git a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs index 05322325a5..e24f951596 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs @@ -76,10 +76,11 @@ pub fn delete_my_taker_order(ctx: MmArc, order: TakerOrder, reason: TakerOrderCa let fut = async move { let uuid = order.request.uuid; let save_in_history = order.save_in_history; + let db_id = order.account_id().clone(); let storage = MyOrdersStorage::new(ctx); storage - .delete_active_taker_order(uuid) + .delete_active_taker_order(uuid, db_id.as_deref()) .await .error_log_with_msg("!delete_active_taker_order"); @@ -97,7 +98,7 @@ pub fn delete_my_taker_order(ctx: MmArc, order: TakerOrder, reason: TakerOrderCa if save_in_history { storage - .update_order_status_in_filtering_history(uuid, reason.to_string()) + .update_order_status_in_filtering_history(uuid, reason.to_string(), db_id.as_deref()) .await .error_log_with_msg("!update_order_status_in_filtering_history"); } @@ -115,12 +116,15 @@ pub fn delete_my_maker_order(ctx: MmArc, order: MakerOrder, reason: MakerOrderCa let storage = MyOrdersStorage::new(ctx); if order_to_save.was_updated() { - if let Ok(order_from_file) = storage.load_active_maker_order(order_to_save.uuid).await { + if let Ok(order_from_file) = storage + .load_active_maker_order(order_to_save.uuid, order_to_save.account_id().as_deref()) + .await + { order_to_save = order_from_file; } } storage - .delete_active_maker_order(uuid) + .delete_active_maker_order(uuid, order_to_save.account_id().as_deref()) .await .error_log_with_msg("!delete_active_maker_order"); @@ -130,7 +134,11 @@ pub fn delete_my_maker_order(ctx: MmArc, order: MakerOrder, reason: MakerOrderCa .await .error_log_with_msg("!save_order_in_history"); storage - .update_order_status_in_filtering_history(uuid, reason.to_string()) + .update_order_status_in_filtering_history( + uuid, + reason.to_string(), + order_to_save.account_id().as_deref(), + ) .await .error_log_with_msg("!update_order_status_in_filtering_history"); } @@ -141,11 +149,11 @@ pub fn delete_my_maker_order(ctx: MmArc, order: MakerOrder, reason: MakerOrderCa #[async_trait] pub trait MyActiveOrders { - async fn load_active_maker_orders(&self) -> MyOrdersResult>; + async fn load_active_maker_orders(&self, db_id: Option<&str>) -> MyOrdersResult>; - async fn load_active_maker_order(&self, uuid: Uuid) -> MyOrdersResult; + async fn load_active_maker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult; - async fn load_active_taker_orders(&self) -> MyOrdersResult>; + async fn load_active_taker_orders(&self, db_id: Option<&str>) -> MyOrdersResult>; async fn save_new_active_order(&self, order: &Order) -> MyOrdersResult<()> { match order { @@ -158,9 +166,9 @@ pub trait MyActiveOrders { async fn save_new_active_taker_order(&self, order: &TakerOrder) -> MyOrdersResult<()>; - async fn delete_active_maker_order(&self, uuid: Uuid) -> MyOrdersResult<()>; + async fn delete_active_maker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()>; - async fn delete_active_taker_order(&self, uuid: Uuid) -> MyOrdersResult<()>; + async fn delete_active_taker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()>; async fn update_active_maker_order(&self, order: &MakerOrder) -> MyOrdersResult<()>; @@ -171,7 +179,7 @@ pub trait MyActiveOrders { pub trait MyOrdersHistory { async fn save_order_in_history(&self, order: &Order) -> MyOrdersResult<()>; - async fn load_order_from_history(&self, uuid: Uuid) -> MyOrdersResult; + async fn load_order_from_history(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult; } #[async_trait] @@ -180,9 +188,10 @@ pub trait MyOrdersFilteringHistory { &self, filter: &MyOrdersFilter, paging_options: Option<&PagingOptions>, + db_id: Option<&str>, ) -> MyOrdersResult; - async fn select_order_status(&self, uuid: Uuid) -> MyOrdersResult; + async fn select_order_status(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult; async fn save_order_in_filtering_history(&self, order: &Order) -> MyOrdersResult<()> { match order { @@ -197,9 +206,14 @@ pub trait MyOrdersFilteringHistory { async fn update_maker_order_in_filtering_history(&self, order: &MakerOrder) -> MyOrdersResult<()>; - async fn update_order_status_in_filtering_history(&self, uuid: Uuid, status: String) -> MyOrdersResult<()>; + async fn update_order_status_in_filtering_history( + &self, + uuid: Uuid, + status: String, + db_id: Option<&str>, + ) -> MyOrdersResult<()>; - async fn update_was_taker_in_filtering_history(&self, uuid: Uuid) -> MyOrdersResult<()>; + async fn update_was_taker_in_filtering_history(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()>; } #[cfg(not(target_arch = "wasm32"))] @@ -237,45 +251,45 @@ mod native_impl { #[async_trait] impl MyActiveOrders for MyOrdersStorage { - async fn load_active_maker_orders(&self) -> MyOrdersResult> { - let dir_path = my_maker_orders_dir(&self.ctx); + async fn load_active_maker_orders(&self, db_id: Option<&str>) -> MyOrdersResult> { + let dir_path = my_maker_orders_dir(&self.ctx, db_id); Ok(read_dir_json(&dir_path).await?) } - async fn load_active_maker_order(&self, uuid: Uuid) -> MyOrdersResult { - let path = my_maker_order_file_path(&self.ctx, &uuid); + async fn load_active_maker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult { + let path = my_maker_order_file_path(&self.ctx, &uuid, db_id); read_json(&path) .await? .or_mm_err(|| MyOrdersError::NoSuchOrder { uuid }) } - async fn load_active_taker_orders(&self) -> MyOrdersResult> { - let dir_path = my_taker_orders_dir(&self.ctx); + async fn load_active_taker_orders(&self, db_id: Option<&str>) -> MyOrdersResult> { + let dir_path = my_taker_orders_dir(&self.ctx, db_id); Ok(read_dir_json(&dir_path).await?) } async fn save_new_active_maker_order(&self, order: &MakerOrder) -> MyOrdersResult<()> { - let path = my_maker_order_file_path(&self.ctx, &order.uuid); + let path = my_maker_order_file_path(&self.ctx, &order.uuid, order.account_id().as_deref()); write_json(order, &path, USE_TMP_FILE).await?; Ok(()) } async fn save_new_active_taker_order(&self, order: &TakerOrder) -> MyOrdersResult<()> { - let path = my_taker_order_file_path(&self.ctx, &order.request.uuid); + let path = my_taker_order_file_path(&self.ctx, &order.request.uuid, order.account_id().as_deref()); write_json(order, &path, USE_TMP_FILE).await?; Ok(()) } - async fn delete_active_maker_order(&self, uuid: Uuid) -> MyOrdersResult<()> { - let path = my_maker_order_file_path(&self.ctx, &uuid); + async fn delete_active_maker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()> { + let path = my_maker_order_file_path(&self.ctx, &uuid, db_id); remove_file_async(&path) .await .mm_err(|e| MyOrdersError::ErrorSaving(e.to_string()))?; Ok(()) } - async fn delete_active_taker_order(&self, uuid: Uuid) -> MyOrdersResult<()> { - let path = my_taker_order_file_path(&self.ctx, &uuid); + async fn delete_active_taker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()> { + let path = my_taker_order_file_path(&self.ctx, &uuid, db_id); remove_file_async(&path) .await .mm_err(|e| MyOrdersError::ErrorSaving(e.to_string()))?; @@ -294,13 +308,13 @@ mod native_impl { #[async_trait] impl MyOrdersHistory for MyOrdersStorage { async fn save_order_in_history(&self, order: &Order) -> MyOrdersResult<()> { - let path = my_order_history_file_path(&self.ctx, &order.uuid()); + let path = my_order_history_file_path(&self.ctx, &order.uuid(), order.account_id().as_deref()); write_json(order, &path, USE_TMP_FILE).await?; Ok(()) } - async fn load_order_from_history(&self, uuid: Uuid) -> MyOrdersResult { - let path = my_order_history_file_path(&self.ctx, &uuid); + async fn load_order_from_history(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult { + let path = my_order_history_file_path(&self.ctx, &uuid, db_id); read_json(&path) .await? .or_mm_err(|| MyOrdersError::NoSuchOrder { uuid }) @@ -313,14 +327,20 @@ mod native_impl { &self, filter: &MyOrdersFilter, paging_options: Option<&PagingOptions>, + db_id: Option<&str>, ) -> MyOrdersResult { - select_orders_by_filter(&self.ctx.sqlite_connection(), filter, paging_options) - .map_to_mm(|e| MyOrdersError::ErrorLoading(e.to_string())) + let filter = filter.clone(); + let paging_options = paging_options.cloned(); + self.ctx.run_sql_query(db_id, move |conn| { + select_orders_by_filter(&conn, &filter, paging_options) + .map_to_mm(|e| MyOrdersError::ErrorLoading(e.to_string())) + }) } - async fn select_order_status(&self, uuid: Uuid) -> MyOrdersResult { - select_status_by_uuid(&self.ctx.sqlite_connection(), &uuid) - .map_to_mm(|e| MyOrdersError::ErrorLoading(e.to_string())) + async fn select_order_status(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult { + self.ctx.run_sql_query(db_id, move |conn| { + select_status_by_uuid(&conn, &uuid).map_to_mm(|e| MyOrdersError::ErrorLoading(e.to_string())) + }) } async fn save_maker_order_in_filtering_history(&self, order: &MakerOrder) -> MyOrdersResult<()> { @@ -336,12 +356,17 @@ mod native_impl { update_maker_order(&self.ctx, order.uuid, order).map_to_mm(|e| MyOrdersError::ErrorSaving(e.to_string())) } - async fn update_order_status_in_filtering_history(&self, uuid: Uuid, status: String) -> MyOrdersResult<()> { - update_order_status(&self.ctx, uuid, status).map_to_mm(|e| MyOrdersError::ErrorSaving(e.to_string())) + async fn update_order_status_in_filtering_history( + &self, + uuid: Uuid, + status: String, + db_id: Option<&str>, + ) -> MyOrdersResult<()> { + update_order_status(&self.ctx, uuid, status, db_id).map_to_mm(|e| MyOrdersError::ErrorSaving(e.to_string())) } - async fn update_was_taker_in_filtering_history(&self, uuid: Uuid) -> MyOrdersResult<()> { - update_was_taker(&self.ctx, uuid).map_to_mm(|e| MyOrdersError::ErrorSaving(e.to_string())) + async fn update_was_taker_in_filtering_history(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()> { + update_was_taker(&self.ctx, uuid, db_id).map_to_mm(|e| MyOrdersError::ErrorSaving(e.to_string())) } } } @@ -401,8 +426,8 @@ mod wasm_impl { #[async_trait] impl MyActiveOrders for MyOrdersStorage { - async fn load_active_maker_orders(&self) -> MyOrdersResult> { - let db = self.ctx.ordermatch_db().await?; + async fn load_active_maker_orders(&self, db_id: Option<&str>) -> MyOrdersResult> { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let maker_orders = table.get_all_items().await?; @@ -412,8 +437,8 @@ mod wasm_impl { .collect()) } - async fn load_active_maker_order(&self, uuid: Uuid) -> MyOrdersResult { - let db = self.ctx.ordermatch_db().await?; + async fn load_active_maker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -424,8 +449,8 @@ mod wasm_impl { .or_mm_err(|| MyOrdersError::NoSuchOrder { uuid }) } - async fn load_active_taker_orders(&self) -> MyOrdersResult> { - let db = self.ctx.ordermatch_db().await?; + async fn load_active_taker_orders(&self, db_id: Option<&str>) -> MyOrdersResult> { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let maker_orders = table.get_all_items().await?; @@ -436,7 +461,7 @@ mod wasm_impl { } async fn save_new_active_maker_order(&self, order: &MakerOrder) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -449,7 +474,7 @@ mod wasm_impl { } async fn save_new_active_taker_order(&self, order: &TakerOrder) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -461,16 +486,16 @@ mod wasm_impl { Ok(()) } - async fn delete_active_maker_order(&self, uuid: Uuid) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + async fn delete_active_maker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()> { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; table.delete_item_by_unique_index("uuid", uuid).await?; Ok(()) } - async fn delete_active_taker_order(&self, uuid: Uuid) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + async fn delete_active_taker_order(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()> { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; table.delete_item_by_unique_index("uuid", uuid).await?; @@ -478,7 +503,7 @@ mod wasm_impl { } async fn update_active_maker_order(&self, order: &MakerOrder) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -491,7 +516,7 @@ mod wasm_impl { } async fn update_active_taker_order(&self, order: &TakerOrder) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -509,7 +534,7 @@ mod wasm_impl { #[async_trait] impl MyOrdersHistory for MyOrdersStorage { async fn save_order_in_history(&self, order: &Order) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -521,8 +546,8 @@ mod wasm_impl { Ok(()) } - async fn load_order_from_history(&self, uuid: Uuid) -> MyOrdersResult { - let db = self.ctx.ordermatch_db().await?; + async fn load_order_from_history(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -540,6 +565,7 @@ mod wasm_impl { &self, _filter: &MyOrdersFilter, _paging_options: Option<&PagingOptions>, + _db_id: Option<&str>, ) -> MyOrdersResult { warn!("'select_orders_by_filter' not supported in WASM yet"); MmError::err(MyOrdersError::InternalError( @@ -547,8 +573,8 @@ mod wasm_impl { )) } - async fn select_order_status(&self, uuid: Uuid) -> MyOrdersResult { - let db = self.ctx.ordermatch_db().await?; + async fn select_order_status(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -562,7 +588,7 @@ mod wasm_impl { async fn save_maker_order_in_filtering_history(&self, order: &MakerOrder) -> MyOrdersResult<()> { let item = maker_order_to_filtering_history_item(order, "Created".to_owned(), false)?; - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; table.add_item(&item).await?; @@ -572,7 +598,7 @@ mod wasm_impl { async fn save_taker_order_in_filtering_history(&self, order: &TakerOrder) -> MyOrdersResult<()> { let item = taker_order_to_filtering_history_item(order, "Created".to_owned())?; - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; table.add_item(&item).await?; @@ -580,7 +606,7 @@ mod wasm_impl { } async fn update_maker_order_in_filtering_history(&self, order: &MakerOrder) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + let db = self.ctx.ordermatch_db(order.account_id().as_deref()).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; // get the previous item to see if the order was taker @@ -594,8 +620,13 @@ mod wasm_impl { Ok(()) } - async fn update_order_status_in_filtering_history(&self, uuid: Uuid, status: String) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + async fn update_order_status_in_filtering_history( + &self, + uuid: Uuid, + status: String, + db_id: Option<&str>, + ) -> MyOrdersResult<()> { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -609,8 +640,8 @@ mod wasm_impl { Ok(()) } - async fn update_was_taker_in_filtering_history(&self, uuid: Uuid) -> MyOrdersResult<()> { - let db = self.ctx.ordermatch_db().await?; + async fn update_was_taker_in_filtering_history(&self, uuid: Uuid, db_id: Option<&str>) -> MyOrdersResult<()> { + let db = self.ctx.ordermatch_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -724,6 +755,8 @@ mod tests { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, } } @@ -752,12 +785,14 @@ mod tests { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, } } async fn get_all_items(ctx: &MmArc) -> Vec { let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).unwrap(); - let db = ordermatch_ctx.ordermatch_db().await.unwrap(); + let db = ordermatch_ctx.ordermatch_db(None).await.unwrap(); let transaction = db.transaction().await.unwrap(); let table = transaction.table::
().await.unwrap(); table @@ -790,12 +825,12 @@ mod tests { .unwrap(); let actual_active_maker_orders = storage - .load_active_taker_orders() + .load_active_taker_orders(None) .await .expect("!MyOrdersStorage::load_active_taker_orders"); assert!(actual_active_maker_orders.is_empty()); let actual_history_order = storage - .load_order_from_history(maker1.uuid) + .load_order_from_history(maker1.uuid, None) .await .expect("!MyOrdersStorage::load_order_from_history"); assert_eq!(actual_history_order, Order::Maker(maker1.clone())); @@ -827,12 +862,12 @@ mod tests { .unwrap(); let actual_active_taker_orders = storage - .load_active_taker_orders() + .load_active_taker_orders(None) .await .expect("!MyOrdersStorage::load_active_taker_orders"); assert!(actual_active_taker_orders.is_empty()); let actual_history_order = storage - .load_order_from_history(taker1.request.uuid) + .load_order_from_history(taker1.request.uuid, None) .await .expect("!MyOrdersStorage::load_order_from_history"); assert_eq!(actual_history_order, Order::Taker(taker1.clone())); @@ -851,11 +886,14 @@ mod tests { .unwrap(); let actual_active_taker_orders = storage - .load_active_taker_orders() + .load_active_taker_orders(None) .await .expect("!MyOrdersStorage::load_active_taker_orders"); assert!(actual_active_taker_orders.is_empty()); - let error = storage.load_order_from_history(taker2.request.uuid).await.expect_err( + let error = storage + .load_order_from_history(taker2.request.uuid, None) + .await + .expect_err( "!MyOrdersStorage::load_order_from_history should have failed with the 'MyOrdersError::NoSuchOrder' error", ); assert_eq!(error.into_inner(), MyOrdersError::NoSuchOrder { @@ -890,7 +928,7 @@ mod tests { .expect("!MyOrdersStorage::update_active_maker_order"); let actual_maker_orders: Vec<_> = storage - .load_active_maker_orders() + .load_active_maker_orders(None) .await .expect("!MyOrdersStorage::load_active_maker_orders") .into_iter() @@ -903,7 +941,7 @@ mod tests { assert_eq!(actual_maker_orders, expected_maker_orders); let actual_taker_orders: Vec<_> = storage - .load_active_taker_orders() + .load_active_taker_orders(None) .await .expect("!MyOrdersStorage::load_active_taker_orders"); let expected_taker_orders = vec![taker1]; @@ -937,7 +975,7 @@ mod tests { .expect("!MyOrdersStorage::save_taker_order_in_filtering_history"); storage - .update_order_status_in_filtering_history(taker1.request.uuid, "MyCustomStatus".to_owned()) + .update_order_status_in_filtering_history(taker1.request.uuid, "MyCustomStatus".to_owned(), None) .await .expect("!MyOrdersStorage::update_order_status_in_filtering_history"); @@ -948,7 +986,7 @@ mod tests { .expect("MyOrdersStorage::update_maker_order_in_filtering_history"); storage - .update_was_taker_in_filtering_history(maker1.uuid) + .update_was_taker_in_filtering_history(maker1.uuid, None) .await .expect("MyOrdersStorage::update_was_taker_in_filtering_history"); @@ -970,14 +1008,14 @@ mod tests { assert_eq!(actual_items, expected_items); let taker1_status = storage - .select_order_status(taker1.request.uuid) + .select_order_status(taker1.request.uuid, None) .await .expect("!MyOrdersStorage::select_order_status"); assert_eq!(taker1_status, "MyCustomStatus"); let unknown_uuid = new_uuid(); let err = storage - .select_order_status(unknown_uuid) + .select_order_status(unknown_uuid, None) .await .expect_err("!MyOrdersStorage::select_order_status should have failed"); assert_eq!(err.into_inner(), MyOrdersError::NoSuchOrder { uuid: unknown_uuid }); diff --git a/mm2src/mm2_main/src/lp_stats.rs b/mm2src/mm2_main/src/lp_stats.rs index e86e742457..1f311facb1 100644 --- a/mm2src/mm2_main/src/lp_stats.rs +++ b/mm2src/mm2_main/src/lp_stats.rs @@ -1,8 +1,13 @@ /// The module is responsible for mm2 network stats collection /// +use crate::lp_network::{add_reserved_peer_addresses, lp_network_ports, request_peers, NetIdError, P2PRequest, + ParseAddressError, PeerDecodedResponse}; +use coins::find_unique_account_ids_active; +#[cfg(not(target_arch = "wasm32"))] use common::async_blocking; use common::executor::{SpawnFuture, Timer}; use common::{log, HttpStatusCode}; use derive_more::Display; +use futures::future::try_join_all; use futures::lock::Mutex as AsyncMutex; use http::StatusCode; use mm2_core::mm_ctx::{from_ctx, MmArc}; @@ -10,11 +15,8 @@ use mm2_err_handle::prelude::*; use mm2_libp2p::{encode_message, NetworkInfo, PeerId, RelayAddress, RelayAddressError}; use serde_json::{self as json, Value as Json}; use std::collections::{HashMap, HashSet}; -use std::sync::Arc; - -use crate::lp_network::{add_reserved_peer_addresses, lp_network_ports, request_peers, NetIdError, P2PRequest, - ParseAddressError, PeerDecodedResponse}; use std::str::FromStr; +use std::sync::Arc; pub type NodeVersionResult = Result>; @@ -37,6 +39,8 @@ pub enum NodeVersionError { CurrentlyStopping, #[display(fmt = "start_version_stat_collection is not running")] NotRunning, + #[display(fmt = "Invalid request: {}", _0)] + InternalError(String), } impl HttpStatusCode for NodeVersionError { @@ -49,7 +53,9 @@ impl HttpStatusCode for NodeVersionError { | NodeVersionError::AlreadyRunning | NodeVersionError::CurrentlyStopping | NodeVersionError::NotRunning => StatusCode::METHOD_NOT_ALLOWED, - NodeVersionError::DatabaseError(_) => StatusCode::INTERNAL_SERVER_ERROR, + NodeVersionError::DatabaseError(_) | NodeVersionError::InternalError(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, } } } @@ -70,7 +76,7 @@ impl From for NodeVersionError { fn from(e: RelayAddressError) -> Self { NodeVersionError::InvalidAddress(e.to_string()) } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct NodeInfo { pub name: String, pub address: String, @@ -89,32 +95,42 @@ pub struct NodeVersionStat { fn insert_node_info_to_db(_ctx: &MmArc, _node_info: &NodeInfo) -> Result<(), String> { Ok(()) } #[cfg(not(target_arch = "wasm32"))] -fn insert_node_info_to_db(ctx: &MmArc, node_info: &NodeInfo) -> Result<(), String> { - crate::database::stats_nodes::insert_node_info(ctx, node_info).map_err(|e| e.to_string()) +fn insert_node_info_to_db(ctx: &MmArc, node_info: NodeInfo, db_id: Option<&str>) -> Result<(), String> { + crate::database::stats_nodes::insert_node_info(ctx, node_info, db_id).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] -fn insert_node_version_stat_to_db(_ctx: &MmArc, _node_version_stat: NodeVersionStat) -> Result<(), String> { Ok(()) } +fn insert_node_version_stat_to_db( + _ctx: &MmArc, + _node_version_stat: NodeVersionStat, + _db_id: Option<&str>, +) -> Result<(), String> { + Ok(()) +} #[cfg(not(target_arch = "wasm32"))] -fn insert_node_version_stat_to_db(ctx: &MmArc, node_version_stat: NodeVersionStat) -> Result<(), String> { - crate::database::stats_nodes::insert_node_version_stat(ctx, node_version_stat).map_err(|e| e.to_string()) +fn insert_node_version_stat_to_db( + ctx: &MmArc, + node_version_stat: NodeVersionStat, + db_id: Option<&str>, +) -> Result<(), String> { + crate::database::stats_nodes::insert_node_version_stat(ctx, node_version_stat, db_id).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] fn delete_node_info_from_db(_ctx: &MmArc, _name: String) -> Result<(), String> { Ok(()) } #[cfg(not(target_arch = "wasm32"))] -fn delete_node_info_from_db(ctx: &MmArc, name: String) -> Result<(), String> { - crate::database::stats_nodes::delete_node_info(ctx, name).map_err(|e| e.to_string()) +fn delete_node_info_from_db(ctx: &MmArc, name: String, db_id: Option<&str>) -> Result<(), String> { + crate::database::stats_nodes::delete_node_info(ctx, name, db_id).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] fn select_peers_addresses_from_db(_ctx: &MmArc) -> Result, String> { Ok(Vec::new()) } #[cfg(not(target_arch = "wasm32"))] -fn select_peers_addresses_from_db(ctx: &MmArc) -> Result, String> { - crate::database::stats_nodes::select_peers_addresses(ctx).map_err(|e| e.to_string()) +fn select_peers_addresses_from_db(ctx: &MmArc, db_id: Option<&str>) -> Result, String> { + crate::database::stats_nodes::select_peers_addresses(ctx, db_id).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -142,7 +158,22 @@ pub async fn add_node_to_version_stat(ctx: MmArc, req: Json) -> NodeVersionResul peer_id: node_info.peer_id, }; - insert_node_info_to_db(&ctx, &node_info_with_ipv4_addr).map_to_mm(NodeVersionError::DatabaseError)?; + let db_ids = find_unique_account_ids_active(&ctx) + .await + .map_to_mm(NodeVersionError::InternalError)?; + let futures = db_ids + .iter() + .map(|db_id| { + let ctx = ctx.clone(); + let node_info_with_ipv4_addr = node_info_with_ipv4_addr.clone(); + let db_id = db_id.to_owned(); + async_blocking(move || { + insert_node_info_to_db(&ctx, node_info_with_ipv4_addr, Some(&db_id)) + .map_to_mm(NodeVersionError::DatabaseError) + }) + }) + .collect::>(); + try_join_all(futures).await?; Ok("success".into()) } @@ -158,8 +189,22 @@ pub async fn remove_node_from_version_stat(_ctx: MmArc, _req: Json) -> NodeVersi #[cfg(not(target_arch = "wasm32"))] pub async fn remove_node_from_version_stat(ctx: MmArc, req: Json) -> NodeVersionResult { let node_name: String = json::from_value(req["name"].clone())?; - - delete_node_info_from_db(&ctx, node_name).map_to_mm(NodeVersionError::DatabaseError)?; + let db_ids = find_unique_account_ids_active(&ctx) + .await + .map_to_mm(NodeVersionError::InternalError)?; + + let futures = db_ids + .iter() + .map(|db_id| { + let ctx = ctx.clone(); + let node_name = node_name.clone(); + let db_id = db_id.clone(); + async_blocking(move || { + delete_node_info_from_db(&ctx, node_name, Some(&db_id)).map_to_mm(NodeVersionError::DatabaseError) + }) + }) + .collect::>(); + try_join_all(futures).await?; Ok("success".into()) } @@ -220,21 +265,10 @@ pub async fn start_version_stat_collection(_ctx: MmArc, _req: Json) -> NodeVersi #[cfg(not(target_arch = "wasm32"))] pub async fn start_version_stat_collection(ctx: MmArc, req: Json) -> NodeVersionResult { - let stats_ctx = StatsContext::from_ctx(&ctx).unwrap(); - { - let state = stats_ctx.status.lock().await; - if *state == StatsCollectionStatus::Stopping { - return MmError::err(NodeVersionError::CurrentlyStopping); - } - if *state != StatsCollectionStatus::Stopped { - return MmError::err(NodeVersionError::AlreadyRunning); - } - } - + let db_ids = find_unique_account_ids_active(&ctx) + .await + .map_to_mm(NodeVersionError::InternalError)?; let interval: f64 = json::from_value(req["interval"].clone())?; - - let peers_addresses = select_peers_addresses_from_db(&ctx).map_to_mm(NodeVersionError::DatabaseError)?; - let netid = ctx.conf["netid"].as_u64().unwrap_or(0) as u16; let network_info = if ctx.p2p_in_memory() { NetworkInfo::InMemory @@ -243,26 +277,42 @@ pub async fn start_version_stat_collection(ctx: MmArc, req: Json) -> NodeVersion NetworkInfo::Distributed { network_ports } }; - for (peer_id, address) in peers_addresses { - let peer_id = peer_id - .parse::() - .map_to_mm(|e| NodeVersionError::PeerIdParseError(peer_id, e.to_string()))?; + for db_id in db_ids { + let stats_ctx = StatsContext::from_ctx(&ctx).unwrap(); + { + let state = stats_ctx.status.lock().await; + if *state == StatsCollectionStatus::Stopping { + return MmError::err(NodeVersionError::CurrentlyStopping); + } + if *state != StatsCollectionStatus::Stopped { + return MmError::err(NodeVersionError::AlreadyRunning); + } + } - let relay_addr = RelayAddress::from_str(&address)?; - let multi_address = relay_addr.try_to_multiaddr(network_info)?; + let peers_addresses = + select_peers_addresses_from_db(&ctx, Some(&db_id)).map_to_mm(NodeVersionError::DatabaseError)?; - let addresses = HashSet::from([multi_address]); - add_reserved_peer_addresses(&ctx, peer_id, addresses); - } + for (peer_id, address) in peers_addresses { + let peer_id = peer_id + .parse::() + .map_to_mm(|e| NodeVersionError::PeerIdParseError(peer_id, e.to_string()))?; + + let relay_addr = RelayAddress::from_str(&address)?; + let multi_address = relay_addr.try_to_multiaddr(network_info)?; - let spawner = ctx.spawner(); - spawner.spawn(stat_collection_loop(ctx, interval)); + let addresses = HashSet::from([multi_address]); + add_reserved_peer_addresses(&ctx, peer_id, addresses); + } + + let spawner = ctx.spawner(); + spawner.spawn(stat_collection_loop(ctx.clone(), interval, db_id.to_owned())); + } Ok("success".into()) } #[cfg(not(target_arch = "wasm32"))] -async fn stat_collection_loop(ctx: MmArc, interval: f64) { +async fn stat_collection_loop(ctx: MmArc, interval: f64, db_id: String) { use common::now_sec; use crate::database::stats_nodes::select_peers_names; @@ -290,7 +340,7 @@ async fn stat_collection_loop(ctx: MmArc, interval: f64) { } } - let peers_names = match select_peers_names(&ctx) { + let peers_names = match select_peers_names(&ctx, Some(&db_id)) { Ok(n) => n, Err(e) => { log::error!("Error selecting peers names from db: {}", e); @@ -331,7 +381,7 @@ async fn stat_collection_loop(ctx: MmArc, interval: f64) { timestamp, error: None, }; - if let Err(e) = insert_node_version_stat_to_db(&ctx, node_version_stat) { + if let Err(e) = insert_node_version_stat_to_db(&ctx, node_version_stat, Some(&db_id)) { log::error!("Error inserting node {} version {} into db: {}", name, v, e); }; }, @@ -347,7 +397,7 @@ async fn stat_collection_loop(ctx: MmArc, interval: f64) { timestamp, error: Some(e.clone()), }; - if let Err(e) = insert_node_version_stat_to_db(&ctx, node_version_stat) { + if let Err(e) = insert_node_version_stat_to_db(&ctx, node_version_stat, Some(&db_id)) { log::error!("Error inserting node {} error into db: {}", name, e); }; }, @@ -359,7 +409,7 @@ async fn stat_collection_loop(ctx: MmArc, interval: f64) { timestamp, error: None, }; - if let Err(e) = insert_node_version_stat_to_db(&ctx, node_version_stat) { + if let Err(e) = insert_node_version_stat_to_db(&ctx, node_version_stat, Some(&db_id)) { log::error!("Error inserting no response for node {} into db: {}", name, e); }; }, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index c7665da02e..3e8676d352 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -62,7 +62,8 @@ use crate::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PPro use crate::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; use crate::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; use bitcrypto::{dhash160, sha256}; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; +use coins::{find_unique_account_ids_active, find_unique_account_ids_any, lp_coinfind, lp_coinfind_or_err, + CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::now_sec; use common::time_cache::DuplicateCache; @@ -163,7 +164,7 @@ cfg_wasm32! { use saved_swap::migrate_swaps_data; use swap_wasm_db::{InitDbResult, InitDbError, SwapDb}; - pub type SwapDbLocked<'a> = DbLocked<'a, SwapDb>; + pub type SwapDbLocked = DbLocked; } #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] @@ -526,6 +527,7 @@ struct SwapsContext { impl SwapsContext { /// Obtains a reference to this crate context, creating it if necessary. + #[allow(unused_variables)] fn from_ctx(ctx: &MmArc) -> Result, String> { Ok(try_s!(from_ctx(&ctx.swaps_ctx, move || { Ok(SwapsContext { @@ -538,8 +540,9 @@ impl SwapsContext { TAKER_SWAP_ENTRY_TIMEOUT_SEC, ))), locked_amounts: Mutex::new(HashMap::new()), + // Using None for db_id here won't matter much since calling `SwapsContext::swap_db(db_id)` will use the provided db_id. #[cfg(target_arch = "wasm32")] - swap_db: ConstructibleDb::new(ctx), + swap_db: ConstructibleDb::new(ctx, None), }) }))) } @@ -559,7 +562,9 @@ impl SwapsContext { pub fn remove_msg_v2_store(&self, uuid: &Uuid) { self.swap_v2_msgs.lock().unwrap().remove(uuid); } #[cfg(target_arch = "wasm32")] - pub async fn swap_db(&self) -> InitDbResult> { self.swap_db.get_or_initialize().await } + pub async fn swap_db(&self, db_id: Option<&str>) -> InitDbResult { + self.swap_db.get_or_initialize(db_id).await + } } #[derive(Debug, Deserialize)] @@ -994,10 +999,12 @@ pub struct TransactionIdentifier { } #[cfg(not(target_arch = "wasm32"))] -pub fn my_swaps_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("SWAPS").join("MY") } +pub fn my_swaps_dir(ctx: &MmArc, db_id: Option<&str>) -> PathBuf { ctx.dbdir(db_id).join("SWAPS").join("MY") } #[cfg(not(target_arch = "wasm32"))] -pub fn my_swap_file_path(ctx: &MmArc, uuid: &Uuid) -> PathBuf { my_swaps_dir(ctx).join(format!("{}.json", uuid)) } +pub fn my_swap_file_path(ctx: &MmArc, db_id: Option<&str>, uuid: &Uuid) -> PathBuf { + my_swaps_dir(ctx, db_id).join(format!("{}.json", uuid)) +} pub async fn insert_new_swap_to_db( ctx: MmArc, @@ -1006,24 +1013,27 @@ pub async fn insert_new_swap_to_db( uuid: Uuid, started_at: u64, swap_type: u8, + db_id: Option<&str>, ) -> Result<(), String> { MySwapsStorage::new(ctx) - .save_new_swap(my_coin, other_coin, uuid, started_at, swap_type) + .save_new_swap(my_coin, other_coin, uuid, started_at, swap_type, db_id) .await .map_err(|e| ERRL!("{}", e)) } #[cfg(not(target_arch = "wasm32"))] -fn add_swap_to_db_index(ctx: &MmArc, swap: &SavedSwap) { - if let Some(conn) = ctx.sqlite_conn_opt() { - crate::database::stats_swaps::add_swap_to_index(&conn, swap) - } +fn add_swap_to_db_index(ctx: &MmArc, swap: &SavedSwap, db_id: Option<&str>) { + let swap = swap.clone(); + ctx.run_sql_query(db_id, move |conn| { + crate::database::stats_swaps::add_swap_to_index(&conn, &swap) + }); } #[cfg(not(target_arch = "wasm32"))] async fn save_stats_swap(ctx: &MmArc, swap: &SavedSwap) -> Result<(), String> { - try_s!(swap.save_to_stats_db(ctx).await); - add_swap_to_db_index(ctx, swap); + let db_id = swap.account_db_id().await; + try_s!(swap.save_to_stats_db(ctx, db_id.as_deref()).await); + add_swap_to_db_index(ctx, swap, db_id.as_deref()); Ok(()) } @@ -1104,35 +1114,70 @@ impl From for MySwapStatusResponse { /// Returns the status of swap performed on `my` node pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); - let swap_type = try_s!(get_swap_type(&ctx, &uuid).await); - - match swap_type { - Some(LEGACY_SWAP_TYPE) => { - let status = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { - Ok(Some(status)) => status, - Ok(None) => return Err("swap data is not found".to_owned()), - Err(e) => return ERR!("{}", e), - }; - - let res_js = json!({ "result": MySwapStatusResponse::from(status) }); - let res = try_s!(json::to_vec(&res_js)); - Ok(try_s!(Response::builder().body(res))) - }, - Some(MAKER_SWAP_V2_TYPE) => { - let swap_data = try_s!(get_maker_swap_data_for_rpc(&ctx, &uuid).await); - let res_js = json!({ "result": swap_data }); - let res = try_s!(json::to_vec(&res_js)); - Ok(try_s!(Response::builder().body(res))) - }, - Some(TAKER_SWAP_V2_TYPE) => { - let swap_data = try_s!(get_taker_swap_data_for_rpc(&ctx, &uuid).await); - let res_js = json!({ "result": swap_data }); - let res = try_s!(json::to_vec(&res_js)); - Ok(try_s!(Response::builder().body(res))) - }, - Some(unsupported_type) => ERR!("Got unsupported swap type from DB: {}", unsupported_type), - None => ERR!("No swap with uuid {}", uuid), + let db_ids = find_unique_account_ids_any(&ctx).await?; + let mut last_error = None; + + for db_id in db_ids.iter() { + match get_swap_type(&ctx, &uuid, Some(db_id)).await { + Ok(Some(LEGACY_SWAP_TYPE)) => { + let status = match SavedSwap::load_my_swap_from_db(&ctx, Some(db_id), uuid).await { + Ok(Some(status)) => status, + Ok(None) => { + last_error = Some("swap data is not found".to_owned()); + continue; + }, + Err(e) => { + last_error = Some(format!("{}", e)); + continue; + }, + }; + + let res_js = json!({ "result": MySwapStatusResponse::from(status) }); + let res = try_s!(json::to_vec(&res_js)); + return Ok(try_s!(Response::builder().body(res))); + }, + Ok(Some(MAKER_SWAP_V2_TYPE)) => { + let swap_data = match get_maker_swap_data_for_rpc(&ctx, &uuid, Some(db_id)).await { + Ok(data) => data, + Err(e) => { + last_error = Some(format!("{}", e)); + continue; + }, + }; + + let res_js = json!({ "result": swap_data }); + let res = try_s!(json::to_vec(&res_js)); + return Ok(try_s!(Response::builder().body(res))); + }, + Ok(Some(TAKER_SWAP_V2_TYPE)) => { + let swap_data = match get_taker_swap_data_for_rpc(&ctx, &uuid, Some(db_id)).await { + Ok(data) => data, + Err(e) => { + last_error = Some(format!("{}", e)); + continue; + }, + }; + + let res_js = json!({ "result": swap_data }); + let res = try_s!(json::to_vec(&res_js)); + return Ok(try_s!(Response::builder().body(res))); + }, + Ok(Some(unsupported_type)) => { + last_error = Some(format!("Got unsupported swap type from DB: {}", unsupported_type)); + continue; + }, + Ok(None) => { + last_error = Some(format!("No swap type found for uuid {}", uuid)); + continue; + }, + Err(e) => { + last_error = Some(format!("{}", e)); + continue; + }, + } } + + Err(last_error.unwrap_or_else(|| format!("swap_status not found for {uuid:?}"))) } #[cfg(target_arch = "wasm32")] @@ -1144,9 +1189,10 @@ pub async fn stats_swap_status(_ctx: MmArc, _req: Json) -> Result Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); + let db_id: Option = try_s!(json::from_value(req["params"]["db_id"].clone())); - let maker_status = try_s!(SavedSwap::load_from_maker_stats_db(&ctx, uuid).await); - let taker_status = try_s!(SavedSwap::load_from_taker_stats_db(&ctx, uuid).await); + let maker_status = try_s!(SavedSwap::load_from_maker_stats_db(&ctx, db_id.as_deref(), uuid).await); + let taker_status = try_s!(SavedSwap::load_from_taker_stats_db(&ctx, db_id.as_deref(), uuid).await); if maker_status.is_none() && taker_status.is_none() { return ERR!("swap data is not found"); @@ -1169,8 +1215,8 @@ struct SwapStatus { } /// Broadcasts `my` swap status to P2P network -async fn broadcast_my_swap_status(ctx: &MmArc, uuid: Uuid) -> Result<(), String> { - let mut status = match try_s!(SavedSwap::load_my_swap_from_db(ctx, uuid).await) { +async fn broadcast_my_swap_status(ctx: &MmArc, uuid: Uuid, db_id: Option<&str>) -> Result<(), String> { + let mut status = match try_s!(SavedSwap::load_my_swap_from_db(ctx, db_id, uuid).await) { Some(status) => status, None => return ERR!("swap data is not found"), }; @@ -1188,7 +1234,7 @@ async fn broadcast_my_swap_status(ctx: &MmArc, uuid: Uuid) -> Result<(), String> Ok(()) } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct MySwapsFilter { pub my_coin: Option, pub other_coin: Option, @@ -1201,22 +1247,30 @@ pub struct MySwapsFilter { /// Returns *all* uuids of swaps, which match the selected filter. pub async fn all_swaps_uuids_by_filter(ctx: MmArc, req: Json) -> Result>, String> { let filter: MySwapsFilter = try_s!(json::from_value(req)); - let db_result = try_s!( - MySwapsStorage::new(ctx) - .my_recent_swaps_with_filters(&filter, None) - .await - ); + let db_ids = try_s!(find_unique_account_ids_active(&ctx).await); + let mut res_js = vec![]; + + for db_id in db_ids { + let db_result = try_s!( + MySwapsStorage::new(ctx.clone()) + .my_recent_swaps_with_filters(&filter, None, &db_id) + .await + ); + let res = json!({ + "result": { + "found_records": db_result.uuids_and_types.len(), + "uuids": db_result.uuids_and_types.into_iter().map(|(uuid, _)| uuid).collect::>(), + "my_coin": filter.my_coin, + "other_coin": filter.other_coin, + "from_timestamp": filter.from_timestamp, + "to_timestamp": filter.to_timestamp, + "pubkey": db_result.pubkey + }, + }); + + res_js.push(res); + } - let res_js = json!({ - "result": { - "found_records": db_result.uuids_and_types.len(), - "uuids": db_result.uuids_and_types.into_iter().map(|(uuid, _)| uuid).collect::>(), - "my_coin": filter.my_coin, - "other_coin": filter.other_coin, - "from_timestamp": filter.from_timestamp, - "to_timestamp": filter.to_timestamp, - }, - }); let res = try_s!(json::to_vec(&res_js)); Ok(try_s!(Response::builder().body(res))) } @@ -1231,6 +1285,8 @@ pub struct MyRecentSwapsReq { #[derive(Debug, Default, PartialEq)] pub struct MyRecentSwapsUuids { + /// Pubkey which swaps belongs to. + pub pubkey: String, /// UUIDs and types of swaps matching the query pub uuids_and_types: Vec<(Uuid, u8)>, /// Total count of swaps matching the query @@ -1247,47 +1303,65 @@ pub enum LatestSwapsErr { UnableToLoadSavedSwaps(SavedSwapError), #[display(fmt = "Unable to query swaps storage")] UnableToQuerySwapStorage, + #[display(fmt = "No active coin pubkey not found")] + CoinNotFound, } +// pub async fn get_account_db_id(ctx: &MmArc, coin: &str) -> Result, String> { +// let db_id = try_s!(lp_coinfind_any(&ctx, &coin).await); +// let db_id = if let Some(id) = db_id { +// try_s!(id.inner.account_db_id()) +// } else { +// None +// }; + +// Ok(db_id) +// } + pub async fn latest_swaps_for_pair( ctx: MmArc, my_coin: String, other_coin: String, limit: usize, ) -> Result, MmError> { - let filter = MySwapsFilter { - my_coin: Some(my_coin), - other_coin: Some(other_coin), - from_timestamp: None, - to_timestamp: None, - }; - - let paging_options = PagingOptions { - limit, - page_number: NonZeroUsize::new(1).expect("1 > 0"), - from_uuid: None, - }; - - let db_result = match MySwapsStorage::new(ctx.clone()) - .my_recent_swaps_with_filters(&filter, Some(&paging_options)) + let db_ids = find_unique_account_ids_active(&ctx) .await - { - Ok(x) => x, - Err(_) => return Err(MmError::new(LatestSwapsErr::UnableToQuerySwapStorage)), - }; + .map_to_mm(|_| LatestSwapsErr::CoinNotFound)?; + let mut swaps = vec![]; + + for db_id in db_ids { + let filter = MySwapsFilter { + my_coin: Some(my_coin.clone()), + other_coin: Some(other_coin.clone()), + from_timestamp: None, + to_timestamp: None, + }; - let mut swaps = Vec::with_capacity(db_result.uuids_and_types.len()); - // TODO this is needed for trading bot, which seems not used as of now. Remove the code? - for (uuid, _) in db_result.uuids_and_types.iter() { - let swap = match SavedSwap::load_my_swap_from_db(&ctx, *uuid).await { - Ok(Some(swap)) => swap, - Ok(None) => { - error!("No such swap with the uuid '{}'", uuid); - continue; - }, - Err(e) => return Err(MmError::new(LatestSwapsErr::UnableToLoadSavedSwaps(e.into_inner()))), + let paging_options = PagingOptions { + limit, + page_number: NonZeroUsize::new(1).expect("1 > 0"), + from_uuid: None, + }; + + let db_result = match MySwapsStorage::new(ctx.clone()) + .my_recent_swaps_with_filters(&filter, Some(&paging_options), &db_id) + .await + { + Ok(x) => x, + Err(_) => return Err(MmError::new(LatestSwapsErr::UnableToQuerySwapStorage)), }; - swaps.push(swap); + + for (uuid, _) in db_result.uuids_and_types.iter() { + let swap = match SavedSwap::load_my_swap_from_db(&ctx, Some(&db_result.pubkey), *uuid).await { + Ok(Some(swap)) => swap, + Ok(None) => { + error!("No such swap with the uuid '{}'", uuid); + continue; + }, + Err(e) => return Err(MmError::new(LatestSwapsErr::UnableToLoadSavedSwaps(e.into_inner()))), + }; + swaps.push(swap); + } } Ok(swaps) @@ -1296,68 +1370,85 @@ pub async fn latest_swaps_for_pair( /// Returns the data of recent swaps of `my` node. pub async fn my_recent_swaps_rpc(ctx: MmArc, req: Json) -> Result>, String> { let req: MyRecentSwapsReq = try_s!(json::from_value(req)); - let db_result = try_s!( - MySwapsStorage::new(ctx.clone()) - .my_recent_swaps_with_filters(&req.filter, Some(&req.paging_options)) - .await - ); - - // iterate over uuids trying to parse the corresponding files content and add to result vector - let mut swaps = Vec::with_capacity(db_result.uuids_and_types.len()); - for (uuid, swap_type) in db_result.uuids_and_types.iter() { - match *swap_type { - LEGACY_SWAP_TYPE => match SavedSwap::load_my_swap_from_db(&ctx, *uuid).await { - Ok(Some(swap)) => { - let swap_json = try_s!(json::to_value(MySwapStatusResponse::from(swap))); - swaps.push(swap_json) + let db_ids = try_s!(find_unique_account_ids_any(&ctx).await); + + let mut res_js = vec![]; + for db_id in db_ids { + let db_result = try_s!( + MySwapsStorage::new(ctx.clone()) + .my_recent_swaps_with_filters(&req.filter, Some(&req.paging_options), &db_id) + .await + ); + + // iterate over uuids trying to parse the corresponding files content and add to result vector + let mut swaps = vec![]; + for (uuid, swap_type) in db_result.uuids_and_types.iter() { + match *swap_type { + LEGACY_SWAP_TYPE => match SavedSwap::load_my_swap_from_db(&ctx, Some(&db_result.pubkey), *uuid).await { + Ok(Some(swap)) => { + let swap_json = try_s!(json::to_value(MySwapStatusResponse::from(swap))); + swaps.push(swap_json) + }, + Ok(None) => warn!("No such swap with the uuid '{}'", uuid), + Err(e) => error!( + "Error loading a swap with the uuid '{}': {} for db_id=({db_id})", + uuid, e + ), }, - Ok(None) => warn!("No such swap with the uuid '{}'", uuid), - Err(e) => error!("Error loading a swap with the uuid '{}': {}", uuid, e), - }, - MAKER_SWAP_V2_TYPE => match get_maker_swap_data_for_rpc(&ctx, uuid).await { - Ok(data) => { - let swap_json = try_s!(json::to_value(data)); - swaps.push(swap_json); + MAKER_SWAP_V2_TYPE => match get_maker_swap_data_for_rpc(&ctx, uuid, Some(&db_result.pubkey)).await { + Ok(data) => { + let swap_json = try_s!(json::to_value(data)); + swaps.push(swap_json); + }, + Err(e) => error!( + "Error loading a swap with the uuid '{}': {} for db_id=({db_id})", + uuid, e + ), }, - Err(e) => error!("Error loading a swap with the uuid '{}': {}", uuid, e), - }, - TAKER_SWAP_V2_TYPE => match get_taker_swap_data_for_rpc(&ctx, uuid).await { - Ok(data) => { - let swap_json = try_s!(json::to_value(data)); - swaps.push(swap_json); + TAKER_SWAP_V2_TYPE => match get_taker_swap_data_for_rpc(&ctx, uuid, Some(&db_result.pubkey)).await { + Ok(data) => { + let swap_json = try_s!(json::to_value(data)); + swaps.push(swap_json); + }, + Err(e) => error!( + "Error loading a swap with the uuid '{}': {} for db_id=({db_id})", + uuid, e + ), }, - Err(e) => error!("Error loading a swap with the uuid '{}': {}", uuid, e), - }, - unknown_type => error!("Swap with the uuid '{}' has unknown type {}", uuid, unknown_type), + unknown_type => error!("Swap with the uuid '{}' has unknown type {}", uuid, unknown_type), + } + } + + if !swaps.is_empty() { + res_js.push(json!({ + "swaps": swaps, + "from_uuid": req.paging_options.from_uuid, + "skipped": db_result.skipped, + "limit": req.paging_options.limit, + "total": db_result.total_count, + "page_number": req.paging_options.page_number, + "total_pages": calc_total_pages(db_result.total_count, req.paging_options.limit), + "found_records": db_result.uuids_and_types.len(), + "pubkey": db_result.pubkey + })); } } - let res_js = json!({ - "result": { - "swaps": swaps, - "from_uuid": req.paging_options.from_uuid, - "skipped": db_result.skipped, - "limit": req.paging_options.limit, - "total": db_result.total_count, - "page_number": req.paging_options.page_number, - "total_pages": calc_total_pages(db_result.total_count, req.paging_options.limit), - "found_records": db_result.uuids_and_types.len(), - }, - }); + let res_js = json!({ "result": res_js }); let res = try_s!(json::to_vec(&res_js)); Ok(try_s!(Response::builder().body(res))) } /// Find out the swaps that need to be kick-started, continue from the point where swap was interrupted /// Return the tickers of coins that must be enabled for swaps to continue -pub async fn swap_kick_starts(ctx: MmArc) -> Result, String> { +pub async fn swap_kick_starts(ctx: MmArc, db_id: Option<&str>) -> Result, String> { + let mut coins = HashSet::new(); #[cfg(target_arch = "wasm32")] - try_s!(migrate_swaps_data(&ctx).await); + try_s!(migrate_swaps_data(&ctx, db_id).await); - let mut coins = HashSet::new(); - let legacy_unfinished_uuids = try_s!(get_unfinished_swaps_uuids(ctx.clone(), LEGACY_SWAP_TYPE).await); + let legacy_unfinished_uuids = try_s!(get_unfinished_swaps_uuids(ctx.clone(), LEGACY_SWAP_TYPE, db_id).await); for uuid in legacy_unfinished_uuids { - let swap = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { + let swap = match SavedSwap::load_my_swap_from_db(&ctx, db_id, uuid).await { Ok(Some(s)) => s, Ok(None) => { warn!("Swap {} is indexed, but doesn't exist in DB", uuid); @@ -1390,7 +1481,7 @@ pub async fn swap_kick_starts(ctx: MmArc) -> Result, String> { ctx.spawner().spawn(fut); } - let maker_swap_storage = MakerSwapStorage::new(ctx.clone()); + let maker_swap_storage = MakerSwapStorage::new(ctx.clone(), db_id); let unfinished_maker_uuids = try_s!(maker_swap_storage.get_unfinished().await); for maker_uuid in unfinished_maker_uuids { info!("Trying to kickstart maker swap {}", maker_uuid); @@ -1415,7 +1506,7 @@ pub async fn swap_kick_starts(ctx: MmArc) -> Result, String> { ctx.spawner().spawn(fut); } - let taker_swap_storage = TakerSwapStorage::new(ctx.clone()); + let taker_swap_storage = TakerSwapStorage::new(ctx.clone(), db_id); let unfinished_taker_uuids = try_s!(taker_swap_storage.get_unfinished().await); for taker_uuid in unfinished_taker_uuids { info!("Trying to kickstart taker swap {}", taker_uuid); @@ -1439,13 +1530,29 @@ pub async fn swap_kick_starts(ctx: MmArc) -> Result, String> { ); ctx.spawner().spawn(fut); } + Ok(coins) } async fn kickstart_thread_handler(ctx: MmArc, swap: SavedSwap, maker_coin_ticker: String, taker_coin_ticker: String) { + let taker_coin_account_id = match &swap { + SavedSwap::Maker(swap) => swap.taker_coin_account_id.as_deref(), + SavedSwap::Taker(swap) => swap.taker_coin_account_id.as_deref(), + }; let taker_coin = loop { match lp_coinfind(&ctx, &taker_coin_ticker).await { - Ok(Some(c)) => break c, + Ok(Some(c)) => { + if taker_coin_account_id == c.account_db_id().await.as_deref() { + break c; + }; + info!( + "Can't kickstart the swap {} until the coin {} is activated with pubkey: {}", + swap.uuid(), + taker_coin_ticker, + taker_coin_account_id.unwrap_or(&ctx.rmd160.to_string()) + ); + Timer::sleep(5.).await; + }, Ok(None) => { info!( "Can't kickstart the swap {} until the coin {} is activated", @@ -1461,9 +1568,24 @@ async fn kickstart_thread_handler(ctx: MmArc, swap: SavedSwap, maker_coin_ticker }; }; + let maker_coin_account_id = match &swap { + SavedSwap::Maker(swap) => swap.maker_coin_account_id.as_deref(), + SavedSwap::Taker(swap) => swap.maker_coin_account_id.as_deref(), + }; let maker_coin = loop { match lp_coinfind(&ctx, &maker_coin_ticker).await { - Ok(Some(c)) => break c, + Ok(Some(c)) => { + if maker_coin_account_id == c.account_db_id().await.as_deref() { + break c; + }; + info!( + "Can't kickstart the swap {} until the coin {} is activated with pubkey: {}", + swap.uuid(), + maker_coin_ticker, + maker_coin_account_id.unwrap_or(&ctx.rmd160.to_string()) + ); + Timer::sleep(5.).await; + }, Ok(None) => { info!( "Can't kickstart the swap {} until the coin {} is activated", @@ -1513,7 +1635,7 @@ pub async fn coins_needed_for_kick_start(ctx: MmArc) -> Result> pub async fn recover_funds_of_swap(ctx: MmArc, req: Json) -> Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); - let swap = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { + let swap = match SavedSwap::load_my_swap_from_db(&ctx, None, uuid).await { Ok(Some(swap)) => swap, Ok(None) => return ERR!("swap data is not found"), Err(e) => return ERR!("{}", e), @@ -1536,7 +1658,8 @@ pub async fn import_swaps(ctx: MmArc, req: Json) -> Result>, St let mut imported = vec![]; let mut skipped = HashMap::new(); for swap in swaps { - match swap.save_to_db(&ctx).await { + let accound_id = swap.account_db_id().await; + match swap.save_to_db(&ctx, accound_id.as_deref()).await { Ok(_) => { if let Some(info) = swap.get_my_info() { if let Err(e) = insert_new_swap_to_db( @@ -1546,6 +1669,7 @@ pub async fn import_swaps(ctx: MmArc, req: Json) -> Result>, St *swap.uuid(), info.started_at, LEGACY_SWAP_TYPE, + accound_id.as_deref(), ) .await { @@ -1590,7 +1714,7 @@ pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result> for (uuid, swap_type) in uuids_with_types.iter() { match *swap_type { LEGACY_SWAP_TYPE => { - let status = match SavedSwap::load_my_swap_from_db(&ctx, *uuid).await { + let status = match SavedSwap::load_my_swap_from_db(&ctx, None, *uuid).await { Ok(Some(status)) => status, Ok(None) => continue, Err(e) => { @@ -1846,6 +1970,7 @@ mod lp_swap_tests { use coins::PrivKeyActivationPolicy; use common::{block_on, new_uuid}; use mm2_core::mm_ctx::MmCtxBuilder; + use mm2_core::sql_connection_pool::SqliteConnPool; use mm2_test_helpers::for_tests::{morty_conf, rick_conf, MORTY_ELECTRUM_ADDRS, RICK_ELECTRUM_ADDRS}; #[test] @@ -1876,7 +2001,7 @@ mod lp_swap_tests { assert_eq!( DexFee::WithBurn { fee_amount: "0.00001".into(), - burn_amount: "0.00000001".into() + burn_amount: "0.00000001".into(), }, actual_fee ); @@ -2269,9 +2394,9 @@ mod lp_swap_tests { .unwrap() .mm2_internal_key_pair(); - fix_directories(&maker_ctx).unwrap(); + fix_directories(&maker_ctx, None, true).unwrap(); block_on(init_p2p(maker_ctx.clone())).unwrap(); - maker_ctx.init_sqlite_connection().unwrap(); + SqliteConnPool::init_test(&maker_ctx).unwrap(); let rick_activation_params = utxo_activation_params(RICK_ELECTRUM_ADDRS); let morty_activation_params = utxo_activation_params(MORTY_ELECTRUM_ADDRS); @@ -2307,9 +2432,9 @@ mod lp_swap_tests { .unwrap() .mm2_internal_key_pair(); - fix_directories(&taker_ctx).unwrap(); + fix_directories(&taker_ctx, None, true).unwrap(); block_on(init_p2p(taker_ctx.clone())).unwrap(); - taker_ctx.init_sqlite_connection().unwrap(); + SqliteConnPool::init_test(&taker_ctx).unwrap(); let rick_taker = block_on(utxo_standard_coin_with_priv_key( &taker_ctx, @@ -2341,7 +2466,7 @@ mod lp_swap_tests { taker_coin_nota: false, }; - let mut maker_swap = MakerSwap::new( + let mut maker_swap = block_on(MakerSwap::new( maker_ctx.clone(), taker_key_pair.public().compressed_unprefixed().unwrap().into(), maker_amount.clone(), @@ -2355,14 +2480,14 @@ mod lp_swap_tests { lock_duration, None, Default::default(), - ); + )); maker_swap.fail_at = maker_fail_at; #[cfg(any(test, feature = "run-docker-tests"))] let fail_at = std::env::var("TAKER_FAIL_AT").map(taker_swap::FailAt::from).ok(); - let taker_swap = TakerSwap::new( + let taker_swap = block_on(TakerSwap::new( taker_ctx.clone(), maker_key_pair.public().compressed_unprefixed().unwrap().into(), maker_amount.into(), @@ -2377,7 +2502,7 @@ mod lp_swap_tests { None, #[cfg(any(test, feature = "run-docker-tests"))] fail_at, - ); + )); block_on(futures::future::join( run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), maker_ctx.clone()), @@ -2386,13 +2511,13 @@ mod lp_swap_tests { println!( "Maker swap path {}", - std::fs::canonicalize(my_swap_file_path(&maker_ctx, &uuid)) + std::fs::canonicalize(my_swap_file_path(&maker_ctx, None, &uuid)) .unwrap() .display() ); println!( "Taker swap path {}", - std::fs::canonicalize(my_swap_file_path(&taker_ctx, &uuid)) + std::fs::canonicalize(my_swap_file_path(&taker_ctx, None, &uuid)) .unwrap() .display() ); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 82e3809c6c..55d2800eb5 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -77,15 +77,22 @@ pub const MAKER_ERROR_EVENTS: [&str; 15] = [ pub const MAKER_PAYMENT_SENT_LOG: &str = "Maker payment sent"; #[cfg(not(target_arch = "wasm32"))] -pub fn stats_maker_swap_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("SWAPS").join("STATS").join("MAKER") } +pub fn stats_maker_swap_dir(ctx: &MmArc, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id).join("SWAPS").join("STATS").join("MAKER") +} #[cfg(not(target_arch = "wasm32"))] -pub fn stats_maker_swap_file_path(ctx: &MmArc, uuid: &Uuid) -> PathBuf { - stats_maker_swap_dir(ctx).join(format!("{}.json", uuid)) +pub fn stats_maker_swap_file_path(ctx: &MmArc, db_id: Option<&str>, uuid: &Uuid) -> PathBuf { + stats_maker_swap_dir(ctx, db_id).join(format!("{}.json", uuid)) } -async fn save_my_maker_swap_event(ctx: &MmArc, swap: &MakerSwap, event: MakerSavedEvent) -> Result<(), String> { - let swap = match SavedSwap::load_my_swap_from_db(ctx, swap.uuid).await { +async fn save_my_maker_swap_event( + ctx: &MmArc, + swap: &MakerSwap, + event: MakerSavedEvent, + db_id: Option<&str>, +) -> Result<(), String> { + let swap = match SavedSwap::load_my_swap_from_db(ctx, db_id, swap.uuid).await { Ok(Some(swap)) => swap, Ok(None) => SavedSwap::Maker(MakerSavedSwap { uuid: swap.uuid, @@ -101,6 +108,8 @@ async fn save_my_maker_swap_event(ctx: &MmArc, swap: &MakerSwap, event: MakerSav events: vec![], success_events: MAKER_SUCCESS_EVENTS.iter().map(|event| event.to_string()).collect(), error_events: MAKER_ERROR_EVENTS.iter().map(|event| event.to_string()).collect(), + maker_coin_account_id: swap.maker_coin.account_db_id().await, + taker_coin_account_id: swap.taker_coin.account_db_id().await, }), Err(e) => return ERR!("{}", e), }; @@ -111,7 +120,7 @@ async fn save_my_maker_swap_event(ctx: &MmArc, swap: &MakerSwap, event: MakerSav maker_swap.fetch_and_set_usd_prices().await; } let new_swap = SavedSwap::Maker(maker_swap); - try_s!(new_swap.save_to_db(ctx).await); + try_s!(new_swap.save_to_db(ctx, db_id).await); Ok(()) } else { ERR!("Expected SavedSwap::Maker, got {:?}", swap) @@ -173,6 +182,8 @@ pub struct MakerSwapData { pub taker_coin_htlc_pubkey: Option, /// Temporary privkey used to sign P2P messages when applicable pub p2p_privkey: Option, + // dynamic database id for maker from it's coin rmd160 + pub db_id: Option, } pub struct MakerSwapMut { @@ -353,7 +364,7 @@ impl MakerSwap { } #[allow(clippy::too_many_arguments)] - pub fn new( + pub async fn new( ctx: MmArc, taker: bits256, maker_amount: BigDecimal, @@ -369,6 +380,7 @@ impl MakerSwap { secret: H256, ) -> Self { let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); + let db_id = maker_coin.account_db_id().await; MakerSwap { maker_coin, taker_coin, @@ -386,7 +398,10 @@ impl MakerSwap { payment_locktime, p2p_privkey, mutable: RwLock::new(MakerSwapMut { - data: MakerSwapData::default(), + data: MakerSwapData { + db_id, + ..MakerSwapData::default() + }, other_maker_coin_htlc_pub: H264::default(), other_taker_coin_htlc_pub: H264::default(), taker_fee: None, @@ -482,7 +497,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::StartFailed( ERRL!("!maker_coin.get_sender_trade_fee {}", e).into(), - )])) + )])); }, }; let taker_payment_spend_trade_fee_fut = self.taker_coin.get_receiver_trade_fee(stage); @@ -491,7 +506,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::StartFailed( ERRL!("!taker_coin.get_receiver_trade_fee {}", e).into(), - )])) + )])); }, }; @@ -514,7 +529,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::StartFailed( ERRL!("!check_balance_for_maker_swap {}", e).into(), - )])) + )])); }, }; @@ -524,7 +539,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::StartFailed( ERRL!("!maker_coin.current_block {}", e).into(), - )])) + )])); }, }; @@ -533,7 +548,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::StartFailed( ERRL!("!taker_coin.current_block {}", e).into(), - )])) + )])); }, }; @@ -571,6 +586,7 @@ impl MakerSwap { maker_coin_htlc_pubkey: Some(maker_coin_htlc_pubkey.as_slice().into()), taker_coin_htlc_pubkey: Some(taker_coin_htlc_pubkey.as_slice().into()), p2p_privkey: self.p2p_privkey.map(SerializableSecp256k1Keypair::from), + db_id: self.maker_coin.account_db_id().await, }; // This will be done during order match @@ -708,7 +724,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::TakerFeeValidateFailed(ERRL!("{}", e).into()), - ])) + ])); }, }; drop(send_abort_handle); @@ -743,7 +759,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::TakerFeeValidateFailed(ERRL!("{:?}", e).into()), - ])) + ])); }, }; @@ -830,7 +846,7 @@ impl MakerSwap { Err(err) => { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()), - ])) + ])); }, } } else { @@ -869,7 +885,7 @@ impl MakerSwap { Err(e) => { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::MakerPaymentTransactionFailed(ERRL!("{}", e).into()), - ])) + ])); }, }; @@ -947,7 +963,7 @@ impl MakerSwap { MakerSwapEvent::MakerPaymentWaitRefundStarted { wait_until: self.wait_refund_until(), }, - ])) + ])); }, }; drop(abort_send_handle); @@ -962,7 +978,7 @@ impl MakerSwap { MakerSwapEvent::MakerPaymentWaitRefundStarted { wait_until: self.wait_refund_until(), }, - ])) + ])); }, }; @@ -1022,7 +1038,7 @@ impl MakerSwap { Err(err) => { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()), - ])) + ])); }, } } else { @@ -1299,8 +1315,9 @@ impl MakerSwap { maker_coin: MmCoinEnum, taker_coin: MmCoinEnum, swap_uuid: &Uuid, + db_id: Option<&str>, ) -> Result<(Self, Option), String> { - let saved = match SavedSwap::load_my_swap_from_db(&ctx, *swap_uuid).await { + let saved = match SavedSwap::load_my_swap_from_db(&ctx, db_id, *swap_uuid).await { Ok(Some(saved)) => saved, Ok(None) => return ERR!("Couldn't find a swap with the uuid '{}'", swap_uuid), Err(e) => return ERR!("{}", e), @@ -1309,10 +1326,10 @@ impl MakerSwap { SavedSwap::Maker(swap) => swap, SavedSwap::Taker(_) => return ERR!("Can not load MakerSwap from SavedSwap::Taker uuid: {}", swap_uuid), }; - Self::load_from_saved(ctx, maker_coin, taker_coin, saved) + Self::load_from_saved(ctx, maker_coin, taker_coin, saved).await } - pub fn load_from_saved( + pub async fn load_from_saved( ctx: MmArc, maker_coin: MmCoinEnum, taker_coin: MmCoinEnum, @@ -1365,7 +1382,8 @@ impl MakerSwap { data.lock_duration, data.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), data.secret.into(), - ); + ) + .await; let command = saved.events.last().unwrap().get_command(); for saved_event in saved.events { swap.apply_event(saved_event.event); @@ -1411,14 +1429,14 @@ impl MakerSwap { "Taker payment was already spent by {} tx {:02x}", selfi.taker_coin.ticker(), tx.tx_hash_as_bytes() - ) + ); }, Ok(Some(FoundSwapTxSpend::Refunded(tx))) => { return ERR!( "Taker payment was already refunded by {} tx {:02x}", selfi.taker_coin.ticker(), tx.tx_hash_as_bytes() - ) + ); }, Err(e) => return ERR!("Error {} when trying to find taker payment spend", e), Ok(None) => (), // payment is not spent, continue @@ -1791,7 +1809,7 @@ impl MakerSwapStatusChanged { } } -#[derive(Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct MakerSavedSwap { pub uuid: Uuid, pub my_order_uuid: Option, @@ -1806,6 +1824,10 @@ pub struct MakerSavedSwap { pub mm_version: Option, pub success_events: Vec, pub error_events: Vec, + /// needed to validate if pending maker coin is activated with the correct `db_id` in `kickstart_thread_handler` + pub maker_coin_account_id: Option, + /// needed to validate if pending taker coin is activated with the correct `db_id` in `kickstart_thread_handler` + pub taker_coin_account_id: Option, } #[cfg(test)] @@ -1840,6 +1862,7 @@ impl MakerSavedSwap { maker_coin_htlc_pubkey: None, taker_coin_htlc_pubkey: None, p2p_privkey: None, + db_id: None, }), }, MakerSavedEvent { @@ -1862,6 +1885,8 @@ impl MakerSavedSwap { mm_version: None, success_events: vec![], error_events: vec![], + maker_coin_account_id: None, + taker_coin_account_id: None, } } } @@ -1970,7 +1995,7 @@ impl MakerSavedSwap { } } - // TODO: Adjust for private coins when/if they are braodcasted + // TODO: Adjust for private coins when/if they are broadcasted // TODO: Adjust for HD wallet when completed pub fn swap_pubkeys(&self) -> Result { let maker = match &self.events.first() { @@ -1996,6 +2021,15 @@ impl MakerSavedSwap { Ok(SwapPubkeys { maker, taker }) } + + pub fn db_id(&self) -> Option { + if let Some(events) = self.events.first() { + if let MakerSwapEvent::Started(data) = &events.event { + return data.db_id.clone(); + } + } + None + } } #[allow(clippy::large_enum_variant)] @@ -2015,6 +2049,13 @@ impl RunMakerSwapInput { RunMakerSwapInput::KickStart { swap_uuid, .. } => swap_uuid, } } + + fn maker_coin(&self) -> &MmCoinEnum { + match self { + RunMakerSwapInput::StartNew(swap) => &swap.maker_coin, + RunMakerSwapInput::KickStart { maker_coin, .. } => maker_coin, + } + } } /// Starts the maker swap and drives it to completion (until None next command received). @@ -2022,10 +2063,11 @@ impl RunMakerSwapInput { /// because it's usually means that swap is in invalid state which is possible only if there's developer error. /// Every produced event is saved to local DB. Swap status is broadcasted to P2P network after completion. pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { + let db_id = swap.maker_coin().account_db_id().await; let uuid = swap.uuid().to_owned(); let mut attempts = 0; let swap_lock = loop { - match SwapLock::lock(&ctx, uuid, 40.).await { + match SwapLock::lock(&ctx, uuid, 40., db_id.as_deref()).await { Ok(Some(l)) => break l, Ok(None) => { if attempts >= 1 { @@ -2052,7 +2094,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { maker_coin, taker_coin, swap_uuid, - } => match MakerSwap::load_from_db_by_uuid(ctx, maker_coin, taker_coin, &swap_uuid).await { + } => match MakerSwap::load_from_db_by_uuid(ctx, maker_coin, taker_coin, &swap_uuid, db_id.as_deref()).await { Ok((swap, command)) => match command { Some(c) => { info!("Swap {} kick started.", uuid); @@ -2098,6 +2140,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); swap_ctx.init_msg_store(running_swap.uuid, running_swap.taker); swap_ctx.running_swaps.lock().unwrap().push(weak_ref); + let mut swap_fut = Box::pin( async move { let mut events; @@ -2117,7 +2160,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { .dispatch_async(ctx.clone(), LpEvents::MakerSwapStatusChanged(event_to_send)) .await; drop(dispatcher); - save_my_maker_swap_event(&ctx, &running_swap, to_save) + save_my_maker_swap_event(&ctx, &running_swap, to_save, db_id.as_deref()) .await .expect("!save_my_maker_swap_event"); if event.should_ban_taker() { @@ -2141,12 +2184,12 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { command = c; }, None => { - if let Err(e) = mark_swap_as_finished(ctx.clone(), running_swap.uuid).await { + if let Err(e) = mark_swap_as_finished(ctx.clone(), running_swap.uuid, db_id.as_deref()).await { error!("!mark_swap_finished({}): {}", uuid, e); } if to_broadcast { - if let Err(e) = broadcast_my_swap_status(&ctx, uuid).await { + if let Err(e) = broadcast_my_swap_status(&ctx, uuid, db_id.as_deref()).await { error!("!broadcast_my_swap_status({}): {}", uuid, e); } } @@ -2282,6 +2325,7 @@ pub async fn maker_swap_trade_preimage( // perform an additional validation let _order = builder .build() + .await .map_to_mm(|e| TradePreimageRpcError::from_maker_order_build_error(e, base_coin_ticker, rel_coin_ticker))?; let volume = if req.max { Some(volume) } else { None }; @@ -2411,7 +2455,13 @@ mod maker_swap_tests { .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let actual = block_on(maker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::RefundedMyPayment, @@ -2446,7 +2496,13 @@ mod maker_swap_tests { .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let actual = block_on(maker_swap.recover_funds()).unwrap(); let expected = RecoveredSwap { action: RecoveredSwapAction::RefundedMyPayment, @@ -2475,7 +2531,13 @@ mod maker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); assert!(block_on(maker_swap.recover_funds()).is_err()); } @@ -2507,7 +2569,13 @@ mod maker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let err = block_on(maker_swap.recover_funds()).expect_err("Expected an error"); assert!(err.contains("Taker payment was already refunded")); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); @@ -2537,7 +2605,13 @@ mod maker_swap_tests { .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let error = block_on(maker_swap.recover_funds()).unwrap_err(); assert!(error.contains("Too early to refund")); assert!(unsafe { MY_PAYMENT_SENT_CALLED }); @@ -2562,7 +2636,13 @@ mod maker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); assert!(block_on(maker_swap.recover_funds()).is_err()); assert!(unsafe { MY_PAYMENT_SENT_CALLED }); } @@ -2579,7 +2659,13 @@ mod maker_swap_tests { TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); assert!(block_on(maker_swap.recover_funds()).is_err()); } @@ -2612,7 +2698,13 @@ mod maker_swap_tests { let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let err = block_on(maker_swap.recover_funds()).expect_err("Expected an error"); assert!(err.contains("Taker payment was already spent")); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); @@ -2631,7 +2723,13 @@ mod maker_swap_tests { TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); assert!(block_on(maker_swap.recover_funds()).is_err()); } @@ -2670,7 +2768,13 @@ mod maker_swap_tests { let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let expected = Ok(RecoveredSwap { coin: "ticker".into(), action: RecoveredSwapAction::SpentOtherPayment, @@ -2714,7 +2818,13 @@ mod maker_swap_tests { let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let err = block_on(maker_swap.recover_funds()).unwrap_err(); assert!(err.contains("Taker payment spend transaction has been sent and confirmed")); assert!(unsafe { !SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); @@ -2735,8 +2845,13 @@ mod maker_swap_tests { TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (_maker_swap, _) = - MakerSwap::load_from_saved(ctx.clone(), maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (_maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx.clone(), + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); let actual = get_locked_amount(&ctx, "ticker"); assert_eq!(actual, MmNumber::from(0)); @@ -2758,7 +2873,13 @@ mod maker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); assert_eq!(unsafe { SWAP_CONTRACT_ADDRESS_CALLED }, 2); assert_eq!( @@ -2787,7 +2908,13 @@ mod maker_swap_tests { }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); - let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); + let (maker_swap, _) = block_on(MakerSwap::load_from_saved( + ctx, + maker_coin, + taker_coin, + maker_saved_swap, + )) + .unwrap(); assert_eq!(unsafe { SWAP_CONTRACT_ADDRESS_CALLED }, 1); let expected_addr = addr_from_str(ETH_SEPOLIA_SWAP_CONTRACT).unwrap(); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 6029bd61b2..c78db7a565 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -34,7 +34,7 @@ use uuid::Uuid; cfg_native!( use crate::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; use common::async_blocking; - use db_common::sqlite::rusqlite::{named_params, Error as SqlError, Result as SqlResult, Row}; + use db_common::sqlite::rusqlite::{named_params, Connection, Error as SqlError, Result as SqlResult, Row}; use db_common::sqlite::rusqlite::types::Type as SqlType; ); @@ -46,7 +46,7 @@ cfg_wasm32!( #[allow(unused_imports)] use prost::Message; /// Negotiation data representation to be stored in DB. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct StoredNegotiationData { taker_payment_locktime: u64, taker_funding_locktime: u64, @@ -58,7 +58,7 @@ pub struct StoredNegotiationData { } /// Represents events produced by maker swap states. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "event_type", content = "event_data")] pub enum MakerSwapEvent { /// Swap has been successfully initialized. @@ -128,14 +128,29 @@ pub enum MakerSwapEvent { Completed, } +#[cfg(not(target_arch = "wasm32"))] +fn get_repr_impl(conn: &Connection, id_str: &str) -> SqlResult { + conn.query_row( + SELECT_MY_SWAP_V2_BY_UUID, + &[(":uuid", &id_str)], + MakerSwapDbRepr::from_sql_row, + ) +} + /// Storage for maker swaps. #[derive(Clone)] pub struct MakerSwapStorage { ctx: MmArc, + db_id: Option, } impl MakerSwapStorage { - pub fn new(ctx: MmArc) -> Self { MakerSwapStorage { ctx } } + pub fn new(ctx: MmArc, db_id: Option<&str>) -> Self { + MakerSwapStorage { + ctx, + db_id: db_id.map(|c| c.to_string()), + } + } } #[async_trait] @@ -147,31 +162,35 @@ impl StateMachineStorage for MakerSwapStorage { #[cfg(not(target_arch = "wasm32"))] async fn store_repr(&mut self, _id: Self::MachineId, repr: Self::DbRepr) -> Result<(), Self::Error> { let ctx = self.ctx.clone(); - + let db_id = self.db_id.clone(); async_blocking(move || { - let sql_params = named_params! { - ":my_coin": &repr.maker_coin, - ":other_coin": &repr.taker_coin, - ":uuid": repr.uuid.to_string(), - ":started_at": repr.started_at, - ":swap_type": MAKER_SWAP_V2_TYPE, - ":maker_volume": repr.maker_volume.to_fraction_string(), - ":taker_volume": repr.taker_volume.to_fraction_string(), - ":premium": repr.taker_premium.to_fraction_string(), - ":dex_fee": repr.dex_fee_amount.to_fraction_string(), - ":dex_fee_burn": repr.dex_fee_burn.to_fraction_string(), - ":secret": repr.maker_secret.0, - ":secret_hash": repr.maker_secret_hash.0, - ":secret_hash_algo": repr.secret_hash_algo as u8, - ":p2p_privkey": repr.p2p_keypair.map(|k| k.priv_key()).unwrap_or_default(), - ":lock_duration": repr.lock_duration, - ":maker_coin_confs": repr.conf_settings.maker_coin_confs, - ":maker_coin_nota": repr.conf_settings.maker_coin_nota, - ":taker_coin_confs": repr.conf_settings.taker_coin_confs, - ":taker_coin_nota": repr.conf_settings.taker_coin_nota, - ":other_p2p_pub": repr.taker_p2p_pub.to_bytes(), - }; - insert_new_swap_v2(&ctx, sql_params)?; + ctx.run_sql_query(db_id.as_deref(), move |conn| { + let sql_params = named_params! { + ":my_coin": &repr.maker_coin, + ":other_coin": &repr.taker_coin, + ":uuid": repr.uuid.to_string(), + ":started_at": repr.started_at, + ":swap_type": MAKER_SWAP_V2_TYPE, + ":maker_volume": repr.maker_volume.to_fraction_string(), + ":taker_volume": repr.taker_volume.to_fraction_string(), + ":premium": repr.taker_premium.to_fraction_string(), + ":dex_fee": repr.dex_fee_amount.to_fraction_string(), + ":dex_fee_burn": repr.dex_fee_burn.to_fraction_string(), + ":secret": repr.maker_secret.0, + ":secret_hash": repr.maker_secret_hash.0, + ":secret_hash_algo": repr.secret_hash_algo as u8, + ":p2p_privkey": repr.p2p_keypair.map(|k| k.priv_key()).unwrap_or_default(), + ":lock_duration": repr.lock_duration, + ":maker_coin_confs": repr.conf_settings.maker_coin_confs, + ":maker_coin_nota": repr.conf_settings.maker_coin_nota, + ":taker_coin_confs": repr.conf_settings.taker_coin_confs, + ":taker_coin_nota": repr.conf_settings.taker_coin_nota, + ":other_p2p_pub": repr.taker_p2p_pub.to_bytes(), + ":taker_coin_db_id": repr.taker_coin_db_id, + ":maker_coin_db_id": repr.maker_coin_db_id, + }; + insert_new_swap_v2(&conn, sql_params) + })?; Ok(()) }) .await @@ -180,7 +199,7 @@ impl StateMachineStorage for MakerSwapStorage { #[cfg(target_arch = "wasm32")] async fn store_repr(&mut self, uuid: Self::MachineId, repr: Self::DbRepr) -> Result<(), Self::Error> { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(self.db_id.as_deref()).await?; let transaction = db.transaction().await?; let filters_table = transaction.table::().await?; @@ -208,36 +227,31 @@ impl StateMachineStorage for MakerSwapStorage { async fn get_repr(&self, id: Self::MachineId) -> Result { let ctx = self.ctx.clone(); let id_str = id.to_string(); + let db_id = self.db_id.clone(); - async_blocking(move || { - Ok(ctx.sqlite_connection().query_row( - SELECT_MY_SWAP_V2_BY_UUID, - &[(":uuid", &id_str)], - MakerSwapDbRepr::from_sql_row, - )?) - }) - .await + async_blocking(move || Ok(ctx.run_sql_query(db_id.as_deref(), move |conn| get_repr_impl(&conn, &id_str))?)) + .await } #[cfg(target_arch = "wasm32")] async fn get_repr(&self, id: Self::MachineId) -> Result { - get_swap_repr(&self.ctx, id).await + get_swap_repr(&self.ctx, id, self.db_id.as_deref()).await } async fn has_record_for(&mut self, id: &Self::MachineId) -> Result { - has_db_record_for(self.ctx.clone(), id).await + has_db_record_for(self.ctx.clone(), id, self.db_id.as_deref()).await } async fn store_event(&mut self, id: Self::MachineId, event: MakerSwapEvent) -> Result<(), Self::Error> { - store_swap_event::(self.ctx.clone(), id, event).await + store_swap_event::(self.ctx.clone(), id, event, self.db_id.as_deref()).await } async fn get_unfinished(&self) -> Result, Self::Error> { - get_unfinished_swaps_uuids(self.ctx.clone(), MAKER_SWAP_V2_TYPE).await + get_unfinished_swaps_uuids(self.ctx.clone(), MAKER_SWAP_V2_TYPE, self.db_id.as_deref()).await } async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { - mark_swap_as_finished(self.ctx.clone(), id).await + mark_swap_as_finished(self.ctx.clone(), id, self.db_id.as_deref()).await } } @@ -277,6 +291,10 @@ pub struct MakerSwapDbRepr { pub events: Vec, /// Taker's P2P pubkey pub taker_p2p_pub: Secp256k1PubkeySerialize, + // Taker's coin db_id. + pub taker_coin_db_id: Option, + // Maker's coin db_id. + pub maker_coin_db_id: Option, } impl StateMachineDbRepr for MakerSwapDbRepr { @@ -289,6 +307,10 @@ impl GetSwapCoins for MakerSwapDbRepr { fn maker_coin(&self) -> &str { &self.maker_coin } fn taker_coin(&self) -> &str { &self.taker_coin } + + fn taker_coin_db_id(&self) -> &Option { &self.taker_coin_db_id } + + fn maker_coin_db_id(&self) -> &Option { &self.maker_coin_db_id } } #[cfg(not(target_arch = "wasm32"))] @@ -343,6 +365,8 @@ impl MakerSwapDbRepr { .map_err(|e| SqlError::FromSqlConversionFailure(19, SqlType::Blob, Box::new(e))) })? .into(), + taker_coin_db_id: row.get(20)?, + maker_coin_db_id: row.get(21)?, }) } } @@ -424,7 +448,7 @@ impl; type RecreateError = MmError; - fn to_db_repr(&self) -> MakerSwapDbRepr { + async fn to_db_repr(&self) -> MakerSwapDbRepr { MakerSwapDbRepr { maker_coin: self.maker_coin.ticker().into(), maker_volume: self.maker_volume.clone(), @@ -443,6 +467,8 @@ impl return MmError::err(SwapRecreateError::SwapAborted), MakerSwapEvent::Completed => return MmError::err(SwapRecreateError::SwapCompleted), MakerSwapEvent::MakerPaymentRefunded { .. } => { - return MmError::err(SwapRecreateError::SwapFinishedWithRefund) + return MmError::err(SwapRecreateError::SwapFinishedWithRefund); }, }; @@ -651,7 +677,7 @@ impl Result { - acquire_reentrancy_lock_impl(&self.ctx, self.uuid).await + acquire_reentrancy_lock_impl(&self.ctx, self.uuid, self.maker_coin.account_db_id().await.as_deref()).await } fn spawn_reentrancy_lock_renew(&mut self, guard: Self::ReentrancyLock) { @@ -1433,6 +1459,7 @@ impl for MakerPaymentRefundRequired { } + impl TransitionFrom> for MakerPaymentRefundRequired { @@ -1849,15 +1876,19 @@ impl TransitionFrom> for Aborted {} + impl TransitionFrom> for Aborted {} + impl TransitionFrom> for Aborted { } + impl TransitionFrom> for Aborted { } + impl TransitionFrom> for Aborted { diff --git a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs index f725dce5dc..80265bf8e7 100644 --- a/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs +++ b/mm2src/mm2_main/src/lp_swap/my_swaps_storage.rs @@ -38,12 +38,14 @@ pub trait MySwapsOps { uuid: Uuid, started_at: u64, swap_type: u8, + db_id: Option<&str>, ) -> MySwapsResult<()>; async fn my_recent_swaps_with_filters( &self, filter: &MySwapsFilter, paging_options: Option<&PagingOptions>, + db_id: &str, ) -> MySwapsResult; } @@ -83,27 +85,34 @@ mod native_impl { uuid: Uuid, started_at: u64, swap_type: u8, + db_id: Option<&str>, ) -> MySwapsResult<()> { - Ok(insert_new_swap( - &self.ctx, - my_coin, - other_coin, - &uuid.to_string(), - &started_at.to_string(), - swap_type, - )?) + let my_coin = my_coin.to_owned(); + let other_coin = other_coin.to_owned(); + Ok(self.ctx.run_sql_query(db_id, move |conn| { + insert_new_swap( + &conn, + &my_coin, + &other_coin, + &uuid.to_string(), + &started_at.to_string(), + swap_type, + ) + })?) } async fn my_recent_swaps_with_filters( &self, filter: &MySwapsFilter, paging_options: Option<&PagingOptions>, + db_id: &str, ) -> MySwapsResult { - Ok(select_uuids_by_my_swaps_filter( - &self.ctx.sqlite_connection(), - filter, - paging_options, - )?) + let filter = filter.clone(); + let paging_options = paging_options.map(|e| e.to_owned()); + let db_id = db_id.to_owned(); + Ok(self.ctx.run_sql_query(Some(&db_id.clone()), move |conn| { + select_uuids_by_my_swaps_filter(&conn, &filter, paging_options, db_id) + })?) } } } @@ -147,17 +156,17 @@ mod wasm_impl { let stringified_error = e.to_string(); match e { // We don't expect that the `String` and `u32` types serialization to fail. - CursorError::ErrorSerializingIndexFieldValue {..} + CursorError::ErrorSerializingIndexFieldValue { .. } // We don't expect that the `String` and `u32` types deserialization to fail. - | CursorError::ErrorDeserializingIndexValue {..} - | CursorError::ErrorOpeningCursor {..} - | CursorError::AdvanceError {..} - | CursorError::InvalidKeyRange {..} - | CursorError::TypeMismatch {..} - | CursorError::IncorrectNumberOfKeysPerIndex {..} + | CursorError::ErrorDeserializingIndexValue { .. } + | CursorError::ErrorOpeningCursor { .. } + | CursorError::AdvanceError { .. } + | CursorError::InvalidKeyRange { .. } + | CursorError::TypeMismatch { .. } + | CursorError::IncorrectNumberOfKeysPerIndex { .. } | CursorError::UnexpectedState(..) - | CursorError::IncorrectUsage {..} => MySwapsError::InternalError(stringified_error), - CursorError::ErrorDeserializingItem {..} => MySwapsError::ErrorDeserializingItem(stringified_error), + | CursorError::IncorrectUsage { .. } => MySwapsError::InternalError(stringified_error), + CursorError::ErrorDeserializingItem { .. } => MySwapsError::ErrorDeserializingItem(stringified_error), } } } @@ -175,9 +184,10 @@ mod wasm_impl { uuid: Uuid, started_at: u64, swap_type: u8, + db_id: Option<&str>, ) -> MySwapsResult<()> { let swap_ctx = SwapsContext::from_ctx(&self.ctx).map_to_mm(MySwapsError::InternalError)?; - let db = swap_ctx.swap_db().await?; + let db = swap_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let my_swaps_table = transaction.table::().await?; @@ -197,9 +207,10 @@ mod wasm_impl { &self, filter: &MySwapsFilter, paging_options: Option<&PagingOptions>, + db_id: &str, ) -> MySwapsResult { let swap_ctx = SwapsContext::from_ctx(&self.ctx).map_to_mm(MySwapsError::InternalError)?; - let db = swap_ctx.swap_db().await?; + let db = swap_ctx.swap_db(Some(db_id)).await?; let transaction = db.transaction().await?; let my_swaps_table = transaction.table::().await?; @@ -257,7 +268,7 @@ mod wasm_impl { .map(|(_item_id, item)| OrderedUuid::from(item)) .collect(); match paging_options { - Some(paging) => take_according_to_paging_opts(uuids, paging), + Some(paging) => take_according_to_paging_opts(uuids, paging, db_id), None => { let total_count = uuids.len(); Ok(MyRecentSwapsUuids { @@ -267,6 +278,7 @@ mod wasm_impl { .collect(), total_count, skipped: 0, + pubkey: db_id.to_string(), }) }, } @@ -276,6 +288,7 @@ mod wasm_impl { pub(super) fn take_according_to_paging_opts( uuids: BTreeSet, paging: &PagingOptions, + db_id: &str, ) -> MySwapsResult { let total_count = uuids.len(); @@ -302,6 +315,7 @@ mod wasm_impl { uuids_and_types, total_count, skipped: skip, + pubkey: db_id.to_string(), }) } @@ -331,6 +345,7 @@ mod wasm_tests { use crate::lp_swap::{LEGACY_SWAP_TYPE, MAKER_SWAP_V2_TYPE, TAKER_SWAP_V2_TYPE}; use common::log::wasm_log::register_wasm_log; use common::new_uuid; + use keys::hash::H160; use mm2_core::mm_ctx::MmCtxBuilder; use rand::seq::SliceRandom; use rand::Rng; @@ -365,6 +380,7 @@ mod wasm_tests { filters: MySwapsFilter, ) { let ctx = MmCtxBuilder::new().with_test_db_namespace().into_mm_arc(); + let pubkey = hex::encode(H160::default().as_slice()); let my_swaps = MySwapsStorage::new(ctx); let mut expected_uuids = BTreeSet::new(); @@ -385,13 +401,13 @@ mod wasm_tests { }); } my_swaps - .save_new_swap(my_coin, other_coin, uuid, started_at, swap_type) + .save_new_swap(my_coin, other_coin, uuid, started_at, swap_type, Some(&pubkey)) .await .expect("!MySwapsStorage::save_new_swap"); } let actual = my_swaps - .my_recent_swaps_with_filters(&filters, None) + .my_recent_swaps_with_filters(&filters, None, &pubkey) .await .expect("!MySwapsStorage::my_recent_swaps_with_filters"); @@ -403,6 +419,7 @@ mod wasm_tests { .collect(), total_count: expected_total_count, skipped: 0, + pubkey, }; assert_eq!(actual, expected); } @@ -411,6 +428,7 @@ mod wasm_tests { fn test_take_according_to_paging_opts() { register_wasm_log(); + let pubkey = hex::encode(H160::default().as_slice()); let uuids: BTreeSet = [ (1, "49c79ea4-e1eb-4fb2-a0ef-265bded0b77f", TAKER_SWAP_V2_TYPE), (2, "2f9afe84-7a89-4194-8947-45fba563118f", MAKER_SWAP_V2_TYPE), @@ -437,7 +455,7 @@ mod wasm_tests { page_number: NonZeroUsize::new(10).unwrap(), from_uuid: Some(Uuid::parse_str("8f5b267a-efa8-49d6-a92d-ec0523cca891").unwrap()), }; - let actual = take_according_to_paging_opts(uuids.clone(), &paging).unwrap(); + let actual = take_according_to_paging_opts(uuids.clone(), &paging, &pubkey).unwrap(); let expected = MyRecentSwapsUuids { uuids_and_types: vec![ ( @@ -451,6 +469,7 @@ mod wasm_tests { ], total_count: uuids.len(), skipped: 6, + pubkey: pubkey.clone(), }; assert_eq!(actual, expected); @@ -459,7 +478,7 @@ mod wasm_tests { page_number: NonZeroUsize::new(2).unwrap(), from_uuid: None, }; - let actual = take_according_to_paging_opts(uuids.clone(), &paging).unwrap(); + let actual = take_according_to_paging_opts(uuids.clone(), &paging, &pubkey).unwrap(); let expected = MyRecentSwapsUuids { uuids_and_types: vec![ ( @@ -477,6 +496,7 @@ mod wasm_tests { ], total_count: uuids.len(), skipped: 3, + pubkey: pubkey.clone(), }; assert_eq!(actual, expected); @@ -489,7 +509,7 @@ mod wasm_tests { // unknown UUID from_uuid: Some(from_uuid), }; - let actual = take_according_to_paging_opts(uuids, &paging) + let actual = take_according_to_paging_opts(uuids, &paging, &pubkey) .expect_err("'take_according_to_paging_opts' must return an error"); assert_eq!(actual.into_inner(), MySwapsError::FromUuidNotFound(from_uuid)); } diff --git a/mm2src/mm2_main/src/lp_swap/pubkey_banning.rs b/mm2src/mm2_main/src/lp_swap/pubkey_banning.rs index 5aa8f94103..085e923358 100644 --- a/mm2src/mm2_main/src/lp_swap/pubkey_banning.rs +++ b/mm2src/mm2_main/src/lp_swap/pubkey_banning.rs @@ -36,6 +36,7 @@ pub fn is_pubkey_banned(ctx: &MmArc, pubkey: &H256Json) -> bool { } pub async fn list_banned_pubkeys_rpc(ctx: MmArc) -> Result>, String> { + // TODO: db_id let ctx = try_s!(SwapsContext::from_ctx(&ctx)); let res = try_s!(json::to_vec(&json!({ "result": *try_s!(ctx.banned_pubkeys.lock()), @@ -51,6 +52,7 @@ struct BanPubkeysReq { pub async fn ban_pubkey_rpc(ctx: MmArc, req: Json) -> Result>, String> { let req: BanPubkeysReq = try_s!(json::from_value(req)); + // TODO: db_id let ctx = try_s!(SwapsContext::from_ctx(&ctx)); let mut banned_pubs = try_s!(ctx.banned_pubkeys.lock()); diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index 0d139df62f..9ad9298bd6 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -94,6 +94,8 @@ fn recreate_maker_swap(ctx: MmArc, taker_swap: TakerSavedSwap) -> RecreateSwapRe mm_version: Some(ctx.mm_version.clone()), success_events: MAKER_SUCCESS_EVENTS.iter().map(|event| event.to_string()).collect(), error_events: MAKER_ERROR_EVENTS.iter().map(|event| event.to_string()).collect(), + taker_coin_account_id: taker_swap.taker_coin_account_id, + maker_coin_account_id: taker_swap.maker_coin_account_id, }; let mut event_it = taker_swap.events.into_iter(); @@ -149,6 +151,7 @@ fn recreate_maker_swap(ctx: MmArc, taker_swap: TakerSavedSwap) -> RecreateSwapRe maker_coin_htlc_pubkey: negotiated_event.maker_coin_htlc_pubkey, taker_coin_htlc_pubkey: negotiated_event.taker_coin_htlc_pubkey, p2p_privkey: None, + db_id: maker_swap.db_id(), }); maker_swap.events.push(MakerSavedEvent { timestamp: started_event_timestamp, @@ -295,6 +298,8 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate mm_version: Some(ctx.mm_version.clone()), success_events: TAKER_SUCCESS_EVENTS.iter().map(|event| event.to_string()).collect(), error_events: TAKER_ERROR_EVENTS.iter().map(|event| event.to_string()).collect(), + taker_coin_account_id: maker_swap.taker_coin_account_id, + maker_coin_account_id: maker_swap.maker_coin_account_id, }; let mut event_it = maker_swap.events.into_iter(); @@ -347,6 +352,7 @@ async fn recreate_taker_swap(ctx: MmArc, maker_swap: MakerSavedSwap) -> Recreate maker_coin_htlc_pubkey: negotiated_event.maker_coin_htlc_pubkey, taker_coin_htlc_pubkey: negotiated_event.taker_coin_htlc_pubkey, p2p_privkey: None, + db_id: taker_swap.db_id(), }); taker_swap.events.push(TakerSavedEvent { timestamp: started_event_timestamp, diff --git a/mm2src/mm2_main/src/lp_swap/saved_swap.rs b/mm2src/mm2_main/src/lp_swap/saved_swap.rs index de6d379b5c..d57a19aa70 100644 --- a/mm2src/mm2_main/src/lp_swap/saved_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/saved_swap.rs @@ -29,7 +29,7 @@ pub enum SavedSwapError { InternalError(String), } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "type")] pub enum SavedSwap { Maker(MakerSavedSwap), @@ -110,7 +110,7 @@ impl SavedSwap { }; match self { SavedSwap::Maker(saved) => { - let (maker_swap, _) = try_s!(MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, saved)); + let (maker_swap, _) = try_s!(MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, saved).await); Ok(try_s!(maker_swap.recover_funds().await)) }, SavedSwap::Taker(saved) => { @@ -153,32 +153,47 @@ impl SavedSwap { SavedSwap::Taker(taker) => taker.fetch_and_set_usd_prices().await, } } + + pub async fn account_db_id(&self) -> Option { + match self { + SavedSwap::Maker(swap) => swap.db_id(), + SavedSwap::Taker(swap) => swap.db_id(), + } + } } #[async_trait] pub trait SavedSwapIo { - async fn load_my_swap_from_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult>; + async fn load_my_swap_from_db(ctx: &MmArc, db_id: Option<&str>, uuid: Uuid) -> SavedSwapResult>; - async fn load_all_my_swaps_from_db(ctx: &MmArc) -> SavedSwapResult>; + async fn load_all_my_swaps_from_db(ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult>; #[cfg(not(target_arch = "wasm32"))] - async fn load_from_maker_stats_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult>; + async fn load_from_maker_stats_db( + ctx: &MmArc, + db_id: Option<&str>, + uuid: Uuid, + ) -> SavedSwapResult>; #[cfg(not(target_arch = "wasm32"))] - async fn load_all_from_maker_stats_db(ctx: &MmArc) -> SavedSwapResult>; + async fn load_all_from_maker_stats_db(ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult>; #[cfg(not(target_arch = "wasm32"))] - async fn load_from_taker_stats_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult>; + async fn load_from_taker_stats_db( + ctx: &MmArc, + db_id: Option<&str>, + uuid: Uuid, + ) -> SavedSwapResult>; #[cfg(not(target_arch = "wasm32"))] - async fn load_all_from_taker_stats_db(ctx: &MmArc) -> SavedSwapResult>; + async fn load_all_from_taker_stats_db(ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult>; /// Save the serialized `SavedSwap` to the swaps db. - async fn save_to_db(&self, ctx: &MmArc) -> SavedSwapResult<()>; + async fn save_to_db(&self, ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult<()>; /// Save the inner maker/taker swap to the corresponding stats db. #[cfg(not(target_arch = "wasm32"))] - async fn save_to_stats_db(&self, ctx: &MmArc) -> SavedSwapResult<()>; + async fn save_to_stats_db(&self, ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult<()>; } #[cfg(not(target_arch = "wasm32"))] @@ -206,51 +221,69 @@ mod native_impl { #[async_trait] impl SavedSwapIo for SavedSwap { - async fn load_my_swap_from_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult> { - let path = my_swap_file_path(ctx, &uuid); + async fn load_my_swap_from_db( + ctx: &MmArc, + db_id: Option<&str>, + uuid: Uuid, + ) -> SavedSwapResult> { + let path = my_swap_file_path(ctx, db_id, &uuid); Ok(read_json(&path).await?) } - async fn load_all_my_swaps_from_db(ctx: &MmArc) -> SavedSwapResult> { - let path = my_swaps_dir(ctx); + async fn load_all_my_swaps_from_db(ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult> { + let path = my_swaps_dir(ctx, db_id); Ok(read_dir_json(&path).await?) } - async fn load_from_maker_stats_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult> { - let path = stats_maker_swap_file_path(ctx, &uuid); + async fn load_from_maker_stats_db( + ctx: &MmArc, + db_id: Option<&str>, + uuid: Uuid, + ) -> SavedSwapResult> { + let path = stats_maker_swap_file_path(ctx, db_id, &uuid); Ok(read_json(&path).await?) } - async fn load_all_from_maker_stats_db(ctx: &MmArc) -> SavedSwapResult> { - let path = stats_maker_swap_dir(ctx); + async fn load_all_from_maker_stats_db( + ctx: &MmArc, + db_id: Option<&str>, + ) -> SavedSwapResult> { + let path = stats_maker_swap_dir(ctx, db_id); Ok(read_dir_json(&path).await?) } - async fn load_from_taker_stats_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult> { - let path = stats_taker_swap_file_path(ctx, &uuid); + async fn load_from_taker_stats_db( + ctx: &MmArc, + db_id: Option<&str>, + uuid: Uuid, + ) -> SavedSwapResult> { + let path = stats_taker_swap_file_path(ctx, db_id, &uuid); Ok(read_json(&path).await?) } - async fn load_all_from_taker_stats_db(ctx: &MmArc) -> SavedSwapResult> { - let path = stats_taker_swap_dir(ctx); + async fn load_all_from_taker_stats_db( + ctx: &MmArc, + db_id: Option<&str>, + ) -> SavedSwapResult> { + let path = stats_taker_swap_dir(ctx, db_id); Ok(read_dir_json(&path).await?) } - async fn save_to_db(&self, ctx: &MmArc) -> SavedSwapResult<()> { - let path = my_swap_file_path(ctx, self.uuid()); + async fn save_to_db(&self, ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult<()> { + let path = my_swap_file_path(ctx, db_id, self.uuid()); write_json(self, &path, USE_TMP_FILE).await?; Ok(()) } /// Save the inner maker/taker swap to the corresponding stats db. - async fn save_to_stats_db(&self, ctx: &MmArc) -> SavedSwapResult<()> { + async fn save_to_stats_db(&self, ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult<()> { match self { SavedSwap::Maker(maker) => { - let path = stats_maker_swap_file_path(ctx, &maker.uuid); + let path = stats_maker_swap_file_path(ctx, db_id, &maker.uuid); write_json(self, &path, USE_TMP_FILE).await?; }, SavedSwap::Taker(taker) => { - let path = stats_taker_swap_file_path(ctx, &taker.uuid); + let path = stats_taker_swap_file_path(ctx, db_id, &taker.uuid); write_json(self, &path, USE_TMP_FILE).await?; }, } @@ -287,9 +320,9 @@ mod wasm_impl { Ok(migrations.first().map(|(_, m)| m.migration).unwrap_or_default()) } - pub async fn migrate_swaps_data(ctx: &MmArc) -> MmResult<(), SavedSwapError> { + pub async fn migrate_swaps_data(ctx: &MmArc, db_id: Option<&str>) -> MmResult<(), SavedSwapError> { let swaps_ctx = SwapsContext::from_ctx(ctx).map_to_mm(SavedSwapError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let migration_table = transaction.table::().await?; @@ -328,7 +361,7 @@ mod wasm_impl { return MmError::err(SavedSwapError::InternalError(format!( "Unsupported migration {}", unsupported - ))) + ))); }, } migration += 1; @@ -374,9 +407,13 @@ mod wasm_impl { #[async_trait] impl SavedSwapIo for SavedSwap { - async fn load_my_swap_from_db(ctx: &MmArc, uuid: Uuid) -> SavedSwapResult> { + async fn load_my_swap_from_db( + ctx: &MmArc, + db_id: Option<&str>, + uuid: Uuid, + ) -> SavedSwapResult> { let swaps_ctx = SwapsContext::from_ctx(ctx).map_to_mm(SavedSwapError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -388,9 +425,9 @@ mod wasm_impl { json::from_value(saved_swap_json).map_to_mm(|e| SavedSwapError::ErrorDeserializing(e.to_string())) } - async fn load_all_my_swaps_from_db(ctx: &MmArc) -> SavedSwapResult> { + async fn load_all_my_swaps_from_db(ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult> { let swaps_ctx = SwapsContext::from_ctx(ctx).map_to_mm(SavedSwapError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -403,7 +440,7 @@ mod wasm_impl { .collect() } - async fn save_to_db(&self, ctx: &MmArc) -> SavedSwapResult<()> { + async fn save_to_db(&self, ctx: &MmArc, db_id: Option<&str>) -> SavedSwapResult<()> { let saved_swap = json::to_value(self).map_to_mm(|e| SavedSwapError::ErrorSerializing(e.to_string()))?; let saved_swap_item = SavedSwapTable { uuid: *self.uuid(), @@ -411,7 +448,7 @@ mod wasm_impl { }; let swaps_ctx = SwapsContext::from_ctx(ctx).map_to_mm(SavedSwapError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -436,7 +473,7 @@ mod tests { async fn get_all_items(ctx: &MmArc) -> Vec<(ItemId, SavedSwapTable)> { let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); - let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); + let db = swaps_ctx.swap_db(None).await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction .table::() @@ -456,7 +493,8 @@ mod tests { saved_swap: json::to_value(&saved_swap).unwrap(), }; - saved_swap.save_to_db(&ctx).await.expect("!save_to_db"); + let accound_id = None; + saved_swap.save_to_db(&ctx, accound_id).await.expect("!save_to_db"); let first_item_id = { let items = get_all_items(&ctx).await; @@ -474,7 +512,7 @@ mod tests { }; assert_ne!(first_saved_item, second_saved_item); - saved_swap.save_to_db(&ctx).await.expect("!save_to_db"); + saved_swap.save_to_db(&ctx, accound_id).await.expect("!save_to_db"); { let items = get_all_items(&ctx).await; @@ -484,7 +522,7 @@ mod tests { assert_eq!(item, second_saved_item); } - let actual_saved_swap = SavedSwap::load_my_swap_from_db(&ctx, *saved_swap.uuid()) + let actual_saved_swap = SavedSwap::load_my_swap_from_db(&ctx, accound_id, *saved_swap.uuid()) .await .expect("!load_from_db") .expect("Swap not found"); @@ -500,7 +538,7 @@ mod tests { let ctx = MmCtxBuilder::new().with_test_db_namespace().into_mm_arc(); let swaps_ctx = SwapsContext::from_ctx(&ctx).unwrap(); - let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); + let db = swaps_ctx.swap_db(None).await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction .table::() @@ -521,11 +559,12 @@ mod tests { let saved_swap_str = r#"{"type":"Maker","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":"e1c9bd12a83f810813dc078ac398069b63d56bf1e94657def995c43cd1975302","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 saved_swap: SavedSwap = json::from_str(saved_swap_str).unwrap(); - saved_swap.save_to_db(&ctx).await.expect("!save_to_db"); + let account_id = None; + saved_swap.save_to_db(&ctx, account_id).await.expect("!save_to_db"); let swaps_ctx = SwapsContext::from_ctx(&ctx).unwrap(); { - let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); + let db = swaps_ctx.swap_db(account_id).await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction .table::() @@ -543,9 +582,9 @@ mod tests { table.add_item(&filter).await.unwrap(); } - wasm_impl::migrate_swaps_data(&ctx).await.unwrap(); + wasm_impl::migrate_swaps_data(&ctx, account_id).await.unwrap(); - let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); + let db = swaps_ctx.swap_db(account_id).await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction .table::() diff --git a/mm2src/mm2_main/src/lp_swap/swap_lock.rs b/mm2src/mm2_main/src/lp_swap/swap_lock.rs index f4347dde3b..d4ef3dfc19 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_lock.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_lock.rs @@ -24,7 +24,7 @@ pub enum SwapLockError { #[async_trait] pub trait SwapLockOps: Sized { - async fn lock(ctx: &MmArc, swap_uuid: Uuid, ttl_sec: f64) -> SwapLockResult>; + async fn lock(ctx: &MmArc, swap_uuid: Uuid, ttl_sec: f64, db_id: Option<&str>) -> SwapLockResult>; async fn touch(&self) -> SwapLockResult<()>; } @@ -56,8 +56,13 @@ mod native_lock { #[async_trait] impl SwapLockOps for SwapLock { - async fn lock(ctx: &MmArc, swap_uuid: Uuid, ttl_sec: f64) -> SwapLockResult> { - let lock_path = my_swaps_dir(ctx).join(format!("{}.lock", swap_uuid)); + async fn lock( + ctx: &MmArc, + swap_uuid: Uuid, + ttl_sec: f64, + db_id: Option<&str>, + ) -> SwapLockResult> { + let lock_path = my_swaps_dir(ctx, db_id).join(format!("{}.lock", swap_uuid)); let file_lock = some_or_return_ok_none!(FileLock::lock(lock_path, ttl_sec)?); Ok(Some(SwapLock { file_lock })) @@ -108,14 +113,16 @@ mod wasm_lock { swap_uuid: Uuid, /// The identifier of the timestamp record in the `SwapLockTable`. pub(super) record_id: ItemId, + db_id: Option, } impl Drop for SwapLock { fn drop(&mut self) { let ctx = self.ctx.clone(); let record_id = self.record_id; + let db_id = self.db_id.to_owned(); let fut = async move { - if let Err(e) = Self::release(ctx, record_id).await { + if let Err(e) = Self::release(ctx, record_id, db_id.as_deref()).await { error!("Error realising the SwapLock: {}", e); } debug!("SwapLock::drop] Finish"); @@ -126,9 +133,9 @@ mod wasm_lock { #[async_trait] impl SwapLockOps for SwapLock { - async fn lock(ctx: &MmArc, uuid: Uuid, ttl_sec: f64) -> SwapLockResult> { + async fn lock(ctx: &MmArc, uuid: Uuid, ttl_sec: f64, db_id: Option<&str>) -> SwapLockResult> { let swaps_ctx = SwapsContext::from_ctx(ctx).map_to_mm(SwapLockError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -153,12 +160,13 @@ mod wasm_lock { ctx: ctx.clone(), swap_uuid: uuid, record_id, + db_id: db_id.map(|s| s.to_string()), })) } async fn touch(&self) -> SwapLockResult<()> { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).map_to_mm(SwapLockError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(self.db_id.as_deref()).await?; let item = SwapLockTable { uuid: self.swap_uuid, @@ -179,9 +187,9 @@ mod wasm_lock { } impl SwapLock { - async fn release(ctx: MmArc, record_id: ItemId) -> SwapLockResult<()> { + async fn release(ctx: MmArc, record_id: ItemId, db_id: Option<&str>) -> SwapLockResult<()> { let swaps_ctx = SwapsContext::from_ctx(&ctx).map_to_mm(SwapLockError::InternalError)?; - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; table.delete_item(record_id).await?; @@ -207,7 +215,7 @@ mod tests { async fn get_all_items(ctx: &MmArc) -> Vec<(ItemId, SwapLockTable)> { let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); - let db = swaps_ctx.swap_db().await.expect("Error getting SwapDb"); + let db = swaps_ctx.swap_db(None).await.expect("Error getting SwapDb"); let transaction = db.transaction().await.expect("Error creating transaction"); let table = transaction.table::().await.expect("Error opening table"); table.get_all_items().await.expect("Error getting items") @@ -219,7 +227,7 @@ mod tests { let uuid = new_uuid(); let started_at = now_sec(); - let swap_lock = SwapLock::lock(&ctx, uuid, 10.) + let swap_lock = SwapLock::lock(&ctx, uuid, 10., None) .await .expect("!SwapLock::lock") .expect("SwapLock::lock must return a value"); @@ -246,11 +254,11 @@ mod tests { async fn test_file_lock_should_return_none_if_lock_acquired() { let ctx = MmCtxBuilder::new().with_test_db_namespace().into_mm_arc(); let uuid = new_uuid(); - let _lock = SwapLock::lock(&ctx, uuid, 10.) + let _lock = SwapLock::lock(&ctx, uuid, 10., None) .await .expect("!SwapLock::lock") .expect("SwapLock::lock must return a value"); - let new_lock = SwapLock::lock(&ctx, uuid, 10.).await.expect("!SwapLock::lock"); + let new_lock = SwapLock::lock(&ctx, uuid, 10., None).await.expect("!SwapLock::lock"); assert!( new_lock.is_none(), "SwapLock::lock must return None if the lock has already been acquired" @@ -263,7 +271,7 @@ mod tests { let uuid = new_uuid(); let started_at = now_sec(); - let first_lock = SwapLock::lock(&ctx, uuid, 1.) + let first_lock = SwapLock::lock(&ctx, uuid, 1., None) .await .expect("!SwapLock::lock") .expect("SwapLock::lock must return a value"); @@ -278,7 +286,7 @@ mod tests { Timer::sleep(2.).await; - let second_lock = SwapLock::lock(&ctx, uuid, 1.) + let second_lock = SwapLock::lock(&ctx, uuid, 1., None) .await .expect("!SwapLock::lock") .expect("SwapLock::lock must return a value after the last ttl is over"); diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs index 686d263d5a..5676636995 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs @@ -37,7 +37,7 @@ pub struct ActiveSwapV2Info { } /// DB representation of tx preimage with signature -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct StoredTxPreimage { pub preimage: BytesJson, pub signature: BytesJson, @@ -98,15 +98,28 @@ pub struct SwapRecreateCtx { } #[cfg(not(target_arch = "wasm32"))] -pub(super) async fn has_db_record_for(ctx: MmArc, id: &Uuid) -> MmResult { +pub(super) async fn has_db_record_for( + ctx: MmArc, + id: &Uuid, + db_id: Option<&str>, +) -> MmResult { let id_str = id.to_string(); - Ok(async_blocking(move || does_swap_exist(&ctx.sqlite_connection(), &id_str)).await?) + let db_id = db_id.map(|e| e.to_string()); + + Ok( + async_blocking(move || ctx.run_sql_query(db_id.as_deref(), move |conn| does_swap_exist(&conn, &id_str))) + .await?, + ) } #[cfg(target_arch = "wasm32")] -pub(super) async fn has_db_record_for(ctx: MmArc, id: &Uuid) -> MmResult { +pub(super) async fn has_db_record_for( + ctx: MmArc, + id: &Uuid, + db_id: Option<&str>, +) -> MmResult { let swaps_ctx = SwapsContext::from_ctx(&ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let maybe_item = table.get_item_by_unique_index("uuid", id).await?; @@ -118,18 +131,24 @@ pub(super) async fn store_swap_event( ctx: MmArc, id: Uuid, event: T::Event, + db_id: Option<&str>, ) -> MmResult<(), SwapStateMachineError> where T::Event: DeserializeOwned + Serialize + Send + 'static, { - let id_str = id.to_string(); + let db_id = db_id.map(|e| e.to_string()); async_blocking(move || { - let events_json = get_swap_events(&ctx.sqlite_connection(), &id_str)?; + let id_str = id.to_string(); + let events_json = ctx.run_sql_query(db_id.as_deref(), move |conn| get_swap_events(&conn, &id_str))?; let mut events: Vec = serde_json::from_str(&events_json)?; events.push(event); drop_mutability!(events); + let serialized_events = serde_json::to_string(&events)?; - update_swap_events(&ctx.sqlite_connection(), &id_str, &serialized_events)?; + let id_str = id.to_string(); + ctx.run_sql_query(db_id.as_deref(), move |conn| { + update_swap_events(&conn, &id_str, &serialized_events) + })?; Ok(()) }) .await @@ -140,9 +159,10 @@ pub(super) async fn store_swap_event, ) -> MmResult<(), SwapStateMachineError> { let swaps_ctx = SwapsContext::from_ctx(&ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -163,9 +183,13 @@ pub(super) async fn store_swap_event(ctx: &MmArc, id: Uuid) -> MmResult { +pub(super) async fn get_swap_repr( + ctx: &MmArc, + id: Uuid, + db_id: Option<&str>, +) -> MmResult { let swaps_ctx = SwapsContext::from_ctx(ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; @@ -182,10 +206,14 @@ pub(super) async fn get_swap_repr(ctx: &MmArc, id: Uuid) -> pub(super) async fn get_unfinished_swaps_uuids( ctx: MmArc, swap_type: u8, + db_id: Option<&str>, ) -> MmResult, SwapStateMachineError> { + let db_id = db_id.map(|e| e.to_string()); async_blocking(move || { - select_unfinished_swaps_uuids(&ctx.sqlite_connection(), swap_type) - .map_to_mm(|e| SwapStateMachineError::StorageError(e.to_string())) + ctx.run_sql_query(db_id.as_deref(), move |conn| { + select_unfinished_swaps_uuids(&conn, swap_type) + .map_to_mm(|e| SwapStateMachineError::StorageError(e.to_string())) + }) }) .await } @@ -194,13 +222,13 @@ pub(super) async fn get_unfinished_swaps_uuids( pub(super) async fn get_unfinished_swaps_uuids( ctx: MmArc, swap_type: u8, + db_id: Option<&str>, ) -> MmResult, SwapStateMachineError> { let index = MultiIndex::new(IS_FINISHED_SWAP_TYPE_INDEX) .with_value(BoolAsInt::new(false))? .with_value(swap_type)?; - let swaps_ctx = SwapsContext::from_ctx(&ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let table_items = table.get_items_by_multi_index(index).await?; @@ -209,14 +237,28 @@ pub(super) async fn get_unfinished_swaps_uuids( } #[cfg(not(target_arch = "wasm32"))] -pub(super) async fn mark_swap_as_finished(ctx: MmArc, id: Uuid) -> MmResult<(), SwapStateMachineError> { - async_blocking(move || Ok(set_swap_is_finished(&ctx.sqlite_connection(), &id.to_string())?)).await +pub(super) async fn mark_swap_as_finished( + ctx: MmArc, + id: Uuid, + db_id: Option<&str>, +) -> MmResult<(), SwapStateMachineError> { + let db_id = db_id.map(|e| e.to_string()); + async_blocking(move || { + ctx.run_sql_query(db_id.as_deref(), move |conn| { + Ok(set_swap_is_finished(&conn, &id.to_string())?) + }) + }) + .await } #[cfg(target_arch = "wasm32")] -pub(super) async fn mark_swap_as_finished(ctx: MmArc, id: Uuid) -> MmResult<(), SwapStateMachineError> { +pub(super) async fn mark_swap_as_finished( + ctx: MmArc, + id: Uuid, + db_id: Option<&str>, +) -> MmResult<(), SwapStateMachineError> { let swaps_ctx = SwapsContext::from_ctx(&ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let mut item = match table.get_item_by_unique_index("uuid", id).await? { @@ -255,10 +297,14 @@ pub(super) fn clean_up_context_impl(ctx: &MmArc, uuid: &Uuid, maker_coin: &str, } } -pub(super) async fn acquire_reentrancy_lock_impl(ctx: &MmArc, uuid: Uuid) -> MmResult { +pub(super) async fn acquire_reentrancy_lock_impl( + ctx: &MmArc, + uuid: Uuid, + db_id: Option<&str>, +) -> MmResult { let mut attempts = 0; loop { - match SwapLock::lock(ctx, uuid, 40.).await? { + match SwapLock::lock(ctx, uuid, 40., db_id).await? { Some(l) => break Ok(l), None => { if attempts >= 1 { @@ -290,6 +336,11 @@ pub(super) trait GetSwapCoins { fn maker_coin(&self) -> &str; fn taker_coin(&self) -> &str; + + // Represenets the taker's coin db_id(coin's pubkey) used for this swap + fn taker_coin_db_id(&self) -> &Option; + // Represenets the maker's coin db_id(coin's pubkey) used for this swap + fn maker_coin_db_id(&self) -> &Option; } /// Generic function for upgraded swaps kickstart handling. @@ -308,10 +359,19 @@ pub(super) async fn swap_kickstart_handler< T::RecreateError: std::fmt::Display, { let taker_coin_ticker = swap_repr.taker_coin(); - + let expected_taker_db_id = swap_repr.taker_coin_db_id(); let taker_coin = loop { match lp_coinfind(&ctx, taker_coin_ticker).await { - Ok(Some(c)) => break c, + Ok(Some(c)) => { + if &c.account_db_id().await == expected_taker_db_id { + break c; + } + info!( + "Can't kickstart taker swap {} until the coin {} is activated with unexpected pubkey:", + uuid, taker_coin_ticker + ); + Timer::sleep(1.).await; + }, Ok(None) => { info!( "Can't kickstart the swap {} until the coin {} is activated", @@ -327,10 +387,19 @@ pub(super) async fn swap_kickstart_handler< }; let maker_coin_ticker = swap_repr.maker_coin(); - + let expected_maker_db_id = swap_repr.maker_coin_db_id(); let maker_coin = loop { match lp_coinfind(&ctx, maker_coin_ticker).await { - Ok(Some(c)) => break c, + Ok(Some(c)) => { + if &c.account_db_id().await == expected_maker_db_id { + break c; + } + info!( + "Can't kickstart maker swap {} until the coin {} is activated with unexpected_pubkey", + uuid, taker_coin_ticker + ); + Timer::sleep(1.).await; + }, Ok(None) => { info!( "Can't kickstart the swap {} until the coin {} is activated", diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs index e3f57e8400..aad7f9300f 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs @@ -5,6 +5,7 @@ use super::taker_swap::TakerSavedSwap; use super::taker_swap_v2::TakerSwapEvent; use super::{active_swaps, MySwapsFilter, SavedSwap, SavedSwapError, SavedSwapIo, LEGACY_SWAP_TYPE, MAKER_SWAP_V2_TYPE, TAKER_SWAP_V2_TYPE}; +use coins::{find_unique_account_ids_active, find_unique_account_ids_any}; use common::log::{error, warn}; use common::{calc_total_pages, HttpStatusCode, PagingOptions}; use derive_more::Display; @@ -34,19 +35,18 @@ cfg_wasm32!( ); #[cfg(not(target_arch = "wasm32"))] -pub(super) async fn get_swap_type(ctx: &MmArc, uuid: &Uuid) -> MmResult, SqlError> { +pub(super) async fn get_swap_type(ctx: &MmArc, uuid: &Uuid, db_id: Option<&str>) -> MmResult, SqlError> { let ctx = ctx.clone(); let uuid = uuid.to_string(); + let db_id = db_id.map(|e| e.to_string()); async_blocking(move || { const SELECT_SWAP_TYPE_BY_UUID: &str = "SELECT swap_type FROM my_swaps WHERE uuid = :uuid;"; - let maybe_swap_type = query_single_row( - &ctx.sqlite_connection(), - SELECT_SWAP_TYPE_BY_UUID, - &[(":uuid", uuid.as_str())], - |row| row.get(0), - )?; - Ok(maybe_swap_type) + Ok(ctx.run_sql_query(db_id.as_deref(), move |conn| { + query_single_row(&conn, SELECT_SWAP_TYPE_BY_UUID, &[(":uuid", uuid.as_str())], |row| { + row.get(0) + }) + })?) }) .await } @@ -76,11 +76,15 @@ impl From for SwapV2DbError { } #[cfg(target_arch = "wasm32")] -pub(super) async fn get_swap_type(ctx: &MmArc, uuid: &Uuid) -> MmResult, SwapV2DbError> { +pub(super) async fn get_swap_type( + ctx: &MmArc, + uuid: &Uuid, + db_id: Option<&str>, +) -> MmResult, SwapV2DbError> { use crate::lp_swap::swap_wasm_db::MySwapsFiltersTable; let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let item = match table.get_item_by_unique_index("uuid", uuid).await? { @@ -91,7 +95,7 @@ pub(super) async fn get_swap_type(ctx: &MmArc, uuid: &Uuid) -> MmResult { my_coin: String, other_coin: String, @@ -149,34 +153,39 @@ impl MySwapForRpc { pub(super) async fn get_maker_swap_data_for_rpc( ctx: &MmArc, uuid: &Uuid, + db_id: Option<&str>, ) -> MmResult>, SqlError> { - get_swap_data_for_rpc_impl(ctx, uuid).await + get_swap_data_for_rpc_impl(ctx, uuid, db_id).await } #[cfg(not(target_arch = "wasm32"))] pub(super) async fn get_taker_swap_data_for_rpc( ctx: &MmArc, uuid: &Uuid, + db_id: Option<&str>, ) -> MmResult>, SqlError> { - get_swap_data_for_rpc_impl(ctx, uuid).await + get_swap_data_for_rpc_impl(ctx, uuid, db_id).await } #[cfg(not(target_arch = "wasm32"))] async fn get_swap_data_for_rpc_impl( ctx: &MmArc, uuid: &Uuid, + db_id: Option<&str>, ) -> MmResult>, SqlError> { let ctx = ctx.clone(); let uuid = uuid.to_string(); + let db_id = db_id.map(|e| e.to_string()); async_blocking(move || { - let swap_data = query_single_row( - &ctx.sqlite_connection(), - SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID, - &[(":uuid", uuid.as_str())], - MySwapForRpc::from_row, - )?; - Ok(swap_data) + Ok(ctx.run_sql_query(db_id.as_deref(), move |conn| { + query_single_row( + &conn, + SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID, + &[(":uuid", uuid.as_str())], + MySwapForRpc::from_row, + ) + })?) }) .await } @@ -185,9 +194,10 @@ async fn get_swap_data_for_rpc_impl( pub(super) async fn get_maker_swap_data_for_rpc( ctx: &MmArc, uuid: &Uuid, + db_id: Option<&str>, ) -> MmResult>, SwapV2DbError> { let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let item = match table.get_item_by_unique_index("uuid", uuid).await? { @@ -225,9 +235,10 @@ pub(super) async fn get_maker_swap_data_for_rpc( pub(super) async fn get_taker_swap_data_for_rpc( ctx: &MmArc, uuid: &Uuid, + db_id: Option<&str>, ) -> MmResult>, SwapV2DbError> { let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(db_id).await?; let transaction = db.transaction().await?; let table = transaction.table::().await?; let item = match table.get_item_by_unique_index("uuid", uuid).await? { @@ -261,7 +272,7 @@ pub(super) async fn get_taker_swap_data_for_rpc( })) } -#[derive(Serialize)] +#[derive(Clone, Serialize)] #[serde(tag = "swap_type", content = "swap_data")] pub(crate) enum SwapRpcData { MakerV1(MakerSavedSwap), @@ -292,23 +303,24 @@ impl From for GetSwapDataErr { async fn get_swap_data_by_uuid_and_type( ctx: &MmArc, + db_id: Option<&str>, uuid: Uuid, swap_type: u8, ) -> MmResult, GetSwapDataErr> { match swap_type { LEGACY_SWAP_TYPE => { - let saved_swap = SavedSwap::load_my_swap_from_db(ctx, uuid).await?; + let saved_swap = SavedSwap::load_my_swap_from_db(ctx, db_id, uuid).await?; Ok(saved_swap.map(|swap| match swap { SavedSwap::Maker(m) => SwapRpcData::MakerV1(m), SavedSwap::Taker(t) => SwapRpcData::TakerV1(t), })) }, MAKER_SWAP_V2_TYPE => { - let data = get_maker_swap_data_for_rpc(ctx, &uuid).await?; + let data = get_maker_swap_data_for_rpc(ctx, &uuid, db_id).await?; Ok(data.map(SwapRpcData::MakerV2)) }, TAKER_SWAP_V2_TYPE => { - let data = get_taker_swap_data_for_rpc(ctx, &uuid).await?; + let data = get_taker_swap_data_for_rpc(ctx, &uuid, db_id).await?; Ok(data.map(SwapRpcData::TakerV2)) }, unsupported => MmError::err(GetSwapDataErr::UnsupportedSwapType(unsupported)), @@ -362,12 +374,38 @@ pub(crate) async fn my_swap_status_rpc( ctx: MmArc, req: MySwapStatusRequest, ) -> MmResult { - let swap_type = get_swap_type(&ctx, &req.uuid) - .await? - .or_mm_err(|| MySwapStatusError::NoSwapWithUuid(req.uuid))?; - get_swap_data_by_uuid_and_type(&ctx, req.uuid, swap_type) - .await? - .or_mm_err(|| MySwapStatusError::NoSwapWithUuid(req.uuid)) + let db_ids = find_unique_account_ids_any(&ctx) + .await + .map_to_mm(|_| MySwapStatusError::DbError("No db_ids found".to_string()))?; + + let mut last_error = MySwapStatusError::NoSwapWithUuid(req.uuid); + + for db_id in db_ids.iter() { + match get_swap_type(&ctx, &req.uuid, Some(db_id)).await { + Ok(Some(swap_type)) => match get_swap_data_by_uuid_and_type(&ctx, Some(db_id), req.uuid, swap_type).await { + Ok(Some(swap_data)) => { + return Ok(swap_data); + }, + Ok(None) => { + last_error = MySwapStatusError::NoSwapWithUuid(req.uuid); + }, + Err(e) => { + last_error = MySwapStatusError::DbError(format!( + "Error loading swap data for uuid {} in db_id: {}: {}", + req.uuid, db_id, e + )); + }, + }, + Ok(None) => { + last_error = MySwapStatusError::NoSwapWithUuid(req.uuid); + }, + Err(e) => { + last_error = MySwapStatusError::DbError(format!("Error getting swap type for db_id: {}: {}", db_id, e)); + }, + } + } + + Err(last_error.into()) } #[derive(Deserialize)] @@ -378,7 +416,13 @@ pub(crate) struct MyRecentSwapsRequest { pub filter: MySwapsFilter, } -#[derive(Serialize)] +#[derive(Clone, Serialize)] +pub(crate) struct MyRecentSwapsMultiResponse { + swaps: MyRecentSwapsResponse, + pubkey: String, +} + +#[derive(Clone, Serialize)] pub(crate) struct MyRecentSwapsResponse { swaps: Vec, from_uuid: Option, @@ -396,6 +440,8 @@ pub(crate) enum MyRecentSwapsErr { FromUuidSwapNotFound(Uuid), InvalidTimeStampRange, DbError(String), + #[display(fmt = "No active coin pubkey not found")] + CoinNotFound, } impl From for MyRecentSwapsErr { @@ -411,9 +457,9 @@ impl From for MyRecentSwapsErr { impl HttpStatusCode for MyRecentSwapsErr { fn status_code(&self) -> StatusCode { match self { - MyRecentSwapsErr::FromUuidSwapNotFound(_) | MyRecentSwapsErr::InvalidTimeStampRange => { - StatusCode::BAD_REQUEST - }, + MyRecentSwapsErr::FromUuidSwapNotFound(_) + | MyRecentSwapsErr::InvalidTimeStampRange + | MyRecentSwapsErr::CoinNotFound => StatusCode::BAD_REQUEST, MyRecentSwapsErr::DbError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } @@ -422,29 +468,41 @@ impl HttpStatusCode for MyRecentSwapsErr { pub(crate) async fn my_recent_swaps_rpc( ctx: MmArc, req: MyRecentSwapsRequest, -) -> MmResult { - let db_result = MySwapsStorage::new(ctx.clone()) - .my_recent_swaps_with_filters(&req.filter, Some(&req.paging_options)) - .await?; - let mut swaps = Vec::with_capacity(db_result.uuids_and_types.len()); - for (uuid, swap_type) in db_result.uuids_and_types.iter() { - match get_swap_data_by_uuid_and_type(&ctx, *uuid, *swap_type).await { - Ok(Some(data)) => swaps.push(data), - Ok(None) => warn!("Swap {} data doesn't exist in DB", uuid), - Err(e) => error!("Error {} while trying to get swap {} data", e, uuid), - }; +) -> MmResult, MyRecentSwapsErr> { + let db_ids = find_unique_account_ids_active(&ctx) + .await + .map_to_mm(|_| MyRecentSwapsErr::CoinNotFound)?; + + let mut db_results = vec![]; + for db_id in db_ids { + let db_result = MySwapsStorage::new(ctx.clone()) + .my_recent_swaps_with_filters(&req.filter, Some(&req.paging_options), &db_id) + .await?; + let mut swaps = Vec::with_capacity(db_result.uuids_and_types.len()); + for (uuid, swap_type) in db_result.uuids_and_types.iter() { + match get_swap_data_by_uuid_and_type(&ctx, Some(&db_id), *uuid, *swap_type).await { + Ok(Some(data)) => swaps.push(data), + Ok(None) => warn!("Swap {} data doesn't exist in DB", uuid), + Err(e) => error!("Error {} while trying to get swap {} data", e, uuid), + }; + + db_results.push(MyRecentSwapsMultiResponse { + swaps: MyRecentSwapsResponse { + swaps: swaps.clone(), + from_uuid: req.paging_options.from_uuid, + skipped: db_result.skipped, + limit: req.paging_options.limit, + total: db_result.total_count, + page_number: req.paging_options.page_number, + total_pages: calc_total_pages(db_result.total_count, req.paging_options.limit), + found_records: db_result.uuids_and_types.len(), + }, + pubkey: db_id.to_string(), + }) + } } - Ok(MyRecentSwapsResponse { - swaps, - from_uuid: req.paging_options.from_uuid, - skipped: db_result.skipped, - limit: req.paging_options.limit, - total: db_result.total_count, - page_number: req.paging_options.page_number, - total_pages: calc_total_pages(db_result.total_count, req.paging_options.limit), - found_records: db_result.uuids_and_types.len(), - }) + Ok(db_results) } #[derive(Deserialize)] @@ -481,7 +539,7 @@ pub(crate) async fn active_swaps_rpc( let statuses = if req.include_status { let mut statuses = HashMap::with_capacity(uuids_with_types.len()); for (uuid, swap_type) in uuids_with_types.iter() { - match get_swap_data_by_uuid_and_type(&ctx, *uuid, *swap_type).await { + match get_swap_data_by_uuid_and_type(&ctx, None, *uuid, *swap_type).await { Ok(Some(data)) => { statuses.insert(*uuid, data); }, diff --git a/mm2src/mm2_main/src/lp_swap/taker_restart.rs b/mm2src/mm2_main/src/lp_swap/taker_restart.rs index 9ab1b48092..ba5a5b70a8 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_restart.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_restart.rs @@ -139,7 +139,7 @@ pub async fn check_maker_payment_spend_and_add_event( swap.apply_event(to_save.event.clone()); saved.events.push(to_save); let new_swap = SavedSwap::Taker(saved); - try_s!(new_swap.save_to_db(ctx).await); + try_s!(new_swap.save_to_db(ctx, None).await); info!("{}", MAKER_PAYMENT_SPENT_BY_WATCHER_LOG); Ok(TakerSwapCommand::Finish) } @@ -261,7 +261,7 @@ pub async fn add_taker_payment_refunded_by_watcher_event( saved.events.push(to_save); let new_swap = SavedSwap::Taker(saved); - try_s!(new_swap.save_to_db(ctx).await); + try_s!(new_swap.save_to_db(ctx, None).await); info!("Taker payment is refunded by the watcher"); Ok(TakerSwapCommand::Finish) } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 1b72199244..a02d688aef 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -98,15 +98,24 @@ pub const WATCHER_MESSAGE_SENT_LOG: &str = "Watcher message sent..."; pub const MAKER_PAYMENT_SPENT_BY_WATCHER_LOG: &str = "Maker payment is spent by the watcher..."; #[cfg(not(target_arch = "wasm32"))] -pub fn stats_taker_swap_dir(ctx: &MmArc) -> PathBuf { ctx.dbdir().join("SWAPS").join("STATS").join("TAKER") } +pub fn stats_taker_swap_dir(ctx: &MmArc, db_id: Option<&str>) -> PathBuf { + ctx.dbdir(db_id).join("SWAPS").join("STATS").join("TAKER") +} #[cfg(not(target_arch = "wasm32"))] -pub fn stats_taker_swap_file_path(ctx: &MmArc, uuid: &Uuid) -> PathBuf { - stats_taker_swap_dir(ctx).join(format!("{}.json", uuid)) +pub fn stats_taker_swap_file_path(ctx: &MmArc, db_id: Option<&str>, uuid: &Uuid) -> PathBuf { + stats_taker_swap_dir(ctx, db_id).join(format!("{}.json", uuid)) } -async fn save_my_taker_swap_event(ctx: &MmArc, swap: &TakerSwap, event: TakerSavedEvent) -> Result<(), String> { - let swap = match SavedSwap::load_my_swap_from_db(ctx, swap.uuid).await { +async fn save_my_taker_swap_event( + ctx: &MmArc, + swap: &TakerSwap, + event: TakerSavedEvent, + db_id: Option<&str>, +) -> Result<(), String> { + info!("event: {event:?}"); + + let swap = match SavedSwap::load_my_swap_from_db(ctx, db_id, swap.uuid).await { Ok(Some(swap)) => swap, Ok(None) => SavedSwap::Taker(TakerSavedSwap { uuid: swap.uuid, @@ -132,6 +141,8 @@ async fn save_my_taker_swap_event(ctx: &MmArc, swap: &TakerSwap, event: TakerSav TAKER_SUCCESS_EVENTS.iter().map(<&str>::to_string).collect() }, error_events: TAKER_ERROR_EVENTS.iter().map(<&str>::to_string).collect(), + taker_coin_account_id: swap.taker_coin.account_db_id().await, + maker_coin_account_id: swap.maker_coin.account_db_id().await, }), Err(e) => return ERR!("{}", e), }; @@ -142,14 +153,14 @@ async fn save_my_taker_swap_event(ctx: &MmArc, swap: &TakerSwap, event: TakerSav taker_swap.fetch_and_set_usd_prices().await; } let new_swap = SavedSwap::Taker(taker_swap); - try_s!(new_swap.save_to_db(ctx).await); + try_s!(new_swap.save_to_db(ctx, db_id).await); Ok(()) } else { ERR!("Expected SavedSwap::Taker, got {:?}", swap) } } -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct TakerSavedEvent { pub timestamp: u64, pub event: TakerSwapEvent, @@ -194,7 +205,7 @@ impl TakerSavedEvent { } } -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct TakerSavedSwap { pub uuid: Uuid, pub my_order_uuid: Option, @@ -209,6 +220,10 @@ pub struct TakerSavedSwap { pub mm_version: Option, pub success_events: Vec, pub error_events: Vec, + /// needed to validate if pending maker coin is activated with the correct `account_id` in `kickstart_thread_handler` + pub maker_coin_account_id: Option, + /// needed to validate if pending taker coin is activated with the correct `account_id` in `kickstart_thread_handler` + pub taker_coin_account_id: Option, } impl TakerSavedSwap { @@ -323,7 +338,7 @@ impl TakerSavedSwap { } } - // TODO: Adjust for private coins when/if they are braodcasted + // TODO: Adjust for private coins when/if they are broadcasted // TODO: Adjust for HD wallet when completed pub fn swap_pubkeys(&self) -> Result { let taker = match &self.events.first() { @@ -349,6 +364,15 @@ impl TakerSavedSwap { Ok(SwapPubkeys { maker, taker }) } + + pub fn db_id(&self) -> Option { + if let Some(events) = self.events.first() { + if let TakerSwapEvent::Started(data) = &events.event { + return data.db_id.clone(); + } + } + None + } } #[allow(clippy::large_enum_variant)] @@ -368,6 +392,13 @@ impl RunTakerSwapInput { RunTakerSwapInput::KickStart { swap_uuid, .. } => swap_uuid, } } + + fn taker_coin(&self) -> &MmCoinEnum { + match self { + RunTakerSwapInput::StartNew(swap) => &swap.taker_coin, + RunTakerSwapInput::KickStart { taker_coin, .. } => taker_coin, + } + } } /// Starts the taker swap and drives it to completion (until None next command received). @@ -375,10 +406,11 @@ impl RunTakerSwapInput { /// because it's usually means that swap is in invalid state which is possible only if there's developer error /// Every produced event is saved to local DB. Swap status is broadcast to P2P network after completion. pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { + let db_id = swap.taker_coin().account_db_id().await; let uuid = swap.uuid().to_owned(); let mut attempts = 0; let swap_lock = loop { - match SwapLock::lock(&ctx, uuid, 40.).await { + match SwapLock::lock(&ctx, uuid, 40., db_id.as_deref()).await { Ok(Some(l)) => break l, Ok(None) => { if attempts >= 1 { @@ -405,7 +437,7 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { maker_coin, taker_coin, swap_uuid, - } => match TakerSwap::load_from_db_by_uuid(ctx, maker_coin, taker_coin, &swap_uuid).await { + } => match TakerSwap::load_from_db_by_uuid(ctx, maker_coin, taker_coin, &swap_uuid, db_id.as_deref()).await { Ok((swap, command)) => match command { Some(c) => { info!("Swap {} kick started.", uuid); @@ -459,7 +491,7 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { event: event.clone(), }; - save_my_taker_swap_event(&ctx, &running_swap, to_save) + save_my_taker_swap_event(&ctx, &running_swap, to_save, db_id.as_deref()) .await .expect("!save_my_taker_swap_event"); if event.should_ban_maker() { @@ -483,12 +515,12 @@ pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { command = c; }, None => { - if let Err(e) = mark_swap_as_finished(ctx.clone(), running_swap.uuid).await { + if let Err(e) = mark_swap_as_finished(ctx.clone(), running_swap.uuid, db_id.as_deref()).await { error!("!mark_swap_finished({}): {}", uuid, e); } if to_broadcast { - if let Err(e) = broadcast_my_swap_status(&ctx, running_swap.uuid).await { + if let Err(e) = broadcast_my_swap_status(&ctx, running_swap.uuid, db_id.as_deref()).await { error!("!broadcast_my_swap_status({}): {}", uuid, e); } } @@ -544,6 +576,8 @@ pub struct TakerSwapData { pub taker_coin_htlc_pubkey: Option, /// Temporary privkey used to sign P2P messages when applicable pub p2p_privkey: Option, + // dynamic database id for taker from it's coin rmd160 + pub db_id: Option, } pub struct TakerSwapMut { @@ -870,7 +904,7 @@ impl TakerSwap { } #[allow(clippy::too_many_arguments)] - pub fn new( + pub async fn new( ctx: MmArc, maker: bits256, maker_amount: MmNumber, @@ -885,6 +919,7 @@ impl TakerSwap { p2p_privkey: Option, #[cfg(any(test, feature = "run-docker-tests"))] fail_at: Option, ) -> Self { + let db_id = taker_coin.account_db_id().await; TakerSwap { maker_coin, taker_coin, @@ -902,7 +937,10 @@ impl TakerSwap { payment_locktime, p2p_privkey, mutable: RwLock::new(TakerSwapMut { - data: TakerSwapData::default(), + data: TakerSwapData { + db_id, + ..TakerSwapData::default() + }, other_maker_coin_htlc_pub: H264::default(), other_taker_coin_htlc_pub: H264::default(), taker_fee: None, @@ -1006,7 +1044,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::StartFailed( ERRL!("!taker_coin.get_fee_to_send_taker_fee {}", e).into(), - )])) + )])); }, }; let get_sender_trade_fee_fut = self @@ -1017,7 +1055,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::StartFailed( ERRL!("!taker_coin.get_sender_trade_fee {}", e).into(), - )])) + )])); }, }; let maker_payment_spend_trade_fee_fut = self.maker_coin.get_receiver_trade_fee(stage); @@ -1026,7 +1064,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::StartFailed( ERRL!("!maker_coin.get_receiver_trade_fee {}", e).into(), - )])) + )])); }, }; @@ -1058,7 +1096,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::StartFailed( ERRL!("!maker_coin.current_block {}", e).into(), - )])) + )])); }, }; @@ -1067,7 +1105,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::StartFailed( ERRL!("!taker_coin.current_block {}", e).into(), - )])) + )])); }, }; @@ -1104,6 +1142,7 @@ impl TakerSwap { maker_coin_htlc_pubkey: Some(maker_coin_htlc_pubkey.as_slice().into()), taker_coin_htlc_pubkey: Some(taker_coin_htlc_pubkey.as_slice().into()), p2p_privkey: self.p2p_privkey.map(SerializableSecp256k1Keypair::from), + db_id: self.taker_coin.account_db_id().await, }; // This will be done during order match @@ -1126,7 +1165,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( ERRL!("{:?}", e).into(), - )])) + )])); }, }; @@ -1163,7 +1202,7 @@ impl TakerSwap { None => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( ERRL!("!maker_coin.negotiate_swap_contract_addr {}", e).into(), - )])) + )])); }, }, }; @@ -1179,7 +1218,7 @@ impl TakerSwap { None => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( ERRL!("!taker_coin.negotiate_swap_contract_addr {}", e).into(), - )])) + )])); }, }, }; @@ -1237,7 +1276,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( ERRL!("{:?}", e).into(), - )])) + )])); }, }; drop(send_abort_handle); @@ -1308,7 +1347,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::MakerPaymentValidateFailed(e.to_string().into()), - ])) + ])); }, }; @@ -1334,7 +1373,7 @@ impl TakerSwap { TakerSwapEvent::MakerPaymentValidateFailed( ERRL!("Error waiting for 'maker-payment' data: {}", e).into(), ), - ])) + ])); }, }; drop(abort_send_handle); @@ -1353,7 +1392,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::MakerPaymentValidateFailed(e.to_string().into()), - ])) + ])); }, } }, @@ -1368,7 +1407,7 @@ impl TakerSwap { TakerSwapEvent::MakerPaymentValidateFailed( ERRL!("Error parsing the 'maker-payment': {:?}", e).into(), ), - ])) + ])); }, }; @@ -1418,7 +1457,7 @@ impl TakerSwap { Err(err) => { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::TakerPaymentTransactionFailed(err.into_inner().to_string().into()), - ])) + ])); }, } } else { @@ -1525,7 +1564,7 @@ impl TakerSwap { TakerSwapEvent::TakerPaymentTransactionFailed( ERRL!("Watcher reward error: {}", err.to_string()).into(), ), - ])) + ])); }, } } else { @@ -1568,7 +1607,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::TakerPaymentTransactionFailed(ERRL!("{}", e).into()), - ])) + ])); }, }; @@ -1759,7 +1798,7 @@ impl TakerSwap { Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![ TakerSwapEvent::TakerPaymentWaitForSpendFailed(ERRL!("{}", e).into()), - ])) + ])); }, }; @@ -1955,8 +1994,9 @@ impl TakerSwap { maker_coin: MmCoinEnum, taker_coin: MmCoinEnum, swap_uuid: &Uuid, + db_id: Option<&str>, ) -> Result<(Self, Option), String> { - let saved = match SavedSwap::load_my_swap_from_db(&ctx, *swap_uuid).await { + let saved = match SavedSwap::load_my_swap_from_db(&ctx, db_id, *swap_uuid).await { Ok(Some(saved)) => saved, Ok(None) => return ERR!("Couldn't find a swap with the uuid '{}'", swap_uuid), Err(e) => return ERR!("{}", e), @@ -2025,7 +2065,8 @@ impl TakerSwap { data.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), #[cfg(any(test, feature = "run-docker-tests"))] fail_at, - ); + ) + .await; for saved_event in &saved.events { swap.apply_event(saved_event.event.clone()); @@ -2100,14 +2141,14 @@ impl TakerSwap { "Maker payment was already spent by {} tx {:02x}", self.maker_coin.ticker(), tx.tx_hash_as_bytes() - ) + ); }, Ok(Some(FoundSwapTxSpend::Refunded(tx))) => { return ERR!( "Maker payment was already refunded by {} tx {:02x}", self.maker_coin.ticker(), tx.tx_hash_as_bytes() - ) + ); }, Err(e) => return ERR!("Error {} when trying to find maker payment spend", e), Ok(None) => (), // payment is not spent, continue @@ -2521,6 +2562,7 @@ pub async fn taker_swap_trade_preimage( .with_sender_pubkey(H256Json::from(our_public_id.bytes)); let _ = order_builder .build() + .await .map_to_mm(|e| TradePreimageRpcError::from_taker_order_build_error(e, &req.base, &req.rel))?; let (base_coin_fee, rel_coin_fee) = match action { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index edeafa57b6..4a1d639615 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -35,7 +35,7 @@ use uuid::Uuid; cfg_native!( use crate::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; use common::async_blocking; - use db_common::sqlite::rusqlite::{named_params, Error as SqlError, Result as SqlResult, Row}; + use db_common::sqlite::rusqlite::{named_params, Connection, Error as SqlError, Result as SqlResult, Row}; use db_common::sqlite::rusqlite::types::Type as SqlType; ); @@ -47,7 +47,7 @@ cfg_wasm32!( #[allow(unused_imports)] use prost::Message; /// Negotiation data representation to be stored in DB. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct StoredNegotiationData { maker_payment_locktime: u64, maker_secret_hash: BytesJson, @@ -59,7 +59,7 @@ pub struct StoredNegotiationData { } /// Represents events produced by taker swap states. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "event_type", content = "event_data")] pub enum TakerSwapEvent { /// Swap has been successfully initialized. @@ -160,14 +160,29 @@ pub enum TakerSwapEvent { Completed, } +#[cfg(not(target_arch = "wasm32"))] +fn get_repr_impl(conn: &Connection, id_str: &str) -> SqlResult { + conn.query_row( + SELECT_MY_SWAP_V2_BY_UUID, + &[(":uuid", &id_str)], + TakerSwapDbRepr::from_sql_row, + ) +} + /// Storage for taker swaps. #[derive(Clone)] pub struct TakerSwapStorage { ctx: MmArc, + db_id: Option, } impl TakerSwapStorage { - pub fn new(ctx: MmArc) -> Self { TakerSwapStorage { ctx } } + pub fn new(ctx: MmArc, db_id: Option<&str>) -> Self { + TakerSwapStorage { + ctx, + db_id: db_id.map(|c| c.to_string()), + } + } } #[async_trait] @@ -179,31 +194,35 @@ impl StateMachineStorage for TakerSwapStorage { #[cfg(not(target_arch = "wasm32"))] async fn store_repr(&mut self, _id: Self::MachineId, repr: Self::DbRepr) -> Result<(), Self::Error> { let ctx = self.ctx.clone(); - + let db_id = self.db_id.clone(); async_blocking(move || { - let sql_params = named_params! { - ":my_coin": repr.taker_coin, - ":other_coin": repr.maker_coin, - ":uuid": repr.uuid.to_string(), - ":started_at": repr.started_at, - ":swap_type": TAKER_SWAP_V2_TYPE, - ":maker_volume": repr.maker_volume.to_fraction_string(), - ":taker_volume": repr.taker_volume.to_fraction_string(), - ":premium": repr.taker_premium.to_fraction_string(), - ":dex_fee": repr.dex_fee_amount.to_fraction_string(), - ":dex_fee_burn": repr.dex_fee_burn.to_fraction_string(), - ":secret": repr.taker_secret.0, - ":secret_hash": repr.taker_secret_hash.0, - ":secret_hash_algo": repr.secret_hash_algo as u8, - ":p2p_privkey": repr.p2p_keypair.map(|k| k.priv_key()).unwrap_or_default(), - ":lock_duration": repr.lock_duration, - ":maker_coin_confs": repr.conf_settings.maker_coin_confs, - ":maker_coin_nota": repr.conf_settings.maker_coin_nota, - ":taker_coin_confs": repr.conf_settings.taker_coin_confs, - ":taker_coin_nota": repr.conf_settings.taker_coin_nota, - ":other_p2p_pub": repr.maker_p2p_pub.to_bytes(), - }; - insert_new_swap_v2(&ctx, sql_params)?; + ctx.run_sql_query(db_id.as_deref(), move |conn| { + let sql_params = named_params! { + ":my_coin": repr.taker_coin, + ":other_coin": repr.maker_coin, + ":uuid": repr.uuid.to_string(), + ":started_at": repr.started_at, + ":swap_type": TAKER_SWAP_V2_TYPE, + ":maker_volume": repr.maker_volume.to_fraction_string(), + ":taker_volume": repr.taker_volume.to_fraction_string(), + ":premium": repr.taker_premium.to_fraction_string(), + ":dex_fee": repr.dex_fee_amount.to_fraction_string(), + ":dex_fee_burn": repr.dex_fee_burn.to_fraction_string(), + ":secret": repr.taker_secret.0, + ":secret_hash": repr.taker_secret_hash.0, + ":secret_hash_algo": repr.secret_hash_algo as u8, + ":p2p_privkey": repr.p2p_keypair.map(|k| k.priv_key()).unwrap_or_default(), + ":lock_duration": repr.lock_duration, + ":maker_coin_confs": repr.conf_settings.maker_coin_confs, + ":maker_coin_nota": repr.conf_settings.maker_coin_nota, + ":taker_coin_confs": repr.conf_settings.taker_coin_confs, + ":taker_coin_nota": repr.conf_settings.taker_coin_nota, + ":other_p2p_pub": repr.maker_p2p_pub.to_bytes(), + ":taker_coin_db_id": repr.taker_coin_db_id, + ":maker_coin_db_id": repr.maker_coin_db_id, + }; + insert_new_swap_v2(&conn, sql_params) + })?; Ok(()) }) .await @@ -212,7 +231,7 @@ impl StateMachineStorage for TakerSwapStorage { #[cfg(target_arch = "wasm32")] async fn store_repr(&mut self, uuid: Self::MachineId, repr: Self::DbRepr) -> Result<(), Self::Error> { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("SwapsContext::from_ctx should not fail"); - let db = swaps_ctx.swap_db().await?; + let db = swaps_ctx.swap_db(self.db_id.as_deref()).await?; let transaction = db.transaction().await?; let filters_table = transaction.table::().await?; @@ -240,36 +259,31 @@ impl StateMachineStorage for TakerSwapStorage { async fn get_repr(&self, id: Self::MachineId) -> Result { let ctx = self.ctx.clone(); let id_str = id.to_string(); + let db_id = self.db_id.clone(); - async_blocking(move || { - Ok(ctx.sqlite_connection().query_row( - SELECT_MY_SWAP_V2_BY_UUID, - &[(":uuid", &id_str)], - TakerSwapDbRepr::from_sql_row, - )?) - }) - .await + async_blocking(move || Ok(ctx.run_sql_query(db_id.as_deref(), move |conn| get_repr_impl(&conn, &id_str))?)) + .await } #[cfg(target_arch = "wasm32")] async fn get_repr(&self, id: Self::MachineId) -> Result { - get_swap_repr(&self.ctx, id).await + get_swap_repr(&self.ctx, id, self.db_id.as_deref()).await } async fn has_record_for(&mut self, id: &Self::MachineId) -> Result { - has_db_record_for(self.ctx.clone(), id).await + has_db_record_for(self.ctx.clone(), id, self.db_id.as_deref()).await } async fn store_event(&mut self, id: Self::MachineId, event: TakerSwapEvent) -> Result<(), Self::Error> { - store_swap_event::(self.ctx.clone(), id, event).await + store_swap_event::(self.ctx.clone(), id, event, self.db_id.as_deref()).await } async fn get_unfinished(&self) -> Result, Self::Error> { - get_unfinished_swaps_uuids(self.ctx.clone(), TAKER_SWAP_V2_TYPE).await + get_unfinished_swaps_uuids(self.ctx.clone(), TAKER_SWAP_V2_TYPE, self.db_id.as_deref()).await } async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { - mark_swap_as_finished(self.ctx.clone(), id).await + mark_swap_as_finished(self.ctx.clone(), id, self.db_id.as_deref()).await } } @@ -309,6 +323,10 @@ pub struct TakerSwapDbRepr { pub events: Vec, /// Maker's P2P pubkey pub maker_p2p_pub: Secp256k1PubkeySerialize, + // Taker's coin db_id. + pub taker_coin_db_id: Option, + // Maker's coin db_id. + pub maker_coin_db_id: Option, } #[cfg(not(target_arch = "wasm32"))] @@ -363,6 +381,8 @@ impl TakerSwapDbRepr { .map_err(|e| SqlError::FromSqlConversionFailure(19, SqlType::Blob, Box::new(e))) })? .into(), + taker_coin_db_id: row.get(20)?, + maker_coin_db_id: row.get(21)?, }) } } @@ -377,6 +397,10 @@ impl GetSwapCoins for TakerSwapDbRepr { fn maker_coin(&self) -> &str { &self.maker_coin } fn taker_coin(&self) -> &str { &self.taker_coin } + + fn taker_coin_db_id(&self) -> &Option { &self.taker_coin_db_id } + + fn maker_coin_db_id(&self) -> &Option { &self.maker_coin_db_id } } /// Represents the state machine for taker's side of the Trading Protocol Upgrade swap (v2). @@ -454,7 +478,7 @@ impl; type RecreateError = MmError; - fn to_db_repr(&self) -> TakerSwapDbRepr { + async fn to_db_repr(&self) -> TakerSwapDbRepr { TakerSwapDbRepr { maker_coin: self.maker_coin.ticker().into(), maker_volume: self.maker_volume.clone(), @@ -473,6 +497,8 @@ impl return MmError::err(SwapRecreateError::SwapAborted), TakerSwapEvent::Completed => return MmError::err(SwapRecreateError::SwapCompleted), TakerSwapEvent::TakerFundingRefunded { .. } => { - return MmError::err(SwapRecreateError::SwapFinishedWithRefund) + return MmError::err(SwapRecreateError::SwapFinishedWithRefund); }, TakerSwapEvent::TakerPaymentRefunded { .. } => { - return MmError::err(SwapRecreateError::SwapFinishedWithRefund) + return MmError::err(SwapRecreateError::SwapFinishedWithRefund); }, }; @@ -767,7 +793,7 @@ impl Result { - acquire_reentrancy_lock_impl(&self.ctx, self.uuid).await + acquire_reentrancy_lock_impl(&self.ctx, self.uuid, self.taker_coin.account_db_id().await.as_deref()).await } fn spawn_reentrancy_lock_renew(&mut self, guard: Self::ReentrancyLock) { @@ -1603,6 +1629,7 @@ impl TransitionFrom> for TakerPaymentSent { } + impl TransitionFrom> for TakerPaymentSent @@ -1768,11 +1795,13 @@ impl TransitionFrom> for TakerFundingRefundRequired { } + impl TransitionFrom> for TakerFundingRefundRequired { } + impl TransitionFrom> for TakerFundingRefundRequired { @@ -1862,6 +1891,7 @@ impl TransitionFrom> for TakerPaymentRefundRequired { } + impl TransitionFrom> for TakerPaymentRefundRequired { @@ -2267,19 +2297,24 @@ impl TransitionFrom> for Aborted {} + impl TransitionFrom> for Aborted {} + impl TransitionFrom> for Aborted { } + impl TransitionFrom> for Aborted { } + impl TransitionFrom> for Aborted { } + impl TransitionFrom> for Aborted { diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index 19ac357cab..696c753356 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -14,7 +14,7 @@ cfg_wasm32! { use mnemonics_wasm_db::{read_encrypted_passphrase_if_available, save_encrypted_passphrase}; use std::sync::Arc; - type WalletsDbLocked<'a> = DbLocked<'a, WalletsDb>; + type WalletsDbLocked = DbLocked; } cfg_native! { @@ -92,7 +92,7 @@ impl WalletsContext { }))) } - pub async fn wallets_db(&self) -> InitDbResult> { self.wallets_db.get_or_initialize().await } + pub async fn wallets_db(&self) -> InitDbResult { self.wallets_db.get_or_intiailize_global().await } } // Utility function for deserialization to reduce repetition diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 38d17af4ed..94068090c2 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -3,9 +3,9 @@ use crate::lp_ordermatch::new_protocol::{MakerOrderUpdated, PubkeyKeepAlive}; use coins::{MmCoin, TestCoin}; 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}; +use mm2_core::sql_connection_pool::SqliteConnPool; use mm2_libp2p::behaviours::atomicdex::generate_ed25519_keypair; use mm2_libp2p::AdexBehaviourCmd; use mm2_libp2p::{decode_message, PeerId}; @@ -16,7 +16,6 @@ use rand::{seq::SliceRandom, thread_rng, Rng}; use secp256k1::PublicKey; use std::collections::HashSet; use std::iter::{self, FromIterator}; -use std::sync::Mutex; #[test] fn test_match_maker_order_and_taker_request() { @@ -37,6 +36,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { @@ -75,6 +76,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { @@ -113,6 +116,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { @@ -151,6 +156,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { @@ -189,6 +196,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { @@ -227,6 +236,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { @@ -267,6 +278,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { base: "KMD".to_owned(), @@ -307,6 +320,8 @@ fn test_match_maker_order_and_taker_request() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let request = TakerRequest { base: "REL".to_owned(), @@ -334,25 +349,31 @@ fn test_match_maker_order_and_taker_request() { fn maker_order_match_with_request_zero_volumes() { let coin = MmCoinEnum::Test(TestCoin::default()); - let maker_order = MakerOrderBuilder::new(&coin, &coin) - .with_max_base_vol(1.into()) - .with_price(1.into()) - .build_unchecked(); + let maker_order = block_on( + MakerOrderBuilder::new(&coin, &coin) + .with_max_base_vol(1.into()) + .with_price(1.into()) + .build_unchecked(), + ); // default taker order has empty coins and zero amounts so it should pass to the price calculation stage (division) - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_rel_amount(1.into()) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_rel_amount(1.into()) + .build_unchecked(), + ); let expected = OrderMatchResult::NotMatched; let actual = maker_order.match_with_request(&taker_order.request); assert_eq!(expected, actual); // default taker order has empty coins and zero amounts so it should pass to the price calculation stage (division) - let taker_request = TakerOrderBuilder::new(&coin, &coin) - .with_base_amount(1.into()) - .with_action(TakerAction::Sell) - .build_unchecked(); + let taker_request = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_base_amount(1.into()) + .with_action(TakerAction::Sell) + .build_unchecked(), + ); let expected = OrderMatchResult::NotMatched; let actual = maker_order.match_with_request(&taker_request.request); @@ -378,6 +399,8 @@ fn test_maker_order_available_amount() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; maker.matches.insert(new_uuid(), MakerMatch { request: TakerRequest { @@ -479,6 +502,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -523,6 +548,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -567,6 +594,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -611,6 +640,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -655,6 +686,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -699,6 +732,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -743,6 +778,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -787,6 +824,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -831,6 +870,8 @@ fn test_taker_match_reserved() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -878,6 +919,8 @@ fn test_taker_order_cancellable() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; assert!(order.is_cancellable()); @@ -908,6 +951,8 @@ fn test_taker_order_cancellable() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; order.matches.insert(new_uuid(), TakerMatch { @@ -972,6 +1017,8 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }, None, ); @@ -994,6 +1041,8 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }, None, ); @@ -1016,6 +1065,8 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }, None, ); @@ -1043,6 +1094,8 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }); rx } @@ -1052,8 +1105,7 @@ fn test_cancel_by_single_coin() { let ctx = mm_ctx_with_iguana(None); let rx = prepare_for_cancel_by(&ctx); - let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + SqliteConnPool::init_test(&ctx).unwrap(); delete_my_maker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); delete_my_taker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); @@ -1071,8 +1123,7 @@ fn test_cancel_by_pair() { let ctx = mm_ctx_with_iguana(None); let rx = prepare_for_cancel_by(&ctx); - let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + SqliteConnPool::init_test(&ctx).unwrap(); delete_my_maker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); delete_my_taker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); @@ -1094,8 +1145,7 @@ fn test_cancel_by_all() { let ctx = mm_ctx_with_iguana(None); let rx = prepare_for_cancel_by(&ctx); - let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); + SqliteConnPool::init_test(&ctx).unwrap(); delete_my_maker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); delete_my_taker_order.mock_safe(|_, _, _| MockResult::Return(Box::new(futures01::future::ok(())))); @@ -1141,6 +1191,8 @@ fn test_taker_order_match_by() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let reserved = MakerReserved { @@ -1195,6 +1247,8 @@ fn test_maker_order_was_updated() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let mut update_msg = MakerOrderUpdated::new(maker_order.uuid); update_msg.with_new_price(BigRational::from_integer(2.into())); @@ -1280,7 +1334,7 @@ fn should_process_request_only_once() { fn test_choose_maker_confs_settings() { let coin = TestCoin::default().into(); // no confs set - let taker_order = TakerOrderBuilder::new(&coin, &coin).build_unchecked(); + let taker_order = block_on(TakerOrderBuilder::new(&coin, &coin).build_unchecked()); TestCoin::requires_notarization.mock_safe(|_| MockResult::Return(true)); TestCoin::required_confirmations.mock_safe(|_| MockResult::Return(8)); let settings = choose_maker_confs_and_notas(None, &taker_order.request, &coin, &coin); @@ -1297,7 +1351,7 @@ fn test_choose_maker_confs_settings() { rel_nota: false, }; // no confs set - let taker_order = TakerOrderBuilder::new(&coin, &coin).build_unchecked(); + let taker_order = block_on(TakerOrderBuilder::new(&coin, &coin).build_unchecked()); let settings = choose_maker_confs_and_notas(Some(maker_conf_settings), &taker_order.request, &coin, &coin); // should pick settings from maker order assert!(!settings.maker_coin_nota); @@ -1317,9 +1371,11 @@ fn test_choose_maker_confs_settings() { rel_confs: 5, rel_nota: false, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let settings = choose_maker_confs_and_notas(Some(maker_conf_settings), &taker_order.request, &coin, &coin); // should pick settings from taker request because taker will wait less time for our // payment confirmation @@ -1340,9 +1396,11 @@ fn test_choose_maker_confs_settings() { rel_confs: 1000, rel_nota: true, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let settings = choose_maker_confs_and_notas(Some(maker_conf_settings), &taker_order.request, &coin, &coin); // keep using our settings allowing taker to wait for our payment conf as much as he likes assert!(!settings.maker_coin_nota); @@ -1363,9 +1421,11 @@ fn test_choose_maker_confs_settings() { base_confs: 1, base_nota: false, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let settings = choose_maker_confs_and_notas(Some(maker_conf_settings), &taker_order.request, &coin, &coin); // Taker conf settings should not have any effect on maker conf requirements for taker payment @@ -1385,10 +1445,12 @@ fn test_choose_maker_confs_settings() { base_confs: 5, base_nota: false, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .with_action(TakerAction::Sell) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .with_action(TakerAction::Sell) + .build_unchecked(), + ); let settings = choose_maker_confs_and_notas(Some(maker_conf_settings), &taker_order.request, &coin, &coin); // should pick settings from taker request because taker will wait less time for our // payment confirmation @@ -1403,7 +1465,7 @@ fn test_choose_taker_confs_settings_buy_action() { let coin = TestCoin::default().into(); // no confs and notas set - let taker_order = TakerOrderBuilder::new(&coin, &coin).build_unchecked(); + let taker_order = block_on(TakerOrderBuilder::new(&coin, &coin).build_unchecked()); // no confs and notas set let maker_reserved = MakerReserved::default(); TestCoin::requires_notarization.mock_safe(|_| MockResult::Return(true)); @@ -1421,9 +1483,11 @@ fn test_choose_taker_confs_settings_buy_action() { rel_confs: 4, rel_nota: false, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); // no confs and notas set let maker_reserved = MakerReserved::default(); let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); @@ -1440,9 +1504,11 @@ fn test_choose_taker_confs_settings_buy_action() { rel_confs: 2, rel_nota: true, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let mut maker_reserved = MakerReserved::default(); let maker_conf_settings = OrderConfirmationsSettings { rel_confs: 1, @@ -1465,9 +1531,11 @@ fn test_choose_taker_confs_settings_buy_action() { rel_confs: 1, rel_nota: false, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let mut maker_reserved = MakerReserved::default(); let maker_conf_settings = OrderConfirmationsSettings { rel_confs: 2, @@ -1490,9 +1558,11 @@ fn test_choose_taker_confs_settings_buy_action() { rel_confs: 1, rel_nota: false, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let mut maker_reserved = MakerReserved::default(); let maker_conf_settings = OrderConfirmationsSettings { base_confs: 1, @@ -1515,9 +1585,11 @@ fn test_choose_taker_confs_settings_sell_action() { let coin = TestCoin::default().into(); // no confs and notas set - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_action(TakerAction::Sell) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_action(TakerAction::Sell) + .build_unchecked(), + ); // no confs and notas set let maker_reserved = MakerReserved::default(); TestCoin::requires_notarization.mock_safe(|_| MockResult::Return(true)); @@ -1535,10 +1607,12 @@ fn test_choose_taker_confs_settings_sell_action() { rel_confs: 5, rel_nota: true, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_action(TakerAction::Sell) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_action(TakerAction::Sell) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); // no confs and notas set let maker_reserved = MakerReserved::default(); let settings = choose_taker_confs_and_notas(&taker_order.request, &maker_reserved.conf_settings, &coin, &coin); @@ -1555,10 +1629,12 @@ fn test_choose_taker_confs_settings_sell_action() { rel_confs: 2, rel_nota: true, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_action(TakerAction::Sell) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_action(TakerAction::Sell) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let mut maker_reserved = MakerReserved::default(); let maker_conf_settings = OrderConfirmationsSettings { base_confs: 2, @@ -1581,10 +1657,12 @@ fn test_choose_taker_confs_settings_sell_action() { rel_confs: 2, rel_nota: true, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_action(TakerAction::Sell) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_action(TakerAction::Sell) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let mut maker_reserved = MakerReserved::default(); let maker_conf_settings = OrderConfirmationsSettings { rel_confs: 2, @@ -1607,10 +1685,12 @@ fn test_choose_taker_confs_settings_sell_action() { rel_confs: 2, rel_nota: true, }; - let taker_order = TakerOrderBuilder::new(&coin, &coin) - .with_action(TakerAction::Sell) - .with_conf_settings(taker_conf_settings) - .build_unchecked(); + let taker_order = block_on( + TakerOrderBuilder::new(&coin, &coin) + .with_action(TakerAction::Sell) + .with_conf_settings(taker_conf_settings) + .build_unchecked(), + ); let mut maker_reserved = MakerReserved::default(); let maker_conf_settings = OrderConfirmationsSettings { rel_confs: 2, @@ -2201,7 +2281,7 @@ fn test_taker_request_can_match_with_maker_pubkey() { let maker_pubkey = H256Json::default(); // default has MatchBy::Any - let mut order = TakerOrderBuilder::new(&coin, &coin).build_unchecked(); + let mut order = block_on(TakerOrderBuilder::new(&coin, &coin).build_unchecked()); assert!(order.request.can_match_with_maker_pubkey(&maker_pubkey)); // the uuids of orders is checked in another method @@ -2223,7 +2303,7 @@ fn test_taker_request_can_match_with_uuid() { let coin = MmCoinEnum::Test(TestCoin::default()); // default has MatchBy::Any - let mut order = TakerOrderBuilder::new(&coin, &coin).build_unchecked(); + let mut order = block_on(TakerOrderBuilder::new(&coin, &coin).build_unchecked()); assert!(order.request.can_match_with_uuid(&uuid)); // the uuids of orders is checked in another method @@ -3201,6 +3281,8 @@ fn test_maker_order_balance_loops() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; let morty_order = MakerOrder { @@ -3220,6 +3302,8 @@ fn test_maker_order_balance_loops() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; assert!(!maker_orders_ctx.balance_loop_exists(rick_ticker)); @@ -3252,6 +3336,8 @@ fn test_maker_order_balance_loops() { base_orderbook_ticker: None, rel_orderbook_ticker: None, p2p_privkey: None, + base_coin_account_id: None, + rel_coin_account_id: None, }; maker_orders_ctx.add_order(ctx.weak(), rick_order_2.clone(), None); diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index 2709d76f32..dd65f8484c 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -51,7 +51,7 @@ mod dispatcher_legacy; pub mod lp_commands_legacy; mod rate_limiter; -/// Lists the RPC method not requiring the "userpass" authentication. +/// Lists the RPC method not requiring the "userpass" authentication. /// None is also public to skip auth and display proper error in case of method is missing const PUBLIC_METHODS: &[Option<&str>] = &[ // Sorted alphanumerically (on the first letter) for readability. @@ -93,6 +93,8 @@ pub enum DispatcherError { UserpassIsInvalid(RateLimitError), #[display(fmt = "Error parsing mmrpc version: {}", _0)] InvalidMmRpcVersion(String), + #[display(fmt = "Not allowed rpc: {}", _0)] + DisabledNameSpace(String), } impl HttpStatusCode for DispatcherError { @@ -104,7 +106,8 @@ impl HttpStatusCode for DispatcherError { DispatcherError::LocalHostOnly | DispatcherError::UserpassIsNotSet | DispatcherError::UserpassIsInvalid(_) - | DispatcherError::Banned => StatusCode::FORBIDDEN, + | DispatcherError::Banned + | DispatcherError::DisabledNameSpace(_) => StatusCode::FORBIDDEN, } } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index b9066bf540..94d8b9afc8 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -149,9 +149,11 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult DispatcherResult>> { - use mm2_gui_storage::rpc_commands as gui_storage_rpc; - - match gui_storage_method { - "activate_coins" => handle_mmrpc(ctx, request, gui_storage_rpc::activate_coins).await, - "add_account" => handle_mmrpc(ctx, request, gui_storage_rpc::add_account).await, - "deactivate_coins" => handle_mmrpc(ctx, request, gui_storage_rpc::deactivate_coins).await, - "delete_account" => handle_mmrpc(ctx, request, gui_storage_rpc::delete_account).await, - "enable_account" => handle_mmrpc(ctx, request, gui_storage_rpc::enable_account).await, - "get_accounts" => handle_mmrpc(ctx, request, gui_storage_rpc::get_accounts).await, - "get_account_coins" => handle_mmrpc(ctx, request, gui_storage_rpc::get_account_coins).await, - "get_enabled_account" => handle_mmrpc(ctx, request, gui_storage_rpc::get_enabled_account).await, - "set_account_balance" => handle_mmrpc(ctx, request, gui_storage_rpc::set_account_balance).await, - "set_account_description" => handle_mmrpc(ctx, request, gui_storage_rpc::set_account_description).await, - "set_account_name" => handle_mmrpc(ctx, request, gui_storage_rpc::set_account_name).await, - _ => MmError::err(DispatcherError::NoSuchMethod), - } -} +// +// /// `gui_storage` dispatcher. +// /// +// /// # Note +// /// +// /// `gui_storage_method` is a method name with the `gui_storage::` prefix removed. +// async fn gui_storage_dispatcher( +// request: MmRpcRequest, +// ctx: MmArc, +// gui_storage_method: &str, +// ) -> DispatcherResult>> { +// use mm2_gui_storage::rpc_commands as gui_storage_rpc; +// +// match gui_storage_method { +// "activate_coins" => handle_mmrpc(ctx, request, gui_storage_rpc::activate_coins).await, +// "add_account" => handle_mmrpc(ctx, request, gui_storage_rpc::add_account).await, +// "deactivate_coins" => handle_mmrpc(ctx, request, gui_storage_rpc::deactivate_coins).await, +// "delete_account" => handle_mmrpc(ctx, request, gui_storage_rpc::delete_account).await, +// "enable_account" => handle_mmrpc(ctx, request, gui_storage_rpc::enable_account).await, +// "get_accounts" => handle_mmrpc(ctx, request, gui_storage_rpc::get_accounts).await, +// "get_account_coins" => handle_mmrpc(ctx, request, gui_storage_rpc::get_account_coins).await, +// "get_enabled_account" => handle_mmrpc(ctx, request, gui_storage_rpc::get_enabled_account).await, +// "set_account_balance" => handle_mmrpc(ctx, request, gui_storage_rpc::set_account_balance).await, +// "set_account_description" => handle_mmrpc(ctx, request, gui_storage_rpc::set_account_description).await, +// "set_account_name" => handle_mmrpc(ctx, request, gui_storage_rpc::set_account_name).await, +// _ => MmError::err(DispatcherError::NoSuchMethod), +// } +// } /// `lightning` dispatcher. /// 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 ae992c6d3e..6d632abdee 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs @@ -34,6 +34,7 @@ impl HttpStatusCode for GetPublicKeyError { } } +// TODO: Return public_key for all available and active unique pubkeys pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); Ok(GetPublicKeyResponse { public_key }) @@ -44,6 +45,7 @@ pub struct GetPublicKeyHashResponse { public_key_hash: H160Json, } +// TODO: Return public_key_hash for all available and active unique pubkeys pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { let public_key_hash = ctx.rmd160().to_owned().into(); Ok(GetPublicKeyHashResponse { public_key_hash }) diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs index 7db7c5b02b..1b84792449 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs @@ -244,11 +244,8 @@ pub async fn my_balance(ctx: MmArc, req: Json) -> Result>, Stri #[cfg(not(target_arch = "wasm32"))] async fn close_async_connection(ctx: &MmArc) { - if let Some(async_conn) = ctx.async_sqlite_connection.as_option() { - let mut conn = async_conn.lock().await; - if let Err(e) = conn.close().await { - error!("Error stopping AsyncConnection: {}", e); - } + if let Some(connections) = ctx.async_sqlite_conn_pool.as_option() { + connections.close_connections().await; } } diff --git a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs index 4165cecf2f..27f69dd72a 100644 --- a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs @@ -726,7 +726,7 @@ mod swap { "NUCLEUS-TEST", &[], NUCLEUS_TESTNET_RPC_URLS, - false + false, ))); dbg!(block_on(enable_tendermint( @@ -734,12 +734,12 @@ mod swap { "NUCLEUS-TEST", &[], NUCLEUS_TESTNET_RPC_URLS, - false + false, ))); - dbg!(block_on(enable_electrum(&mm_bob, "DOC", false, DOC_ELECTRUM_ADDRS,))); + dbg!(block_on(enable_electrum(&mm_bob, "DOC", false, DOC_ELECTRUM_ADDRS))); - dbg!(block_on(enable_electrum(&mm_alice, "DOC", false, DOC_ELECTRUM_ADDRS,))); + dbg!(block_on(enable_electrum(&mm_alice, "DOC", false, DOC_ELECTRUM_ADDRS))); block_on(trade_base_rel_tendermint( mm_bob, @@ -816,7 +816,7 @@ mod swap { "NUCLEUS-TEST", &[], NUCLEUS_TESTNET_RPC_URLS, - false + false, ))); dbg!(block_on(enable_tendermint( @@ -824,7 +824,7 @@ mod swap { "NUCLEUS-TEST", &[], NUCLEUS_TESTNET_RPC_URLS, - false + false, ))); let swap_contract = format!("0x{}", hex::encode(swap_contract())); @@ -910,7 +910,7 @@ mod swap { "NUCLEUS-TEST", &["IRIS-IBC-NUCLEUS-TEST"], NUCLEUS_TESTNET_RPC_URLS, - false + false, ))); dbg!(block_on(enable_tendermint( @@ -918,7 +918,7 @@ mod swap { "NUCLEUS-TEST", &["IRIS-IBC-NUCLEUS-TEST"], NUCLEUS_TESTNET_RPC_URLS, - false + false, ))); dbg!(block_on(enable_electrum(&mm_bob, "DOC", false, DOC_ELECTRUM_ADDRS))); diff --git a/mm2src/mm2_main/tests/mm2_tests/gui_storage_tests.rs b/mm2src/mm2_main/tests/mm2_tests/gui_storage_tests.rs new file mode 100644 index 0000000000..25eedeac4a --- /dev/null +++ b/mm2src/mm2_main/tests/mm2_tests/gui_storage_tests.rs @@ -0,0 +1,270 @@ +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_gui_storage_accounts_functionality() { + let passphrase = "test_gui_storage passphrase"; + + let conf = Mm2TestConf::seednode(passphrase, &json!([])); + let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm.mm_dump(); + log!("Log path: {}", mm.log_path.display()); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::enable_account", + "params": { + "policy": "new", + "account_id": { + "type": "iguana" + }, + "name": "My Iguana wallet", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::enable_account: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::add_account", + "params": { + "account_id": { + "type": "hw", + "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" + }, + "description": "Any description", + "name": "My HW", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::add_account: {}", resp.1); + + // Add `HD{1}` account that will be deleted later. + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::add_account", + "params": { + "account_id": { + "type": "hd", + "account_idx": 1, + }, + "name": "An HD account" + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::add_account: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::delete_account", + "params": { + "account_id": { + "type": "hd", + "account_idx": 1, + } + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::delete_account: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::set_account_balance", + "params": { + "account_id": { + "type": "hw", + "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" + }, + "balance_usd": "123.567", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::set_account_balance: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::set_account_name", + "params": { + "account_id": { + "type": "iguana" + }, + "name": "New Iguana account name", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::set_account_name: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::set_account_description", + "params": { + "account_id": { + "type": "iguana" + }, + "description": "Another description", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::set_account_description: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::get_accounts" + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::get_accounts: {}", resp.1); + + let actual: RpcV2Response> = json::from_str(&resp.1).unwrap(); + let expected = vec![ + gui_storage::AccountWithEnabledFlag { + account_id: gui_storage::AccountId::Iguana, + name: "New Iguana account name".to_string(), + description: "Another description".to_string(), + balance_usd: BigDecimal::from(0i32), + enabled: true, + }, + gui_storage::AccountWithEnabledFlag { + account_id: gui_storage::AccountId::HW { + device_pubkey: "1549128bbfb33b997949b4105b6a6371c998e212".to_string(), + }, + name: "My HW".to_string(), + description: "Any description".to_string(), + balance_usd: BigDecimal::from(123567i32) / BigDecimal::from(1000i32), + enabled: false, + }, + ]; + assert_eq!(actual.result, expected); +} + +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_gui_storage_coins_functionality() { + let passphrase = "test_gui_storage passphrase"; + + let conf = Mm2TestConf::seednode(passphrase, &json!([])); + let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm.mm_dump(); + log!("Log path: {}", mm.log_path.display()); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::enable_account", + "params": { + "policy": "new", + "account_id": { + "type": "iguana" + }, + "name": "My Iguana wallet", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::enable_account: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::add_account", + "params": { + "account_id": { + "type": "hw", + "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" + }, + "description": "Any description", + "name": "My HW", + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::add_account: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::activate_coins", + "params": { + "account_id": { + "type": "iguana" + }, + "tickers": ["RICK", "MORTY", "KMD"], + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::activate_coins: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::activate_coins", + "params": { + "account_id": { + "type": "hw", + "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" + }, + "tickers": ["KMD", "MORTY", "BCH"], + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::activate_coins: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::deactivate_coins", + "params": { + "account_id": { + "type": "hw", + "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" + }, + "tickers": ["BTC", "MORTY"], + }, + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::deactivate_coins: {}", resp.1); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::get_enabled_account", + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::get_enabled_account: {}", resp.1); + let actual: RpcV2Response = json::from_str(&resp.1).unwrap(); + let expected = gui_storage::AccountWithCoins { + account_id: gui_storage::AccountId::Iguana, + name: "My Iguana wallet".to_string(), + description: String::new(), + balance_usd: BigDecimal::from(0i32), + coins: vec!["RICK".to_string(), "MORTY".to_string(), "KMD".to_string()] + .into_iter() + .collect(), + }; + assert_eq!(actual.result, expected); + + let resp = block_on(mm.rpc(&json!({ + "userpass": mm.userpass, + "mmrpc": "2.0", + "method": "gui_storage::get_account_coins", + "params": { + "account_id": { + "type": "hw", + "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" + } + } + }))) + .unwrap(); + assert!(resp.0.is_success(), "!gui_storage::get_enabled_account: {}", resp.1); + let actual: RpcV2Response = json::from_str(&resp.1).unwrap(); + let expected = gui_storage::AccountCoins { + account_id: gui_storage::AccountId::HW { + device_pubkey: "1549128bbfb33b997949b4105b6a6371c998e212".to_string(), + }, + coins: vec!["KMD".to_string(), "BCH".to_string()].into_iter().collect(), + }; + assert_eq!(actual.result, expected); +} 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 137eb6ef09..6dcd6e5126 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -53,7 +53,7 @@ cfg_wasm32! { fn test_rpc() { let (_, mm, _dump_log, _dump_dashboard) = mm_spat(); - let no_method = block_on(mm.rpc(&json! ({ + let no_method = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "coin": "RICK", "ipaddr": "electrum1.cipig.net", @@ -67,7 +67,7 @@ fn test_rpc() { assert!(not_json.0.is_server_error()); assert_eq!((not_json.2)[ACCESS_CONTROL_ALLOW_ORIGIN], "http://localhost:4000"); - let unknown_method = block_on(mm.rpc(&json! ({ + let unknown_method = block_on(mm.rpc(&json!({ "method": "unknown_method", }))) .unwrap(); @@ -75,7 +75,7 @@ fn test_rpc() { assert!(unknown_method.0.is_server_error()); assert_eq!((unknown_method.2)[ACCESS_CONTROL_ALLOW_ORIGIN], "http://localhost:4000"); - let version = block_on(mm.rpc(&json! ({ + let version = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "version", }))) @@ -84,7 +84,7 @@ fn test_rpc() { assert_eq!((version.2)[ACCESS_CONTROL_ALLOW_ORIGIN], "http://localhost:4000"); let _version: MmVersionResponse = json::from_str(&version.1).unwrap(); - let help = block_on(mm.rpc(&json! ({ + let help = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "help", }))) @@ -108,7 +108,7 @@ fn orders_of_banned_pubkeys_should_not_be_displayed() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -132,7 +132,7 @@ fn orders_of_banned_pubkeys_should_not_be_displayed() { ); // issue sell request on Bob side by setting base/rel price log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -144,7 +144,7 @@ fn orders_of_banned_pubkeys_should_not_be_displayed() { assert!(rc.0.is_success(), "!setprice: {}", rc.1); let mut mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), @@ -163,7 +163,7 @@ fn orders_of_banned_pubkeys_should_not_be_displayed() { log!("Alice log path: {}", mm_alice.log_path.display()); log!("Ban Bob pubkey on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "ban_pubkey", "pubkey": "2cd3021a2197361fb70b862c412bc8e44cff6951fa1de45ceabfdd9b4c520420", @@ -173,7 +173,7 @@ fn orders_of_banned_pubkeys_should_not_be_displayed() { assert!(rc.0.is_success(), "!ban_pubkey: {}", rc.1); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -213,7 +213,7 @@ fn test_my_balance() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -233,7 +233,7 @@ fn test_my_balance() { let json = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS)); assert_eq!(json.balance, "7.777".parse().unwrap()); - let my_balance = block_on(mm.rpc(&json! ({ + let my_balance = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "my_balance", "coin": "RICK", @@ -297,7 +297,7 @@ fn test_p2wpkh_my_balance() { #[cfg(not(target_arch = "wasm32"))] fn check_set_price_fails(mm: &MarketMakerIt, base: &str, rel: &str) { - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "setprice", "base": base, @@ -315,7 +315,7 @@ fn check_set_price_fails(mm: &MarketMakerIt, base: &str, rel: &str) { #[cfg(not(target_arch = "wasm32"))] fn check_buy_fails(mm: &MarketMakerIt, base: &str, rel: &str, vol: f64) { - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "buy", "base": base, @@ -329,7 +329,7 @@ fn check_buy_fails(mm: &MarketMakerIt, base: &str, rel: &str, vol: f64) { #[cfg(not(target_arch = "wasm32"))] fn check_sell_fails(mm: &MarketMakerIt, base: &str, rel: &str, vol: f64) { - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "sell", "base": base, @@ -353,7 +353,7 @@ fn test_check_balance_on_order_post() { // start bob and immediately place the order let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -415,7 +415,7 @@ fn test_rpc_password_from_json() { // do not allow empty password let mut err_mm1 = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "passphrase": "bob passphrase", @@ -432,7 +432,7 @@ fn test_rpc_password_from_json() { // do not allow empty password let mut err_mm2 = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "passphrase": "bob passphrase", @@ -448,7 +448,7 @@ fn test_rpc_password_from_json() { block_on(err_mm2.wait_for_log(5., |log| log.contains("rpc_password must be string"))).unwrap(); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "passphrase": "bob passphrase", @@ -462,7 +462,7 @@ fn test_rpc_password_from_json() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - let electrum_invalid = block_on(mm.rpc(&json! ({ + let electrum_invalid = block_on(mm.rpc(&json!({ "userpass": "password1", "method": "electrum", "coin": "RICK", @@ -497,7 +497,7 @@ fn test_rpc_password_from_json() { electrum.1 ); - let electrum = block_on(mm.rpc(&json! ({ + let electrum = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "electrum", "coin": "MORTY", @@ -515,7 +515,7 @@ fn test_rpc_password_from_json() { electrum.1 ); - let orderbook = block_on(mm.rpc(&json! ({ + let orderbook = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "orderbook", "base": "RICK", @@ -542,7 +542,7 @@ fn test_mmrpc_v2() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "passphrase": "bob passphrase", @@ -560,7 +560,7 @@ fn test_mmrpc_v2() { let _electrum = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS)); // no `userpass` - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "method": "withdraw", "params": { @@ -580,7 +580,7 @@ fn test_mmrpc_v2() { assert!(withdraw_error.error_data.is_none()); // invalid `userpass` - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": "another password", "method": "withdraw", @@ -601,7 +601,7 @@ fn test_mmrpc_v2() { assert!(withdraw_error.error_data.is_some()); // invalid `mmrpc` version - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "mmrpc": "1.0", "userpass": mm.userpass, "method": "withdraw", @@ -622,7 +622,7 @@ fn test_mmrpc_v2() { assert_eq!(withdraw_error.error_type, "InvalidMmRpcVersion"); // 'id' = 3 - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "withdraw", @@ -648,7 +648,7 @@ fn test_rpc_password_from_json_no_userpass() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "passphrase": "bob passphrase", @@ -661,7 +661,7 @@ fn test_rpc_password_from_json_no_userpass() { .unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - let electrum = block_on(mm.rpc(&json! ({ + let electrum = block_on(mm.rpc(&json!({ "method": "electrum", "coin": "RICK", "urls": ["electrum2.cipig.net:10017"], @@ -807,7 +807,7 @@ async fn trade_base_rel_electrum( for (base, rel) in pairs.iter() { log!("Get {}/{} orderbook", base, rel); let rc = mm_bob - .rpc(&json! ({ + .rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": base, @@ -873,7 +873,7 @@ fn withdraw_and_send( use std::ops::Sub; let from = from.map(WithdrawFrom::AddressId); - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "withdraw", @@ -910,7 +910,7 @@ fn withdraw_and_send( assert_eq!(tx_details.from, vec![from_str]); } - let send = block_on(mm.rpc(&json! ({ + let send = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "send_raw_transaction", "coin": coin, @@ -927,14 +927,14 @@ fn withdraw_and_send( fn test_withdraw_and_send() { let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); - let coins = json! ([ + let coins = json!([ {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, {"coin":"MORTY","asset":"MORTY","rpcport":8923,"txversion":4,"overwintered":1,"txfee":1000,"protocol":{"type":"UTXO"}}, {"coin":"MORTY_SEGWIT","asset":"MORTY_SEGWIT","txversion":4,"overwintered":1,"segwit":true,"txfee":1000,"protocol":{"type":"UTXO"}}, ]); let mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8100, "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), @@ -971,7 +971,7 @@ fn test_withdraw_and_send() { ); // allow to withdraw non-Segwit coin to P2SH addresses - let withdraw = block_on(mm_alice.rpc(&json! ({ + let withdraw = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "mmrpc": "2.0", "method": "withdraw", @@ -986,7 +986,7 @@ fn test_withdraw_and_send() { assert!(withdraw.0.is_success(), "MORTY withdraw: {}", withdraw.1); // allow to withdraw to P2SH addresses if Segwit flag is true - let withdraw = block_on(mm_alice.rpc(&json! ({ + let withdraw = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "mmrpc": "2.0", "method": "withdraw", @@ -1003,7 +1003,7 @@ fn test_withdraw_and_send() { // must not allow to withdraw too small amount 0.000005 (less than 0.00001 dust) let small_amount = MmNumber::from("0.000005").to_decimal(); - let withdraw = block_on(mm_alice.rpc(&json! ({ + let withdraw = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "mmrpc": "2.0", "method": "withdraw", @@ -1250,10 +1250,10 @@ fn test_withdraw_segwit() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_swap_status() { - let coins = json! ([{"coin":"RICK","asset":"RICK"},]); + let coins = json!([{"coin":"RICK","asset":"RICK"},]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8100, "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), @@ -1268,7 +1268,7 @@ fn test_swap_status() { ) .unwrap(); - let my_swap = block_on(mm.rpc(&json! ({ + let my_swap = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "my_swap_status", "params": { @@ -1279,7 +1279,7 @@ fn test_swap_status() { assert!(my_swap.0.is_server_error(), "!not found status code: {}", my_swap.1); - let stats_swap = block_on(mm.rpc(&json! ({ + let stats_swap = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "stats_swap_status", "params": { @@ -1305,7 +1305,7 @@ fn test_order_errors_when_base_equal_rel() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -1324,7 +1324,7 @@ fn test_order_errors_when_base_equal_rel() { log!("Log path: {}", mm.log_path.display()); block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS)); - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "setprice", "base": "RICK", @@ -1334,7 +1334,7 @@ fn test_order_errors_when_base_equal_rel() { .unwrap(); assert!(rc.0.is_server_error(), "setprice should have failed, but got {:?}", rc); - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "buy", "base": "RICK", @@ -1345,7 +1345,7 @@ fn test_order_errors_when_base_equal_rel() { .unwrap(); assert!(rc.0.is_server_error(), "buy should have failed, but got {:?}", rc); - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "sell", "base": "RICK", @@ -1364,7 +1364,7 @@ fn startup_passphrase(passphrase: &str, expected_address: &str) { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -1430,7 +1430,7 @@ fn test_cancel_order() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1456,7 +1456,7 @@ fn test_cancel_order() { ); log!("Issue sell request on Bob side by setting base/rel price…"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -1470,7 +1470,7 @@ fn test_cancel_order() { log!("{:?}", setprice_json); let mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1497,7 +1497,7 @@ fn test_cancel_order() { ); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -1514,7 +1514,7 @@ fn test_cancel_order() { "Alice RICK/MORTY orderbook must have exactly 1 ask" ); - let cancel_rc = block_on(mm_bob.rpc(&json! ({ + let cancel_rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "cancel_order", "uuid": setprice_json["result"]["uuid"], @@ -1535,7 +1535,7 @@ fn test_cancel_order() { // Bob orderbook must show no orders log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "RICK", @@ -1550,7 +1550,7 @@ fn test_cancel_order() { // Alice orderbook must show no orders log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -1575,7 +1575,7 @@ fn test_cancel_all_orders() { let bob_passphrase = "bob passphrase"; // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1600,7 +1600,7 @@ fn test_cancel_all_orders() { ); log!("Issue sell request on Bob side by setting base/rel price…"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -1614,7 +1614,7 @@ fn test_cancel_all_orders() { log!("{:?}", setprice_json); let mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1643,7 +1643,7 @@ fn test_cancel_all_orders() { thread::sleep(Duration::from_secs(3)); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -1657,7 +1657,7 @@ fn test_cancel_all_orders() { let asks = alice_orderbook["asks"].as_array().unwrap(); assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); - let cancel_rc = block_on(mm_bob.rpc(&json! ({ + let cancel_rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "cancel_all_orders", "cancel_by": { @@ -1680,7 +1680,7 @@ fn test_cancel_all_orders() { // Bob orderbook must show no orders log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "RICK", @@ -1696,7 +1696,7 @@ fn test_cancel_all_orders() { // Alice orderbook must show no orders log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -1723,7 +1723,7 @@ fn test_electrum_enable_conn_errors() { ]); let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1769,7 +1769,7 @@ fn test_order_should_not_be_displayed_when_node_is_down() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1795,7 +1795,7 @@ fn test_order_should_not_be_displayed_when_node_is_down() { log!("Bob enable MORTY {:?}", electrum_morty); let mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), @@ -1822,7 +1822,7 @@ fn test_order_should_not_be_displayed_when_node_is_down() { // issue sell request on Bob side by setting base/rel price log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -1836,7 +1836,7 @@ fn test_order_should_not_be_displayed_when_node_is_down() { thread::sleep(Duration::from_secs(2)); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -1853,7 +1853,7 @@ fn test_order_should_not_be_displayed_when_node_is_down() { block_on(mm_bob.stop()).unwrap(); thread::sleep(Duration::from_secs(6)); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -1880,7 +1880,7 @@ fn test_own_orders_should_not_be_removed_from_orderbook() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -1908,7 +1908,7 @@ fn test_own_orders_should_not_be_removed_from_orderbook() { // issue sell request on Bob side by setting base/rel price log!("Issue bob sell request"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -1921,7 +1921,7 @@ fn test_own_orders_should_not_be_removed_from_orderbook() { thread::sleep(Duration::from_secs(6)); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "RICK", @@ -1940,7 +1940,7 @@ fn test_own_orders_should_not_be_removed_from_orderbook() { #[cfg(not(target_arch = "wasm32"))] fn check_priv_key(mm: &MarketMakerIt, coin: &str, expected_priv_key: &str) { - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "show_priv_key", "coin": coin @@ -1958,7 +1958,7 @@ fn test_show_priv_key() { let coins = json!([rick_conf(), morty_conf(), eth_dev_conf()]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -1992,13 +1992,13 @@ fn test_show_priv_key() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_electrum_and_enable_response() { - let coins = json! ([ + let coins = json!([ {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"},"mature_confirmations":101}, eth_dev_conf(), ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -2041,7 +2041,7 @@ fn test_electrum_and_enable_response() { assert_eq!(rick_response["mature_confirmations"], Json::from(101)); // should change requires notarization at runtime - let requires_nota_rick = block_on(mm.rpc(&json! ({ + let requires_nota_rick = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "set_requires_notarization", "coin": "RICK", @@ -2062,7 +2062,7 @@ fn test_electrum_and_enable_response() { Json::from(false) ); - let enable_eth = block_on(mm.rpc(&json! ({ + let enable_eth = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "enable", "coin": "ETH", @@ -2100,7 +2100,7 @@ fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -2124,7 +2124,7 @@ fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { block_on(enable_coins_rick_morty_electrum(&mm_bob)) ); - let set_price_json = json! ({ + let set_price_json = json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -2137,7 +2137,7 @@ fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { assert!(rc.0.is_success(), "!setprice: {}", rc.1); let mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -2163,7 +2163,7 @@ fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { ); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -2187,7 +2187,7 @@ fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { // Bob orderbook must show 1 order log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "RICK", @@ -2203,7 +2203,7 @@ fn set_price_with_cancel_previous_should_broadcast_cancelled_message() { // Alice orderbook must have 1 order log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -2225,7 +2225,7 @@ fn test_batch_requests() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -2302,7 +2302,7 @@ fn test_metrics_method() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -2352,7 +2352,7 @@ fn test_electrum_tx_history() { ]); let mut mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -2410,7 +2410,7 @@ fn spin_n_nodes(seednodes: &[&str], coins: &Json, n: usize) -> Vec<(MarketMakerI let mut mm_nodes = Vec::with_capacity(n); for i in 0..n { let mut mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("ALICE_TRADE_IP") .ok(), @@ -2444,7 +2444,7 @@ fn test_convert_utxo_address() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -2464,7 +2464,7 @@ fn test_convert_utxo_address() { let _electrum = block_on(enable_electrum(&mm, "BCH", false, T_BCH_ELECTRUMS)); // test standard to cashaddress - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "BCH", @@ -2488,7 +2488,7 @@ fn test_convert_utxo_address() { assert_eq!(actual, expected); // test cashaddress to standard - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "BCH", @@ -2512,7 +2512,7 @@ fn test_convert_utxo_address() { assert_eq!(actual, expected); // test standard to standard - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "BCH", @@ -2536,7 +2536,7 @@ fn test_convert_utxo_address() { assert_eq!(actual, expected); // test invalid address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "BCH", @@ -2566,7 +2566,7 @@ fn test_convert_segwit_address() { let _electrum = block_on(enable_electrum(&mm, "tBTC-Segwit", false, TBTC_ELECTRUMS)); // test standard to segwit - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "tBTC-Segwit", @@ -2590,7 +2590,7 @@ fn test_convert_segwit_address() { assert_eq!(actual, expected); // test segwit to standard - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "tBTC-Segwit", @@ -2614,7 +2614,7 @@ fn test_convert_segwit_address() { assert_eq!(actual, expected); // test invalid tBTC standard address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "tBTC-Segwit", @@ -2630,7 +2630,7 @@ fn test_convert_segwit_address() { assert!(rc.1.contains("invalid address prefix")); // test invalid tBTC segwit address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "tBTC-Segwit", @@ -2653,7 +2653,7 @@ fn test_convert_eth_address() { // start mm and immediately place the order let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -2675,7 +2675,7 @@ fn test_convert_eth_address() { block_on(enable_native(&mm, "ETH", ETH_SEPOLIA_NODES, None)); // test single-case to mixed-case - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "ETH", @@ -2699,7 +2699,7 @@ fn test_convert_eth_address() { assert_eq!(actual, expected); // test mixed-case to mixed-case (expect error) - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "ETH", @@ -2723,7 +2723,7 @@ fn test_convert_eth_address() { assert_eq!(actual, expected); // test invalid address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "ETH", @@ -2948,13 +2948,13 @@ fn test_get_staking_infos_qtum() { #[cfg(not(target_arch = "wasm32"))] fn test_convert_qrc20_address() { let passphrase = "cV463HpebE2djP9ugJry5wZ9st5cc6AbkHXGryZVPXMH1XJK8cVU"; - let coins = json! ([ + let coins = json!([ {"coin":"QRC20","required_confirmations":0,"pubtype": 120,"p2shtype": 50,"wiftype": 128,"txfee": 0,"mm2": 1,"mature_confirmations":2000, "protocol":{"type":"QRC20","protocol_data":{"platform":"QTUM","contract_address":"0xd362e096e873eb7907e205fadc6175c6fec7bc44"}}}, ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -2986,7 +2986,7 @@ fn test_convert_qrc20_address() { )); // test wallet to contract - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "QRC20", @@ -3010,7 +3010,7 @@ fn test_convert_qrc20_address() { assert_eq!(actual, expected); // test contract to wallet - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "QRC20", @@ -3034,7 +3034,7 @@ fn test_convert_qrc20_address() { assert_eq!(actual, expected); // test wallet to wallet - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "QRC20", @@ -3058,7 +3058,7 @@ fn test_convert_qrc20_address() { assert_eq!(actual, expected); // test invalid address (invalid prefixes) - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "QRC20", @@ -3075,7 +3075,7 @@ fn test_convert_qrc20_address() { assert!(rc.1.contains("invalid address prefix")); // test invalid address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "convertaddress", "coin": "QRC20", @@ -3147,7 +3147,7 @@ fn test_validateaddress() { // test valid ETH address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "validateaddress", "coin": "ETH", @@ -3171,7 +3171,7 @@ fn test_validateaddress() { // test invalid RICK address (legacy address format activated) - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "validateaddress", "coin": "RICK", @@ -3194,7 +3194,7 @@ fn test_validateaddress() { // test invalid RICK address (invalid prefixes) - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "validateaddress", "coin": "RICK", @@ -3218,7 +3218,7 @@ fn test_validateaddress() { // test invalid ETH address - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "validateaddress", "coin": "ETH", @@ -3257,7 +3257,7 @@ fn test_validateaddress_segwit() { log!("enable_coins (alice): {:?}", electrum); // test valid Segwit address - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "validateaddress", "coin": "tBTC-Segwit", @@ -3280,7 +3280,7 @@ fn test_validateaddress_segwit() { assert_eq!(actual, expected); // test invalid tBTC Segwit address (invalid hrp) - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "validateaddress", "coin": "tBTC-Segwit", @@ -3309,13 +3309,13 @@ fn test_validateaddress_segwit() { #[cfg(not(target_arch = "wasm32"))] fn qrc20_activate_electrum() { let passphrase = "cV463HpebE2djP9ugJry5wZ9st5cc6AbkHXGryZVPXMH1XJK8cVU"; - let coins = json! ([ + let coins = json!([ {"coin":"QRC20","required_confirmations":0,"pubtype": 120,"p2shtype": 50,"wiftype": 128,"txfee": 0,"mm2": 1,"mature_confirmations":2000, "protocol":{"type":"QRC20","protocol_data":{"platform":"QTUM","contract_address":"0xd362e096e873eb7907e205fadc6175c6fec7bc44"}}}, ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -3363,7 +3363,7 @@ fn test_qrc20_withdraw() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -3404,7 +3404,7 @@ fn test_qrc20_withdraw() { let amount = 10; - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "withdraw", "coin": "QRC20", @@ -3424,7 +3424,7 @@ fn test_qrc20_withdraw() { log!("{}", withdraw_json); assert!(withdraw_json["tx_hex"].as_str().unwrap().contains("5403a02526012844a9059cbb0000000000000000000000000240b898276ad2cc0d2fe6f527e8e31104e7fde3000000000000000000000000000000000000000000000000000000003b9aca0014d362e096e873eb7907e205fadc6175c6fec7bc44c2")); - let send_tx = block_on(mm.rpc(&json! ({ + let send_tx = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "send_raw_transaction", "coin": "QRC20", @@ -3445,7 +3445,7 @@ fn test_qrc20_withdraw_error() { ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -3479,7 +3479,7 @@ fn test_qrc20_withdraw_error() { assert_eq!(balance, "10"); // try to transfer more than balance - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "withdraw", "coin": "QRC20", @@ -3498,7 +3498,7 @@ fn test_qrc20_withdraw_error() { .contains("Not enough QRC20 to withdraw: available 10, required at least 11")); // try to transfer with zero QTUM balance - let withdraw = block_on(mm.rpc(&json! ({ + let withdraw = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "withdraw", "coin": "QRC20", @@ -3526,12 +3526,12 @@ fn test_qrc20_withdraw_error() { #[test] #[cfg(not(target_arch = "wasm32"))] fn test_get_raw_transaction() { - let coins = json! ([ + let coins = json!([ {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, {"coin":"ETH","name":"ethereum","chain_id":1,"protocol":{"type":"ETH"}}, ]); let mm = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "passphrase": "boob", @@ -3548,7 +3548,7 @@ fn test_get_raw_transaction() { log!("log path: {}", mm.log_path.display()); // RICK let _electrum = block_on(enable_electrum(&mm, "RICK", false, DOC_ELECTRUM_ADDRS)); - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3566,7 +3566,7 @@ fn test_get_raw_transaction() { assert_eq!(res.result.tx_hex, expected_hex); // ETH - let eth = block_on(mm.rpc(&json! ({ + let eth = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "enable", "coin": "ETH", @@ -3577,7 +3577,7 @@ fn test_get_raw_transaction() { }))) .unwrap(); assert_eq!(eth.0, StatusCode::OK, "'enable' failed: {}", eth.1); - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3594,7 +3594,7 @@ fn test_get_raw_transaction() { json::from_str(&raw.1).expect("Expected 'RpcSuccessResponse'"); let expected_hex = "f9012a19851a0de19041830249f09424abe4c71fc658c91313b6552cd40cd808b3ea8080b8c49b415b2a167d3413b0116abb8e99f4c2d4cd39a64df9bc9950006c4ae884527258247dc100000000000000000000000000000000000000000000000006f05b59d3b200000000000000000000000000000d8775f648430679a709e98d2b0cb6250d2887ef0000000000000000000000000112679fc5e6338a52098ab095bee1e9a15bc630ba9528127bcff524677236f3739cef013311f42000000000000000000000000000000000000000000000000000000000000000000000000000000000619626fa25a0b143893550c8d0164278f94d5fa51ba71e3dfefa112e6f53a575bcb494633a07a00cc60b65e44ae5053257b91c1023b637a38d87ffc32c822591275a6283cd6ec5"; assert_eq!(res.result.tx_hex, expected_hex); - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3614,7 +3614,7 @@ fn test_get_raw_transaction() { // invalid coin let zombi_coin = String::from("ZOMBI"); - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3636,7 +3636,7 @@ fn test_get_raw_transaction() { assert_eq!(error.error_data, Some(expected_error)); // empty hash - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3655,7 +3655,7 @@ fn test_get_raw_transaction() { let error: RpcErrorResponse = json::from_str(&raw.1).unwrap(); assert_eq!(error.error_type, "InvalidHashError"); // invalid hash - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3675,7 +3675,7 @@ fn test_get_raw_transaction() { assert_eq!(error.error_type, "InvalidHashError"); // valid hash but hash not exist - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3694,7 +3694,7 @@ fn test_get_raw_transaction() { let error: RpcErrorResponse = json::from_str(&raw.1).unwrap(); assert_eq!(error.error_type, "HashNotExist"); // valid hash but hash not exist without 0x prefix - let raw = block_on(mm.rpc(&json! ({ + let raw = block_on(mm.rpc(&json!({ "mmrpc": "2.0", "userpass": mm.userpass, "method": "get_raw_transaction", @@ -3917,13 +3917,13 @@ fn test_tx_history_tbtc_non_segwit() { fn test_update_maker_order() { let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json! ([ + let coins = json!([ {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} ]); let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -3945,7 +3945,7 @@ fn test_update_maker_order() { log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); log!("Issue bob sell request"); - let setprice = block_on(mm_bob.rpc(&json! ({ + let setprice = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -3964,7 +3964,7 @@ fn test_update_maker_order() { let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); log!("Issue bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -3982,7 +3982,7 @@ fn test_update_maker_order() { assert_eq!(update_maker_order_json["result"]["min_base_vol"], Json::from("1")); log!("Issue another bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4000,7 +4000,7 @@ fn test_update_maker_order() { assert_eq!(update_maker_order_json["result"]["min_base_vol"], Json::from("1")); log!("Get bob balance"); - let my_balance = block_on(mm_bob.rpc(&json! ({ + let my_balance = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "my_balance", "coin": "RICK", @@ -4031,7 +4031,7 @@ fn test_update_maker_order() { let max_volume = balance - trade_fee; log!("Issue another bob update maker order request"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4057,13 +4057,13 @@ fn test_update_maker_order() { fn test_update_maker_order_fail() { let bob_passphrase = get_passphrase(&".env.seed", "BOB_PASSPHRASE").unwrap(); - let coins = json! ([ + let coins = json!([ {"coin":"RICK","asset":"RICK","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, {"coin":"MORTY","asset":"MORTY","required_confirmations":0,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}} ]); let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -4085,7 +4085,7 @@ fn test_update_maker_order_fail() { log!("{:?}", block_on(enable_coins_rick_morty_electrum(&mm_bob))); log!("Issue bob sell request"); - let setprice = block_on(mm_bob.rpc(&json! ({ + let setprice = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -4103,7 +4103,7 @@ fn test_update_maker_order_fail() { let uuid: Uuid = json::from_value(setprice_json["result"]["uuid"].clone()).unwrap(); log!("Issue bob update maker order request that should fail because price is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4117,7 +4117,7 @@ fn test_update_maker_order_fail() { ); log!("Issue bob update maker order request that should fail because New Volume is Less than Zero"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4131,7 +4131,7 @@ fn test_update_maker_order_fail() { ); log!("Issue bob update maker order request that should fail because Min base vol is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4146,7 +4146,7 @@ fn test_update_maker_order_fail() { ); log!("Issue bob update maker order request that should fail because Max base vol is below Min base vol"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4161,7 +4161,7 @@ fn test_update_maker_order_fail() { ); log!("Issue bob update maker order request that should fail because Max base vol is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4176,7 +4176,7 @@ fn test_update_maker_order_fail() { ); log!("Issue bob update maker order request that should fail because Max rel vol is too low"); - let update_maker_order = block_on(mm_bob.rpc(&json! ({ + let update_maker_order = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "update_maker_order", "uuid": uuid, @@ -4266,7 +4266,7 @@ fn test_trade_fee_returns_numbers_in_various_formats() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -4286,7 +4286,7 @@ fn test_trade_fee_returns_numbers_in_various_formats() { log!("Bob log path: {}", mm_bob.log_path.display()); block_on(enable_coins_rick_morty_electrum(&mm_bob)); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "get_trade_fee", "coin": "RICK", @@ -4308,7 +4308,7 @@ fn test_orderbook_is_mine_orders() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -4332,7 +4332,7 @@ fn test_orderbook_is_mine_orders() { block_on(enable_coins_rick_morty_electrum(&mm_bob)) ); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -4345,7 +4345,7 @@ fn test_orderbook_is_mine_orders() { let _bob_setprice: Json = json::from_str(&rc.1).unwrap(); let mm_alice = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "dht": "on", // Enable DHT without delay. @@ -4372,7 +4372,7 @@ fn test_orderbook_is_mine_orders() { // Bob orderbook must show 1 mine order log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "RICK", @@ -4390,7 +4390,7 @@ fn test_orderbook_is_mine_orders() { // Alice orderbook must show 1 not-mine order log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -4407,7 +4407,7 @@ fn test_orderbook_is_mine_orders() { assert!(!is_mine); // make another order by Alice - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "setprice", "base": "RICK", @@ -4424,7 +4424,7 @@ fn test_orderbook_is_mine_orders() { // Bob orderbook must show 1 mine and 1 non-mine orders. // Request orderbook with reverse base and rel coins to check bids instead of asks log!("Get RICK/MORTY orderbook on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "MORTY", @@ -4444,7 +4444,7 @@ fn test_orderbook_is_mine_orders() { // Alice orderbook must show 1 mine and 1 non-mine orders log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "orderbook", "base": "RICK", @@ -4486,7 +4486,7 @@ fn test_mm2_db_migration() { // if there is an issue with migration the start will fail MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 8999, "dht": "on", // Enable DHT without delay. @@ -4647,7 +4647,7 @@ fn test_get_orderbook_with_same_orderbook_ticker() { let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("Log path: {}", mm.log_path.display()); - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "orderbook", "base": "RICK", @@ -4660,7 +4660,7 @@ fn test_get_orderbook_with_same_orderbook_ticker() { rc.1 ); - let rc = block_on(mm.rpc(&json! ({ + let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, "method": "orderbook", "base": "RICK", @@ -4700,7 +4700,7 @@ fn test_conf_settings_in_orderbook() { ); log!("Issue set_price request for RICK/MORTY on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -4712,7 +4712,7 @@ fn test_conf_settings_in_orderbook() { assert!(rc.0.is_success(), "!setprice: {}", rc.1); log!("Issue set_price request for MORTY/RICK on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "MORTY", @@ -4745,7 +4745,7 @@ fn test_conf_settings_in_orderbook() { ); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -4823,7 +4823,7 @@ fn alice_can_see_confs_in_orderbook_after_sync() { ); log!("Issue sell request on Bob side"); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "method": "setprice", "base": "RICK", @@ -4834,7 +4834,7 @@ fn alice_can_see_confs_in_orderbook_after_sync() { .unwrap(); assert!(rc.0.is_success(), "!setprice: {}", rc.1); - let rc = block_on(mm_bob.rpc(&json! ({ + let rc = block_on(mm_bob.rpc(&json!({ "userpass": mm_bob.userpass, "mmrpc": "2.0", "method": "get_public_key", @@ -4874,7 +4874,7 @@ fn alice_can_see_confs_in_orderbook_after_sync() { // setting the price will trigger Alice's subscription to the orderbook topic // but won't request the actual orderbook - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "setprice", "base": "RICK", @@ -4894,7 +4894,7 @@ fn alice_can_see_confs_in_orderbook_after_sync() { .unwrap(); log!("Get RICK/MORTY orderbook on Alice side"); - let rc = block_on(mm_alice.rpc(&json! ({ + let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, "method": "orderbook", "base": "RICK", @@ -4950,7 +4950,7 @@ fn test_sign_verify_message_utxo() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -5017,7 +5017,7 @@ fn test_sign_verify_message_utxo_segwit() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -5094,7 +5094,7 @@ fn test_sign_verify_message_eth() { // start bob and immediately place the order let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -5164,7 +5164,7 @@ fn test_no_login() { ]; for (base, rel, price, volume, min_volume) in orders.iter() { - let rc = block_on(seednode.rpc(&json! ({ + let rc = block_on(seednode.rpc(&json!({ "userpass": seednode.userpass, "method": "setprice", "base": base, @@ -5178,7 +5178,7 @@ fn test_no_login() { assert!(rc.0.is_success(), "!setprice: {}", rc.1); } - let orderbook = block_on(no_login_node.rpc(&json! ({ + let orderbook = block_on(no_login_node.rpc(&json!({ "userpass": no_login_node.userpass, "method": "orderbook", "base": "RICK", @@ -5190,7 +5190,7 @@ fn test_no_login() { assert_eq!(orderbook.asks.len(), 3); assert_eq!(orderbook.bids.len(), 2); - let orderbook_v2 = block_on(no_login_node.rpc(&json! ({ + let orderbook_v2 = block_on(no_login_node.rpc(&json!({ "userpass": no_login_node.userpass, "mmrpc": "2.0", "method": "orderbook", @@ -5206,7 +5206,7 @@ fn test_no_login() { assert_eq!(orderbook_v2.asks.len(), 3); assert_eq!(orderbook_v2.bids.len(), 2); - let best_orders = block_on(no_login_node.rpc(&json! ({ + let best_orders = block_on(no_login_node.rpc(&json!({ "userpass": no_login_node.userpass, "method": "best_orders", "coin": "RICK", @@ -5221,7 +5221,7 @@ fn test_no_login() { let expected_price: BigDecimal = "0.8".parse().unwrap(); assert_eq!(expected_price, best_morty_orders[0].price); - let best_orders_v2 = block_on(no_login_node.rpc(&json! ({ + let best_orders_v2 = block_on(no_login_node.rpc(&json!({ "userpass": no_login_node.userpass, "mmrpc": "2.0", "method": "best_orders", @@ -5242,7 +5242,7 @@ fn test_no_login() { let expected_price: BigDecimal = "0.7".parse().unwrap(); assert_eq!(expected_price, best_morty_orders[0].price.decimal); - let orderbook_depth = block_on(no_login_node.rpc(&json! ({ + let orderbook_depth = block_on(no_login_node.rpc(&json!({ "userpass": no_login_node.userpass, "method": "orderbook_depth", "pairs":[["RICK","MORTY"]] @@ -5258,7 +5258,7 @@ fn test_no_login() { assert_eq!(orderbook_depth[0].depth.asks, 3); assert_eq!(orderbook_depth[0].depth.bids, 2); - let version = block_on(no_login_node.rpc(&json! ({ + let version = block_on(no_login_node.rpc(&json!({ "userpass": no_login_node.userpass, "method": "version", }))) @@ -5266,284 +5266,13 @@ fn test_no_login() { assert!(version.0.is_success(), "!version: {}", version.1); } -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_gui_storage_accounts_functionality() { - let passphrase = "test_gui_storage passphrase"; - - let conf = Mm2TestConf::seednode(passphrase, &json!([])); - let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm.mm_dump(); - log!("Log path: {}", mm.log_path.display()); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::enable_account", - "params": { - "policy": "new", - "account_id": { - "type": "iguana" - }, - "name": "My Iguana wallet", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::enable_account: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::add_account", - "params": { - "account_id": { - "type": "hw", - "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" - }, - "description": "Any description", - "name": "My HW", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::add_account: {}", resp.1); - - // Add `HD{1}` account that will be deleted later. - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::add_account", - "params": { - "account_id": { - "type": "hd", - "account_idx": 1, - }, - "name": "An HD account" - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::add_account: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::delete_account", - "params": { - "account_id": { - "type": "hd", - "account_idx": 1, - } - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::delete_account: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::set_account_balance", - "params": { - "account_id": { - "type": "hw", - "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" - }, - "balance_usd": "123.567", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::set_account_balance: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::set_account_name", - "params": { - "account_id": { - "type": "iguana" - }, - "name": "New Iguana account name", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::set_account_name: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::set_account_description", - "params": { - "account_id": { - "type": "iguana" - }, - "description": "Another description", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::set_account_description: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::get_accounts" - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::get_accounts: {}", resp.1); - - let actual: RpcV2Response> = json::from_str(&resp.1).unwrap(); - let expected = vec![ - gui_storage::AccountWithEnabledFlag { - account_id: gui_storage::AccountId::Iguana, - name: "New Iguana account name".to_string(), - description: "Another description".to_string(), - balance_usd: BigDecimal::from(0i32), - enabled: true, - }, - gui_storage::AccountWithEnabledFlag { - account_id: gui_storage::AccountId::HW { - device_pubkey: "1549128bbfb33b997949b4105b6a6371c998e212".to_string(), - }, - name: "My HW".to_string(), - description: "Any description".to_string(), - balance_usd: BigDecimal::from(123567i32) / BigDecimal::from(1000i32), - enabled: false, - }, - ]; - assert_eq!(actual.result, expected); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_gui_storage_coins_functionality() { - let passphrase = "test_gui_storage passphrase"; - - let conf = Mm2TestConf::seednode(passphrase, &json!([])); - let mm = block_on(MarketMakerIt::start_async(conf.conf, conf.rpc_password, None)).unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm.mm_dump(); - log!("Log path: {}", mm.log_path.display()); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::enable_account", - "params": { - "policy": "new", - "account_id": { - "type": "iguana" - }, - "name": "My Iguana wallet", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::enable_account: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::add_account", - "params": { - "account_id": { - "type": "hw", - "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" - }, - "description": "Any description", - "name": "My HW", - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::add_account: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::activate_coins", - "params": { - "account_id": { - "type": "iguana" - }, - "tickers": ["RICK", "MORTY", "KMD"], - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::activate_coins: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::activate_coins", - "params": { - "account_id": { - "type": "hw", - "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" - }, - "tickers": ["KMD", "MORTY", "BCH"], - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::activate_coins: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::deactivate_coins", - "params": { - "account_id": { - "type": "hw", - "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" - }, - "tickers": ["BTC", "MORTY"], - }, - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::deactivate_coins: {}", resp.1); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::get_enabled_account", - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::get_enabled_account: {}", resp.1); - let actual: RpcV2Response = json::from_str(&resp.1).unwrap(); - let expected = gui_storage::AccountWithCoins { - account_id: gui_storage::AccountId::Iguana, - name: "My Iguana wallet".to_string(), - description: String::new(), - balance_usd: BigDecimal::from(0i32), - coins: vec!["RICK".to_string(), "MORTY".to_string(), "KMD".to_string()] - .into_iter() - .collect(), - }; - assert_eq!(actual.result, expected); - - let resp = block_on(mm.rpc(&json!({ - "userpass": mm.userpass, - "mmrpc": "2.0", - "method": "gui_storage::get_account_coins", - "params": { - "account_id": { - "type": "hw", - "device_pubkey": "1549128bbfb33b997949b4105b6a6371c998e212" - } - } - }))) - .unwrap(); - assert!(resp.0.is_success(), "!gui_storage::get_enabled_account: {}", resp.1); - let actual: RpcV2Response = json::from_str(&resp.1).unwrap(); - let expected = gui_storage::AccountCoins { - account_id: gui_storage::AccountId::HW { - device_pubkey: "1549128bbfb33b997949b4105b6a6371c998e212".to_string(), - }, - coins: vec!["KMD".to_string(), "BCH".to_string()].into_iter().collect(), - }; - assert_eq!(actual.result, expected); -} - #[test] #[cfg(not(target_arch = "wasm32"))] fn test_enable_btc_with_sync_starting_header() { let coins = json!([btc_with_sync_starting_header()]); let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), @@ -5573,7 +5302,7 @@ fn test_btc_block_header_sync() { let coins = json!([btc_with_spv_conf()]); let mm_bob = MarketMakerIt::start( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(), diff --git a/mm2src/mm2_state_machine/src/storable_state_machine.rs b/mm2src/mm2_state_machine/src/storable_state_machine.rs index 49b57af532..a361c4cf57 100644 --- a/mm2src/mm2_state_machine/src/storable_state_machine.rs +++ b/mm2src/mm2_state_machine/src/storable_state_machine.rs @@ -151,7 +151,7 @@ pub trait StorableStateMachine: Send + Sync + Sized + 'static { type RecreateError: Send; /// Returns State machine's DB representation() - fn to_db_repr(&self) -> ::DbRepr; + async fn to_db_repr(&self) -> ::DbRepr; /// Gets a mutable reference to the storage for the state machine. fn storage(&mut self) -> &mut Self::Storage; @@ -248,7 +248,7 @@ impl StateMachineTrait for T { let reentrancy_lock = self.acquire_reentrancy_lock().await?; let id = self.id(); if !self.storage().has_record_for(&id).await? { - let repr = self.to_db_repr(); + let repr = self.to_db_repr().await; self.storage().store_repr(id, repr).await?; } self.spawn_reentrancy_lock_renew(reentrancy_lock); @@ -457,7 +457,7 @@ mod tests { type RecreateCtx = (); type RecreateError = Infallible; - fn to_db_repr(&self) -> TestStateMachineRepr { TestStateMachineRepr {} } + async fn to_db_repr(&self) -> TestStateMachineRepr { TestStateMachineRepr {} } fn storage(&mut self) -> &mut Self::Storage { &mut self.storage } diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 57084a72e2..dd34570acb 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -41,6 +41,7 @@ cfg_native! { use futures::task::SpawnExt; use http::Request; use regex::Regex; + use mm2_core::sql_connection_pool::{AsyncSqliteConnPool, SqliteConnPool}; use std::fs; use std::io::Write; use std::net::Ipv4Addr; @@ -1101,34 +1102,21 @@ pub fn mm_ctx_with_custom_db() -> MmArc { mm_ctx_with_custom_db_with_conf(None) #[cfg(not(target_arch = "wasm32"))] pub fn mm_ctx_with_custom_db_with_conf(conf: Option) -> MmArc { - use db_common::sqlite::rusqlite::Connection; - use std::sync::Arc; - let mut ctx_builder = MmCtxBuilder::new(); if let Some(conf) = conf { ctx_builder = ctx_builder.with_conf(conf); } let ctx = ctx_builder.into_mm_arc(); - - let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.sqlite_connection.pin(Arc::new(Mutex::new(connection))); - - let connection = Connection::open_in_memory().unwrap(); - let _ = ctx.shared_sqlite_conn.pin(Arc::new(Mutex::new(connection))); + let _ = SqliteConnPool::init_test(&ctx).unwrap(); + let _ = SqliteConnPool::init_shared_test(&ctx).unwrap(); ctx } #[cfg(not(target_arch = "wasm32"))] pub async fn mm_ctx_with_custom_async_db() -> MmArc { - use db_common::async_sql_conn::AsyncConnection; - use futures::lock::Mutex as AsyncMutex; - use std::sync::Arc; - let ctx = MmCtxBuilder::new().into_mm_arc(); - - let connection = AsyncConnection::open_in_memory().await.unwrap(); - let _ = ctx.async_sqlite_connection.pin(Arc::new(AsyncMutex::new(connection))); + AsyncSqliteConnPool::init_test(&ctx, None).await.unwrap(); ctx } @@ -1138,6 +1126,7 @@ pub struct RaiiKill { pub handle: Child, running: bool, } + impl RaiiKill { pub fn from_handle(handle: Child) -> RaiiKill { RaiiKill { handle, running: true } } pub fn running(&mut self) -> bool { @@ -1153,6 +1142,7 @@ impl RaiiKill { } } } + impl Drop for RaiiKill { fn drop(&mut self) { // The cached `running` check might provide some protection against killing a wrong process under the same PID, @@ -1173,6 +1163,7 @@ pub struct RaiiDump { #[cfg(not(target_arch = "wasm32"))] pub log_path: PathBuf, } + #[cfg(not(target_arch = "wasm32"))] impl Drop for RaiiDump { fn drop(&mut self) { @@ -2180,7 +2171,7 @@ pub async fn init_lightning(mm: &MarketMakerIt, coin: &str) -> Json { pub async fn init_lightning_status(mm: &MarketMakerIt, task_id: u64) -> Json { let request = mm - .rpc(&json! ({ + .rpc(&json!({ "userpass": mm.userpass, "method": "task::enable_lightning::status", "mmrpc": "2.0", @@ -2267,7 +2258,8 @@ pub async fn my_swap_status(mm: &MarketMakerIt, uuid: &str) -> Result = swaps_response["result"]["swaps"].as_array().unwrap(); + let swaps: &Vec = swaps_response["result"][0]["swaps"].as_array().unwrap(); assert_eq!(expected_len, swaps.len()); } @@ -2854,7 +2846,7 @@ pub async fn max_maker_vol(mm: &MarketMakerIt, coin: &str) -> RpcResponse { } pub async fn disable_coin(mm: &MarketMakerIt, coin: &str, force_disable: bool) -> DisableResult { - let req = json! ({ + let req = json!({ "userpass": mm.userpass, "method": "disable_coin", "coin": coin, @@ -2870,7 +2862,7 @@ pub async fn disable_coin(mm: &MarketMakerIt, coin: &str, force_disable: bool) - /// Returns a `DisableCoinError` error. pub async fn disable_coin_err(mm: &MarketMakerIt, coin: &str, force_disable: bool) -> DisableCoinError { let disable = mm - .rpc(&json! ({ + .rpc(&json!({ "userpass": mm.userpass, "method": "disable_coin", "coin": coin, @@ -2884,7 +2876,7 @@ pub async fn disable_coin_err(mm: &MarketMakerIt, coin: &str, force_disable: boo pub async fn assert_coin_not_found_on_balance(mm: &MarketMakerIt, coin: &str) { let balance = mm - .rpc(&json! ({ + .rpc(&json!({ "userpass": mm.userpass, "method": "my_balance", "coin": coin @@ -3035,8 +3027,8 @@ pub async fn init_utxo_electrum( "rpc": "Electrum", "rpc_data": { "servers": servers, - "path_to_address": path_to_address, - } + }, + "path_to_address": path_to_address } }); if let Some(priv_key_policy) = priv_key_policy { @@ -3334,7 +3326,7 @@ pub async fn test_qrc20_history_impl(local_start: Option) { ]); let mut mm = MarketMakerIt::start_async( - json! ({ + json!({ "gui": "nogui", "netid": 9998, "myipaddr": env::var ("BOB_TRADE_IP") .ok(),