Skip to content

Commit

Permalink
fix(p2pk): show and spend P2PK balance (#2053)
Browse files Browse the repository at this point in the history
UTXO P2PK balance is shown as part of the P2PKH/Legacy address balance, it can be also spent in withdraws and swaps.
  • Loading branch information
mariocynicys authored May 15, 2024
1 parent 29c48bb commit df6ab98
Show file tree
Hide file tree
Showing 35 changed files with 706 additions and 858 deletions.
11 changes: 0 additions & 11 deletions mm2src/coins/lightning/ln_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
)
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
@@ -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 *
Expand Down
5 changes: 0 additions & 5 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)?;
Expand Down
26 changes: 15 additions & 11 deletions mm2src/coins/qrc20/qrc20_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -103,6 +104,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 {
Expand All @@ -112,17 +120,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,
Expand Down
67 changes: 33 additions & 34 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
@@ -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 *
Expand Down Expand Up @@ -309,21 +309,20 @@ pub struct CachedUnspentInfo {
pub value: u64,
}

impl From<UnspentInfo> 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<CachedUnspentInfo> 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,
}
}
}
Expand All @@ -350,22 +349,17 @@ impl RecentlySpentOutPoints {
}

pub fn add_spent(&mut self, inputs: Vec<UnspentInfo>, spend_tx_hash: H256, outputs: Vec<TransactionOutput>) {
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();

Expand Down Expand Up @@ -400,13 +394,14 @@ impl RecentlySpentOutPoints {
pub fn replace_spent_outputs_with_cache(&self, mut outputs: HashSet<UnspentInfo>) -> HashSet<UnspentInfo> {
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
},
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -1795,6 +1794,7 @@ where
outpoint: input.previous_output,
value: input.amount,
height: None,
script: input.prev_script.clone(),
})
.collect();

Expand All @@ -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
));
Expand All @@ -1830,6 +1827,9 @@ pub fn output_script(address: &Address) -> Result<Script, keys::Error> {
}
}

/// 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,
Expand All @@ -1854,16 +1854,15 @@ pub fn address_by_conf_and_pubkey_str(
let conf_builder = UtxoConfBuilder::new(conf, &params, 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()
}
Expand Down
9 changes: 9 additions & 0 deletions mm2src/coins/utxo/bchd_grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 1000,
},
Expand All @@ -271,6 +272,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 8999,
},
Expand All @@ -294,6 +296,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 1000,
},
Expand All @@ -305,6 +308,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 8999,
},
Expand All @@ -316,6 +320,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 8999,
},
Expand All @@ -341,6 +346,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 999,
};
Expand All @@ -353,6 +359,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 8999,
}];
Expand Down Expand Up @@ -386,6 +393,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 1000,
},
Expand All @@ -397,6 +405,7 @@ mod bchd_grpc_tests {
},
value: 0,
height: None,
script: Vec::new().into(),
},
slp_amount: 8999,
},
Expand Down
17 changes: 1 addition & 16 deletions mm2src/coins/utxo/qtum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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<H160, String> {
let utxo = self.as_ref();
let qtum_address = try_s!(utxo_common::address_from_raw_pubkey(
Expand Down
13 changes: 2 additions & 11 deletions mm2src/coins/utxo/qtum_delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit df6ab98

Please sign in to comment.