From 596261a723ce022c1ce60819461847b3b0a9b84c Mon Sep 17 00:00:00 2001 From: undeath Date: Sat, 29 Dec 2018 16:51:54 +0100 Subject: [PATCH] implement wallet-tools display[all] extended usage status --- jmclient/jmclient/wallet_utils.py | 100 +++++++++++++++++++++--------- scripts/joinmarket-qt.py | 2 +- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index 23cd620..a96847d 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -10,6 +10,7 @@ import binascii from datetime import datetime from optparse import OptionParser from numbers import Integral +from collections import Counter from jmclient import (get_network, WALLET_IMPLEMENTATIONS, Storage, podle, jm_single, BitcoinCoreInterface, JsonRpcError, sync_wallet, WalletError, VolatileStorage, StoragePasswordError, @@ -291,6 +292,39 @@ class WalletView(WalletViewBase): return self.serclass(entryseparator.join([header] + [ x.serialize(entryseparator, summarize=False) for x in self.accounts] + [footer])) + +def get_tx_info(txid): + """ + Retrieve some basic information about the given transaction. + + :param txid: txid as hex-str + :return: tuple + is_coinjoin: bool + cj_amount: int, only useful if is_coinjoin==True + cj_n: int, number of cj participants, only useful if is_coinjoin==True + output_script_values: {script: value} dict including all outputs + blocktime: int, blocktime this tx was mined + txd: deserialized transaction object (hex-encoded data) + """ + rpctx = jm_single().bc_interface.rpc('gettransaction', [txid]) + txhex = str(rpctx['hex']) + txd = btc.deserialize(txhex) + output_script_values = {binascii.unhexlify(sv['script']): sv['value'] + for sv in txd['outs']} + value_freq_list = sorted( + Counter(output_script_values.values()).most_common(), + key=lambda x: -x[1]) + non_cj_freq = (0 if len(value_freq_list) == 1 else + sum(next(islice(zip(*value_freq_list[1:]), 1, None)))) + is_coinjoin = (value_freq_list[0][1] > 1 and + value_freq_list[0][1] in + [non_cj_freq, non_cj_freq + 1]) + cj_amount = value_freq_list[0][0] + cj_n = value_freq_list[0][1] + return is_coinjoin, cj_amount, cj_n, output_script_values,\ + rpctx['blocktime'], txd + + def get_imported_privkey_branch(wallet, m, showprivkey): entries = [] for path in wallet.yield_imported_paths(m): @@ -338,13 +372,41 @@ def wallet_showutxos(wallet, showprivkey): return json.dumps(unsp, indent=4) + def wallet_display(wallet, gaplimit, showprivkey, displayall=False, serialized=True, summarized=False): """build the walletview object, then return its serialization directly if serialized, else return the WalletView object. """ + def get_addr_status(addr_path, utxos, is_new, is_internal): + addr_balance = 0 + status = [] + for utxo, utxodata in iteritems(utxos): + if addr_path != utxodata['path']: + continue + addr_balance += utxodata['value'] + is_coinjoin, cj_amount, cj_n = \ + get_tx_info(binascii.hexlify(utxo[0]).decode('ascii'))[:3] + if is_coinjoin and utxodata['value'] == cj_amount: + status.append('cj-out') + elif is_coinjoin: + status.append('change-out') + elif is_internal: + status.append('non-cj-change') + else: + status.append('deposit') + + out_status = 'new' if is_new else 'used' + if len(status) > 1: + out_status = 'reused' + elif len(status) == 1: + out_status = status[0] + + return addr_balance, out_status + acctlist = [] + utxos = wallet.get_utxos_by_mixdepth_() for m in range(wallet.mixdepth + 1): branchlist = [] for forchange in [0, 1]: @@ -359,11 +421,8 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False, for k in range(unused_index + gaplimit): path = wallet.get_path(m, forchange, k) addr = wallet.get_addr_path(path) - balance = 0 - for utxodata in wallet.get_utxos_by_mixdepth_()[m].values(): - if path == utxodata['path']: - balance += utxodata['value'] - used = 'used' if k < unused_index else 'new' + balance, used = get_addr_status( + path, utxos[m], k >= unused_index, forchange) if showprivkey: privkey = wallet.get_wif_path(path) else: @@ -582,23 +641,11 @@ def wallet_fetch_history(wallet, options): deposit_times = [] tx_number = 0 for tx in txes: - rpctx = jm_single().bc_interface.rpc('gettransaction', [tx['txid']]) - txhex = str(rpctx['hex']) - txd = btc.deserialize(txhex) - output_script_values = {binascii.unhexlify(sv['script']): sv['value'] - for sv in txd['outs']} + is_coinjoin, cj_amount, cj_n, output_script_values, blocktime, txd =\ + get_tx_info(tx['txid']) + our_output_scripts = wallet_script_set.intersection( - output_script_values.keys()) - - from collections import Counter - value_freq_list = sorted(Counter(output_script_values.values()) - .most_common(), key=lambda x: -x[1]) - non_cj_freq = 0 if len(value_freq_list)==1 else sum(list(zip( - *value_freq_list[1:]))[1]) - is_coinjoin = (value_freq_list[0][1] > 1 and value_freq_list[0][1] in - [non_cj_freq, non_cj_freq+1]) - cj_amount = value_freq_list[0][0] - cj_n = value_freq_list[0][1] + output_script_values.keys()) rpc_inputs = [] for ins in txd['ins']: @@ -686,15 +733,12 @@ def wallet_fetch_history(wallet, options): utxo_count += (len(our_output_scripts) - utxos_consumed) index = '%4d'%(tx_number) tx_number += 1 - timestamp = datetime.fromtimestamp(rpctx['blocktime'] - ).strftime("%Y-%m-%d %H:%M") - utxo_count_str = '% 3d' % (utxo_count) if options.verbosity > 0: if options.verbosity <= 2: n = cj_batch[0] if tx_type == 'cj internal': cj_batch[0] += 1 - cj_batch[1] += rpctx['blocktime'] + cj_batch[1] += blocktime cj_batch[2] += amount cj_batch[3] += delta_balance cj_batch[4] = balance @@ -712,18 +756,18 @@ def wallet_fetch_history(wallet, options): min(cj_batch[8]), max(cj_batch[9]), '...') cj_batch = [0]*8 + [[]]*2 # reset the batch collector # print batch terminating row - print_row(index, rpctx['blocktime'], tx_type, amount, + print_row(index, blocktime, tx_type, amount, delta_balance, balance, cj_n, fees, utxo_count, mixdepth_src, mixdepth_dst, tx['txid']) elif options.verbosity >= 5 or \ (options.verbosity >= 3 and tx_type != 'unknown type'): - print_row(index, rpctx['blocktime'], tx_type, amount, + print_row(index, blocktime, tx_type, amount, delta_balance, balance, cj_n, fees, utxo_count, mixdepth_src, mixdepth_dst, tx['txid']) if tx_type != 'cj internal': deposits.append(delta_balance) - deposit_times.append(rpctx['blocktime']) + deposit_times.append(blocktime) # we could have a leftover batch! if options.verbosity <= 2: diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index f5b0caa..eb27c45 100644 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -1093,7 +1093,7 @@ class JMWalletTab(QWidget): for j in range(len(rows[i][forchange])): item = QTreeWidgetItem(rows[i][forchange][j]) item.setFont(0, QFont(MONOSPACE_FONT)) - if rows[i][forchange][j][3] == 'used': + if rows[i][forchange][j][3] != 'new': item.setForeground(3, QBrush(QColor('red'))) seq_item.addChild(item)