Skip to content

Commit

Permalink
Display incomplete coinswaps in wallet-balance
Browse files Browse the repository at this point in the history
The wallet-balance method will now also display incomplete coinswaps.
For example, ones which were unsuccessful and require broadcasting
the contract transactions and waiting for the locktime to get ones
money back.
  • Loading branch information
chris-belcher committed Jul 14, 2021
1 parent 5741b6d commit aa6093e
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 5 deletions.
64 changes: 64 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::path::PathBuf;
use std::sync::{Arc, RwLock};

use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::key::PublicKey;
use bitcoin::Amount;
use bitcoin_wallet::mnemonic;
use bitcoincore_rpc::{Auth, Client, Error, RpcApi};
Expand All @@ -18,6 +20,11 @@ mod wallet_sync;
use wallet_sync::Wallet;

mod contracts;
use contracts::{
read_hashlock_pubkey_from_contract, read_locktime_from_contract,
read_timelock_pubkey_from_contract,
};

mod error;
mod maker_protocol;
mod messages;
Expand Down Expand Up @@ -140,6 +147,7 @@ fn display_wallet_balance(wallet_file_name: &PathBuf, long_form: Option<bool>) {
utxos.sort_by(|a, b| b.confirmations.cmp(&a.confirmations));
let utxo_count = utxos.len();
let balance: Amount = utxos.iter().fold(Amount::ZERO, |acc, u| acc + u.amount);
println!("= wallet balance =");
println!(
"{:16} {:24} {:8} {:<7} value",
"outpoint", "address", "swapcoin", "conf",
Expand All @@ -164,6 +172,62 @@ fn display_wallet_balance(wallet_file_name: &PathBuf, long_form: Option<bool>) {
}
println!("coin count = {}", utxo_count);
println!("total balance = {}", balance);

let secp = Secp256k1::new();
let incomplete_coinswaps = wallet.find_incomplete_coinswaps(&rpc).unwrap();
if incomplete_coinswaps.len() > 0 {
println!("= incomplete coinswaps =");
for (hashvalue, utxo_swapcoins) in incomplete_coinswaps {
let balance: Amount = utxo_swapcoins
.iter()
.fold(Amount::ZERO, |acc, us| acc + us.0.amount);
println!(
"{:16} {:8} {:8} {:<15} {:<7} value",
"outpoint", "type", "preimage", "locktime/blocks", "conf",
);
for (utxo, swapcoin) in utxo_swapcoins {
let txid = utxo.txid.to_hex();
let contract_pubkey = PublicKey {
compressed: true,
key: bitcoin::secp256k1::PublicKey::from_secret_key(
&secp,
&swapcoin.contract_privkey,
),
};
let type_string = if contract_pubkey
== read_hashlock_pubkey_from_contract(&swapcoin.contract_redeemscript).unwrap()
{
"hashlock"
} else {
assert_eq!(
contract_pubkey,
read_timelock_pubkey_from_contract(&swapcoin.contract_redeemscript)
.unwrap()
);
"timelock"
};

#[rustfmt::skip]
println!("{}{}{}:{} {:8} {:8} {:^15} {:<7} {}",
if long_form { &txid } else {&txid[0..6] },
if long_form { "" } else { ".." },
if long_form { &"" } else { &txid[58..64] },
utxo.vout,
type_string,
if swapcoin.hash_preimage.is_some() { "known" } else { "unknown" },
read_locktime_from_contract(&swapcoin.contract_redeemscript)
.expect("unable to read locktime from contract"),
utxo.confirmations,
utxo.amount
);
}
println!(
"hashvalue = {}\ntotal balance = {}",
&hashvalue.to_hex()[..],
balance
);
}
}
}

fn display_wallet_keys(wallet_file_name: &PathBuf) {
Expand Down
57 changes: 52 additions & 5 deletions src/wallet_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::path::Path;
use std::str::FromStr;
use std::sync::Arc;

use std::collections::HashMap;
use std::collections::{HashMap, HashSet};

use itertools::izip;

Expand Down Expand Up @@ -46,7 +46,7 @@ use rand::rngs::OsRng;
use rand::RngCore;

use crate::contracts;
use crate::contracts::SwapCoin;
use crate::contracts::{read_hashvalue_from_contract, SwapCoin};
use crate::error::Error;

//these subroutines are coded so that as much as possible they keep all their
Expand Down Expand Up @@ -539,7 +539,7 @@ impl Wallet {
Ok(())
}

fn is_utxo_ours(&self, u: &ListUnspentResultEntry) -> bool {
fn is_utxo_ours_and_spendable(&self, u: &ListUnspentResultEntry) -> bool {
if u.descriptor.is_none() {
return false;
}
Expand Down Expand Up @@ -573,7 +573,7 @@ impl Wallet {
let all_unspents = rpc.list_unspent(None, None, None, None, None)?;
let utxos_to_lock = &all_unspents
.into_iter()
.filter(|u| !self.is_utxo_ours(u))
.filter(|u| !self.is_utxo_ours_and_spendable(u))
.map(|u| OutPoint {
txid: u.txid,
vout: u.vout,
Expand All @@ -592,11 +592,58 @@ impl Wallet {
Ok(rpc
.list_unspent(None, None, None, None, None)?
.iter()
.filter(|u| self.is_utxo_ours(u))
.filter(|u| self.is_utxo_ours_and_spendable(u))
.cloned()
.collect::<Vec<ListUnspentResultEntry>>())
}

pub fn find_incomplete_coinswaps(
&self,
rpc: &Client,
) -> Result<HashMap<[u8; 20], Vec<(ListUnspentResultEntry, &WalletSwapCoin)>>, Error> {
rpc.call::<Value>("lockunspent", &[Value::Bool(true)])
.map_err(|e| Error::Rpc(e))?;

let completed_coinswap_hashvalues = self
.swap_coins
.values()
.filter(|sc| sc.other_privkey.is_some())
.map(|sc| read_hashvalue_from_contract(&sc.contract_redeemscript).unwrap())
.collect::<HashSet<[u8; 20]>>();
//TODO make this read_hashvalue_from_contract() a struct function of WalletCoinSwap

let mut incomplete_swapcoin_groups =
HashMap::<[u8; 20], Vec<(ListUnspentResultEntry, &WalletSwapCoin)>>::new();
for utxo in rpc.list_unspent(None, None, None, None, None)? {
if utxo.descriptor.is_none() {
continue;
}
let multisig_redeemscript = if let Some(rs) = utxo.witness_script.as_ref() {
rs
} else {
continue;
};
let swapcoin = if let Some(s) = self.find_swapcoin(multisig_redeemscript) {
s
} else {
continue;
};
if swapcoin.other_privkey.is_some() {
continue;
}
let swapcoin_hashvalue = read_hashvalue_from_contract(&swapcoin.contract_redeemscript)
.expect("unable to read hashvalue from contract_redeemscript");
if completed_coinswap_hashvalues.contains(&swapcoin_hashvalue) {
continue;
}
incomplete_swapcoin_groups
.entry(swapcoin_hashvalue)
.or_insert(Vec::<(ListUnspentResultEntry, &WalletSwapCoin)>::new())
.push((utxo, swapcoin));
}
Ok(incomplete_swapcoin_groups)
}

// returns None if not a hd descriptor (but possibly a swapcoin (multisig) descriptor instead)
fn get_hd_path_from_descriptor<'a>(&self, descriptor: &'a str) -> Option<(&'a str, u32, i32)> {
//e.g
Expand Down

0 comments on commit aa6093e

Please sign in to comment.