From 0713b5f0cc1bf04c056dd3c7862b00e347ac4b68 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 12 Sep 2023 12:27:09 +0700 Subject: [PATCH 01/30] Removing access by index. --- mm2src/coins/utxo/utxo_common.rs | 66 +++++++++------------ mm2src/mm2_bitcoin/chain/src/transaction.rs | 15 +++++ 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 4f5970afc2..28ced5b575 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1121,9 +1121,7 @@ async fn p2sh_spending_tx_preimage( sequence: u32, outputs: Vec, ) -> Result { - if prev_tx.outputs.is_empty() { - return ERR!("Previous transaction doesn't have any output"); - } + let amount = try_s!(prev_tx.first_output()).value; let lock_time = match lock_time { LocktimeSetting::CalcByHtlcLocktime(lock) => try_s!(coin.p2sh_tx_locktime(lock).await), LocktimeSetting::UseExact(lock) => lock, @@ -1150,7 +1148,7 @@ async fn p2sh_spending_tx_preimage( hash: prev_tx.hash(), index: DEFAULT_SWAP_VOUT as u32, }, - amount: prev_tx.outputs[0].value, + amount, witness: Vec::new(), }], outputs, @@ -1358,7 +1356,9 @@ pub async fn sign_and_broadcast_taker_payment_spend( ); let mut signer: TransactionInputSigner = preimage_tx.clone().into(); - signer.inputs[0].amount = taker_tx.outputs[0].value; + let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); + let payment_output = try_tx_s!(taker_tx.first_output()); + payment_input.amount = payment_output.value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; let miner_fee = try_tx_s!( @@ -1369,7 +1369,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( 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"); + return TX_PLAIN_ERR!("Maker amount is too small to cover miner fee + dust"); } let maker_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()); @@ -1408,7 +1408,8 @@ pub async fn sign_and_broadcast_taker_payment_spend( .push_data(&redeem_script) .into_bytes(); let mut final_tx: UtxoTx = signer.into(); - final_tx.inputs[0].script_sig = script_sig; + let final_tx_input = try_tx_s!(final_tx.inputs.first_mut().ok_or("Final tx doesn't have inputs")); + final_tx_input.script_sig = script_sig; drop_mutability!(final_tx); try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); @@ -1510,9 +1511,8 @@ pub fn send_maker_spends_taker_payment(coin: T, args let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() @@ -1533,16 +1533,16 @@ pub fn send_maker_spends_taker_payment(coin: T, args coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1620,9 +1620,7 @@ pub fn create_maker_payment_spend_preimage( let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(swap_unique_data); @@ -1641,16 +1639,16 @@ pub fn create_maker_payment_spend_preimage( .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1684,9 +1682,7 @@ pub fn create_taker_payment_refund_preimage( try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); @@ -1702,16 +1698,16 @@ pub fn create_taker_payment_refund_preimage( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WatcherPreimage) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1736,9 +1732,7 @@ pub fn send_taker_spends_maker_payment(coin: T, args let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_fus!(prev_transaction.first_output()).value; let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); @@ -1761,16 +1755,16 @@ pub fn send_taker_spends_maker_payment(coin: T, args coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; @@ -1803,9 +1797,7 @@ async fn refund_htlc_payment( try_tx_s!(deserialize(args.payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); - if prev_transaction.outputs.is_empty() { - return try_tx_s!(TX_PLAIN_ERR!("Transaction doesn't have any output")); - } + let payment_value = try_tx_s!(prev_transaction.first_output()).value; let other_public = try_tx_s!(Public::from_slice(args.other_pubkey)); let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); @@ -1825,16 +1817,16 @@ async fn refund_htlc_payment( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await ); - if fee >= prev_transaction.outputs[0].value { + if fee >= payment_value { return TX_PLAIN_ERR!( "HTLC spend fee {} is greater than transaction output {}", fee, - prev_transaction.outputs[0].value + payment_value ); } let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); let output = TransactionOutput { - value: prev_transaction.outputs[0].value - fee, + value: payment_value - fee, script_pubkey, }; diff --git a/mm2src/mm2_bitcoin/chain/src/transaction.rs b/mm2src/mm2_bitcoin/chain/src/transaction.rs index ca75eaa1ad..0490447d29 100644 --- a/mm2src/mm2_bitcoin/chain/src/transaction.rs +++ b/mm2src/mm2_bitcoin/chain/src/transaction.rs @@ -14,6 +14,7 @@ use hash::{CipherText, EncCipherText, OutCipherText, ZkProof, ZkProofSapling, H2 use hex::FromHex; use ser::{deserialize, serialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; use ser::{CompactInteger, Deserializable, Error, Reader, Serializable, Stream}; +use std::fmt::Formatter; use std::io; use std::io::Read; @@ -257,6 +258,14 @@ impl Default for TxHashAlgo { fn default() -> Self { TxHashAlgo::DSHA256 } } +/// Represents the error returned when transaction has no outputs +#[derive(Debug)] +pub struct TxHasNoOutputs {} + +impl std::fmt::Display for TxHasNoOutputs { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.write_str("Tx has no outputs") } +} + impl Transaction { pub fn hash(&self) -> H256 { let serialized = &serialize(self); @@ -318,6 +327,12 @@ impl Transaction { } result } + + /// Returns reference to first output of the transaction or error if outputs are empty + #[inline] + pub fn first_output(&self) -> Result<&TransactionOutput, TxHasNoOutputs> { + self.outputs.first().ok_or(TxHasNoOutputs {}) + } } impl Serializable for TransactionInput { From e638c5be975cea4f73891a4b0a43ada4d561c616 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Wed, 13 Sep 2023 12:35:50 +0700 Subject: [PATCH 02/30] Removing access by index. --- mm2src/coins/utxo/utxo_common.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 28ced5b575..aa048e2dad 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -2595,10 +2595,16 @@ pub fn wait_for_output_spend( let tx_hash_algo = coin.tx_hash_algo; let fut = async move { loop { + let script_pubkey = &try_tx_s!(tx + .outputs + .get(output_index) + .ok_or(ERRL!("No output with index {}", output_index))) + .script_pubkey; + match client .find_output_spend( tx.hash(), - &tx.outputs[output_index].script_pubkey, + script_pubkey, output_index, BlockHashOrHeight::Height(from_block as i64), ) @@ -4111,10 +4117,17 @@ async fn search_for_swap_output_spend( } let script = payment_script(time_lock, secret_hash, first_pub, second_pub); let expected_script_pubkey = Builder::build_p2sh(&dhash160(&script).into()).to_bytes(); - if tx.outputs[0].script_pubkey != expected_script_pubkey { + let script_pubkey = &tx + .outputs + .get(output_index) + .ok_or(ERRL!("No output with index {}", output_index))? + .script_pubkey; + + if *script_pubkey != expected_script_pubkey { return ERR!( - "Transaction {:?} output 0 script_pubkey doesn't match expected {:?}", + "Transaction {:?} output {} script_pubkey doesn't match expected {:?}", tx, + output_index, expected_script_pubkey ); } @@ -4123,7 +4136,7 @@ async fn search_for_swap_output_spend( coin.rpc_client .find_output_spend( tx.hash(), - &tx.outputs[output_index].script_pubkey, + script_pubkey, output_index, BlockHashOrHeight::Height(search_from_block as i64) ) @@ -4607,10 +4620,11 @@ where script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&redeem_script))).into(), }; - if dex_fee_tx.outputs[0] != expected_output { + if dex_fee_tx.outputs.get(0) != Some(&expected_output) { return MmError::err(ValidateTakerPaymentError::InvalidDestinationOrAmount(format!( "Expected {:?}, got {:?}", - expected_output, dex_fee_tx.outputs[0] + expected_output, + dex_fee_tx.outputs.get(0) ))); } From 72e25a46b57fb207a4724e84eb8aa6234083cf97 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Thu, 14 Sep 2023 13:09:00 +0700 Subject: [PATCH 03/30] WIP. SQLite storage for swaps. --- mm2src/mm2_main/src/database.rs | 5 ++ mm2src/mm2_main/src/database/my_swaps.rs | 65 ++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/mm2src/mm2_main/src/database.rs b/mm2src/mm2_main/src/database.rs index 47b1241cb3..8ce87db0e1 100644 --- a/mm2src/mm2_main/src/database.rs +++ b/mm2src/mm2_main/src/database.rs @@ -101,6 +101,10 @@ fn migration_8() -> Vec<(&'static str, Vec)> { db_common::sqlite::execute_batch(stats_swaps::ADD_MAKER_TAKER_PUBKEYS) } +fn migration_9() -> Vec<(&'static str, Vec)> { + db_common::sqlite::execute_batch(my_swaps::TRADING_PROTO_UPGRADE_MIGRATION) +} + async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option)>> { match current_migration { 1 => Some(migration_1(ctx).await), @@ -111,6 +115,7 @@ async fn statements_for_migration(ctx: &MmArc, current_migration: i64) -> Option 6 => Some(migration_6()), 7 => Some(migration_7()), 8 => Some(migration_8()), + 9 => Some(migration_9()), _ => None, } } diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index ab5a08b84b..453a45d5d4 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -27,8 +27,73 @@ macro_rules! CREATE_MY_SWAPS_TABLE { );" }; } + +/// Adds new fields required for trading protocol upgrade implementation (swap v2) +pub const TRADING_PROTO_UPGRADE_MIGRATION: &[&str] = &[ + "ALTER TABLE my_swaps ADD COLUMN is_finished BOOLEAN NOT NULL DEFAULT 0;", + "ALTER TABLE my_swaps ADD COLUMN events_json TEXT NOT NULL DEFAULT '[]';", + "ALTER TABLE my_swaps ADD COLUMN swap_type INTEGER;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN maker_volume TEXT;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN taker_volume TEXT;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN premium TEXT;", + // Storing rational numbers as text to maintain precision + "ALTER TABLE my_swaps ADD COLUMN dex_fee TEXT;", + "ALTER TABLE my_swaps ADD COLUMN secret BLOB;", + "ALTER TABLE my_swaps ADD COLUMN secret_hash BLOB;", + "ALTER TABLE my_swaps ADD COLUMN secret_hash_algo INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN p2p_privkey BLOB;", + "ALTER TABLE my_swaps ADD COLUMN lock_duration INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN maker_coin_confs INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN maker_coin_nota BOOLEAN;", + "ALTER TABLE my_swaps ADD COLUMN taker_coin_confs INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN taker_coin_nota BOOLEAN;", +]; + const INSERT_MY_SWAP: &str = "INSERT INTO my_swaps (my_coin, other_coin, uuid, started_at) VALUES (?1, ?2, ?3, ?4)"; +const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( + my_coin, + other_coin, + uuid, + started_at, + swap_type, + maker_volume, + taker_volume, + premium, + dex_fee, + secret, + secret_hash, + secret_hash_algo, + p2p_privkey, + lock_duration, + maker_coin_confs, + maker_coin_nota, + taker_coin_confs, + taker_coin_nota +) VALUES ( + ?1, + ?2, + ?3, + ?4, + ?5, + ?6, + ?7, + ?8, + ?9, + ?10, + ?11, + ?12, + ?13, + ?14, + ?15, + ?16, + ?17, + ?18 +);"#; + pub fn insert_new_swap(ctx: &MmArc, my_coin: &str, other_coin: &str, uuid: &str, started_at: &str) -> SqlResult<()> { debug!("Inserting new swap {} to the SQLite database", uuid); let conn = ctx.sqlite_connection(); From 530725f044672fe84b756db4884d493b431693a6 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Thu, 14 Sep 2023 15:28:36 +0700 Subject: [PATCH 04/30] WIP. SQLite storage for swaps. --- mm2src/mm2_main/src/database/my_swaps.rs | 13 +++++++--- mm2src/mm2_main/src/lp_ordermatch.rs | 8 +++--- mm2src/mm2_main/src/lp_swap.rs | 5 ++-- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 26 +++++++++++++++++++ .../tests/docker_tests/swap_proto_v2_tests.rs | 2 ++ mm2src/mm2_number/src/mm_number.rs | 21 ++++++++++++++- 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index 453a45d5d4..58a43cbb5d 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -54,6 +54,13 @@ pub const TRADING_PROTO_UPGRADE_MIGRATION: &[&str] = &[ const INSERT_MY_SWAP: &str = "INSERT INTO my_swaps (my_coin, other_coin, uuid, started_at) VALUES (?1, ?2, ?3, ?4)"; +pub fn insert_new_swap(ctx: &MmArc, my_coin: &str, other_coin: &str, uuid: &str, started_at: &str) -> SqlResult<()> { + debug!("Inserting new swap {} to the SQLite database", uuid); + let conn = ctx.sqlite_connection(); + let params = [my_coin, other_coin, uuid, started_at]; + conn.execute(INSERT_MY_SWAP, params).map(|_| ()) +} + const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( my_coin, other_coin, @@ -94,11 +101,9 @@ const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( ?18 );"#; -pub fn insert_new_swap(ctx: &MmArc, my_coin: &str, other_coin: &str, uuid: &str, started_at: &str) -> SqlResult<()> { - debug!("Inserting new swap {} to the SQLite database", uuid); +pub fn insert_new_swap_v2(ctx: &MmArc, params: &[&dyn ToSql]) -> SqlResult<()> { let conn = ctx.sqlite_connection(); - let params = [my_coin, other_coin, uuid, started_at]; - conn.execute(INSERT_MY_SWAP, params).map(|_| ()) + conn.execute(INSERT_MY_SWAP_V2, params).map(|_| ()) } /// Returns SQL statements to initially fill my_swaps table using existing DB with JSON files diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index b43f8838ea..5fc23408df 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2955,9 +2955,6 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO ); let now = now_sec(); - if let Err(e) = insert_new_swap_to_db(ctx.clone(), maker_coin.ticker(), taker_coin.ticker(), uuid, now).await { - error!("Error {} on new swap insertion", e); - } let secret = match MakerSwap::generate_secret() { Ok(s) => s.into(), @@ -2997,6 +2994,11 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO _ => todo!("implement fallback to the old protocol here"), } } else { + if let Err(e) = + insert_new_swap_to_db(ctx.clone(), maker_coin.ticker(), taker_coin.ticker(), uuid, now).await + { + error!("Error {} on new swap insertion", e); + } let maker_swap = MakerSwap::new( ctx.clone(), alice, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index ba69c3d199..1be7302641 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1415,11 +1415,12 @@ pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result> } /// Algorithm used to hash swap secret. +#[derive(Clone, Copy)] pub enum SecretHashAlgo { /// ripemd160(sha256(secret)) - DHASH160, + DHASH160 = 1, /// sha256(secret) - SHA256, + SHA256 = 2, } impl Default for SecretHashAlgo { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index eb601df2e2..469c8d796b 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -1,4 +1,5 @@ use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; +use crate::mm2::database::my_swaps::insert_new_swap_v2; 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, @@ -20,6 +21,7 @@ use std::marker::PhantomData; use uuid::Uuid; // This is needed to have Debug on messages +use db_common::sqlite::rusqlite::params; #[allow(unused_imports)] use prost::Message; /// Represents events produced by maker swap states. @@ -200,6 +202,30 @@ impl; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + { + let sql_params = params![ + state_machine.maker_coin.ticker(), + state_machine.taker_coin.ticker(), + state_machine.uuid.to_string(), + state_machine.started_at, + 1, + state_machine.maker_volume.to_fraction_string(), + state_machine.taker_volume.to_fraction_string(), + state_machine.taker_premium.to_fraction_string(), + state_machine.dex_fee_amount.to_fraction_string(), + state_machine.secret.take(), + state_machine.secret_hash(), + state_machine.secret_hash_algo as u8, + state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), + state_machine.lock_duration, + state_machine.conf_settings.maker_coin_confs, + state_machine.conf_settings.maker_coin_nota, + state_machine.conf_settings.taker_coin_confs, + state_machine.conf_settings.taker_coin_nota + ]; + insert_new_swap_v2(&state_machine.ctx, sql_params).unwrap(); + } + 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()); 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 331b5b918b..4129957a71 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 @@ -144,6 +144,7 @@ fn test_v2_swap_utxo_utxo() { 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); + log!("Bob log path: {}", mm_bob.log_path.display()); let alice_conf = Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob @@ -151,6 +152,7 @@ fn test_v2_swap_utxo_utxo() { .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!("Alice log path: {}", mm_alice.log_path.display()); log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN", &[], None))); log!("{:?}", block_on(enable_native(&mm_bob, "MYCOIN1", &[], None))); diff --git a/mm2src/mm2_number/src/mm_number.rs b/mm2src/mm2_number/src/mm_number.rs index 3934f02391..6e3c5896a6 100644 --- a/mm2src/mm2_number/src/mm_number.rs +++ b/mm2src/mm2_number/src/mm_number.rs @@ -3,7 +3,7 @@ use crate::{from_dec_to_ratio, from_ratio_to_dec}; use bigdecimal::BigDecimal; use core::ops::{Add, AddAssign, Div, Mul, Sub}; use num_bigint::BigInt; -use num_rational::BigRational; +use num_rational::{BigRational, ParseRatioError}; use num_traits::CheckedDiv; use num_traits::Zero; use serde::Serialize; @@ -228,11 +228,20 @@ impl MmNumber { /// Get BigDecimal representation pub fn to_decimal(&self) -> BigDecimal { from_ratio_to_dec(&self.0) } + /// Returns the numerator of the internal BigRational pub fn numer(&self) -> &BigInt { self.0.numer() } + /// Returns the denominator of the internal BigRational pub fn denom(&self) -> &BigInt { self.0.denom() } + /// Returns whether the number is zero pub fn is_zero(&self) -> bool { self.0.is_zero() } + + /// Returns the stringified representation of a number in a format like "1/3". + pub fn to_fraction_string(&self) -> String { self.0.to_string() } + + /// Attempts to parse a number from string, expects input to have fraction format like "1/3". + pub fn from_fraction_string(input: &str) -> Result { Ok(MmNumber(input.parse()?)) } } impl From for MmNumber { @@ -399,4 +408,14 @@ mod tests { assert_eq!(actual.num, expected); } + + #[test] + fn test_from_to_fraction_string() { + let input = "1000/999"; + let mm_num = MmNumber::from_fraction_string(input).unwrap(); + assert_eq!(*mm_num.numer(), BigInt::from(1000)); + assert_eq!(*mm_num.denom(), BigInt::from(999)); + + assert_eq!(input, mm_num.to_fraction_string()); + } } From 3377afe41b8796bb5fd7d024dd5c9d42ceb66c1a Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Fri, 15 Sep 2023 13:55:01 +0700 Subject: [PATCH 05/30] WIP. SQLite storage. --- mm2src/mm2_main/src/database/my_swaps.rs | 10 +++++- mm2src/mm2_main/src/lp_ordermatch.rs | 25 +++++++++------ mm2src/mm2_main/src/lp_swap.rs | 32 +++++++++++++------ mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 4 +-- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 32 +++++++++++++++++-- .../tests/docker_tests/swap_proto_v2_tests.rs | 7 ++-- 6 files changed, 84 insertions(+), 26 deletions(-) diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index 58a43cbb5d..b0ec55339b 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -32,7 +32,7 @@ macro_rules! CREATE_MY_SWAPS_TABLE { pub const TRADING_PROTO_UPGRADE_MIGRATION: &[&str] = &[ "ALTER TABLE my_swaps ADD COLUMN is_finished BOOLEAN NOT NULL DEFAULT 0;", "ALTER TABLE my_swaps ADD COLUMN events_json TEXT NOT NULL DEFAULT '[]';", - "ALTER TABLE my_swaps ADD COLUMN swap_type INTEGER;", + "ALTER TABLE my_swaps ADD COLUMN swap_type INTEGER NOT NULL DEFAULT 0;", // Storing rational numbers as text to maintain precision "ALTER TABLE my_swaps ADD COLUMN maker_volume TEXT;", // Storing rational numbers as text to maintain precision @@ -224,3 +224,11 @@ pub fn select_uuids_by_my_swaps_filter( skipped, }) } + +/// Queries swap type by uuid +pub fn get_swap_type(conn: &Connection, uuid: &str) -> SqlResult { + const SELECT_SWAP_TYPE_BY_UUID: &str = "SELECT swap_type FROM my_swaps WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(SELECT_SWAP_TYPE_BY_UUID)?; + let swap_type = stmt.query_row(&[(":uuid", uuid)], |row| row.get(0))?; + Ok(swap_type) +} diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 5fc23408df..ea1692a22b 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -74,12 +74,12 @@ use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_p 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, dex_fee_amount_from_taker_coin, get_max_maker_vol, - insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, + check_other_coin_balance_for_swap, detect_secret_hash_algo, 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}; + CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, + TakerSwap}; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; @@ -2965,6 +2965,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }; if ctx.use_trading_proto_v2() { + let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { let mut maker_swap_state_machine = MakerSwapStateMachine { @@ -2982,7 +2983,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO p2p_topic: swap_v2_topic(&uuid), uuid, p2p_keypair: maker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - secret_hash_algo: SecretHashAlgo::DHASH160, + secret_hash_algo, lock_duration: lock_time, }; #[allow(clippy::box_default)] @@ -3100,17 +3101,14 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat ); let now = now_sec(); - 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); - } - if ctx.use_trading_proto_v2() { + let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { let mut taker_swap_state_machine = TakerSwapStateMachine { ctx, storage: DummyTakerSwapStorage::default(), - started_at: now_sec(), + started_at: now, lock_duration: locktime, maker_coin: m.clone(), maker_volume: maker_amount, @@ -3118,6 +3116,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), taker_volume: taker_amount, taker_premium: Default::default(), + secret_hash_algo, conf_settings: my_conf_settings, p2p_topic: swap_v2_topic(&uuid), uuid, @@ -3132,6 +3131,12 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat _ => todo!("implement fallback to the old protocol here"), } } else { + 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, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 1be7302641..6f996e61a3 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -111,6 +111,7 @@ mod swap_v2_pb; #[path = "lp_swap/swap_wasm_db.rs"] mod swap_wasm_db; +use crate::mm2::database::my_swaps::get_swap_type; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use crypto::CryptoCtx; use keys::{KeyPair, SECP_SIGN, SECP_VERIFY}; @@ -140,6 +141,10 @@ pub const SWAP_V2_PREFIX: TopicPrefix = "swapv2"; pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; +const LEGACY_SWAP_TYPE: u8 = 0; +const MAKER_SWAP_V2_TYPE: u8 = 1; +const TAKER_SWAP_V2_TYPE: u8 = 2; + const NEGOTIATE_SEND_INTERVAL: f64 = 30.; /// If a certain P2P message is not received, swap will be aborted after this time expires. @@ -1012,15 +1017,22 @@ impl From for MySwapStatusResponse { /// Returns the status of swap performed on `my` node pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); - let status = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { - Ok(Some(status)) => status, - Ok(None) => return Err("swap data is not found".to_owned()), - Err(e) => return ERR!("{}", e), - }; + let swap_type = try_s!(get_swap_type(&ctx.sqlite_connection(), &uuid.to_string())); - let res_js = json!({ "result": MySwapStatusResponse::from(status) }); - let res = try_s!(json::to_vec(&res_js)); - Ok(try_s!(Response::builder().body(res))) + match swap_type { + LEGACY_SWAP_TYPE => { + let status = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { + Ok(Some(status)) => status, + Ok(None) => return Err("swap data is not found".to_owned()), + Err(e) => return ERR!("{}", e), + }; + + let res_js = json!({ "result": MySwapStatusResponse::from(status) }); + let res = try_s!(json::to_vec(&res_js)); + Ok(try_s!(Response::builder().body(res))) + }, + unsupported_type => ERR!("Got unsupported swap type from DB: {}", unsupported_type), + } } #[cfg(target_arch = "wasm32")] @@ -1437,8 +1449,9 @@ impl SecretHashAlgo { } // Todo: Maybe add a secret_hash_algo method to the SwapOps trait instead +/// Selects secret hash algorithm depending on types of coins being swapped #[cfg(not(target_arch = "wasm32"))] -fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { +pub fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { match (maker_coin, taker_coin) { (MmCoinEnum::Tendermint(_) | MmCoinEnum::TendermintToken(_) | MmCoinEnum::LightningCoin(_), _) => { SecretHashAlgo::SHA256 @@ -1449,6 +1462,7 @@ fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> } } +/// Selects secret hash algorithm depending on types of coins being swapped #[cfg(target_arch = "wasm32")] fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) -> SecretHashAlgo { match (maker_coin, taker_coin) { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 469c8d796b..a1ec3d627c 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -3,7 +3,7 @@ use crate::mm2::database::my_swaps::insert_new_swap_v2; 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}; + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, MAKER_SWAP_V2_TYPE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, SendPaymentArgs, @@ -208,7 +208,7 @@ impl { pub dex_fee: MmNumber, /// Premium amount, which might be paid to maker as additional reward. pub taker_premium: MmNumber, + /// Algorithm used to hash the swap secret. + pub secret_hash_algo: SecretHashAlgo, /// Swap transactions' confirmations settings. pub conf_settings: SwapConfirmationsSettings, /// UUID of the swap. @@ -191,6 +195,30 @@ impl; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + { + let sql_params = params![ + state_machine.taker_coin.ticker(), + state_machine.maker_coin.ticker(), + state_machine.uuid.to_string(), + state_machine.started_at, + TAKER_SWAP_V2_TYPE, + state_machine.maker_volume.to_fraction_string(), + state_machine.taker_volume.to_fraction_string(), + state_machine.taker_premium.to_fraction_string(), + state_machine.dex_fee.to_fraction_string(), + [], // secret is unknown at this point + [], // secret hash is unknown at this point + state_machine.secret_hash_algo as u8, + state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), + state_machine.lock_duration, + state_machine.conf_settings.maker_coin_confs, + state_machine.conf_settings.maker_coin_nota, + state_machine.conf_settings.taker_coin_confs, + state_machine.conf_settings.taker_coin_nota + ]; + insert_new_swap_v2(&state_machine.ctx, sql_params).unwrap(); + } + 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()); 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 4129957a71..b8be1990ed 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 @@ -4,8 +4,8 @@ use coins::utxo::UtxoCommonOps; 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 mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, + MarketMakerIt, Mm2TestConf}; use script::{Builder, Opcode}; #[test] @@ -173,5 +173,8 @@ fn test_v2_swap_utxo_utxo() { 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(); + + let maker_swap_status = block_on(my_swap_status(&mm_bob, &uuid)); + println!("{:?}", maker_swap_status); } } From 1c9442b9a804e594d676b81319c96a0e5ad6884b Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 18 Sep 2023 16:54:39 +0700 Subject: [PATCH 06/30] WIP. SQLite storage for upgraded swaps. --- mm2src/mm2_main/src/database/my_swaps.rs | 99 ++++++++++++++++++- mm2src/mm2_main/src/lp_ordermatch.rs | 4 +- mm2src/mm2_main/src/lp_swap.rs | 11 ++- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 43 +++++--- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 41 +++++--- .../tests/docker_tests/swap_proto_v2_tests.rs | 3 + 6 files changed, 171 insertions(+), 30 deletions(-) diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index b0ec55339b..d613645d73 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -5,7 +5,7 @@ use crate::mm2::lp_swap::{MyRecentSwapsUuids, MySwapsFilter, SavedSwap, SavedSwa use common::log::debug; use common::PagingOptions; use db_common::sqlite::offset_by_uuid; -use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, ToSql}; +use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, ToSql}; use db_common::sqlite::sql_builder::SqlBuilder; use mm2_core::mm_ctx::MmArc; use std::convert::TryInto; @@ -232,3 +232,100 @@ pub fn get_swap_type(conn: &Connection, uuid: &str) -> SqlResult { let swap_type = stmt.query_row(&[(":uuid", uuid)], |row| row.get(0))?; Ok(swap_type) } + +/// Queries swap events by uuid +pub fn get_swap_events(conn: &Connection, uuid: &str) -> SqlResult { + const SELECT_SWAP_EVENTS_BY_UUID: &str = "SELECT events_json FROM my_swaps WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(SELECT_SWAP_EVENTS_BY_UUID)?; + let swap_type = stmt.query_row(&[(":uuid", uuid)], |row| row.get(0))?; + Ok(swap_type) +} + +/// Updates swap events by uuid +pub fn update_swap_events(conn: &Connection, uuid: &str, events_json: &str) -> SqlResult<()> { + const UPDATE_SWAP_EVENTS_BY_UUID: &str = "UPDATE my_swaps SET events_json = :events_json WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(UPDATE_SWAP_EVENTS_BY_UUID)?; + stmt.execute(&[(":uuid", uuid), (":events_json", events_json)]) + .map(|_| ()) +} + +pub fn set_swap_is_finished(conn: &Connection, uuid: &str) -> SqlResult<()> { + const UPDATE_SWAP_IS_FINISHED_BY_UUID: &str = "UPDATE my_swaps SET is_finished = 1 WHERE uuid = :uuid;"; + let mut stmt = conn.prepare(UPDATE_SWAP_IS_FINISHED_BY_UUID)?; + stmt.execute(&[(":uuid", uuid)]).map(|_| ()) +} + +const SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID: &str = r#"SELECT + my_coin, + other_coin, + uuid, + started_at, + is_finished, + events_json, + maker_volume, + taker_volume, + premium, + dex_fee, + secret_hash, + secret_hash_algo, + lock_duration, + maker_coin_confs, + maker_coin_nota, + taker_coin_confs, + taker_coin_nota +FROM my_swaps +WHERE uuid = :uuid; +"#; + +/// Represents data of the swap used for RPC, omits fields that should be kept in secret +#[derive(Debug, Serialize)] +pub struct MySwapForRpc { + my_coin: String, + other_coin: String, + uuid: String, + started_at: i64, + is_finished: bool, + events_json: String, + maker_volume: String, + taker_volume: String, + premium: String, + dex_fee: String, + secret_hash: Vec, + secret_hash_algo: i64, + lock_duration: i64, + maker_coin_confs: i64, + maker_coin_nota: bool, + taker_coin_confs: i64, + taker_coin_nota: bool, +} + +impl MySwapForRpc { + fn from_row(row: &Row) -> SqlResult { + Ok(Self { + my_coin: row.get(0)?, + other_coin: row.get(1)?, + uuid: row.get(2)?, + started_at: row.get(3)?, + is_finished: row.get(4)?, + events_json: row.get(5)?, + maker_volume: row.get(6)?, + taker_volume: row.get(7)?, + premium: row.get(8)?, + dex_fee: row.get(9)?, + secret_hash: row.get(10)?, + secret_hash_algo: row.get(11)?, + lock_duration: row.get(12)?, + maker_coin_confs: row.get(13)?, + maker_coin_nota: row.get(14)?, + taker_coin_confs: row.get(15)?, + taker_coin_nota: row.get(16)?, + }) + } +} + +/// Queries `MySwapForRpc` by uuid +pub fn get_swap_data_for_rpc(conn: &Connection, uuid: &str) -> SqlResult { + let mut stmt = conn.prepare(SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID)?; + let swap_data = stmt.query_row(&[(":uuid", uuid)], MySwapForRpc::from_row)?; + Ok(swap_data) +} diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index ea1692a22b..69cf26f3a9 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2969,8 +2969,8 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { let mut maker_swap_state_machine = MakerSwapStateMachine { + storage: DummyMakerSwapStorage::new(ctx.clone()), ctx, - storage: DummyMakerSwapStorage::default(), started_at: now_sec(), maker_coin: m.clone(), maker_volume: maker_amount, @@ -3106,8 +3106,8 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { let mut taker_swap_state_machine = TakerSwapStateMachine { + storage: DummyTakerSwapStorage::new(ctx.clone()), ctx, - storage: DummyTakerSwapStorage::default(), started_at: now, lock_duration: locktime, maker_coin: m.clone(), diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 6f996e61a3..bd469874fb 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -111,7 +111,7 @@ mod swap_v2_pb; #[path = "lp_swap/swap_wasm_db.rs"] mod swap_wasm_db; -use crate::mm2::database::my_swaps::get_swap_type; +use crate::mm2::database::my_swaps::{get_swap_data_for_rpc, get_swap_type}; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use crypto::CryptoCtx; use keys::{KeyPair, SECP_SIGN, SECP_VERIFY}; @@ -1017,7 +1017,8 @@ impl From for MySwapStatusResponse { /// Returns the status of swap performed on `my` node pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); - let swap_type = try_s!(get_swap_type(&ctx.sqlite_connection(), &uuid.to_string())); + let uuid_str = uuid.to_string(); + let swap_type = try_s!(get_swap_type(&ctx.sqlite_connection(), &uuid_str)); match swap_type { LEGACY_SWAP_TYPE => { @@ -1031,6 +1032,12 @@ pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, let res = try_s!(json::to_vec(&res_js)); Ok(try_s!(Response::builder().body(res))) }, + MAKER_SWAP_V2_TYPE | TAKER_SWAP_V2_TYPE => { + let swap_data = try_s!(get_swap_data_for_rpc(&ctx.sqlite_connection(), &uuid_str)); + let res_js = json!({ "result": swap_data }); + let res = try_s!(json::to_vec(&res_js)); + Ok(try_s!(Response::builder().body(res))) + }, unsupported_type => ERR!("Got unsupported swap type from DB: {}", unsupported_type), } } diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index a1ec3d627c..0fff27fc08 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -1,5 +1,5 @@ use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; -use crate::mm2::database::my_swaps::insert_new_swap_v2; +use crate::mm2::database::my_swaps::{get_swap_events, insert_new_swap_v2, set_swap_is_finished, update_swap_events}; 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, @@ -10,22 +10,22 @@ use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, Marke SwapOpsV2, TxPreimageWithSig}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; +use db_common::sqlite::rusqlite::params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; 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 -use db_common::sqlite::rusqlite::params; #[allow(unused_imports)] use prost::Message; /// Represents events produced by maker swap states. -#[derive(Debug, PartialEq)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] pub enum MakerSwapEvent { /// Swap has been successfully initialized. Initialized { @@ -74,30 +74,47 @@ pub enum MakerSwapEvent { /// Represents errors that can be produced by [`MakerSwapStateMachine`] run. #[derive(Debug, Display)] -pub enum MakerSwapStateMachineError {} +pub enum MakerSwapStateMachineError { + StorageError(String), + SerdeError(String), +} /// Dummy storage for maker swap events (used temporary). -#[derive(Default)] pub struct DummyMakerSwapStorage { - events: HashMap>, + ctx: MmArc, +} + +impl DummyMakerSwapStorage { + pub fn new(ctx: MmArc) -> Self { DummyMakerSwapStorage { ctx } } } #[async_trait] impl StateMachineStorage for DummyMakerSwapStorage { type MachineId = Uuid; type Event = MakerSwapEvent; - type Error = MakerSwapStateMachineError; + type Error = MmError; 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); + let id_str = id.to_string(); + let events_json = get_swap_events(&self.ctx.sqlite_connection(), &id_str) + .map_to_mm(|e| MakerSwapStateMachineError::StorageError(e.to_string()))?; + let mut events: Vec = + serde_json::from_str(&events_json).map_to_mm(|e| MakerSwapStateMachineError::SerdeError(e.to_string()))?; + events.push(event); + drop_mutability!(events); + let serialized_events = + serde_json::to_string(&events).map_to_mm(|e| MakerSwapStateMachineError::SerdeError(e.to_string()))?; + update_swap_events(&self.ctx.sqlite_connection(), &id_str, &serialized_events) + .map_to_mm(|e| MakerSwapStateMachineError::StorageError(e.to_string()))?; Ok(()) } - async fn get_unfinished(&self) -> Result, Self::Error> { - Ok(self.events.keys().copied().collect()) - } + async fn get_unfinished(&self) -> Result, Self::Error> { todo!() } - async fn mark_finished(&mut self, _id: Self::MachineId) -> Result<(), Self::Error> { Ok(()) } + async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { + set_swap_is_finished(&self.ctx.sqlite_connection(), &id.to_string()) + .map_to_mm(|e| MakerSwapStateMachineError::StorageError(e.to_string())) + } } /// Represents the state machine for maker's side of the Trading Protocol Upgrade swap (v2). diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 6b0395a76a..61cd0c0a31 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -1,5 +1,5 @@ use super::{NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; -use crate::mm2::database::my_swaps::insert_new_swap_v2; +use crate::mm2::database::my_swaps::{get_swap_events, insert_new_swap_v2, set_swap_is_finished, update_swap_events}; 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, SecretHashAlgo, @@ -12,11 +12,11 @@ use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; 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; @@ -24,7 +24,7 @@ use uuid::Uuid; #[allow(unused_imports)] use prost::Message; /// Represents events produced by taker swap states. -#[derive(Debug, PartialEq)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] pub enum TakerSwapEvent { /// Swap has been successfully initialized. Initialized { @@ -83,30 +83,47 @@ pub enum TakerSwapEvent { /// Represents errors that can be produced by [`TakerSwapStateMachine`] run. #[derive(Debug, Display)] -pub enum TakerSwapStateMachineError {} +pub enum TakerSwapStateMachineError { + StorageError(String), + SerdeError(String), +} /// Dummy storage for taker swap events (used temporary). -#[derive(Default)] pub struct DummyTakerSwapStorage { - events: HashMap>, + ctx: MmArc, +} + +impl DummyTakerSwapStorage { + pub fn new(ctx: MmArc) -> Self { DummyTakerSwapStorage { ctx } } } #[async_trait] impl StateMachineStorage for DummyTakerSwapStorage { type MachineId = Uuid; type Event = TakerSwapEvent; - type Error = TakerSwapStateMachineError; + type Error = MmError; 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); + let id_str = id.to_string(); + let events_json = get_swap_events(&self.ctx.sqlite_connection(), &id_str) + .map_to_mm(|e| TakerSwapStateMachineError::StorageError(e.to_string()))?; + let mut events: Vec = + serde_json::from_str(&events_json).map_to_mm(|e| TakerSwapStateMachineError::SerdeError(e.to_string()))?; + events.push(event); + drop_mutability!(events); + let serialized_events = + serde_json::to_string(&events).map_to_mm(|e| TakerSwapStateMachineError::SerdeError(e.to_string()))?; + update_swap_events(&self.ctx.sqlite_connection(), &id_str, &serialized_events) + .map_to_mm(|e| TakerSwapStateMachineError::StorageError(e.to_string()))?; Ok(()) } - async fn get_unfinished(&self) -> Result, Self::Error> { - Ok(self.events.keys().copied().collect()) - } + async fn get_unfinished(&self) -> Result, Self::Error> { todo!() } - async fn mark_finished(&mut self, _id: Self::MachineId) -> Result<(), Self::Error> { Ok(()) } + async fn mark_finished(&mut self, id: Self::MachineId) -> Result<(), Self::Error> { + set_swap_is_finished(&self.ctx.sqlite_connection(), &id.to_string()) + .map_to_mm(|e| TakerSwapStateMachineError::StorageError(e.to_string())) + } } /// Represents the state machine for taker's side of the Trading Protocol Upgrade swap (v2). 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 b8be1990ed..f33807f6ee 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 @@ -176,5 +176,8 @@ fn test_v2_swap_utxo_utxo() { let maker_swap_status = block_on(my_swap_status(&mm_bob, &uuid)); println!("{:?}", maker_swap_status); + + let taker_swap_status = block_on(my_swap_status(&mm_alice, &uuid)); + println!("{:?}", taker_swap_status); } } From ca53aa2083182c763bc83a949014c0bd571b62cb Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 18 Sep 2023 17:52:34 +0700 Subject: [PATCH 07/30] WIP. Additional validation. --- mm2src/coins/utxo/utxo_common.rs | 46 +++++++++----- mm2src/mm2_main/src/lp_swap.rs | 1 + mm2src/mm2_main/src/lp_swap/maker_swap.rs | 4 +- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 64 ++++++++++++++------ mm2src/mm2_main/src/lp_swap/taker_swap.rs | 4 +- 5 files changed, 82 insertions(+), 37 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index aa048e2dad..f6d6b74aaa 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1114,10 +1114,16 @@ enum LocktimeSetting { UseExact(u32), } +enum NTimeSetting { + UseNow, + UseValue(Option), +} + async fn p2sh_spending_tx_preimage( coin: &T, prev_tx: &UtxoTx, lock_time: LocktimeSetting, + set_n_time: NTimeSetting, sequence: u32, outputs: Vec, ) -> Result { @@ -1127,7 +1133,10 @@ async fn p2sh_spending_tx_preimage( LocktimeSetting::UseExact(lock) => lock, }; let n_time = if coin.as_ref().conf.is_pos { - Some(now_sec_u32()) + match set_n_time { + NTimeSetting::UseNow => Some(now_sec_u32()), + NTimeSetting::UseValue(value) => value, + } } else { None }; @@ -1172,6 +1181,7 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI coin, &input.prev_transaction, LocktimeSetting::CalcByHtlcLocktime(input.lock_time), + NTimeSetting::UseNow, input.sequence, input.outputs ) @@ -1215,6 +1225,7 @@ async fn gen_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_>, lock_time: LocktimeSetting, + n_time: NTimeSetting, ) -> GenDexFeeSpendResult { 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; @@ -1236,7 +1247,7 @@ async fn gen_taker_payment_spend_preimage( script_pubkey: Builder::build_p2pkh(&dex_fee_address.hash).to_bytes(), }; - p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, SEQUENCE_FINAL, vec![dex_fee_output]) + p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, n_time, SEQUENCE_FINAL, vec![dex_fee_output]) .await .map_to_mm(TxGenError::Legacy) } @@ -1253,7 +1264,13 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( .try_into() .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; - let preimage = gen_taker_payment_spend_preimage(coin, args, LocktimeSetting::CalcByHtlcLocktime(time_lock)).await?; + let preimage = gen_taker_payment_spend_preimage( + coin, + args, + LocktimeSetting::CalcByHtlcLocktime(time_lock), + NTimeSetting::UseNow, + ) + .await?; let redeem_script = swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, &taker_pub, &maker_pub); @@ -1280,7 +1297,6 @@ pub async fn validate_taker_payment_spend_preimage( gen_args: &GenTakerPaymentSpendArgs<'_>, preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { - // TODO validate that preimage has exactly 2 outputs let actual_preimage_tx: UtxoTx = deserialize(preimage.preimage.as_slice()) .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::TxDeserialization(e.to_string()))?; @@ -1289,14 +1305,17 @@ pub async fn validate_taker_payment_spend_preimage( let taker_pub = Public::from_slice(gen_args.taker_pub) .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 // 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_taker_payment_spend_preimage(coin, gen_args, LocktimeSetting::UseExact(actual_preimage_tx.lock_time)) - .await?; + let expected_preimage = gen_taker_payment_spend_preimage( + coin, + gen_args, + LocktimeSetting::UseExact(actual_preimage_tx.lock_time), + NTimeSetting::UseValue(actual_preimage_tx.n_time), + ) + .await?; let time_lock = gen_args .time_lock @@ -4591,11 +4610,8 @@ pub async fn validate_combined_taker_payment( where T: UtxoCommonOps + SwapOps, { - let dex_fee_tx: UtxoTx = + let taker_tx: UtxoTx = deserialize(args.taker_tx).map_to_mm(|e| ValidateTakerPaymentError::TxDeserialization(e.to_string()))?; - if dex_fee_tx.outputs.len() < 2 { - return MmError::err(ValidateTakerPaymentError::TxLacksOfOutputs); - } let taker_pub = Public::from_slice(args.other_pub).map_to_mm(|e| ValidateTakerPaymentError::InvalidPubkey(e.to_string()))?; @@ -4620,18 +4636,18 @@ where script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&redeem_script))).into(), }; - if dex_fee_tx.outputs.get(0) != Some(&expected_output) { + if taker_tx.outputs.get(0) != Some(&expected_output) { return MmError::err(ValidateTakerPaymentError::InvalidDestinationOrAmount(format!( "Expected {:?}, got {:?}", expected_output, - dex_fee_tx.outputs.get(0) + taker_tx.outputs.get(0) ))); } let tx_bytes_from_rpc = coin .as_ref() .rpc_client - .get_transaction_bytes(&dex_fee_tx.hash().reversed().into()) + .get_transaction_bytes(&taker_tx.hash().reversed().into()) .compat() .await?; if tx_bytes_from_rpc.0 != args.taker_tx { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index bd469874fb..4ba8deb6d4 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -144,6 +144,7 @@ pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; const LEGACY_SWAP_TYPE: u8 = 0; const MAKER_SWAP_V2_TYPE: u8 = 1; const TAKER_SWAP_V2_TYPE: u8 = 2; +const MAX_STARTED_AT_DIFF: u64 = 60; const NEGOTIATE_SEND_INTERVAL: f64 = 30.; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 7c51fe0c30..9a8669997a 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -13,7 +13,7 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_msg_e use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; 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 crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, @@ -613,7 +613,7 @@ impl MakerSwap { }; drop(send_abort_handle); let time_dif = self.r().data.started_at.abs_diff(taker_data.started_at()); - if time_dif > 60 { + if time_dif > MAX_STARTED_AT_DIFF { self.broadcast_negotiated_false(); return Ok((Some(MakerSwapCommand::Finish), vec![MakerSwapEvent::NegotiateFailed( ERRL!("The time difference between you and the taker cannot be longer than 60 seconds. Current difference: {}. Please make sure that your system clock is synced to the correct time before starting another swap!", time_dif).into(), diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 0fff27fc08..d8b3119813 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -3,7 +3,8 @@ use crate::mm2::database::my_swaps::{get_swap_events, insert_new_swap_v2, set_sw 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, MAKER_SWAP_V2_TYPE}; + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, MAKER_SWAP_V2_TYPE, + MAX_STARTED_AT_DIFF}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, SendPaymentArgs, @@ -25,7 +26,7 @@ use uuid::Uuid; #[allow(unused_imports)] use prost::Message; /// Represents events produced by maker swap states. -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, Serialize)] pub enum MakerSwapEvent { /// Swap has been successfully initialized. Initialized { @@ -67,7 +68,7 @@ pub enum MakerSwapEvent { taker_payment_spend: TransactionIdentifier, }, /// Swap has been aborted before maker payment was sent. - Aborted { reason: String }, + Aborted { reason: AbortReason }, /// Swap completed successfully. Completed, } @@ -249,12 +250,18 @@ impl b, - Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + Err(e) => { + let reason = AbortReason::FailedToGetMakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), 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, + Err(e) => { + let reason = AbortReason::FailedToGetTakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, }; if let Err(e) = check_balance_for_maker_swap( @@ -268,7 +275,8 @@ impl State for Initialized d, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive TakerNegotiation: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveTakerNegotiation(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; drop(abort_handle); @@ -349,15 +357,21 @@ impl State for Initialized data, Some(taker_negotiation::Action::Abort(abort)) => { - let next_state = Aborted::new(abort.reason); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::TakerAbortedNegotiation(abort.reason); + return Self::change_state(Aborted::new(reason), 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 reason = AbortReason::ReceivedInvalidTakerNegotiation; + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; + let started_at_diff = state_machine.started_at.abs_diff(taker_data.started_at); + if started_at_diff > MAX_STARTED_AT_DIFF { + let reason = AbortReason::TooLargeStartedAtDiff(started_at_diff); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + let next_state = WaitingForTakerPayment { maker_coin: Default::default(), taker_coin: Default::default(), @@ -420,8 +434,8 @@ impl State for WaitingForTaker 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; + let reason = AbortReason::DidNotReceiveTakerPaymentInfo(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; drop(abort_handle); @@ -497,8 +511,8 @@ impl State for TakerPaymentRec 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; + let reason = AbortReason::FailedToSendMakerPayment(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; info!( @@ -853,14 +867,28 @@ impl StorableState for Tak } } +/// Represents possible reasons of maker swap being aborted +#[derive(Clone, Debug, Deserialize, Display, Serialize)] +pub enum AbortReason { + FailedToGetMakerCoinBlock(String), + FailedToGetTakerCoinBlock(String), + BalanceCheckFailure(String), + DidNotReceiveTakerNegotiation(String), + TakerAbortedNegotiation(String), + ReceivedInvalidTakerNegotiation, + DidNotReceiveTakerPaymentInfo(String), + FailedToSendMakerPayment(String), + TooLargeStartedAtDiff(u64), +} + struct Aborted { maker_coin: PhantomData, taker_coin: PhantomData, - reason: String, + reason: AbortReason, } impl Aborted { - fn new(reason: String) -> Aborted { + fn new(reason: AbortReason) -> Aborted { Aborted { maker_coin: Default::default(), taker_coin: Default::default(), diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index c6fc2a774e..c273eda985 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -13,7 +13,7 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_msg use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::TakerOrderBuilder; use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, - wait_for_maker_payment_conf_duration, TakerSwapWatcherData}; + wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -1101,7 +1101,7 @@ impl TakerSwap { debug!("Received maker negotiation data {:?}", maker_data); let time_dif = self.r().data.started_at.abs_diff(maker_data.started_at()); - if time_dif > 60 { + if time_dif > MAX_STARTED_AT_DIFF { return Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::NegotiateFailed( ERRL!("The time difference between you and the maker cannot be longer than 60 seconds. Current difference: {}. Please make sure that your system clock is synced to the correct time before starting another swap!", time_dif).into(), )])); From 3c1cf641e1432042649409d14a870f75ac472c71 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 19 Sep 2023 14:40:43 +0700 Subject: [PATCH 08/30] WIP. Additional validation and refactoring. --- mm2src/coins/lp_coins.rs | 15 +- mm2src/coins/test_coin.rs | 31 ++- mm2src/coins/utxo/utxo_standard.rs | 25 ++- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 126 ++++++----- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 224 ++++++++++++------- 5 files changed, 272 insertions(+), 149 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 564863ad98..f9713983fa 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1222,9 +1222,22 @@ impl From for ValidateTakerPaymentSpendPreimageError { fn from(err: TxGenError) -> Self { ValidateTakerPaymentSpendPreimageError::TxGenError(format!("{:?}", err)) } } +/// Helper trait used for various types serialization to bytes +pub trait ToBytes { + fn to_bytes(&self) -> Vec; +} + +/// Defines associated types specific to each coin (Pubkey, Address, etc.) +pub trait CoinAssocTypes { + type Pubkey: ToBytes + Send + Sync; + type PubkeyParseError: Send + std::fmt::Display; + + fn parse_pubkey(&self, pubkey: &[u8]) -> Result; +} + /// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait SwapOpsV2: Send + Sync + 'static { +pub trait SwapOpsV2: CoinAssocTypes + 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; diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 24408cf506..59d075d974 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -2,19 +2,19 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; -use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, - ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenTakerPaymentSpendArgs, +use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, + CoinFutSpawner, 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}; + SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, + 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; @@ -382,6 +382,17 @@ impl MmCoin for TestCoin { fn on_token_deactivated(&self, _ticker: &str) { () } } +impl ToBytes for () { + fn to_bytes(&self) -> Vec { vec![] } +} + +impl CoinAssocTypes for TestCoin { + type Pubkey = (); + type PubkeyParseError = String; + + fn parse_pubkey(&self, pubkey: &[u8]) -> Result { unimplemented!() } +} + #[async_trait] #[mockable] impl SwapOpsV2 for TestCoin { diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 47582f8524..e6977bdf02 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -22,12 +22,12 @@ use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, IguanaPrivKey, - MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, +use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, CoinBalance, CoinWithDerivationMethod, + ConfirmPaymentInput, GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, + IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, @@ -582,6 +582,19 @@ impl WatcherOps for UtxoStandardCoin { } } +impl ToBytes for Public { + fn to_bytes(&self) -> Vec { self.to_vec() } +} + +impl CoinAssocTypes for UtxoStandardCoin { + type Pubkey = Public; + type PubkeyParseError = MmError; + + fn parse_pubkey(&self, pubkey: &[u8]) -> Result { + Ok(Public::from_slice(pubkey)?) + } +} + #[async_trait] impl SwapOpsV2 for UtxoStandardCoin { async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index d8b3119813..03bb6d8759 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -7,8 +7,8 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_s MAX_STARTED_AT_DIFF}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, SendPaymentArgs, - SwapOpsV2, TxPreimageWithSig}; +use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, + SendPaymentArgs, SwapOpsV2, ToBytes, TxPreimageWithSig}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -311,7 +311,7 @@ impl StorableState for Ini } #[async_trait] -impl State for Initialized { +impl State for Initialized { type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -372,14 +372,36 @@ impl State for Initialized p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + let maker_coin_htlc_pub_from_taker = + match state_machine.maker_coin.parse_pubkey(&taker_data.maker_coin_htlc_pub) { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), 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, + taker_payment_locktime: expected_taker_payment_locktime, + maker_coin_htlc_pub_from_taker, + taker_coin_htlc_pub_from_taker, maker_coin_swap_contract: taker_data.maker_coin_swap_contract, taker_coin_swap_contract: taker_data.taker_coin_swap_contract, }; @@ -387,25 +409,25 @@ impl State for Initialized { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct WaitingForTakerPayment { 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_htlc_pub_from_taker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> +impl TransitionFrom> for WaitingForTakerPayment { } #[async_trait] -impl State for WaitingForTakerPayment { +impl State + for WaitingForTakerPayment +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -442,8 +464,6 @@ impl State for WaitingForTaker 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, @@ -460,7 +480,7 @@ impl State for WaitingForTaker } } -impl StorableState +impl StorableState for WaitingForTakerPayment { type StateMachine = MakerSwapStateMachine; @@ -473,33 +493,33 @@ impl StorableState } } -struct TakerPaymentReceived { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentReceived { 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_htlc_pub_from_taker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, taker_payment: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentReceived { } #[async_trait] -impl State for TakerPaymentReceived { +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, + other_pubkey: &self.maker_coin_htlc_pub_from_taker.to_bytes(), secret_hash: &state_machine.secret_hash(), amount: state_machine.maker_volume.to_decimal(), swap_contract_address: &None, @@ -522,8 +542,6 @@ impl State for TakerPaymentRec 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, @@ -542,7 +560,7 @@ impl State for TakerPaymentRec } } -impl StorableState +impl StorableState for TakerPaymentReceived { type StateMachine = MakerSwapStateMachine; @@ -556,27 +574,27 @@ impl StorableState } } -struct MakerPaymentSent { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct MakerPaymentSent { 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_htlc_pub_from_taker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, taker_payment: TransactionIdentifier, maker_payment: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentSent { } #[async_trait] -impl State for MakerPaymentSent { +impl State + for MakerPaymentSent +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -614,8 +632,6 @@ impl State for MakerPaymentSen } 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, @@ -630,7 +646,9 @@ impl State for MakerPaymentSen } } -impl StorableState for MakerPaymentSent { +impl StorableState + for MakerPaymentSent +{ type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -657,11 +675,11 @@ struct MakerPaymentRefundRequired { reason: MakerPaymentRefundReason, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentRefundRequired { } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentRefundRequired { } @@ -694,27 +712,27 @@ impl StorableState } #[allow(dead_code)] -struct TakerPaymentConfirmed { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentConfirmed { 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_htlc_pub_from_taker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentConfirmed { } #[async_trait] -impl State for TakerPaymentConfirmed { +impl State + for TakerPaymentConfirmed +{ type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -745,7 +763,7 @@ impl State for TakerPaymentCon 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, + taker_pub: &self.taker_coin_htlc_pub_from_taker.to_bytes(), dex_fee_amount: state_machine.dex_fee_amount.to_decimal(), premium_amount: Default::default(), trading_amount: state_machine.taker_volume.to_decimal(), @@ -812,7 +830,7 @@ impl State for TakerPaymentCon } } -impl StorableState +impl StorableState for TakerPaymentConfirmed { type StateMachine = MakerSwapStateMachine; @@ -837,7 +855,7 @@ struct TakerPaymentSpent { taker_payment_spend: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSpent { } @@ -879,6 +897,8 @@ pub enum AbortReason { DidNotReceiveTakerPaymentInfo(String), FailedToSendMakerPayment(String), TooLargeStartedAtDiff(u64), + TakerProvidedInvalidLocktime(u64), + FailedToParsePubkey(String), } struct Aborted { @@ -921,11 +941,11 @@ impl StorableState for Abo impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> +impl TransitionFrom> for Aborted { } -impl TransitionFrom> +impl TransitionFrom> for Aborted { } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 61cd0c0a31..56a11c2c00 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -3,10 +3,11 @@ use crate::mm2::database::my_swaps::{get_swap_events, insert_new_swap_v2, set_sw 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, SecretHashAlgo, - SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, TAKER_SWAP_V2_TYPE}; + SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, MAX_STARTED_AT_DIFF, + TAKER_SWAP_V2_TYPE}; use async_trait::async_trait; -use coins::{ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, SendCombinedTakerPaymentArgs, - SpendPaymentArgs, SwapOpsV2, WaitForHTLCTxSpendArgs}; +use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, + SendCombinedTakerPaymentArgs, SpendPaymentArgs, SwapOpsV2, ToBytes, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -24,7 +25,7 @@ use uuid::Uuid; #[allow(unused_imports)] use prost::Message; /// Represents events produced by taker swap states. -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, Serialize)] pub enum TakerSwapEvent { /// Swap has been successfully initialized. Initialized { @@ -76,7 +77,7 @@ pub enum TakerSwapEvent { maker_payment_spend: TransactionIdentifier, }, /// Swap has been aborted before taker payment was sent. - Aborted { reason: String }, + Aborted { reason: AbortReason }, /// Swap completed successfully. Completed, } @@ -242,12 +243,18 @@ impl b, - Err(e) => return Self::change_state(Aborted::new(e), state_machine).await, + Err(e) => { + let reason = AbortReason::FailedToGetMakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), 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, + Err(e) => { + let reason = AbortReason::FailedToGetTakerCoinBlock(e); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, }; if let Err(e) = check_balance_for_taker_swap( @@ -261,7 +268,8 @@ impl StorableState for Ini } #[async_trait] -impl State - for Initialized -{ +impl State for Initialized { type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -312,13 +318,52 @@ impl d, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive MakerNegotiation: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveMakerNegotiation(e); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; debug!("Received maker negotiation message {:?}", maker_negotiation); + let started_at_diff = state_machine.started_at.abs_diff(maker_negotiation.started_at); + if started_at_diff > MAX_STARTED_AT_DIFF { + let reason = AbortReason::TooLargeStartedAtDiff(started_at_diff); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + if !(maker_negotiation.secret_hash.len() == 20 || maker_negotiation.secret_hash.len() == 32) { + let reason = AbortReason::SecretHashUnexpectedLen(maker_negotiation.secret_hash.len()); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let expected_maker_payment_locktime = maker_negotiation.started_at + 2 * state_machine.lock_duration; + if maker_negotiation.payment_locktime != expected_maker_payment_locktime { + let reason = AbortReason::MakerProvidedInvalidLocktime(maker_negotiation.payment_locktime); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + + let maker_coin_htlc_pub_from_maker = match state_machine + .maker_coin + .parse_pubkey(&maker_negotiation.maker_coin_htlc_pub) + { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + let taker_coin_htlc_pub_from_maker = match state_machine + .taker_coin + .parse_pubkey(&maker_negotiation.taker_coin_htlc_pub) + { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToParsePubkey(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + let unique_data = state_machine.unique_data(); let taker_negotiation = TakerNegotiation { action: Some(taker_negotiation::Action::Continue(TakerNegotiationData { @@ -352,30 +397,25 @@ impl d, Err(e) => { - let next_state = Aborted::new(format!("Failed to receive MakerNegotiated: {}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::DidNotReceiveMakerNegotiated(e); + return Self::change_state(Aborted::new(reason), 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 reason = AbortReason::MakerDidNotNegotiate(maker_negotiated.reason.unwrap_or_default()); + return Self::change_state(Aborted::new(reason), 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_payment_locktime: expected_maker_payment_locktime, + maker_coin_htlc_pub_from_maker, + taker_coin_htlc_pub_from_maker, maker_coin_swap_contract: maker_negotiation.maker_coin_swap_contract, taker_coin_swap_contract: maker_negotiation.taker_coin_swap_contract, }; @@ -383,30 +423,31 @@ impl { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct Negotiated { 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_htlc_pub_from_maker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> for Negotiated {} +impl TransitionFrom> + for Negotiated +{ +} #[async_trait] -impl State for Negotiated { +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, + other_pub: &self.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee_amount: state_machine.dex_fee.to_decimal(), premium_amount: BigDecimal::from(0), trading_amount: state_machine.taker_volume.to_decimal(), @@ -416,8 +457,8 @@ impl State for Negotiated tx, Err(e) => { - let next_state = Aborted::new(format!("Failed to send taker payment {:?}", e)); - return Self::change_state(next_state, state_machine).await; + let reason = AbortReason::FailedToSendTakerPayment(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; info!( @@ -428,8 +469,6 @@ impl State for Negotiated State for Negotiated StorableState for Negotiated { +impl StorableState + for Negotiated +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -459,24 +500,27 @@ impl StorableState for Neg } } -struct TakerPaymentSent { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentSent { 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_htlc_pub_from_maker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> for TakerPaymentSent {} +impl TransitionFrom> + for TakerPaymentSent +{ +} #[async_trait] -impl State for TakerPaymentSent { +impl State + for TakerPaymentSent +{ type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -538,8 +582,6 @@ impl State for TakerPaymentSen } 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 { @@ -558,7 +600,9 @@ impl State for TakerPaymentSen } } -impl StorableState for TakerPaymentSent { +impl StorableState + for TakerPaymentSent +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -587,11 +631,11 @@ struct TakerPaymentRefundRequired { reason: TakerPaymentRefundReason, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentRefundRequired { } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentRefundRequired { } @@ -624,28 +668,28 @@ impl StorableState } } -struct MakerPaymentConfirmed { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct MakerPaymentConfirmed { 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_htlc_pub_from_maker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentConfirmed { } #[async_trait] -impl State for MakerPaymentConfirmed { +impl State + for MakerPaymentConfirmed +{ type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -655,7 +699,7 @@ impl State for MakerPaymentCon 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, + maker_pub: &self.maker_coin_htlc_pub_from_maker.to_bytes(), 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(), @@ -736,8 +780,6 @@ impl State for MakerPaymentCon ); 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, @@ -757,7 +799,7 @@ impl State for MakerPaymentCon } } -impl StorableState +impl StorableState for MakerPaymentConfirmed { type StateMachine = TakerSwapStateMachine; @@ -774,9 +816,7 @@ impl StorableState } #[allow(dead_code)] -struct TakerPaymentSpent { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentSpent { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, @@ -784,19 +824,19 @@ struct TakerPaymentSpent { 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_htlc_pub_from_maker: MakerCoin::Pubkey, + taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSpent { } #[async_trait] -impl State +impl State for TakerPaymentSpent { type StateMachine = TakerSwapStateMachine; @@ -809,15 +849,15 @@ impl S { 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 reason = AbortReason::CouldNotExtractSecret(e); + return Self::change_state(Aborted::new(reason), 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, + other_pubkey: &self.maker_coin_htlc_pub_from_maker.to_bytes(), secret: &secret, secret_hash: &self.secret_hash, swap_contract_address: &self.maker_coin_swap_contract.clone().map(|bytes| bytes.into()), @@ -832,8 +872,8 @@ impl S { 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; + let reason = AbortReason::FailedToSpendMakerPayment(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; }, }; info!( @@ -859,7 +899,9 @@ impl S } } -impl StorableState for TakerPaymentSpent { +impl StorableState + for TakerPaymentSpent +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -885,7 +927,7 @@ struct MakerPaymentSpent { maker_payment_spend: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentSpent { } @@ -916,14 +958,32 @@ impl State } } +/// Represents possible reasons of taker swap being aborted +#[derive(Clone, Debug, Deserialize, Display, Serialize)] +pub enum AbortReason { + FailedToGetMakerCoinBlock(String), + FailedToGetTakerCoinBlock(String), + BalanceCheckFailure(String), + DidNotReceiveMakerNegotiation(String), + TooLargeStartedAtDiff(u64), + FailedToParsePubkey(String), + MakerProvidedInvalidLocktime(u64), + SecretHashUnexpectedLen(usize), + DidNotReceiveMakerNegotiated(String), + MakerDidNotNegotiate(String), + FailedToSendTakerPayment(String), + CouldNotExtractSecret(String), + FailedToSpendMakerPayment(String), +} + struct Aborted { maker_coin: PhantomData, taker_coin: PhantomData, - reason: String, + reason: AbortReason, } impl Aborted { - fn new(reason: String) -> Aborted { + fn new(reason: AbortReason) -> Aborted { Aborted { maker_coin: Default::default(), taker_coin: Default::default(), @@ -956,8 +1016,14 @@ impl StorableState for Abo impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> for Aborted {} -impl TransitionFrom> for Aborted {} +impl TransitionFrom> + for Aborted +{ +} +impl TransitionFrom> + for Aborted +{ +} struct Completed { maker_coin: PhantomData, From c49d424546bac222d83802f427691c085ca076ce Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Wed, 20 Sep 2023 13:09:48 +0700 Subject: [PATCH 09/30] WIP. Additional validation and refactoring. --- mm2src/coins/lp_coins.rs | 4 + mm2src/coins/test_coin.rs | 23 +- mm2src/coins/utxo/utxo_standard.rs | 7 + mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 225 +++++++++++-------- 4 files changed, 164 insertions(+), 95 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f9713983fa..55abc33280 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1231,8 +1231,12 @@ pub trait ToBytes { pub trait CoinAssocTypes { type Pubkey: ToBytes + Send + Sync; type PubkeyParseError: Send + std::fmt::Display; + type Tx: Transaction + Send + Sync; + type TxParseError: Send + std::fmt::Display; fn parse_pubkey(&self, pubkey: &[u8]) -> Result; + + fn parse_tx(&self, tx: &[u8]) -> Result; } /// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 59d075d974..486797f9d4 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -8,8 +8,8 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionResult, TxMarshalingErr, - TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionResult, + TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, @@ -382,15 +382,30 @@ impl MmCoin for TestCoin { fn on_token_deactivated(&self, _ticker: &str) { () } } -impl ToBytes for () { +pub struct TestPubkey {} + +impl ToBytes for TestPubkey { fn to_bytes(&self) -> Vec { vec![] } } +#[derive(Debug)] +pub struct TestTx {} + +impl Transaction for TestTx { + fn tx_hex(&self) -> Vec { todo!() } + + fn tx_hash(&self) -> BytesJson { todo!() } +} + impl CoinAssocTypes for TestCoin { - type Pubkey = (); + type Pubkey = TestPubkey; type PubkeyParseError = String; + type Tx = TestTx; + type TxParseError = String; fn parse_pubkey(&self, pubkey: &[u8]) -> Result { unimplemented!() } + + fn parse_tx(&self, tx: &[u8]) -> Result { unimplemented!() } } #[async_trait] diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index e6977bdf02..b65519c771 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -39,6 +39,7 @@ use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; use mm2_metrics::MetricsArc; use mm2_number::MmNumber; +use serialization::deserialize; use utxo_signer::UtxoSignerOps; #[derive(Clone)] @@ -589,10 +590,16 @@ impl ToBytes for Public { impl CoinAssocTypes for UtxoStandardCoin { type Pubkey = Public; type PubkeyParseError = MmError; + type Tx = UtxoTx; + type TxParseError = MmError; + #[inline] fn parse_pubkey(&self, pubkey: &[u8]) -> Result { Ok(Public::from_slice(pubkey)?) } + + #[inline] + fn parse_tx(&self, tx: &[u8]) -> Result { Ok(deserialize(tx)?) } } #[async_trait] diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 03bb6d8759..7206b74cb6 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -8,7 +8,7 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_s use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, - SendPaymentArgs, SwapOpsV2, ToBytes, TxPreimageWithSig}; + SendPaymentArgs, SwapOpsV2, ToBytes, Transaction, TxPreimageWithSig}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -19,12 +19,23 @@ use mm2_number::MmNumber; use mm2_state_machine::prelude::*; use mm2_state_machine::storable_state_machine::*; use primitives::hash::H256; +use rpc::v1::types::Bytes as BytesJson; use std::marker::PhantomData; use uuid::Uuid; // This is needed to have Debug on messages #[allow(unused_imports)] use prost::Message; +/// Negotiation data representation to be stored in DB. +#[derive(Debug, Deserialize, Serialize)] +pub struct StoredNegotiationData { + taker_payment_locktime: u64, + maker_coin_htlc_pub_from_taker: BytesJson, + taker_coin_htlc_pub_from_taker: BytesJson, + maker_coin_swap_contract: Option, + taker_coin_swap_contract: Option, +} + /// Represents events produced by maker swap states. #[derive(Debug, Deserialize, Serialize)] pub enum MakerSwapEvent { @@ -37,25 +48,34 @@ pub enum MakerSwapEvent { WaitingForTakerPayment { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, }, /// Received taker payment info. TakerPaymentReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, taker_payment: TransactionIdentifier, }, /// Sent maker payment. MakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, maker_payment: TransactionIdentifier, }, /// Something went wrong, so maker payment refund is required. - MakerPaymentRefundRequired { maker_payment: TransactionIdentifier }, + MakerPaymentRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + maker_payment: TransactionIdentifier, + }, /// Taker payment has been confirmed on-chain. TakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, }, @@ -214,9 +234,7 @@ impl InitialState for Init } #[async_trait] -impl State - for Initialize -{ +impl State for Initialize { type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { @@ -399,19 +417,19 @@ impl State fo let next_state = WaitingForTakerPayment { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - taker_payment_locktime: expected_taker_payment_locktime, - maker_coin_htlc_pub_from_taker, - taker_coin_htlc_pub_from_taker, - maker_coin_swap_contract: taker_data.maker_coin_swap_contract, - taker_coin_swap_contract: taker_data.taker_coin_swap_contract, + negotiation_data: NegotiationData { + taker_payment_locktime: expected_taker_payment_locktime, + maker_coin_htlc_pub_from_taker, + taker_coin_htlc_pub_from_taker, + 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_start_block: u64, - taker_coin_start_block: u64, +struct NegotiationData { taker_payment_locktime: u64, maker_coin_htlc_pub_from_taker: MakerCoin::Pubkey, taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, @@ -419,7 +437,25 @@ struct WaitingForTakerPayment { taker_coin_swap_contract: Option>, } -impl TransitionFrom> +impl NegotiationData { + fn to_stored_data(&self) -> StoredNegotiationData { + StoredNegotiationData { + taker_payment_locktime: self.taker_payment_locktime, + maker_coin_htlc_pub_from_taker: self.maker_coin_htlc_pub_from_taker.to_bytes().into(), + taker_coin_htlc_pub_from_taker: self.taker_coin_htlc_pub_from_taker.to_bytes().into(), + maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), + taker_coin_swap_contract: self.taker_coin_swap_contract.clone().map(|b| b.into()), + } + } +} + +struct WaitingForTakerPayment { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, +} + +impl TransitionFrom> for WaitingForTakerPayment { } @@ -453,7 +489,7 @@ impl State &state_machine.uuid, NEGOTIATION_TIMEOUT_SEC, ); - let taker_payment = match recv_fut.await { + let taker_payment_info = match recv_fut.await { Ok(p) => p, Err(e) => { let reason = AbortReason::DidNotReceiveTakerPaymentInfo(e); @@ -462,19 +498,19 @@ impl State }; drop(abort_handle); - debug!("Received taker payment info message {:?}", taker_payment); + debug!("Received taker payment info message {:?}", taker_payment_info); + let taker_payment = match state_machine.taker_coin.parse_tx(&taker_payment_info.tx_bytes) { + Ok(tx) => tx, + Err(e) => { + let reason = AbortReason::FailedToParseTakerPayment(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; let next_state = TakerPaymentReceived { 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(), - }, + negotiation_data: self.negotiation_data, + taker_payment, }; Self::change_state(next_state, state_machine).await } @@ -489,22 +525,19 @@ impl StorableS MakerSwapEvent::WaitingForTakerPayment { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct TakerPaymentReceived { +struct TakerPaymentReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment_locktime: u64, - maker_coin_htlc_pub_from_taker: MakerCoin::Pubkey, - taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, - taker_payment: TransactionIdentifier, + negotiation_data: NegotiationData, + taker_payment: TakerCoin::Tx, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentReceived { } @@ -519,7 +552,7 @@ impl State 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.to_bytes(), + other_pubkey: &self.negotiation_data.maker_coin_htlc_pub_from_taker.to_bytes(), secret_hash: &state_machine.secret_hash(), amount: state_machine.maker_volume.to_decimal(), swap_contract_address: &None, @@ -544,11 +577,7 @@ impl State let next_state = MakerPaymentSent { 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, + negotiation_data: self.negotiation_data, taker_payment: self.taker_payment, maker_payment: TransactionIdentifier { tx_hex: maker_payment.tx_hex().into(), @@ -569,24 +598,24 @@ impl StorableS 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(), + negotiation_data: self.negotiation_data.to_stored_data(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, } } } -struct MakerPaymentSent { +struct MakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment_locktime: u64, - maker_coin_htlc_pub_from_taker: MakerCoin::Pubkey, - taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, - taker_payment: TransactionIdentifier, + negotiation_data: NegotiationData, + taker_payment: TakerCoin::Tx, maker_payment: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentSent { } @@ -615,7 +644,7 @@ impl State state_machine.p2p_keypair, ); let input = ConfirmPaymentInput { - payment_tx: self.taker_payment.tx_hex.0.clone(), + payment_tx: self.taker_payment.tx_hex(), 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(), @@ -623,8 +652,9 @@ impl State }; 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_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::TakerPaymentNotConfirmedInTime(e), }; @@ -636,11 +666,7 @@ impl State 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, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } @@ -655,6 +681,7 @@ impl StorableS MakerSwapEvent::MakerPaymentSent { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), maker_payment: self.maker_payment.clone(), } } @@ -668,9 +695,10 @@ enum MakerPaymentRefundReason { TakerPaymentSpendBroadcastFailed(String), } -struct MakerPaymentRefundRequired { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct MakerPaymentRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, maker_payment: TransactionIdentifier, reason: MakerPaymentRefundReason, } @@ -685,8 +713,10 @@ impl TransitionFrom State - for MakerPaymentRefundRequired +impl< + MakerCoin: CoinAssocTypes + Send + Sync + 'static, + TakerCoin: MarketCoinOps + CoinAssocTypes + Send + Sync + 'static, + > State for MakerPaymentRefundRequired { type StateMachine = MakerSwapStateMachine; @@ -699,32 +729,31 @@ impl StorableState +impl StorableState for MakerPaymentRefundRequired { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { MakerSwapEvent::MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), maker_payment: self.maker_payment.clone(), } } } #[allow(dead_code)] -struct TakerPaymentConfirmed { +struct TakerPaymentConfirmed { 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: MakerCoin::Pubkey, - taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + taker_payment: TakerCoin::Tx, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentConfirmed { } @@ -746,8 +775,9 @@ impl State Ok(preimage) => preimage, Err(e) => { let next_state = MakerPaymentRefundRequired { - 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, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::DidNotGetTakerPaymentSpendPreimage(e), }; @@ -759,11 +789,11 @@ impl State 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, + taker_tx: &self.taker_payment.tx_hex(), + time_lock: self.negotiation_data.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.to_bytes(), + taker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker.to_bytes(), dex_fee_amount: state_machine.dex_fee_amount.to_decimal(), premium_amount: Default::default(), trading_amount: state_machine.taker_volume.to_decimal(), @@ -779,8 +809,9 @@ impl State .await { let next_state = MakerPaymentRefundRequired { - 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, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::TakerPaymentSpendPreimageIsNotValid(e.to_string()), }; @@ -800,8 +831,9 @@ impl State Ok(tx) => tx, Err(e) => { let next_state = MakerPaymentRefundRequired { - 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, + negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, reason: MakerPaymentRefundReason::TakerPaymentSpendBroadcastFailed(format!("{:?}", e)), }; @@ -816,7 +848,6 @@ impl State ); 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, @@ -839,29 +870,32 @@ impl StorableS MakerSwapEvent::TakerPaymentConfirmed { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), maker_payment: self.maker_payment.clone(), - taker_payment: self.taker_payment.clone(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, } } } -struct TakerPaymentSpent { +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: TakerCoin::Tx, taker_payment_spend: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSpent { } #[async_trait] -impl State +impl State for TakerPaymentSpent { type StateMachine = MakerSwapStateMachine; @@ -871,7 +905,9 @@ impl State } } -impl StorableState for TakerPaymentSpent { +impl StorableState + for TakerPaymentSpent +{ type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -879,7 +915,10 @@ impl StorableState for Tak 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: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, taker_payment_spend: self.taker_payment_spend.clone(), } } @@ -895,6 +934,7 @@ pub enum AbortReason { TakerAbortedNegotiation(String), ReceivedInvalidTakerNegotiation, DidNotReceiveTakerPaymentInfo(String), + FailedToParseTakerPayment(String), FailedToSendMakerPayment(String), TooLargeStartedAtDiff(u64), TakerProvidedInvalidLocktime(u64), @@ -941,11 +981,11 @@ impl StorableState for Abo impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> +impl TransitionFrom> for Aborted { } -impl TransitionFrom> +impl TransitionFrom> for Aborted { } @@ -984,4 +1024,7 @@ impl LastSta } } -impl TransitionFrom> for Completed {} +impl TransitionFrom> + for Completed +{ +} From 137988c0c3e4277749bc129573293177b340b111 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Fri, 22 Sep 2023 14:29:32 +0700 Subject: [PATCH 10/30] WIP. Refactor a bit. --- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 186 ++++++++++--------- 1 file changed, 94 insertions(+), 92 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 56a11c2c00..fa51bdf290 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -24,6 +24,17 @@ use uuid::Uuid; // This is needed to have Debug on messages #[allow(unused_imports)] use prost::Message; +/// Negotiation data representation to be stored in DB. +#[derive(Debug, Deserialize, Serialize)] +pub struct StoredNegotiationData { + maker_payment_locktime: u64, + secret_hash: BytesJson, + maker_coin_htlc_pub_from_maker: BytesJson, + taker_coin_htlc_pub_from_maker: BytesJson, + maker_coin_swap_contract: Option, + taker_coin_swap_contract: Option, +} + /// Represents events produced by taker swap states. #[derive(Debug, Deserialize, Serialize)] pub enum TakerSwapEvent { @@ -36,19 +47,19 @@ pub enum TakerSwapEvent { Negotiated { maker_coin_start_block: u64, taker_coin_start_block: u64, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Sent taker payment. TakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, taker_payment: TransactionIdentifier, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Something went wrong, so taker payment refund is required. TakerPaymentRefundRequired { taker_payment: TransactionIdentifier, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Both payments are confirmed on-chain BothPaymentsSentAndConfirmed { @@ -56,7 +67,7 @@ pub enum TakerSwapEvent { taker_coin_start_block: u64, maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, - secret_hash: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Maker spent taker's payment and taker discovered the tx on-chain. TakerPaymentSpent { @@ -65,7 +76,7 @@ pub enum TakerSwapEvent { maker_payment: TransactionIdentifier, taker_payment: TransactionIdentifier, taker_payment_spend: TransactionIdentifier, - secret: BytesJson, + negotiation_data: StoredNegotiationData, }, /// Taker spent maker's payment. MakerPaymentSpent { @@ -412,20 +423,20 @@ impl State fo let next_state = Negotiated { 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: expected_maker_payment_locktime, - maker_coin_htlc_pub_from_maker, - taker_coin_htlc_pub_from_maker, - maker_coin_swap_contract: maker_negotiation.maker_coin_swap_contract, - taker_coin_swap_contract: maker_negotiation.taker_coin_swap_contract, + negotiation_data: NegotiationData { + secret_hash: maker_negotiation.secret_hash, + maker_payment_locktime: expected_maker_payment_locktime, + maker_coin_htlc_pub_from_maker, + taker_coin_htlc_pub_from_maker, + 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_start_block: u64, - taker_coin_start_block: u64, +struct NegotiationData { secret_hash: Vec, maker_payment_locktime: u64, maker_coin_htlc_pub_from_maker: MakerCoin::Pubkey, @@ -434,6 +445,25 @@ struct Negotiated { taker_coin_swap_contract: Option>, } +impl NegotiationData { + fn to_stored_data(&self) -> StoredNegotiationData { + StoredNegotiationData { + maker_payment_locktime: self.maker_payment_locktime, + secret_hash: self.secret_hash.clone().into(), + maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker.to_bytes().into(), + taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker.to_bytes().into(), + maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), + taker_coin_swap_contract: self.taker_coin_swap_contract.clone().map(|b| b.into()), + } + } +} + +struct Negotiated { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, +} + impl TransitionFrom> for Negotiated { @@ -446,8 +476,8 @@ impl State fo 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.to_bytes(), + secret_hash: &self.negotiation_data.secret_hash, + other_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee_amount: state_machine.dex_fee.to_decimal(), premium_amount: BigDecimal::from(0), trading_amount: state_machine.taker_volume.to_decimal(), @@ -475,12 +505,7 @@ impl State fo 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, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } @@ -495,24 +520,19 @@ impl StorableS TakerSwapEvent::Negotiated { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, - secret_hash: Default::default(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct TakerPaymentSent { +struct TakerPaymentSent { 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: MakerCoin::Pubkey, - taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSent { } @@ -550,10 +570,8 @@ impl State 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, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::DidNotReceiveMakerPayment(e), }; return Self::change_state(next_state, state_machine).await; @@ -572,10 +590,8 @@ impl State 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, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::MakerPaymentNotConfirmedInTime(e), }; return Self::change_state(next_state, state_machine).await; @@ -589,12 +605,7 @@ impl State 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, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } @@ -610,7 +621,7 @@ impl StorableS 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(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } @@ -623,25 +634,23 @@ enum TakerPaymentRefundReason { MakerDidNotSpendInTime(String), } -struct TakerPaymentRefundRequired { - maker_coin: PhantomData, - taker_coin: PhantomData, +struct TakerPaymentRefundRequired { taker_payment: TransactionIdentifier, - secret_hash: Vec, + negotiation_data: NegotiationData, reason: TakerPaymentRefundReason, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentRefundRequired { } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentRefundRequired { } #[async_trait] -impl State +impl State for TakerPaymentRefundRequired { type StateMachine = TakerSwapStateMachine; @@ -655,7 +664,7 @@ impl State } } -impl StorableState +impl StorableState for TakerPaymentRefundRequired { type StateMachine = TakerSwapStateMachine; @@ -663,25 +672,20 @@ impl StorableState fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { TakerSwapEvent::TakerPaymentRefundRequired { taker_payment: self.taker_payment.clone(), - secret_hash: self.secret_hash.clone().into(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct MakerPaymentConfirmed { +struct MakerPaymentConfirmed { 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: MakerCoin::Pubkey, - taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentConfirmed { } @@ -698,8 +702,8 @@ impl State 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.to_bytes(), + secret_hash: &self.negotiation_data.secret_hash, + maker_pub: &self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), 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(), @@ -715,10 +719,8 @@ impl State 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, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::FailedToGenerateSpendPreimage(e.to_string()), }; return Self::change_state(next_state, state_machine).await; @@ -747,10 +749,14 @@ impl State let wait_args = WaitForHTLCTxSpendArgs { tx_bytes: &self.taker_payment.tx_hex.0, - secret_hash: &self.secret_hash, + secret_hash: &self.negotiation_data.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()), + swap_contract_address: &self + .negotiation_data + .taker_coin_swap_contract + .clone() + .map(|bytes| bytes.into()), check_every: 10.0, watcher_reward: false, }; @@ -763,10 +769,8 @@ impl State 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, + negotiation_data: self.negotiation_data, reason: TakerPaymentRefundReason::MakerDidNotSpendInTime(format!("{:?}", e)), }; return Self::change_state(next_state, state_machine).await; @@ -788,12 +792,7 @@ impl State 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, + negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await } @@ -810,27 +809,22 @@ impl StorableS 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(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } #[allow(dead_code)] -struct TakerPaymentSpent { +struct TakerPaymentSpent { 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: MakerCoin::Pubkey, - taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, - maker_coin_swap_contract: Option>, - taker_coin_swap_contract: Option>, + negotiation_data: NegotiationData, } -impl TransitionFrom> +impl TransitionFrom> for TakerPaymentSpent { } @@ -844,7 +838,11 @@ impl State 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) + .extract_secret( + &self.negotiation_data.secret_hash, + &self.taker_payment_spend.tx_hex.0, + false, + ) .await { Ok(s) => s, @@ -856,11 +854,15 @@ impl State 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.to_bytes(), + time_lock: self.negotiation_data.maker_payment_locktime, + other_pubkey: &self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), secret: &secret, - secret_hash: &self.secret_hash, - swap_contract_address: &self.maker_coin_swap_contract.clone().map(|bytes| bytes.into()), + secret_hash: &self.negotiation_data.secret_hash, + swap_contract_address: &self + .negotiation_data + .maker_coin_swap_contract + .clone() + .map(|bytes| bytes.into()), swap_unique_data: &state_machine.unique_data(), watcher_reward: false, }; @@ -911,7 +913,7 @@ impl StorableS maker_payment: self.maker_payment.clone(), taker_payment: self.taker_payment.clone(), taker_payment_spend: self.taker_payment_spend.clone(), - secret: Vec::new().into(), + negotiation_data: self.negotiation_data.to_stored_data(), } } } From d8a96085890ed1ee7bcd7ed7ea03839df9db61fa Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 25 Sep 2023 13:51:56 +0700 Subject: [PATCH 11/30] WIP. Refactor. --- mm2src/coins/lp_coins.rs | 39 +++++----- mm2src/coins/test_coin.rs | 20 +++-- mm2src/coins/utxo/utxo_common.rs | 77 ++++++++----------- mm2src/coins/utxo/utxo_standard.rs | 26 +++++-- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 6 +- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 67 +++++++++------- .../tests/docker_tests/swap_proto_v2_tests.rs | 30 +++----- 7 files changed, 142 insertions(+), 123 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 55abc33280..33ab0a703c 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1080,15 +1080,15 @@ pub struct SendCombinedTakerPaymentArgs<'a> { } /// Helper struct wrapping arguments for [SwapOpsV2::validate_combined_taker_payment] -pub struct ValidateTakerPaymentArgs<'a> { +pub struct ValidateTakerPaymentArgs<'a, Tx, Pubkey> { /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a [u8], + pub taker_tx: &'a Tx, /// 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], + pub other_pub: &'a Pubkey, /// DEX fee amount pub dex_fee_amount: BigDecimal, /// Additional reward for maker (premium) @@ -1102,17 +1102,17 @@ pub struct ValidateTakerPaymentArgs<'a> { /// 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> { +pub struct GenTakerPaymentSpendArgs<'a, Tx, Pubkey> { /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a [u8], + pub taker_tx: &'a Tx, /// 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], + pub maker_pub: &'a Pubkey, /// Taker's pubkey - pub taker_pub: &'a [u8], + pub taker_pub: &'a Pubkey, /// Pubkey of address, receiving DEX fees pub dex_fee_pub: &'a [u8], /// DEX fee amount @@ -1169,16 +1169,12 @@ impl From for TxGenError { 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. @@ -1196,8 +1192,6 @@ impl From for ValidateTakerPaymentError { /// 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. @@ -1243,10 +1237,16 @@ pub trait CoinAssocTypes { #[async_trait] pub trait SwapOpsV2: CoinAssocTypes + 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 send_combined_taker_payment( + &self, + args: SendCombinedTakerPaymentArgs<'_>, + ) -> Result; /// Validates taker payment transaction. - async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult; + async fn validate_combined_taker_payment( + &self, + args: ValidateTakerPaymentArgs<'_, Self::Tx, Self::Pubkey>, + ) -> ValidateTakerPaymentResult; /// Refunds taker payment transaction. async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; @@ -1255,14 +1255,14 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { /// shared with maker to proceed with protocol execution. async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, swap_unique_data: &[u8], ) -> GenTakerPaymentSpendResult; /// Validate taker payment spend preimage on maker's side. async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult; @@ -1270,10 +1270,13 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { async fn sign_and_broadcast_taker_payment_spend( &self, preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult; + + /// Derives an HTLC key-pair and returns a public key corresponding to that key. + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey; } /// Operations that coins have independently from the MarketMaker. diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 486797f9d4..f1b45bfa08 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -8,7 +8,7 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionResult, + TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentResult, @@ -411,11 +411,17 @@ impl CoinAssocTypes for TestCoin { #[async_trait] #[mockable] impl SwapOpsV2 for TestCoin { - async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { + async fn send_combined_taker_payment( + &self, + args: SendCombinedTakerPaymentArgs<'_>, + ) -> Result { unimplemented!() } - async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult { + async fn validate_combined_taker_payment( + &self, + args: ValidateTakerPaymentArgs<'_, TestTx, TestPubkey>, + ) -> ValidateTakerPaymentResult { unimplemented!() } @@ -423,7 +429,7 @@ impl SwapOpsV2 for TestCoin { async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, swap_unique_data: &[u8], ) -> GenTakerPaymentSpendResult { unimplemented!() @@ -431,7 +437,7 @@ impl SwapOpsV2 for TestCoin { async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { unimplemented!() @@ -440,10 +446,12 @@ impl SwapOpsV2 for TestCoin { async fn sign_and_broadcast_taker_payment_spend( &self, preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { unimplemented!() } + + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index f6d6b74aaa..5e22d2f7a9 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1223,14 +1223,10 @@ pub type GenDexFeeSpendResult = MmResult; async fn gen_taker_payment_spend_preimage( coin: &T, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, lock_time: LocktimeSetting, n_time: NTimeSetting, ) -> GenDexFeeSpendResult { - 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 dex_fee_address = address_from_raw_pubkey( @@ -1247,18 +1243,18 @@ async fn gen_taker_payment_spend_preimage( script_pubkey: Builder::build_p2pkh(&dex_fee_address.hash).to_bytes(), }; - p2sh_spending_tx_preimage(coin, &prev_tx, lock_time, n_time, SEQUENCE_FINAL, vec![dex_fee_output]) - .await - .map_to_mm(TxGenError::Legacy) + p2sh_spending_tx_preimage(coin, args.taker_tx, lock_time, n_time, SEQUENCE_FINAL, vec![ + dex_fee_output, + ]) + .await + .map_to_mm(TxGenError::Legacy) } pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, htlc_keypair: &KeyPair, ) -> 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() @@ -1273,7 +1269,7 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( .await?; let redeem_script = - swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, &taker_pub, &maker_pub); + swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, args.taker_pub, args.maker_pub); let signature = calc_and_sign_sighash( &preimage, DEFAULT_SWAP_VOUT, @@ -1294,17 +1290,12 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( /// Checks taker's signature and compares received preimage with the expected tx. pub async fn validate_taker_payment_spend_preimage( coin: &T, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { let actual_preimage_tx: UtxoTx = deserialize(preimage.preimage.as_slice()) .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::TxDeserialization(e.to_string()))?; - let maker_pub = Public::from_slice(gen_args.maker_pub) - .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::InvalidPubkey(e.to_string()))?; - let taker_pub = Public::from_slice(gen_args.taker_pub) - .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::InvalidPubkey(e.to_string()))?; - // TODO validate that output amounts are larger than dust // Here, we have to use the exact lock time from the preimage because maker @@ -1321,8 +1312,12 @@ pub async fn validate_taker_payment_spend_preimage( .time_lock .try_into() .map_to_mm(|e: TryFromIntError| ValidateTakerPaymentSpendPreimageError::LocktimeOverflow(e.to_string()))?; - let redeem_script = - swap_proto_v2_scripts::taker_payment_script(time_lock, gen_args.secret_hash, &taker_pub, &maker_pub); + let redeem_script = swap_proto_v2_scripts::taker_payment_script( + time_lock, + gen_args.secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); let sig_hash = signature_hash_to_sign( &expected_preimage, DEFAULT_SWAP_VOUT, @@ -1332,7 +1327,8 @@ pub async fn validate_taker_payment_spend_preimage( coin.as_ref().conf.fork_id, )?; - if !taker_pub + if !gen_args + .taker_pub .verify(&sig_hash, &preimage.signature.clone().into()) .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::SignatureVerificationFailure(e.to_string()))? { @@ -1352,16 +1348,10 @@ pub async fn validate_taker_payment_spend_preimage( pub async fn sign_and_broadcast_taker_payment_spend( coin: &T, preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, secret: &[u8], htlc_keypair: &KeyPair, ) -> TransactionResult { - let taker_pub = try_tx_s!(Public::from_slice(gen_args.taker_pub)); - - 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); @@ -1370,13 +1360,13 @@ pub async fn sign_and_broadcast_taker_payment_spend( 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, + gen_args.taker_pub, htlc_keypair.public(), ); let mut signer: TransactionInputSigner = preimage_tx.clone().into(); let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); - let payment_output = try_tx_s!(taker_tx.first_output()); + let payment_output = try_tx_s!(gen_args.taker_tx.first_output()); payment_input.amount = payment_output.value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; @@ -4572,7 +4562,10 @@ where } /// Common implementation of combined taker payment generation and broadcast for UTXO coins. -pub async fn send_combined_taker_payment(coin: T, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult +pub async fn send_combined_taker_payment( + coin: T, + args: SendCombinedTakerPaymentArgs<'_>, +) -> Result where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { @@ -4599,22 +4592,17 @@ where .compat() .await?; } - send_outputs_from_my_address(coin, outputs).compat().await + send_outputs_from_my_address_impl(coin, outputs).await } /// Common implementation of combined taker payment validation for UTXO coins. pub async fn validate_combined_taker_payment( coin: &T, - args: ValidateTakerPaymentArgs<'_>, + args: ValidateTakerPaymentArgs<'_, UtxoTx, Public>, ) -> ValidateTakerPaymentResult where T: UtxoCommonOps + SwapOps, { - let taker_tx: UtxoTx = - deserialize(args.taker_tx).map_to_mm(|e| ValidateTakerPaymentError::TxDeserialization(e.to_string()))?; - - let taker_pub = - 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 + &args.trading_amount; @@ -4628,7 +4616,7 @@ where let redeem_script = swap_proto_v2_scripts::taker_payment_script( time_lock, args.secret_hash, - &taker_pub, + args.other_pub, maker_htlc_key_pair.public(), ); let expected_output = TransactionOutput { @@ -4636,24 +4624,25 @@ where script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&redeem_script))).into(), }; - if taker_tx.outputs.get(0) != Some(&expected_output) { + if args.taker_tx.outputs.get(0) != Some(&expected_output) { return MmError::err(ValidateTakerPaymentError::InvalidDestinationOrAmount(format!( "Expected {:?}, got {:?}", expected_output, - taker_tx.outputs.get(0) + args.taker_tx.outputs.get(0) ))); } let tx_bytes_from_rpc = coin .as_ref() .rpc_client - .get_transaction_bytes(&taker_tx.hash().reversed().into()) + .get_transaction_bytes(&args.taker_tx.hash().reversed().into()) .compat() .await?; - if tx_bytes_from_rpc.0 != args.taker_tx { + let actual_tx_bytes = serialize(args.taker_tx).take(); + if tx_bytes_from_rpc.0 != actual_tx_bytes { return MmError::err(ValidateTakerPaymentError::TxBytesMismatch { from_rpc: tx_bytes_from_rpc, - actual: args.taker_tx.into(), + actual: actual_tx_bytes.into(), }); } Ok(()) diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index b65519c771..a4aca4e19d 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -599,16 +599,26 @@ impl CoinAssocTypes for UtxoStandardCoin { } #[inline] - fn parse_tx(&self, tx: &[u8]) -> Result { Ok(deserialize(tx)?) } + fn parse_tx(&self, tx: &[u8]) -> Result { + let mut tx: UtxoTx = deserialize(tx)?; + tx.tx_hash_algo = self.as_ref().tx_hash_algo; + Ok(tx) + } } #[async_trait] impl SwapOpsV2 for UtxoStandardCoin { - async fn send_combined_taker_payment(&self, args: SendCombinedTakerPaymentArgs<'_>) -> TransactionResult { + async fn send_combined_taker_payment( + &self, + args: SendCombinedTakerPaymentArgs<'_>, + ) -> Result { utxo_common::send_combined_taker_payment(self.clone(), args).await } - async fn validate_combined_taker_payment(&self, args: ValidateTakerPaymentArgs<'_>) -> ValidateTakerPaymentResult { + async fn validate_combined_taker_payment( + &self, + args: ValidateTakerPaymentArgs<'_, Self::Tx, Self::Pubkey>, + ) -> ValidateTakerPaymentResult { utxo_common::validate_combined_taker_payment(self, args).await } @@ -618,7 +628,7 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_>, + args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, swap_unique_data: &[u8], ) -> GenTakerPaymentSpendResult { let key_pair = self.derive_htlc_key_pair(swap_unique_data); @@ -627,7 +637,7 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { utxo_common::validate_taker_payment_spend_preimage(self, gen_args, preimage).await @@ -636,13 +646,17 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn sign_and_broadcast_taker_payment_spend( &self, preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_>, + gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); utxo_common::sign_and_broadcast_taker_payment_spend(self, preimage, gen_args, secret, &htlc_keypair).await } + + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { + *self.derive_htlc_key_pair(swap_unique_data).public() + } } impl MarketCoinOps for UtxoStandardCoin { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 7206b74cb6..7085000f4b 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -789,11 +789,11 @@ impl State let unique_data = state_machine.unique_data(); let gen_args = GenTakerPaymentSpendArgs { - taker_tx: &self.taker_payment.tx_hex(), + taker_tx: &self.taker_payment, time_lock: self.negotiation_data.taker_payment_locktime, secret_hash: &state_machine.secret_hash(), - maker_pub: &state_machine.maker_coin.derive_htlc_pubkey(&unique_data), - taker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker.to_bytes(), + maker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), + taker_pub: &self.negotiation_data.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(), diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index fa51bdf290..eeb1fb62a5 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -7,7 +7,7 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_s TAKER_SWAP_V2_TYPE}; use async_trait::async_trait; use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, - SendCombinedTakerPaymentArgs, SpendPaymentArgs, SwapOpsV2, ToBytes, WaitForHTLCTxSpendArgs}; + SendCombinedTakerPaymentArgs, SpendPaymentArgs, SwapOpsV2, ToBytes, Transaction, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -501,10 +501,7 @@ impl State fo let next_state = TakerPaymentSent { 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(), - }, + taker_payment, negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await @@ -528,7 +525,7 @@ impl StorableS struct TakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment: TransactionIdentifier, + taker_payment: TakerCoin::Tx, negotiation_data: NegotiationData, } @@ -545,7 +542,7 @@ impl State 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, + tx_bytes: self.taker_payment.tx_hex(), next_step_instructions: None, }; let swap_msg = SwapMessage { @@ -620,7 +617,10 @@ impl StorableS 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(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, negotiation_data: self.negotiation_data.to_stored_data(), } } @@ -635,7 +635,7 @@ enum TakerPaymentRefundReason { } struct TakerPaymentRefundRequired { - taker_payment: TransactionIdentifier, + taker_payment: TakerCoin::Tx, negotiation_data: NegotiationData, reason: TakerPaymentRefundReason, } @@ -671,7 +671,10 @@ impl <::Storage as StateMachineStorage>::Event { TakerSwapEvent::TakerPaymentRefundRequired { - taker_payment: self.taker_payment.clone(), + taker_payment: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, negotiation_data: self.negotiation_data.to_stored_data(), } } @@ -681,7 +684,7 @@ struct MakerPaymentConfirmed, } @@ -700,11 +703,11 @@ impl State let unique_data = state_machine.unique_data(); let args = GenTakerPaymentSpendArgs { - taker_tx: &self.taker_payment.tx_hex.0, + taker_tx: &self.taker_payment, time_lock: state_machine.taker_payment_locktime(), secret_hash: &self.negotiation_data.secret_hash, - maker_pub: &self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), - taker_pub: &state_machine.taker_coin.derive_htlc_pubkey(&unique_data), + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker, + taker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount: state_machine.dex_fee.to_decimal(), premium_amount: Default::default(), @@ -748,7 +751,7 @@ impl State ); let wait_args = WaitForHTLCTxSpendArgs { - tx_bytes: &self.taker_payment.tx_hex.0, + tx_bytes: &self.taker_payment.tx_hex(), secret_hash: &self.negotiation_data.secret_hash, wait_until: state_machine.taker_payment_locktime(), from_block: self.taker_coin_start_block, @@ -808,7 +811,10 @@ impl StorableS 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: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, negotiation_data: self.negotiation_data.to_stored_data(), } } @@ -819,7 +825,7 @@ struct TakerPaymentSpent { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, - taker_payment: TransactionIdentifier, + taker_payment: TakerCoin::Tx, taker_payment_spend: TransactionIdentifier, negotiation_data: NegotiationData, } @@ -886,7 +892,6 @@ impl State ); 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, @@ -911,20 +916,22 @@ impl StorableS 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: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, taker_payment_spend: self.taker_payment_spend.clone(), negotiation_data: self.negotiation_data.to_stored_data(), } } } -struct MakerPaymentSpent { +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: TakerCoin::Tx, taker_payment_spend: TransactionIdentifier, maker_payment_spend: TransactionIdentifier, } @@ -934,7 +941,9 @@ impl TransitionFrom StorableState for MakerPaymentSpent { +impl StorableState + for MakerPaymentSpent +{ type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { @@ -942,7 +951,10 @@ impl StorableState for Mak 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: TransactionIdentifier { + tx_hex: self.taker_payment.tx_hex().into(), + tx_hash: self.taker_payment.tx_hash(), + }, taker_payment_spend: self.taker_payment_spend.clone(), maker_payment_spend: self.maker_payment_spend.clone(), } @@ -950,7 +962,7 @@ impl StorableState for Mak } #[async_trait] -impl State +impl State for MakerPaymentSpent { type StateMachine = TakerSwapStateMachine; @@ -1061,4 +1073,7 @@ impl LastSta } } -impl TransitionFrom> for Completed {} +impl TransitionFrom> + for Completed +{ +} 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 f33807f6ee..dc7ffc34bf 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 @@ -2,11 +2,12 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; use coins::{GenTakerPaymentSpendArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, SwapOpsV2, Transaction, - TransactionEnum, ValidateTakerPaymentArgs}; + ValidateTakerPaymentArgs}; use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, Mm2TestConf}; use script::{Builder, Opcode}; +use serialization::serialize; #[test] fn send_and_refund_taker_payment() { @@ -25,12 +26,8 @@ fn send_and_refund_taker_payment() { trading_amount: 1.into(), swap_unique_data: &[], }; - 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), - }; + let taker_payment_utxo_tx = block_on(coin.send_combined_taker_payment(send_args)).unwrap(); + println!("{:02x}", taker_payment_utxo_tx.tx_hash()); // tx must have 3 outputs: actual payment, OP_RETURN containing the secret hash and change assert_eq!(3, taker_payment_utxo_tx.outputs.len()); @@ -44,10 +41,8 @@ fn send_and_refund_taker_payment() { .into_bytes(); assert_eq!(expected_op_return, taker_payment_utxo_tx.outputs[1].script_pubkey); - let taker_payment_bytes = taker_payment_utxo_tx.tx_hex(); - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_bytes, + taker_tx: &taker_payment_utxo_tx, time_lock, secret_hash, other_pub, @@ -59,7 +54,7 @@ fn send_and_refund_taker_payment() { block_on(coin.validate_combined_taker_payment(validate_args)).unwrap(); let refund_args = RefundPaymentArgs { - payment_tx: &taker_payment_bytes, + payment_tx: &serialize(&taker_payment_utxo_tx).take(), time_lock, other_pubkey: coin.my_public_key().unwrap(), secret_hash: &[0; 20], @@ -89,16 +84,11 @@ fn send_and_spend_taker_payment() { trading_amount: 1.into(), swap_unique_data: &[], }; - 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 taker_payment_utxo_tx = block_on(taker_coin.send_combined_taker_payment(send_args)).unwrap(); + println!("taker_payment_tx hash {:02x}", taker_payment_utxo_tx.tx_hash()); - let taker_payment_bytes = taker_payment_utxo_tx.tx_hex(); let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_bytes, + taker_tx: &taker_payment_utxo_tx, time_lock, secret_hash: secret_hash.as_slice(), other_pub: taker_coin.my_public_key().unwrap(), @@ -110,7 +100,7 @@ fn send_and_spend_taker_payment() { block_on(maker_coin.validate_combined_taker_payment(validate_args)).unwrap(); let gen_preimage_args = GenTakerPaymentSpendArgs { - taker_tx: &taker_payment_utxo_tx.tx_hex(), + taker_tx: &taker_payment_utxo_tx, time_lock, secret_hash: secret_hash.as_slice(), maker_pub: maker_coin.my_public_key().unwrap(), From 596fc22ebebc6087c07cc40ac297b6a794f7271e Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 26 Sep 2023 11:59:50 +0700 Subject: [PATCH 12/30] WIP. Protocol enhancement. --- mm2src/coins/lp_coins.rs | 48 +++++- mm2src/coins/test_coin.rs | 32 ++-- mm2src/coins/utxo/swap_proto_v2_scripts.rs | 70 +++++++-- mm2src/coins/utxo/utxo_common.rs | 128 +++++++++++++-- mm2src/coins/utxo/utxo_standard.rs | 34 ++-- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 4 +- .../tests/docker_tests/swap_proto_v2_tests.rs | 148 ++++++++++++++++-- 7 files changed, 400 insertions(+), 64 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 33ab0a703c..803ae5b367 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1061,14 +1061,45 @@ pub trait WatcherOps { ) -> Result, MmError>; } +/// Helper struct wrapping arguments for [SwapOpsV2::send_taker_funding] +pub struct SendTakerFundingArgs<'a> { + /// Taker will be able to refund the payment after this timestamp + pub time_lock: u64, + /// The hash of the secret generated by taker, this needs to be revealed for immediate refund + pub taker_secret_hash: &'a [u8], + /// Maker's pubkey + pub maker_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], +} + +/// Helper struct wrapping arguments for [SwapOpsV2::refund_taker_funding_secret] +#[derive(Clone, Debug)] +pub struct RefundFundingSecretArgs<'a, Tx, Pubkey> { + pub funding_tx: &'a Tx, + pub time_lock: u64, + pub maker_pubkey: &'a Pubkey, + pub taker_secret: &'a [u8], + pub taker_secret_hash: &'a [u8], + pub swap_contract_address: &'a Option, + pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, +} + /// 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], + pub maker_secret_hash: &'a [u8], /// Maker's pubkey - pub other_pub: &'a [u8], + pub maker_pub: &'a [u8], /// DEX fee amount pub dex_fee_amount: BigDecimal, /// Additional reward for maker (premium) @@ -1236,6 +1267,19 @@ pub trait CoinAssocTypes { /// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { + /// Generate and broadcast taker funding transaction that includes dex fee, maker premium and actual trading volume. + /// Funding tx can be reclaimed immediately if maker back-outs (doesn't send maker payment) + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result; + + /// Refunds taker funding transaction using time-locked path without secret reveal. + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; + + /// Reclaims taker funding transaction using immediate refund path with secret reveal. + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, + ) -> Result; + /// Generate and broadcast taker payment transaction that includes dex fee, maker premium and actual trading volume. async fn send_combined_taker_payment( &self, diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index f1b45bfa08..75b9ca64f7 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -5,16 +5,17 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransaction use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, - TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentResult, - ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundFundingSecretArgs, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, + SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageFut, TradePreimageResult, + TradePreimageValue, Transaction, TransactionErr, 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; @@ -411,6 +412,17 @@ impl CoinAssocTypes for TestCoin { #[async_trait] #[mockable] impl SwapOpsV2 for TestCoin { + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { todo!() } + + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { todo!() } + + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, + ) -> Result { + todo!() + } + async fn send_combined_taker_payment( &self, args: SendCombinedTakerPaymentArgs<'_>, diff --git a/mm2src/coins/utxo/swap_proto_v2_scripts.rs b/mm2src/coins/utxo/swap_proto_v2_scripts.rs index 153f0bc4bb..f0b5231e04 100644 --- a/mm2src/coins/utxo/swap_proto_v2_scripts.rs +++ b/mm2src/coins/utxo/swap_proto_v2_scripts.rs @@ -4,37 +4,79 @@ use bitcrypto::ripemd160; use keys::Public; use script::{Builder, Opcode, Script}; -/// Builds a script for refundable dex_fee + premium taker transaction -pub fn taker_payment_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: &Public) -> Script { +/// Builds a script for taker funding transaction +pub fn taker_funding_script( + time_lock: u32, + taker_secret_hash: &[u8], + taker_pub: &Public, + maker_pub: &Public, +) -> Script { let mut builder = Builder::default() - // Dex fee refund path, same lock time as for taker payment .push_opcode(Opcode::OP_IF) .push_bytes(&time_lock.to_le_bytes()) .push_opcode(Opcode::OP_CHECKLOCKTIMEVERIFY) .push_opcode(Opcode::OP_DROP) - .push_bytes(pub_0) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIG) + .push_opcode(Opcode::OP_ELSE) + .push_opcode(Opcode::OP_IF) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIGVERIFY) + .push_bytes(maker_pub) .push_opcode(Opcode::OP_CHECKSIG) - // Dex fee redeem path, Maker needs to reveal the secret to prevent case of getting - // the premium but not proceeding with spending the taker payment .push_opcode(Opcode::OP_ELSE) .push_opcode(Opcode::OP_SIZE) .push_bytes(&[32]) .push_opcode(Opcode::OP_EQUALVERIFY) .push_opcode(Opcode::OP_HASH160); - if secret_hash.len() == 32 { - builder = builder.push_bytes(ripemd160(secret_hash).as_slice()); + if taker_secret_hash.len() == 32 { + builder = builder.push_bytes(ripemd160(taker_secret_hash).as_slice()); } else { - builder = builder.push_bytes(secret_hash); + builder = builder.push_bytes(taker_secret_hash); } builder .push_opcode(Opcode::OP_EQUALVERIFY) - .push_opcode(Opcode::OP_2) - .push_bytes(pub_0) - .push_bytes(pub_1) - .push_opcode(Opcode::OP_2) - .push_opcode(Opcode::OP_CHECKMULTISIG) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIG) + .push_opcode(Opcode::OP_ENDIF) + .push_opcode(Opcode::OP_ENDIF) + .into_script() +} + +/// Builds a script for combined trading_volume + dex_fee + premium taker transaction +pub fn taker_payment_script( + time_lock: u32, + maker_secret_hash: &[u8], + taker_pub: &Public, + maker_pub: &Public, +) -> Script { + let mut builder = Builder::default() + .push_opcode(Opcode::OP_IF) + .push_bytes(&time_lock.to_le_bytes()) + .push_opcode(Opcode::OP_CHECKLOCKTIMEVERIFY) + .push_opcode(Opcode::OP_DROP) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIG) + .push_opcode(Opcode::OP_ELSE) + .push_opcode(Opcode::OP_SIZE) + .push_bytes(&[32]) + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_opcode(Opcode::OP_HASH160); + + if maker_secret_hash.len() == 32 { + builder = builder.push_bytes(ripemd160(maker_secret_hash).as_slice()); + } else { + builder = builder.push_bytes(maker_secret_hash); + } + + builder + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_bytes(taker_pub) + .push_opcode(Opcode::OP_CHECKSIGVERIFY) + .push_bytes(maker_pub) + .push_opcode(Opcode::OP_CHECKSIG) .push_opcode(Opcode::OP_ENDIF) .into_script() } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 5e22d2f7a9..932358d97c 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -17,16 +17,17 @@ use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWit use crate::watcher_common::validate_watcher_reward; use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, - RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, - SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, - SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TransactionResult, - 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}; + RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, + SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, + TradePreimageValue, TransactionFut, TransactionResult, 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}; @@ -62,6 +63,7 @@ use utxo_signer::with_key_pair::{calc_and_sign_sighash, p2sh_spend, signature_ha SIGHASH_SINGLE}; use utxo_signer::UtxoSignerOps; +use crate::utxo::swap_proto_v2_scripts::taker_funding_script; pub use chain::Transaction as UtxoTx; pub mod utxo_tx_history_v2_common; @@ -1409,9 +1411,8 @@ pub async fn sign_and_broadcast_taker_payment_spend( drop_mutability!(maker_signature_with_sighash); let script_sig = Builder::default() - .push_opcode(Opcode::OP_0) - .push_data(&taker_signature_with_sighash) .push_data(&maker_signature_with_sighash) + .push_data(&taker_signature_with_sighash) .push_data(secret) .push_opcode(Opcode::OP_0) .push_data(&redeem_script) @@ -1817,6 +1818,9 @@ async fn refund_htlc_payment( SwapPaymentType::TakerOrMakerPayment => { payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public).into() }, + SwapPaymentType::TakerFunding => { + taker_funding_script(time_lock, args.secret_hash, key_pair.public(), &other_public).into() + }, SwapPaymentType::TakerPaymentV2 => { swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public) .into() @@ -4185,6 +4189,7 @@ struct SwapPaymentOutputsResult { enum SwapPaymentType { TakerOrMakerPayment, + TakerFunding, TakerPaymentV2, } @@ -4204,6 +4209,7 @@ 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::TakerFunding => taker_funding_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) }, @@ -4561,6 +4567,100 @@ where .collect() } +/// Common implementation of taker funding generation and broadcast for UTXO coins. +pub async fn send_taker_funding(coin: T, args: SendTakerFundingArgs<'_>) -> Result +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 + &args.trading_amount; + + let SwapPaymentOutputsResult { + payment_address, + outputs, + } = try_tx_s!(generate_swap_payment_outputs( + &coin, + try_tx_s!(args.time_lock.try_into()), + taker_htlc_key_pair.public_slice(), + args.maker_pub, + args.taker_secret_hash, + total_amount, + SwapPaymentType::TakerFunding, + )); + if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { + let addr_string = try_tx_s!(payment_address.display_address()); + client + .import_address(&addr_string, &addr_string, false) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))) + .compat() + .await?; + } + send_outputs_from_my_address_impl(coin, outputs).await +} + +/// Common implementation of taker funding reclaim for UTXO coins using time-locked path. +pub async fn refund_taker_funding_timelock(coin: T, args: RefundPaymentArgs<'_>) -> TransactionResult +where + T: UtxoCommonOps + GetUtxoListOps + SwapOps, +{ + refund_htlc_payment(coin, args, SwapPaymentType::TakerFunding).await +} + +/// Common implementation of taker funding reclaim for UTXO coins using immediate refund path with secret reveal. +pub async fn refund_taker_funding_secret( + coin: T, + args: RefundFundingSecretArgs<'_, UtxoTx, Public>, +) -> Result +where + T: UtxoCommonOps + GetUtxoListOps + SwapOps, +{ + let my_address = try_tx_s!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); + let payment_value = try_tx_s!(args.funding_tx.first_output()).value; + + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); + let script_data = Builder::default() + .push_data(args.taker_secret) + .push_opcode(Opcode::OP_0) + .push_opcode(Opcode::OP_0) + .into_script(); + let time_lock = try_tx_s!(args.time_lock.try_into()); + + let redeem_script = + taker_funding_script(time_lock, args.taker_secret_hash, key_pair.public(), args.maker_pubkey).into(); + let fee = try_tx_s!( + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await + ); + if fee >= payment_value { + return TX_PLAIN_ERR!( + "HTLC spend fee {} is greater than transaction output {}", + fee, + payment_value + ); + } + let script_pubkey = output_script(&my_address, ScriptType::P2PKH).to_bytes(); + let output = TransactionOutput { + value: payment_value - fee, + script_pubkey, + }; + + let input = P2SHSpendingTxInput { + prev_transaction: args.funding_tx.clone(), + redeem_script, + outputs: vec![output], + script_data, + sequence: SEQUENCE_FINAL, + lock_time: time_lock, + keypair: &key_pair, + }; + let transaction = try_tx_s!(coin.p2sh_spending_tx(input).await); + + let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); + try_tx_s!(tx_fut.await, transaction); + + Ok(transaction.into()) +} + /// Common implementation of combined taker payment generation and broadcast for UTXO coins. pub async fn send_combined_taker_payment( coin: T, @@ -4579,8 +4679,8 @@ where &coin, try_tx_s!(args.time_lock.try_into()), taker_htlc_key_pair.public_slice(), - args.other_pub, - args.secret_hash, + args.maker_pub, + args.maker_secret_hash, total_amount, SwapPaymentType::TakerPaymentV2, )); diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index a4aca4e19d..509a186dab 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -25,15 +25,16 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, - 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}; + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, + SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, 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}; @@ -608,6 +609,21 @@ impl CoinAssocTypes for UtxoStandardCoin { #[async_trait] impl SwapOpsV2 for UtxoStandardCoin { + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { + utxo_common::send_taker_funding(self.clone(), args).await + } + + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { + utxo_common::refund_taker_funding_timelock(self.clone(), args).await + } + + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, + ) -> Result { + utxo_common::refund_taker_funding_secret(self.clone(), args).await + } + async fn send_combined_taker_payment( &self, args: SendCombinedTakerPaymentArgs<'_>, diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index eeb1fb62a5..10e641a248 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -476,8 +476,8 @@ impl State fo 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.negotiation_data.secret_hash, - other_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), + maker_secret_hash: &self.negotiation_data.secret_hash, + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee_amount: state_machine.dex_fee.to_decimal(), premium_amount: BigDecimal::from(0), trading_amount: state_machine.taker_volume.to_decimal(), 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 dc7ffc34bf..a136a1e54f 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,26 +1,148 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; -use coins::{GenTakerPaymentSpendArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, SwapOpsV2, Transaction, - ValidateTakerPaymentArgs}; +use coins::{GenTakerPaymentSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, + SendTakerFundingArgs, SwapOpsV2, Transaction, ValidateTakerPaymentArgs}; use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, Mm2TestConf}; use script::{Builder, Opcode}; use serialization::serialize; +#[test] +fn send_and_refund_taker_funding_timelock() { + let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + + let time_lock = now_sec() - 1000; + let taker_secret_hash = &[0; 20]; + let maker_pub = coin.my_public_key().unwrap(); + + let send_args = SendTakerFundingArgs { + time_lock, + taker_secret_hash, + maker_pub, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), + swap_unique_data: &[], + }; + let taker_funding_utxo_tx = block_on(coin.send_taker_funding(send_args)).unwrap(); + println!("{:02x}", taker_funding_utxo_tx.tx_hash()); + // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change + assert_eq!(3, taker_funding_utxo_tx.outputs.len()); + + // dex_fee_amount + premium_amount + trading_amount + let expected_amount = 111000000u64; + assert_eq!(expected_amount, taker_funding_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, taker_funding_utxo_tx.outputs[1].script_pubkey); + + /* + let validate_args = ValidateTakerPaymentArgs { + taker_tx: &taker_payment_utxo_tx, + time_lock, + secret_hash: maker_secret_hash, + other_pub: maker_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_combined_taker_payment(validate_args)).unwrap(); + */ + + let refund_args = RefundPaymentArgs { + payment_tx: &serialize(&taker_funding_utxo_tx).take(), + time_lock, + other_pubkey: coin.my_public_key().unwrap(), + secret_hash: &[0; 20], + swap_unique_data: &[], + swap_contract_address: &None, + watcher_reward: false, + }; + + let refund_tx = block_on(coin.refund_taker_funding_timelock(refund_args)).unwrap(); + println!("{:02x}", refund_tx.tx_hash()); +} + +#[test] +fn send_and_refund_taker_funding_secret() { + let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + + let time_lock = now_sec() - 1000; + let taker_secret = [0; 32]; + let taker_secret_hash = dhash160(&taker_secret); + let maker_pub = coin.my_public_key().unwrap(); + + let send_args = SendTakerFundingArgs { + time_lock, + taker_secret_hash: taker_secret_hash.as_slice(), + maker_pub, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), + swap_unique_data: &[], + }; + let taker_funding_utxo_tx = block_on(coin.send_taker_funding(send_args)).unwrap(); + println!("{:02x}", taker_funding_utxo_tx.tx_hash()); + // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change + assert_eq!(3, taker_funding_utxo_tx.outputs.len()); + + // dex_fee_amount + premium_amount + trading_amount + let expected_amount = 111000000u64; + assert_eq!(expected_amount, taker_funding_utxo_tx.outputs[0].value); + + let expected_op_return = Builder::default() + .push_opcode(Opcode::OP_RETURN) + .push_data(taker_secret_hash.as_slice()) + .into_bytes(); + assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); + + /* + let validate_args = ValidateTakerPaymentArgs { + taker_tx: &taker_payment_utxo_tx, + time_lock, + secret_hash: maker_secret_hash, + other_pub: maker_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_combined_taker_payment(validate_args)).unwrap(); + */ + + let refund_args = RefundFundingSecretArgs { + funding_tx: &taker_funding_utxo_tx, + time_lock, + maker_pubkey: maker_pub, + taker_secret: &taker_secret, + taker_secret_hash: taker_secret_hash.as_slice(), + swap_unique_data: &[], + swap_contract_address: &None, + watcher_reward: false, + }; + + let refund_tx = block_on(coin.refund_taker_funding_secret(refund_args)).unwrap(); + println!("{:02x}", refund_tx.tx_hash()); +} + #[test] 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() - 1000; - let secret_hash = &[0; 20]; - let other_pub = coin.my_public_key().unwrap(); + let maker_secret_hash = &[0; 20]; + let maker_pub = coin.my_public_key().unwrap(); let send_args = SendCombinedTakerPaymentArgs { time_lock, - secret_hash, - other_pub, + maker_secret_hash, + maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -44,8 +166,8 @@ fn send_and_refund_taker_payment() { let validate_args = ValidateTakerPaymentArgs { taker_tx: &taker_payment_utxo_tx, time_lock, - secret_hash, - other_pub, + secret_hash: maker_secret_hash, + other_pub: maker_pub, dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -74,11 +196,11 @@ fn send_and_spend_taker_payment() { let time_lock = now_sec() - 1000; let secret = [1; 32]; - let secret_hash = dhash160(&secret); + let maker_secret_hash = dhash160(&secret); let send_args = SendCombinedTakerPaymentArgs { time_lock, - secret_hash: secret_hash.as_slice(), - other_pub: maker_coin.my_public_key().unwrap(), + maker_secret_hash: maker_secret_hash.as_slice(), + maker_pub: maker_coin.my_public_key().unwrap(), dex_fee_amount: "0.01".parse().unwrap(), premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -90,7 +212,7 @@ fn send_and_spend_taker_payment() { let validate_args = ValidateTakerPaymentArgs { taker_tx: &taker_payment_utxo_tx, time_lock, - secret_hash: secret_hash.as_slice(), + secret_hash: maker_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(), @@ -102,7 +224,7 @@ fn send_and_spend_taker_payment() { let gen_preimage_args = GenTakerPaymentSpendArgs { taker_tx: &taker_payment_utxo_tx, time_lock, - secret_hash: secret_hash.as_slice(), + secret_hash: maker_secret_hash.as_slice(), maker_pub: maker_coin.my_public_key().unwrap(), taker_pub: taker_coin.my_public_key().unwrap(), dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, From c4a8c68a64e08823140ec906a55e701037f92ede Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 26 Sep 2023 14:26:36 +0700 Subject: [PATCH 13/30] WIP. Protocol enhancement. --- mm2src/coins/lp_coins.rs | 36 +++++++++--- mm2src/coins/test_coin.rs | 36 +++++++----- mm2src/coins/utxo/utxo_common.rs | 91 +++++++++++++++++++++++++++--- mm2src/coins/utxo/utxo_standard.rs | 29 ++++++---- 4 files changed, 152 insertions(+), 40 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 803ae5b367..f5a70bb390 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -313,8 +313,8 @@ pub type RawTransactionResult = Result = Box> + Send + 'a>; pub type RefundResult = Result>; -/// Helper type used for taker payment's spend preimage generation result -pub type GenTakerPaymentSpendResult = MmResult; +/// Helper type used for swap transactions' spend preimage generation result +pub type GenPreimageResult = 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 @@ -1080,7 +1080,6 @@ pub struct SendTakerFundingArgs<'a> { } /// Helper struct wrapping arguments for [SwapOpsV2::refund_taker_funding_secret] -#[derive(Clone, Debug)] pub struct RefundFundingSecretArgs<'a, Tx, Pubkey> { pub funding_tx: &'a Tx, pub time_lock: u64, @@ -1092,6 +1091,24 @@ pub struct RefundFundingSecretArgs<'a, Tx, Pubkey> { pub watcher_reward: bool, } +/// Helper struct wrapping arguments for [SwapOpsV2::gen_taker_funding_spend_preimage] +pub struct GenTakerFundingSpendArgs<'a, Tx, Pubkey> { + /// Taker payment transaction serialized to raw bytes + pub funding_tx: &'a Tx, + /// Maker's pubkey + pub maker_pub: &'a Pubkey, + /// Taker's pubkey + pub taker_pub: &'a Pubkey, + /// Timelock of the funding tx + pub funding_time_lock: u64, + /// The hash of the secret generated by taker + pub taker_secret_hash: &'a [u8], + /// Timelock of the taker payment + pub taker_payment_time_lock: u64, + /// The hash of the secret generated by maker + pub maker_secret_hash: &'a [u8], +} + /// 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 @@ -1171,10 +1188,6 @@ pub enum TxGenError { 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), /// Legacy error produced by usage of try_s/try_fus and other similar macros. @@ -1280,6 +1293,13 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, ) -> Result; + /// Generates and signs a preimage spending funding tx to the combined taker payment + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + swap_unique_data: &[u8], + ) -> GenPreimageResult; + /// Generate and broadcast taker payment transaction that includes dex fee, maker premium and actual trading volume. async fn send_combined_taker_payment( &self, @@ -1301,7 +1321,7 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { &self, args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, swap_unique_data: &[u8], - ) -> GenTakerPaymentSpendResult; + ) -> GenPreimageResult; /// Validate taker payment spend preimage on maker's side. async fn validate_taker_payment_spend_preimage( diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 75b9ca64f7..33a47b969b 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -3,19 +3,19 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, - CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenTakerPaymentSpendArgs, - GenTakerPaymentSpendResult, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundFundingSecretArgs, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, - SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageFut, TradePreimageResult, - TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateTakerPaymentArgs, ValidateTakerPaymentResult, ValidateTakerPaymentSpendPreimageResult, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawRequest}; + CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, + NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, + SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageFut, + TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, 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; @@ -423,6 +423,14 @@ impl SwapOpsV2 for TestCoin { todo!() } + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + swap_unique_data: &[u8], + ) -> GenPreimageResult { + todo!() + } + async fn send_combined_taker_payment( &self, args: SendCombinedTakerPaymentArgs<'_>, @@ -443,7 +451,7 @@ impl SwapOpsV2 for TestCoin { &self, args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, swap_unique_data: &[u8], - ) -> GenTakerPaymentSpendResult { + ) -> GenPreimageResult { unimplemented!() } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 932358d97c..5ccd583b71 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,10 +15,10 @@ 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, GenTakerPaymentSpendArgs, - GenTakerPaymentSpendResult, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, - RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, - SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, + RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, + RewardTarget, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, @@ -1221,14 +1221,89 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI }) } -pub type GenDexFeeSpendResult = MmResult; +type GenPreimageResInner = MmResult; + +async fn gen_taker_funding_spend_preimage( + coin: &T, + args: &GenTakerFundingSpendArgs<'_, UtxoTx, Public>, + lock_time: LocktimeSetting, + n_time: NTimeSetting, +) -> GenPreimageResInner { + let payment_time_lock = args + .taker_payment_time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; + + let payment_redeem_script = swap_proto_v2_scripts::taker_payment_script( + payment_time_lock, + args.maker_secret_hash, + args.taker_pub, + args.maker_pub, + ); + + let funding_amount = args.funding_tx.first_output().unwrap().value; + let fee = coin + .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await?; + + let payment_output = TransactionOutput { + value: funding_amount - fee, + script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&payment_redeem_script))).to_bytes(), + }; + + p2sh_spending_tx_preimage(coin, args.funding_tx, lock_time, n_time, SEQUENCE_FINAL, vec![ + payment_output, + ]) + .await + .map_to_mm(TxGenError::Legacy) +} + +pub async fn gen_and_sign_taker_funding_spend_preimage( + coin: &T, + args: &GenTakerFundingSpendArgs<'_, UtxoTx, Public>, + htlc_keypair: &KeyPair, +) -> GenPreimageResult { + let funding_time_lock = args + .funding_time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; + + let preimage = gen_taker_funding_spend_preimage( + coin, + args, + LocktimeSetting::CalcByHtlcLocktime(funding_time_lock), + NTimeSetting::UseNow, + ) + .await?; + + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + funding_time_lock, + args.taker_secret_hash, + args.taker_pub, + args.maker_pub, + ); + let signature = calc_and_sign_sighash( + &preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id, + )?; + let preimage_tx: UtxoTx = preimage.into(); + Ok(TxPreimageWithSig { + preimage: serialize(&preimage_tx).take(), + signature: signature.take(), + }) +} async fn gen_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, lock_time: LocktimeSetting, n_time: NTimeSetting, -) -> GenDexFeeSpendResult { +) -> GenPreimageResInner { let dex_fee_sat = sat_from_big_decimal(&args.dex_fee_amount, coin.as_ref().decimals)?; let dex_fee_address = address_from_raw_pubkey( @@ -1256,7 +1331,7 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, htlc_keypair: &KeyPair, -) -> GenTakerPaymentSpendResult { +) -> GenPreimageResult { let time_lock = args .time_lock .try_into() @@ -4658,7 +4733,7 @@ where let tx_fut = coin.as_ref().rpc_client.send_transaction(&transaction).compat(); try_tx_s!(tx_fut.await, transaction); - Ok(transaction.into()) + Ok(transaction) } /// Common implementation of combined taker payment generation and broadcast for UTXO coins. diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 509a186dab..6bfc386bcb 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,15 +23,15 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, CoinBalance, CoinWithDerivationMethod, - ConfirmPaymentInput, GenTakerPaymentSpendArgs, GenTakerPaymentSpendResult, GetWithdrawSenderAddress, - IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, - SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, - TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentResult, + ConfirmPaymentInput, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, + RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, + SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, 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}; @@ -624,6 +624,15 @@ impl SwapOpsV2 for UtxoStandardCoin { utxo_common::refund_taker_funding_secret(self.clone(), args).await } + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + swap_unique_data: &[u8], + ) -> GenPreimageResult { + let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); + utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await + } + async fn send_combined_taker_payment( &self, args: SendCombinedTakerPaymentArgs<'_>, @@ -646,7 +655,7 @@ impl SwapOpsV2 for UtxoStandardCoin { &self, args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, swap_unique_data: &[u8], - ) -> GenTakerPaymentSpendResult { + ) -> GenPreimageResult { let key_pair = self.derive_htlc_key_pair(swap_unique_data); utxo_common::gen_and_sign_taker_payment_spend_preimage(self, args, &key_pair).await } From fffab294b2dd2a53908812a588946352055c281e Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 26 Sep 2023 16:04:20 +0700 Subject: [PATCH 14/30] WIP. Protocol enhancement. --- mm2src/coins/lp_coins.rs | 8 +++ mm2src/coins/test_coin.rs | 9 +++ mm2src/coins/utxo/utxo_common.rs | 60 +++++++++++++++++++ mm2src/coins/utxo/utxo_standard.rs | 10 ++++ .../tests/docker_tests/swap_proto_v2_tests.rs | 49 +++++++++++++++ 5 files changed, 136 insertions(+) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f5a70bb390..0b5260f746 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1300,6 +1300,14 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { swap_unique_data: &[u8], ) -> GenPreimageResult; + /// Generates and signs a preimage spending funding tx to the combined taker payment + async fn sign_and_send_taker_funding_spend( + &self, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + swap_unique_data: &[u8], + ) -> GenPreimageResult; + /// Generate and broadcast taker payment transaction that includes dex fee, maker premium and actual trading volume. async fn send_combined_taker_payment( &self, diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 33a47b969b..8f5ca35ebc 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -431,6 +431,15 @@ impl SwapOpsV2 for TestCoin { todo!() } + async fn sign_and_send_taker_funding_spend( + &self, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + swap_unique_data: &[u8], + ) -> GenPreimageResult { + todo!() + } + async fn send_combined_taker_payment( &self, args: SendCombinedTakerPaymentArgs<'_>, diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 5ccd583b71..69d66ba322 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1298,6 +1298,66 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( }) } +/// 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_send_taker_funding_spend( + coin: &T, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerFundingSpendArgs<'_, UtxoTx, Public>, + htlc_keypair: &KeyPair, +) -> TransactionResult { + 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 redeem_script = swap_proto_v2_scripts::taker_funding_script( + try_tx_s!(gen_args.funding_time_lock.try_into()), + gen_args.taker_secret_hash, + gen_args.taker_pub, + htlc_keypair.public(), + ); + + let mut signer: TransactionInputSigner = preimage_tx.clone().into(); + let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); + let payment_output = try_tx_s!(gen_args.taker_tx.first_output()); + payment_input.amount = payment_output.value; + signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; + + let maker_signature = try_tx_s!(calc_and_sign_sighash( + &signer, + DEFAULT_SWAP_VOUT, + &redeem_script, + htlc_keypair, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id + )); + let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; + + let mut taker_signature_with_sighash = preimage.signature.clone(); + taker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(taker_signature_with_sighash); + + let mut maker_signature_with_sighash: Vec = maker_signature.take(); + maker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(maker_signature_with_sighash); + + let script_sig = Builder::default() + .push_data(&maker_signature_with_sighash) + .push_data(&taker_signature_with_sighash) + .push_opcode(Opcode::OP_1) + .push_opcode(Opcode::OP_0) + .push_data(&redeem_script) + .into_bytes(); + let mut final_tx: UtxoTx = signer.into(); + let final_tx_input = try_tx_s!(final_tx.inputs.first_mut().ok_or("Final tx doesn't have inputs")); + final_tx_input.script_sig = script_sig; + drop_mutability!(final_tx); + + try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); + Ok(final_tx.into()) +} + async fn gen_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 6bfc386bcb..9ecbd386ab 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -633,6 +633,16 @@ impl SwapOpsV2 for UtxoStandardCoin { utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await } + async fn sign_and_send_taker_funding_spend( + &self, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + swap_unique_data: &[u8], + ) -> Result { + let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); + utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await + } + async fn send_combined_taker_payment( &self, args: SendCombinedTakerPaymentArgs<'_>, 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 a136a1e54f..e9a40686be 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 @@ -131,6 +131,55 @@ fn send_and_refund_taker_funding_secret() { println!("{:02x}", refund_tx.tx_hash()); } +#[test] +fn send_and_spend_taker_funding() { + let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + + let time_lock = now_sec() - 1000; + let taker_secret_hash = &[0; 20]; + let maker_pub = coin.my_public_key().unwrap(); + + let send_args = SendTakerFundingArgs { + time_lock, + taker_secret_hash, + maker_pub, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), + swap_unique_data: &[], + }; + let taker_funding_utxo_tx = block_on(coin.send_taker_funding(send_args)).unwrap(); + println!("{:02x}", taker_funding_utxo_tx.tx_hash()); + // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change + assert_eq!(3, taker_funding_utxo_tx.outputs.len()); + + // dex_fee_amount + premium_amount + trading_amount + let expected_amount = 111000000u64; + assert_eq!(expected_amount, taker_funding_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, taker_funding_utxo_tx.outputs[1].script_pubkey); + + /* + let validate_args = ValidateTakerPaymentArgs { + taker_tx: &taker_payment_utxo_tx, + time_lock, + secret_hash: maker_secret_hash, + other_pub: maker_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_combined_taker_payment(validate_args)).unwrap(); + */ + + // let preimage_args +} + #[test] fn send_and_refund_taker_payment() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); From 6c6c86f68ed9679a4677f59da08ee058519a2c0d Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Tue, 26 Sep 2023 18:16:53 +0700 Subject: [PATCH 15/30] WIP. Protocol enhancement. --- mm2src/coins/lp_coins.rs | 74 ++++++------ mm2src/coins/test_coin.rs | 34 +++--- mm2src/coins/utxo.rs | 32 +++++- mm2src/coins/utxo/utxo_common.rs | 106 ++++++++---------- mm2src/coins/utxo/utxo_standard.rs | 64 ++++------- .../tests/docker_tests/swap_proto_v2_tests.rs | 43 +++---- 6 files changed, 183 insertions(+), 170 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 0b5260f746..47fde144f1 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -314,7 +314,7 @@ pub type RawTransactionFut<'a> = Box> + Send + 'a>; pub type RefundResult = Result>; /// Helper type used for swap transactions' spend preimage generation result -pub type GenPreimageResult = MmResult; +pub type GenPreimageResult = MmResult, TxGenError>; /// 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 @@ -1080,10 +1080,10 @@ pub struct SendTakerFundingArgs<'a> { } /// Helper struct wrapping arguments for [SwapOpsV2::refund_taker_funding_secret] -pub struct RefundFundingSecretArgs<'a, Tx, Pubkey> { - pub funding_tx: &'a Tx, +pub struct RefundFundingSecretArgs<'a, Coin: CoinAssocTypes + ?Sized> { + pub funding_tx: &'a Coin::Tx, pub time_lock: u64, - pub maker_pubkey: &'a Pubkey, + pub maker_pubkey: &'a Coin::Pubkey, pub taker_secret: &'a [u8], pub taker_secret_hash: &'a [u8], pub swap_contract_address: &'a Option, @@ -1092,13 +1092,13 @@ pub struct RefundFundingSecretArgs<'a, Tx, Pubkey> { } /// Helper struct wrapping arguments for [SwapOpsV2::gen_taker_funding_spend_preimage] -pub struct GenTakerFundingSpendArgs<'a, Tx, Pubkey> { +pub struct GenTakerFundingSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes - pub funding_tx: &'a Tx, + pub funding_tx: &'a Coin::Tx, /// Maker's pubkey - pub maker_pub: &'a Pubkey, + pub maker_pub: &'a Coin::Pubkey, /// Taker's pubkey - pub taker_pub: &'a Pubkey, + pub taker_pub: &'a Coin::Pubkey, /// Timelock of the funding tx pub funding_time_lock: u64, /// The hash of the secret generated by taker @@ -1128,15 +1128,15 @@ pub struct SendCombinedTakerPaymentArgs<'a> { } /// Helper struct wrapping arguments for [SwapOpsV2::validate_combined_taker_payment] -pub struct ValidateTakerPaymentArgs<'a, Tx, Pubkey> { +pub struct ValidateTakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a Tx, + pub taker_tx: &'a Coin::Tx, /// 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 Pubkey, + pub other_pub: &'a Coin::Pubkey, /// DEX fee amount pub dex_fee_amount: BigDecimal, /// Additional reward for maker (premium) @@ -1150,17 +1150,17 @@ pub struct ValidateTakerPaymentArgs<'a, Tx, Pubkey> { /// 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, Tx, Pubkey> { +pub struct GenTakerPaymentSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a Tx, + pub taker_tx: &'a Coin::Tx, /// 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 Pubkey, + pub maker_pub: &'a Coin::Pubkey, /// Taker's pubkey - pub taker_pub: &'a Pubkey, + pub taker_pub: &'a Coin::Pubkey, /// Pubkey of address, receiving DEX fees pub dex_fee_pub: &'a [u8], /// DEX fee amount @@ -1172,11 +1172,11 @@ pub struct GenTakerPaymentSpendArgs<'a, Tx, Pubkey> { } /// Taker payment spend preimage with taker's signature -pub struct TxPreimageWithSig { - /// The preimage tx serialized to raw bytes, might be empty for certain coin protocols - pub preimage: Vec, +pub struct TxPreimageWithSig { + /// The preimage, might be () for certain coin types (only signature might be used) + pub preimage: Coin::Preimage, /// Taker's signature - pub signature: Vec, + pub signature: Coin::Sig, } /// Enum covering error cases that can happen during taker payment spend preimage generation. @@ -1242,8 +1242,6 @@ pub enum ValidateTakerPaymentSpendPreimageError { 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. @@ -1271,10 +1269,18 @@ pub trait CoinAssocTypes { type PubkeyParseError: Send + std::fmt::Display; type Tx: Transaction + Send + Sync; type TxParseError: Send + std::fmt::Display; + type Preimage: Send + Sync; + type PreimageParseError: Send + std::fmt::Display; + type Sig: Send + Sync; + type SigParseError: Send + std::fmt::Display; fn parse_pubkey(&self, pubkey: &[u8]) -> Result; fn parse_tx(&self, tx: &[u8]) -> Result; + + fn parse_preimage(&self, tx: &[u8]) -> Result; + + fn parse_signature(&self, sig: &[u8]) -> Result; } /// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) @@ -1290,23 +1296,23 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { /// Reclaims taker funding transaction using immediate refund path with secret reveal. async fn refund_taker_funding_secret( &self, - args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, + args: RefundFundingSecretArgs<'_, Self>, ) -> Result; /// Generates and signs a preimage spending funding tx to the combined taker payment async fn gen_taker_funding_spend_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult; + ) -> GenPreimageResult; /// Generates and signs a preimage spending funding tx to the combined taker payment async fn sign_and_send_taker_funding_spend( &self, - preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult; + ) -> Result; /// Generate and broadcast taker payment transaction that includes dex fee, maker premium and actual trading volume. async fn send_combined_taker_payment( @@ -1317,7 +1323,7 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { /// Validates taker payment transaction. async fn validate_combined_taker_payment( &self, - args: ValidateTakerPaymentArgs<'_, Self::Tx, Self::Pubkey>, + args: ValidateTakerPaymentArgs<'_, Self>, ) -> ValidateTakerPaymentResult; /// Refunds taker payment transaction. @@ -1327,22 +1333,22 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { /// shared with maker to proceed with protocol execution. async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, + args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult; + ) -> GenPreimageResult; /// Validate taker payment spend preimage on maker's side. async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult; /// Sign and broadcast taker payment spend on maker's side. async fn sign_and_broadcast_taker_payment_spend( &self, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_, Self::Tx, Self::Pubkey>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult; diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 8f5ca35ebc..dc698a5008 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -403,10 +403,18 @@ impl CoinAssocTypes for TestCoin { type PubkeyParseError = String; type Tx = TestTx; type TxParseError = String; + type Preimage = (); + type PreimageParseError = String; + type Sig = (); + type SigParseError = String; fn parse_pubkey(&self, pubkey: &[u8]) -> Result { unimplemented!() } fn parse_tx(&self, tx: &[u8]) -> Result { unimplemented!() } + + fn parse_preimage(&self, tx: &[u8]) -> Result { todo!() } + + fn parse_signature(&self, tx: &[u8]) -> Result { todo!() } } #[async_trait] @@ -418,25 +426,25 @@ impl SwapOpsV2 for TestCoin { async fn refund_taker_funding_secret( &self, - args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, + args: RefundFundingSecretArgs<'_, Self>, ) -> Result { todo!() } async fn gen_taker_funding_spend_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult { + ) -> GenPreimageResult { todo!() } async fn sign_and_send_taker_funding_spend( &self, - preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult { + ) -> Result { todo!() } @@ -449,7 +457,7 @@ impl SwapOpsV2 for TestCoin { async fn validate_combined_taker_payment( &self, - args: ValidateTakerPaymentArgs<'_, TestTx, TestPubkey>, + args: ValidateTakerPaymentArgs<'_, Self>, ) -> ValidateTakerPaymentResult { unimplemented!() } @@ -458,24 +466,24 @@ impl SwapOpsV2 for TestCoin { async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, + args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult { + ) -> GenPreimageResult { unimplemented!() } async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { unimplemented!() } async fn sign_and_broadcast_taker_payment_spend( &self, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_, TestTx, TestPubkey>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index ab1fee5bde..54584c9d74 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -60,6 +60,7 @@ use futures::compat::Future01CompatExt; use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use futures01::Future; use keys::bytes::Bytes; +use keys::Signature; pub use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHashEnum, KeyPair, Private, Public, Secret, Type as ScriptType}; #[cfg(not(target_arch = "wasm32"))] @@ -75,7 +76,7 @@ use primitives::hash::{H160, H256, H264}; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use script::{Builder, Script, SignatureVersion, TransactionInputSigner}; use serde_json::{self as json, Value as Json}; -use serialization::{serialize, serialize_with_flags, Error as SerError, SERIALIZE_TRANSACTION_WITNESS}; +use serialization::{deserialize, serialize, serialize_with_flags, Error as SerError, SERIALIZE_TRANSACTION_WITNESS}; use spv_validation::conf::SPVConf; use spv_validation::helpers_validation::SPVError; use spv_validation::storage::BlockHeaderStorageError; @@ -110,6 +111,7 @@ use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDAddressId, HD InvalidBip44ChainError}; use crate::hd_wallet_storage::{HDAccountStorageItem, HDWalletCoinStorage, HDWalletStorageError, HDWalletStorageResult}; use crate::utxo::tx_cache::UtxoVerboseCacheShared; +use crate::CoinAssocTypes; pub mod tx_cache; @@ -1011,6 +1013,34 @@ pub trait UtxoCommonOps: } } +impl CoinAssocTypes for T { + type Pubkey = Public; + type PubkeyParseError = MmError; + type Tx = UtxoTx; + type TxParseError = MmError; + type Preimage = UtxoTx; + type PreimageParseError = MmError; + type Sig = Signature; + type SigParseError = String; + + #[inline] + fn parse_pubkey(&self, pubkey: &[u8]) -> Result { + Ok(Public::from_slice(pubkey)?) + } + + #[inline] + fn parse_tx(&self, tx: &[u8]) -> Result { + let mut tx: UtxoTx = deserialize(tx)?; + tx.tx_hash_algo = self.as_ref().tx_hash_algo; + Ok(tx) + } + + #[inline] + fn parse_preimage(&self, tx: &[u8]) -> Result { self.parse_tx(tx) } + + fn parse_signature(&self, sig: &[u8]) -> Result { todo!() } +} + #[async_trait] #[cfg_attr(test, mockable)] pub trait GetUtxoListOps { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 69d66ba322..4ad2e52ba1 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,19 +15,19 @@ 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, GenPreimageResult, - GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, - RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, - RewardTarget, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, - TradePreimageValue, TransactionFut, TransactionResult, 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::{CanRefundHtlc, CoinAssocTypes, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, + GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, + HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, + RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, + SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, + SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, + TransactionResult, 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}; @@ -43,7 +43,7 @@ use futures01::future::Either; use itertools::Itertools; use keys::bytes::Bytes; use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHashEnum, CompactSignature, Public, SegwitAddress, - Type as ScriptType}; + Signature, Type as ScriptType}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::bigdecimal_custom::CheckedDivision; @@ -51,7 +51,7 @@ use mm2_number::{BigDecimal, MmNumber}; use primitives::hash::H512; use rpc::v1::types::{Bytes as BytesJson, ToTxHash, TransactionInputEnum, H256 as H256Json}; use script::{Builder, Opcode, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput}; -use secp256k1::{PublicKey, Signature}; +use secp256k1::PublicKey; use serde_json::{self as json}; use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Serializable, Stream, SERIALIZE_TRANSACTION_WITNESS}; @@ -1223,9 +1223,9 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI type GenPreimageResInner = MmResult; -async fn gen_taker_funding_spend_preimage( +async fn gen_taker_funding_spend_preimage>( coin: &T, - args: &GenTakerFundingSpendArgs<'_, UtxoTx, Public>, + args: &GenTakerFundingSpendArgs<'_, T>, lock_time: LocktimeSetting, n_time: NTimeSetting, ) -> GenPreimageResInner { @@ -1260,9 +1260,9 @@ async fn gen_taker_funding_spend_preimage( pub async fn gen_and_sign_taker_funding_spend_preimage( coin: &T, - args: &GenTakerFundingSpendArgs<'_, UtxoTx, Public>, + args: &GenTakerFundingSpendArgs<'_, T>, htlc_keypair: &KeyPair, -) -> GenPreimageResult { +) -> GenPreimageResult { let funding_time_lock = args .funding_time_lock .try_into() @@ -1291,25 +1291,20 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( SIGHASH_ALL, coin.as_ref().conf.fork_id, )?; - let preimage_tx: UtxoTx = preimage.into(); Ok(TxPreimageWithSig { - preimage: serialize(&preimage_tx).take(), - signature: signature.take(), + preimage: preimage.into(), + signature, }) } /// 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_send_taker_funding_spend( +pub async fn sign_and_send_taker_funding_spend( coin: &T, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerFundingSpendArgs<'_, UtxoTx, Public>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerFundingSpendArgs<'_, T>, htlc_keypair: &KeyPair, -) -> TransactionResult { - 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); - +) -> Result { let redeem_script = swap_proto_v2_scripts::taker_funding_script( try_tx_s!(gen_args.funding_time_lock.try_into()), gen_args.taker_secret_hash, @@ -1317,10 +1312,10 @@ pub async fn sign_and_send_taker_funding_spend( htlc_keypair.public(), ); - let mut signer: TransactionInputSigner = preimage_tx.clone().into(); + let mut signer: TransactionInputSigner = preimage.preimage.clone().into(); let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); - let payment_output = try_tx_s!(gen_args.taker_tx.first_output()); - payment_input.amount = payment_output.value; + let funding_output = try_tx_s!(gen_args.funding_tx.first_output()); + payment_input.amount = funding_output.value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; let maker_signature = try_tx_s!(calc_and_sign_sighash( @@ -1334,7 +1329,7 @@ pub async fn sign_and_send_taker_funding_spend( )); let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; - let mut taker_signature_with_sighash = preimage.signature.clone(); + let mut taker_signature_with_sighash = preimage.signature.to_vec(); taker_signature_with_sighash.push(sig_hash_all_fork_id); drop_mutability!(taker_signature_with_sighash); @@ -1355,12 +1350,12 @@ pub async fn sign_and_send_taker_funding_spend( drop_mutability!(final_tx); try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); - Ok(final_tx.into()) + Ok(final_tx) } -async fn gen_taker_payment_spend_preimage( +async fn gen_taker_payment_spend_preimage( coin: &T, - args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, + args: &GenTakerPaymentSpendArgs<'_, T>, lock_time: LocktimeSetting, n_time: NTimeSetting, ) -> GenPreimageResInner { @@ -1387,11 +1382,11 @@ async fn gen_taker_payment_spend_preimage( .map_to_mm(TxGenError::Legacy) } -pub async fn gen_and_sign_taker_payment_spend_preimage( +pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, - args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, + args: &GenTakerPaymentSpendArgs<'_, T>, htlc_keypair: &KeyPair, -) -> GenPreimageResult { +) -> GenPreimageResult { let time_lock = args .time_lock .try_into() @@ -1427,12 +1422,9 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( /// Checks taker's signature and compares received preimage with the expected tx. pub async fn validate_taker_payment_spend_preimage( coin: &T, - gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, T>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { - let actual_preimage_tx: UtxoTx = deserialize(preimage.preimage.as_slice()) - .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::TxDeserialization(e.to_string()))?; - // TODO validate that output amounts are larger than dust // Here, we have to use the exact lock time from the preimage because maker @@ -1440,8 +1432,8 @@ pub async fn validate_taker_payment_spend_preimage( let expected_preimage = gen_taker_payment_spend_preimage( coin, gen_args, - LocktimeSetting::UseExact(actual_preimage_tx.lock_time), - NTimeSetting::UseValue(actual_preimage_tx.n_time), + LocktimeSetting::UseExact(preimage.preimage.lock_time), + NTimeSetting::UseValue(preimage.preimage.n_time), ) .await?; @@ -1466,13 +1458,13 @@ pub async fn validate_taker_payment_spend_preimage( if !gen_args .taker_pub - .verify(&sig_hash, &preimage.signature.clone().into()) + .verify(&sig_hash, &preimage.signature) .map_to_mm(|e| ValidateTakerPaymentSpendPreimageError::SignatureVerificationFailure(e.to_string()))? { return MmError::err(ValidateTakerPaymentSpendPreimageError::InvalidTakerSignature); }; let expected_preimage_tx: UtxoTx = expected_preimage.into(); - if expected_preimage_tx != actual_preimage_tx { + if expected_preimage_tx != preimage.preimage { return MmError::err(ValidateTakerPaymentSpendPreimageError::InvalidPreimage( "Preimage is not equal to expected".into(), )); @@ -1484,15 +1476,11 @@ pub async fn validate_taker_payment_spend_preimage( /// 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: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, T>, secret: &[u8], htlc_keypair: &KeyPair, ) -> TransactionResult { - 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::taker_payment_script( try_tx_s!(gen_args.time_lock.try_into()), @@ -1501,7 +1489,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( htlc_keypair.public(), ); - let mut signer: TransactionInputSigner = preimage_tx.clone().into(); + let mut signer: TransactionInputSigner = preimage.preimage.clone().into(); let payment_input = try_tx_s!(signer.inputs.first_mut().ok_or("Preimage doesn't have inputs")); let payment_output = try_tx_s!(gen_args.taker_tx.first_output()); payment_input.amount = payment_output.value; @@ -1536,7 +1524,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( coin.as_ref().conf.fork_id )); 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(); + let mut taker_signature_with_sighash = preimage.signature.to_vec(); taker_signature_with_sighash.push(sig_hash_single_fork_id); drop_mutability!(taker_signature_with_sighash); @@ -4744,7 +4732,7 @@ where /// Common implementation of taker funding reclaim for UTXO coins using immediate refund path with secret reveal. pub async fn refund_taker_funding_secret( coin: T, - args: RefundFundingSecretArgs<'_, UtxoTx, Public>, + args: RefundFundingSecretArgs<'_, T>, ) -> Result where T: UtxoCommonOps + GetUtxoListOps + SwapOps, @@ -4833,7 +4821,7 @@ where /// Common implementation of combined taker payment validation for UTXO coins. pub async fn validate_combined_taker_payment( coin: &T, - args: ValidateTakerPaymentArgs<'_, UtxoTx, Public>, + args: ValidateTakerPaymentArgs<'_, T>, ) -> ValidateTakerPaymentResult where T: UtxoCommonOps + SwapOps, diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 9ecbd386ab..6c7ca8c9d7 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -22,16 +22,16 @@ use crate::tx_history_storage::{GetTxHistoryFilters, WalletId}; use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, CoinBalance, CoinWithDerivationMethod, - ConfirmPaymentInput, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, - GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, - RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, - SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, - TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentResult, +use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, + GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, + IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, + SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, 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}; @@ -40,7 +40,6 @@ use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; use mm2_metrics::MetricsArc; use mm2_number::MmNumber; -use serialization::deserialize; use utxo_signer::UtxoSignerOps; #[derive(Clone)] @@ -588,25 +587,6 @@ impl ToBytes for Public { fn to_bytes(&self) -> Vec { self.to_vec() } } -impl CoinAssocTypes for UtxoStandardCoin { - type Pubkey = Public; - type PubkeyParseError = MmError; - type Tx = UtxoTx; - type TxParseError = MmError; - - #[inline] - fn parse_pubkey(&self, pubkey: &[u8]) -> Result { - Ok(Public::from_slice(pubkey)?) - } - - #[inline] - fn parse_tx(&self, tx: &[u8]) -> Result { - let mut tx: UtxoTx = deserialize(tx)?; - tx.tx_hash_algo = self.as_ref().tx_hash_algo; - Ok(tx) - } -} - #[async_trait] impl SwapOpsV2 for UtxoStandardCoin { async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { @@ -619,24 +599,24 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn refund_taker_funding_secret( &self, - args: RefundFundingSecretArgs<'_, Self::Tx, Self::Pubkey>, + args: RefundFundingSecretArgs<'_, Self>, ) -> Result { utxo_common::refund_taker_funding_secret(self.clone(), args).await } async fn gen_taker_funding_spend_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult { + ) -> GenPreimageResult { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await } async fn sign_and_send_taker_funding_spend( &self, - preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self::Tx, Self::Pubkey>, + preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, swap_unique_data: &[u8], ) -> Result { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); @@ -652,7 +632,7 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn validate_combined_taker_payment( &self, - args: ValidateTakerPaymentArgs<'_, Self::Tx, Self::Pubkey>, + args: ValidateTakerPaymentArgs<'_, Self>, ) -> ValidateTakerPaymentResult { utxo_common::validate_combined_taker_payment(self, args).await } @@ -663,25 +643,25 @@ impl SwapOpsV2 for UtxoStandardCoin { async fn gen_taker_payment_spend_preimage( &self, - args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, + args: &GenTakerPaymentSpendArgs<'_, Self>, swap_unique_data: &[u8], - ) -> GenPreimageResult { + ) -> GenPreimageResult { let key_pair = self.derive_htlc_key_pair(swap_unique_data); utxo_common::gen_and_sign_taker_payment_spend_preimage(self, args, &key_pair).await } async fn validate_taker_payment_spend_preimage( &self, - gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, - preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { utxo_common::validate_taker_payment_spend_preimage(self, gen_args, preimage).await } async fn sign_and_broadcast_taker_payment_spend( &self, - preimage: &TxPreimageWithSig, - gen_args: &GenTakerPaymentSpendArgs<'_, UtxoTx, Public>, + preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, secret: &[u8], swap_unique_data: &[u8], ) -> TransactionResult { 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 e9a40686be..f5844b217d 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,8 +1,8 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; -use coins::{GenTakerPaymentSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, - SendTakerFundingArgs, SwapOpsV2, Transaction, ValidateTakerPaymentArgs}; +use coins::{GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, + SendCombinedTakerPaymentArgs, SendTakerFundingArgs, SwapOpsV2, Transaction, ValidateTakerPaymentArgs}; use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, Mm2TestConf}; @@ -133,14 +133,17 @@ fn send_and_refund_taker_funding_secret() { #[test] fn send_and_spend_taker_funding() { - let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + let (_mm_arc, taker_coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); + let (_mm_arc, maker_coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec() - 1000; + let funding_time_lock = now_sec() - 1000; let taker_secret_hash = &[0; 20]; - let maker_pub = coin.my_public_key().unwrap(); + + let taker_pub = taker_coin.my_public_key().unwrap(); + let maker_pub = maker_coin.my_public_key().unwrap(); let send_args = SendTakerFundingArgs { - time_lock, + time_lock: funding_time_lock, taker_secret_hash, maker_pub, dex_fee_amount: "0.01".parse().unwrap(), @@ -148,8 +151,8 @@ fn send_and_spend_taker_funding() { trading_amount: 1.into(), swap_unique_data: &[], }; - let taker_funding_utxo_tx = block_on(coin.send_taker_funding(send_args)).unwrap(); - println!("{:02x}", taker_funding_utxo_tx.tx_hash()); + let taker_funding_utxo_tx = block_on(taker_coin.send_taker_funding(send_args)).unwrap(); + println!("Funding tx {:02x}", taker_funding_utxo_tx.tx_hash()); // tx must have 3 outputs: actual funding, OP_RETURN containing the secret hash and change assert_eq!(3, taker_funding_utxo_tx.outputs.len()); @@ -163,21 +166,19 @@ fn send_and_spend_taker_funding() { .into_bytes(); assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); - /* - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_utxo_tx, - time_lock, - secret_hash: maker_secret_hash, - other_pub: maker_pub, - dex_fee_amount: "0.01".parse().unwrap(), - premium_amount: "0.1".parse().unwrap(), - trading_amount: 1.into(), - swap_unique_data: &[], + let preimage_args = GenTakerFundingSpendArgs { + funding_tx: &taker_funding_utxo_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash, + taker_payment_time_lock: 0, + maker_secret_hash: &[0; 20], }; - block_on(coin.validate_combined_taker_payment(validate_args)).unwrap(); - */ + let preimage = block_on(taker_coin.gen_taker_funding_spend_preimage(&preimage_args, &[])).unwrap(); - // let preimage_args + let payment_tx = block_on(maker_coin.sign_and_send_taker_funding_spend(&preimage, &preimage_args, &[])).unwrap(); + println!("Taker payment tx {:02x}", payment_tx.tx_hash()); } #[test] From 01670f76710b3023c49df62104e251fa61e724bb Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Wed, 27 Sep 2023 14:47:53 +0700 Subject: [PATCH 16/30] WIP. Demo of maker trying to use taker preimage signature for refund script path. --- mm2src/coins/lp_coins.rs | 8 +-- mm2src/coins/test_coin.rs | 20 ++++-- mm2src/coins/utxo.rs | 20 ++++-- mm2src/coins/utxo/utxo_common.rs | 49 +++++++------ .../src/lp_swap/komodefi.swap_v2.pb.rs | 4 +- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 37 ++++++++-- mm2src/mm2_main/src/lp_swap/swap_v2.proto | 2 +- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 8 +-- .../tests/docker_tests/swap_proto_v2_tests.rs | 69 ++++++++++++++++++- 9 files changed, 165 insertions(+), 52 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 47fde144f1..1d43f70612 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1269,18 +1269,18 @@ pub trait CoinAssocTypes { type PubkeyParseError: Send + std::fmt::Display; type Tx: Transaction + Send + Sync; type TxParseError: Send + std::fmt::Display; - type Preimage: Send + Sync; + type Preimage: ToBytes + Send + Sync; type PreimageParseError: Send + std::fmt::Display; - type Sig: Send + Sync; + type Sig: ToBytes + Send + Sync; type SigParseError: Send + std::fmt::Display; fn parse_pubkey(&self, pubkey: &[u8]) -> Result; fn parse_tx(&self, tx: &[u8]) -> Result; - fn parse_preimage(&self, tx: &[u8]) -> Result; + fn parse_preimage(&self, tx: &[u8]) -> Result; - fn parse_signature(&self, sig: &[u8]) -> Result; + fn parse_signature(&self, sig: &[u8]) -> Result; } /// Operations specific to the [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index dc698a5008..64d892a489 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -398,23 +398,35 @@ impl Transaction for TestTx { fn tx_hash(&self) -> BytesJson { todo!() } } +pub struct TestPreimage {} + +impl ToBytes for TestPreimage { + fn to_bytes(&self) -> Vec { vec![] } +} + +pub struct TestSig {} + +impl ToBytes for TestSig { + fn to_bytes(&self) -> Vec { vec![] } +} + impl CoinAssocTypes for TestCoin { type Pubkey = TestPubkey; type PubkeyParseError = String; type Tx = TestTx; type TxParseError = String; - type Preimage = (); + type Preimage = TestPreimage; type PreimageParseError = String; - type Sig = (); + type Sig = TestSig; type SigParseError = String; fn parse_pubkey(&self, pubkey: &[u8]) -> Result { unimplemented!() } fn parse_tx(&self, tx: &[u8]) -> Result { unimplemented!() } - fn parse_preimage(&self, tx: &[u8]) -> Result { todo!() } + fn parse_preimage(&self, preimage: &[u8]) -> Result { todo!() } - fn parse_signature(&self, tx: &[u8]) -> Result { todo!() } + fn parse_signature(&self, sig: &[u8]) -> Result { todo!() } } #[async_trait] diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 54584c9d74..f48c2b56c2 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -75,6 +75,7 @@ use num_traits::ToPrimitive; use primitives::hash::{H160, H256, H264}; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use script::{Builder, Script, SignatureVersion, TransactionInputSigner}; +use secp256k1::Signature as SecpSignature; use serde_json::{self as json, Value as Json}; use serialization::{deserialize, serialize, serialize_with_flags, Error as SerError, SERIALIZE_TRANSACTION_WITNESS}; use spv_validation::conf::SPVConf; @@ -111,7 +112,7 @@ use crate::hd_wallet::{HDAccountOps, HDAccountsMutex, HDAddress, HDAddressId, HD InvalidBip44ChainError}; use crate::hd_wallet_storage::{HDAccountStorageItem, HDWalletCoinStorage, HDWalletStorageError, HDWalletStorageResult}; use crate::utxo::tx_cache::UtxoVerboseCacheShared; -use crate::CoinAssocTypes; +use crate::{CoinAssocTypes, ToBytes}; pub mod tx_cache; @@ -1013,6 +1014,14 @@ pub trait UtxoCommonOps: } } +impl ToBytes for UtxoTx { + fn to_bytes(&self) -> Vec { serialize(self).take() } +} + +impl ToBytes for Signature { + fn to_bytes(&self) -> Vec { self.to_vec() } +} + impl CoinAssocTypes for T { type Pubkey = Public; type PubkeyParseError = MmError; @@ -1021,7 +1030,7 @@ impl CoinAssocTypes for T { type Preimage = UtxoTx; type PreimageParseError = MmError; type Sig = Signature; - type SigParseError = String; + type SigParseError = MmError; #[inline] fn parse_pubkey(&self, pubkey: &[u8]) -> Result { @@ -1036,9 +1045,12 @@ impl CoinAssocTypes for T { } #[inline] - fn parse_preimage(&self, tx: &[u8]) -> Result { self.parse_tx(tx) } + fn parse_preimage(&self, tx: &[u8]) -> Result { self.parse_tx(tx) } - fn parse_signature(&self, sig: &[u8]) -> Result { todo!() } + fn parse_signature(&self, sig: &[u8]) -> Result { + SecpSignature::from_der(sig)?; + Ok(sig.into()) + } } #[async_trait] diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 4ad2e52ba1..e95e345e96 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,19 +15,19 @@ 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, CoinAssocTypes, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, - HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, - RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, - SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, - SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, - TransactionResult, 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::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, + RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, + RewardTarget, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, + TradePreimageValue, TransactionFut, TransactionResult, 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}; @@ -43,7 +43,7 @@ use futures01::future::Either; use itertools::Itertools; use keys::bytes::Bytes; use keys::{Address, AddressFormat as UtxoAddressFormat, AddressHashEnum, CompactSignature, Public, SegwitAddress, - Signature, Type as ScriptType}; + Type as ScriptType}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::bigdecimal_custom::CheckedDivision; @@ -51,7 +51,7 @@ use mm2_number::{BigDecimal, MmNumber}; use primitives::hash::H512; use rpc::v1::types::{Bytes as BytesJson, ToTxHash, TransactionInputEnum, H256 as H256Json}; use script::{Builder, Opcode, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput}; -use secp256k1::PublicKey; +use secp256k1::{PublicKey, Signature as SecpSignature}; use serde_json::{self as json}; use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Serializable, Stream, SERIALIZE_TRANSACTION_WITNESS}; @@ -1223,7 +1223,7 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI type GenPreimageResInner = MmResult; -async fn gen_taker_funding_spend_preimage>( +async fn gen_taker_funding_spend_preimage( coin: &T, args: &GenTakerFundingSpendArgs<'_, T>, lock_time: LocktimeSetting, @@ -1293,13 +1293,13 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( )?; Ok(TxPreimageWithSig { preimage: preimage.into(), - signature, + signature: signature.take().into(), }) } /// 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_send_taker_funding_spend( +pub async fn sign_and_send_taker_funding_spend( coin: &T, preimage: &TxPreimageWithSig, gen_args: &GenTakerFundingSpendArgs<'_, T>, @@ -1353,7 +1353,7 @@ pub async fn sign_and_send_taker_funding_spend( +async fn gen_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, T>, lock_time: LocktimeSetting, @@ -1382,7 +1382,7 @@ async fn gen_taker_payment_spend_preimage( .map_to_mm(TxGenError::Legacy) } -pub async fn gen_and_sign_taker_payment_spend_preimage( +pub async fn gen_and_sign_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, T>, htlc_keypair: &KeyPair, @@ -1411,10 +1411,9 @@ pub async fn gen_and_sign_taker_payment_spend_preimage Result { match script.get_instruction(0) { Some(Ok(instruction)) => match instruction.opcode { Opcode::OP_PUSHBYTES_70 | Opcode::OP_PUSHBYTES_71 | Opcode::OP_PUSHBYTES_72 => match instruction.data { - Some(bytes) => try_s!(Signature::from_der(&bytes[..bytes.len() - 1])), + Some(bytes) => try_s!(SecpSignature::from_der(&bytes[..bytes.len() - 1])), None => return ERR!("No data at instruction 0 of script {:?}", script), }, _ => return ERR!("Unexpected opcode {:?}", instruction.opcode), @@ -2060,7 +2059,7 @@ fn pubkey_from_witness_script(witness_script: &[Bytes]) -> Result if signature.is_empty() { return ERR!("Empty signature data in witness script"); } - try_s!(Signature::from_der(&signature[..signature.len() - 1])); + try_s!(SecpSignature::from_der(&signature[..signature.len() - 1])); let pubkey = try_s!(PublicKey::from_slice(&witness_script[1])); 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 index d677f903a0..3596ebd342 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -86,8 +86,8 @@ pub struct MakerPaymentInfo { 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>, + #[prost(bytes="vec", tag="2")] + pub tx_preimage: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct SwapMessage { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 7085000f4b..ba0794ca54 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -692,6 +692,8 @@ enum MakerPaymentRefundReason { TakerPaymentNotConfirmedInTime(String), DidNotGetTakerPaymentSpendPreimage(String), TakerPaymentSpendPreimageIsNotValid(String), + FailedToParseTakerPreimage(String), + FailedToParseTakerSignature(String), TakerPaymentSpendBroadcastFailed(String), } @@ -771,7 +773,7 @@ impl State &state_machine.uuid, state_machine.taker_payment_conf_timeout(), ); - let preimage = match recv_fut.await { + let preimage_data = match recv_fut.await { Ok(preimage) => preimage, Err(e) => { let next_state = MakerPaymentRefundRequired { @@ -784,7 +786,7 @@ impl State return Self::change_state(next_state, state_machine).await; }, }; - debug!("Received taker payment spend preimage message {:?}", preimage); + debug!("Received taker payment spend preimage message {:?}", preimage_data); let unique_data = state_machine.unique_data(); @@ -799,10 +801,35 @@ impl State 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, + + let preimage = match state_machine.taker_coin.parse_preimage(&preimage_data.tx_preimage) { + Ok(p) => p, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::FailedToParseTakerPreimage(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, }; + let signature = match state_machine.taker_coin.parse_signature(&preimage_data.signature) { + Ok(s) => s, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::FailedToParseTakerSignature(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + let tx_preimage = TxPreimageWithSig { preimage, signature }; if let Err(e) = state_machine .taker_coin .validate_taker_payment_spend_preimage(&gen_args, &tx_preimage) diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2.proto b/mm2src/mm2_main/src/lp_swap/swap_v2.proto index 5ef75bc241..2f7e307d91 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2.proto +++ b/mm2src/mm2_main/src/lp_swap/swap_v2.proto @@ -57,7 +57,7 @@ message MakerPaymentInfo { message TakerPaymentSpendPreimage { bytes signature = 1; - optional bytes tx_preimage = 2; + bytes tx_preimage = 2; } message SwapMessage { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 10e641a248..54103b946e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -731,12 +731,8 @@ impl State }; let preimage_msg = TakerPaymentSpendPreimage { - signature: preimage.signature, - tx_preimage: if !preimage.preimage.is_empty() { - Some(preimage.preimage) - } else { - None - }, + signature: preimage.signature.to_bytes(), + tx_preimage: preimage.preimage.to_bytes(), }; let swap_msg = SwapMessage { inner: Some(swap_message::Inner::TakerPaymentSpendPreimage(preimage_msg)), 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 f5844b217d..628299bfd1 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,9 +1,12 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; -use coins::utxo::UtxoCommonOps; +use chain::TransactionOutput; +use coins::utxo::swap_proto_v2_scripts::taker_payment_script; +use coins::utxo::{UtxoCommonOps, UtxoTxBroadcastOps}; use coins::{GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, SendCombinedTakerPaymentArgs, SendTakerFundingArgs, SwapOpsV2, Transaction, ValidateTakerPaymentArgs}; use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use keys::AddressHashEnum; use mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, Mm2TestConf}; use script::{Builder, Opcode}; @@ -297,6 +300,70 @@ fn send_and_spend_taker_payment() { println!("taker_payment_spend hash {:02x}", taker_payment_spend.tx_hash()); } +#[test] +fn test_bob_using_alice_sig_for_payment_refund_path() { + 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() - 1000; + let secret = [1; 32]; + let maker_secret_hash = dhash160(&secret); + let send_args = SendCombinedTakerPaymentArgs { + time_lock, + maker_secret_hash: maker_secret_hash.as_slice(), + maker_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 taker_payment_utxo_tx = block_on(taker_coin.send_combined_taker_payment(send_args)).unwrap(); + println!("taker_payment_tx hash {:02x}", taker_payment_utxo_tx.tx_hash()); + + let gen_preimage_args = GenTakerPaymentSpendArgs { + taker_tx: &taker_payment_utxo_tx, + time_lock, + secret_hash: maker_secret_hash.as_slice(), + maker_pub: maker_coin.my_public_key().unwrap(), + taker_pub: taker_coin.my_public_key().unwrap(), + dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee_amount: "0.01".parse().unwrap(), + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), + }; + let preimage_with_taker_sig = + block_on(taker_coin.gen_taker_payment_spend_preimage(&gen_preimage_args, &[])).unwrap(); + + let mut refund_tx = preimage_with_taker_sig.preimage; + refund_tx.outputs.push(TransactionOutput { + value: 110000000 - 10000, + script_pubkey: Builder::build_p2pkh(&AddressHashEnum::AddressHash(dhash160( + maker_coin.my_public_key().unwrap(), + ))) + .into(), + }); + + let sig_hash_single_fork_id = (3 | taker_coin.as_ref().conf.fork_id) as u8; + let mut taker_signature_with_sighash = preimage_with_taker_sig.signature.to_vec(); + taker_signature_with_sighash.push(sig_hash_single_fork_id); + + let redeem_script = taker_payment_script( + time_lock as u32, + maker_secret_hash.as_slice(), + taker_coin.my_public_key().unwrap(), + maker_coin.my_public_key().unwrap(), + ); + let script_sig = Builder::default() + .push_data(&taker_signature_with_sighash) + .push_opcode(Opcode::OP_1) + .push_data(&redeem_script) + .into_bytes(); + refund_tx.inputs[0].script_sig = script_sig; + + println!("Tx locktime {}", refund_tx.lock_time); + block_on(maker_coin.broadcast_tx(&refund_tx)).unwrap(); +} + #[test] fn test_v2_swap_utxo_utxo() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); From b7c327aa77b98be92d4323e0109c2f645b52c0b0 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Wed, 27 Sep 2023 16:14:18 +0700 Subject: [PATCH 17/30] WIP. Protocol enhancement. --- mm2src/coins/utxo/utxo_common.rs | 18 +-- mm2src/mm2_main/src/lp_ordermatch.rs | 20 ++- mm2src/mm2_main/src/lp_swap.rs | 20 ++- .../src/lp_swap/komodefi.swap_v2.pb.rs | 25 +++- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 6 - mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 116 ++++++++++++------ mm2src/mm2_main/src/lp_swap/swap_v2.proto | 18 ++- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 15 ++- .../tests/docker_tests/swap_watcher_tests.rs | 16 +-- 9 files changed, 172 insertions(+), 82 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index e95e345e96..6f9952a723 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1268,13 +1268,8 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( .try_into() .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; - let preimage = gen_taker_funding_spend_preimage( - coin, - args, - LocktimeSetting::CalcByHtlcLocktime(funding_time_lock), - NTimeSetting::UseNow, - ) - .await?; + let preimage = + gen_taker_funding_spend_preimage(coin, args, LocktimeSetting::UseExact(0), NTimeSetting::UseNow).await?; let redeem_script = swap_proto_v2_scripts::taker_funding_script( funding_time_lock, @@ -1392,13 +1387,8 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( .try_into() .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; - let preimage = gen_taker_payment_spend_preimage( - coin, - args, - LocktimeSetting::CalcByHtlcLocktime(time_lock), - NTimeSetting::UseNow, - ) - .await?; + let preimage = + gen_taker_payment_spend_preimage(coin, args, LocktimeSetting::UseExact(0), NTimeSetting::UseNow).await?; let redeem_script = swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, args.taker_pub, args.maker_pub); diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 69cf26f3a9..f79e804cec 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -75,11 +75,11 @@ use crate::mm2::lp_swap::maker_swap_v2::{self, DummyMakerSwapStorage, MakerSwapS 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, detect_secret_hash_algo, 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, SwapConfirmationsSettings, - TakerSwap}; + generate_secret, 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, SwapConfirmationsSettings, TakerSwap}; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; @@ -2956,7 +2956,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO let now = now_sec(); - let secret = match MakerSwap::generate_secret() { + let secret = match generate_secret() { Ok(s) => s.into(), Err(e) => { error!("Error {} on secret generation", e); @@ -3102,6 +3102,13 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat let now = now_sec(); if ctx.use_trading_proto_v2() { + let taker_secret = match generate_secret() { + Ok(s) => s.into(), + Err(e) => { + error!("Error {} on secret generation", e); + return; + }, + }; let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); match (maker_coin, taker_coin) { (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { @@ -3121,6 +3128,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat p2p_topic: swap_v2_topic(&uuid), uuid, p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + taker_secret, }; #[allow(clippy::box_default)] taker_swap_state_machine diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 4ba8deb6d4..d61a9ed2e1 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -194,8 +194,10 @@ pub struct SwapV2MsgStore { maker_negotiation: Option, taker_negotiation: Option, maker_negotiated: Option, - taker_payment: Option, + taker_funding: Option, maker_payment: Option, + taker_funding_spend_preimage: Option, + taker_payment: Option, taker_payment_spend_preimage: Option, #[allow(dead_code)] accept_only_from: bits256, @@ -1567,12 +1569,18 @@ pub fn process_swap_v2_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PProcessRes 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::TakerFundingInfo(taker_funding)) => { + msg_store.taker_funding = Some(taker_funding) }, Some(swap_v2_pb::swap_message::Inner::MakerPaymentInfo(maker_payment)) => { msg_store.maker_payment = Some(maker_payment) }, + Some(swap_v2_pb::swap_message::Inner::TakerFundingSpendPreimage(preimage)) => { + msg_store.taker_funding_spend_preimage = Some(preimage) + }, + Some(swap_v2_pb::swap_message::Inner::TakerPaymentInfo(taker_payment)) => { + msg_store.taker_payment = Some(taker_payment) + }, Some(swap_v2_pb::swap_message::Inner::TakerPaymentSpendPreimage(preimage)) => { msg_store.taker_payment_spend_preimage = Some(preimage) }, @@ -1607,6 +1615,12 @@ async fn recv_swap_v2_msg( } } +pub fn generate_secret() -> Result<[u8; 32], rand::Error> { + let mut sec = [0u8; 32]; + common::os_rng(&mut sec)?; + Ok(sec) +} + #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; 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 index 3596ebd342..a25407b1d3 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -35,7 +35,8 @@ pub struct TakerNegotiationData { pub started_at: u64, #[prost(uint64, tag="2")] pub payment_locktime: u64, - /// add bytes secret_hash = 3 if required + #[prost(bytes="vec", tag="3")] + pub taker_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")] @@ -69,6 +70,20 @@ pub struct MakerNegotiated { pub reason: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct TakerFundingInfo { + #[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 TakerFundingSpendPreimage { + #[prost(bytes="vec", tag="1")] + pub signature: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="2")] + pub tx_preimage: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct TakerPaymentInfo { #[prost(bytes="vec", tag="1")] pub tx_bytes: ::prost::alloc::vec::Vec, @@ -91,7 +106,7 @@ pub struct TakerPaymentSpendPreimage { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct SwapMessage { - #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6")] + #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6, 7, 8")] pub inner: ::core::option::Option, } /// Nested message and enum types in `SwapMessage`. @@ -105,10 +120,14 @@ pub mod swap_message { #[prost(message, tag="3")] MakerNegotiated(super::MakerNegotiated), #[prost(message, tag="4")] - TakerPaymentInfo(super::TakerPaymentInfo), + TakerFundingInfo(super::TakerFundingInfo), #[prost(message, tag="5")] MakerPaymentInfo(super::MakerPaymentInfo), #[prost(message, tag="6")] + TakerFundingSpendPreimage(super::TakerFundingSpendPreimage), + #[prost(message, tag="7")] + TakerPaymentInfo(super::TakerPaymentInfo), + #[prost(message, tag="8")] 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 9a8669997a..67f1b6285a 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -239,12 +239,6 @@ impl MakerSwap { #[inline] fn r(&self) -> RwLockReadGuard { self.mutable.read().unwrap() } - pub fn generate_secret() -> Result<[u8; 32], rand::Error> { - let mut sec = [0u8; 32]; - common::os_rng(&mut sec)?; - Ok(sec) - } - #[inline] fn secret_hash(&self) -> Vec { self.r() diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index ba0794ca54..e201a62791 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -34,6 +34,7 @@ pub struct StoredNegotiationData { taker_coin_htlc_pub_from_taker: BytesJson, maker_coin_swap_contract: Option, taker_coin_swap_contract: Option, + taker_secret_hash: BytesJson, } /// Represents events produced by maker swap states. @@ -44,18 +45,18 @@ pub enum MakerSwapEvent { maker_coin_start_block: u64, taker_coin_start_block: u64, }, - /// Started waiting for taker payment. - WaitingForTakerPayment { + /// Started waiting for taker funding tx. + WaitingForTakerFunding { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, }, - /// Received taker payment info. - TakerPaymentReceived { + /// Received taker funding info. + TakerFundingReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, - taker_payment: TransactionIdentifier, + taker_funding: TransactionIdentifier, }, /// Sent maker payment. MakerPaymentSent { @@ -64,6 +65,16 @@ pub enum MakerSwapEvent { negotiation_data: StoredNegotiationData, maker_payment: TransactionIdentifier, }, + /// Received funding spend preimage. + TakerFundingSpendReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + maker_payment: TransactionIdentifier, + taker_funding_preimage: BytesJson, + taker_funding_spend_signature: BytesJson, + }, /// Something went wrong, so maker payment refund is required. MakerPaymentRefundRequired { maker_coin_start_block: u64, @@ -414,7 +425,7 @@ impl State fo }, }; - let next_state = WaitingForTakerPayment { + let next_state = WaitingForTakerFunding { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: NegotiationData { @@ -423,6 +434,7 @@ impl State fo taker_coin_htlc_pub_from_taker, maker_coin_swap_contract: taker_data.maker_coin_swap_contract, taker_coin_swap_contract: taker_data.taker_coin_swap_contract, + taker_secret_hash: taker_data.taker_secret_hash, }, }; Self::change_state(next_state, state_machine).await @@ -435,6 +447,7 @@ struct NegotiationData { taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, taker_coin_swap_contract: Option>, + taker_secret_hash: Vec, } impl NegotiationData { @@ -445,24 +458,25 @@ impl NegotiationData { +struct WaitingForTakerFunding { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, } impl TransitionFrom> - for WaitingForTakerPayment + for WaitingForTakerFunding { } #[async_trait] impl State - for WaitingForTakerPayment + for WaitingForTakerFunding { type StateMachine = MakerSwapStateMachine; @@ -485,44 +499,44 @@ impl State let recv_fut = recv_swap_v2_msg( state_machine.ctx.clone(), - |store| store.taker_payment.take(), + |store| store.taker_funding.take(), &state_machine.uuid, NEGOTIATION_TIMEOUT_SEC, ); - let taker_payment_info = match recv_fut.await { + let taker_funding_info = match recv_fut.await { Ok(p) => p, Err(e) => { - let reason = AbortReason::DidNotReceiveTakerPaymentInfo(e); + let reason = AbortReason::DidNotReceiveTakerFundingInfo(e); return Self::change_state(Aborted::new(reason), state_machine).await; }, }; drop(abort_handle); - debug!("Received taker payment info message {:?}", taker_payment_info); - let taker_payment = match state_machine.taker_coin.parse_tx(&taker_payment_info.tx_bytes) { + debug!("Received taker funding info message {:?}", taker_funding_info); + let taker_funding = match state_machine.taker_coin.parse_tx(&taker_funding_info.tx_bytes) { Ok(tx) => tx, Err(e) => { - let reason = AbortReason::FailedToParseTakerPayment(e.to_string()); + let reason = AbortReason::FailedToParseTakerFunding(e.to_string()); return Self::change_state(Aborted::new(reason), state_machine).await; }, }; - let next_state = TakerPaymentReceived { + let next_state = TakerFundingReceived { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data, - taker_payment, + taker_funding, }; Self::change_state(next_state, state_machine).await } } impl StorableState - for WaitingForTakerPayment + for WaitingForTakerFunding { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { - MakerSwapEvent::WaitingForTakerPayment { + MakerSwapEvent::WaitingForTakerFunding { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data.to_stored_data(), @@ -530,21 +544,21 @@ impl StorableS } } -struct TakerPaymentReceived { +struct TakerFundingReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, - taker_payment: TakerCoin::Tx, + taker_funding: TakerCoin::Tx, } -impl TransitionFrom> - for TakerPaymentReceived +impl TransitionFrom> + for TakerFundingReceived { } #[async_trait] impl State - for TakerPaymentReceived + for TakerFundingReceived { type StateMachine = MakerSwapStateMachine; @@ -578,7 +592,7 @@ impl State maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data, - taker_payment: self.taker_payment, + taker_funding: self.taker_funding, maker_payment: TransactionIdentifier { tx_hex: maker_payment.tx_hex().into(), tx_hash: maker_payment.tx_hash(), @@ -590,18 +604,18 @@ impl State } impl StorableState - for TakerPaymentReceived + for TakerFundingReceived { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { - MakerSwapEvent::TakerPaymentReceived { + MakerSwapEvent::TakerFundingReceived { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data.to_stored_data(), - taker_payment: TransactionIdentifier { - tx_hex: self.taker_payment.tx_hex().into(), - tx_hash: self.taker_payment.tx_hash(), + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), }, } } @@ -611,11 +625,11 @@ struct MakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, - taker_payment: TakerCoin::Tx, + taker_funding: TakerCoin::Tx, maker_payment: TransactionIdentifier, } -impl TransitionFrom> +impl TransitionFrom> for MakerPaymentSent { } @@ -636,15 +650,37 @@ impl State }; debug!("Sending maker payment info message {:?}", swap_msg); - let _abort_handle = broadcast_swap_v2_msg_every( + 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.taker_funding_spend_preimage.take(), + &state_machine.uuid, + NEGOTIATION_TIMEOUT_SEC, + ); + let taker_funding_spend_preimage = match recv_fut.await { + Ok(p) => p, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::DidNotReceiveTakerFundingPreimage(e), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + /* let input = ConfirmPaymentInput { - payment_tx: self.taker_payment.tx_hex(), + payment_tx: self.taker_funding.tx_hex(), 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(), @@ -669,6 +705,9 @@ impl State negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await + + */ + unimplemented!() } } @@ -689,6 +728,7 @@ impl StorableS #[derive(Debug)] enum MakerPaymentRefundReason { + DidNotReceiveTakerFundingPreimage(String), TakerPaymentNotConfirmedInTime(String), DidNotGetTakerPaymentSpendPreimage(String), TakerPaymentSpendPreimageIsNotValid(String), @@ -960,8 +1000,8 @@ pub enum AbortReason { DidNotReceiveTakerNegotiation(String), TakerAbortedNegotiation(String), ReceivedInvalidTakerNegotiation, - DidNotReceiveTakerPaymentInfo(String), - FailedToParseTakerPayment(String), + DidNotReceiveTakerFundingInfo(String), + FailedToParseTakerFunding(String), FailedToSendMakerPayment(String), TooLargeStartedAtDiff(u64), TakerProvidedInvalidLocktime(u64), @@ -1008,11 +1048,11 @@ impl StorableState for Abo impl TransitionFrom> for Aborted {} impl TransitionFrom> for Aborted {} -impl TransitionFrom> +impl TransitionFrom> for Aborted { } -impl TransitionFrom> +impl TransitionFrom> for Aborted { } diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2.proto b/mm2src/mm2_main/src/lp_swap/swap_v2.proto index 2f7e307d91..7139e1b2d6 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2.proto +++ b/mm2src/mm2_main/src/lp_swap/swap_v2.proto @@ -25,7 +25,7 @@ message Abort { message TakerNegotiationData { uint64 started_at = 1; uint64 payment_locktime = 2; - // add bytes secret_hash = 3 if required + bytes taker_secret_hash = 3; bytes maker_coin_htlc_pub = 4; bytes taker_coin_htlc_pub = 5; optional bytes maker_coin_swap_contract = 6; @@ -45,6 +45,16 @@ message MakerNegotiated { optional string reason = 2; } +message TakerFundingInfo { + bytes tx_bytes = 1; + optional bytes next_step_instructions = 2; +} + +message TakerFundingSpendPreimage { + bytes signature = 1; + bytes tx_preimage = 2; +} + message TakerPaymentInfo { bytes tx_bytes = 1; optional bytes next_step_instructions = 2; @@ -65,8 +75,10 @@ message SwapMessage { MakerNegotiation maker_negotiation = 1; TakerNegotiation taker_negotiation = 2; MakerNegotiated maker_negotiated = 3; - TakerPaymentInfo taker_payment_info = 4; + TakerFundingInfo taker_funding_info = 4; MakerPaymentInfo maker_payment_info = 5; - TakerPaymentSpendPreimage taker_payment_spend_preimage = 6; + TakerFundingSpendPreimage taker_funding_spend_preimage = 6; + TakerPaymentInfo taker_payment_info = 7; + TakerPaymentSpendPreimage taker_payment_spend_preimage = 8; } } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 54103b946e..cdf5626250 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -6,6 +6,7 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_s SwapConfirmationsSettings, SwapsContext, TransactionIdentifier, MAX_STARTED_AT_DIFF, TAKER_SWAP_V2_TYPE}; use async_trait::async_trait; +use bitcrypto::{dhash160, sha256}; use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, SendCombinedTakerPaymentArgs, SpendPaymentArgs, SwapOpsV2, ToBytes, Transaction, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; @@ -17,6 +18,7 @@ use mm2_err_handle::prelude::*; use mm2_number::{BigDecimal, MmNumber}; use mm2_state_machine::prelude::*; use mm2_state_machine::storable_state_machine::*; +use primitives::hash::H256; use rpc::v1::types::Bytes as BytesJson; use std::marker::PhantomData; use uuid::Uuid; @@ -160,7 +162,7 @@ pub struct TakerSwapStateMachine { pub dex_fee: MmNumber, /// Premium amount, which might be paid to maker as additional reward. pub taker_premium: MmNumber, - /// Algorithm used to hash the swap secret. + /// Algorithm used to hash swap secrets. pub secret_hash_algo: SecretHashAlgo, /// Swap transactions' confirmations settings. pub conf_settings: SwapConfirmationsSettings, @@ -170,6 +172,8 @@ pub struct TakerSwapStateMachine { pub p2p_topic: String, /// If Some, used to sign P2P messages of this swap. pub p2p_keypair: Option, + /// The secret used for immediate taker funding tx reclaim if maker back-outs + pub taker_secret: H256, } impl TakerSwapStateMachine { @@ -178,6 +182,14 @@ impl TakerSwapStateMachine { fn taker_payment_locktime(&self) -> u64 { self.started_at + self.lock_duration } fn unique_data(&self) -> Vec { self.uuid.as_bytes().to_vec() } + + /// Returns secret hash generated using selected [SecretHashAlgo]. + fn taker_secret_hash(&self) -> Vec { + match self.secret_hash_algo { + SecretHashAlgo::DHASH160 => dhash160(self.taker_secret.as_slice()).take().into(), + SecretHashAlgo::SHA256 => sha256(self.taker_secret.as_slice()).take().into(), + } + } } impl StorableStateMachine @@ -380,6 +392,7 @@ impl State fo action: Some(taker_negotiation::Action::Continue(TakerNegotiationData { started_at: state_machine.started_at, payment_locktime: state_machine.taker_payment_locktime(), + taker_secret_hash: state_machine.taker_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), 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 41b3bf105e..dc520cefbe 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -11,8 +11,8 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin 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, - MakerSwap, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, +use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, dex_fee_threshold, generate_secret, + get_payment_locktime, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; use mm2_number::MmNumber; @@ -1103,7 +1103,7 @@ fn test_watcher_validate_taker_payment_utxo() { let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let maker_pubkey = maker_coin.my_public_key().unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { @@ -1181,7 +1181,7 @@ fn test_watcher_validate_taker_payment_utxo() { } // Used to get wrong swap id - let wrong_secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let wrong_secret_hash = dhash160(&generate_secret().unwrap()); let error = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1318,7 +1318,7 @@ fn test_watcher_validate_taker_payment_eth() { 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()); + let secret_hash = dhash160(&generate_secret().unwrap()); let watcher_reward = Some( block_on(taker_coin.get_taker_watcher_reward( &MmCoinEnum::from(taker_coin.clone()), @@ -1439,7 +1439,7 @@ fn test_watcher_validate_taker_payment_eth() { } // Used to get wrong swap id - let wrong_secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let wrong_secret_hash = dhash160(&generate_secret().unwrap()); let error = taker_coin .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1561,7 +1561,7 @@ fn test_watcher_validate_taker_payment_erc20() { let wait_for_confirmation_until = wait_until_sec(time_lock_duration); let time_lock = wait_for_confirmation_until; - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_amount = BigDecimal::from_str("0.01").unwrap(); let maker_amount = BigDecimal::from_str("0.01").unwrap(); @@ -1686,7 +1686,7 @@ fn test_watcher_validate_taker_payment_erc20() { } // Used to get wrong swap id - let wrong_secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let wrong_secret_hash = dhash160(&generate_secret().unwrap()); let error = taker_coin .watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), From c6805d31dac74500df249ea4f91b7c947f5ddbfa Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Thu, 28 Sep 2023 12:59:37 +0700 Subject: [PATCH 18/30] WIP. Protocol enhancement. --- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 323 ++++++++++++++++++- 1 file changed, 308 insertions(+), 15 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index cdf5626250..80fdfd40fa 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -7,8 +7,9 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_s TAKER_SWAP_V2_TYPE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MmCoin, - SendCombinedTakerPaymentArgs, SpendPaymentArgs, SwapOpsV2, ToBytes, Transaction, WaitForHTLCTxSpendArgs}; +use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MmCoin, SendCombinedTakerPaymentArgs, SendTakerFundingArgs, SpendPaymentArgs, SwapOps, SwapOpsV2, ToBytes, + Transaction, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -30,7 +31,7 @@ use uuid::Uuid; #[derive(Debug, Deserialize, Serialize)] pub struct StoredNegotiationData { maker_payment_locktime: u64, - secret_hash: BytesJson, + maker_secret_hash: BytesJson, maker_coin_htlc_pub_from_maker: BytesJson, taker_coin_htlc_pub_from_maker: BytesJson, maker_coin_swap_contract: Option, @@ -51,6 +52,28 @@ pub enum TakerSwapEvent { taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, }, + /// Sent taker funding tx. + TakerFundingSent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + }, + /// Taker funding tx refund is required. + TakerFundingRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + }, + /// Received maker payment + MakerPaymentReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: StoredNegotiationData, + taker_funding: TransactionIdentifier, + maker_payment: TransactionIdentifier, + }, /// Sent taker payment. TakerPaymentSent { maker_coin_start_block: u64, @@ -179,6 +202,8 @@ pub struct TakerSwapStateMachine { impl TakerSwapStateMachine { fn maker_payment_conf_timeout(&self) -> u64 { self.started_at + self.lock_duration * 2 / 3 } + fn taker_funding_locktime(&self) -> u64 { self.started_at + self.lock_duration * 3 } + fn taker_payment_locktime(&self) -> u64 { self.started_at + self.lock_duration } fn unique_data(&self) -> Vec { self.uuid.as_bytes().to_vec() } @@ -437,7 +462,7 @@ impl State fo maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: NegotiationData { - secret_hash: maker_negotiation.secret_hash, + maker_secret_hash: maker_negotiation.secret_hash, maker_payment_locktime: expected_maker_payment_locktime, maker_coin_htlc_pub_from_maker, taker_coin_htlc_pub_from_maker, @@ -450,7 +475,7 @@ impl State fo } struct NegotiationData { - secret_hash: Vec, + maker_secret_hash: Vec, maker_payment_locktime: u64, maker_coin_htlc_pub_from_maker: MakerCoin::Pubkey, taker_coin_htlc_pub_from_maker: TakerCoin::Pubkey, @@ -462,7 +487,7 @@ impl NegotiationData StoredNegotiationData { StoredNegotiationData { maker_payment_locktime: self.maker_payment_locktime, - secret_hash: self.secret_hash.clone().into(), + maker_secret_hash: self.maker_secret_hash.clone().into(), maker_coin_htlc_pub_from_maker: self.maker_coin_htlc_pub_from_maker.to_bytes().into(), taker_coin_htlc_pub_from_maker: self.taker_coin_htlc_pub_from_maker.to_bytes().into(), maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), @@ -487,9 +512,42 @@ impl State fo type StateMachine = TakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let args = SendTakerFundingArgs { + time_lock: state_machine.taker_funding_locktime(), + taker_secret_hash: &state_machine.taker_secret_hash(), + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), + dex_fee_amount: state_machine.dex_fee.to_decimal(), + premium_amount: state_machine.taker_premium.to_decimal(), + trading_amount: state_machine.taker_volume.to_decimal(), + swap_unique_data: &state_machine.unique_data(), + }; + + let taker_funding = match state_machine.taker_coin.send_taker_funding(args).await { + Ok(tx) => tx, + Err(e) => { + let reason = AbortReason::FailedToSendTakerFunding(format!("{:?}", e)); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + info!( + "Sent taker funding {} tx {:02x} during swap {}", + state_machine.taker_coin.ticker(), + taker_funding.tx_hash(), + state_machine.uuid + ); + + let next_state = TakerFundingSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding, + negotiation_data: self.negotiation_data, + }; + Self::change_state(next_state, state_machine).await + /* let args = SendCombinedTakerPaymentArgs { time_lock: state_machine.taker_payment_locktime(), - maker_secret_hash: &self.negotiation_data.secret_hash, + maker_secret_hash: &self.negotiation_data.maker_secret_hash, maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee_amount: state_machine.dex_fee.to_decimal(), premium_amount: BigDecimal::from(0), @@ -518,6 +576,8 @@ impl State fo negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await + + */ } } @@ -535,18 +595,199 @@ impl StorableS } } -struct TakerPaymentSent { +struct TakerFundingSent { maker_coin_start_block: u64, taker_coin_start_block: u64, - taker_payment: TakerCoin::Tx, + taker_funding: TakerCoin::Tx, negotiation_data: NegotiationData, } +#[async_trait] +impl State + for TakerFundingSent +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let taker_funding_info = TakerFundingInfo { + tx_bytes: self.taker_funding.tx_hex(), + next_step_instructions: None, + }; + + let swap_msg = SwapMessage { + inner: Some(swap_message::Inner::TakerFundingInfo(taker_funding_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 = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::DidNotReceiveMakerPayment(e), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + drop(abort_handle); + + debug!("Received maker payment info message {:?}", maker_payment_info); + + let next_state = MakerPaymentReceived { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + taker_funding: self.taker_funding, + maker_payment: TransactionIdentifier { + tx_hex: maker_payment_info.tx_bytes.into(), + tx_hash: Default::default(), + }, + }; + Self::change_state(next_state, state_machine).await + } +} + impl TransitionFrom> - for TakerPaymentSent + for TakerFundingSent +{ +} + +impl StorableState + for TakerFundingSent +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerFundingSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), + } + } +} + +struct MakerPaymentReceived { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + negotiation_data: NegotiationData, + taker_funding: TakerCoin::Tx, + maker_payment: TransactionIdentifier, +} + +impl TransitionFrom> + for MakerPaymentReceived { } +impl StorableState + for MakerPaymentReceived +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::MakerPaymentReceived { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data.to_stored_data(), + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, + maker_payment: self.maker_payment.clone(), + } + } +} + +#[async_trait] +impl State + for MakerPaymentReceived +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let unique_data = state_machine.unique_data(); + + let input = ValidatePaymentInput { + payment_tx: self.maker_payment.tx_hex.0.clone(), + time_lock_duration: state_machine.lock_duration, + time_lock: self.negotiation_data.maker_payment_locktime, + other_pub: self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), + secret_hash: self.negotiation_data.maker_secret_hash.clone(), + amount: state_machine.maker_volume.to_decimal(), + swap_contract_address: None, + try_spv_proof_until: state_machine.maker_payment_conf_timeout(), + confirmations: state_machine.conf_settings.maker_coin_confs, + unique_swap_data: unique_data.clone(), + watcher_reward: None, + }; + if let Err(e) = state_machine.maker_coin.validate_maker_payment(input).compat().await { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::MakerPaymentValidationFailed(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }; + + let args = GenTakerFundingSpendArgs { + funding_tx: &self.taker_funding, + maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker, + taker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), + funding_time_lock: state_machine.taker_funding_locktime(), + taker_secret_hash: &state_machine.taker_secret_hash(), + taker_payment_time_lock: state_machine.taker_payment_locktime(), + maker_secret_hash: &self.negotiation_data.maker_secret_hash, + }; + let funding_spend_preimage = match state_machine + .taker_coin + .gen_taker_funding_spend_preimage(&args, &unique_data) + .await + { + Ok(p) => p, + Err(e) => { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FailedToGenerateSpendPreimage(format!("{:?}", e)), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + unimplemented!() + } +} + +struct TakerPaymentSent { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_payment: TakerCoin::Tx, + negotiation_data: NegotiationData, +} + #[async_trait] impl State for TakerPaymentSent @@ -639,6 +880,58 @@ impl StorableS } } +#[derive(Debug)] +enum TakerFundingRefundReason { + DidNotReceiveMakerPayment(String), + MakerPaymentValidationFailed(String), + FailedToGenerateSpendPreimage(String), + MakerDidNotBroadcastInTime(String), +} + +struct TakerFundingRefundRequired { + maker_coin_start_block: u64, + taker_coin_start_block: u64, + taker_funding: TakerCoin::Tx, + negotiation_data: NegotiationData, + reason: TakerFundingRefundReason, +} + +impl TransitionFrom> + for TakerFundingRefundRequired +{ +} +impl TransitionFrom> + for TakerFundingRefundRequired +{ +} + +#[async_trait] +impl State + for TakerFundingRefundRequired +{ + type StateMachine = TakerSwapStateMachine; + + async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult { todo!() } +} + +impl StorableState + for TakerFundingRefundRequired +{ + type StateMachine = TakerSwapStateMachine; + + fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { + TakerSwapEvent::TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: TransactionIdentifier { + tx_hex: self.taker_funding.tx_hex().into(), + tx_hash: self.taker_funding.tx_hash(), + }, + negotiation_data: self.negotiation_data.to_stored_data(), + } + } +} + #[derive(Debug)] enum TakerPaymentRefundReason { DidNotReceiveMakerPayment(String), @@ -718,7 +1011,7 @@ impl State let args = GenTakerPaymentSpendArgs { taker_tx: &self.taker_payment, time_lock: state_machine.taker_payment_locktime(), - secret_hash: &self.negotiation_data.secret_hash, + secret_hash: &self.negotiation_data.maker_secret_hash, maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker, taker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -761,7 +1054,7 @@ impl State let wait_args = WaitForHTLCTxSpendArgs { tx_bytes: &self.taker_payment.tx_hex(), - secret_hash: &self.negotiation_data.secret_hash, + secret_hash: &self.negotiation_data.maker_secret_hash, wait_until: state_machine.taker_payment_locktime(), from_block: self.taker_coin_start_block, swap_contract_address: &self @@ -854,7 +1147,7 @@ impl State let secret = match state_machine .taker_coin .extract_secret( - &self.negotiation_data.secret_hash, + &self.negotiation_data.maker_secret_hash, &self.taker_payment_spend.tx_hex.0, false, ) @@ -872,7 +1165,7 @@ impl State time_lock: self.negotiation_data.maker_payment_locktime, other_pubkey: &self.negotiation_data.maker_coin_htlc_pub_from_maker.to_bytes(), secret: &secret, - secret_hash: &self.negotiation_data.secret_hash, + secret_hash: &self.negotiation_data.maker_secret_hash, swap_contract_address: &self .negotiation_data .maker_coin_swap_contract @@ -994,7 +1287,7 @@ pub enum AbortReason { SecretHashUnexpectedLen(usize), DidNotReceiveMakerNegotiated(String), MakerDidNotNegotiate(String), - FailedToSendTakerPayment(String), + FailedToSendTakerFunding(String), CouldNotExtractSecret(String), FailedToSpendMakerPayment(String), } From b3cc12f8de7695066d508a5eb70c652864c16198 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Thu, 28 Sep 2023 15:29:45 +0700 Subject: [PATCH 19/30] WIP. Protocol enhancement. --- mm2src/coins/utxo/utxo_common.rs | 17 ++- mm2src/mm2_main/src/lp_swap.rs | 4 - .../src/lp_swap/komodefi.swap_v2.pb.rs | 29 ++--- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 92 ++++++++++---- mm2src/mm2_main/src/lp_swap/swap_v2.proto | 25 ++-- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 116 +++++++++++------- .../tests/docker_tests/swap_proto_v2_tests.rs | 4 +- 7 files changed, 180 insertions(+), 107 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 6f9952a723..e153460fcb 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1292,8 +1292,7 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( }) } -/// 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. +/// Common implementation of taker funding spend finalization and broadcast for UTXO coins. pub async fn sign_and_send_taker_funding_spend( coin: &T, preimage: &TxPreimageWithSig, @@ -1304,7 +1303,7 @@ pub async fn sign_and_send_taker_funding_spend( try_tx_s!(gen_args.funding_time_lock.try_into()), gen_args.taker_secret_hash, gen_args.taker_pub, - htlc_keypair.public(), + gen_args.maker_pub, ); let mut signer: TransactionInputSigner = preimage.preimage.clone().into(); @@ -1313,7 +1312,7 @@ pub async fn sign_and_send_taker_funding_spend( payment_input.amount = funding_output.value; signer.consensus_branch_id = coin.as_ref().conf.consensus_branch_id; - let maker_signature = try_tx_s!(calc_and_sign_sighash( + let taker_signature = try_tx_s!(calc_and_sign_sighash( &signer, DEFAULT_SWAP_VOUT, &redeem_script, @@ -1324,14 +1323,14 @@ pub async fn sign_and_send_taker_funding_spend( )); let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; - let mut taker_signature_with_sighash = preimage.signature.to_vec(); - taker_signature_with_sighash.push(sig_hash_all_fork_id); - drop_mutability!(taker_signature_with_sighash); - - let mut maker_signature_with_sighash: Vec = maker_signature.take(); + let mut maker_signature_with_sighash = preimage.signature.to_vec(); maker_signature_with_sighash.push(sig_hash_all_fork_id); drop_mutability!(maker_signature_with_sighash); + let mut taker_signature_with_sighash: Vec = taker_signature.take(); + taker_signature_with_sighash.push(sig_hash_all_fork_id); + drop_mutability!(taker_signature_with_sighash); + let script_sig = Builder::default() .push_data(&maker_signature_with_sighash) .push_data(&taker_signature_with_sighash) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index d61a9ed2e1..144c6f8037 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -196,7 +196,6 @@ pub struct SwapV2MsgStore { maker_negotiated: Option, taker_funding: Option, maker_payment: Option, - taker_funding_spend_preimage: Option, taker_payment: Option, taker_payment_spend_preimage: Option, #[allow(dead_code)] @@ -1575,9 +1574,6 @@ pub fn process_swap_v2_msg(ctx: MmArc, topic: &str, msg: &[u8]) -> P2PProcessRes Some(swap_v2_pb::swap_message::Inner::MakerPaymentInfo(maker_payment)) => { msg_store.maker_payment = Some(maker_payment) }, - Some(swap_v2_pb::swap_message::Inner::TakerFundingSpendPreimage(preimage)) => { - msg_store.taker_funding_spend_preimage = Some(preimage) - }, Some(swap_v2_pb::swap_message::Inner::TakerPaymentInfo(taker_payment)) => { msg_store.taker_payment = Some(taker_payment) }, 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 index a25407b1d3..761fabd0e3 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -34,16 +34,18 @@ pub struct TakerNegotiationData { #[prost(uint64, tag="1")] pub started_at: u64, #[prost(uint64, tag="2")] + pub funding_locktime: u64, + #[prost(uint64, tag="3")] pub payment_locktime: u64, - #[prost(bytes="vec", tag="3")] - pub taker_secret_hash: ::prost::alloc::vec::Vec, #[prost(bytes="vec", tag="4")] - pub maker_coin_htlc_pub: ::prost::alloc::vec::Vec, + pub taker_secret_hash: ::prost::alloc::vec::Vec, #[prost(bytes="vec", tag="5")] + pub maker_coin_htlc_pub: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] 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 maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", optional, tag="8")] pub taker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] @@ -77,13 +79,6 @@ pub struct TakerFundingInfo { pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] -pub struct TakerFundingSpendPreimage { - #[prost(bytes="vec", tag="1")] - pub signature: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", tag="2")] - pub tx_preimage: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, ::prost::Message)] pub struct TakerPaymentInfo { #[prost(bytes="vec", tag="1")] pub tx_bytes: ::prost::alloc::vec::Vec, @@ -96,6 +91,10 @@ pub struct MakerPaymentInfo { pub tx_bytes: ::prost::alloc::vec::Vec, #[prost(bytes="vec", optional, tag="2")] pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(bytes="vec", tag="3")] + pub funding_preimage_sig: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="4")] + pub funding_preimage_tx: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct TakerPaymentSpendPreimage { @@ -106,7 +105,7 @@ pub struct TakerPaymentSpendPreimage { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct SwapMessage { - #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6, 7, 8")] + #[prost(oneof="swap_message::Inner", tags="1, 2, 3, 4, 5, 6, 7")] pub inner: ::core::option::Option, } /// Nested message and enum types in `SwapMessage`. @@ -124,10 +123,8 @@ pub mod swap_message { #[prost(message, tag="5")] MakerPaymentInfo(super::MakerPaymentInfo), #[prost(message, tag="6")] - TakerFundingSpendPreimage(super::TakerFundingSpendPreimage), - #[prost(message, tag="7")] TakerPaymentInfo(super::TakerPaymentInfo), - #[prost(message, tag="8")] + #[prost(message, tag="7")] TakerPaymentSpendPreimage(super::TakerPaymentSpendPreimage), } } diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index e201a62791..fbcdcfd816 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -7,8 +7,8 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_s MAX_STARTED_AT_DIFF}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; -use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerPaymentSpendArgs, MarketCoinOps, MmCoin, - SendPaymentArgs, SwapOpsV2, ToBytes, Transaction, TxPreimageWithSig}; +use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MarketCoinOps, MmCoin, SendPaymentArgs, SwapOpsV2, ToBytes, Transaction, TxPreimageWithSig}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -401,9 +401,15 @@ impl State fo return Self::change_state(Aborted::new(reason), state_machine).await; } + let expected_taker_funding_locktime = taker_data.started_at + 3 * state_machine.lock_duration; + if taker_data.funding_locktime != expected_taker_funding_locktime { + let reason = AbortReason::TakerProvidedInvalidFundingLocktime(taker_data.funding_locktime); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + let expected_taker_payment_locktime = taker_data.started_at + state_machine.lock_duration; if taker_data.payment_locktime != expected_taker_payment_locktime { - let reason = AbortReason::TakerProvidedInvalidLocktime(taker_data.payment_locktime); + let reason = AbortReason::TakerProvidedInvalidPaymentLocktime(taker_data.payment_locktime); return Self::change_state(Aborted::new(reason), state_machine).await; } @@ -430,6 +436,7 @@ impl State fo taker_coin_start_block: self.taker_coin_start_block, negotiation_data: NegotiationData { taker_payment_locktime: expected_taker_payment_locktime, + taker_funding_locktime: expected_taker_funding_locktime, maker_coin_htlc_pub_from_taker, taker_coin_htlc_pub_from_taker, maker_coin_swap_contract: taker_data.maker_coin_swap_contract, @@ -443,6 +450,7 @@ impl State fo struct NegotiationData { taker_payment_locktime: u64, + taker_funding_locktime: u64, maker_coin_htlc_pub_from_taker: MakerCoin::Pubkey, taker_coin_htlc_pub_from_taker: TakerCoin::Pubkey, maker_coin_swap_contract: Option>, @@ -563,6 +571,28 @@ impl State type StateMachine = MakerSwapStateMachine; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { + let unique_data = state_machine.unique_data(); + let args = GenTakerFundingSpendArgs { + funding_tx: &self.taker_funding, + maker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), + taker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker, + funding_time_lock: self.negotiation_data.taker_funding_locktime, + taker_secret_hash: &self.negotiation_data.taker_secret_hash, + taker_payment_time_lock: self.negotiation_data.taker_payment_locktime, + maker_secret_hash: &state_machine.secret_hash(), + }; + let funding_spend_preimage = match state_machine + .taker_coin + .gen_taker_funding_spend_preimage(&args, &unique_data) + .await + { + Ok(p) => p, + Err(e) => { + let reason = AbortReason::FailedToGenerateFundingSpend(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + let args = SendPaymentArgs { time_lock_duration: state_machine.lock_duration, time_lock: state_machine.maker_payment_locktime(), @@ -570,7 +600,7 @@ impl State secret_hash: &state_machine.secret_hash(), amount: state_machine.maker_volume.to_decimal(), swap_contract_address: &None, - swap_unique_data: &state_machine.unique_data(), + swap_unique_data: &unique_data, payment_instructions: &None, watcher_reward: None, wait_for_confirmation_until: 0, @@ -588,11 +618,12 @@ impl State maker_payment.tx_hash(), state_machine.uuid ); - let next_state = MakerPaymentSent { + let next_state = MakerPaymentSentFundingSpendGenerated { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data, taker_funding: self.taker_funding, + funding_spend_preimage, maker_payment: TransactionIdentifier { tx_hex: maker_payment.tx_hex().into(), tx_hash: maker_payment.tx_hash(), @@ -621,22 +652,23 @@ impl StorableS } } -struct MakerPaymentSent { +struct MakerPaymentSentFundingSpendGenerated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, taker_funding: TakerCoin::Tx, + funding_spend_preimage: TxPreimageWithSig, maker_payment: TransactionIdentifier, } impl TransitionFrom> - for MakerPaymentSent + for MakerPaymentSentFundingSpendGenerated { } #[async_trait] impl State - for MakerPaymentSent + for MakerPaymentSentFundingSpendGenerated { type StateMachine = MakerSwapStateMachine; @@ -644,6 +676,8 @@ impl State let maker_payment_info = MakerPaymentInfo { tx_bytes: self.maker_payment.tx_hex.0.clone(), next_step_instructions: None, + funding_preimage_sig: self.funding_spend_preimage.signature.to_bytes(), + funding_preimage_tx: self.funding_spend_preimage.preimage.to_bytes(), }; let swap_msg = SwapMessage { inner: Some(swap_message::Inner::MakerPaymentInfo(maker_payment_info)), @@ -660,11 +694,11 @@ impl State let recv_fut = recv_swap_v2_msg( state_machine.ctx.clone(), - |store| store.taker_funding_spend_preimage.take(), + |store| store.taker_payment.take(), &state_machine.uuid, NEGOTIATION_TIMEOUT_SEC, ); - let taker_funding_spend_preimage = match recv_fut.await { + let taker_payment_info = match recv_fut.await { Ok(p) => p, Err(e) => { let next_state = MakerPaymentRefundRequired { @@ -672,13 +706,27 @@ impl State taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data, maker_payment: self.maker_payment, - reason: MakerPaymentRefundReason::DidNotReceiveTakerFundingPreimage(e), + reason: MakerPaymentRefundReason::DidNotGetTakerPayment(e), }; return Self::change_state(next_state, state_machine).await; }, }; drop(abort_handle); - /* + + let taker_payment = match state_machine.taker_coin.parse_tx(&taker_payment_info.tx_bytes) { + Ok(tx) => tx, + Err(e) => { + let next_state = MakerPaymentRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + negotiation_data: self.negotiation_data, + maker_payment: self.maker_payment, + reason: MakerPaymentRefundReason::FailedToParseTakerPayment(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + let input = ConfirmPaymentInput { payment_tx: self.taker_funding.tx_hex(), confirmations: state_machine.conf_settings.taker_coin_confs, @@ -701,18 +749,15 @@ impl State 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, negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await - - */ - unimplemented!() } } impl StorableState - for MakerPaymentSent + for MakerPaymentSentFundingSpendGenerated { type StateMachine = MakerSwapStateMachine; @@ -728,7 +773,8 @@ impl StorableS #[derive(Debug)] enum MakerPaymentRefundReason { - DidNotReceiveTakerFundingPreimage(String), + DidNotGetTakerPayment(String), + FailedToParseTakerPayment(String), TakerPaymentNotConfirmedInTime(String), DidNotGetTakerPaymentSpendPreimage(String), TakerPaymentSpendPreimageIsNotValid(String), @@ -745,7 +791,8 @@ struct MakerPaymentRefundRequired TransitionFrom> +impl + TransitionFrom> for MakerPaymentRefundRequired { } @@ -795,7 +842,8 @@ struct TakerPaymentConfirmed, } -impl TransitionFrom> +impl + TransitionFrom> for TakerPaymentConfirmed { } @@ -1002,9 +1050,11 @@ pub enum AbortReason { ReceivedInvalidTakerNegotiation, DidNotReceiveTakerFundingInfo(String), FailedToParseTakerFunding(String), + FailedToGenerateFundingSpend(String), FailedToSendMakerPayment(String), TooLargeStartedAtDiff(u64), - TakerProvidedInvalidLocktime(u64), + TakerProvidedInvalidFundingLocktime(u64), + TakerProvidedInvalidPaymentLocktime(u64), FailedToParsePubkey(String), } diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2.proto b/mm2src/mm2_main/src/lp_swap/swap_v2.proto index 7139e1b2d6..9bbaa87e5d 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2.proto +++ b/mm2src/mm2_main/src/lp_swap/swap_v2.proto @@ -24,12 +24,13 @@ message Abort { message TakerNegotiationData { uint64 started_at = 1; - uint64 payment_locktime = 2; - bytes taker_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; + uint64 funding_locktime = 2; + uint64 payment_locktime = 3; + bytes taker_secret_hash = 4; + bytes maker_coin_htlc_pub = 5; + bytes taker_coin_htlc_pub = 6; + optional bytes maker_coin_swap_contract = 7; + optional bytes taker_coin_swap_contract = 8; } message TakerNegotiation { @@ -50,11 +51,6 @@ message TakerFundingInfo { optional bytes next_step_instructions = 2; } -message TakerFundingSpendPreimage { - bytes signature = 1; - bytes tx_preimage = 2; -} - message TakerPaymentInfo { bytes tx_bytes = 1; optional bytes next_step_instructions = 2; @@ -63,6 +59,8 @@ message TakerPaymentInfo { message MakerPaymentInfo { bytes tx_bytes = 1; optional bytes next_step_instructions = 2; + bytes funding_preimage_sig = 3; + bytes funding_preimage_tx = 4; } message TakerPaymentSpendPreimage { @@ -77,8 +75,7 @@ message SwapMessage { MakerNegotiated maker_negotiated = 3; TakerFundingInfo taker_funding_info = 4; MakerPaymentInfo maker_payment_info = 5; - TakerFundingSpendPreimage taker_funding_spend_preimage = 6; - TakerPaymentInfo taker_payment_info = 7; - TakerPaymentSpendPreimage taker_payment_spend_preimage = 8; + TakerPaymentInfo taker_payment_info = 6; + TakerPaymentSpendPreimage taker_payment_spend_preimage = 7; } } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 80fdfd40fa..dc1092adbe 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MmCoin, SendCombinedTakerPaymentArgs, SendTakerFundingArgs, SpendPaymentArgs, SwapOps, SwapOpsV2, ToBytes, - Transaction, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; + Transaction, TxPreimageWithSig, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -416,6 +416,7 @@ impl State fo let taker_negotiation = TakerNegotiation { action: Some(taker_negotiation::Action::Continue(TakerNegotiationData { started_at: state_machine.started_at, + funding_locktime: state_machine.taker_funding_locktime(), payment_locktime: state_machine.taker_payment_locktime(), taker_secret_hash: state_machine.taker_secret_hash(), maker_coin_htlc_pub: state_machine.maker_coin.derive_htlc_pubkey(&unique_data), @@ -649,11 +650,49 @@ impl State debug!("Received maker payment info message {:?}", maker_payment_info); - let next_state = MakerPaymentReceived { + let preimage_tx = match state_machine + .taker_coin + .parse_preimage(&maker_payment_info.funding_preimage_tx) + { + Ok(p) => p, + Err(e) => { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FailedToParseFundingSpendPreimg(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + let preimage_sig = match state_machine + .taker_coin + .parse_signature(&maker_payment_info.funding_preimage_sig) + { + Ok(p) => p, + Err(e) => { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FailedToParseFundingSpendSig(e.to_string()), + }; + return Self::change_state(next_state, state_machine).await; + }, + }; + + let next_state = MakerPaymentAndFundingSpendPreimgReceived { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data, taker_funding: self.taker_funding, + funding_spend_preimage: TxPreimageWithSig { + preimage: preimage_tx, + signature: preimage_sig, + }, maker_payment: TransactionIdentifier { tx_hex: maker_payment_info.tx_bytes.into(), tx_hash: Default::default(), @@ -686,21 +725,22 @@ impl StorableS } } -struct MakerPaymentReceived { +struct MakerPaymentAndFundingSpendPreimgReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, taker_funding: TakerCoin::Tx, + funding_spend_preimage: TxPreimageWithSig, maker_payment: TransactionIdentifier, } impl TransitionFrom> - for MakerPaymentReceived + for MakerPaymentAndFundingSpendPreimgReceived { } impl StorableState - for MakerPaymentReceived + for MakerPaymentAndFundingSpendPreimgReceived { type StateMachine = TakerSwapStateMachine; @@ -719,8 +759,8 @@ impl StorableS } #[async_trait] -impl State - for MakerPaymentReceived +impl State + for MakerPaymentAndFundingSpendPreimgReceived { type StateMachine = TakerSwapStateMachine; @@ -760,24 +800,32 @@ impl p, + Ok(tx) => tx, Err(e) => { let next_state = TakerFundingRefundRequired { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, taker_funding: self.taker_funding, negotiation_data: self.negotiation_data, - reason: TakerFundingRefundReason::FailedToGenerateSpendPreimage(format!("{:?}", e)), + reason: TakerFundingRefundReason::MakerPaymentValidationFailed(format!("{:?}", e)), }; return Self::change_state(next_state, state_machine).await; }, }; - unimplemented!() + + let next_state = TakerPaymentSent { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_payment, + maker_payment: self.maker_payment, + negotiation_data: self.negotiation_data, + }; + Self::change_state(next_state, state_machine).await } } @@ -785,9 +833,16 @@ struct TakerPaymentSent { maker_coin_start_block: u64, taker_coin_start_block: u64, taker_payment: TakerCoin::Tx, + maker_payment: TransactionIdentifier, negotiation_data: NegotiationData, } +impl + TransitionFrom> + for TakerPaymentSent +{ +} + #[async_trait] impl State for TakerPaymentSent @@ -802,7 +857,7 @@ impl State let swap_msg = SwapMessage { inner: Some(swap_message::Inner::TakerPaymentInfo(taker_payment_info)), }; - let abort_handle = broadcast_swap_v2_msg_every( + let _abort_handle = broadcast_swap_v2_msg_every( state_machine.ctx.clone(), state_machine.p2p_topic.clone(), swap_msg, @@ -810,29 +865,8 @@ impl State 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 { - taker_payment: self.taker_payment, - negotiation_data: self.negotiation_data, - 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(), + payment_tx: self.maker_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.maker_payment_conf_timeout(), @@ -851,10 +885,7 @@ impl State let next_state = MakerPaymentConfirmed { 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(), - }, + maker_payment: self.maker_payment, taker_payment: self.taker_payment, negotiation_data: self.negotiation_data, }; @@ -883,8 +914,10 @@ impl StorableS #[derive(Debug)] enum TakerFundingRefundReason { DidNotReceiveMakerPayment(String), + FailedToParseFundingSpendPreimg(String), + FailedToParseFundingSpendSig(String), + FailedToSendTakerPayment(String), MakerPaymentValidationFailed(String), - FailedToGenerateSpendPreimage(String), MakerDidNotBroadcastInTime(String), } @@ -900,7 +933,8 @@ impl TransitionFrom { } -impl TransitionFrom> +impl + TransitionFrom> for TakerFundingRefundRequired { } 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 628299bfd1..a60a80a67d 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 @@ -178,9 +178,9 @@ fn send_and_spend_taker_funding() { taker_payment_time_lock: 0, maker_secret_hash: &[0; 20], }; - let preimage = block_on(taker_coin.gen_taker_funding_spend_preimage(&preimage_args, &[])).unwrap(); + let preimage = block_on(maker_coin.gen_taker_funding_spend_preimage(&preimage_args, &[])).unwrap(); - let payment_tx = block_on(maker_coin.sign_and_send_taker_funding_spend(&preimage, &preimage_args, &[])).unwrap(); + let payment_tx = block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &preimage_args, &[])).unwrap(); println!("Taker payment tx {:02x}", payment_tx.tx_hash()); } From 89f6185b5e5b111f63c74c08cb9fae8187e7cdaf Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Fri, 29 Sep 2023 11:20:06 +0700 Subject: [PATCH 20/30] WIP. Made successful swap after enhancement. --- mm2src/coins/utxo/utxo_common.rs | 41 ++++++++++++++++--- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 27 ++++++------ .../tests/docker_tests/swap_proto_v2_tests.rs | 2 +- mm2src/mm2_state_machine/src/state_machine.rs | 2 +- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index e153460fcb..a9cb52b798 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -63,7 +63,6 @@ use utxo_signer::with_key_pair::{calc_and_sign_sighash, p2sh_spend, signature_ha SIGHASH_SINGLE}; use utxo_signer::UtxoSignerOps; -use crate::utxo::swap_proto_v2_scripts::taker_funding_script; pub use chain::Transaction as UtxoTx; pub mod utxo_tx_history_v2_common; @@ -1343,6 +1342,30 @@ pub async fn sign_and_send_taker_funding_spend( final_tx_input.script_sig = script_sig; drop_mutability!(final_tx); + if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { + let payment_redeem_script = swap_proto_v2_scripts::taker_payment_script( + try_tx_s!(gen_args.taker_payment_time_lock.try_into()), + gen_args.maker_secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); + let payment_address = Address { + checksum_type: coin.as_ref().conf.checksum_type, + hash: AddressHashEnum::AddressHash(dhash160(&payment_redeem_script)), + prefix: coin.as_ref().conf.p2sh_addr_prefix, + t_addr_prefix: coin.as_ref().conf.p2sh_t_addr_prefix, + hrp: coin.as_ref().conf.bech32_hrp.clone(), + addr_format: UtxoAddressFormat::Standard, + }; + let payment_address_str = payment_address.to_string(); + try_tx_s!( + client + .import_address(&payment_address_str, &payment_address_str, false) + .compat() + .await + ); + } + try_tx_s!(coin.broadcast_tx(&final_tx).await, final_tx); Ok(final_tx) } @@ -1930,7 +1953,8 @@ async fn refund_htlc_payment( payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public).into() }, SwapPaymentType::TakerFunding => { - taker_funding_script(time_lock, args.secret_hash, key_pair.public(), &other_public).into() + swap_proto_v2_scripts::taker_funding_script(time_lock, args.secret_hash, key_pair.public(), &other_public) + .into() }, SwapPaymentType::TakerPaymentV2 => { swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, key_pair.public(), &other_public) @@ -4320,7 +4344,9 @@ 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::TakerFunding => taker_funding_script(time_lock, secret_hash, &my_public, &other_public), + SwapPaymentType::TakerFunding => { + swap_proto_v2_scripts::taker_funding_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) }, @@ -4736,8 +4762,13 @@ where .into_script(); let time_lock = try_tx_s!(args.time_lock.try_into()); - let redeem_script = - taker_funding_script(time_lock, args.taker_secret_hash, key_pair.public(), args.maker_pubkey).into(); + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + time_lock, + args.taker_secret_hash, + key_pair.public(), + args.maker_pubkey, + ) + .into(); let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .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 index dc1092adbe..fd0e523f02 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -8,15 +8,15 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_s use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, - MmCoin, SendCombinedTakerPaymentArgs, SendTakerFundingArgs, SpendPaymentArgs, SwapOps, SwapOpsV2, ToBytes, - Transaction, TxPreimageWithSig, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; + MmCoin, SendTakerFundingArgs, SpendPaymentArgs, SwapOps, SwapOpsV2, ToBytes, Transaction, + TxPreimageWithSig, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; +use mm2_number::MmNumber; use mm2_state_machine::prelude::*; use mm2_state_machine::storable_state_machine::*; use primitives::hash::H256; @@ -65,6 +65,7 @@ pub enum TakerSwapEvent { taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, taker_funding: TransactionIdentifier, + reason: TakerFundingRefundReason, }, /// Received maker payment MakerPaymentReceived { @@ -86,8 +87,8 @@ pub enum TakerSwapEvent { taker_payment: TransactionIdentifier, negotiation_data: StoredNegotiationData, }, - /// Both payments are confirmed on-chain - BothPaymentsSentAndConfirmed { + /// Maker payment is confirmed on-chain + MakerPaymentConfirmed { maker_coin_start_block: u64, taker_coin_start_block: u64, maker_payment: TransactionIdentifier, @@ -812,7 +813,7 @@ impl State taker_coin_start_block: self.taker_coin_start_block, taker_funding: self.taker_funding, negotiation_data: self.negotiation_data, - reason: TakerFundingRefundReason::MakerPaymentValidationFailed(format!("{:?}", e)), + reason: TakerFundingRefundReason::FailedToSendTakerPayment(format!("{:?}", e)), }; return Self::change_state(next_state, state_machine).await; }, @@ -911,14 +912,14 @@ impl StorableS } } -#[derive(Debug)] -enum TakerFundingRefundReason { +/// Represents the reason taker funding refund +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum TakerFundingRefundReason { DidNotReceiveMakerPayment(String), FailedToParseFundingSpendPreimg(String), FailedToParseFundingSpendSig(String), FailedToSendTakerPayment(String), MakerPaymentValidationFailed(String), - MakerDidNotBroadcastInTime(String), } struct TakerFundingRefundRequired { @@ -945,7 +946,9 @@ impl; - async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult { todo!() } + async fn on_changed(self: Box, _state_machine: &mut Self::StateMachine) -> StateResult { + todo!() + } } impl StorableState @@ -962,13 +965,13 @@ impl StorableS type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> <::Storage as StateMachineStorage>::Event { - TakerSwapEvent::BothPaymentsSentAndConfirmed { + TakerSwapEvent::MakerPaymentConfirmed { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, maker_payment: self.maker_payment.clone(), 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 a60a80a67d..fc3fd2174f 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 @@ -401,7 +401,7 @@ fn test_v2_swap_utxo_utxo() { 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(); + block_on(mm_alice.wait_for_log(30., |log| log.contains(&expected_msg))).unwrap(); let maker_swap_status = block_on(my_swap_status(&mm_bob, &uuid)); println!("{:?}", maker_swap_status); diff --git a/mm2src/mm2_state_machine/src/state_machine.rs b/mm2src/mm2_state_machine/src/state_machine.rs index 63ce6a96a3..9deae81120 100644 --- a/mm2src/mm2_state_machine/src/state_machine.rs +++ b/mm2src/mm2_state_machine/src/state_machine.rs @@ -63,7 +63,7 @@ pub trait State: Send + Sync + 'static { /// ```rust /// return Self::change_state(next_state); /// ``` - async fn on_changed(self: Box, ctx: &mut Self::StateMachine) -> StateResult; + async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult; } /// A trait for transitioning between states in the state machine. From eee4470039c24b427db597912d3c7fd12e8368af Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Fri, 29 Sep 2023 12:55:54 +0700 Subject: [PATCH 21/30] WIP. Added validate_taker_funding. --- mm2src/coins/lp_coins.rs | 62 ++--- mm2src/coins/test_coin.rs | 30 +-- mm2src/coins/utxo/utxo_common.rs | 69 ++---- mm2src/coins/utxo/utxo_standard.rs | 32 +-- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 21 +- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 45 +--- .../tests/docker_tests/swap_proto_v2_tests.rs | 223 ++---------------- 7 files changed, 108 insertions(+), 374 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 1d43f70612..9f904204cf 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -315,8 +315,8 @@ pub type RawTransactionFut<'a> = pub type RefundResult = Result>; /// Helper type used for swap transactions' spend preimage generation result pub type GenPreimageResult = MmResult, TxGenError>; -/// Helper type used for taker payment's validation result -pub type ValidateTakerPaymentResult = MmResult<(), ValidateTakerPaymentError>; +/// Helper type used for taker funding's validation result +pub type ValidateTakerFundingResult = MmResult<(), ValidateTakerFundingError>; /// Helper type used for taker payment's spend preimage validation result pub type ValidateTakerPaymentSpendPreimageResult = MmResult<(), ValidateTakerPaymentSpendPreimageError>; @@ -1109,32 +1109,14 @@ pub struct GenTakerFundingSpendArgs<'a, Coin: CoinAssocTypes + ?Sized> { pub maker_secret_hash: &'a [u8], } -/// 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 maker_secret_hash: &'a [u8], - /// Maker's pubkey - pub maker_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], -} - -/// Helper struct wrapping arguments for [SwapOpsV2::validate_combined_taker_payment] -pub struct ValidateTakerPaymentArgs<'a, Coin: CoinAssocTypes + ?Sized> { - /// Taker payment transaction serialized to raw bytes - pub taker_tx: &'a Coin::Tx, +/// Helper struct wrapping arguments for [SwapOpsV2::validate_taker_funding] +pub struct ValidateTakerFundingArgs<'a, Coin: CoinAssocTypes + ?Sized> { + /// Taker funding transaction + pub funding_tx: &'a Coin::Tx, /// 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], + /// The hash of the secret generated by taker + pub taker_secret_hash: &'a [u8], /// Taker's pubkey pub other_pub: &'a Coin::Pubkey, /// DEX fee amount @@ -1208,16 +1190,17 @@ 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 ValidateTakerPaymentError { +/// Enum covering error cases that can happen during taker funding validation. +#[derive(Debug, Display)] +pub enum ValidateTakerFundingError { /// Payment sent to wrong address or has invalid amount. InvalidDestinationOrAmount(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. + /// Serialized tx bytes don't match ones received from coin's RPC. + #[display(fmt = "Tx bytes {:02x} don't match ones received from rpc {:02x}", actual, from_rpc)] TxBytesMismatch { from_rpc: BytesJson, actual: BytesJson }, /// Provided transaction doesn't have output with specific index TxLacksOfOutputs, @@ -1225,12 +1208,12 @@ pub enum ValidateTakerPaymentError { LocktimeOverflow(String), } -impl From for ValidateTakerPaymentError { - fn from(err: NumConversError) -> Self { ValidateTakerPaymentError::NumConversion(err.to_string()) } +impl From for ValidateTakerFundingError { + fn from(err: NumConversError) -> Self { ValidateTakerFundingError::NumConversion(err.to_string()) } } -impl From for ValidateTakerPaymentError { - fn from(err: UtxoRpcError) -> Self { ValidateTakerPaymentError::Rpc(err.to_string()) } +impl From for ValidateTakerFundingError { + fn from(err: UtxoRpcError) -> Self { ValidateTakerFundingError::Rpc(err.to_string()) } } /// Enum covering error cases that can happen during taker payment spend preimage validation. @@ -1314,17 +1297,8 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { swap_unique_data: &[u8], ) -> Result; - /// 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<'_>, - ) -> Result; - /// Validates taker payment transaction. - async fn validate_combined_taker_payment( - &self, - args: ValidateTakerPaymentArgs<'_, Self>, - ) -> ValidateTakerPaymentResult; + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult; /// Refunds taker payment transaction. async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 64d892a489..cbff2e4fd1 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -7,15 +7,15 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, - SignatureResult, SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageFut, - TradePreimageResult, TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, - TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentResult, - ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, + SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageFut, TradePreimageResult, + TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerPaymentSpendPreimageResult, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, + WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -460,17 +460,7 @@ impl SwapOpsV2 for TestCoin { todo!() } - async fn send_combined_taker_payment( - &self, - args: SendCombinedTakerPaymentArgs<'_>, - ) -> Result { - unimplemented!() - } - - async fn validate_combined_taker_payment( - &self, - args: ValidateTakerPaymentArgs<'_, Self>, - ) -> ValidateTakerPaymentResult { + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { unimplemented!() } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index a9cb52b798..4660211ba0 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -18,11 +18,11 @@ use crate::watcher_common::validate_watcher_reward; use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, - RewardTarget, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, - TradePreimageValue, TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, - TxPreimageWithSig, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, ValidateTakerPaymentArgs, ValidateTakerPaymentError, ValidateTakerPaymentResult, + RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SendTakerFundingArgs, SignatureError, SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, + TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, + ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + ValidateTakerFundingArgs, ValidateTakerFundingError, ValidateTakerFundingResult, ValidateTakerPaymentSpendPreimageError, ValidateTakerPaymentSpendPreimageResult, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, WithdrawSenderAddress, @@ -4803,45 +4803,8 @@ where Ok(transaction) } -/// Common implementation of combined taker payment generation and broadcast for UTXO coins. -pub async fn send_combined_taker_payment( - coin: T, - args: SendCombinedTakerPaymentArgs<'_>, -) -> Result -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 + &args.trading_amount; - - let SwapPaymentOutputsResult { - payment_address, - outputs, - } = try_tx_s!(generate_swap_payment_outputs( - &coin, - try_tx_s!(args.time_lock.try_into()), - taker_htlc_key_pair.public_slice(), - args.maker_pub, - args.maker_secret_hash, - total_amount, - SwapPaymentType::TakerPaymentV2, - )); - if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { - let addr_string = try_tx_s!(payment_address.display_address()); - client - .import_address(&addr_string, &addr_string, false) - .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))) - .compat() - .await?; - } - send_outputs_from_my_address_impl(coin, outputs).await -} - -/// Common implementation of combined taker payment validation for UTXO coins. -pub async fn validate_combined_taker_payment( - coin: &T, - args: ValidateTakerPaymentArgs<'_, T>, -) -> ValidateTakerPaymentResult +/// Common implementation of taker funding validation for UTXO coins. +pub async fn validate_taker_funding(coin: &T, args: ValidateTakerFundingArgs<'_, T>) -> ValidateTakerFundingResult where T: UtxoCommonOps + SwapOps, { @@ -4853,11 +4816,11 @@ where let time_lock = args .time_lock .try_into() - .map_to_mm(|e: TryFromIntError| ValidateTakerPaymentError::LocktimeOverflow(e.to_string()))?; + .map_to_mm(|e: TryFromIntError| ValidateTakerFundingError::LocktimeOverflow(e.to_string()))?; - let redeem_script = swap_proto_v2_scripts::taker_payment_script( + let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, - args.secret_hash, + args.taker_secret_hash, args.other_pub, maker_htlc_key_pair.public(), ); @@ -4866,23 +4829,23 @@ where script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&redeem_script))).into(), }; - if args.taker_tx.outputs.get(0) != Some(&expected_output) { - return MmError::err(ValidateTakerPaymentError::InvalidDestinationOrAmount(format!( + if args.funding_tx.outputs.get(0) != Some(&expected_output) { + return MmError::err(ValidateTakerFundingError::InvalidDestinationOrAmount(format!( "Expected {:?}, got {:?}", expected_output, - args.taker_tx.outputs.get(0) + args.funding_tx.outputs.get(0) ))); } let tx_bytes_from_rpc = coin .as_ref() .rpc_client - .get_transaction_bytes(&args.taker_tx.hash().reversed().into()) + .get_transaction_bytes(&args.funding_tx.hash().reversed().into()) .compat() .await?; - let actual_tx_bytes = serialize(args.taker_tx).take(); + let actual_tx_bytes = serialize(args.funding_tx).take(); if tx_bytes_from_rpc.0 != actual_tx_bytes { - return MmError::err(ValidateTakerPaymentError::TxBytesMismatch { + return MmError::err(ValidateTakerFundingError::TxBytesMismatch { from_rpc: tx_bytes_from_rpc, actual: actual_tx_bytes.into(), }); diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 6c7ca8c9d7..6d97c143a2 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -26,15 +26,15 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendCombinedTakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, - SpendPaymentArgs, SwapOps, SwapOpsV2, TakerSwapMakerCoin, ToBytes, 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}; + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SendTakerFundingArgs, SignatureResult, SpendPaymentArgs, SwapOps, SwapOpsV2, + TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, + TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerPaymentSpendPreimageResult, + VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, + WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; @@ -623,18 +623,8 @@ impl SwapOpsV2 for UtxoStandardCoin { utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await } - async fn send_combined_taker_payment( - &self, - args: SendCombinedTakerPaymentArgs<'_>, - ) -> Result { - utxo_common::send_combined_taker_payment(self.clone(), args).await - } - - async fn validate_combined_taker_payment( - &self, - args: ValidateTakerPaymentArgs<'_, Self>, - ) -> ValidateTakerPaymentResult { - utxo_common::validate_combined_taker_payment(self, args).await + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { + utxo_common::validate_taker_funding(self, args).await } async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index fbcdcfd816..0a87681333 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -8,7 +8,8 @@ use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_s use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, - MarketCoinOps, MmCoin, SendPaymentArgs, SwapOpsV2, ToBytes, Transaction, TxPreimageWithSig}; + MarketCoinOps, MmCoin, SendPaymentArgs, SwapOpsV2, ToBytes, Transaction, TxPreimageWithSig, + ValidateTakerFundingArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; use db_common::sqlite::rusqlite::params; @@ -572,6 +573,23 @@ impl State async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { let unique_data = state_machine.unique_data(); + + let validation_args = ValidateTakerFundingArgs { + funding_tx: &self.taker_funding, + time_lock: self.negotiation_data.taker_funding_locktime, + taker_secret_hash: &self.negotiation_data.taker_secret_hash, + other_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker, + dex_fee_amount: state_machine.dex_fee_amount.to_decimal(), + premium_amount: state_machine.taker_premium.to_decimal(), + trading_amount: state_machine.taker_volume.to_decimal(), + swap_unique_data: &unique_data, + }; + + if let Err(e) = state_machine.taker_coin.validate_taker_funding(validation_args).await { + let reason = AbortReason::TakerFundingValidationFailed(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + } + let args = GenTakerFundingSpendArgs { funding_tx: &self.taker_funding, maker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), @@ -1050,6 +1068,7 @@ pub enum AbortReason { ReceivedInvalidTakerNegotiation, DidNotReceiveTakerFundingInfo(String), FailedToParseTakerFunding(String), + TakerFundingValidationFailed(String), FailedToGenerateFundingSpend(String), FailedToSendMakerPayment(String), TooLargeStartedAtDiff(u64), diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index fd0e523f02..fd50d55b35 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -273,8 +273,8 @@ impl State fo negotiation_data: self.negotiation_data, }; Self::change_state(next_state, state_machine).await - /* - let args = SendCombinedTakerPaymentArgs { - time_lock: state_machine.taker_payment_locktime(), - maker_secret_hash: &self.negotiation_data.maker_secret_hash, - maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), - 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 reason = AbortReason::FailedToSendTakerPayment(format!("{:?}", e)); - return Self::change_state(Aborted::new(reason), 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_start_block: self.maker_coin_start_block, - taker_coin_start_block: self.taker_coin_start_block, - taker_payment, - negotiation_data: self.negotiation_data, - }; - Self::change_state(next_state, state_machine).await - - */ } } @@ -819,6 +785,13 @@ impl State }, }; + info!( + "Sent taker payment {} tx {:02x} during swap {}", + state_machine.taker_coin.ticker(), + taker_payment.tx_hash(), + state_machine.uuid + ); + let next_state = TakerPaymentSent { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, 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 fc3fd2174f..7276fd1847 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,12 +1,9 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; -use chain::TransactionOutput; -use coins::utxo::swap_proto_v2_scripts::taker_payment_script; -use coins::utxo::{UtxoCommonOps, UtxoTxBroadcastOps}; -use coins::{GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, - SendCombinedTakerPaymentArgs, SendTakerFundingArgs, SwapOpsV2, Transaction, ValidateTakerPaymentArgs}; -use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; -use keys::AddressHashEnum; +use coins::utxo::UtxoCommonOps; +use coins::{GenTakerFundingSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs, SendTakerFundingArgs, SwapOpsV2, + Transaction, ValidateTakerFundingArgs}; +use common::{block_on, now_sec}; use mm2_test_helpers::for_tests::{enable_native, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, MarketMakerIt, Mm2TestConf}; use script::{Builder, Opcode}; @@ -44,19 +41,17 @@ fn send_and_refund_taker_funding_timelock() { .into_bytes(); assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); - /* - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_utxo_tx, + let validate_args = ValidateTakerFundingArgs { + funding_tx: &taker_funding_utxo_tx, time_lock, - secret_hash: maker_secret_hash, + taker_secret_hash, other_pub: maker_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_combined_taker_payment(validate_args)).unwrap(); - */ + block_on(coin.validate_taker_funding(validate_args)).unwrap(); let refund_args = RefundPaymentArgs { payment_tx: &serialize(&taker_funding_utxo_tx).take(), @@ -105,19 +100,17 @@ fn send_and_refund_taker_funding_secret() { .into_bytes(); assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); - /* - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_utxo_tx, + let validate_args = ValidateTakerFundingArgs { + funding_tx: &taker_funding_utxo_tx, time_lock, - secret_hash: maker_secret_hash, + taker_secret_hash: taker_secret_hash.as_slice(), other_pub: maker_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_combined_taker_payment(validate_args)).unwrap(); - */ + block_on(coin.validate_taker_funding(validate_args)).unwrap(); let refund_args = RefundFundingSecretArgs { funding_tx: &taker_funding_utxo_tx, @@ -169,6 +162,18 @@ fn send_and_spend_taker_funding() { .into_bytes(); assert_eq!(expected_op_return, taker_funding_utxo_tx.outputs[1].script_pubkey); + let validate_args = ValidateTakerFundingArgs { + funding_tx: &taker_funding_utxo_tx, + time_lock: funding_time_lock, + taker_secret_hash, + other_pub: taker_pub, + 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_taker_funding(validate_args)).unwrap(); + let preimage_args = GenTakerFundingSpendArgs { funding_tx: &taker_funding_utxo_tx, maker_pub, @@ -184,186 +189,6 @@ fn send_and_spend_taker_funding() { println!("Taker payment tx {:02x}", payment_tx.tx_hash()); } -#[test] -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() - 1000; - let maker_secret_hash = &[0; 20]; - let maker_pub = coin.my_public_key().unwrap(); - - let send_args = SendCombinedTakerPaymentArgs { - time_lock, - maker_secret_hash, - maker_pub, - dex_fee_amount: "0.01".parse().unwrap(), - premium_amount: "0.1".parse().unwrap(), - trading_amount: 1.into(), - swap_unique_data: &[], - }; - let taker_payment_utxo_tx = block_on(coin.send_combined_taker_payment(send_args)).unwrap(); - println!("{:02x}", taker_payment_utxo_tx.tx_hash()); - // tx must have 3 outputs: actual payment, OP_RETURN containing the secret hash and change - assert_eq!(3, taker_payment_utxo_tx.outputs.len()); - - // 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, taker_payment_utxo_tx.outputs[1].script_pubkey); - - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_utxo_tx, - time_lock, - secret_hash: maker_secret_hash, - other_pub: maker_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_combined_taker_payment(validate_args)).unwrap(); - - let refund_args = RefundPaymentArgs { - payment_tx: &serialize(&taker_payment_utxo_tx).take(), - time_lock, - other_pubkey: coin.my_public_key().unwrap(), - secret_hash: &[0; 20], - swap_unique_data: &[], - swap_contract_address: &None, - watcher_reward: false, - }; - - let refund_tx = block_on(coin.refund_combined_taker_payment(refund_args)).unwrap(); - println!("{:02x}", refund_tx.tx_hash()); -} - -#[test] -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() - 1000; - let secret = [1; 32]; - let maker_secret_hash = dhash160(&secret); - let send_args = SendCombinedTakerPaymentArgs { - time_lock, - maker_secret_hash: maker_secret_hash.as_slice(), - maker_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 taker_payment_utxo_tx = block_on(taker_coin.send_combined_taker_payment(send_args)).unwrap(); - println!("taker_payment_tx hash {:02x}", taker_payment_utxo_tx.tx_hash()); - - let validate_args = ValidateTakerPaymentArgs { - taker_tx: &taker_payment_utxo_tx, - time_lock, - secret_hash: maker_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_combined_taker_payment(validate_args)).unwrap(); - - let gen_preimage_args = GenTakerPaymentSpendArgs { - taker_tx: &taker_payment_utxo_tx, - time_lock, - secret_hash: maker_secret_hash.as_slice(), - maker_pub: maker_coin.my_public_key().unwrap(), - taker_pub: taker_coin.my_public_key().unwrap(), - dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee_amount: "0.01".parse().unwrap(), - premium_amount: "0.1".parse().unwrap(), - trading_amount: 1.into(), - }; - let preimage_with_taker_sig = - block_on(taker_coin.gen_taker_payment_spend_preimage(&gen_preimage_args, &[])).unwrap(); - - block_on(maker_coin.validate_taker_payment_spend_preimage(&gen_preimage_args, &preimage_with_taker_sig)).unwrap(); - - let taker_payment_spend = block_on(maker_coin.sign_and_broadcast_taker_payment_spend( - &preimage_with_taker_sig, - &gen_preimage_args, - &secret, - &[], - )) - .unwrap(); - println!("taker_payment_spend hash {:02x}", taker_payment_spend.tx_hash()); -} - -#[test] -fn test_bob_using_alice_sig_for_payment_refund_path() { - 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() - 1000; - let secret = [1; 32]; - let maker_secret_hash = dhash160(&secret); - let send_args = SendCombinedTakerPaymentArgs { - time_lock, - maker_secret_hash: maker_secret_hash.as_slice(), - maker_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 taker_payment_utxo_tx = block_on(taker_coin.send_combined_taker_payment(send_args)).unwrap(); - println!("taker_payment_tx hash {:02x}", taker_payment_utxo_tx.tx_hash()); - - let gen_preimage_args = GenTakerPaymentSpendArgs { - taker_tx: &taker_payment_utxo_tx, - time_lock, - secret_hash: maker_secret_hash.as_slice(), - maker_pub: maker_coin.my_public_key().unwrap(), - taker_pub: taker_coin.my_public_key().unwrap(), - dex_fee_pub: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee_amount: "0.01".parse().unwrap(), - premium_amount: "0.1".parse().unwrap(), - trading_amount: 1.into(), - }; - let preimage_with_taker_sig = - block_on(taker_coin.gen_taker_payment_spend_preimage(&gen_preimage_args, &[])).unwrap(); - - let mut refund_tx = preimage_with_taker_sig.preimage; - refund_tx.outputs.push(TransactionOutput { - value: 110000000 - 10000, - script_pubkey: Builder::build_p2pkh(&AddressHashEnum::AddressHash(dhash160( - maker_coin.my_public_key().unwrap(), - ))) - .into(), - }); - - let sig_hash_single_fork_id = (3 | taker_coin.as_ref().conf.fork_id) as u8; - let mut taker_signature_with_sighash = preimage_with_taker_sig.signature.to_vec(); - taker_signature_with_sighash.push(sig_hash_single_fork_id); - - let redeem_script = taker_payment_script( - time_lock as u32, - maker_secret_hash.as_slice(), - taker_coin.my_public_key().unwrap(), - maker_coin.my_public_key().unwrap(), - ); - let script_sig = Builder::default() - .push_data(&taker_signature_with_sighash) - .push_opcode(Opcode::OP_1) - .push_data(&redeem_script) - .into_bytes(); - refund_tx.inputs[0].script_sig = script_sig; - - println!("Tx locktime {}", refund_tx.lock_time); - block_on(maker_coin.broadcast_tx(&refund_tx)).unwrap(); -} - #[test] fn test_v2_swap_utxo_utxo() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); From 5bdb141cf44827c2bae615370545bc0b1c3ce5d8 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Fri, 29 Sep 2023 15:12:51 +0700 Subject: [PATCH 22/30] WIP. Started addition of validate_taker_funding_spend_preimage. --- mm2src/coins/lp_coins.rs | 40 ++++++++++++++++++-- mm2src/coins/test_coin.rs | 24 ++++++++---- mm2src/coins/utxo/utxo_standard.rs | 24 ++++++++---- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 17 +++++++++ 4 files changed, 86 insertions(+), 19 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 9f904204cf..ad2e0ed546 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -317,6 +317,8 @@ pub type RefundResult = Result>; pub type GenPreimageResult = MmResult, TxGenError>; /// Helper type used for taker funding's validation result pub type ValidateTakerFundingResult = MmResult<(), ValidateTakerFundingError>; +/// Helper type used for taker funding's spend preimage validation result +pub type ValidateTakerFundingSpendPreimageResult = MmResult<(), ValidateTakerFundingSpendPreimageError>; /// Helper type used for taker payment's spend preimage validation result pub type ValidateTakerPaymentSpendPreimageResult = MmResult<(), ValidateTakerPaymentSpendPreimageError>; @@ -1216,6 +1218,31 @@ impl From for ValidateTakerFundingError { fn from(err: UtxoRpcError) -> Self { ValidateTakerFundingError::Rpc(err.to_string()) } } +/// Enum covering error cases that can happen during taker funding spend preimage validation. +#[derive(Debug, Display)] +pub enum ValidateTakerFundingSpendPreimageError { + /// Error during signature deserialization. + InvalidMakerSignature, + /// Error during preimage comparison to an expected one. + InvalidPreimage(String), + /// Error during taker's signature check. + SignatureVerificationFailure(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 ValidateTakerFundingSpendPreimageError { + fn from(err: UtxoSignWithKeyPairError) -> Self { + ValidateTakerFundingSpendPreimageError::SignatureVerificationFailure(err.to_string()) + } +} + +impl From for ValidateTakerFundingSpendPreimageError { + fn from(err: TxGenError) -> Self { ValidateTakerFundingSpendPreimageError::TxGenError(format!("{:?}", err)) } +} + /// Enum covering error cases that can happen during taker payment spend preimage validation. #[derive(Debug, Display)] pub enum ValidateTakerPaymentSpendPreimageError { @@ -1273,6 +1300,9 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { /// Funding tx can be reclaimed immediately if maker back-outs (doesn't send maker payment) async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result; + /// Validates taker funding transaction. + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult; + /// Refunds taker funding transaction using time-locked path without secret reveal. async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; @@ -1289,6 +1319,13 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { swap_unique_data: &[u8], ) -> GenPreimageResult; + /// Validates taker funding spend preimage generated and signed by maker + async fn validate_taker_funding_spend_preimage( + &self, + gen_args: &GenTakerFundingSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult; + /// Generates and signs a preimage spending funding tx to the combined taker payment async fn sign_and_send_taker_funding_spend( &self, @@ -1297,9 +1334,6 @@ pub trait SwapOpsV2: CoinAssocTypes + Send + Sync + 'static { swap_unique_data: &[u8], ) -> Result; - /// Validates taker payment transaction. - async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult; - /// Refunds taker payment transaction. async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult; diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index cbff2e4fd1..96cb8fec7e 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -12,10 +12,10 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerPaymentSpendPreimageResult, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawRequest}; + ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -434,6 +434,10 @@ impl CoinAssocTypes for TestCoin { impl SwapOpsV2 for TestCoin { async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { todo!() } + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { + unimplemented!() + } + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { todo!() } async fn refund_taker_funding_secret( @@ -451,6 +455,14 @@ impl SwapOpsV2 for TestCoin { todo!() } + async fn validate_taker_funding_spend_preimage( + &self, + gen_args: &GenTakerFundingSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult { + todo!() + } + async fn sign_and_send_taker_funding_spend( &self, preimage: &TxPreimageWithSig, @@ -460,10 +472,6 @@ impl SwapOpsV2 for TestCoin { todo!() } - async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { - unimplemented!() - } - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { unimplemented!() } async fn gen_taker_payment_spend_preimage( diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 6d97c143a2..9be0694b1b 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -31,10 +31,10 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerPaymentSpendPreimageResult, - VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawSenderAddress}; + ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; @@ -593,6 +593,10 @@ impl SwapOpsV2 for UtxoStandardCoin { utxo_common::send_taker_funding(self.clone(), args).await } + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { + utxo_common::validate_taker_funding(self, args).await + } + async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { utxo_common::refund_taker_funding_timelock(self.clone(), args).await } @@ -613,6 +617,14 @@ impl SwapOpsV2 for UtxoStandardCoin { utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await } + async fn validate_taker_funding_spend_preimage( + &self, + gen_args: &GenTakerFundingSpendArgs<'_, Self>, + preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult { + todo!() + } + async fn sign_and_send_taker_funding_spend( &self, preimage: &TxPreimageWithSig, @@ -623,10 +635,6 @@ impl SwapOpsV2 for UtxoStandardCoin { utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await } - async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateTakerFundingResult { - utxo_common::validate_taker_funding(self, args).await - } - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> TransactionResult { utxo_common::refund_combined_taker_payment(self.clone(), args).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 index fd50d55b35..6626a239f4 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -767,6 +767,22 @@ impl State taker_payment_time_lock: state_machine.taker_payment_locktime(), maker_secret_hash: &self.negotiation_data.maker_secret_hash, }; + + if let Err(e) = state_machine + .taker_coin + .validate_taker_funding_spend_preimage(&args, &self.funding_spend_preimage) + .await + { + let next_state = TakerFundingRefundRequired { + maker_coin_start_block: self.maker_coin_start_block, + taker_coin_start_block: self.taker_coin_start_block, + taker_funding: self.taker_funding, + negotiation_data: self.negotiation_data, + reason: TakerFundingRefundReason::FundingSpendPreimageValidationFailed(format!("{:?}", e)), + }; + return Self::change_state(next_state, state_machine).await; + } + let taker_payment = match state_machine .taker_coin .sign_and_send_taker_funding_spend(&self.funding_spend_preimage, &args, &unique_data) @@ -893,6 +909,7 @@ pub enum TakerFundingRefundReason { FailedToParseFundingSpendSig(String), FailedToSendTakerPayment(String), MakerPaymentValidationFailed(String), + FundingSpendPreimageValidationFailed(String), } struct TakerFundingRefundRequired { From 987c50d4dd9a7b2f0b438ff4d6d9a1df4806c53c Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 2 Oct 2023 10:44:18 +0700 Subject: [PATCH 23/30] WIP. Implementing validate_taker_funding_spend_preimage. --- mm2src/coins/lp_coins.rs | 2 + mm2src/coins/utxo/utxo_common.rs | 63 ++++++++++++++++++++++++++++-- mm2src/coins/utxo/utxo_standard.rs | 2 +- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index ad2e0ed546..62e8e5aabf 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1178,6 +1178,8 @@ pub enum TxGenError { Legacy(String), /// Input payment timelock overflows the type used by specific coin. LocktimeOverflow(String), + /// Transaction fee is too high + TxFeeTooHigh(String), } impl From for TxGenError { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 4660211ba0..c675b43a6e 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -23,6 +23,7 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPayment TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerFundingArgs, ValidateTakerFundingError, ValidateTakerFundingResult, + ValidateTakerFundingSpendPreimageError, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageError, ValidateTakerPaymentSpendPreimageResult, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, WithdrawResult, WithdrawSenderAddress, @@ -1245,6 +1246,14 @@ async fn gen_taker_funding_spend_preimage( .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await?; + let fee_plus_dust = fee + coin.as_ref().dust_amount; + if funding_amount < fee_plus_dust { + return MmError::err(TxGenError::Legacy(format!( + "Funding amount {} is less than fee + dust {}", + funding_amount, fee_plus_dust + ))); + } + let payment_output = TransactionOutput { value: funding_amount - fee, script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&payment_redeem_script))).to_bytes(), @@ -1291,6 +1300,56 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( }) } +/// Common implementation of taker funding spend preimage validation for UTXO coins. +/// Checks maker's signature and compares received preimage with the expected tx. +pub async fn validate_taker_funding_spend_preimage( + coin: &T, + gen_args: &GenTakerFundingSpendArgs<'_, T>, + preimage: &TxPreimageWithSig, +) -> ValidateTakerFundingSpendPreimageResult { + let expected_preimage = gen_taker_funding_spend_preimage( + coin, + gen_args, + LocktimeSetting::UseExact(0), + NTimeSetting::UseValue(preimage.preimage.n_time), + ) + .await?; + + let funding_time_lock = gen_args + .funding_time_lock + .try_into() + .map_to_mm(|e: TryFromIntError| ValidateTakerFundingSpendPreimageError::LocktimeOverflow(e.to_string()))?; + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + funding_time_lock, + gen_args.taker_secret_hash, + gen_args.taker_pub, + gen_args.maker_pub, + ); + let sig_hash = signature_hash_to_sign( + &expected_preimage, + DEFAULT_SWAP_VOUT, + &redeem_script, + coin.as_ref().conf.signature_version, + SIGHASH_ALL, + coin.as_ref().conf.fork_id, + )?; + + if !gen_args + .maker_pub + .verify(&sig_hash, &preimage.signature) + .map_to_mm(|e| ValidateTakerFundingSpendPreimageError::SignatureVerificationFailure(e.to_string()))? + { + return MmError::err(ValidateTakerFundingSpendPreimageError::InvalidMakerSignature); + }; + let expected_preimage_tx: UtxoTx = expected_preimage.into(); + if expected_preimage_tx != preimage.preimage { + return MmError::err(ValidateTakerFundingSpendPreimageError::InvalidPreimage( + "Preimage is not equal to expected".into(), + )); + } + Ok(()) +} + /// Common implementation of taker funding spend finalization and broadcast for UTXO coins. pub async fn sign_and_send_taker_funding_spend( coin: &T, @@ -1436,14 +1495,12 @@ pub async fn validate_taker_payment_spend_preimage( gen_args: &GenTakerPaymentSpendArgs<'_, T>, preimage: &TxPreimageWithSig, ) -> ValidateTakerPaymentSpendPreimageResult { - // TODO validate that output amounts are larger than dust - // 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_taker_payment_spend_preimage( coin, gen_args, - LocktimeSetting::UseExact(preimage.preimage.lock_time), + LocktimeSetting::UseExact(0), NTimeSetting::UseValue(preimage.preimage.n_time), ) .await?; diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 9be0694b1b..74caac61ca 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -622,7 +622,7 @@ impl SwapOpsV2 for UtxoStandardCoin { gen_args: &GenTakerFundingSpendArgs<'_, Self>, preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult { - todo!() + utxo_common::validate_taker_funding_spend_preimage(self, gen_args, preimage).await } async fn sign_and_send_taker_funding_spend( From 122464c08cf6c73a693518470e035ca146ab8081 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 2 Oct 2023 11:45:40 +0700 Subject: [PATCH 24/30] WIP. Finish validate_taker_funding_spend_preimage for UTXO coins. --- mm2src/coins/lp_coins.rs | 10 ++++ mm2src/coins/utxo/utxo_common.rs | 90 ++++++++++++++++++++++++-------- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 62e8e5aabf..46351ee726 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1223,6 +1223,10 @@ impl From for ValidateTakerFundingError { /// Enum covering error cases that can happen during taker funding spend preimage validation. #[derive(Debug, Display)] pub enum ValidateTakerFundingSpendPreimageError { + /// Funding tx has no outputs + FundingTxNoOutputs, + /// Actual preimage fee is either too high or too small + UnexpectedPreimageFee(String), /// Error during signature deserialization. InvalidMakerSignature, /// Error during preimage comparison to an expected one. @@ -1233,6 +1237,8 @@ pub enum ValidateTakerFundingSpendPreimageError { TxGenError(String), /// Input payment timelock overflows the type used by specific coin. LocktimeOverflow(String), + /// Coin's RPC error + Rpc(String), } impl From for ValidateTakerFundingSpendPreimageError { @@ -1245,6 +1251,10 @@ impl From for ValidateTakerFundingSpendPreimageError { fn from(err: TxGenError) -> Self { ValidateTakerFundingSpendPreimageError::TxGenError(format!("{:?}", err)) } } +impl From for ValidateTakerFundingSpendPreimageError { + fn from(err: UtxoRpcError) -> Self { ValidateTakerFundingSpendPreimageError::Rpc(err.to_string()) } +} + /// Enum covering error cases that can happen during taker payment spend preimage validation. #[derive(Debug, Display)] pub enum ValidateTakerPaymentSpendPreimageError { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index c675b43a6e..c0dc2b7c3b 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1121,6 +1121,11 @@ enum NTimeSetting { UseValue(Option), } +enum FundingSpendFeeSetting { + GetFromCoin, + UseExact(u64), +} + async fn p2sh_spending_tx_preimage( coin: &T, prev_tx: &UtxoTx, @@ -1226,8 +1231,8 @@ type GenPreimageResInner = MmResult; async fn gen_taker_funding_spend_preimage( coin: &T, args: &GenTakerFundingSpendArgs<'_, T>, - lock_time: LocktimeSetting, n_time: NTimeSetting, + fee: FundingSpendFeeSetting, ) -> GenPreimageResInner { let payment_time_lock = args .taker_payment_time_lock @@ -1242,9 +1247,13 @@ async fn gen_taker_funding_spend_preimage( ); let funding_amount = args.funding_tx.first_output().unwrap().value; - let fee = coin - .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await?; + let fee = match fee { + FundingSpendFeeSetting::GetFromCoin => { + coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await? + }, + FundingSpendFeeSetting::UseExact(f) => f, + }; let fee_plus_dust = fee + coin.as_ref().dust_amount; if funding_amount < fee_plus_dust { @@ -1259,9 +1268,14 @@ async fn gen_taker_funding_spend_preimage( script_pubkey: Builder::build_p2sh(&AddressHashEnum::AddressHash(dhash160(&payment_redeem_script))).to_bytes(), }; - p2sh_spending_tx_preimage(coin, args.funding_tx, lock_time, n_time, SEQUENCE_FINAL, vec![ - payment_output, - ]) + p2sh_spending_tx_preimage( + coin, + args.funding_tx, + LocktimeSetting::UseExact(0), + n_time, + SEQUENCE_FINAL, + vec![payment_output], + ) .await .map_to_mm(TxGenError::Legacy) } @@ -1277,7 +1291,7 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; let preimage = - gen_taker_funding_spend_preimage(coin, args, LocktimeSetting::UseExact(0), NTimeSetting::UseNow).await?; + gen_taker_funding_spend_preimage(coin, args, NTimeSetting::UseNow, FundingSpendFeeSetting::GetFromCoin).await?; let redeem_script = swap_proto_v2_scripts::taker_funding_script( funding_time_lock, @@ -1307,11 +1321,45 @@ pub async fn validate_taker_funding_spend_preimage( gen_args: &GenTakerFundingSpendArgs<'_, T>, preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult { + let funding_amount = gen_args + .funding_tx + .first_output() + .map_to_mm(|_| ValidateTakerFundingSpendPreimageError::FundingTxNoOutputs)? + .value; + + let payment_amount = preimage + .preimage + .first_output() + .map_to_mm(|_| ValidateTakerFundingSpendPreimageError::InvalidPreimage("Preimage has no outputs".into()))? + .value; + + if payment_amount > funding_amount { + return MmError::err(ValidateTakerFundingSpendPreimageError::InvalidPreimage(format!( + "Preimage output {} larger than funding input {}", + payment_amount, funding_amount + ))); + } + + let expected_fee = coin + .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await?; + + let actual_fee = funding_amount - payment_amount; + + let fee_div = expected_fee as f64 / actual_fee as f64; + + if !(0.9..=1.1).contains(&fee_div) { + return MmError::err(ValidateTakerFundingSpendPreimageError::UnexpectedPreimageFee(format!( + "Too large difference between expected {} and actual {} fees", + expected_fee, actual_fee + ))); + } + let expected_preimage = gen_taker_funding_spend_preimage( coin, gen_args, - LocktimeSetting::UseExact(0), NTimeSetting::UseValue(preimage.preimage.n_time), + FundingSpendFeeSetting::UseExact(actual_fee), ) .await?; @@ -1432,7 +1480,6 @@ pub async fn sign_and_send_taker_funding_spend( async fn gen_taker_payment_spend_preimage( coin: &T, args: &GenTakerPaymentSpendArgs<'_, T>, - lock_time: LocktimeSetting, n_time: NTimeSetting, ) -> GenPreimageResInner { let dex_fee_sat = sat_from_big_decimal(&args.dex_fee_amount, coin.as_ref().decimals)?; @@ -1451,9 +1498,14 @@ async fn gen_taker_payment_spend_preimage( script_pubkey: Builder::build_p2pkh(&dex_fee_address.hash).to_bytes(), }; - p2sh_spending_tx_preimage(coin, args.taker_tx, lock_time, n_time, SEQUENCE_FINAL, vec![ - dex_fee_output, - ]) + p2sh_spending_tx_preimage( + coin, + args.taker_tx, + LocktimeSetting::UseExact(0), + n_time, + SEQUENCE_FINAL, + vec![dex_fee_output], + ) .await .map_to_mm(TxGenError::Legacy) } @@ -1468,8 +1520,7 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( .try_into() .map_to_mm(|e: TryFromIntError| TxGenError::LocktimeOverflow(e.to_string()))?; - let preimage = - gen_taker_payment_spend_preimage(coin, args, LocktimeSetting::UseExact(0), NTimeSetting::UseNow).await?; + let preimage = gen_taker_payment_spend_preimage(coin, args, NTimeSetting::UseNow).await?; let redeem_script = swap_proto_v2_scripts::taker_payment_script(time_lock, args.secret_hash, args.taker_pub, args.maker_pub); @@ -1497,13 +1548,8 @@ pub async fn validate_taker_payment_spend_preimage( ) -> ValidateTakerPaymentSpendPreimageResult { // 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_taker_payment_spend_preimage( - coin, - gen_args, - LocktimeSetting::UseExact(0), - NTimeSetting::UseValue(preimage.preimage.n_time), - ) - .await?; + let expected_preimage = + gen_taker_payment_spend_preimage(coin, gen_args, NTimeSetting::UseValue(preimage.preimage.n_time)).await?; let time_lock = gen_args .time_lock From 92990cb29eacc6b730ee5c90def92a2c5a04d96f Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 2 Oct 2023 18:17:28 +0700 Subject: [PATCH 25/30] Temporary disable upgraded swaps under WASM. --- .../eth/web3_transport/http_transport.rs | 3 +- mm2src/mm2_main/src/lp_ordermatch.rs | 148 ++++++++++-------- mm2src/mm2_main/src/lp_swap.rs | 24 ++- 3 files changed, 103 insertions(+), 72 deletions(-) diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index 02822ebe5d..83aca34741 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -3,7 +3,8 @@ use crate::eth::{web3_transport::Web3SendOut, EthCoin, GuiAuthMessages, RpcTrans use common::APPLICATION_JSON; use futures::lock::Mutex as AsyncMutex; use http::header::CONTENT_TYPE; -use jsonrpc_core::{Call, Id, Response}; +#[cfg(not(target_arch = "wasm32"))] use jsonrpc_core::Id; +use jsonrpc_core::{Call, Response}; use mm2_net::transport::{GuiAuthValidation, GuiAuthValidationGenerator}; use serde_json::Value as Json; #[cfg(not(target_arch = "wasm32"))] use std::ops::Deref; diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index f79e804cec..d4433346eb 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -71,15 +71,19 @@ use uuid::Uuid; use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest, P2PRequestError}; +#[cfg(not(target_arch = "wasm32"))] +use crate::mm2::lp_swap::detect_secret_hash_algo; +#[cfg(not(target_arch = "wasm32"))] use crate::mm2::lp_swap::maker_swap_v2::{self, DummyMakerSwapStorage, MakerSwapStateMachine}; +#[cfg(not(target_arch = "wasm32"))] 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, detect_secret_hash_algo, dex_fee_amount_from_taker_coin, - generate_secret, 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, SwapConfirmationsSettings, TakerSwap}; + check_other_coin_balance_for_swap, dex_fee_amount_from_taker_coin, generate_secret, + 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, SwapConfirmationsSettings, + TakerSwap}; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; @@ -2965,34 +2969,37 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO }; if ctx.use_trading_proto_v2() { - let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); - match (maker_coin, taker_coin) { - (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { - let mut maker_swap_state_machine = MakerSwapStateMachine { - storage: DummyMakerSwapStorage::new(ctx.clone()), - ctx, - 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, - 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"), + #[cfg(not(target_arch = "wasm32"))] + { + let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); + match (maker_coin, taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + let mut maker_swap_state_machine = MakerSwapStateMachine { + storage: DummyMakerSwapStorage::new(ctx.clone()), + ctx, + 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, + 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 { if let Err(e) = @@ -3102,41 +3109,44 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat let now = now_sec(); if ctx.use_trading_proto_v2() { - let taker_secret = match generate_secret() { - Ok(s) => s.into(), - Err(e) => { - error!("Error {} on secret generation", e); - return; - }, - }; - let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); - match (maker_coin, taker_coin) { - (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { - let mut taker_swap_state_machine = TakerSwapStateMachine { - storage: DummyTakerSwapStorage::new(ctx.clone()), - ctx, - started_at: now, - 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(), - secret_hash_algo, - conf_settings: my_conf_settings, - p2p_topic: swap_v2_topic(&uuid), - uuid, - p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), - taker_secret, - }; - #[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"), + #[cfg(not(target_arch = "wasm32"))] + { + let taker_secret = match generate_secret() { + Ok(s) => s.into(), + Err(e) => { + error!("Error {} on secret generation", e); + return; + }, + }; + let secret_hash_algo = detect_secret_hash_algo(&maker_coin, &taker_coin); + match (maker_coin, taker_coin) { + (MmCoinEnum::UtxoCoin(m), MmCoinEnum::UtxoCoin(t)) => { + let mut taker_swap_state_machine = TakerSwapStateMachine { + storage: DummyTakerSwapStorage::new(ctx.clone()), + ctx, + started_at: now, + 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(), + secret_hash_algo, + conf_settings: my_conf_settings, + p2p_topic: swap_v2_topic(&uuid), + uuid, + p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), + taker_secret, + }; + #[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 { if let Err(e) = diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 144c6f8037..c2ccf13d53 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -92,7 +92,9 @@ 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; +#[cfg(not(target_arch = "wasm32"))] +#[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; @@ -104,13 +106,16 @@ use std::sync::atomic::{AtomicU64, Ordering}; 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; +#[cfg(not(target_arch = "wasm32"))] +#[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")] #[path = "lp_swap/swap_wasm_db.rs"] mod swap_wasm_db; +#[cfg(not(target_arch = "wasm32"))] use crate::mm2::database::my_swaps::{get_swap_data_for_rpc, get_swap_type}; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use crypto::CryptoCtx; @@ -1017,6 +1022,7 @@ impl From for MySwapStatusResponse { } /// Returns the status of swap performed on `my` node +#[cfg(not(target_arch = "wasm32"))] pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); let uuid_str = uuid.to_string(); @@ -1044,6 +1050,20 @@ pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, } } +#[cfg(target_arch = "wasm32")] +pub async fn my_swap_status(ctx: MmArc, req: Json) -> Result>, String> { + let uuid: Uuid = try_s!(json::from_value(req["params"]["uuid"].clone())); + let status = match SavedSwap::load_my_swap_from_db(&ctx, uuid).await { + Ok(Some(status)) => status, + Ok(None) => return Err("swap data is not found".to_owned()), + Err(e) => return ERR!("{}", e), + }; + + let res_js = json!({ "result": MySwapStatusResponse::from(status) }); + let res = try_s!(json::to_vec(&res_js)); + Ok(try_s!(Response::builder().body(res))) +} + #[cfg(target_arch = "wasm32")] pub async fn stats_swap_status(_ctx: MmArc, _req: Json) -> Result>, String> { ERR!("'stats_swap_status' is only supported in native mode") From c18a597a398c401eb3cce55c7711f8c5f5a8d101 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Wed, 4 Oct 2023 10:04:55 +0700 Subject: [PATCH 26/30] Fix imports after merging with dev. --- mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0c113baeae..f57b60e2e3 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -11,7 +11,7 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin 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, get_payment_locktime, MakerSwap, +use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, generate_secret, get_payment_locktime, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; From c3c527cefb275501d4bcd89ab7c5291d6111fb38 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Wed, 4 Oct 2023 10:28:44 +0700 Subject: [PATCH 27/30] Review fixes. --- mm2src/coins/lp_coins.rs | 4 +++- mm2src/coins/utxo.rs | 2 +- mm2src/coins/utxo/utxo_common.rs | 13 +++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 46351ee726..4b81498922 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1163,7 +1163,7 @@ pub struct TxPreimageWithSig { pub signature: Coin::Sig, } -/// Enum covering error cases that can happen during taker payment spend preimage generation. +/// Enum covering error cases that can happen during transaction preimage generation. #[derive(Debug, Display)] pub enum TxGenError { /// RPC error @@ -1180,6 +1180,8 @@ pub enum TxGenError { LocktimeOverflow(String), /// Transaction fee is too high TxFeeTooHigh(String), + /// Previous tx is not valid + PrevTxIsNotValid(String), } impl From for TxGenError { diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index f48c2b56c2..19a9d9cd7d 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -1015,7 +1015,7 @@ pub trait UtxoCommonOps: } impl ToBytes for UtxoTx { - fn to_bytes(&self) -> Vec { serialize(self).take() } + fn to_bytes(&self) -> Vec { self.tx_hex() } } impl ToBytes for Signature { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index c0dc2b7c3b..4b78e97b60 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1246,7 +1246,12 @@ async fn gen_taker_funding_spend_preimage( args.maker_pub, ); - let funding_amount = args.funding_tx.first_output().unwrap().value; + let funding_amount = args + .funding_tx + .first_output() + .map_to_mm(|_| TxGenError::PrevTxIsNotValid("Funding tx has no outputs".into()))? + .value; + let fee = match fee { FundingSpendFeeSetting::GetFromCoin => { coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1257,9 +1262,9 @@ async fn gen_taker_funding_spend_preimage( let fee_plus_dust = fee + coin.as_ref().dust_amount; if funding_amount < fee_plus_dust { - return MmError::err(TxGenError::Legacy(format!( - "Funding amount {} is less than fee + dust {}", - funding_amount, fee_plus_dust + return MmError::err(TxGenError::TxFeeTooHigh(format!( + "Fee + dust {} is larger than funding amount {}", + fee_plus_dust, funding_amount ))); } From 7697d24cb90410d7a13632c3c531de5af36dc9fc Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Thu, 5 Oct 2023 12:40:13 +0700 Subject: [PATCH 28/30] Review fixes: use named_params macro. --- mm2src/mm2_main/src/database/my_swaps.rs | 38 +++++++++--------- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 42 ++++++++++---------- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 42 ++++++++++---------- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index d613645d73..4b852db835 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -81,27 +81,27 @@ const INSERT_MY_SWAP_V2: &str = r#"INSERT INTO my_swaps ( taker_coin_confs, taker_coin_nota ) VALUES ( - ?1, - ?2, - ?3, - ?4, - ?5, - ?6, - ?7, - ?8, - ?9, - ?10, - ?11, - ?12, - ?13, - ?14, - ?15, - ?16, - ?17, - ?18 + :my_coin, + :other_coin, + :uuid, + :started_at, + :swap_type, + :maker_volume, + :taker_volume, + :premium, + :dex_fee, + :secret, + :secret_hash, + :secret_hash_algo, + :p2p_privkey, + :lock_duration, + :maker_coin_confs, + :maker_coin_nota, + :taker_coin_confs, + :taker_coin_nota );"#; -pub fn insert_new_swap_v2(ctx: &MmArc, params: &[&dyn ToSql]) -> SqlResult<()> { +pub fn insert_new_swap_v2(ctx: &MmArc, params: &[(&str, &dyn ToSql)]) -> SqlResult<()> { let conn = ctx.sqlite_connection(); conn.execute(INSERT_MY_SWAP_V2, params).map(|_| ()) } diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 0a87681333..79702bee83 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -12,7 +12,7 @@ use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFunding ValidateTakerFundingArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; -use db_common::sqlite::rusqlite::params; +use db_common::sqlite::rusqlite::named_params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -251,26 +251,26 @@ impl State fo async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { { - let sql_params = params![ - state_machine.maker_coin.ticker(), - state_machine.taker_coin.ticker(), - state_machine.uuid.to_string(), - state_machine.started_at, - MAKER_SWAP_V2_TYPE, - state_machine.maker_volume.to_fraction_string(), - state_machine.taker_volume.to_fraction_string(), - state_machine.taker_premium.to_fraction_string(), - state_machine.dex_fee_amount.to_fraction_string(), - state_machine.secret.take(), - state_machine.secret_hash(), - state_machine.secret_hash_algo as u8, - state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), - state_machine.lock_duration, - state_machine.conf_settings.maker_coin_confs, - state_machine.conf_settings.maker_coin_nota, - state_machine.conf_settings.taker_coin_confs, - state_machine.conf_settings.taker_coin_nota - ]; + let sql_params = named_params! { + ":my_coin": state_machine.maker_coin.ticker(), + ":other_coin": state_machine.taker_coin.ticker(), + ":uuid": state_machine.uuid.to_string(), + ":started_at": state_machine.started_at, + ":swap_type": MAKER_SWAP_V2_TYPE, + ":maker_volume": state_machine.maker_volume.to_fraction_string(), + ":taker_volume": state_machine.taker_volume.to_fraction_string(), + ":premium": state_machine.taker_premium.to_fraction_string(), + ":dex_fee": state_machine.dex_fee_amount.to_fraction_string(), + ":secret": state_machine.secret.take(), + ":secret_hash": state_machine.secret_hash(), + ":secret_hash_algo": state_machine.secret_hash_algo as u8, + ":p2p_privkey": state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), + ":lock_duration": state_machine.lock_duration, + ":maker_coin_confs": state_machine.conf_settings.maker_coin_confs, + ":maker_coin_nota": state_machine.conf_settings.maker_coin_nota, + ":taker_coin_confs": state_machine.conf_settings.taker_coin_confs, + ":taker_coin_nota": state_machine.conf_settings.taker_coin_nota + }; insert_new_swap_v2(&state_machine.ctx, sql_params).unwrap(); } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 6626a239f4..0e825d5e9c 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -12,7 +12,7 @@ use coins::{CoinAssocTypes, ConfirmPaymentInput, FeeApproxStage, GenTakerFunding TxPreimageWithSig, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::{debug, info, warn}; use common::{bits256, Future01CompatExt, DEX_FEE_ADDR_RAW_PUBKEY}; -use db_common::sqlite::rusqlite::params; +use db_common::sqlite::rusqlite::named_params; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; @@ -263,26 +263,26 @@ impl, state_machine: &mut Self::StateMachine) -> StateResult { { - let sql_params = params![ - state_machine.taker_coin.ticker(), - state_machine.maker_coin.ticker(), - state_machine.uuid.to_string(), - state_machine.started_at, - TAKER_SWAP_V2_TYPE, - state_machine.maker_volume.to_fraction_string(), - state_machine.taker_volume.to_fraction_string(), - state_machine.taker_premium.to_fraction_string(), - state_machine.dex_fee.to_fraction_string(), - state_machine.taker_secret.take(), - state_machine.taker_secret_hash(), - state_machine.secret_hash_algo as u8, - state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), - state_machine.lock_duration, - state_machine.conf_settings.maker_coin_confs, - state_machine.conf_settings.maker_coin_nota, - state_machine.conf_settings.taker_coin_confs, - state_machine.conf_settings.taker_coin_nota - ]; + let sql_params = named_params! { + ":my_coin": state_machine.taker_coin.ticker(), + ":other_coin": state_machine.maker_coin.ticker(), + ":uuid": state_machine.uuid.to_string(), + ":started_at": state_machine.started_at, + ":swap_type": TAKER_SWAP_V2_TYPE, + ":maker_volume": state_machine.maker_volume.to_fraction_string(), + ":taker_volume": state_machine.taker_volume.to_fraction_string(), + ":premium": state_machine.taker_premium.to_fraction_string(), + ":dex_fee": state_machine.dex_fee.to_fraction_string(), + ":secret": state_machine.taker_secret.take(), + ":secret_hash": state_machine.taker_secret_hash(), + ":secret_hash_algo": state_machine.secret_hash_algo as u8, + ":p2p_privkey": state_machine.p2p_keypair.map(|k| k.private_bytes()).unwrap_or_default(), + ":lock_duration": state_machine.lock_duration, + ":maker_coin_confs": state_machine.conf_settings.maker_coin_confs, + ":maker_coin_nota": state_machine.conf_settings.maker_coin_nota, + ":taker_coin_confs": state_machine.conf_settings.taker_coin_confs, + ":taker_coin_nota": state_machine.conf_settings.taker_coin_nota + }; insert_new_swap_v2(&state_machine.ctx, sql_params).unwrap(); } From f197ffb0090e0418c9d8944c95254415d435a34b Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Fri, 6 Oct 2023 09:57:07 +0700 Subject: [PATCH 29/30] Wait for taker payment conf instead of funding. --- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 79702bee83..097e71d9c3 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -640,7 +640,6 @@ impl State maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data, - taker_funding: self.taker_funding, funding_spend_preimage, maker_payment: TransactionIdentifier { tx_hex: maker_payment.tx_hex().into(), @@ -674,7 +673,6 @@ struct MakerPaymentSentFundingSpendGenerated, - taker_funding: TakerCoin::Tx, funding_spend_preimage: TxPreimageWithSig, maker_payment: TransactionIdentifier, } @@ -746,7 +744,7 @@ impl State }; let input = ConfirmPaymentInput { - payment_tx: self.taker_funding.tx_hex(), + payment_tx: taker_payment.tx_hex(), 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(), From 9de1ea29ffca580910fa4ea7bf16194c3577f5c2 Mon Sep 17 00:00:00 2001 From: Artem Vitae Date: Mon, 30 Oct 2023 11:30:37 +0700 Subject: [PATCH 30/30] Fixes after syncing with dev. --- mm2src/coins/test_coin.rs | 17 +++++++++-------- mm2src/coins/utxo/utxo_common.rs | 10 +++++----- mm2src/coins/utxo/utxo_standard.rs | 6 +++--- mm2src/mm2_main/src/lp_swap.rs | 2 +- .../tests/docker_tests/swap_watcher_tests.rs | 12 ++++++------ 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 3ca4364b49..d21b438f58 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -2,20 +2,21 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; -use crate::ValidateWatcherSpendInput; -use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, - 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, +use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinAssocTypes, + CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerSwapTakerCoin, MmCoinEnum, + NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignatureResult, + SpendPaymentArgs, SwapOpsV2, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; +use crate::{ToBytes, ValidateWatcherSpendInput}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 6958af6c24..82e33e4b36 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -24,11 +24,11 @@ use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPayment ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerFundingArgs, ValidateTakerFundingError, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageError, ValidateTakerFundingSpendPreimageResult, - 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}; + ValidateTakerPaymentSpendPreimageError, ValidateTakerPaymentSpendPreimageResult, + ValidateWatcherSpendInput, 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}; diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 22f39917e0..26bc6bdc88 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -32,9 +32,9 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva TxPreimageWithSig, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateTakerFundingArgs, ValidateTakerFundingResult, ValidateTakerFundingSpendPreimageResult, - ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; + ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; use futures::{FutureExt, TryFutureExt}; diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 985d342fc9..5cacc9458a 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -107,7 +107,7 @@ mod swap_v2_pb; #[path = "lp_swap/swap_watcher.rs"] pub(crate) mod swap_watcher; #[path = "lp_swap/taker_restart.rs"] pub(crate) mod taker_restart; -#[path = "lp_swap/taker_swap.rs"] mod taker_swap; +#[path = "lp_swap/taker_swap.rs"] pub(crate) mod taker_swap; #[cfg(not(target_arch = "wasm32"))] #[path = "lp_swap/taker_swap_v2.rs"] pub mod taker_swap_v2; 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 400676c6c2..4f3a10593f 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -2156,7 +2156,7 @@ fn test_taker_validates_taker_payment_refund_utxo() { let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); let maker_pubkey = maker_coin.my_public_key().unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_payment = taker_coin .send_taker_payment(SendPaymentArgs { @@ -2243,7 +2243,7 @@ fn test_taker_validates_taker_payment_refund_eth() { let time_lock = now_sec() - 10; let taker_amount = BigDecimal::from_str("0.001").unwrap(); let maker_amount = BigDecimal::from_str("0.001").unwrap(); - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let watcher_reward = block_on(taker_coin.get_taker_watcher_reward( &MmCoinEnum::from(taker_coin.clone()), @@ -2562,7 +2562,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { let wait_for_confirmation_until = wait_until_sec(time_lock_duration); let time_lock = now_sec() - 10; - let secret_hash = dhash160(&MakerSwap::generate_secret().unwrap()); + let secret_hash = dhash160(&generate_secret().unwrap()); let taker_amount = BigDecimal::from_str("0.001").unwrap(); let maker_amount = BigDecimal::from_str("0.001").unwrap(); @@ -2684,7 +2684,7 @@ fn test_taker_validates_maker_payment_spend_utxo() { let taker_pubkey = taker_coin.my_public_key().unwrap(); let maker_pubkey = maker_coin.my_public_key().unwrap(); - let secret = MakerSwap::generate_secret().unwrap(); + let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); let maker_payment = maker_coin @@ -2771,7 +2771,7 @@ fn test_taker_validates_maker_payment_spend_eth() { let time_lock = wait_for_confirmation_until; let maker_amount = BigDecimal::from_str("0.001").unwrap(); - let secret = MakerSwap::generate_secret().unwrap(); + let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); let watcher_reward = block_on(maker_coin.get_maker_watcher_reward( @@ -3092,7 +3092,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { let time_lock = wait_for_confirmation_until; let maker_amount = BigDecimal::from_str("0.001").unwrap(); - let secret = MakerSwap::generate_secret().unwrap(); + let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); let watcher_reward = block_on(maker_coin.get_maker_watcher_reward(