From 761e3a9bb86dd1380b01a1e2ffdf8c97d43dd2b9 Mon Sep 17 00:00:00 2001 From: chris-belcher Date: Sat, 25 Jun 2022 15:16:41 +0100 Subject: [PATCH] Add alternative function for creating funding txes Based on my dogfooding which showed that sometimes the code is unable to properly choose UTXOs to create funding transactions. So a fallback is needed and maybe more than one. This commit renames the functions create_spending_txes -> create_funding_txes This commit also moves many of those routines to a new file funding_tx.rs as wallet_sync.rs was getting pretty big, and going forward the creation of funding transactions will have to be very careful in order to get the best privacy. --- src/funding_tx.rs | 375 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/wallet_sync.rs | 307 +++++++++++-------------------------- 3 files changed, 464 insertions(+), 219 deletions(-) create mode 100644 src/funding_tx.rs diff --git a/src/funding_tx.rs b/src/funding_tx.rs new file mode 100644 index 00000000..1347ad41 --- /dev/null +++ b/src/funding_tx.rs @@ -0,0 +1,375 @@ +//this file contains routines for creating funding transactions + +use std::collections::HashMap; + +use itertools::izip; + +use bitcoin::{hashes::hex::FromHex, Address, Amount, OutPoint, Transaction, Txid}; + +use bitcoincore_rpc::json::{CreateRawTransactionInput, WalletCreateFundedPsbtOptions}; +use bitcoincore_rpc::{Client, RpcApi}; + +use serde_json::Value; + +use rand::rngs::OsRng; +use rand::RngCore; + +use crate::error::Error; +use crate::wallet_sync::{convert_json_rpc_bitcoin_to_satoshis, Wallet}; + +pub struct CreateFundingTxesResult { + pub funding_txes: Vec, + pub payment_output_positions: Vec, + pub total_miner_fee: u64, +} + +impl Wallet { + pub fn create_funding_txes( + &self, + rpc: &Client, + coinswap_amount: u64, + destinations: &[Address], + fee_rate: u64, + ) -> Result { + let ret = + self.create_funding_txes_random_amounts(rpc, coinswap_amount, destinations, fee_rate); + if ret.is_ok() { + log::debug!(target: "wallet", "created funding txes with random amounts"); + return ret; + } + + let ret = + self.create_funding_txes_utxo_max_sends(rpc, coinswap_amount, destinations, fee_rate); + if ret.is_ok() { + log::debug!(target: "wallet", "created funding txes with fully-spending utxos"); + return ret; + } + + ret + } + + fn generate_amount_fractions( + count: usize, + total_amount: u64, + lower_limit: u64, + ) -> Result, Error> { + for _ in 0..100000 { + let mut knives = (1..count) + .map(|_| OsRng.next_u32() as f32 / u32::MAX as f32) + .collect::>(); + knives.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); + + let mut fractions = Vec::::new(); + let mut last: f32 = 1.0; + for k in knives { + fractions.push(last - k); + last = k; + } + fractions.push(last); + + if fractions + .iter() + .all(|f| *f * (total_amount as f32) > lower_limit as f32) + { + return Ok(fractions); + } + } + Err(Error::Protocol( + "unable to generate amount fractions, probably amount too small", + )) + } + + fn create_funding_txes_random_amounts( + &self, + rpc: &Client, + coinswap_amount: u64, + destinations: &[Address], + fee_rate: u64, + ) -> Result { + log::debug!(target: "wallet", "coinswap_amount = {} destinations = {:?}", + coinswap_amount, destinations); + + //TODO needs perhaps better way to create multiple txes for + //multi-tx-coinswap could try multiple ways, and in combination + //* come up with your own algorithm that sums up UTXOs + // would lose bitcoin core's cool utxo choosing algorithm though + // until their total value is >desired_amount + //* use listunspent with minimumSumAmount + //* pick individual utxos for no-change txes, and for the last one + // use walletcreatefundedpsbt which will create change + + //* randomly generate some satoshi amounts and send them into + // walletcreatefundedpsbt to create funding txes that create change + //this is the solution used right now + + let change_addresses = self.get_next_internal_addresses(rpc, destinations.len() as u32)?; + log::debug!(target: "wallet", "change addrs = {:?}", change_addresses); + + let mut output_values = Wallet::generate_amount_fractions( + destinations.len(), + coinswap_amount, + 5000, //use 5000 satoshi as the lower limit for now + //there should always be enough to pay miner fees + )? + .iter() + .map(|f| (*f * coinswap_amount as f32) as u64) + .collect::>(); + + //rounding errors mean usually 1 or 2 satoshis are lost, add them back + + //this calculation works like this: + //o = [a, b, c, ...] | list of output values + //t = coinswap amount | total desired value + //a' <-- a + (t - (a+b+c+...)) | assign new first output value + //a' <-- a + (t -a-b-c-...) | rearrange + //a' <-- t - b - c -... | + *output_values.first_mut().unwrap() = + coinswap_amount - output_values.iter().skip(1).sum::(); + assert_eq!(output_values.iter().sum::(), coinswap_amount); + log::debug!(target: "wallet", "output values = {:?}", output_values); + + self.lock_all_nonwallet_unspents(rpc)?; + + let mut funding_txes = Vec::::new(); + let mut payment_output_positions = Vec::::new(); + let mut total_miner_fee = 0; + for (address, &output_value, change_address) in izip!( + destinations.iter(), + output_values.iter(), + change_addresses.iter() + ) { + log::debug!(target: "wallet", "output_value = {} to addr={}", output_value, address); + + let mut outputs = HashMap::::new(); + outputs.insert(address.to_string(), Amount::from_sat(output_value)); + + let wcfp_result = rpc.wallet_create_funded_psbt( + &[], + &outputs, + None, + Some(WalletCreateFundedPsbtOptions { + include_watching: Some(true), + change_address: Some(change_address.clone()), + fee_rate: Some(Amount::from_sat(fee_rate)), + ..Default::default() + }), + None, + )?; + total_miner_fee += wcfp_result.fee.as_sat(); + log::debug!(target: "wallet", "created funding tx, miner fee={}", wcfp_result.fee); + + let funding_tx = self.from_walletcreatefundedpsbt_to_tx(rpc, &wcfp_result.psbt)?; + + rpc.lock_unspent( + &funding_tx + .input + .iter() + .map(|vin| vin.previous_output) + .collect::>(), + )?; + + let payment_pos = if wcfp_result.change_position == 0 { + 1 + } else { + 0 + }; + log::debug!(target: "wallet", "payment_pos = {}", payment_pos); + + funding_txes.push(funding_tx); + payment_output_positions.push(payment_pos); + } + + Ok(CreateFundingTxesResult { + funding_txes, + payment_output_positions, + total_miner_fee, + }) + } + + fn create_funding_txes_utxo_max_sends( + &self, + rpc: &Client, + coinswap_amount: u64, + destinations: &[Address], + fee_rate: u64, + ) -> Result { + //this function creates txes by + //using walletcreatefundedpsbt for the total amount, and if + //the number if inputs UTXOs is >number_of_txes then split those inputs into groups + //across multiple transactions + + let mut outputs = HashMap::::new(); + outputs.insert( + destinations[0].to_string(), + Amount::from_sat(coinswap_amount), + ); + let change_address = self.get_next_internal_addresses(rpc, 1)?[0].clone(); + + self.lock_all_nonwallet_unspents(rpc)?; + let wcfp_result = rpc.wallet_create_funded_psbt( + &[], + &outputs, + None, + Some(WalletCreateFundedPsbtOptions { + include_watching: Some(true), + change_address: Some(change_address.clone()), + fee_rate: Some(Amount::from_sat(fee_rate)), + ..Default::default() + }), + None, + )?; + //TODO rust-bitcoin handles psbt, use those functions instead + let decoded_psbt = rpc.call::("decodepsbt", &[Value::String(wcfp_result.psbt)])?; + log::debug!(target: "wallet", "total tx decoded_psbt = {:?}", decoded_psbt); + + let total_tx_inputs_len = decoded_psbt["inputs"].as_array().unwrap().len(); + log::debug!(target: "wallet", "total tx inputs.len = {}", total_tx_inputs_len); + if total_tx_inputs_len < destinations.len() { + //not enough UTXOs found, cant use this method + return Err(Error::Protocol( + "not enough UTXOs found, cant use this method", + )); + } + + let mut total_tx_inputs = decoded_psbt["tx"]["vin"] + .as_array() + .unwrap() + .iter() + .zip(decoded_psbt["inputs"].as_array().unwrap().iter()) + .collect::>(); + + total_tx_inputs.sort_by(|(_, a), (_, b)| { + b["witness_utxo"]["amount"] + .as_f64() + .unwrap() + .partial_cmp(&a["witness_utxo"]["amount"].as_f64().unwrap()) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + let mut total_tx_inputs_iter = total_tx_inputs.iter(); + + let first_tx_input = total_tx_inputs_iter.next().unwrap(); + + let mut destinations_iter = destinations.iter(); + + let mut funding_txes = Vec::::new(); + let mut payment_output_positions = Vec::::new(); + let mut total_miner_fee = 0; + + let mut leftover_coinswap_amount = coinswap_amount; + + for _ in 0..(destinations.len() - 2) { + let (vin, input_info) = total_tx_inputs_iter.next().unwrap(); + + let mut outputs = HashMap::::new(); + outputs.insert( + destinations_iter.next().unwrap().to_string(), + Amount::from_sat(convert_json_rpc_bitcoin_to_satoshis( + &input_info["witness_utxo"]["amount"], + )), + ); + let wcfp_result = rpc.wallet_create_funded_psbt( + &[CreateRawTransactionInput { + txid: Txid::from_hex(vin["txid"].as_str().unwrap()).unwrap(), + vout: vin["vout"].as_u64().unwrap() as u32, + sequence: None, + }], + &outputs, + None, + Some(WalletCreateFundedPsbtOptions { + add_inputs: Some(false), + subtract_fee_from_outputs: vec![0], + fee_rate: Some(Amount::from_sat(fee_rate)), + ..Default::default() + }), + None, + )?; + let funding_tx = self.from_walletcreatefundedpsbt_to_tx(rpc, &wcfp_result.psbt)?; + leftover_coinswap_amount -= funding_tx.output[0].value; + + total_miner_fee += wcfp_result.fee.as_sat(); + log::debug!(target: "wallet", "created funding tx, miner fee={}", wcfp_result.fee); + + funding_txes.push(funding_tx); + payment_output_positions.push(0); + } + + let (leftover_inputs, leftover_inputs_values): (Vec<_>, Vec<_>) = total_tx_inputs_iter + .map(|(vin, input_info)| { + ( + CreateRawTransactionInput { + txid: Txid::from_hex(vin["txid"].as_str().unwrap()).unwrap(), + vout: vin["vout"].as_u64().unwrap() as u32, + sequence: None, + }, + convert_json_rpc_bitcoin_to_satoshis(&input_info["witness_utxo"]["amount"]), + ) + }) + .unzip(); + let mut outputs = HashMap::::new(); + outputs.insert( + destinations_iter.next().unwrap().to_string(), + Amount::from_sat(leftover_inputs_values.iter().sum::()), + ); + let wcfp_result = rpc.wallet_create_funded_psbt( + &leftover_inputs, + &outputs, + None, + Some(WalletCreateFundedPsbtOptions { + add_inputs: Some(false), + subtract_fee_from_outputs: vec![0], + fee_rate: Some(Amount::from_sat(fee_rate)), + ..Default::default() + }), + None, + )?; + let funding_tx = self.from_walletcreatefundedpsbt_to_tx(rpc, &wcfp_result.psbt)?; + leftover_coinswap_amount -= funding_tx.output[0].value; + + total_miner_fee += wcfp_result.fee.as_sat(); + log::debug!(target: "wallet", "created funding tx, miner fee={}", wcfp_result.fee); + + funding_txes.push(funding_tx); + payment_output_positions.push(0); + + let (first_vin, _first_input_info) = first_tx_input; + let mut outputs = HashMap::::new(); + outputs.insert( + destinations_iter.next().unwrap().to_string(), + Amount::from_sat(leftover_coinswap_amount), + ); + let wcfp_result = rpc.wallet_create_funded_psbt( + &[CreateRawTransactionInput { + txid: Txid::from_hex(first_vin["txid"].as_str().unwrap()).unwrap(), + vout: first_vin["vout"].as_u64().unwrap() as u32, + sequence: None, + }], + &outputs, + None, + Some(WalletCreateFundedPsbtOptions { + add_inputs: Some(false), + change_address: Some(change_address.clone()), + fee_rate: Some(Amount::from_sat(fee_rate)), + ..Default::default() + }), + None, + )?; + let funding_tx = self.from_walletcreatefundedpsbt_to_tx(rpc, &wcfp_result.psbt)?; + + total_miner_fee += wcfp_result.fee.as_sat(); + log::debug!(target: "wallet", "created funding tx, miner fee={}", wcfp_result.fee); + + funding_txes.push(funding_tx); + payment_output_positions.push(if wcfp_result.change_position == 0 { + 1 + } else { + 0 + }); + + Ok(CreateFundingTxesResult { + funding_txes, + payment_output_positions, + total_miner_fee, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 21fe56d7..5899a692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ use fidelity_bonds::{get_locktime_from_index, YearAndMonth}; pub mod directory_servers; pub mod error; +pub mod funding_tx; pub mod messages; pub mod watchtower_client; pub mod watchtower_protocol; diff --git a/src/wallet_sync.rs b/src/wallet_sync.rs index dde49d22..04b7ee8f 100644 --- a/src/wallet_sync.rs +++ b/src/wallet_sync.rs @@ -38,7 +38,7 @@ use bitcoin::{ use bitcoincore_rpc::json::{ ImportMultiOptions, ImportMultiRequest, ImportMultiRequestScriptPubkey, ImportMultiRescanSince, - ListUnspentResultEntry, WalletCreateFundedPsbtOptions, + ListUnspentResultEntry, }; use bitcoincore_rpc::{Client, RpcApi}; @@ -1430,7 +1430,7 @@ impl Wallet { pub fn sign_transaction( &self, - spending_tx: &mut Transaction, + tx: &mut Transaction, inputs_info: &mut dyn Iterator, ) { let secp = Secp256k1::new(); @@ -1438,11 +1438,9 @@ impl Wallet { .master_key .derive_priv(&secp, &DerivationPath::from_str(DERIVATION_PATH).unwrap()) .unwrap(); - let tx_clone = spending_tx.clone(); + let tx_clone = tx.clone(); - for (ix, (mut input, input_info)) in - spending_tx.input.iter_mut().zip(inputs_info).enumerate() - { + for (ix, (mut input, input_info)) in tx.input.iter_mut().zip(inputs_info).enumerate() { log::debug!(target: "wallet", "signing with input_info = {:?}", input_info); match input_info { UTXOSpendInfo::SwapCoin { @@ -1511,215 +1509,83 @@ impl Wallet { } } - fn generate_amount_fractions( - count: usize, - total_amount: u64, - lower_limit: u64, - ) -> Result, Error> { - for _ in 0..100000 { - let mut knives = (1..count) - .map(|_| OsRng.next_u32() as f32 / u32::MAX as f32) - .collect::>(); - knives.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal)); - - let mut fractions = Vec::::new(); - let mut last: f32 = 1.0; - for k in knives { - fractions.push(last - k); - last = k; - } - fractions.push(last); - - if fractions - .iter() - .all(|f| *f * (total_amount as f32) > lower_limit as f32) - { - return Ok(fractions); - } - } - Err(Error::Protocol( - "unable to generate amount fractions, probably amount too small", - )) - } - - fn create_spending_txes( + pub fn from_walletcreatefundedpsbt_to_tx( &self, rpc: &Client, - coinswap_amount: u64, - destinations: &[Address], - fee_rate: u64, - ) -> Result<(Vec, Vec, Vec, u64), Error> { - //return funding_txes, position_of_output, output_value, total_miner_fee - - log::debug!(target: "wallet", "coinswap_amount = {} destinations = {:?}", coinswap_amount, destinations); - - //TODO needs perhaps better way to create multiple txes for - //multi-tx-coinswap could try multiple ways, and in combination - //* use walletcreatefundedpsbt for the total amount, and if - // the number if inputs UTXOs is >number_of_txes then you're done - //* come up with your own algorithm that sums up UTXOs - // would lose bitcoin core's cool utxo choosing algorithm though - // until their total value is >desired_amount - //* use listunspent with minimumSumAmount - //* pick individual utxos for no-change txes, and for the last one - // use walletcreatefundedpsbt which will create change - - //* randomly generate some satoshi amounts and send them into - // walletcreatefundedpsbt to create funding txes that create change - //this is the solution used right now - - let change_addresses = self.get_next_internal_addresses(rpc, destinations.len() as u32)?; - log::debug!(target: "wallet", "change addrs = {:?}", change_addresses); - - self.lock_all_nonwallet_unspents(rpc)?; - let mut output_values = Wallet::generate_amount_fractions( - destinations.len(), - coinswap_amount, - 5000, //use 5000 satoshi as the lower limit for now - //there should always be enough to pay miner fees - )? - .iter() - .map(|f| (*f * coinswap_amount as f32) as u64) - .collect::>(); - - //rounding errors mean usually 1 or 2 satoshis are lost, add them back - - //this calculation works like this: - //o = [a, b, c, ...] | list of output values - //t = coinswap amount | total desired value - //a' <-- a + (t - (a+b+c+...)) | assign new first output value - //a' <-- a + (t -a-b-c-...) | rearrange - //a' <-- t - b - c -... | - *output_values.first_mut().unwrap() = - coinswap_amount - output_values.iter().skip(1).sum::(); - assert_eq!(output_values.iter().sum::(), coinswap_amount); - log::debug!(target: "wallet", "output values = {:?}", output_values); - - let mut spending_txes = Vec::::new(); - let mut payment_output_positions = Vec::::new(); - let mut total_miner_fee = 0; - for (address, &output_value, change_address) in izip!( - destinations.iter(), - output_values.iter(), - change_addresses.iter() - ) { - log::debug!(target: "wallet", "output_value = {} to addr={}", output_value, address); - - let mut outputs = HashMap::::new(); - outputs.insert(address.to_string(), Amount::from_sat(output_value)); - - let psbt_result = rpc.wallet_create_funded_psbt( - &[], - &outputs, - None, - Some(WalletCreateFundedPsbtOptions { - include_watching: Some(true), - change_address: Some(change_address.clone()), - fee_rate: Some(Amount::from_sat(fee_rate)), - ..Default::default() - }), - None, - )?; - let decoded_psbt = - rpc.call::("decodepsbt", &[Value::String(psbt_result.psbt)])?; - log::debug!(target: "wallet", "decoded_psbt = {:?}", decoded_psbt); - - total_miner_fee += psbt_result.fee.as_sat(); - log::debug!(target: "wallet", "created spending tx, miner fee={}", psbt_result.fee); - - //TODO proper error handling, theres many unwrap()s here - //make this function return Result<> - let inputs = decoded_psbt["tx"]["vin"] - .as_array() - .unwrap() - .iter() - .map(|vin| TxIn { - previous_output: OutPoint { - txid: Txid::from_hex(vin["txid"].as_str().unwrap()).unwrap(), - vout: vin["vout"].as_u64().unwrap() as u32, - }, - sequence: 0, - witness: Vec::new(), - script_sig: Script::new(), - }) - .collect::>(); - rpc.lock_unspent( - &inputs - .iter() - .map(|vin| vin.previous_output) - .collect::>(), - )?; - let outputs = decoded_psbt["tx"]["vout"] - .as_array() - .unwrap() - .iter() - .map(|vout| TxOut { - script_pubkey: Builder::from( - Vec::from_hex(vout["scriptPubKey"]["hex"].as_str().unwrap()).unwrap(), - ) - .into_script(), - value: convert_json_rpc_bitcoin_to_satoshis(&vout["value"]), - }) - .collect::>(); + psbt: &String, + ) -> Result { + //TODO rust-bitcoin handles psbt, use those functions instead + let decoded_psbt = rpc.call::("decodepsbt", &[Value::String(psbt.to_string())])?; + log::debug!(target: "wallet", "decoded_psbt = {:?}", decoded_psbt); + + //TODO proper error handling, theres many unwrap()s here + //make this function return Result<> + let inputs = decoded_psbt["tx"]["vin"] + .as_array() + .unwrap() + .iter() + .map(|vin| TxIn { + previous_output: OutPoint { + txid: Txid::from_hex(vin["txid"].as_str().unwrap()).unwrap(), + vout: vin["vout"].as_u64().unwrap() as u32, + }, + sequence: 0, + witness: Vec::new(), + script_sig: Script::new(), + }) + .collect::>(); + let outputs = decoded_psbt["tx"]["vout"] + .as_array() + .unwrap() + .iter() + .map(|vout| TxOut { + script_pubkey: Builder::from( + Vec::from_hex(vout["scriptPubKey"]["hex"].as_str().unwrap()).unwrap(), + ) + .into_script(), + value: convert_json_rpc_bitcoin_to_satoshis(&vout["value"]), + }) + .collect::>(); - let mut spending_tx = Transaction { - input: inputs, - output: outputs, - lock_time: 0, - version: 2, - }; - log::debug!(target: "wallet", "spending_tx = {:?}", spending_tx); + let mut tx = Transaction { + input: inputs, + output: outputs, + lock_time: 0, + version: 2, + }; + log::debug!(target: "wallet", "tx = {:?}", tx); - let mut inputs_info = decoded_psbt["inputs"] - .as_array() - .unwrap() - .iter() - .map(|input_info| (input_info, input_info["bip32_derivs"].as_array().unwrap())) - .map(|(input_info, bip32_info)| { - if bip32_info.len() == 2 { - UTXOSpendInfo::SwapCoin { - multisig_redeemscript: Builder::from( - Vec::from_hex( - &input_info["witness_script"]["hex"].as_str().unwrap(), - ) + let mut inputs_info = decoded_psbt["inputs"] + .as_array() + .unwrap() + .iter() + .map(|input_info| (input_info, input_info["bip32_derivs"].as_array().unwrap())) + .map(|(input_info, bip32_info)| { + if bip32_info.len() == 2 { + UTXOSpendInfo::SwapCoin { + multisig_redeemscript: Builder::from( + Vec::from_hex(&input_info["witness_script"]["hex"].as_str().unwrap()) .unwrap(), - ) - .into_script(), - } - } else { - UTXOSpendInfo::SeedCoin { - path: bip32_info[0]["path"].as_str().unwrap().to_string(), - input_value: convert_json_rpc_bitcoin_to_satoshis( - &input_info["witness_utxo"]["amount"], - ), - } + ) + .into_script(), } - }); - log::debug!(target: "wallet", "inputs_info = {:?}", inputs_info); - self.sign_transaction(&mut spending_tx, &mut inputs_info); - - log::debug!(target: "wallet", - "txhex = {}", - bitcoin::consensus::encode::serialize_hex(&spending_tx) - ); - - let payment_pos = if psbt_result.change_position == 0 { - 1 - } else { - 0 - }; - log::debug!(target: "wallet", "payment_pos = {}", payment_pos); - - spending_txes.push(spending_tx); - payment_output_positions.push(payment_pos); - } + } else { + UTXOSpendInfo::SeedCoin { + path: bip32_info[0]["path"].as_str().unwrap().to_string(), + input_value: convert_json_rpc_bitcoin_to_satoshis( + &input_info["witness_utxo"]["amount"], + ), + } + } + }); + log::debug!(target: "wallet", "inputs_info = {:?}", inputs_info); + self.sign_transaction(&mut tx, &mut inputs_info); - Ok(( - spending_txes, - payment_output_positions, - output_values, - total_miner_fee, - )) + log::debug!(target: "wallet", + "txhex = {}", + bitcoin::consensus::encode::serialize_hex(&tx) + ); + Ok(tx) } fn create_and_import_coinswap_address( @@ -1806,10 +1672,10 @@ impl Wallet { .iter() .map(|other_key| self.create_and_import_coinswap_address(rpc, other_key)) .unzip(); - log::debug!(target: "wallet", "coinswap_addresses = {:#?}", coinswap_addresses); + log::debug!(target: "wallet", "coinswap_addresses = {:?}", coinswap_addresses); - let (my_funding_txes, utxo_indexes, funding_amounts, total_miner_fee) = - self.create_spending_txes(rpc, total_coinswap_amount, &coinswap_addresses, fee_rate)?; + let create_funding_txes_result = + self.create_funding_txes(rpc, total_coinswap_amount, &coinswap_addresses, fee_rate)?; //for sweeping there would be another function, probably //probably have an enum called something like SendAmount which can be // an integer but also can be Sweep @@ -1818,18 +1684,16 @@ impl Wallet { for ( my_funding_tx, - utxo_index, + &utxo_index, &my_multisig_privkey, &other_multisig_pubkey, hashlock_pubkey, - &funding_amount, ) in izip!( - my_funding_txes.iter(), - utxo_indexes.iter(), + create_funding_txes_result.funding_txes.iter(), + create_funding_txes_result.payment_output_positions.iter(), my_multisig_privkeys.iter(), other_multisig_pubkeys.iter(), hashlock_pubkeys.iter(), - funding_amounts.iter() ) { let (timelock_pubkey, timelock_privkey) = generate_keypair(); let contract_redeemscript = contracts::create_contract_redeemscript( @@ -1838,10 +1702,11 @@ impl Wallet { hashvalue, locktime, ); + let funding_amount = my_funding_tx.output[utxo_index as usize].value; let my_senders_contract_tx = contracts::create_senders_contract_tx( OutPoint { txid: my_funding_tx.txid(), - vout: *utxo_index, + vout: utxo_index, }, funding_amount, &contract_redeemscript, @@ -1857,7 +1722,11 @@ impl Wallet { )); } - Ok((my_funding_txes, outgoing_swapcoins, total_miner_fee)) + Ok(( + create_funding_txes_result.funding_txes, + outgoing_swapcoins, + create_funding_txes_result.total_miner_fee, + )) } } @@ -1973,7 +1842,7 @@ fn apply_two_signatures_to_2of2_multisig_spend( input.witness.push(redeemscript.to_bytes()); } -fn convert_json_rpc_bitcoin_to_satoshis(amount: &Value) -> u64 { +pub fn convert_json_rpc_bitcoin_to_satoshis(amount: &Value) -> u64 { //to avoid floating point arithmetic, convert the bitcoin amount to //string with 8 decimal places, then remove the decimal point to //obtain the value in satoshi