diff --git a/Cargo.lock b/Cargo.lock index eba3d3c01d..8bdd4d52cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4079,7 +4079,7 @@ dependencies = [ [[package]] name = "mm2_bin_lib" -version = "1.0.6-beta" +version = "1.0.7-beta" dependencies = [ "chrono", "common", diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 4dda9ea124..cba9ece714 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -110,7 +110,7 @@ use crate::nft::{find_wallet_nft_amount, WithdrawNftResult}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; mod nonce; -use crate::{PrivKeyPolicy, WithdrawFrom}; +use crate::{PrivKeyPolicy, TransactionResult, WithdrawFrom}; use nonce::ParityNonce; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -1094,18 +1094,18 @@ impl SwapOps for EthCoin { ) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - Box::new( - self.refund_hash_time_locked_payment(taker_refunds_payment_args) - .map(TransactionEnum::from), - ) + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + self.refund_hash_time_locked_payment(taker_refunds_payment_args) + .map(TransactionEnum::from) + .compat() + .await } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - Box::new( - self.refund_hash_time_locked_payment(maker_refunds_payment_args) - .map(TransactionEnum::from), - ) + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + self.refund_hash_time_locked_payment(maker_refunds_payment_args) + .map(TransactionEnum::from) + .compat() + .await } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 5c2d844381..5ab37c7e9b 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -380,10 +380,7 @@ fn send_and_refund_erc20_payment() { swap_unique_data: &[], watcher_reward: false, }; - let refund = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); log!("{:?}", refund); let status = block_on( @@ -470,10 +467,7 @@ fn send_and_refund_eth_payment() { swap_unique_data: &[], watcher_reward: false, }; - let refund = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); log!("{:?}", refund); diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 02a0c41cfd..979276d230 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -22,10 +22,10 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, - TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::bech32::ToBase32; @@ -660,16 +660,22 @@ impl SwapOps for LightningCoin { self.spend_swap_payment(taker_spends_payment_args) } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { - Box::new(futures01::future::err(TransactionErr::Plain( + async fn send_taker_refunds_payment( + &self, + _taker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { + Err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund lightning HTLC".into(), - ))) + )) } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { - Box::new(futures01::future::err(TransactionErr::Plain( + async fn send_maker_refunds_payment( + &self, + _maker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { + Err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund lightning HTLC".into(), - ))) + )) } // Todo: This validates the dummy fee for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 5479ca7047..9213115d31 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -293,6 +293,7 @@ pub mod z_coin; use z_coin::{ZCoin, ZcoinProtocolInfo}; pub type TransactionFut = Box + Send>; +pub type TransactionResult = Result; pub type BalanceResult = Result>; pub type BalanceFut = Box> + Send>; pub type NonZeroBalanceFut = Box> + Send>; @@ -312,6 +313,9 @@ pub type RawTransactionResult = Result = Box> + Send + 'a>; pub type RefundResult = Result>; +pub type GenAndSignDexFeeSpendResult = MmResult; +pub type ValidateDexFeeResult = MmResult<(), ValidateDexFeeError>; +pub type ValidateDexFeeSpendPreimageResult = MmResult<(), ValidateDexFeeSpendPreimageError>; pub type IguanaPrivKey = Secp256k1Secret; @@ -845,9 +849,9 @@ pub trait SwapOps { fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut; + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult; - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut; + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult; fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()>; @@ -1013,6 +1017,133 @@ pub trait WatcherOps { ) -> Result, MmError>; } +pub struct SendDexFeeWithPremiumArgs<'a> { + pub time_lock: u32, + pub secret_hash: &'a [u8], + pub other_pub: &'a [u8], + pub dex_fee_amount: BigDecimal, + pub premium_amount: BigDecimal, + pub swap_unique_data: &'a [u8], +} + +pub struct ValidateDexFeeArgs<'a> { + pub dex_fee_tx: &'a [u8], + pub time_lock: u32, + pub secret_hash: &'a [u8], + pub other_pub: &'a [u8], + pub dex_fee_amount: BigDecimal, + pub premium_amount: BigDecimal, + pub swap_unique_data: &'a [u8], +} + +pub struct GenDexFeeSpendArgs<'a> { + pub dex_fee_tx: &'a [u8], + pub time_lock: u32, + pub secret_hash: &'a [u8], + pub maker_pub: &'a [u8], + pub taker_pub: &'a [u8], + pub dex_fee_pub: &'a [u8], + pub dex_fee_amount: BigDecimal, + pub premium_amount: BigDecimal, +} + +pub struct TxPreimageWithSig { + preimage: Vec, + signature: Vec, +} + +#[derive(Debug)] +pub enum TxGenError { + Rpc(String), + NumConversion(String), + AddressDerivation(String), + TxDeserialization(String), + InvalidPubkey(String), + Signing(String), + MinerFeeExceedsPremium { miner_fee: BigDecimal, premium: BigDecimal }, + Legacy(String), +} + +impl From for TxGenError { + fn from(err: UtxoRpcError) -> Self { TxGenError::Rpc(err.to_string()) } +} + +impl From for TxGenError { + fn from(err: NumConversError) -> Self { TxGenError::NumConversion(err.to_string()) } +} + +impl From for TxGenError { + fn from(err: UtxoSignWithKeyPairError) -> Self { TxGenError::Signing(err.to_string()) } +} + +#[derive(Debug)] +pub enum ValidateDexFeeError { + InvalidDestinationOrAmount(String), + InvalidPubkey(String), + NumConversion(String), + Rpc(String), + TxBytesMismatch { from_rpc: BytesJson, actual: BytesJson }, + TxDeserialization(String), + TxLacksOfOutputs, +} + +impl From for ValidateDexFeeError { + fn from(err: NumConversError) -> Self { ValidateDexFeeError::NumConversion(err.to_string()) } +} + +impl From for ValidateDexFeeError { + fn from(err: UtxoRpcError) -> Self { ValidateDexFeeError::Rpc(err.to_string()) } +} + +#[derive(Debug)] +pub enum ValidateDexFeeSpendPreimageError { + InvalidPubkey(String), + InvalidTakerSignature, + InvalidPreimage(String), + SignatureVerificationFailure(String), + TxDeserialization(String), + TxGenError(String), +} + +impl From for ValidateDexFeeSpendPreimageError { + fn from(err: UtxoSignWithKeyPairError) -> Self { + ValidateDexFeeSpendPreimageError::SignatureVerificationFailure(err.to_string()) + } +} + +impl From for ValidateDexFeeSpendPreimageError { + fn from(err: TxGenError) -> Self { ValidateDexFeeSpendPreimageError::TxGenError(format!("{:?}", err)) } +} + +#[async_trait] +pub trait SwapOpsV2 { + async fn send_dex_fee_with_premium(&self, args: SendDexFeeWithPremiumArgs<'_>) -> TransactionResult; + + async fn validate_dex_fee_with_premium(&self, args: ValidateDexFeeArgs<'_>) -> ValidateDexFeeResult; + + async fn refund_dex_fee_with_premium(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; + + async fn gen_and_sign_dex_fee_spend_preimage( + &self, + args: &GenDexFeeSpendArgs<'_>, + swap_unique_data: &[u8], + ) -> GenAndSignDexFeeSpendResult; + + async fn validate_dex_fee_spend_preimage( + &self, + gen_args: &GenDexFeeSpendArgs<'_>, + preimage: &TxPreimageWithSig, + ) -> ValidateDexFeeSpendPreimageResult; + + async fn sign_and_broadcast_dex_fee_spend( + &self, + preimage: &TxPreimageWithSig, + gen_args: &GenDexFeeSpendArgs<'_>, + secret: &[u8], + swap_unique_data: &[u8], + ) -> TransactionResult; +} + /// Operations that coins have independently from the MarketMaker. /// That is, things implemented by the coin wallets or public coin services. pub trait MarketCoinOps { diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 1c37458fbf..c84d0cbe24 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -11,14 +11,14 @@ pub(crate) mod storage; use crate::{coin_conf, get_my_address, MyAddressReq, WithdrawError}; use nft_errors::{GetInfoFromUriError, GetNftInfoError, UpdateNftError}; use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq, - NftTransferHistory, NftTransfersReq, NftTxHistoryFromMoralis, NftsTransferHistoryList, + NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList, TransactionNftDetails, UpdateNftReq, WithdrawNftReq}; use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721}; use crate::nft::nft_errors::ProtectFromSpamError; -use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetadataReq, TransferStatus, TxMeta, +use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetadataReq, TransferMeta, TransferStatus, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTxHistoryStorageOps}; +use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps}; use common::{parse_rfc3339_to_timestamp, APPLICATION_JSON}; use crypto::StandardHDCoinAddress; use ethereum_types::Address; @@ -92,16 +92,16 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult MmResult<(), UpdateNft let storage = NftStorageBuilder::new(&ctx).build()?; for chain in req.chains.iter() { - let tx_history_initialized = NftTxHistoryStorageOps::is_initialized(&storage, chain).await?; + let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; - let from_block = if tx_history_initialized { - let last_tx_block = NftTxHistoryStorageOps::get_last_block_number(&storage, chain).await?; - last_tx_block.map(|b| b + 1) + 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 { - NftTxHistoryStorageOps::init(&storage, chain).await?; + NftTransferHistoryStorageOps::init(&storage, chain).await?; None }; let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url).await?; - storage.add_txs_to_history(chain, nft_transfers).await?; + storage.add_transfers_to_history(chain, nft_transfers).await?; let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await { Ok(Some(block)) => block, Ok(None) => { // if there are no rows in NFT LIST table we can try to get all info from moralis. let nfts = cache_nfts_from_moralis(&ctx, &storage, chain, &req.url).await?; - update_meta_in_txs(&storage, chain, nfts).await?; - update_txs_with_empty_meta(&storage, chain, &req.url).await?; + update_meta_in_transfers(&storage, chain, nfts).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url).await?; continue; }, Err(_) => { // if there is an error, then NFT LIST table doesnt exist, so we need to cache from mroalis. NftListStorageOps::init(&storage, chain).await?; let nft_list = get_moralis_nft_list(&ctx, chain, &req.url).await?; - let last_scanned_block = NftTxHistoryStorageOps::get_last_block_number(&storage, chain) + let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain) .await? .unwrap_or(0); storage .add_nfts_to_list(chain, nft_list.clone(), last_scanned_block) .await?; - update_meta_in_txs(&storage, chain, nft_list).await?; - update_txs_with_empty_meta(&storage, chain, &req.url).await?; + update_meta_in_transfers(&storage, chain, nft_list).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url).await?; continue; }, }; @@ -167,7 +167,7 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft }); } update_nft_list(ctx.clone(), &storage, chain, scanned_block + 1, &req.url).await?; - update_txs_with_empty_meta(&storage, chain, &req.url).await?; + update_transfers_with_empty_meta(&storage, chain, &req.url).await?; } Ok(()) } @@ -205,8 +205,10 @@ pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResu storage .refresh_nft_metadata(&moralis_meta.chain, nft_db.clone()) .await?; - let tx_meta = TxMeta::from(nft_db.clone()); - storage.update_txs_meta_by_token_addr_id(&nft_db.chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_db.clone()); + storage + .update_transfers_meta_by_token_addr_id(&nft_db.chain, transfer_meta) + .await?; Ok(()) } @@ -297,12 +299,13 @@ async fn get_moralis_nft_transfers( let response = send_request_to_uri(uri.as_str()).await?; if let Some(transfer_list) = response["result"].as_array() { for transfer in transfer_list { - let transfer_moralis: NftTxHistoryFromMoralis = serde_json::from_str(&transfer.to_string())?; + let transfer_moralis: NftTransferHistoryFromMoralis = serde_json::from_str(&transfer.to_string())?; let contract_type = match transfer_moralis.contract_type { Some(contract_type) => contract_type, None => continue, }; - let status = get_tx_status(&wallet_address, ð_addr_to_hex(&transfer_moralis.common.to_address)); + let status = + get_transfer_status(&wallet_address, ð_addr_to_hex(&transfer_moralis.common.to_address)); let block_timestamp = parse_rfc3339_to_timestamp(&transfer_moralis.block_timestamp)?; let transfer_history = NftTransferHistory { common: NftTransferCommon { @@ -482,8 +485,8 @@ async fn get_uri_meta(token_uri: Option<&str>, metadata: Option<&str>) -> UriMet uri_meta } -fn get_tx_status(my_wallet: &str, to_address: &str) -> TransferStatus { - // if my_wallet == from_address && my_wallet == to_address it is incoming tx, so we can check just to_address. +fn get_transfer_status(my_wallet: &str, to_address: &str) -> TransferStatus { + // if my_wallet == from_address && my_wallet == to_address it is incoming transfer, so we can check just to_address. if my_wallet.to_lowercase() == to_address.to_lowercase() { TransferStatus::Receive } else { @@ -493,97 +496,99 @@ fn get_tx_status(my_wallet: &str, to_address: &str) -> TransferStatus { /// `update_nft_list` function gets nft transfers from NFT HISTORY table, iterates through them /// and updates NFT LIST table info. -async fn update_nft_list( +async fn update_nft_list( ctx: MmArc, storage: &T, chain: &Chain, scan_from_block: u64, url: &Url, ) -> MmResult<(), UpdateNftError> { - let txs = storage.get_txs_from_block(chain, scan_from_block).await?; + let transfers = storage.get_transfers_from_block(chain, scan_from_block).await?; let req = MyAddressReq { coin: chain.to_ticker(), path_to_address: StandardHDCoinAddress::default(), }; let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase(); - for tx in txs.into_iter() { - handle_nft_tx(storage, chain, url, tx, &my_address).await?; + for transfer in transfers.into_iter() { + handle_nft_transfer(storage, chain, url, transfer, &my_address).await?; } Ok(()) } -async fn handle_nft_tx( +async fn handle_nft_transfer( storage: &T, chain: &Chain, url: &Url, - tx: NftTransferHistory, + transfer: NftTransferHistory, my_address: &str, ) -> MmResult<(), UpdateNftError> { - match (tx.status, tx.contract_type) { - (TransferStatus::Send, ContractType::Erc721) => handle_send_erc721(storage, chain, tx).await, + match (transfer.status, transfer.contract_type) { + (TransferStatus::Send, ContractType::Erc721) => handle_send_erc721(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc721) => { - handle_receive_erc721(storage, chain, tx, url, my_address).await + handle_receive_erc721(storage, chain, transfer, url, my_address).await }, - (TransferStatus::Send, ContractType::Erc1155) => handle_send_erc1155(storage, chain, tx).await, + (TransferStatus::Send, ContractType::Erc1155) => handle_send_erc1155(storage, chain, transfer).await, (TransferStatus::Receive, ContractType::Erc1155) => { - handle_receive_erc1155(storage, chain, tx, url, my_address).await + handle_receive_erc1155(storage, chain, transfer, url, my_address).await }, } } -async fn handle_send_erc721( +async fn handle_send_erc721( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, ) -> MmResult<(), UpdateNftError> { let nft_db = storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { - token_address: eth_addr_to_hex(&tx.common.token_address), - token_id: tx.common.token_id.to_string(), + token_address: eth_addr_to_hex(&transfer.common.token_address), + token_id: transfer.common.token_id.to_string(), })?; - let tx_meta = TxMeta::from(nft_db); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_db); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; storage .remove_nft_from_list( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id, - tx.block_number, + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id, + transfer.block_number, ) .await?; Ok(()) } -async fn handle_receive_erc721( +async fn handle_receive_erc721( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, url: &Url, my_address: &str, ) -> MmResult<(), UpdateNftError> { let nft = match storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? { Some(mut nft_db) => { // An error is raised if user tries to receive an identical ERC-721 token they already own // and if owner address != from address - if my_address != eth_addr_to_hex(&tx.common.from_address) { + if my_address != eth_addr_to_hex(&transfer.common.from_address) { return MmError::err(UpdateNftError::AttemptToReceiveAlreadyOwnedErc721 { - tx_hash: tx.common.transaction_hash, + tx_hash: transfer.common.transaction_hash, }); } - nft_db.block_number = tx.block_number; + nft_db.block_number = transfer.block_number; drop_mutability!(nft_db); storage .update_nft_amount_and_block_number(chain, nft_db.clone()) @@ -593,8 +598,8 @@ async fn handle_receive_erc721( // If token isn't in NFT LIST table then add nft to the table. None => { let mut nft = get_moralis_metadata( - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id, + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id, chain, url, ) @@ -603,86 +608,90 @@ async fn handle_receive_erc721( // than History by Wallet update nft.common.owner_of = Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?; - nft.block_number = tx.block_number; + nft.block_number = transfer.block_number; drop_mutability!(nft); storage - .add_nfts_to_list(chain, vec![nft.clone()], tx.block_number) + .add_nfts_to_list(chain, vec![nft.clone()], transfer.block_number) .await?; nft }, }; - let tx_meta = TxMeta::from(nft); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; Ok(()) } -async fn handle_send_erc1155( +async fn handle_send_erc1155( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, ) -> MmResult<(), UpdateNftError> { let mut nft_db = storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? .ok_or_else(|| UpdateNftError::TokenNotFoundInWallet { - token_address: eth_addr_to_hex(&tx.common.token_address), - token_id: tx.common.token_id.to_string(), + token_address: eth_addr_to_hex(&transfer.common.token_address), + token_id: transfer.common.token_id.to_string(), })?; - match nft_db.common.amount.cmp(&tx.common.amount) { + match nft_db.common.amount.cmp(&transfer.common.amount) { Ordering::Equal => { storage .remove_nft_from_list( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id, - tx.block_number, + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id, + transfer.block_number, ) .await?; }, Ordering::Greater => { - nft_db.common.amount -= tx.common.amount; + nft_db.common.amount -= transfer.common.amount; storage - .update_nft_amount(chain, nft_db.clone(), tx.block_number) + .update_nft_amount(chain, nft_db.clone(), transfer.block_number) .await?; }, Ordering::Less => { return MmError::err(UpdateNftError::InsufficientAmountInCache { amount_list: nft_db.common.amount.to_string(), - amount_history: tx.common.amount.to_string(), + amount_history: transfer.common.amount.to_string(), }); }, } - let tx_meta = TxMeta::from(nft_db); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_db); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; Ok(()) } -async fn handle_receive_erc1155( +async fn handle_receive_erc1155( storage: &T, chain: &Chain, - tx: NftTransferHistory, + transfer: NftTransferHistory, url: &Url, my_address: &str, ) -> MmResult<(), UpdateNftError> { let nft = match storage .get_nft( chain, - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), ) .await? { Some(mut nft_db) => { // if owner address == from address, then owner sent tokens to themself, // which means that the amount will not change. - if my_address != eth_addr_to_hex(&tx.common.from_address) { - nft_db.common.amount += tx.common.amount; + if my_address != eth_addr_to_hex(&transfer.common.from_address) { + nft_db.common.amount += transfer.common.amount; } - nft_db.block_number = tx.block_number; + nft_db.block_number = transfer.block_number; drop_mutability!(nft_db); storage .update_nft_amount_and_block_number(chain, nft_db.clone()) @@ -692,8 +701,8 @@ async fn handle_receive_erc1155( // If token isn't in NFT LIST table then add nft to the table. None => { let moralis_meta = get_moralis_metadata( - eth_addr_to_hex(&tx.common.token_address), - tx.common.token_id.clone(), + eth_addr_to_hex(&transfer.common.token_address), + transfer.common.token_id.clone(), chain, url, ) @@ -704,7 +713,7 @@ async fn handle_receive_erc1155( common: NftCommon { token_address: moralis_meta.common.token_address, token_id: moralis_meta.common.token_id, - amount: tx.common.amount, + amount: transfer.common.amount, owner_of: Address::from_str(my_address) .map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?, token_hash: moralis_meta.common.token_hash, @@ -719,16 +728,20 @@ async fn handle_receive_erc1155( }, chain: *chain, block_number_minted: moralis_meta.block_number_minted, - block_number: tx.block_number, + block_number: transfer.block_number, contract_type: moralis_meta.contract_type, uri_meta, }; - storage.add_nfts_to_list(chain, [nft.clone()], tx.block_number).await?; + storage + .add_nfts_to_list(chain, [nft.clone()], transfer.block_number) + .await?; nft }, }; - let tx_meta = TxMeta::from(nft); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; Ok(()) } @@ -757,14 +770,14 @@ pub(crate) async fn find_wallet_nft_amount( Ok(nft_meta.common.amount) } -async fn cache_nfts_from_moralis( +async fn cache_nfts_from_moralis( ctx: &MmArc, storage: &T, chain: &Chain, url: &Url, ) -> MmResult, UpdateNftError> { let nft_list = get_moralis_nft_list(ctx, chain, url).await?; - let last_scanned_block = NftTxHistoryStorageOps::get_last_block_number(storage, chain) + let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(storage, chain) .await? .unwrap_or(0); storage @@ -773,28 +786,32 @@ async fn cache_nfts_from_moralis( Ok(nft_list) } -/// `update_meta_in_txs` function updates only txs related to current nfts in wallet. -async fn update_meta_in_txs(storage: &T, chain: &Chain, nfts: Vec) -> MmResult<(), UpdateNftError> +/// `update_meta_in_transfers` function updates only transfers related to current nfts in wallet. +async fn update_meta_in_transfers(storage: &T, chain: &Chain, nfts: Vec) -> MmResult<(), UpdateNftError> where - T: NftListStorageOps + NftTxHistoryStorageOps, + T: NftListStorageOps + NftTransferHistoryStorageOps, { for nft in nfts.into_iter() { - let tx_meta = TxMeta::from(nft); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; } Ok(()) } -/// `update_txs_with_empty_meta` function updates empty metadata in transfers. -async fn update_txs_with_empty_meta(storage: &T, chain: &Chain, url: &Url) -> MmResult<(), UpdateNftError> +/// `update_transfers_with_empty_meta` function updates empty metadata in transfers. +async fn update_transfers_with_empty_meta(storage: &T, chain: &Chain, url: &Url) -> MmResult<(), UpdateNftError> where - T: NftListStorageOps + NftTxHistoryStorageOps, + T: NftListStorageOps + NftTransferHistoryStorageOps, { - let nft_token_addr_id = storage.get_txs_with_empty_meta(chain).await?; + let nft_token_addr_id = storage.get_transfers_with_empty_meta(chain).await?; for addr_id_pair in nft_token_addr_id.into_iter() { let nft_meta = get_moralis_metadata(addr_id_pair.token_address, addr_id_pair.token_id, chain, url).await?; - let tx_meta = TxMeta::from(nft_meta); - storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?; + let transfer_meta = TransferMeta::from(nft_meta); + storage + .update_transfers_meta_by_token_addr_id(chain, transfer_meta) + .await?; } Ok(()) } @@ -824,12 +841,12 @@ fn check_and_redact_if_spam(text: &mut Option) -> Result MmResult<(), ProtectFromSpamError> { - let collection_name_spam = check_and_redact_if_spam(&mut tx.collection_name)?; - let token_name_spam = check_and_redact_if_spam(&mut tx.token_name)?; +fn protect_from_history_spam(transfer: &mut NftTransferHistory) -> MmResult<(), ProtectFromSpamError> { + let collection_name_spam = check_and_redact_if_spam(&mut transfer.collection_name)?; + let token_name_spam = check_and_redact_if_spam(&mut transfer.token_name)?; if collection_name_spam || token_name_spam { - tx.common.possible_spam = true; + transfer.common.possible_spam = true; } Ok(()) } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 3db093540c..9d7a07b89a 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -325,7 +325,7 @@ pub struct TransactionNftDetails { #[derive(Debug, Deserialize)] pub struct NftTransfersReq { pub(crate) chains: Vec, - pub(crate) filters: Option, + pub(crate) filters: Option, #[serde(default)] pub(crate) max: bool, #[serde(default = "ten")] @@ -368,14 +368,14 @@ impl fmt::Display for TransferStatus { } } -/// [`NftTransferCommon`] structure contains common fields from [`NftTransferHistory`] and [`NftTxHistoryFromMoralis`] +/// [`NftTransferCommon`] structure contains common fields from [`NftTransferHistory`] and [`NftTransferHistoryFromMoralis`] #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NftTransferCommon { pub(crate) block_hash: Option, /// Transaction hash in hexadecimal format pub(crate) transaction_hash: String, pub(crate) transaction_index: Option, - pub(crate) log_index: Option, + pub(crate) log_index: u32, pub(crate) value: Option, pub(crate) transaction_type: Option, pub(crate) token_address: Address, @@ -404,9 +404,9 @@ pub struct NftTransferHistory { pub(crate) status: TransferStatus, } -/// This structure is for deserializing moralis NFT transaction json to struct. +/// This structure is for deserializing moralis NFT transfer json to struct. #[derive(Debug, Deserialize)] -pub(crate) struct NftTxHistoryFromMoralis { +pub(crate) struct NftTransferHistoryFromMoralis { #[serde(flatten)] pub(crate) common: NftTransferCommon, pub(crate) block_number: SerdeStringWrap, @@ -422,7 +422,7 @@ pub struct NftsTransferHistoryList { } #[derive(Copy, Clone, Debug, Deserialize)] -pub struct NftTxHistoryFilters { +pub struct NftTransferHistoryFilters { #[serde(default)] pub receive: bool, #[serde(default)] @@ -444,7 +444,7 @@ pub struct NftTokenAddrId { } #[derive(Debug)] -pub struct TxMeta { +pub struct TransferMeta { pub(crate) token_address: String, pub(crate) token_id: BigDecimal, pub(crate) token_uri: Option, @@ -453,9 +453,9 @@ pub struct TxMeta { pub(crate) token_name: Option, } -impl From for TxMeta { +impl From for TransferMeta { fn from(nft_db: Nft) -> Self { - TxMeta { + TransferMeta { token_address: eth_addr_to_hex(&nft_db.common.token_address), token_id: nft_db.common.token_id, token_uri: nft_db.common.token_uri, diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index 7beb9e5f12..02710e0ac4 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -6,7 +6,7 @@ const TEST_WALLET_ADDR_EVM: &str = "0x394d86994f954ed931b86791b62fe64f4c5dac37"; #[cfg(all(test, not(target_arch = "wasm32")))] mod native_tests { use crate::eth::eth_addr_to_hex; - use crate::nft::nft_structs::{NftFromMoralis, NftTxHistoryFromMoralis, UriMeta}; + use crate::nft::nft_structs::{NftFromMoralis, NftTransferHistoryFromMoralis, UriMeta}; use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; use crate::nft::storage::db_test_helpers::*; use crate::nft::{check_and_redact_if_spam, check_moralis_ipfs_bafy, check_nft_metadata_for_spam, @@ -70,11 +70,12 @@ mod native_tests { assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); } - let response_tx_history = block_on(send_request_to_uri(NFT_HISTORY_URL_TEST)).unwrap(); - let mut transfer_list = response_tx_history["result"].as_array().unwrap().clone(); + let response_transfer_history = block_on(send_request_to_uri(NFT_HISTORY_URL_TEST)).unwrap(); + let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); assert!(!transfer_list.is_empty()); - let first_tx = transfer_list.remove(transfer_list.len() - 1); - let transfer_moralis: NftTxHistoryFromMoralis = serde_json::from_str(&first_tx.to_string()).unwrap(); + let first_transfer = transfer_list.remove(transfer_list.len() - 1); + let transfer_moralis: NftTransferHistoryFromMoralis = + serde_json::from_str(&first_transfer.to_string()).unwrap(); assert_eq!( TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&transfer_moralis.common.to_address) @@ -107,25 +108,25 @@ mod native_tests { fn test_nft_amount() { block_on(test_nft_amount_impl()) } #[test] - fn test_add_get_txs() { block_on(test_add_get_txs_impl()) } + fn test_add_get_transfers() { block_on(test_add_get_transfers_impl()) } #[test] - fn test_last_tx_block() { block_on(test_last_tx_block_impl()) } + fn test_last_transfer_block() { block_on(test_last_transfer_block_impl()) } #[test] - fn test_tx_history() { block_on(test_tx_history_impl()) } + fn test_transfer_history() { block_on(test_transfer_history_impl()) } #[test] - fn test_tx_history_filters() { block_on(test_tx_history_filters_impl()) } + fn test_transfer_history_filters() { block_on(test_transfer_history_filters_impl()) } #[test] - fn test_get_update_tx_meta() { block_on(test_get_update_tx_meta_impl()) } + fn test_get_update_transfer_meta() { block_on(test_get_update_transfer_meta_impl()) } } #[cfg(target_arch = "wasm32")] mod wasm_tests { use crate::eth::eth_addr_to_hex; - use crate::nft::nft_structs::{NftFromMoralis, NftTxHistoryFromMoralis}; + use crate::nft::nft_structs::{NftFromMoralis, NftTransferHistoryFromMoralis}; use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; use crate::nft::send_request_to_uri; use crate::nft::storage::db_test_helpers::*; @@ -142,11 +143,12 @@ mod wasm_tests { assert_eq!(TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&nft_moralis.common.owner_of)); } - let response_tx_history = send_request_to_uri(NFT_HISTORY_URL_TEST).await.unwrap(); - let mut transfer_list = response_tx_history["result"].as_array().unwrap().clone(); + let response_transfer_history = send_request_to_uri(NFT_HISTORY_URL_TEST).await.unwrap(); + let mut transfer_list = response_transfer_history["result"].as_array().unwrap().clone(); assert!(!transfer_list.is_empty()); - let first_tx = transfer_list.remove(transfer_list.len() - 1); - let transfer_moralis: NftTxHistoryFromMoralis = serde_json::from_str(&first_tx.to_string()).unwrap(); + let first_transfer = transfer_list.remove(transfer_list.len() - 1); + let transfer_moralis: NftTransferHistoryFromMoralis = + serde_json::from_str(&first_transfer.to_string()).unwrap(); assert_eq!( TEST_WALLET_ADDR_EVM, eth_addr_to_hex(&transfer_moralis.common.to_address) @@ -176,17 +178,17 @@ mod wasm_tests { async fn test_refresh_metadata() { test_refresh_metadata_impl().await } #[wasm_bindgen_test] - async fn test_add_get_txs() { test_add_get_txs_impl().await } + async fn test_add_get_transfers() { test_add_get_transfers_impl().await } #[wasm_bindgen_test] - async fn test_last_tx_block() { test_last_tx_block_impl().await } + async fn test_last_transfer_block() { test_last_transfer_block_impl().await } #[wasm_bindgen_test] - async fn test_tx_history() { test_tx_history_impl().await } + async fn test_transfer_history() { test_transfer_history_impl().await } #[wasm_bindgen_test] - async fn test_tx_history_filters() { test_tx_history_filters_impl().await } + async fn test_transfer_history_filters() { test_transfer_history_filters_impl().await } #[wasm_bindgen_test] - async fn test_get_update_tx_meta() { test_get_update_tx_meta_impl().await } + async fn test_get_update_transfer_meta() { test_get_update_transfer_meta_impl().await } } diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs index 44e0ede9e1..7961d4c1ea 100644 --- a/mm2src/coins/nft/storage/db_test_helpers.rs +++ b/mm2src/coins/nft/storage/db_test_helpers.rs @@ -1,7 +1,7 @@ use crate::eth::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftTransferCommon, NftTransferHistory, - NftTxHistoryFilters, TransferStatus, TxMeta, UriMeta}; -use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTxHistoryStorageOps, RemoveNftResult}; + NftTransferHistoryFilters, TransferMeta, TransferStatus, UriMeta}; +use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps, RemoveNftResult}; use ethereum_types::Address; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::mm_ctx_with_custom_db; @@ -17,6 +17,7 @@ cfg_wasm32! { const TOKEN_ADD: &str = "0xfd913a305d70a60aac4faac70c739563738e1f81"; const TOKEN_ID: &str = "214300044414"; const TX_HASH: &str = "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe"; +const LOG_INDEX: u32 = 495; pub(crate) fn nft() -> Nft { Nft { @@ -56,13 +57,13 @@ pub(crate) fn nft() -> Nft { } } -fn tx() -> NftTransferHistory { +fn transfer() -> NftTransferHistory { NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), transaction_index: Some(198), - log_index: Some(495), + log_index: 495, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), @@ -158,6 +159,44 @@ fn nft_list() -> Vec { }; let nft2 = Nft { + common: NftCommon { + token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), + token_id: BigDecimal::from_str("214300047253").unwrap(), + amount: BigDecimal::from_str("1").unwrap(), + owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), + token_hash: Some("c5d1cfd75a0535b0ec750c0156e6ddfe".to_string()), + collection_name: Some("Binance NFT Mystery Box-Back to Blockchain Future".to_string()), + symbol: Some("BMBBBF".to_string()), + token_uri: Some("https://public.nftstatic.com/static/nft/BSC/BMBBBF/214300047252".to_string()), + metadata: Some( + "{\"image\":\"https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png\"}" + .to_string(), + ), + last_token_uri_sync: Some("2023-02-16T16:35:52.392Z".to_string()), + last_metadata_sync: Some("2023-02-16T16:36:04.283Z".to_string()), + minter_address: Some("0xdbdeb0895f3681b87fb3654b5cf3e05546ba24a9".to_string()), + possible_spam: false, + }, + chain: Chain::Bsc, + + block_number_minted: Some(25721963), + block_number: 28056726, + contract_type: ContractType::Erc721, + uri_meta: UriMeta { + image_url: Some( + "https://public.nftstatic.com/static/nft/res/4df0a5da04174e1e9be04b22a805f605.png".to_string(), + ), + raw_image_url: None, + token_name: Some("Nebula Nodes".to_string()), + description: Some("Interchain nodes".to_string()), + attributes: None, + animation_url: None, + external_url: None, + image_details: None, + }, + }; + + let nft3 = Nft { common: NftCommon { token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), token_id: BigDecimal::from_str("214300044414").unwrap(), @@ -194,16 +233,16 @@ fn nft_list() -> Vec { image_details: None, }, }; - vec![nft, nft1, nft2] + vec![nft, nft1, nft2, nft3] } -fn nft_tx_history() -> Vec { - let tx = NftTransferHistory { +fn nft_transfer_history() -> Vec { + let transfer = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0xcb41654fc5cf2bf5d7fd3f061693405c74d419def80993caded0551ecfaeaae5".to_string()), transaction_hash: "0x9c16b962f63eead1c5d2355cc9037dde178b14b53043c57eb40c27964d22ae6a".to_string(), transaction_index: Some(57), - log_index: Some(139), + log_index: 139, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(), @@ -226,12 +265,12 @@ fn nft_tx_history() -> Vec { status: TransferStatus::Receive, }; - let tx1 = NftTransferHistory { + let transfer1 = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), transaction_index: Some(198), - log_index: Some(495), + log_index: 495, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), @@ -256,12 +295,43 @@ fn nft_tx_history() -> Vec { status: TransferStatus::Receive, }; - let tx2 = NftTransferHistory { + // Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction + let transfer2 = NftTransferHistory { + common: NftTransferCommon { + block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), + transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), + transaction_index: Some(198), + log_index: 496, + value: Default::default(), + transaction_type: Some("Single".to_string()), + token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), + token_id: BigDecimal::from_str("214300047253").unwrap(), + from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), + to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), + amount: BigDecimal::from_str("1").unwrap(), + verified: Some(1), + operator: None, + possible_spam: false, + }, + chain: Chain::Bsc, + block_number: 28056726, + block_timestamp: 1683627432, + contract_type: ContractType::Erc721, + + token_uri: None, + collection_name: None, + image_url: None, + token_name: None, + + status: TransferStatus::Receive, + }; + + let transfer3 = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x326db41c5a4fd5f033676d95c590ced18936ef2ef6079e873b23af087fd966c6".to_string()), transaction_hash: "0x981bad702cc6e088f0e9b5e7287ff7a3487b8d269103cee3b9e5803141f63f91".to_string(), transaction_index: Some(83), - log_index: Some(201), + log_index: 201, value: Default::default(), transaction_type: Some("Single".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), @@ -286,10 +356,10 @@ fn nft_tx_history() -> Vec { status: TransferStatus::Receive, }; - vec![tx, tx1, tx2] + vec![transfer, transfer1, transfer2, transfer3] } -async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTxHistoryStorageOps { +async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { let ctx = mm_ctx_with_custom_db(); let storage = NftStorageBuilder::new(&ctx).build().unwrap(); NftListStorageOps::init(&storage, chain).await.unwrap(); @@ -298,11 +368,13 @@ async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTxH storage } -async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTxHistoryStorageOps { +async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps { let ctx = mm_ctx_with_custom_db(); let storage = NftStorageBuilder::new(&ctx).build().unwrap(); - NftTxHistoryStorageOps::init(&storage, chain).await.unwrap(); - let is_initialized = NftTxHistoryStorageOps::is_initialized(&storage, chain).await.unwrap(); + NftTransferHistoryStorageOps::init(&storage, chain).await.unwrap(); + let is_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain) + .await + .unwrap(); assert!(is_initialized); storage } @@ -344,14 +416,14 @@ pub(crate) async fn test_nft_list_impl() { storage.add_nfts_to_list(&chain, nft_list, 28056726).await.unwrap(); let nft_list = storage - .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(2).unwrap())) + .get_nft_list(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap())) .await .unwrap(); assert_eq!(nft_list.nfts.len(), 1); let nft = nft_list.nfts.get(0).unwrap(); assert_eq!(nft.block_number, 28056721); - assert_eq!(nft_list.skipped, 1); - assert_eq!(nft_list.total, 3); + assert_eq!(nft_list.skipped, 2); + assert_eq!(nft_list.total, 4); } pub(crate) async fn test_remove_nft_impl() { @@ -372,7 +444,7 @@ pub(crate) async fn test_remove_nft_impl() { .unwrap() .nfts .len(); - assert_eq!(list_len, 2); + assert_eq!(list_len, 3); let last_scanned_block = storage.get_last_scanned_block(&chain).await.unwrap().unwrap(); assert_eq!(last_scanned_block, 28056800); } @@ -435,126 +507,126 @@ pub(crate) async fn test_refresh_metadata_impl() { assert_eq!(new_symbol.to_string(), nft_upd.common.symbol.unwrap()); } -pub(crate) async fn test_add_get_txs_impl() { +pub(crate) async fn test_add_get_transfers_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); let token_id = BigDecimal::from_str(TOKEN_ID).unwrap(); - let tx1 = storage - .get_txs_by_token_addr_id(&chain, TOKEN_ADD.to_string(), token_id) + let transfer1 = storage + .get_transfers_by_token_addr_id(&chain, TOKEN_ADD.to_string(), token_id) .await .unwrap() .get(0) .unwrap() .clone(); - assert_eq!(tx1.block_number, 28056721); - let tx2 = storage - .get_tx_by_tx_hash(&chain, TX_HASH.to_string()) + assert_eq!(transfer1.block_number, 28056721); + let transfer2 = storage + .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) .await .unwrap() .unwrap(); - assert_eq!(tx2.block_number, 28056726); - let tx_from = storage.get_txs_from_block(&chain, 28056721).await.unwrap(); - assert_eq!(tx_from.len(), 2); + assert_eq!(transfer2.block_number, 28056726); + let transfer_from = storage.get_transfers_from_block(&chain, 28056721).await.unwrap(); + assert_eq!(transfer_from.len(), 3); } -pub(crate) async fn test_last_tx_block_impl() { +pub(crate) async fn test_last_transfer_block_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let last_block = NftTxHistoryStorageOps::get_last_block_number(&storage, &chain) + let last_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, &chain) .await .unwrap() .unwrap(); assert_eq!(last_block, 28056726); } -pub(crate) async fn test_tx_history_impl() { +pub(crate) async fn test_transfer_history_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let tx_history = storage - .get_tx_history(vec![chain], false, 1, Some(NonZeroUsize::new(2).unwrap()), None) + let transfer_history = storage + .get_transfer_history(vec![chain], false, 1, Some(NonZeroUsize::new(3).unwrap()), None) .await .unwrap(); - assert_eq!(tx_history.transfer_history.len(), 1); - let tx = tx_history.transfer_history.get(0).unwrap(); - assert_eq!(tx.block_number, 28056721); - assert_eq!(tx_history.skipped, 1); - assert_eq!(tx_history.total, 3); + assert_eq!(transfer_history.transfer_history.len(), 1); + let transfer = transfer_history.transfer_history.get(0).unwrap(); + assert_eq!(transfer.block_number, 28056721); + assert_eq!(transfer_history.skipped, 2); + assert_eq!(transfer_history.total, 4); } -pub(crate) async fn test_tx_history_filters_impl() { +pub(crate) async fn test_transfer_history_filters_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let filters = NftTxHistoryFilters { + let filters = NftTransferHistoryFilters { receive: true, send: false, from_date: None, to_date: None, }; - let filters1 = NftTxHistoryFilters { + let filters1 = NftTransferHistoryFilters { receive: false, send: false, from_date: None, to_date: Some(1677166110), }; - let filters2 = NftTxHistoryFilters { + let filters2 = NftTransferHistoryFilters { receive: false, send: false, from_date: Some(1677166110), to_date: Some(1683627417), }; - let tx_history = storage - .get_tx_history(vec![chain], true, 1, None, Some(filters)) + let transfer_history = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters)) .await .unwrap(); - assert_eq!(tx_history.transfer_history.len(), 3); - let tx = tx_history.transfer_history.get(0).unwrap(); - assert_eq!(tx.block_number, 28056726); + assert_eq!(transfer_history.transfer_history.len(), 4); + let transfer = transfer_history.transfer_history.get(0).unwrap(); + assert_eq!(transfer.block_number, 28056726); - let tx_history1 = storage - .get_tx_history(vec![chain], true, 1, None, Some(filters1)) + let transfer_history1 = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters1)) .await .unwrap(); - assert_eq!(tx_history1.transfer_history.len(), 1); - let tx1 = tx_history1.transfer_history.get(0).unwrap(); - assert_eq!(tx1.block_number, 25919780); + assert_eq!(transfer_history1.transfer_history.len(), 1); + let transfer1 = transfer_history1.transfer_history.get(0).unwrap(); + assert_eq!(transfer1.block_number, 25919780); - let tx_history2 = storage - .get_tx_history(vec![chain], true, 1, None, Some(filters2)) + let transfer_history2 = storage + .get_transfer_history(vec![chain], true, 1, None, Some(filters2)) .await .unwrap(); - assert_eq!(tx_history2.transfer_history.len(), 2); - let tx_0 = tx_history2.transfer_history.get(0).unwrap(); - assert_eq!(tx_0.block_number, 28056721); - let tx_1 = tx_history2.transfer_history.get(1).unwrap(); - assert_eq!(tx_1.block_number, 25919780); + assert_eq!(transfer_history2.transfer_history.len(), 2); + let transfer_0 = transfer_history2.transfer_history.get(0).unwrap(); + assert_eq!(transfer_0.block_number, 28056721); + let transfer_1 = transfer_history2.transfer_history.get(1).unwrap(); + assert_eq!(transfer_1.block_number, 25919780); } -pub(crate) async fn test_get_update_tx_meta_impl() { +pub(crate) async fn test_get_update_transfer_meta_impl() { let chain = Chain::Bsc; let storage = init_nft_history_storage(&chain).await; - let txs = nft_tx_history(); - storage.add_txs_to_history(&chain, txs).await.unwrap(); + let transfers = nft_transfer_history(); + storage.add_transfers_to_history(&chain, transfers).await.unwrap(); - let vec_token_add_id = storage.get_txs_with_empty_meta(&chain).await.unwrap(); - assert_eq!(vec_token_add_id.len(), 2); + let vec_token_add_id = storage.get_transfers_with_empty_meta(&chain).await.unwrap(); + assert_eq!(vec_token_add_id.len(), 3); let token_add = "0x5c7d6712dfaf0cb079d48981781c8705e8417ca0".to_string(); - let tx_meta = TxMeta { + let transfer_meta = TransferMeta { token_address: token_add.clone(), token_id: Default::default(), token_uri: None, @@ -562,20 +634,26 @@ pub(crate) async fn test_get_update_tx_meta_impl() { image_url: None, token_name: Some("Tiki box".to_string()), }; - storage.update_txs_meta_by_token_addr_id(&chain, tx_meta).await.unwrap(); - let tx_upd = storage - .get_txs_by_token_addr_id(&chain, token_add, Default::default()) + storage + .update_transfers_meta_by_token_addr_id(&chain, transfer_meta) + .await + .unwrap(); + let transfer_upd = storage + .get_transfers_by_token_addr_id(&chain, token_add, Default::default()) .await .unwrap(); - let tx_upd = tx_upd.get(0).unwrap(); - assert_eq!(tx_upd.token_name, Some("Tiki box".to_string())); + let transfer_upd = transfer_upd.get(0).unwrap(); + assert_eq!(transfer_upd.token_name, Some("Tiki box".to_string())); - let tx_meta = tx(); - storage.update_tx_meta_by_hash(&chain, tx_meta).await.unwrap(); - let tx_by_hash = storage - .get_tx_by_tx_hash(&chain, TX_HASH.to_string()) + let transfer_meta = transfer(); + storage + .update_transfer_meta_by_hash_and_log_index(&chain, transfer_meta) + .await + .unwrap(); + let transfer_by_hash = storage + .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) .await .unwrap() .unwrap(); - assert_eq!(tx_by_hash.token_name, Some("Nebula Nodes".to_string())) + assert_eq!(transfer_by_hash.token_name, Some("Nebula Nodes".to_string())) } diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs index d5f4319a2a..0a2e906ccc 100644 --- a/mm2src/coins/nft/storage/mod.rs +++ b/mm2src/coins/nft/storage/mod.rs @@ -1,5 +1,5 @@ -use crate::nft::nft_structs::{Chain, Nft, NftList, NftTokenAddrId, NftTransferHistory, NftTxHistoryFilters, - NftsTransferHistoryList, TxMeta}; +use crate::nft::nft_structs::{Chain, Nft, NftList, NftTokenAddrId, NftTransferHistory, NftTransferHistoryFilters, + NftsTransferHistoryList, TransferMeta}; use crate::WithdrawError; use async_trait::async_trait; use derive_more::Display; @@ -88,7 +88,7 @@ pub trait NftListStorageOps { } #[async_trait] -pub trait NftTxHistoryStorageOps { +pub trait NftTransferHistoryStorageOps { type Error: NftStorageError; /// Initializes tables in storage for the specified chain type. @@ -97,48 +97,57 @@ pub trait NftTxHistoryStorageOps { /// Whether tables are initialized for the specified chain. async fn is_initialized(&self, chain: &Chain) -> MmResult; - async fn get_tx_history( + async fn get_transfer_history( &self, chains: Vec, max: bool, limit: usize, page_number: Option, - filters: Option, + filters: Option, ) -> MmResult; - async fn add_txs_to_history(&self, chain: &Chain, txs: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, chain: &Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send; async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error>; - /// `get_txs_from_block` function returns transfers sorted by + /// `get_transfers_from_block` function returns transfers sorted by /// block_number in ascending order. It is needed to update the NFT LIST table correctly. - async fn get_txs_from_block( + async fn get_transfers_from_block( &self, chain: &Chain, from_block: u64, ) -> MmResult, Self::Error>; - async fn get_txs_by_token_addr_id( + async fn get_transfers_by_token_addr_id( &self, chain: &Chain, token_address: String, token_id: BigDecimal, ) -> MmResult, Self::Error>; - async fn get_tx_by_tx_hash( + async fn get_transfer_by_tx_hash_and_log_index( &self, chain: &Chain, transaction_hash: String, + log_index: u32, ) -> MmResult, Self::Error>; - async fn update_tx_meta_by_hash(&self, chain: &Chain, tx: NftTransferHistory) -> MmResult<(), Self::Error>; + async fn update_transfer_meta_by_hash_and_log_index( + &self, + chain: &Chain, + transfer: NftTransferHistory, + ) -> MmResult<(), Self::Error>; - async fn update_txs_meta_by_token_addr_id(&self, chain: &Chain, tx_meta: TxMeta) -> MmResult<(), Self::Error>; + async fn update_transfers_meta_by_token_addr_id( + &self, + chain: &Chain, + transfer_meta: TransferMeta, + ) -> MmResult<(), Self::Error>; - async fn get_txs_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error>; + async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error>; } #[derive(Debug, Deserialize, Display, Serialize)] @@ -155,7 +164,7 @@ impl From for WithdrawError { } /// `NftStorageBuilder` is used to create an instance that implements the [`NftListStorageOps`] -/// and [`NftTxHistoryStorageOps`] traits.Also has guard to lock write operations. +/// and [`NftTransferHistoryStorageOps`] traits.Also has guard to lock write operations. pub struct NftStorageBuilder<'a> { ctx: &'a MmArc, } @@ -164,9 +173,9 @@ impl<'a> NftStorageBuilder<'a> { #[inline] pub fn new(ctx: &MmArc) -> NftStorageBuilder<'_> { NftStorageBuilder { ctx } } - /// `build` function is used to build nft storage which implements [`NftListStorageOps`] and [`NftTxHistoryStorageOps`] traits. + /// `build` function is used to build nft storage which implements [`NftListStorageOps`] and [`NftTransferHistoryStorageOps`] traits. #[inline] - pub fn build(&self) -> MmResult { + pub fn build(&self) -> MmResult { #[cfg(target_arch = "wasm32")] return wasm::wasm_storage::IndexedDbNftStorage::new(self.ctx); #[cfg(not(target_arch = "wasm32"))] diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index e37780111d..9179704467 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -1,8 +1,8 @@ use crate::nft::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ConvertChain, Nft, NftList, NftTokenAddrId, NftTransferHistory, - NftTxHistoryFilters, NftsTransferHistoryList, TxMeta}; + NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta}; use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftStorageError, - NftTxHistoryStorageOps, RemoveNftResult}; + NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; use common::async_blocking; use db_common::sql_build::{SqlCondition, SqlQuery}; @@ -22,7 +22,7 @@ use std::sync::{Arc, Mutex}; fn nft_list_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_list" } -fn nft_tx_history_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_tx_history" } +fn nft_transfer_history_table_name(chain: &Chain) -> String { chain.to_ticker() + "_nft_transfer_history" } fn scanned_nft_blocks_table_name() -> String { "scanned_nft_blocks".to_string() } @@ -45,12 +45,13 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn create_tx_history_table_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn create_transfer_history_table_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( - transaction_hash VARCHAR(256) PRIMARY KEY, + transaction_hash VARCHAR(256) NOT NULL, + log_index INTEGER NOT NULL, chain TEXT NOT NULL, block_number INTEGER NOT NULL, block_timestamp INTEGER NOT NULL, @@ -63,7 +64,8 @@ fn create_tx_history_table_sql(chain: &Chain) -> MmResult { collection_name TEXT, image_url TEXT, token_name TEXT, - details_json TEXT + details_json TEXT, + PRIMARY KEY (transaction_hash, log_index) );", table_name ); @@ -121,14 +123,14 @@ fn get_nft_list_builder_preimage(chains: Vec) -> MmResult, - filters: Option, + filters: Option, ) -> MmResult { let union_sql_strings = chains .into_iter() .map(|chain| { - let table_name = nft_tx_history_table_name(&chain); + let table_name = nft_transfer_history_table_name(&chain); validate_table_name(&table_name)?; let sql_builder = nft_history_table_builder_preimage(table_name.as_str(), filters)?; let sql_string = sql_builder @@ -148,7 +150,7 @@ fn get_nft_tx_builder_preimage( fn nft_history_table_builder_preimage( table_name: &str, - filters: Option, + filters: Option, ) -> Result { let mut sql_builder = SqlBuilder::select_from(table_name); if let Some(filters) = filters { @@ -201,7 +203,7 @@ fn nft_from_row(row: &Row<'_>) -> Result { json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) } -fn tx_history_from_row(row: &Row<'_>) -> Result { +fn transfer_history_from_row(row: &Row<'_>) -> Result { let json_string: String = row.get(0)?; json::from_str(&json_string).map_err(|e| SqlError::FromSqlConversionFailure(0, Type::Text, Box::new(e))) } @@ -231,16 +233,16 @@ fn insert_nft_in_list_sql(chain: &Chain) -> MmResult { Ok(sql) } -fn insert_tx_in_history_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn insert_transfer_in_history_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let sql = format!( "INSERT INTO {} ( - transaction_hash, chain, block_number, block_timestamp, contract_type, + transaction_hash, log_index, chain, block_number, block_timestamp, contract_type, token_address, token_id, status, amount, collection_name, image_url, token_name, details_json ) VALUES ( - ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13 + ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14 );", table_name ); @@ -271,12 +273,12 @@ where Ok(sql) } -fn update_meta_by_tx_hash_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn update_meta_by_tx_hash_and_log_index_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let sql = format!( - "UPDATE {} SET token_uri = ?1, collection_name = ?2, image_url = ?3, token_name = ?4, details_json = ?5 WHERE transaction_hash = ?6;", + "UPDATE {} SET token_uri = ?1, collection_name = ?2, image_url = ?3, token_name = ?4, details_json = ?5 WHERE transaction_hash = ?6 AND log_index = ?7;", table_name ); Ok(sql) @@ -367,12 +369,12 @@ fn block_number_from_row(row: &Row<'_>) -> Result { row.get::<_, fn nft_amount_from_row(row: &Row<'_>) -> Result { row.get(0) } -fn get_txs_from_block_builder<'a>( +fn get_transfers_from_block_builder<'a>( conn: &'a Connection, chain: &'a Chain, from_block: u64, ) -> MmResult, SqlError> { - let table_name = nft_tx_history_table_name(chain); + let table_name = nft_transfer_history_table_name(chain); validate_table_name(table_name.as_str())?; let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; sql_builder @@ -384,8 +386,11 @@ fn get_txs_from_block_builder<'a>( Ok(sql_builder) } -fn get_txs_by_token_addr_id_statement<'a>(conn: &'a Connection, chain: &'a Chain) -> MmResult, SqlError> { - let table_name = nft_tx_history_table_name(chain); +fn get_transfers_by_token_addr_id_statement<'a>( + conn: &'a Connection, + chain: &'a Chain, +) -> MmResult, SqlError> { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(table_name.as_str())?; let sql_query = format!( "SELECT details_json FROM {} WHERE token_address = ? AND token_id = ?", @@ -395,8 +400,11 @@ fn get_txs_by_token_addr_id_statement<'a>(conn: &'a Connection, chain: &'a Chain Ok(stmt) } -fn get_txs_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Chain) -> MmResult, SqlError> { - let table_name = nft_tx_history_table_name(chain); +fn get_transfers_with_empty_meta_builder<'a>( + conn: &'a Connection, + chain: &'a Chain, +) -> MmResult, SqlError> { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(table_name.as_str())?; let mut sql_builder = SqlQuery::select_from(conn, table_name.as_str())?; sql_builder @@ -412,10 +420,13 @@ fn get_txs_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Chain) - Ok(sql_builder) } -fn get_tx_by_tx_hash_sql(chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); +fn get_transfer_by_tx_hash_and_log_index_sql(chain: &Chain) -> MmResult { + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; - let sql = format!("SELECT details_json FROM {} WHERE transaction_hash=?1", table_name); + let sql = format!( + "SELECT details_json FROM {} WHERE transaction_hash=?1 AND log_index = ?2", + table_name + ); Ok(sql) } @@ -675,22 +686,22 @@ impl NftListStorageOps for SqliteNftStorage { } #[async_trait] -impl NftTxHistoryStorageOps for SqliteNftStorage { +impl NftTransferHistoryStorageOps for SqliteNftStorage { type Error = SqlError; async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { let selfi = self.clone(); - let sql_tx_history = create_tx_history_table_sql(chain)?; + let sql_transfer_history = create_transfer_history_table_sql(chain)?; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - conn.execute(&sql_tx_history, []).map(|_| ())?; + conn.execute(&sql_transfer_history, []).map(|_| ())?; Ok(()) }) .await } async fn is_initialized(&self, chain: &Chain) -> MmResult { - let table_name = nft_tx_history_table_name(chain); + let table_name = nft_transfer_history_table_name(chain); validate_table_name(&table_name)?; let selfi = self.clone(); async_blocking(move || { @@ -701,18 +712,18 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .await } - async fn get_tx_history( + async fn get_transfer_history( &self, chains: Vec, max: bool, limit: usize, page_number: Option, - filters: Option, + filters: Option, ) -> MmResult { let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_nft_tx_builder_preimage(chains, filters)?; + let sql_builder = get_nft_transfer_builder_preimage(chains, filters)?; let total_count_builder_sql = sql_builder .clone() .count("*") @@ -725,12 +736,12 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let (offset, limit) = get_offset_limit(max, limit, page_number, count_total); let sql = finalize_nft_history_sql_builder(sql_builder, offset, limit)?; - let txs = conn + let transfers = conn .prepare(&sql)? - .query_map([], tx_history_from_row)? + .query_map([], transfer_history_from_row)? .collect::, _>>()?; let result = NftsTransferHistoryList { - transfer_history: txs, + transfer_history: transfers, skipped: offset, total: count_total, }; @@ -739,7 +750,7 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .await } - async fn add_txs_to_history(&self, chain: &Chain, txs: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, chain: &Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, @@ -750,24 +761,25 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let mut conn = selfi.0.lock().unwrap(); let sql_transaction = conn.transaction()?; - for tx in txs { - let tx_json = json::to_string(&tx).expect("serialization should not fail"); + for transfer in transfers { + let transfer_json = json::to_string(&transfer).expect("serialization should not fail"); let params = [ - Some(tx.common.transaction_hash), - Some(tx.chain.to_string()), - Some(tx.block_number.to_string()), - Some(tx.block_timestamp.to_string()), - Some(tx.contract_type.to_string()), - Some(eth_addr_to_hex(&tx.common.token_address)), - Some(tx.common.token_id.to_string()), - Some(tx.status.to_string()), - Some(tx.common.amount.to_string()), - tx.collection_name, - tx.image_url, - tx.token_name, - Some(tx_json), + Some(transfer.common.transaction_hash), + Some(transfer.common.log_index.to_string()), + Some(transfer.chain.to_string()), + Some(transfer.block_number.to_string()), + Some(transfer.block_timestamp.to_string()), + Some(transfer.contract_type.to_string()), + Some(eth_addr_to_hex(&transfer.common.token_address)), + Some(transfer.common.token_id.to_string()), + Some(transfer.status.to_string()), + Some(transfer.common.amount.to_string()), + transfer.collection_name, + transfer.image_url, + transfer.token_name, + Some(transfer_json), ]; - sql_transaction.execute(&insert_tx_in_history_sql(&chain)?, params)?; + sql_transaction.execute(&insert_transfer_in_history_sql(&chain)?, params)?; } sql_transaction.commit()?; Ok(()) @@ -776,7 +788,7 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { } async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { - let sql = select_last_block_number_sql(chain, nft_tx_history_table_name)?; + let sql = select_last_block_number_sql(chain, nft_transfer_history_table_name)?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); @@ -788,7 +800,7 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .map_to_mm(|e| SqlError::FromSqlConversionFailure(2, Type::Integer, Box::new(e))) } - async fn get_txs_from_block( + async fn get_transfers_from_block( &self, chain: &Chain, from_block: u64, @@ -797,14 +809,14 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_txs_from_block_builder(&conn, &chain, from_block)?; - let txs = sql_builder.query(tx_history_from_row)?; - Ok(txs) + let sql_builder = get_transfers_from_block_builder(&conn, &chain, from_block)?; + let transfers = sql_builder.query(transfer_history_from_row)?; + Ok(transfers) }) .await } - async fn get_txs_by_token_addr_id( + async fn get_transfers_by_token_addr_id( &self, chain: &Chain, token_address: String, @@ -814,39 +826,51 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let mut stmt = get_txs_by_token_addr_id_statement(&conn, &chain)?; - let txs = stmt - .query_map([token_address, token_id.to_string()], tx_history_from_row)? + let mut stmt = get_transfers_by_token_addr_id_statement(&conn, &chain)?; + let transfers = stmt + .query_map([token_address, token_id.to_string()], transfer_history_from_row)? .collect::, _>>()?; - Ok(txs) + Ok(transfers) }) .await } - async fn get_tx_by_tx_hash( + async fn get_transfer_by_tx_hash_and_log_index( &self, chain: &Chain, transaction_hash: String, + log_index: u32, ) -> MmResult, Self::Error> { - let sql = get_tx_by_tx_hash_sql(chain)?; + let sql = get_transfer_by_tx_hash_and_log_index_sql(chain)?; let selfi = self.clone(); async_blocking(move || { let conn = selfi.0.lock().unwrap(); - query_single_row(&conn, &sql, [transaction_hash], tx_history_from_row).map_to_mm(SqlError::from) + query_single_row( + &conn, + &sql, + [transaction_hash, log_index.to_string()], + transfer_history_from_row, + ) + .map_to_mm(SqlError::from) }) .await } - async fn update_tx_meta_by_hash(&self, chain: &Chain, tx: NftTransferHistory) -> MmResult<(), Self::Error> { - let sql = update_meta_by_tx_hash_sql(chain)?; - let tx_json = json::to_string(&tx).expect("serialization should not fail"); + async fn update_transfer_meta_by_hash_and_log_index( + &self, + chain: &Chain, + transfer: NftTransferHistory, + ) -> MmResult<(), Self::Error> { + let sql = update_meta_by_tx_hash_and_log_index_sql(chain)?; + let transfer_json = json::to_string(&transfer).expect("serialization should not fail"); let params = [ - tx.token_uri, - tx.collection_name, - tx.image_url, - tx.token_name, - Some(tx_json), - Some(tx.common.transaction_hash), + transfer.token_uri, + transfer.collection_name, + transfer.image_url, + transfer.token_name, + Some(transfer_json), + Some(transfer.common.transaction_hash), + Some(transfer.common.log_index.to_string()), ]; let selfi = self.clone(); async_blocking(move || { @@ -859,28 +883,34 @@ impl NftTxHistoryStorageOps for SqliteNftStorage { .await } - async fn update_txs_meta_by_token_addr_id(&self, chain: &Chain, tx_meta: TxMeta) -> MmResult<(), Self::Error> { + async fn update_transfers_meta_by_token_addr_id( + &self, + chain: &Chain, + transfer_meta: TransferMeta, + ) -> MmResult<(), Self::Error> { let selfi = self.clone(); - let txs = selfi - .get_txs_by_token_addr_id(chain, tx_meta.token_address, tx_meta.token_id) + let transfers = selfi + .get_transfers_by_token_addr_id(chain, transfer_meta.token_address, transfer_meta.token_id) .await?; - for mut tx in txs.into_iter() { - tx.token_uri = tx_meta.token_uri.clone(); - tx.collection_name = tx_meta.collection_name.clone(); - tx.image_url = tx_meta.image_url.clone(); - tx.token_name = tx_meta.token_name.clone(); - drop_mutability!(tx); - selfi.update_tx_meta_by_hash(chain, tx).await?; + for mut transfer in transfers.into_iter() { + transfer.token_uri = transfer_meta.token_uri.clone(); + transfer.collection_name = transfer_meta.collection_name.clone(); + transfer.image_url = transfer_meta.image_url.clone(); + transfer.token_name = transfer_meta.token_name.clone(); + drop_mutability!(transfer); + selfi + .update_transfer_meta_by_hash_and_log_index(chain, transfer) + .await?; } Ok(()) } - async fn get_txs_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { + async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { let selfi = self.clone(); let chain = *chain; async_blocking(move || { let conn = selfi.0.lock().unwrap(); - let sql_builder = get_txs_with_empty_meta_builder(&conn, &chain)?; + let sql_builder = get_transfers_with_empty_meta_builder(&conn, &chain)?; let token_addr_id_pair = sql_builder.query(token_address_id_from_row)?; Ok(token_addr_id_pair) }) diff --git a/mm2src/coins/nft/storage/wasm/nft_idb.rs b/mm2src/coins/nft/storage/wasm/nft_idb.rs index 287bcab30a..0d7758d61a 100644 --- a/mm2src/coins/nft/storage/wasm/nft_idb.rs +++ b/mm2src/coins/nft/storage/wasm/nft_idb.rs @@ -1,4 +1,4 @@ -use crate::nft::storage::wasm::wasm_storage::{LastScannedBlockTable, NftListTable, NftTxHistoryTable}; +use crate::nft::storage::wasm::wasm_storage::{LastScannedBlockTable, NftListTable, NftTransferHistoryTable}; use async_trait::async_trait; use mm2_db::indexed_db::InitDbResult; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder}; @@ -19,7 +19,7 @@ impl DbInstance for NftCacheIDB { let inner = IndexedDbBuilder::new(db_id) .with_version(DB_VERSION) .with_table::() - .with_table::() + .with_table::() .with_table::() .build() .await?; diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index 6fb341c26e..058b6cdffd 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -1,10 +1,10 @@ use crate::eth::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCtx, NftList, NftTransferHistory, NftsTransferHistoryList, - TransferStatus, TxMeta}; + TransferMeta, TransferStatus}; use crate::nft::storage::wasm::nft_idb::{NftCacheIDB, NftCacheIDBLocked}; use crate::nft::storage::wasm::{WasmNftCacheError, WasmNftCacheResult}; use crate::nft::storage::{get_offset_limit, CreateNftStorageError, NftListStorageOps, NftTokenAddrId, - NftTxHistoryFilters, NftTxHistoryStorageOps, RemoveNftResult}; + NftTransferHistoryFilters, NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; use common::is_initial_upgrade; use mm2_core::mm_ctx::MmArc; @@ -52,54 +52,54 @@ impl IndexedDbNftStorage { }) } - fn take_txs_according_to_paging_opts( - mut txs: Vec, + fn take_transfers_according_to_paging_opts( + mut transfers: Vec, max: bool, limit: usize, page_number: Option, ) -> WasmNftCacheResult { - let total_count = txs.len(); - txs.sort_by(|a, b| b.block_timestamp.cmp(&a.block_timestamp)); + let total_count = transfers.len(); + transfers.sort_by(|a, b| b.block_timestamp.cmp(&a.block_timestamp)); let (offset, limit) = get_offset_limit(max, limit, page_number, total_count); Ok(NftsTransferHistoryList { - transfer_history: txs.into_iter().skip(offset).take(limit).collect(), + transfer_history: transfers.into_iter().skip(offset).take(limit).collect(), skipped: offset, total: total_count, }) } - fn take_txs_according_to_filters( - txs: I, - filters: Option, + fn take_transfers_according_to_filters( + transfers: I, + filters: Option, ) -> WasmNftCacheResult> where - I: Iterator, + I: Iterator, { - let mut filtered_txs = Vec::new(); - for tx_table in txs { - let tx = tx_details_from_item(tx_table)?; + let mut filtered_transfers = Vec::new(); + for transfers_table in transfers { + let transfer = transfer_details_from_item(transfers_table)?; if let Some(filters) = &filters { - if filters.is_status_match(&tx) && filters.is_date_match(&tx) { - filtered_txs.push(tx); + if filters.is_status_match(&transfer) && filters.is_date_match(&transfer) { + filtered_transfers.push(transfer); } } else { - filtered_txs.push(tx); + filtered_transfers.push(transfer); } } - Ok(filtered_txs) + Ok(filtered_transfers) } } -impl NftTxHistoryFilters { - fn is_status_match(&self, tx: &NftTransferHistory) -> bool { +impl NftTransferHistoryFilters { + fn is_status_match(&self, transfer: &NftTransferHistory) -> bool { (!self.receive && !self.send) - || (self.receive && tx.status == TransferStatus::Receive) - || (self.send && tx.status == TransferStatus::Send) + || (self.receive && transfer.status == TransferStatus::Receive) + || (self.send && transfer.status == TransferStatus::Send) } - fn is_date_match(&self, tx: &NftTransferHistory) -> bool { - self.from_date.map_or(true, |from| tx.block_timestamp >= from) - && self.to_date.map_or(true, |to| tx.block_timestamp <= to) + fn is_date_match(&self, transfer: &NftTransferHistory) -> bool { + self.from_date.map_or(true, |from| transfer.block_timestamp >= from) + && self.to_date.map_or(true, |to| transfer.block_timestamp <= to) } } @@ -318,48 +318,48 @@ impl NftListStorageOps for IndexedDbNftStorage { } #[async_trait] -impl NftTxHistoryStorageOps for IndexedDbNftStorage { +impl NftTransferHistoryStorageOps for IndexedDbNftStorage { type Error = WasmNftCacheError; async fn init(&self, _chain: &Chain) -> MmResult<(), Self::Error> { Ok(()) } async fn is_initialized(&self, _chain: &Chain) -> MmResult { Ok(true) } - async fn get_tx_history( + async fn get_transfer_history( &self, chains: Vec, max: bool, limit: usize, page_number: Option, - filters: Option, + filters: Option, ) -> MmResult { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - let mut txs = Vec::new(); + let table = db_transaction.table::().await?; + let mut transfers = Vec::new(); for chain in chains { - let tx_tables = table + let transfer_tables = table .get_items("chain", chain.to_string()) .await? .into_iter() - .map(|(_item_id, tx)| tx); - let filtered = Self::take_txs_according_to_filters(tx_tables, filters)?; - txs.extend(filtered); + .map(|(_item_id, transfer)| transfer); + let filtered = Self::take_transfers_according_to_filters(transfer_tables, filters)?; + transfers.extend(filtered); } - Self::take_txs_according_to_paging_opts(txs, max, limit, page_number) + Self::take_transfers_according_to_paging_opts(transfers, max, limit, page_number) } - async fn add_txs_to_history(&self, _chain: &Chain, txs: I) -> MmResult<(), Self::Error> + async fn add_transfers_to_history(&self, _chain: &Chain, transfers: I) -> MmResult<(), Self::Error> where I: IntoIterator + Send + 'static, I::IntoIter: Send, { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - for tx in txs { - let tx_item = NftTxHistoryTable::from_tx_history(&tx)?; - table.add_item(&tx_item).await?; + let table = db_transaction.table::().await?; + for transfer in transfers { + let transfer_item = NftTransferHistoryTable::from_transfer_history(&transfer)?; + table.add_item(&transfer_item).await?; } Ok(()) } @@ -367,24 +367,24 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - get_last_block_from_table(chain, table, NftTxHistoryTable::CHAIN_BLOCK_NUMBER_INDEX).await + let table = db_transaction.table::().await?; + get_last_block_from_table(chain, table, NftTransferHistoryTable::CHAIN_BLOCK_NUMBER_INDEX).await } - async fn get_txs_from_block( + async fn get_transfers_from_block( &self, chain: &Chain, from_block: u64, ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; let items = table .cursor_builder() .only("chain", chain.to_string()) .map_err(|e| WasmNftCacheError::GetLastNftBlockError(e.to_string()))? .bound("block_number", BeBigUint::from(from_block), BeBigUint::from(u64::MAX)) - .open_cursor(NftTxHistoryTable::CHAIN_BLOCK_NUMBER_INDEX) + .open_cursor(NftTransferHistoryTable::CHAIN_BLOCK_NUMBER_INDEX) .await .map_err(|e| WasmNftCacheError::GetLastNftBlockError(e.to_string()))? .collect() @@ -393,13 +393,13 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { let mut res = Vec::new(); for (_item_id, item) in items.into_iter() { - let tx = tx_details_from_item(item)?; - res.push(tx); + let transfer = transfer_details_from_item(item)?; + res.push(transfer); } Ok(res) } - async fn get_txs_by_token_addr_id( + async fn get_transfers_by_token_addr_id( &self, chain: &Chain, token_address: String, @@ -407,9 +407,9 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TOKEN_ADD_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&token_address)? .with_value(token_id.to_string())?; @@ -418,71 +418,83 @@ impl NftTxHistoryStorageOps for IndexedDbNftStorage { .get_items_by_multi_index(index_keys) .await? .into_iter() - .map(|(_item_id, item)| tx_details_from_item(item)) + .map(|(_item_id, item)| transfer_details_from_item(item)) .collect() } - async fn get_tx_by_tx_hash( + async fn get_transfer_by_tx_hash_and_log_index( &self, chain: &Chain, transaction_hash: String, + log_index: u32, ) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TX_HASH_INDEX) + let table = db_transaction.table::().await?; + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? - .with_value(&transaction_hash)?; + .with_value(&transaction_hash)? + .with_value(log_index)?; if let Some((_item_id, item)) = table.get_item_by_unique_multi_index(index_keys).await? { - Ok(Some(tx_details_from_item(item)?)) + Ok(Some(transfer_details_from_item(item)?)) } else { Ok(None) } } - async fn update_tx_meta_by_hash(&self, chain: &Chain, tx: NftTransferHistory) -> MmResult<(), Self::Error> { + async fn update_transfer_meta_by_hash_and_log_index( + &self, + chain: &Chain, + transfer: NftTransferHistory, + ) -> MmResult<(), Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TX_HASH_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? - .with_value(&tx.common.transaction_hash)?; + .with_value(&transfer.common.transaction_hash)? + .with_value(transfer.common.log_index)?; - let item = NftTxHistoryTable::from_tx_history(&tx)?; + let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; Ok(()) } - async fn update_txs_meta_by_token_addr_id(&self, chain: &Chain, tx_meta: TxMeta) -> MmResult<(), Self::Error> { - let txs: Vec = self - .get_txs_by_token_addr_id(chain, tx_meta.token_address, tx_meta.token_id) + async fn update_transfers_meta_by_token_addr_id( + &self, + chain: &Chain, + transfer_meta: TransferMeta, + ) -> MmResult<(), Self::Error> { + let transfers: Vec = self + .get_transfers_by_token_addr_id(chain, transfer_meta.token_address, transfer_meta.token_id) .await?; let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; - for mut tx in txs { - tx.token_uri = tx_meta.token_uri.clone(); - tx.collection_name = tx_meta.collection_name.clone(); - tx.image_url = tx_meta.image_url.clone(); - tx.token_name = tx_meta.token_name.clone(); - drop_mutability!(tx); - - let index_keys = MultiIndex::new(NftTxHistoryTable::CHAIN_TX_HASH_INDEX) + let table = db_transaction.table::().await?; + for mut transfer in transfers { + transfer.token_uri = transfer_meta.token_uri.clone(); + transfer.collection_name = transfer_meta.collection_name.clone(); + transfer.image_url = transfer_meta.image_url.clone(); + transfer.token_name = transfer_meta.token_name.clone(); + drop_mutability!(transfer); + + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) .with_value(chain.to_string())? - .with_value(&tx.common.transaction_hash)?; + .with_value(&transfer.common.transaction_hash)? + .with_value(transfer.common.log_index)?; - let item = NftTxHistoryTable::from_tx_history(&tx)?; + let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; } Ok(()) } - async fn get_txs_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { + async fn get_transfers_with_empty_meta(&self, chain: &Chain) -> MmResult, Self::Error> { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; - let table = db_transaction.table::().await?; + let table = db_transaction.table::().await?; let items = table .cursor_builder() .only("chain", chain.to_string()) @@ -553,7 +565,7 @@ impl BlockNumberTable for NftListTable { fn get_block_number(&self) -> &BeBigUint { &self.block_number } } -impl BlockNumberTable for NftTxHistoryTable { +impl BlockNumberTable for NftTransferHistoryTable { fn get_block_number(&self) -> &BeBigUint { &self.block_number } } @@ -607,8 +619,9 @@ impl TableSignature for NftListTable { } #[derive(Clone, Debug, Deserialize, Serialize)] -pub(crate) struct NftTxHistoryTable { +pub(crate) struct NftTransferHistoryTable { transaction_hash: String, + log_index: u32, chain: String, block_number: BeBigUint, block_timestamp: BeBigUint, @@ -624,36 +637,38 @@ pub(crate) struct NftTxHistoryTable { details_json: Json, } -impl NftTxHistoryTable { +impl NftTransferHistoryTable { const CHAIN_TOKEN_ADD_TOKEN_ID_INDEX: &str = "chain_token_add_token_id_index"; - const CHAIN_TX_HASH_INDEX: &str = "chain_tx_hash_index"; + const CHAIN_TX_HASH_LOG_INDEX_INDEX: &str = "chain_tx_hash_log_index_index"; const CHAIN_BLOCK_NUMBER_INDEX: &str = "chain_block_number_index"; - fn from_tx_history(tx: &NftTransferHistory) -> WasmNftCacheResult { - let details_json = json::to_value(tx).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; - Ok(NftTxHistoryTable { - transaction_hash: tx.common.transaction_hash.clone(), - chain: tx.chain.to_string(), - block_number: BeBigUint::from(tx.block_number), - block_timestamp: BeBigUint::from(tx.block_timestamp), - contract_type: tx.contract_type, - token_address: eth_addr_to_hex(&tx.common.token_address), - token_id: tx.common.token_id.to_string(), - status: tx.status, - amount: tx.common.amount.to_string(), - token_uri: tx.token_uri.clone(), - collection_name: tx.collection_name.clone(), - image_url: tx.image_url.clone(), - token_name: tx.token_name.clone(), + fn from_transfer_history(transfer: &NftTransferHistory) -> WasmNftCacheResult { + let details_json = + json::to_value(transfer).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; + Ok(NftTransferHistoryTable { + transaction_hash: transfer.common.transaction_hash.clone(), + log_index: transfer.common.log_index, + chain: transfer.chain.to_string(), + block_number: BeBigUint::from(transfer.block_number), + block_timestamp: BeBigUint::from(transfer.block_timestamp), + contract_type: transfer.contract_type, + token_address: eth_addr_to_hex(&transfer.common.token_address), + token_id: transfer.common.token_id.to_string(), + status: transfer.status, + amount: transfer.common.amount.to_string(), + token_uri: transfer.token_uri.clone(), + collection_name: transfer.collection_name.clone(), + image_url: transfer.image_url.clone(), + token_name: transfer.token_name.clone(), details_json, }) } } -impl TableSignature for NftTxHistoryTable { - fn table_name() -> &'static str { "nft_tx_history_cache_table" } +impl TableSignature for NftTransferHistoryTable { + fn table_name() -> &'static str { "nft_transfer_history_cache_table" } fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { if is_initial_upgrade(old_version, new_version) { @@ -663,7 +678,11 @@ impl TableSignature for NftTxHistoryTable { &["chain", "token_address", "token_id"], false, )?; - table.create_multi_index(Self::CHAIN_TX_HASH_INDEX, &["chain", "transaction_hash"], true)?; + table.create_multi_index( + Self::CHAIN_TX_HASH_LOG_INDEX_INDEX, + &["chain", "transaction_hash", "log_index"], + true, + )?; table.create_multi_index(Self::CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; table.create_index("block_number", false)?; table.create_index("chain", false)?; @@ -694,6 +713,6 @@ fn nft_details_from_item(item: NftListTable) -> WasmNftCacheResult { json::from_value(item.details_json).map_to_mm(|e| WasmNftCacheError::ErrorDeserializing(e.to_string())) } -fn tx_details_from_item(item: NftTxHistoryTable) -> WasmNftCacheResult { +fn transfer_details_from_item(item: NftTransferHistoryTable) -> WasmNftCacheResult { json::from_value(item.details_json).map_to_mm(|e| WasmNftCacheError::ErrorDeserializing(e.to_string())) } diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index d4c14bd010..116c651ce1 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -22,12 +22,12 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; + TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, + TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, + ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -832,33 +832,23 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { let payment_tx: UtxoTx = - try_tx_fus!(deserialize(taker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); - let swap_contract_address = try_tx_fus!(taker_refunds_payment_args.swap_contract_address.try_to_address()); + try_tx_s!(deserialize(taker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); + let swap_contract_address = try_tx_s!(taker_refunds_payment_args.swap_contract_address.try_to_address()); - let selfi = self.clone(); - let fut = async move { - selfi - .refund_hash_time_locked_payment(swap_contract_address, payment_tx) - .await - }; - Box::new(fut.boxed().compat()) + self.refund_hash_time_locked_payment(swap_contract_address, payment_tx) + .await } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { let payment_tx: UtxoTx = - try_tx_fus!(deserialize(maker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); - let swap_contract_address = try_tx_fus!(maker_refunds_payment_args.swap_contract_address.try_to_address()); + try_tx_s!(deserialize(maker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); + let swap_contract_address = try_tx_s!(maker_refunds_payment_args.swap_contract_address.try_to_address()); - let selfi = self.clone(); - let fut = async move { - selfi - .refund_hash_time_locked_payment(swap_contract_address, payment_tx) - .await - }; - Box::new(fut.boxed().compat()) + self.refund_hash_time_locked_payment(swap_contract_address, payment_tx) + .await } #[inline] diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 99cdc71c3a..1c4964f3a0 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -8,12 +8,12 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, - WithdrawResult}; + TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -482,11 +482,17 @@ impl SwapOps for SolanaCoin { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_taker_refunds_payment( + &self, + _taker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_maker_refunds_payment( + &self, + _maker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { unimplemented!() } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index d5d5b3250e..06b43688d7 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -7,8 +7,8 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPayment PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, + TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, @@ -303,11 +303,19 @@ impl SwapOps for SplToken { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_taker_refunds_payment( + &self, + _taker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { todo!() } + async fn send_maker_refunds_payment( + &self, + _maker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { + todo!() + } fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index a5d9188cb1..33e48d1885 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -22,12 +22,13 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, - VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFrom, WithdrawFut, WithdrawRequest}; + TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, + TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFrom, + WithdrawFut, WithdrawRequest}; use async_std::prelude::FutureExt as AsyncStdFutureExt; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; @@ -2533,16 +2534,16 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - Box::new(futures01::future::err(TransactionErr::Plain( + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + Err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund IRIS HTLC".into(), - ))) + )) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - Box::new(futures01::future::err(TransactionErr::Plain( + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + Err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund IRIS HTLC".into(), - ))) + )) } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 6486fd1f9c..f985ccec3c 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -14,8 +14,8 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TransactionType, + TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, @@ -305,16 +305,16 @@ impl SwapOps for TendermintToken { .send_taker_spends_maker_payment(taker_spends_payment_args) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - Box::new(futures01::future::err(TransactionErr::Plain( + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + Err(TransactionErr::Plain( "Doesn't need transaction broadcast to be refunded".into(), - ))) + )) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - Box::new(futures01::future::err(TransactionErr::Plain( + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + Err(TransactionErr::Plain( "Doesn't need transaction broadcast to be refunded".into(), - ))) + )) } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs index e4851a626a..5b16fe04e3 100644 --- a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs +++ b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs @@ -10,6 +10,7 @@ use bitcrypto::sha256; use common::executor::Timer; use common::log; use common::state_machine::prelude::*; +use common::state_machine::StateMachineTrait; use cosmrs::tendermint::abci::Code as TxCode; use cosmrs::tendermint::abci::Event; use cosmrs::tx::Fee; @@ -98,7 +99,7 @@ impl CoinWithTxHistoryV2 for TendermintToken { } } -struct TendermintTxHistoryCtx { +struct TendermintTxHistoryStateMachine { coin: Coin, storage: Storage, balances: AllBalancesResult, @@ -106,6 +107,12 @@ struct TendermintTxHistoryCtx last_spent_page: u32, } +impl StateMachineTrait + for TendermintTxHistoryStateMachine +{ + type Result = (); +} + struct TendermintInit { phantom: std::marker::PhantomData<(Coin, Storage)>, } @@ -181,10 +188,12 @@ where Coin: CoinCapabilities, Storage: TxHistoryStorage, { - type Ctx = TendermintTxHistoryCtx; - type Result = (); + type StateMachine = TendermintTxHistoryStateMachine; - async fn on_changed(mut self: Box, _ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + mut self: Box, + _ctx: &mut TendermintTxHistoryStateMachine, + ) -> StateResult> { Timer::sleep(30.).await; // retry history fetching process from last saved block @@ -233,10 +242,12 @@ where Coin: CoinCapabilities, Storage: TxHistoryStorage, { - type Ctx = TendermintTxHistoryCtx; - type Result = (); + type StateMachine = TendermintTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut TendermintTxHistoryStateMachine, + ) -> StateResult> { loop { Timer::sleep(30.).await; @@ -268,10 +279,12 @@ where Coin: CoinCapabilities, Storage: TxHistoryStorage, { - type Ctx = TendermintTxHistoryCtx; - type Result = (); + type StateMachine = TendermintTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut TendermintTxHistoryStateMachine, + ) -> StateResult> { const TX_PAGE_SIZE: u8 = 50; const DEFAULT_TRANSFER_EVENT_COUNT: usize = 1; @@ -821,10 +834,12 @@ where Coin: CoinCapabilities, Storage: TxHistoryStorage, { - type Ctx = TendermintTxHistoryCtx; - type Result = (); + type StateMachine = TendermintTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut TendermintTxHistoryStateMachine, + ) -> StateResult> { const INITIAL_SEARCH_HEIGHT: u64 = 0; ctx.coin.set_history_sync_state(HistorySyncState::NotStarted); @@ -855,10 +870,9 @@ where Coin: CoinCapabilities, Storage: TxHistoryStorage, { - type Ctx = TendermintTxHistoryCtx; - type Result = (); + type StateMachine = TendermintTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> Self::Result { + async fn on_changed(self: Box, ctx: &mut TendermintTxHistoryStateMachine) -> () { log::info!( "Stopping tx history fetching for {}. Reason: {:?}", ctx.coin.ticker(), @@ -887,7 +901,7 @@ pub async fn tendermint_history_loop( }, }; - let ctx = TendermintTxHistoryCtx { + let mut state_machine = TendermintTxHistoryStateMachine { coin, storage, balances, @@ -895,6 +909,5 @@ pub async fn tendermint_history_loop( last_spent_page: 1, }; - let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(ctx); - state_machine.run(TendermintInit::new()).await; + state_machine.run(Box::new(TendermintInit::new())).await; } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 1fd9694fc7..c731b12b01 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -7,7 +7,7 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TxMarshalingErr, UnexpectedDerivationMethod, + TradePreimageResult, TradePreimageValue, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, @@ -114,11 +114,17 @@ impl SwapOps for TestCoin { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_taker_refunds_payment( + &self, + _taker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_maker_refunds_payment( + &self, + _maker_refunds_payment_args: RefundPaymentArgs<'_>, + ) -> TransactionResult { unimplemented!() } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 33d1e74617..36c37c6fb6 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -31,6 +31,7 @@ pub mod qtum; pub mod rpc_clients; pub mod slp; pub mod spv; +pub mod swap_proto_v2_scripts; pub mod utxo_block_header_storage; pub mod utxo_builder; pub mod utxo_common; @@ -973,6 +974,8 @@ pub trait UtxoCommonOps: utxo_tx_map: &'b mut HistoryUtxoTxMap, ) -> UtxoRpcResult<&'b mut HistoryUtxoTx>; + /// Generates a transaction spending P2SH vout (typically, with 0 index [`utxo_common::DEFAULT_SWAP_VOUT`]) of input.prev_transaction + /// Works only if single signature is required! async fn p2sh_spending_tx(&self, input: utxo_common::P2SHSpendingTxInput<'_>) -> Result; /// Loads verbose transactions from cache or requests it using RPC client. diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index b089af276d..8d0592797d 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -15,11 +15,11 @@ use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBal PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, - TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut}; + TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use derive_more::Display; @@ -864,13 +864,13 @@ impl SwapOps for BchCoin { } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args).await } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 9640e66184..cfe3f2f4c1 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -29,7 +29,7 @@ use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithD NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, StakingInfosFut, - SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, + SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, @@ -550,13 +550,13 @@ impl SwapOps for QtumCoin { } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args).await } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index f094f54aef..a660124278 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -20,7 +20,7 @@ use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, C RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, - TransactionEnum, TransactionErr, TransactionFut, TxFeeDetails, TxMarshalingErr, + TransactionEnum, TransactionErr, TransactionFut, TransactionResult, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, @@ -1285,40 +1285,32 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { let tx = taker_refunds_payment_args.payment_tx.to_owned(); - let maker_pub = try_tx_fus!(Public::from_slice(taker_refunds_payment_args.other_pubkey)); + let maker_pub = try_tx_s!(Public::from_slice(taker_refunds_payment_args.other_pubkey)); let secret_hash = taker_refunds_payment_args.secret_hash.to_owned(); let htlc_keypair = self.derive_htlc_key_pair(taker_refunds_payment_args.swap_unique_data); - let coin = self.clone(); let time_lock = taker_refunds_payment_args.time_lock; - let fut = async move { - let tx = try_s!( - coin.refund_htlc(&tx, &maker_pub, time_lock, &secret_hash, &htlc_keypair) - .await - ); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat().map_err(TransactionErr::Plain)) + let tx = try_tx_s!( + self.refund_htlc(&tx, &maker_pub, time_lock, &secret_hash, &htlc_keypair) + .await + ); + Ok(tx.into()) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { let tx = maker_refunds_payment_args.payment_tx.to_owned(); - let taker_pub = try_tx_fus!(Public::from_slice(maker_refunds_payment_args.other_pubkey)); + let taker_pub = try_tx_s!(Public::from_slice(maker_refunds_payment_args.other_pubkey)); let secret_hash = maker_refunds_payment_args.secret_hash.to_owned(); let htlc_keypair = self.derive_htlc_key_pair(maker_refunds_payment_args.swap_unique_data); - let coin = self.clone(); let time_lock = maker_refunds_payment_args.time_lock; - let fut = async move { - let tx = try_tx_s!( - coin.refund_htlc(&tx, &taker_pub, time_lock, &secret_hash, &htlc_keypair) - .await - ); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + let tx = try_tx_s!( + self.refund_htlc(&tx, &taker_pub, time_lock, &secret_hash, &htlc_keypair) + .await + ); + Ok(tx.into()) } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/utxo/swap_proto_v2_scripts.rs b/mm2src/coins/utxo/swap_proto_v2_scripts.rs new file mode 100644 index 0000000000..d204f7e26b --- /dev/null +++ b/mm2src/coins/utxo/swap_proto_v2_scripts.rs @@ -0,0 +1,50 @@ +/// This module contains functions building Bitcoins scripts for the "Swap protocol upgrade" feature +/// For more info, see https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895 +use bitcrypto::ripemd160; +use keys::Public; +use script::{Builder, Opcode, Script}; + +/// Builds a script for refundable dex_fee + premium taker transaction +pub fn dex_fee_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: &Public) -> Script { + let mut builder = Builder::default() + // Dex fee refund path, same lock time as for taker payment + .push_opcode(Opcode::OP_IF) + .push_bytes(&time_lock.to_le_bytes()) + .push_opcode(Opcode::OP_CHECKLOCKTIMEVERIFY) + .push_opcode(Opcode::OP_DROP) + .push_bytes(pub_0) + .push_opcode(Opcode::OP_CHECKSIG) + // Dex fee redeem path, Maker needs to reveal the secret to prevent case of getting + // the premium but not proceeding with spending the taker payment + .push_opcode(Opcode::OP_ELSE) + .push_opcode(Opcode::OP_SIZE) + .push_bytes(&[32]) + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_opcode(Opcode::OP_HASH160); + + if secret_hash.len() == 32 { + builder = builder.push_bytes(ripemd160(secret_hash).as_slice()); + } else { + builder = builder.push_bytes(secret_hash); + } + + builder + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_opcode(Opcode::OP_2) + .push_bytes(pub_0) + .push_bytes(pub_1) + .push_opcode(Opcode::OP_2) + .push_opcode(Opcode::OP_CHECKMULTISIG) + .push_opcode(Opcode::OP_ENDIF) + .into_script() +} + +#[cfg(test)] +mod swap_proto_v2_scripts_tests { + use super::*; + + #[test] + fn it_builds_the_dex_fee_script() { + let _script = dex_fee_script(1689069073, &[0; 20], &Public::default(), &Public::default()); + } +} diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 49af674baa..9262471acb 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,15 +15,18 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::watcher_common::validate_watcher_reward; -use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GetWithdrawSenderAddress, - HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, - RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, - TxFeeDetails, TxMarshalingErr, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, - WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, - INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenAndSignDexFeeSpendResult, + GenDexFeeSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, + RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, + SendDexFeeWithPremiumArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TransactionResult, + TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateDexFeeArgs, + ValidateDexFeeError, ValidateDexFeeResult, ValidateDexFeeSpendPreimageError, + ValidateDexFeeSpendPreimageResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, WithdrawSenderAddress, + EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, + INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; @@ -55,7 +58,7 @@ use std::cmp::Ordering; use std::collections::hash_map::{Entry, HashMap}; use std::str::FromStr; use std::sync::atomic::Ordering as AtomicOrdering; -use utxo_signer::with_key_pair::p2sh_spend; +use utxo_signer::with_key_pair::{calc_and_sign_sighash, p2sh_spend, signature_hash_to_sign}; use utxo_signer::UtxoSignerOps; pub use chain::Transaction as UtxoTx; @@ -1100,11 +1103,25 @@ pub struct P2SHSpendingTxInput<'a> { keypair: &'a KeyPair, } -pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxInput<'_>) -> Result { - if input.prev_transaction.outputs.is_empty() { - return ERR!("Transaction doesn't have any output"); +enum LocktimeSetting { + CalcByHtlcLocktime(u32), + UseExact(u32), +} + +async fn p2sh_spending_tx_preimage( + coin: &T, + prev_tx: &UtxoTx, + lock_time: LocktimeSetting, + sequence: u32, + outputs: Vec, +) -> Result { + if prev_tx.outputs.is_empty() { + return ERR!("Previous transaction doesn't have any output"); } - let lock_time = try_s!(coin.p2sh_tx_locktime(input.lock_time).await); + let lock_time = match lock_time { + LocktimeSetting::CalcByHtlcLocktime(lock) => try_s!(coin.p2sh_tx_locktime(lock).await), + LocktimeSetting::UseExact(lock) => lock, + }; let n_time = if coin.as_ref().conf.is_pos { Some(now_sec_u32()) } else { @@ -1116,21 +1133,21 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI None }; let hash_algo = coin.as_ref().tx_hash_algo.into(); - let unsigned = TransactionInputSigner { + Ok(TransactionInputSigner { lock_time, version: coin.as_ref().conf.tx_version, n_time, overwintered: coin.as_ref().conf.overwintered, inputs: vec![UnsignedTransactionInput { - sequence: input.sequence, + sequence, previous_output: OutPoint { - hash: input.prev_transaction.hash(), + hash: prev_tx.hash(), index: DEFAULT_SWAP_VOUT as u32, }, - amount: input.prev_transaction.outputs[0].value, + amount: prev_tx.outputs[0].value, witness: Vec::new(), }], - outputs: input.outputs, + outputs, expiry_height: 0, join_splits: vec![], shielded_spends: vec![], @@ -1142,7 +1159,20 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI posv: coin.as_ref().conf.is_posv, str_d_zeel, hash_algo, - }; + }) +} + +pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxInput<'_>) -> Result { + let unsigned = try_s!( + p2sh_spending_tx_preimage( + coin, + &input.prev_transaction, + LocktimeSetting::CalcByHtlcLocktime(input.lock_time), + input.sequence, + input.outputs + ) + .await + ); let signed_input = try_s!(p2sh_spend( &unsigned, DEFAULT_SWAP_VOUT, @@ -1175,6 +1205,234 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI }) } +pub type GenDexFeeSpendResult = MmResult; + +enum CalcPremiumBy { + DeductMinerFee, + UseExactAmount(u64), +} + +async fn gen_dex_fee_spend_preimage( + coin: &T, + args: &GenDexFeeSpendArgs<'_>, + lock_time: LocktimeSetting, + calc_premium: CalcPremiumBy, +) -> GenDexFeeSpendResult { + let mut prev_tx: UtxoTx = + deserialize(args.dex_fee_tx).map_to_mm(|e| TxGenError::TxDeserialization(e.to_string()))?; + prev_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; + drop_mutability!(prev_tx); + + let dex_fee_sat = sat_from_big_decimal(&args.dex_fee_amount, coin.as_ref().decimals)?; + let premium_sat = match calc_premium { + CalcPremiumBy::UseExactAmount(sat) => sat, + CalcPremiumBy::DeductMinerFee => { + let miner_fee = coin + .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await?; + + let premium_sat = sat_from_big_decimal(&args.premium_amount, coin.as_ref().decimals)?; + if miner_fee + coin.as_ref().dust_amount > premium_sat { + return MmError::err(TxGenError::MinerFeeExceedsPremium { + miner_fee: big_decimal_from_sat_unsigned(miner_fee, coin.as_ref().decimals), + premium: args.premium_amount.clone(), + }); + } + premium_sat - miner_fee + }, + }; + + let dex_fee_address = address_from_raw_pubkey( + args.dex_fee_pub, + coin.as_ref().conf.pub_addr_prefix, + coin.as_ref().conf.pub_t_addr_prefix, + coin.as_ref().conf.checksum_type, + coin.as_ref().conf.bech32_hrp.clone(), + coin.addr_format().clone(), + ) + .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive dex_fee_address: {}", e)))?; + let dex_fee_output = TransactionOutput { + value: dex_fee_sat, + script_pubkey: Builder::build_p2pkh(&dex_fee_address.hash).to_bytes(), + }; + + let premium_address = address_from_raw_pubkey( + args.maker_pub, + coin.as_ref().conf.pub_addr_prefix, + coin.as_ref().conf.pub_t_addr_prefix, + coin.as_ref().conf.checksum_type, + coin.as_ref().conf.bech32_hrp.clone(), + coin.addr_format().clone(), + ) + .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive premium_address: {}", e)))?; + let premium_output = TransactionOutput { + value: premium_sat, + script_pubkey: Builder::build_p2pkh(&premium_address.hash).to_bytes(), + }; + + p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, SEQUENCE_FINAL, vec![ + dex_fee_output, + premium_output, + ]) + .await + .map_to_mm(TxGenError::Legacy) +} + +pub async fn gen_and_sign_dex_fee_spend_preimage( + coin: &T, + args: &GenDexFeeSpendArgs<'_>, + htlc_keypair: &KeyPair, +) -> GenAndSignDexFeeSpendResult { + let maker_pub = Public::from_slice(args.maker_pub).map_to_mm(|e| TxGenError::InvalidPubkey(e.to_string()))?; + let taker_pub = Public::from_slice(args.taker_pub).map_to_mm(|e| TxGenError::InvalidPubkey(e.to_string()))?; + + let preimage = gen_dex_fee_spend_preimage( + coin, + args, + LocktimeSetting::CalcByHtlcLocktime(args.time_lock), + CalcPremiumBy::DeductMinerFee, + ) + .await?; + + let redeem_script = swap_proto_v2_scripts::dex_fee_script(args.time_lock, args.secret_hash, &taker_pub, &maker_pub); + let signature = calc_and_sign_sighash( + &preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + coin.as_ref().conf.fork_id, + )?; + let preimage_tx: UtxoTx = preimage.into(); + Ok(TxPreimageWithSig { + preimage: serialize(&preimage_tx).take(), + signature: signature.take(), + }) +} + +pub async fn validate_dex_fee_spend_preimage( + coin: &T, + gen_args: &GenDexFeeSpendArgs<'_>, + preimage: &TxPreimageWithSig, +) -> ValidateDexFeeSpendPreimageResult { + // TODO validate that preimage has exactly 2 outputs + let actual_preimage_tx: UtxoTx = deserialize(preimage.preimage.as_slice()) + .map_to_mm(|e| ValidateDexFeeSpendPreimageError::TxDeserialization(e.to_string()))?; + + let maker_pub = Public::from_slice(gen_args.maker_pub) + .map_to_mm(|e| ValidateDexFeeSpendPreimageError::InvalidPubkey(e.to_string()))?; + let taker_pub = Public::from_slice(gen_args.taker_pub) + .map_to_mm(|e| ValidateDexFeeSpendPreimageError::InvalidPubkey(e.to_string()))?; + + // TODO validate premium amount. Might be a bit tricky in the case of dynamic miner fee + // TODO validate that output amounts are larger than dust + + let premium = match actual_preimage_tx.outputs.get(1) { + Some(o) => o.value, + None => { + return MmError::err(ValidateDexFeeSpendPreimageError::InvalidPreimage( + "Preimage doesn't have output 1".into(), + )) + }, + }; + + // Here, we have to use the exact lock time and premium amount from the preimage because maker + // can get different values (e.g. if MTP advances during preimage exchange/fee rate changes) + let expected_preimage = gen_dex_fee_spend_preimage( + coin, + gen_args, + LocktimeSetting::UseExact(actual_preimage_tx.lock_time), + CalcPremiumBy::UseExactAmount(premium), + ) + .await?; + let redeem_script = + swap_proto_v2_scripts::dex_fee_script(gen_args.time_lock, gen_args.secret_hash, &taker_pub, &maker_pub); + let sig_hash = signature_hash_to_sign( + &expected_preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + coin.as_ref().conf.signature_version, + coin.as_ref().conf.fork_id, + )?; + + if !taker_pub + .verify(&sig_hash, &preimage.signature.clone().into()) + .map_to_mm(|e| ValidateDexFeeSpendPreimageError::SignatureVerificationFailure(e.to_string()))? + { + return MmError::err(ValidateDexFeeSpendPreimageError::InvalidTakerSignature); + }; + let expected_preimage_tx: UtxoTx = expected_preimage.into(); + if expected_preimage_tx != actual_preimage_tx { + return MmError::err(ValidateDexFeeSpendPreimageError::InvalidPreimage( + "Preimage is not equal to expected".into(), + )); + } + Ok(()) +} + +pub async fn sign_and_broadcast_dex_fee_spend( + coin: &T, + preimage: &TxPreimageWithSig, + gen_args: &GenDexFeeSpendArgs<'_>, + secret: &[u8], + htlc_keypair: &KeyPair, +) -> TransactionResult { + let taker_pub = try_tx_s!(Public::from_slice(gen_args.taker_pub)); + + let mut dex_fee_tx: UtxoTx = try_tx_s!(deserialize(gen_args.dex_fee_tx)); + dex_fee_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; + drop_mutability!(dex_fee_tx); + + let mut preimage_tx: UtxoTx = try_tx_s!(deserialize(preimage.preimage.as_slice())); + preimage_tx.tx_hash_algo = coin.as_ref().tx_hash_algo; + drop_mutability!(preimage_tx); + + let secret_hash = dhash160(secret); + let redeem_script = swap_proto_v2_scripts::dex_fee_script( + gen_args.time_lock, + secret_hash.as_slice(), + &taker_pub, + htlc_keypair.public(), + ); + + let mut signer: TransactionInputSigner = preimage_tx.clone().into(); + signer.inputs[0].amount = dex_fee_tx.outputs[0].value; + signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; + drop_mutability!(signer); + + let maker_signature = try_tx_s!(calc_and_sign_sighash( + &signer, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + coin.as_ref().conf.fork_id + )); + let sig_hash_all_fork_id = 1 | coin.as_ref().conf.fork_id as u8; + let mut taker_signature_with_sighash = preimage.signature.clone(); + taker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(taker_signature_with_sighash); + + let mut maker_signature_with_sighash: Vec = maker_signature.take(); + maker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(maker_signature_with_sighash); + + let script_sig = Builder::default() + .push_opcode(Opcode::OP_0) + .push_data(&taker_signature_with_sighash) + .push_data(&maker_signature_with_sighash) + .push_data(secret) + .push_opcode(Opcode::OP_0) + .push_data(&redeem_script) + .into_bytes(); + let mut final_tx: UtxoTx = signer.into(); + final_tx.inputs[0].script_sig = script_sig; + drop_mutability!(final_tx); + + try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); + Ok(final_tx.into()) +} + pub fn send_taker_fee(coin: T, fee_pub_key: &[u8], amount: BigDecimal) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps, @@ -1209,7 +1467,8 @@ where maker_htlc_key_pair.public_slice(), args.other_pubkey, args.secret_hash, - args.amount + args.amount, + SwapPaymentType::TakerOrMakerPayment, )); let send_fut = match &coin.as_ref().rpc_client { UtxoRpcClientEnum::Electrum(_) => Either::A(send_outputs_from_my_address(coin, outputs)), @@ -1245,7 +1504,8 @@ where taker_htlc_key_pair.public_slice(), args.other_pubkey, args.secret_hash, - total_amount + total_amount, + SwapPaymentType::TakerOrMakerPayment, )); let send_fut = match &coin.as_ref().rpc_client { @@ -1550,61 +1810,73 @@ pub fn send_taker_spends_maker_payment(coin: T, args Box::new(fut.boxed().compat()) } -pub fn send_taker_refunds_payment(coin: T, args: RefundPaymentArgs) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); +async fn refund_htlc_payment( + coin: T, + args: RefundPaymentArgs<'_>, + payment_type: SwapPaymentType, +) -> TransactionResult { + let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); let mut prev_transaction: UtxoTx = - try_tx_fus!(deserialize(args.payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); + try_tx_s!(deserialize(args.payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); + return try_tx_s!(TX_PLAIN_ERR!("Transaction doesn't have any output")); } + let other_public = try_tx_s!(Public::from_slice(args.other_pubkey)); let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); - let redeem_script = payment_script( - args.time_lock, - args.secret_hash, - key_pair.public(), - &try_tx_fus!(Public::from_slice(args.other_pubkey)), - ) - .into(); + let redeem_script = match payment_type { + SwapPaymentType::TakerOrMakerPayment => { + payment_script(args.time_lock, args.secret_hash, key_pair.public(), &other_public).into() + }, + SwapPaymentType::DexFeeWithPremium => { + swap_proto_v2_scripts::dex_fee_script(args.time_lock, args.secret_hash, key_pair.public(), &other_public) + .into() + }, + }; let time_lock = args.time_lock; - let fut = async move { - let fee = try_tx_s!( - coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); + if fee >= prev_transaction.outputs[0].value { + return TX_PLAIN_ERR!( + "HTLC spend fee {} is greater than transaction output {}", + fee, + prev_transaction.outputs[0].value ); - if fee >= prev_transaction.outputs[0].value { - return TX_PLAIN_ERR!( - "HTLC spend fee {} is greater than transaction output {}", - fee, - prev_transaction.outputs[0].value - ); - } - let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); - let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, - script_pubkey, - }; + } + let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); + let output = TransactionOutput { + value: prev_transaction.outputs[0].value - fee, + script_pubkey, + }; - let input = P2SHSpendingTxInput { - prev_transaction, - redeem_script, - outputs: vec![output], - script_data, - sequence: SEQUENCE_FINAL - 1, - lock_time: time_lock, - keypair: &key_pair, - }; - let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); + let input = P2SHSpendingTxInput { + prev_transaction, + redeem_script, + outputs: vec![output], + script_data, + sequence: SEQUENCE_FINAL - 1, + lock_time: time_lock, + keypair: &key_pair, + }; + let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); - let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); - try_tx_s!(tx_fut.await, transaction); + let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); + try_tx_s!(tx_fut.await, transaction); - Ok(transaction.into()) - }; - Box::new(fut.boxed().compat()) + Ok(transaction.into()) +} + +#[inline] +pub async fn send_taker_refunds_payment( + coin: T, + args: RefundPaymentArgs<'_>, +) -> TransactionResult { + refund_htlc_payment(coin, args, SwapPaymentType::TakerOrMakerPayment).await } pub fn send_taker_payment_refund_preimage( @@ -1626,59 +1898,12 @@ pub fn send_taker_payment_refund_preimage( Box::new(fut.boxed().compat()) } -pub fn send_maker_refunds_payment(coin: T, args: RefundPaymentArgs) -> TransactionFut { - let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.payment_tx).map_err(|e| ERRL!("{:?}", e))); - prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; - drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } - let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); - let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); - let redeem_script = payment_script( - args.time_lock, - args.secret_hash, - key_pair.public(), - &try_tx_fus!(Public::from_slice(args.other_pubkey)), - ) - .into(); - let time_lock = args.time_lock; - let fut = async move { - let fee = try_tx_s!( - coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await - ); - if fee >= prev_transaction.outputs[0].value { - return TX_PLAIN_ERR!( - "HTLC spend fee {} is greater than transaction output {}", - fee, - prev_transaction.outputs[0].value - ); - } - let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); - let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, - script_pubkey, - }; - - let input = P2SHSpendingTxInput { - prev_transaction, - redeem_script, - outputs: vec![output], - script_data, - sequence: SEQUENCE_FINAL - 1, - lock_time: time_lock, - keypair: &key_pair, - }; - let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); - - let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); - try_tx_s!(tx_fut.await, transaction); - - Ok(transaction.into()) - }; - Box::new(fut.boxed().compat()) +#[inline] +pub async fn send_maker_refunds_payment( + coin: T, + args: RefundPaymentArgs<'_>, +) -> TransactionResult { + refund_htlc_payment(coin, args, SwapPaymentType::TakerOrMakerPayment).await } /// Extracts pubkey from script sig @@ -3490,9 +3715,16 @@ where let secret_hash = &[0; 20]; // H160 is 20 bytes // `generate_swap_payment_outputs` may fail due to either invalid `other_pub` or a number conversation error - let SwapPaymentOutputsResult { outputs, .. } = - generate_swap_payment_outputs(coin, time_lock, my_pub, other_pub, secret_hash, amount) - .map_to_mm(TradePreimageError::InternalError)?; + let SwapPaymentOutputsResult { outputs, .. } = generate_swap_payment_outputs( + coin, + time_lock, + my_pub, + other_pub, + secret_hash, + amount, + SwapPaymentType::TakerOrMakerPayment, + ) + .map_to_mm(TradePreimageError::InternalError)?; let gas_fee = None; let fee_amount = coin .preimage_trade_fee_required_to_send_outputs(outputs, fee_policy, gas_fee, &stage) @@ -3971,6 +4203,11 @@ struct SwapPaymentOutputsResult { outputs: Vec, } +enum SwapPaymentType { + TakerOrMakerPayment, + DexFeeWithPremium, +} + fn generate_swap_payment_outputs( coin: T, time_lock: u32, @@ -3978,17 +4215,19 @@ fn generate_swap_payment_outputs( other_pub: &[u8], secret_hash: &[u8], amount: BigDecimal, + payment_type: SwapPaymentType, ) -> Result where T: AsRef, { let my_public = try_s!(Public::from_slice(my_pub)); - let redeem_script = payment_script( - time_lock, - secret_hash, - &my_public, - &try_s!(Public::from_slice(other_pub)), - ); + let other_public = try_s!(Public::from_slice(other_pub)); + let redeem_script = match payment_type { + SwapPaymentType::TakerOrMakerPayment => payment_script(time_lock, secret_hash, &my_public, &other_public), + SwapPaymentType::DexFeeWithPremium => { + swap_proto_v2_scripts::dex_fee_script(time_lock, secret_hash, &my_public, &other_public) + }, + }; let redeem_script_hash = dhash160(&redeem_script); let amount = try_s!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); let htlc_out = TransactionOutput { @@ -4342,6 +4581,93 @@ where .collect() } +pub async fn send_dex_fee_with_premium(coin: T, args: SendDexFeeWithPremiumArgs<'_>) -> TransactionResult +where + T: UtxoCommonOps + GetUtxoListOps + SwapOps, +{ + let taker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let total_amount = &args.dex_fee_amount + &args.premium_amount; + + let SwapPaymentOutputsResult { + payment_address, + outputs, + } = try_tx_s!(generate_swap_payment_outputs( + &coin, + args.time_lock, + taker_htlc_key_pair.public_slice(), + args.other_pub, + args.secret_hash, + total_amount, + SwapPaymentType::DexFeeWithPremium, + )); + if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { + let addr_string = try_tx_s!(payment_address.display_address()); + client + .import_address(&addr_string, &addr_string, false) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))) + .compat() + .await?; + } + send_outputs_from_my_address(coin, outputs).compat().await +} + +pub async fn validate_dex_fee_with_premium(coin: &T, args: ValidateDexFeeArgs<'_>) -> ValidateDexFeeResult +where + T: UtxoCommonOps + SwapOps, +{ + let dex_fee_tx: UtxoTx = + deserialize(args.dex_fee_tx).map_to_mm(|e| ValidateDexFeeError::TxDeserialization(e.to_string()))?; + if dex_fee_tx.outputs.len() < 2 { + return MmError::err(ValidateDexFeeError::TxLacksOfOutputs); + } + + let taker_pub = + Public::from_slice(args.other_pub).map_to_mm(|e| ValidateDexFeeError::InvalidPubkey(e.to_string()))?; + let maker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let total_expected_amount = &args.dex_fee_amount + &args.premium_amount; + + let expected_amount_sat = sat_from_big_decimal(&total_expected_amount, coin.as_ref().decimals)?; + + let redeem_script = swap_proto_v2_scripts::dex_fee_script( + args.time_lock, + args.secret_hash, + &taker_pub, + maker_htlc_key_pair.public(), + ); + let expected_output = TransactionOutput { + value: expected_amount_sat, + script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&redeem_script))).into(), + }; + + if dex_fee_tx.outputs[0] != expected_output { + return MmError::err(ValidateDexFeeError::InvalidDestinationOrAmount(format!( + "Expected {:?}, got {:?}", + expected_output, dex_fee_tx.outputs[0] + ))); + } + + let tx_bytes_from_rpc = coin + .as_ref() + .rpc_client + .get_transaction_bytes(&dex_fee_tx.hash().reversed().into()) + .compat() + .await?; + if tx_bytes_from_rpc.0 != args.dex_fee_tx { + return MmError::err(ValidateDexFeeError::TxBytesMismatch { + from_rpc: tx_bytes_from_rpc, + actual: args.dex_fee_tx.into(), + }); + } + Ok(()) +} + +pub async fn refund_dex_fee_with_premium(coin: T, args: RefundPaymentArgs<'_>) -> TransactionResult +where + T: UtxoCommonOps + GetUtxoListOps + SwapOps, +{ + refund_htlc_payment(coin, args, SwapPaymentType::DexFeeWithPremium).await +} + #[test] fn test_increase_by_percent() { assert_eq!(increase_by_percent(4300, 1.), 4343); diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 8c03e66a8c..6a0ca49dac 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,15 +23,17 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, - TransactionFut, TxMarshalingErr, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawSenderAddress}; + GenAndSignDexFeeSpendResult, GenDexFeeSpendArgs, GetWithdrawSenderAddress, IguanaPrivKey, + MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendDexFeeWithPremiumArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, TradePreimageValue, + TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, + ValidateDexFeeArgs, ValidateDexFeeResult, ValidateDexFeeSpendPreimageResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, + WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; @@ -315,13 +317,13 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args).await } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { @@ -579,6 +581,49 @@ impl WatcherOps for UtxoStandardCoin { } } +#[async_trait] +impl SwapOpsV2 for UtxoStandardCoin { + async fn send_dex_fee_with_premium(&self, args: SendDexFeeWithPremiumArgs<'_>) -> TransactionResult { + utxo_common::send_dex_fee_with_premium(self.clone(), args).await + } + + async fn validate_dex_fee_with_premium(&self, args: ValidateDexFeeArgs<'_>) -> ValidateDexFeeResult { + utxo_common::validate_dex_fee_with_premium(self, args).await + } + + async fn refund_dex_fee_with_premium(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::refund_dex_fee_with_premium(self.clone(), args).await + } + + async fn gen_and_sign_dex_fee_spend_preimage( + &self, + args: &GenDexFeeSpendArgs<'_>, + swap_unique_data: &[u8], + ) -> GenAndSignDexFeeSpendResult { + let key_pair = self.derive_htlc_key_pair(swap_unique_data); + utxo_common::gen_and_sign_dex_fee_spend_preimage(self, args, &key_pair).await + } + + async fn validate_dex_fee_spend_preimage( + &self, + gen_args: &GenDexFeeSpendArgs<'_>, + preimage: &TxPreimageWithSig, + ) -> ValidateDexFeeSpendPreimageResult { + utxo_common::validate_dex_fee_spend_preimage(self, gen_args, preimage).await + } + + async fn sign_and_broadcast_dex_fee_spend( + &self, + preimage: &TxPreimageWithSig, + gen_args: &GenDexFeeSpendArgs<'_>, + secret: &[u8], + swap_unique_data: &[u8], + ) -> TransactionResult { + let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); + utxo_common::sign_and_broadcast_dex_fee_spend(self, preimage, gen_args, secret, &htlc_keypair).await + } +} + impl MarketCoinOps for UtxoStandardCoin { fn ticker(&self) -> &str { &self.utxo_arc.conf.ticker } diff --git a/mm2src/coins/utxo/utxo_tx_history_v2.rs b/mm2src/coins/utxo/utxo_tx_history_v2.rs index 8a3e47c100..ca3b3a6dcd 100644 --- a/mm2src/coins/utxo/utxo_tx_history_v2.rs +++ b/mm2src/coins/utxo/utxo_tx_history_v2.rs @@ -11,6 +11,7 @@ use async_trait::async_trait; use common::executor::Timer; use common::log::{error, info}; use common::state_machine::prelude::*; +use common::state_machine::StateMachineTrait; use derive_more::Display; use keys::Address; use mm2_err_handle::prelude::*; @@ -136,7 +137,7 @@ pub trait UtxoTxHistoryOps: CoinWithTxHistoryV2 + MarketCoinOps + Send + Sync + fn set_history_sync_state(&self, new_state: HistorySyncState); } -struct UtxoTxHistoryCtx { +struct UtxoTxHistoryStateMachine { coin: Coin, storage: Storage, metrics: MetricsArc, @@ -145,17 +146,21 @@ struct UtxoTxHistoryCtx { balances: HashMap, } -impl UtxoTxHistoryCtx +impl StateMachineTrait for UtxoTxHistoryStateMachine { + type Result = (); +} + +impl UtxoTxHistoryStateMachine where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - /// Requests balances for every activated address, updates the balances in [`UtxoTxHistoryCtx::balances`] + /// Requests balances for every activated address, updates the balances in [`UtxoTxHistoryStateMachine::balances`] /// and returns the addresses whose balance has changed. /// /// # Note /// - /// [`UtxoTxHistoryCtx::balances`] is changed if we successfully handled all balances **only**. + /// [`UtxoTxHistoryStateMachine::balances`] is changed if we successfully handled all balances **only**. async fn updated_addresses(&mut self) -> BalanceResult> { let current_balances = self.coin.my_addresses_balances().await?; @@ -222,10 +227,12 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut UtxoTxHistoryStateMachine, + ) -> StateResult> { ctx.coin.set_history_sync_state(HistorySyncState::NotStarted); if let Err(e) = ctx.storage.init(&ctx.coin.history_wallet_id()).await { @@ -268,10 +275,12 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut UtxoTxHistoryStateMachine, + ) -> StateResult> { let wallet_id = ctx.coin.history_wallet_id(); if let Err(e) = ctx.storage.init(&wallet_id).await { return Self::change_state(Stopped::storage_error(e)); @@ -330,7 +339,7 @@ where } /// An I/O cooldown before `FetchingTxHashes` state. -/// States have to be generic over storage type because `UtxoTxHistoryCtx` is generic over it. +/// States have to be generic over storage type because `UtxoTxHistoryStateMachine` is generic over it. struct OnIoErrorCooldown { /// The list of addresses of those we need to fetch TX hashes at the upcoming `FetchingTxHashses` state. fetch_for_addresses: HashSet
, @@ -356,10 +365,12 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(mut self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + mut self: Box, + ctx: &mut UtxoTxHistoryStateMachine, + ) -> StateResult> { loop { Timer::sleep(30.).await; @@ -406,10 +417,12 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut UtxoTxHistoryStateMachine, + ) -> StateResult> { let wallet_id = ctx.coin.history_wallet_id(); loop { Timer::sleep(30.).await; @@ -471,10 +484,12 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut UtxoTxHistoryStateMachine, + ) -> StateResult> { let wallet_id = ctx.coin.history_wallet_id(); let for_addresses = to_filtering_addresses(&self.requested_for_addresses); @@ -551,10 +566,12 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed( + self: Box, + ctx: &mut UtxoTxHistoryStateMachine, + ) -> StateResult> { let ticker = ctx.coin.ticker(); let wallet_id = ctx.coin.history_wallet_id(); @@ -653,10 +670,9 @@ where Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - type Ctx = UtxoTxHistoryCtx; - type Result = (); + type StateMachine = UtxoTxHistoryStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> Self::Result { + async fn on_changed(self: Box, ctx: &mut UtxoTxHistoryStateMachine) -> () { info!( "Stopping tx history fetching for {}. Reason: {:?}", ctx.coin.ticker(), @@ -711,14 +727,13 @@ pub async fn bch_and_slp_history_loop( }, }; - let ctx = UtxoTxHistoryCtx { + let mut state_machine = UtxoTxHistoryStateMachine { coin, storage, metrics, balances, }; - let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(ctx); - state_machine.run(Init::new()).await; + state_machine.run(Box::new(Init::new())).await; } pub async fn utxo_history_loop( @@ -730,14 +745,13 @@ pub async fn utxo_history_loop( Coin: UtxoTxHistoryOps, Storage: TxHistoryStorage, { - let ctx = UtxoTxHistoryCtx { + let mut state_machine = UtxoTxHistoryStateMachine { coin, storage, metrics, balances: current_balances, }; - let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(ctx); - state_machine.run(Init::new()).await; + state_machine.run(Box::new(Init::new())).await; } fn to_filtering_addresses(addresses: &HashSet
) -> FilteringAddresses { diff --git a/mm2src/coins/utxo_signer/src/with_key_pair.rs b/mm2src/coins/utxo_signer/src/with_key_pair.rs index 3db094e2e8..a061fe1017 100644 --- a/mm2src/coins/utxo_signer/src/with_key_pair.rs +++ b/mm2src/coins/utxo_signer/src/with_key_pair.rs @@ -82,7 +82,7 @@ pub fn p2pk_spend( let unsigned_input = get_input(signer, input_index)?; let script = Builder::build_p2pk(key_pair.public()); - let signature = calc_and_sign_sighash(signer, input_index, script, key_pair, signature_version, fork_id)?; + let signature = calc_and_sign_sighash(signer, input_index, &script, key_pair, signature_version, fork_id)?; Ok(p2pk_spend_with_signature(unsigned_input, fork_id, signature)) } @@ -106,7 +106,7 @@ pub fn p2pkh_spend( }); } - let signature = calc_and_sign_sighash(signer, input_index, script, key_pair, signature_version, fork_id)?; + let signature = calc_and_sign_sighash(signer, input_index, &script, key_pair, signature_version, fork_id)?; Ok(p2pkh_spend_with_signature( unsigned_input, key_pair.public(), @@ -130,7 +130,7 @@ pub fn p2sh_spend( let signature = calc_and_sign_sighash( signer, input_index, - redeem_script.clone(), + &redeem_script, key_pair, signature_version, fork_id, @@ -164,7 +164,7 @@ pub fn p2wpkh_spend( }); } - let signature = calc_and_sign_sighash(signer, input_index, script, key_pair, signature_version, fork_id)?; + let signature = calc_and_sign_sighash(signer, input_index, &script, key_pair, signature_version, fork_id)?; Ok(p2wpkh_spend_with_signature( unsigned_input, key_pair.public(), @@ -174,10 +174,10 @@ pub fn p2wpkh_spend( } /// Calculates the input script hash and sign it using `key_pair`. -pub(crate) fn calc_and_sign_sighash( +pub fn calc_and_sign_sighash( signer: &TransactionInputSigner, input_index: usize, - output_script: Script, + output_script: &Script, key_pair: &KeyPair, signature_version: SignatureVersion, fork_id: u32, @@ -186,10 +186,10 @@ pub(crate) fn calc_and_sign_sighash( sign_message(&sighash, key_pair) } -fn signature_hash_to_sign( +pub fn signature_hash_to_sign( signer: &TransactionInputSigner, input_index: usize, - output_script: Script, + output_script: &Script, signature_version: SignatureVersion, fork_id: u32, ) -> UtxoSignWithKeyPairResult { @@ -199,7 +199,7 @@ fn signature_hash_to_sign( Ok(signer.signature_hash( input_index, input_amount, - &output_script, + output_script, signature_version, sighash_type, )) diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index cf0011c4dc..041b830693 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -20,10 +20,10 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionEnum, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + TransactionEnum, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; @@ -1285,60 +1285,53 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { - let tx = try_tx_fus!(ZTransaction::read(taker_refunds_payment_args.payment_tx)); + async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + let tx = try_tx_s!(ZTransaction::read(taker_refunds_payment_args.payment_tx)); let key_pair = self.derive_htlc_key_pair(taker_refunds_payment_args.swap_unique_data); let time_lock = taker_refunds_payment_args.time_lock; let redeem_script = payment_script( time_lock, taker_refunds_payment_args.secret_hash, key_pair.public(), - &try_tx_fus!(Public::from_slice(taker_refunds_payment_args.other_pubkey)), + &try_tx_s!(Public::from_slice(taker_refunds_payment_args.other_pubkey)), ); let script_data = ScriptBuilder::default().push_opcode(Opcode::OP_1).into_script(); - let selfi = self.clone(); - let fut = async move { - let tx_fut = z_p2sh_spend( - &selfi, - tx, - time_lock, - SEQUENCE_FINAL - 1, - redeem_script, - script_data, - &key_pair, - ); - let tx = try_ztx_s!(tx_fut.await); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + + let tx_fut = z_p2sh_spend( + self, + tx, + time_lock, + SEQUENCE_FINAL - 1, + redeem_script, + script_data, + &key_pair, + ); + let tx = try_ztx_s!(tx_fut.await); + Ok(tx.into()) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { - let tx = try_tx_fus!(ZTransaction::read(maker_refunds_payment_args.payment_tx)); + async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { + let tx = try_tx_s!(ZTransaction::read(maker_refunds_payment_args.payment_tx)); let key_pair = self.derive_htlc_key_pair(maker_refunds_payment_args.swap_unique_data); let time_lock = maker_refunds_payment_args.time_lock; let redeem_script = payment_script( time_lock, maker_refunds_payment_args.secret_hash, key_pair.public(), - &try_tx_fus!(Public::from_slice(maker_refunds_payment_args.other_pubkey)), + &try_tx_s!(Public::from_slice(maker_refunds_payment_args.other_pubkey)), ); let script_data = ScriptBuilder::default().push_opcode(Opcode::OP_1).into_script(); - let selfi = self.clone(); - let fut = async move { - let tx_fut = z_p2sh_spend( - &selfi, - tx, - time_lock, - SEQUENCE_FINAL - 1, - redeem_script, - script_data, - &key_pair, - ); - let tx = try_ztx_s!(tx_fut.await); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + let tx_fut = z_p2sh_spend( + self, + tx, + time_lock, + SEQUENCE_FINAL - 1, + redeem_script, + script_data, + &key_pair, + ); + let tx = try_ztx_s!(tx_fut.await); + Ok(tx.into()) } fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 9c3ae3a0d6..efe4079ea2 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -65,7 +65,7 @@ fn zombie_coin_send_and_refund_maker_payment() { swap_unique_data: pk_data.as_slice(), watcher_reward: false, }; - let refund_tx = coin.send_maker_refunds_payment(refund_args).wait().unwrap(); + let refund_tx = block_on(coin.send_maker_refunds_payment(refund_args)).unwrap(); println!("refund tx {}", hex::encode(refund_tx.tx_hash().0)); } diff --git a/mm2src/common/patterns/state_machine.rs b/mm2src/common/patterns/state_machine.rs index a558378de0..bd00be8857 100644 --- a/mm2src/common/patterns/state_machine.rs +++ b/mm2src/common/patterns/state_machine.rs @@ -6,39 +6,28 @@ use crate::NotSame; use async_trait::async_trait; pub mod prelude { - pub use super::{LastState, State, StateExt, StateMachine, StateResult, TransitionFrom}; + pub use super::{LastState, State, StateExt, StateResult, TransitionFrom}; } -pub struct StateMachine { - /// The shared between states context. - ctx: Ctx, - phantom: std::marker::PhantomData, -} +pub trait TransitionFrom {} -impl StateMachine { - pub fn from_ctx(ctx: Ctx) -> Self { - StateMachine { - ctx, - phantom: std::marker::PhantomData::default(), - } - } +#[async_trait] +pub trait StateMachineTrait: Send + Sized + 'static { + type Result: Send; - pub async fn run(mut self, initial_state: impl State) -> Result { - let mut state: Box> = Box::new(initial_state); + async fn run(&mut self, mut state: Box>) -> Self::Result { loop { - let result = state.on_changed(&mut self.ctx).await; - let next_state = match result { - StateResult::ChangeState(ChangeGuard { next }) => next, + let result = state.on_changed(self).await; + match result { + StateResult::ChangeState(ChangeGuard { next }) => { + state = next; + }, StateResult::Finish(ResultGuard { result }) => return result, }; - - state = next_state; } } } -pub trait TransitionFrom {} - /// Prevent implementing [`TransitionFrom`] for `Next` If `T` implements `LastState` already. impl !TransitionFrom for Next where @@ -52,24 +41,22 @@ where impl !TransitionFrom for T {} #[async_trait] -pub trait State: Send + 'static { - type Ctx: Send; - type Result; - +pub trait State: Send + Sync + 'static { + type StateMachine: StateMachineTrait; /// An action is called on entering this state. /// To change the state to another one in the end of processing, use [`StateExt::change_state`]. /// For example: /// ```rust /// return Self::change_state(next_state); /// ``` - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult; + async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult; } pub trait StateExt { /// Change the state to the `next_state`. /// This function performs the compile-time validation whether this state can transition to the `Next` state, /// i.e checks if `Next` implements [`Transition::from(ThisState)`]. - fn change_state(next_state: Next) -> StateResult + fn change_state(next_state: Next) -> StateResult where Self: Sized, Next: State + TransitionFrom, @@ -81,41 +68,42 @@ pub trait StateExt { impl StateExt for T {} #[async_trait] -pub trait LastState: Send + 'static { - type Ctx: Send; - type Result; +pub trait LastState: Send + Sync + 'static { + type StateMachine: StateMachineTrait; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> Self::Result; + async fn on_changed( + self: Box, + ctx: &mut Self::StateMachine, + ) -> ::Result; } #[async_trait] impl State for T { - type Ctx = T::Ctx; - type Result = T::Result; + type StateMachine = T::StateMachine; /// The last state always returns the result of the state machine calculations. - async fn on_changed(self: Box, ctx: &mut T::Ctx) -> StateResult { + async fn on_changed(self: Box, ctx: &mut T::StateMachine) -> StateResult { let result = LastState::on_changed(self, ctx).await; StateResult::Finish(ResultGuard::new(result)) } } -pub enum StateResult { - ChangeState(ChangeGuard), - Finish(ResultGuard), +pub enum StateResult { + ChangeState(ChangeGuard), + Finish(ResultGuard), } /* vvv The access guards that prevents the user using this pattern from entering an invalid state vvv */ /// An instance of `ChangeGuard` can be initialized within `state_machine` module only. -pub struct ChangeGuard { +pub struct ChangeGuard { /// The private field. - next: Box>, + next: Box>, } -impl ChangeGuard { +impl ChangeGuard { /// The private constructor. - fn next>(next_state: Next) -> Self { + fn next>(next_state: Next) -> Self { ChangeGuard { next: Box::new(next_state), } @@ -152,10 +140,16 @@ mod tests { UnknownUser, } - struct AuthCtx { + struct AuthStateMachine { users: HashMap<(Login, Password), UserId>, } + type AuthResult = Result; + + impl StateMachineTrait for AuthStateMachine { + type Result = AuthResult; + } + struct ReadingState { rx: mpsc::Receiver, } @@ -181,26 +175,23 @@ mod tests { #[async_trait] impl LastState for SuccessfulState { - type Ctx = AuthCtx; - type Result = Result; + type StateMachine = AuthStateMachine; - async fn on_changed(self: Box, _ctx: &mut Self::Ctx) -> Self::Result { Ok(self.user_id) } + async fn on_changed(self: Box, _ctx: &mut AuthStateMachine) -> AuthResult { Ok(self.user_id) } } #[async_trait] impl LastState for ErrorState { - type Ctx = AuthCtx; - type Result = Result; + type StateMachine = AuthStateMachine; - async fn on_changed(self: Box, _ctx: &mut Self::Ctx) -> Self::Result { Err(self.error) } + async fn on_changed(self: Box, _ctx: &mut AuthStateMachine) -> AuthResult { Err(self.error) } } #[async_trait] impl State for ReadingState { - type Ctx = AuthCtx; - type Result = Result; + type StateMachine = AuthStateMachine; - async fn on_changed(mut self: Box, _ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed(mut self: Box, _ctx: &mut AuthStateMachine) -> StateResult { let mut line = String::with_capacity(80); while let Some(ch) = self.rx.next().await { line.push(ch); @@ -212,10 +203,9 @@ mod tests { #[async_trait] impl State for ParsingState { - type Ctx = AuthCtx; - type Result = Result; + type StateMachine = AuthStateMachine; - async fn on_changed(self: Box, _ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed(self: Box, _ctx: &mut AuthStateMachine) -> StateResult { // parse the line into two chunks: (login, password) let chunks: Vec<_> = self.line.split(' ').collect(); if chunks.len() == 2 { @@ -235,10 +225,9 @@ mod tests { #[async_trait] impl State for AuthenticationState { - type Ctx = AuthCtx; - type Result = Result; + type StateMachine = AuthStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed(self: Box, ctx: &mut AuthStateMachine) -> StateResult { let credentials = (self.login, self.password); match ctx.users.get(&credentials) { Some(user_id) => Self::change_state(SuccessfulState { user_id: *user_id }), @@ -266,8 +255,8 @@ mod tests { let fut = async move { let initial_state: ReadingState = ReadingState { rx }; - let state_machine = StateMachine::from_ctx(AuthCtx { users }); - state_machine.run(initial_state).await + let mut state_machine = AuthStateMachine { users }; + state_machine.run(Box::new(initial_state)).await }; block_on(fut) } diff --git a/mm2src/mm2_bin_lib/Cargo.toml b/mm2src/mm2_bin_lib/Cargo.toml index e708e460aa..bd3bf5aac9 100644 --- a/mm2src/mm2_bin_lib/Cargo.toml +++ b/mm2src/mm2_bin_lib/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "mm2_bin_lib" -version = "1.0.6-beta" +version = "1.0.7-beta" authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann "] edition = "2018" default-run = "mm2" diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index bfd69634ab..e0fa771ff2 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -34,6 +34,7 @@ use parking_lot::Mutex as PaMutex; use primitives::hash::{H256, H264}; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json, H264 as H264Json}; use std::any::TypeId; +use std::convert::TryInto; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -1216,17 +1217,31 @@ impl MakerSwap { } } - let spend_fut = self.maker_coin.send_maker_refunds_payment(RefundPaymentArgs { - payment_tx: &maker_payment, - time_lock: locktime as u32, - other_pubkey: &*self.r().other_maker_coin_htlc_pub, - secret_hash: self.secret_hash().as_slice(), - swap_contract_address: &self.r().data.maker_coin_swap_contract_address, - swap_unique_data: &self.unique_swap_data(), - watcher_reward: self.r().watcher_reward, - }); + let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub; + let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); + let watcher_reward = self.r().watcher_reward; + let time_lock: u32 = match locktime.try_into() { + Ok(t) => t, + Err(e) => { + return Ok((Some(MakerSwapCommand::Finish), vec![ + MakerSwapEvent::MakerPaymentRefundFailed(ERRL!("!locktime.try_into: {}", e.to_string()).into()), + ])) + }, + }; + let spend_result = self + .maker_coin + .send_maker_refunds_payment(RefundPaymentArgs { + payment_tx: &maker_payment, + time_lock, + other_pubkey: other_maker_coin_htlc_pub.as_slice(), + secret_hash: self.secret_hash().as_slice(), + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &self.unique_swap_data(), + watcher_reward, + }) + .await; - let transaction = match spend_fut.compat().await { + let transaction = match spend_result { Ok(t) => t, Err(err) => { if let Some(tx) = err.get_tx() { @@ -1531,7 +1546,7 @@ impl MakerSwap { watcher_reward, }); - let transaction = match fut.compat().await { + let transaction = match fut.await { Ok(t) => t, Err(err) => { if let Some(tx) = err.get_tx() { @@ -2394,7 +2409,7 @@ mod maker_swap_tests { static mut MAKER_REFUND_CALLED: bool = false; TestCoin::send_maker_refunds_payment.mock_safe(|_, _| { unsafe { MAKER_REFUND_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(eth_tx_for_test().into()))) + MockResult::Return(Box::pin(futures::future::ok(eth_tx_for_test().into()))) }); TestCoin::search_for_swap_tx_spend_my .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); @@ -2428,7 +2443,7 @@ mod maker_swap_tests { static mut MAKER_REFUND_CALLED: bool = false; TestCoin::send_maker_refunds_payment.mock_safe(|_, _| { unsafe { MAKER_REFUND_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(eth_tx_for_test().into()))) + MockResult::Return(Box::pin(futures::future::ok(eth_tx_for_test().into()))) }); TestCoin::search_for_swap_tx_spend_my @@ -2698,7 +2713,7 @@ mod maker_swap_tests { static mut SEND_MAKER_REFUNDS_PAYMENT_CALLED: bool = false; TestCoin::send_maker_refunds_payment.mock_safe(|_, _| { unsafe { SEND_MAKER_REFUNDS_PAYMENT_CALLED = true } - MockResult::Return(Box::new(futures01::future::ok(eth_tx_for_test().into()))) + MockResult::Return(Box::pin(futures::future::ok(eth_tx_for_test().into()))) }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index 1e6bcdf3f2..f85faf2bae 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -9,6 +9,7 @@ use coins::{CanRefundHtlc, ConfirmPaymentInput, FoundSwapTxSpend, MmCoinEnum, Re use common::executor::{AbortSettings, SpawnAbortable, Timer}; use common::log::{debug, error, info}; use common::state_machine::prelude::*; +use common::state_machine::StateMachineTrait; use common::{now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use futures::compat::Future01CompatExt; use mm2_core::mm_ctx::MmArc; @@ -28,7 +29,7 @@ pub const MAKER_PAYMENT_SPEND_SENT_LOG: &str = "Maker payment spend sent"; pub const MAKER_PAYMENT_SPEND_FOUND_LOG: &str = "Maker payment spend found by watcher"; pub const TAKER_PAYMENT_REFUND_SENT_LOG: &str = "Taker payment refund sent"; -struct WatcherContext { +struct WatcherStateMachine { ctx: MmArc, taker_coin: MmCoinEnum, maker_coin: MmCoinEnum, @@ -38,7 +39,11 @@ struct WatcherContext { watcher_reward: bool, } -impl WatcherContext { +impl StateMachineTrait for WatcherStateMachine { + type Result = (); +} + +impl WatcherStateMachine { fn taker_locktime(&self) -> u64 { self.data.swap_started_at + self.data.lock_duration } fn wait_for_maker_payment_spend_deadline(&self) -> u64 { @@ -167,10 +172,9 @@ impl TransitionFrom for Stopped {} #[async_trait] impl State for ValidateTakerFee { - type Ctx = WatcherContext; - type Result = (); + type StateMachine = WatcherStateMachine; - async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { let validated_f = watcher_ctx .taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { @@ -195,10 +199,9 @@ impl State for ValidateTakerFee { // TODO: Validate also maker payment #[async_trait] impl State for ValidateTakerPayment { - type Ctx = WatcherContext; - type Result = (); + type StateMachine = WatcherStateMachine; - async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { let taker_payment_spend_deadline = taker_payment_spend_deadline(watcher_ctx.data.swap_started_at, watcher_ctx.data.lock_duration); @@ -269,10 +272,9 @@ impl State for ValidateTakerPayment { #[async_trait] impl State for WaitForTakerPaymentSpend { - type Ctx = WatcherContext; - type Result = (); + type StateMachine = WatcherStateMachine; - async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { let payment_search_interval = watcher_ctx.conf.search_interval; let wait_until = watcher_ctx.refund_start_time(); let search_input = WatcherSearchForSwapTxSpendInput { @@ -374,10 +376,9 @@ impl State for WaitForTakerPaymentSpend { #[async_trait] impl State for SpendMakerPayment { - type Ctx = WatcherContext; - type Result = (); + type StateMachine = WatcherStateMachine; - async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { let spend_fut = watcher_ctx .maker_coin .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { @@ -426,10 +427,9 @@ impl State for SpendMakerPayment { #[async_trait] impl State for RefundTakerPayment { - type Ctx = WatcherContext; - type Result = (); + type StateMachine = WatcherStateMachine; - async fn on_changed(self: Box, watcher_ctx: &mut WatcherContext) -> StateResult { + async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { if std::env::var("USE_TEST_LOCKTIME").is_err() { loop { match watcher_ctx @@ -497,9 +497,9 @@ impl State for RefundTakerPayment { #[async_trait] impl LastState for Stopped { - type Ctx = WatcherContext; - type Result = (); - async fn on_changed(self: Box, _watcher_ctx: &mut Self::Ctx) -> Self::Result {} + type StateMachine = WatcherStateMachine; + + async fn on_changed(self: Box, _watcher_ctx: &mut WatcherStateMachine) -> () {} } pub fn process_watcher_msg(ctx: MmArc, msg: &[u8]) -> P2PRequestResult<()> { @@ -624,7 +624,7 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri let conf = json::from_value::(ctx.conf["watcher_conf"].clone()).unwrap_or_default(); let watcher_reward = maker_coin.is_eth(); - let watcher_ctx = WatcherContext { + let mut state_machine = WatcherStateMachine { ctx, maker_coin, taker_coin, @@ -633,8 +633,7 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri conf, watcher_reward, }; - let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(watcher_ctx); - state_machine.run(ValidateTakerFee {}).await; + state_machine.run(Box::new(ValidateTakerFee {})).await; // This allows to move the `taker_watcher_lock` value into this async block to keep it alive // until the Swap Watcher finishes. diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 1be5345b2d..ad7e9d0ca4 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -34,7 +34,7 @@ use parking_lot::Mutex as PaMutex; use primitives::hash::H264; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json, H264 as H264Json}; use serde_json::{self as json, Value as Json}; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -1828,17 +1828,32 @@ impl TakerSwap { } } - let refund_fut = self.taker_coin.send_taker_refunds_payment(RefundPaymentArgs { - payment_tx: &taker_payment, - time_lock: locktime as u32, - other_pubkey: &*self.r().other_taker_coin_htlc_pub, - secret_hash: &self.r().secret_hash.0, - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, - swap_unique_data: &self.unique_swap_data(), - watcher_reward: self.r().watcher_reward, - }); + let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; + let secret_hash = self.r().secret_hash.clone(); + let swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); + let watcher_reward = self.r().watcher_reward; + let time_lock: u32 = match locktime.try_into() { + Ok(t) => t, + Err(e) => { + return Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::TakerPaymentRefundFailed(ERRL!("!locktime.try_into: {}", e.to_string()).into()), + ])) + }, + }; + let refund_result = self + .taker_coin + .send_taker_refunds_payment(RefundPaymentArgs { + payment_tx: &taker_payment, + time_lock, + other_pubkey: other_taker_coin_htlc_pub.as_slice(), + secret_hash: &secret_hash, + swap_contract_address: &swap_contract_address, + swap_unique_data: &self.unique_swap_data(), + watcher_reward, + }) + .await; - let transaction = match refund_fut.compat().await { + let transaction = match refund_result { Ok(t) => t, Err(err) => { if let Some(tx) = err.get_tx() { @@ -2183,7 +2198,7 @@ impl TakerSwap { watcher_reward, }); - let transaction = match fut.compat().await { + let transaction = match fut.await { Ok(t) => t, Err(err) => { if let Some(tx) = err.get_tx() { @@ -2668,7 +2683,7 @@ mod taker_swap_tests { static mut TAKER_PAYMENT_REFUND_CALLED: bool = false; TestCoin::send_taker_refunds_payment.mock_safe(|_, _| { unsafe { TAKER_PAYMENT_REFUND_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(eth_tx_for_test().into()))) + MockResult::Return(Box::pin(futures::future::ok(eth_tx_for_test().into()))) }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); @@ -2753,7 +2768,7 @@ mod taker_swap_tests { static mut REFUND_CALLED: bool = false; TestCoin::send_taker_refunds_payment.mock_safe(|_, _| { unsafe { REFUND_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(eth_tx_for_test().into()))) + MockResult::Return(Box::pin(futures::future::ok(eth_tx_for_test().into()))) }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index f8e52b4df7..39dbcbe6e6 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -69,6 +69,9 @@ pub const UTXO_ASSET_DOCKER_IMAGE: &str = "docker.io/artempikulin/testblockchain pub const QTUM_ADDRESS_LABEL: &str = "MM2_ADDRESS_LABEL"; +pub const MYCOIN: &str = "MYCOIN"; +pub const _MYCOIN1: &str = "MYCOIN1"; + pub trait CoinDockerOps { fn rpc_client(&self) -> &UtxoRpcClientEnum; diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index afc497bc28..922bff0623 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -60,10 +60,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { swap_unique_data: &[], watcher_reward: false, }; - let refund_tx = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund_tx = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: refund_tx.tx_hex(), @@ -148,10 +145,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { swap_unique_data: &[], watcher_reward: false, }; - let refund_tx = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund_tx = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: refund_tx.tx_hex(), diff --git a/mm2src/mm2_main/tests/docker_tests/mod.rs b/mm2src/mm2_main/tests/docker_tests/mod.rs index 4ac4e6541d..c608944faf 100644 --- a/mm2src/mm2_main/tests/docker_tests/mod.rs +++ b/mm2src/mm2_main/tests/docker_tests/mod.rs @@ -4,6 +4,7 @@ mod docker_ordermatch_tests; mod docker_tests_inner; pub mod qrc20_tests; mod slp_tests; +mod swap_proto_v2_tests; mod swap_watcher_tests; mod swaps_confs_settings_sync_tests; mod swaps_file_lock_tests; diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index bbdf333f6c..dc6f915948 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -422,10 +422,7 @@ fn test_maker_refunds_payment() { swap_unique_data: &[], watcher_reward: false, }; - let refund = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); let refund_tx_hash = refund.tx_hash(); let refund_tx_hex = refund.tx_hex(); log!("Maker refunds payment: {:?}", refund_tx_hash); @@ -495,10 +492,7 @@ fn test_taker_refunds_payment() { swap_unique_data: &[], watcher_reward: false, }; - let refund = coin - .send_taker_refunds_payment(taker_refunds_payment_args) - .wait() - .unwrap(); + let refund = block_on(coin.send_taker_refunds_payment(taker_refunds_payment_args)).unwrap(); let refund_tx_hash = refund.tx_hash(); let refund_tx_hex = refund.tx_hex(); log!("Taker refunds payment: {:?}", refund_tx_hash); @@ -701,10 +695,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { swap_unique_data: &[], watcher_reward: false, }; - let refund = maker_coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund = block_on(maker_coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); let refund_tx_hash = refund.tx_hash(); let refund_tx_hex = refund.tx_hex(); log!("Maker refunds tx: {:?}", refund_tx_hash); @@ -1556,10 +1547,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { swap_unique_data: &[], watcher_reward: false, }; - let refund_tx = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund_tx = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: refund_tx.tx_hex(), @@ -1625,10 +1613,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { swap_unique_data: &[], watcher_reward: false, }; - let refund_tx = coin - .send_maker_refunds_payment(maker_refunds_payment_args) - .wait() - .unwrap(); + let refund_tx = block_on(coin.send_maker_refunds_payment(maker_refunds_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: refund_tx.tx_hex(), diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs new file mode 100644 index 0000000000..a661b6cdc7 --- /dev/null +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -0,0 +1,129 @@ +use crate::{generate_utxo_coin_with_random_privkey, MYCOIN}; +use bitcrypto::dhash160; +use coins::utxo::UtxoCommonOps; +use coins::{GenDexFeeSpendArgs, RefundPaymentArgs, SendDexFeeWithPremiumArgs, SwapOpsV2, Transaction, TransactionEnum, + ValidateDexFeeArgs}; +use common::{block_on, now_sec_u32, DEX_FEE_ADDR_RAW_PUBKEY}; +use script::{Builder, Opcode}; + +#[test] +fn send_and_refund_dex_fee() { + let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + + let time_lock = now_sec_u32() - 1000; + let secret_hash = &[0; 20]; + let other_pub = coin.my_public_key().unwrap(); + + let send_args = SendDexFeeWithPremiumArgs { + time_lock, + secret_hash, + other_pub, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + swap_unique_data: &[], + }; + let dex_fee_tx = block_on(coin.send_dex_fee_with_premium(send_args)).unwrap(); + println!("{:02x}", dex_fee_tx.tx_hash()); + let dex_fee_utxo_tx = match dex_fee_tx { + TransactionEnum::UtxoTx(tx) => tx, + unexpected => panic!("Unexpected tx {:?}", unexpected), + }; + // tx must have 3 outputs: actual payment, OP_RETURN containing the secret hash and change + assert_eq!(3, dex_fee_utxo_tx.outputs.len()); + + // dex_fee_amount + premium_amount + let expected_amount = 11000000u64; + assert_eq!(expected_amount, dex_fee_utxo_tx.outputs[0].value); + + let expected_op_return = Builder::default() + .push_opcode(Opcode::OP_RETURN) + .push_data(&[0; 20]) + .into_bytes(); + assert_eq!(expected_op_return, dex_fee_utxo_tx.outputs[1].script_pubkey); + + let dex_fee_bytes = dex_fee_utxo_tx.tx_hex(); + + let validate_args = ValidateDexFeeArgs { + dex_fee_tx: &dex_fee_bytes, + time_lock, + secret_hash, + other_pub, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + swap_unique_data: &[], + }; + block_on(coin.validate_dex_fee_with_premium(validate_args)).unwrap(); + + let refund_args = RefundPaymentArgs { + payment_tx: &dex_fee_bytes, + time_lock, + other_pubkey: coin.my_public_key().unwrap(), + secret_hash: &[0; 20], + swap_unique_data: &[], + swap_contract_address: &None, + watcher_reward: false, + }; + + let refund_tx = block_on(coin.refund_dex_fee_with_premium(refund_args)).unwrap(); + println!("{:02x}", refund_tx.tx_hash()); +} + +#[test] +fn send_and_spend_dex_fee() { + let (_, taker_coin, _) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + let (_, maker_coin, _) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + + let time_lock = now_sec_u32() - 1000; + let secret = [1; 32]; + let secret_hash = dhash160(&secret); + let send_args = SendDexFeeWithPremiumArgs { + time_lock, + secret_hash: secret_hash.as_slice(), + other_pub: maker_coin.my_public_key().unwrap(), + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + swap_unique_data: &[], + }; + let dex_fee_tx = block_on(taker_coin.send_dex_fee_with_premium(send_args)).unwrap(); + println!("dex_fee_tx hash {:02x}", dex_fee_tx.tx_hash()); + let dex_fee_utxo_tx = match dex_fee_tx { + TransactionEnum::UtxoTx(tx) => tx, + unexpected => panic!("Unexpected tx {:?}", unexpected), + }; + + let dex_fee_bytes = dex_fee_utxo_tx.tx_hex(); + let validate_args = ValidateDexFeeArgs { + dex_fee_tx: &dex_fee_bytes, + time_lock, + secret_hash: secret_hash.as_slice(), + other_pub: taker_coin.my_public_key().unwrap(), + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + swap_unique_data: &[], + }; + block_on(maker_coin.validate_dex_fee_with_premium(validate_args)).unwrap(); + + let gen_preimage_args = GenDexFeeSpendArgs { + dex_fee_tx: &dex_fee_utxo_tx.tx_hex(), + time_lock, + secret_hash: secret_hash.as_slice(), + maker_pub: maker_coin.my_public_key().unwrap(), + taker_pub: taker_coin.my_public_key().unwrap(), + dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + }; + let preimage_with_taker_sig = + block_on(taker_coin.gen_and_sign_dex_fee_spend_preimage(&gen_preimage_args, &[])).unwrap(); + + block_on(maker_coin.validate_dex_fee_spend_preimage(&gen_preimage_args, &preimage_with_taker_sig)).unwrap(); + + let dex_fee_spend = block_on(maker_coin.sign_and_broadcast_dex_fee_spend( + &preimage_with_taker_sig, + &gen_preimage_args, + &secret, + &[], + )) + .unwrap(); + println!("dex_fee_spend hash {:02x}", dex_fee_spend.tx_hash()); +} diff --git a/mm2src/mm2_net/src/wasm_ws.rs b/mm2src/mm2_net/src/wasm_ws.rs index bf8008de54..fefdfa74cf 100644 --- a/mm2src/mm2_net/src/wasm_ws.rs +++ b/mm2src/mm2_net/src/wasm_ws.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use common::executor::SpawnFuture; use common::log::{debug, error}; -use common::state_machine::{LastState, State, StateExt, StateMachine, StateResult, TransitionFrom}; +use common::state_machine::{LastState, State, StateExt, StateMachineTrait, StateResult, TransitionFrom}; use common::stringify_js_error; use futures::channel::mpsc::{self, SendError, TrySendError}; use futures::channel::oneshot; @@ -206,7 +206,7 @@ fn spawn_ws_transport( let user_shutdown = into_one_shutdown(incoming_shutdown, outgoing_shutdown); let state_event_rx = StateEventListener::new(outgoing_rx, ws_transport_rx, user_shutdown); - let ws_ctx = WsContext { + let mut state_machine = WsStateMachine { idx, ws, event_tx: incoming_tx, @@ -214,8 +214,7 @@ fn spawn_ws_transport( }; let fut = async move { - let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(ws_ctx); - state_machine.run(ConnectingState).await; + state_machine.run(Box::new(ConnectingState)).await; }; spawner.spawn(fut); @@ -367,7 +366,7 @@ impl Drop for WebSocketImpl { } } -struct WsContext { +struct WsStateMachine { idx: ConnIdx, ws: WebSocketImpl, /// The sender is used to send the transport events outside (to the userspace). @@ -376,7 +375,11 @@ struct WsContext { state_event_rx: StateEventListener, } -impl WsContext { +impl StateMachineTrait for WsStateMachine { + type Result = (); +} + +impl WsStateMachine { /// Send the `event` to the corresponding `WebSocketReceiver` instance. fn notify_listener(&mut self, event: WebSocketEvent) { if !self.event_tx.is_closed() { @@ -406,10 +409,10 @@ impl WsContext { } } -/// `WsContext` is not thread-safety `Send` because [`WebSocket::ws`] is not `Send` by default. -/// Although wasm is currently single-threaded, we can implement the `Send` trait for `WsContext`, +/// `WsStateMachine` is not thread-safety `Send` because [`WebSocket::ws`] is not `Send` by default. +/// Although wasm is currently single-threaded, we can implement the `Send` trait for `WsStateMachine`, /// but it won't be safe when wasm becomes multi-threaded. -unsafe impl Send for WsContext {} +unsafe impl Send for WsStateMachine {} struct StateEventListener { rx: Box + Unpin + Send>, @@ -488,10 +491,9 @@ impl TransitionFrom for ClosedState {} #[async_trait] impl LastState for ClosedState { - type Ctx = WsContext; - type Result = (); + type StateMachine = WsStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> Self::Result { + async fn on_changed(self: Box, ctx: &mut WsStateMachine) -> () { debug!("WebScoket idx={} => ClosedState", ctx.idx); // Notify the listener that the connection has been closed to prevent new outgoing messages. ctx.notify_listener(WebSocketEvent::Closed { @@ -499,16 +501,15 @@ impl LastState for ClosedState { }); // Please note that we don't need to close websocket via `ctx.ws.close_with_code()`. - // It will be closed on [`WsContext::drop`] right after the state machine is finished. + // It will be closed on [`WsStateMachine::drop`] right after the state machine is finished. } } #[async_trait] impl State for ConnectingState { - type Ctx = WsContext; - type Result = (); + type StateMachine = WsStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed(self: Box, ctx: &mut WsStateMachine) -> StateResult { debug!("WebSocket idx={} => ConnectingState", ctx.idx); while let Some(event) = ctx.state_event_rx.receive_one().await { match event { @@ -544,10 +545,9 @@ impl State for ConnectingState { #[async_trait] impl State for OpenState { - type Ctx = WsContext; - type Result = (); + type StateMachine = WsStateMachine; - async fn on_changed(self: Box, ctx: &mut Self::Ctx) -> StateResult { + async fn on_changed(self: Box, ctx: &mut WsStateMachine) -> StateResult { debug!("WebSocket idx={} => OpenState", ctx.idx); // notify the listener about the changed state ctx.notify_listener(WebSocketEvent::Establish);