diff --git a/mm2src/coins/lightning/ln_events.rs b/mm2src/coins/lightning/ln_events.rs index 3a761cc2b3..d6c78f9ad9 100644 --- a/mm2src/coins/lightning/ln_events.rs +++ b/mm2src/coins/lightning/ln_events.rs @@ -2,7 +2,6 @@ use super::*; use crate::lightning::ln_db::{DBChannelDetails, HTLCStatus, LightningDB, PaymentType}; use crate::lightning::ln_errors::{SaveChannelClosingError, SaveChannelClosingResult}; use crate::lightning::ln_sql::SqliteLightningDB; -use crate::utxo::UtxoCommonOps; use bitcoin::blockdata::script::Script; use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode::serialize_hex; @@ -209,25 +208,15 @@ async fn sign_funding_transaction( }; unsigned.outputs[0].script_pubkey = output_script_pubkey.to_bytes().into(); - let my_address = coin - .as_ref() - .derivation_method - .single_addr_or_err() - .await - .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; let key_pair = coin .as_ref() .priv_key_policy .activated_key_or_err() .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; - let prev_script = coin - .script_for_address(&my_address) - .map_err(|e| SignFundingTransactionError::Internal(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, - prev_script, SignatureVersion::WitnessV0, coin.as_ref().conf.fork_id, ) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 9b6c465f7b..ad2228b055 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2023 Pampex LTD and TillyHK LTD * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 98b12031d7..d1dd834b82 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -539,16 +539,11 @@ impl Qrc20Coin { .build() .await?; - let my_address = self.utxo.derivation_method.single_addr_or_err().await?; let key_pair = self.utxo.priv_key_policy.activated_key_or_err()?; - let prev_script = self - .script_for_address(&my_address) - .map_err(|e| Qrc20GenTxError::InvalidAddress(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, - prev_script, self.utxo.conf.signature_version, self.utxo.conf.fork_id, )?; diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 9df455a188..718e200817 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -74,14 +74,15 @@ fn test_withdraw_to_p2sh_address_should_fail() { let p2sh_address = AddressBuilder::new( UtxoAddressFormat::Standard, - block_on(coin.as_ref().derivation_method.unwrap_single_addr()) - .hash() - .clone(), *block_on(coin.as_ref().derivation_method.unwrap_single_addr()).checksum_type(), coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh( + block_on(coin.as_ref().derivation_method.unwrap_single_addr()) + .hash() + .clone(), + ) .build() .expect("valid address props"); @@ -102,6 +103,13 @@ fn test_withdraw_to_p2sh_address_should_fail() { #[cfg(not(target_arch = "wasm32"))] #[test] fn test_withdraw_impl_fee_details() { + // priv_key of qXxsj5RtciAby9T7m98AgAATL4zTi4UwDG + let priv_key = [ + 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, + 172, 110, 180, 13, 123, 179, 10, 49, + ]; + let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); + Qrc20Coin::get_unspent_ordered_list.mock_safe(|coin, _| { let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); let unspents = vec![UnspentInfo { @@ -111,17 +119,13 @@ fn test_withdraw_impl_fee_details() { }, value: 1000000000, height: Default::default(), + script: coin + .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) + .unwrap(), }]; MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) }); - // priv_key of qXxsj5RtciAby9T7m98AgAATL4zTi4UwDG - let priv_key = [ - 3, 98, 177, 3, 108, 39, 234, 144, 131, 178, 103, 103, 127, 80, 230, 166, 53, 68, 147, 215, 42, 216, 144, 72, - 172, 110, 180, 13, 123, 179, 10, 49, - ]; - let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); - let withdraw_req = WithdrawRequest { amount: 10.into(), from: None, diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 6cf25fcf7f..7d13e63398 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright © 2023 Pampex LTD and TillyHK LTD * + * Copyright © 2023 Pampex LTD and TillyHK LTD * * * * See the CONTRIBUTOR-LICENSE-AGREEMENT, COPYING, LICENSE-COPYRIGHT-NOTICE * * and DEVELOPER-CERTIFICATE-OF-ORIGIN files in the LEGAL directory in * @@ -309,21 +309,20 @@ pub struct CachedUnspentInfo { pub value: u64, } -impl From for CachedUnspentInfo { - fn from(unspent: UnspentInfo) -> CachedUnspentInfo { +impl CachedUnspentInfo { + fn from_unspent_info(unspent: &UnspentInfo) -> CachedUnspentInfo { CachedUnspentInfo { outpoint: unspent.outpoint, value: unspent.value, } } -} -impl From for UnspentInfo { - fn from(cached: CachedUnspentInfo) -> UnspentInfo { + fn to_unspent_info(&self, script: Script) -> UnspentInfo { UnspentInfo { - outpoint: cached.outpoint, - value: cached.value, + outpoint: self.outpoint, + value: self.value, height: None, + script, } } } @@ -350,22 +349,17 @@ impl RecentlySpentOutPoints { } pub fn add_spent(&mut self, inputs: Vec, spend_tx_hash: H256, outputs: Vec) { - let inputs: HashSet<_> = inputs.into_iter().map(From::from).collect(); + let inputs: HashSet<_> = inputs.iter().map(CachedUnspentInfo::from_unspent_info).collect(); let to_replace: HashSet<_> = outputs - .iter() + .into_iter() .enumerate() - .filter_map(|(index, output)| { - if output.script_pubkey == self.for_script_pubkey { - Some(CachedUnspentInfo { - outpoint: OutPoint { - hash: spend_tx_hash, - index: index as u32, - }, - value: output.value, - }) - } else { - None - } + .filter(|(_, output)| output.script_pubkey == self.for_script_pubkey) + .map(|(index, output)| CachedUnspentInfo { + outpoint: OutPoint { + hash: spend_tx_hash, + index: index as u32, + }, + value: output.value, }) .collect(); @@ -400,13 +394,14 @@ impl RecentlySpentOutPoints { pub fn replace_spent_outputs_with_cache(&self, mut outputs: HashSet) -> HashSet { let mut replacement_unspents = HashSet::new(); outputs.retain(|unspent| { - let outs = self.input_to_output_map.get(&unspent.clone().into()); + let outs = self + .input_to_output_map + .get(&CachedUnspentInfo::from_unspent_info(unspent)); + match outs { Some(outs) => { - for out in outs.iter() { - if !replacement_unspents.contains(out) { - replacement_unspents.insert(out.clone()); - } + for out in outs { + replacement_unspents.insert(out.clone()); } false }, @@ -416,7 +411,11 @@ impl RecentlySpentOutPoints { if replacement_unspents.is_empty() { return outputs; } - outputs.extend(replacement_unspents.into_iter().map(From::from)); + outputs.extend( + replacement_unspents + .iter() + .map(|cached| cached.to_unspent_info(self.for_script_pubkey.clone().into())), + ); self.replace_spent_outputs_with_cache(outputs) } } @@ -1795,6 +1794,7 @@ where outpoint: input.previous_output, value: input.amount, height: None, + script: input.prev_script.clone(), }) .collect(); @@ -1803,12 +1803,9 @@ where _ => coin.as_ref().conf.signature_version, }; - let prev_script = utxo_common::output_script_checked(coin.as_ref(), &my_address) - .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; let signed = try_tx_s!(sign_tx( unsigned, key_pair, - prev_script, signature_version, coin.as_ref().conf.fork_id )); @@ -1830,6 +1827,9 @@ pub fn output_script(address: &Address) -> Result { } } +/// Builds transaction output script for a legacy P2PK address +pub fn output_script_p2pk(pubkey: &Public) -> Script { Builder::build_p2pk(pubkey) } + pub fn address_by_conf_and_pubkey_str( coin: &str, conf: &Json, @@ -1854,16 +1854,15 @@ pub fn address_by_conf_and_pubkey_str( let conf_builder = UtxoConfBuilder::new(conf, ¶ms, coin); let utxo_conf = try_s!(conf_builder.build()); let pubkey_bytes = try_s!(hex::decode(pubkey)); - let hash = dhash160(&pubkey_bytes); + let pubkey = try_s!(Public::from_slice(&pubkey_bytes)); let address = AddressBuilder::new( addr_format, - hash.into(), utxo_conf.checksum_type, utxo_conf.address_prefixes, utxo_conf.bech32_hrp, ) - .as_pkh() + .as_pkh_from_pk(pubkey) .build()?; address.display_address() } diff --git a/mm2src/coins/utxo/bchd_grpc.rs b/mm2src/coins/utxo/bchd_grpc.rs index 6017f3b9c0..da240508c6 100644 --- a/mm2src/coins/utxo/bchd_grpc.rs +++ b/mm2src/coins/utxo/bchd_grpc.rs @@ -260,6 +260,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 1000, }, @@ -271,6 +272,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 8999, }, @@ -294,6 +296,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 1000, }, @@ -305,6 +308,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 8999, }, @@ -316,6 +320,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 8999, }, @@ -341,6 +346,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 999, }; @@ -353,6 +359,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 8999, }]; @@ -386,6 +393,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 1000, }, @@ -397,6 +405,7 @@ mod bchd_grpc_tests { }, value: 0, height: None, + script: Vec::new().into(), }, slp_amount: 8999, }, diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 9d1b16723d..55c211e848 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -146,12 +146,11 @@ pub trait QtumBasedCoin: UtxoCommonOps + MarketCoinOps { let utxo = self.as_ref(); AddressBuilder::new( self.addr_format().clone(), - AddressHashEnum::AddressHash(address.0.into()), utxo.conf.checksum_type, utxo.conf.address_prefixes.clone(), utxo.conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh(AddressHashEnum::AddressHash(address.0.into())) .build() .expect("valid address props") } @@ -161,20 +160,6 @@ pub trait QtumBasedCoin: UtxoCommonOps + MarketCoinOps { contract_addr_from_utxo_addr(my_address).mm_err(Qrc20AddressError::from) } - fn utxo_address_from_contract_addr(&self, address: H160) -> Address { - let utxo = self.as_ref(); - AddressBuilder::new( - self.addr_format().clone(), - AddressHashEnum::AddressHash(address.0.into()), - utxo.conf.checksum_type, - utxo.conf.address_prefixes.clone(), - utxo.conf.bech32_hrp.clone(), - ) - .as_pkh() - .build() - .expect("valid address props") - } - fn contract_address_from_raw_pubkey(&self, pubkey: &[u8]) -> Result { let utxo = self.as_ref(); let qtum_address = try_s!(utxo_common::address_from_raw_pubkey( diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index 4a62adcd26..22235fedae 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -186,7 +186,7 @@ impl QtumCoin { .map(|padded_staker_address_hex| padded_staker_address_hex.trim_start_matches('0')) }) { let hash = H160::from_str(raw).map_to_mm(|e| StakingInfosError::Internal(e.to_string()))?; - let address = self.utxo_address_from_contract_addr(hash); + let address = self.utxo_addr_from_contract_addr(hash); Ok(Some(address.to_string())) } else { Ok(None) @@ -290,16 +290,7 @@ impl QtumCoin { DelegationError::from_generate_tx_error(gen_tx_error, self.ticker().to_string(), utxo.decimals) })?; - let prev_script = self - .script_for_address(&my_address) - .map_err(|e| DelegationError::InternalError(e.to_string()))?; - let signed = sign_tx( - unsigned, - key_pair, - prev_script, - utxo.conf.signature_version, - utxo.conf.fork_id, - )?; + let signed = sign_tx(unsigned, key_pair, utxo.conf.signature_version, utxo.conf.fork_id)?; let miner_fee = data.fee_amount + data.unused_change; let generated_tx = GenerateQrc20TxResult { diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index b67946e40a..e8f86bc8e0 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -2,15 +2,15 @@ #![cfg_attr(target_arch = "wasm32", allow(dead_code))] use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; -use crate::utxo::{output_script, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, GetTxError, - GetTxHeightError, ScripthashNotification}; +use crate::utxo::{output_script, output_script_p2pk, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, + GetTxError, GetTxHeightError, NumConversResult, ScripthashNotification}; use crate::{big_decimal_from_sat_unsigned, MyAddressError, NumConversError, RpcTransportEventHandler, RpcTransportEventHandlerShared}; use async_trait::async_trait; use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, OutPoint, Transaction as UtxoTx, TransactionInput, TxHashAlgo}; use common::custom_futures::{select_ok_sequential, timeout::FutureTimerExt}; -use common::custom_iter::{CollectInto, TryIntoGroupMap}; +use common::custom_iter::TryIntoGroupMap; use common::executor::{abortable_queue, abortable_queue::AbortableQueue, AbortableSystem, SpawnFuture, Timer}; use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcBatchResponse, JsonRpcClient, JsonRpcError, JsonRpcErrorType, JsonRpcId, JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcRequest, JsonRpcRequestEnum, @@ -37,6 +37,7 @@ use mm2_number::{BigDecimal, BigInt, MmNumber}; use mm2_rpc::data::legacy::ElectrumProtocol; #[cfg(test)] use mocktopus::macros::*; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; +use script::Script; use serde_json::{self as json, Value as Json}; use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Reader, SERIALIZE_TRANSACTION_WITNESS}; @@ -256,19 +257,34 @@ pub struct UnspentInfo { /// The block height transaction mined in. /// Note None if the transaction is not mined yet. pub height: Option, + /// The script pubkey of the UTXO + pub script: Script, } -impl From for UnspentInfo { - fn from(electrum: ElectrumUnspent) -> UnspentInfo { +impl UnspentInfo { + fn from_electrum(unspent: ElectrumUnspent, script: Script) -> UnspentInfo { UnspentInfo { outpoint: OutPoint { - hash: electrum.tx_hash.reversed().into(), - index: electrum.tx_pos, + hash: unspent.tx_hash.reversed().into(), + index: unspent.tx_pos, }, - value: electrum.value, - height: electrum.height, + value: unspent.value, + height: unspent.height, + script, } } + + fn from_native(unspent: NativeUnspent, decimals: u8, height: Option) -> NumConversResult { + Ok(UnspentInfo { + outpoint: OutPoint { + hash: unspent.txid.reversed().into(), + index: unspent.vout, + }, + value: sat_from_big_decimal(&unspent.amount.to_decimal(), decimals)?, + height, + script: unspent.script_pub_key.0.into(), + }) + } } #[derive(Debug, PartialEq)] @@ -758,20 +774,10 @@ impl UtxoRpcClientOps for NativeClient { .list_unspent_impl(0, std::i32::MAX, vec![address.to_string()]) .map_to_mm_fut(UtxoRpcError::from) .and_then(move |unspents| { - let unspents: UtxoRpcResult> = unspents - .into_iter() - .map(|unspent| { - Ok(UnspentInfo { - outpoint: OutPoint { - hash: unspent.txid.reversed().into(), - index: unspent.vout, - }, - value: sat_from_big_decimal(&unspent.amount.to_decimal(), decimals)?, - height: None, - }) - }) - .collect(); unspents + .into_iter() + .map(|unspent| Ok(UnspentInfo::from_native(unspent, decimals, None)?)) + .collect::>() }); Box::new(fut) } @@ -799,14 +805,7 @@ impl UtxoRpcClientOps for NativeClient { UtxoRpcError::InvalidResponse(format!("Unexpected address '{}'", unspent.address)) })? .clone(); - let unspent_info = UnspentInfo { - outpoint: OutPoint { - hash: unspent.txid.reversed().into(), - index: unspent.vout, - }, - value: sat_from_big_decimal(&unspent.amount.to_decimal(), decimals)?, - height: None, - }; + let unspent_info = UnspentInfo::from_native(unspent, decimals, None)?; Ok((orig_address, unspent_info)) }) // Collect `(Address, UnspentInfo)` items into `HashMap>` grouped by the addresses. @@ -2237,48 +2236,67 @@ impl ElectrumClient { #[cfg_attr(test, mockable)] impl UtxoRpcClientOps for ElectrumClient { fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { - let script = try_f!(output_script(address)); - let script_hash = electrum_script_hash(&script); - Box::new( - self.scripthash_list_unspent(&hex::encode(script_hash)) - .map_to_mm_fut(UtxoRpcError::from) - .map(move |unspents| { + let mut output_scripts = vec![try_f!(output_script(address))]; + + // If the plain pubkey is available, fetch the UTXOs found in P2PK outputs as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_output_script = output_script_p2pk(pubkey); + output_scripts.push(p2pk_output_script); + } + + let this = self.clone(); + let fut = async move { + let hashes = output_scripts + .iter() + .map(|s| hex::encode(electrum_script_hash(s))) + .collect(); + let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; + + let unspents = unspents + .into_iter() + .zip(output_scripts) + .flat_map(|(unspents, output_script)| { unspents - .iter() - .map(|unspent| UnspentInfo { - outpoint: OutPoint { - hash: unspent.tx_hash.reversed().into(), - index: unspent.tx_pos, - }, - value: unspent.value, - height: unspent.height, - }) - .collect() - }), - ) + .into_iter() + .map(move |unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) + }) + .collect(); + Ok(unspents) + }; + + Box::new(fut.boxed().compat()) } fn list_unspent_group(&self, addresses: Vec
, _decimals: u8) -> UtxoRpcFut { - let script_hashes = try_f!(addresses + let output_scripts = try_f!(addresses .iter() - .map(|addr| { - let script = output_script(addr)?; - let script_hash = electrum_script_hash(&script); - Ok(hex::encode(script_hash)) - }) + .map(output_script) .collect::, keys::Error>>()); let this = self.clone(); let fut = async move { - let unspents = this.scripthash_list_unspent_batch(script_hashes).compat().await?; + let hashes = output_scripts + .iter() + .map(|s| hex::encode(electrum_script_hash(s))) + .collect(); + let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; + + let unspents: Vec> = unspents + .into_iter() + .zip(output_scripts) + .map(|(unspents, output_script)| { + unspents + .into_iter() + .map(|unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) + .collect() + }) + .collect(); let unspent_map = addresses .into_iter() // `scripthash_list_unspent_batch` returns `ScriptHashUnspents` elements in the same order in which they were requested. // So we can zip `addresses` and `unspents` into one iterator. .zip(unspents) - // Map `(Address, Vec)` pairs into `(Address, Vec)`. - .map(|(address, electrum_unspents)| (address, electrum_unspents.collect_into())) .collect(); Ok(unspent_map) }; @@ -2346,12 +2364,26 @@ impl UtxoRpcClientOps for ElectrumClient { rpc_req!(self, "blockchain.scripthash.get_balance").into(), JsonRpcErrorType::Internal(err.to_string()) ))); - let hash = electrum_script_hash(&output_script); - let hash_str = hex::encode(hash); - Box::new( - self.scripthash_get_balance(&hash_str) - .map(move |electrum_balance| electrum_balance.to_big_decimal(decimals)), - ) + let mut hashes = vec![hex::encode(electrum_script_hash(&output_script))]; + + // If the plain pubkey is available, fetch the balance found in P2PK output as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_output_script = output_script_p2pk(pubkey); + hashes.push(hex::encode(electrum_script_hash(&p2pk_output_script))); + } + + let this = self.clone(); + let fut = async move { + Ok(this + .scripthash_get_balances(hashes) + .compat() + .await? + .into_iter() + .fold(BigDecimal::from(0), |sum, electrum_balance| { + sum + electrum_balance.to_big_decimal(decimals) + })) + }; + Box::new(fut.boxed().compat()) } fn display_balances(&self, addresses: Vec
, decimals: u8) -> UtxoRpcFut> { diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 4138d3b7a8..d39d7a50a5 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -455,10 +455,11 @@ impl SlpToken { bch_unspent: UnspentInfo { outpoint: OutPoint { hash: tx.hash(), - index: 1, + index: SLP_SWAP_VOUT as u32, }, value: 0, height: None, + script: tx.outputs[SLP_SWAP_VOUT].script_pubkey.clone().into(), }, slp_amount: slp_satoshis, }; @@ -555,8 +556,9 @@ impl SlpToken { hash: tx.hash(), index: SLP_SWAP_VOUT as u32, }, - value: tx.outputs[1].value, + value: tx.outputs[SLP_SWAP_VOUT].value, height: None, + script: tx.outputs[SLP_SWAP_VOUT].script_pubkey.clone().into(), }, slp_amount, }; @@ -606,8 +608,9 @@ impl SlpToken { hash: tx.hash(), index: SLP_SWAP_VOUT as u32, }, - value: tx.outputs[1].value, + value: tx.outputs[SLP_SWAP_VOUT].value, height: None, + script: tx.outputs[SLP_SWAP_VOUT].script_pubkey.clone().into(), }, slp_amount, }; @@ -677,7 +680,6 @@ impl SlpToken { &unsigned, i, my_key_pair, - my_script_pubkey.clone(), self.platform_coin.as_ref().conf.signature_version, self.platform_coin.as_ref().conf.fork_id, ) @@ -1622,12 +1624,6 @@ impl MmCoin for SlpToken { )); } - let my_address = coin - .platform_coin - .as_ref() - .derivation_method - .single_addr_or_err() - .await?; let key_pair = coin.platform_coin.as_ref().priv_key_policy.activated_key_or_err()?; let address = CashAddress::decode(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; @@ -1694,14 +1690,9 @@ impl MmCoin for SlpToken { WithdrawError::from_generate_tx_error(gen_tx_error, coin.platform_ticker().into(), platform_decimals) })?; - let prev_script = coin - .platform_coin - .script_for_address(&my_address) - .map_err(|e| WithdrawError::InvalidAddress(e.to_string()))?; let signed = sign_tx( unsigned, key_pair, - prev_script, coin.platform_conf().signature_version, coin.platform_conf().fork_id, )?; diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index edf34ebb65..0b07d1596c 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -171,12 +171,11 @@ pub trait UtxoFieldsWithIguanaSecretBuilder: UtxoCoinBuilderCommonOps { let addr_format = self.address_format()?; let my_address = AddressBuilder::new( addr_format, - AddressHashEnum::AddressHash(key_pair.public().address_hash()), conf.checksum_type, conf.address_prefixes.clone(), conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh_from_pk(*key_pair.public()) .build() .map_to_mm(UtxoCoinBuildError::Internal)?; let derivation_method = DerivationMethod::SingleAddress(my_address); @@ -276,12 +275,11 @@ where let addr_format = builder.address_format()?; let my_address = AddressBuilder::new( addr_format, - AddressHashEnum::AddressHash(key_pair.public().address_hash()), conf.checksum_type, conf.address_prefixes.clone(), conf.bech32_hrp.clone(), ) - .as_pkh() + .as_pkh_from_pk(*key_pair.public()) .build() .map_to_mm(UtxoCoinBuildError::Internal)?; diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index c757e942db..da916c6cc8 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -310,19 +310,18 @@ pub fn addresses_from_script(coin: &T, script: &Script) -> Res let (addr_format, build_option) = match dst.kind { AddressScriptType::P2PKH => ( coin.addr_format_for_standard_scripts(), - AddressBuilderOption::BuildAsPubkeyHash, + AddressBuilderOption::PubkeyHash(dst.hash), ), AddressScriptType::P2SH => ( coin.addr_format_for_standard_scripts(), - AddressBuilderOption::BuildAsScriptHash, + AddressBuilderOption::ScriptHash(dst.hash), ), - AddressScriptType::P2WPKH => (UtxoAddressFormat::Segwit, AddressBuilderOption::BuildAsPubkeyHash), - AddressScriptType::P2WSH => (UtxoAddressFormat::Segwit, AddressBuilderOption::BuildAsScriptHash), + AddressScriptType::P2WPKH => (UtxoAddressFormat::Segwit, AddressBuilderOption::PubkeyHash(dst.hash)), + AddressScriptType::P2WSH => (UtxoAddressFormat::Segwit, AddressBuilderOption::ScriptHash(dst.hash)), }; AddressBuilder::new( addr_format, - dst.hash, conf.checksum_type, conf.address_prefixes.clone(), conf.bech32_hrp.clone(), @@ -514,9 +513,9 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { .inputs .extend(inputs.into_iter().map(|input| UnsignedTransactionInput { previous_output: input.outpoint, + prev_script: input.script, sequence: SEQUENCE_FINAL, amount: input.value, - witness: Vec::new(), })); self } @@ -589,9 +588,9 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { } if let Some(min_relay) = self.min_relay_fee { if self.tx_fee < min_relay { - outputs_plus_fee -= self.tx_fee; - outputs_plus_fee += min_relay; - self.tx_fee = min_relay; + let fee_diff = min_relay - self.tx_fee; + outputs_plus_fee += fee_diff; + self.tx_fee += fee_diff; } } self.sum_inputs >= outputs_plus_fee @@ -681,17 +680,17 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { }; for utxo in self.available_inputs.clone() { + if self.update_fee_and_check_completeness(from.addr_format(), &actual_tx_fee) { + break; + } + self.tx.inputs.push(UnsignedTransactionInput { previous_output: utxo.outpoint, + prev_script: utxo.script, sequence: SEQUENCE_FINAL, amount: utxo.value, - witness: vec![], }); self.sum_inputs += utxo.value; - - if self.update_fee_and_check_completeness(from.addr_format(), &actual_tx_fee) { - break; - } } match self.fee_policy { @@ -769,12 +768,13 @@ impl<'a, T: AsRef + UtxoTxGenerationOps> UtxoTxBuilder<'a, T> { .find(|input| input.previous_output == utxo.outpoint) { input.amount = utxo.value; + input.prev_script = utxo.script; } else { self.tx.inputs.push(UnsignedTransactionInput { previous_output: utxo.outpoint, + prev_script: utxo.script, sequence: SEQUENCE_FINAL, amount: utxo.value, - witness: vec![], }); } } @@ -913,8 +913,8 @@ async fn p2sh_spending_tx_preimage( hash: prev_tx.hash(), index: DEFAULT_SWAP_VOUT as u32, }, + prev_script: Vec::new().into(), amount, - witness: Vec::new(), }], outputs, expiry_height: 0, @@ -1212,12 +1212,11 @@ pub async fn sign_and_send_taker_funding_spend( ); let payment_address = AddressBuilder::new( UtxoAddressFormat::Standard, - AddressHashEnum::AddressHash(dhash160(&payment_redeem_script)), coin.as_ref().conf.checksum_type, coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(dhash160(&payment_redeem_script).into()) .build() .map_err(TransactionErr::Plain)?; let payment_address_str = payment_address.to_string(); @@ -2443,12 +2442,11 @@ pub fn check_if_my_payment_sent( UtxoRpcClientEnum::Native(client) => { let target_addr = AddressBuilder::new( coin.addr_format_for_standard_scripts(), - hash.into(), coin.as_ref().conf.checksum_type, coin.as_ref().conf.address_prefixes.clone(), coin.as_ref().conf.bech32_hrp.clone(), ) - .as_sh() + .as_sh(hash.into()) .build()?; let target_addr = target_addr.to_string(); let is_imported = try_s!(client.is_address_imported(&target_addr).await); @@ -2687,25 +2685,23 @@ pub fn send_raw_tx_bytes( } /// Helper to load unspent outputs from cache or rpc -/// also returns first previous scriptpubkey async fn get_unspents_for_inputs( coin: &UtxoCoinFields, inputs: &Vec, -) -> Result<(Option