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