diff --git a/joinmarket/blockchaininterface.py b/joinmarket/blockchaininterface.py index 96fa1170..277c36d7 100644 --- a/joinmarket/blockchaininterface.py +++ b/joinmarket/blockchaininterface.py @@ -135,6 +135,10 @@ def estimate_fee_per_kb(self, N): required for inclusion in the next N blocks. ''' + @abc.abstractmethod + def get_block_count(self): + """Returns the amount of blocks""" + class BlockrInterface(BlockchainInterface): BLOCKR_MAX_ADDR_REQ_COUNT = 20 @@ -196,6 +200,9 @@ def sync_unspent(self, wallet): log.info('no tx used') return i = 0 + + blockr_base = 'https://' + self.blockr_domain + '.blockr.io/api/v1/' + while i < len(addrs): inc = min(len(addrs) - i, self.BLOCKR_MAX_ADDR_REQ_COUNT) req = addrs[i:i + inc] @@ -205,8 +212,7 @@ def sync_unspent(self, wallet): # unspent() doesnt tell you which address, you get a bunch of utxos # but dont know which privkey to sign with - blockr_url = 'https://' + self.blockr_domain + \ - '.blockr.io/api/v1/address/unspent/' + blockr_url = blockr_base + 'address/unspent/' data = btc.make_request_blockr(blockr_url + ','.join(req))['data'] if 'unspent' in data: data = [data] @@ -214,7 +220,8 @@ def sync_unspent(self, wallet): for u in dat['unspent']: wallet.unspent[u['tx'] + ':' + str(u['n'])] = { 'address': dat['address'], - 'value': int(u['amount'].replace('.', '')) + 'value': int(u['amount'].replace('.', '')), + 'blockheight': self.get_block_count() - dat['confirmations'] } for u in wallet.spent_utxos: wallet.unspent.pop(u, None) @@ -412,6 +419,11 @@ def estimate_fee_per_kb(self, N): return fee_per_kb + def get_block_count(self): + blockr_base = 'https://' + self.blockr_domain + '.blockr.io/api/v1/' + blockr_url = blockr_base + "block/info/last" + return btc.make_request_blockr(blockr_url)['data']['nb'] + def bitcoincore_timeout_callback(uc_called, txout_set, txnotify_fun_list, timeoutfun): @@ -820,7 +832,8 @@ def sync_unspent(self, wallet): continue wallet.unspent[u['txid'] + ':' + str(u['vout'])] = { 'address': u['address'], - 'value': int(Decimal(str(u['amount'])) * Decimal('1e8')) + 'value': int(Decimal(str(u['amount'])) * Decimal('1e8')), + 'blockheight': self.get_block_count() - u['confirmations'] } et = time.time() log.debug('bitcoind sync_unspent took ' + str((et - st)) + 'sec') @@ -892,6 +905,9 @@ def estimate_fee_per_kb(self, N): else: return estimate + def get_block_count(self): + return self.rpc('getblockcount', []) + # class for regtest chain access # running on local daemon. Only diff --git a/wallet-tool.py b/wallet-tool.py index af8c1ffa..8761c0bc 100644 --- a/wallet-tool.py +++ b/wallet-tool.py @@ -6,6 +6,7 @@ import os import sys import sqlite3 +import json from optparse import OptionParser from joinmarket import load_program_config, get_network, Wallet, encryptData, \ @@ -18,6 +19,7 @@ 'Does useful little tasks involving your bip32 wallet. The ' 'method is one of the following: (display) Shows addresses and ' 'balances. (displayall) Shows ALL addresses and balances. ' + '(displayold) Shows the wallet with addresses instead of UTXOs' '(summary) Shows a summary of mixing depth balances. (generate) ' 'Generates a new wallet. (recover) Recovers a wallet from the 12 ' 'word recovery seed. (showutxos) Shows all utxos in the wallet, ' @@ -86,7 +88,7 @@ options.maxmixdepth = 5 noseed_methods = ['generate', 'recover', 'listwallets'] -methods = ['display', 'displayall', 'summary', 'showseed', 'importprivkey', +methods = ['display', 'displayall', 'summary', 'displayold', 'showseed', 'importprivkey', 'history', 'showutxos'] methods.extend(noseed_methods) noscan_methods = ['showseed', 'importprivkey', 'dumpprivkey'] @@ -134,7 +136,66 @@ print(json.dumps(unsp, indent=4)) sys.exit(0) -if method == 'display' or method == 'displayall' or method == 'summary': +elif method == 'display' or method == 'displayall' or method == 'summary': + + def cus_print(s): + if method != 'summary': + print(s) + + total_balance = 0 + for m in range(wallet.max_mix_depth): + cus_print('mixing depth %d m/0/%d/' % (m, m)) + balance_depth = 0 + for forchange in [0, 1]: + if forchange == 0: + xpub_key = btc.bip32_privtopub(wallet.keys[m][forchange]) + else: + xpub_key = '' + cus_print(' ' + ('external' if forchange == 0 else 'internal') + + ' addresses m/0/%d/%d' % (m, forchange) + ' ' + xpub_key) + + for k in range(wallet.index[m][forchange] + options.gaplimit): + addr = wallet.get_addr(m, forchange, k) + confs = '' + balance = 0.0 + for utxo, addrvalue in wallet.unspent.iteritems(): + if addr == addrvalue['address']: + balance += addrvalue['value'] + addr = utxo + confs = '{0} confs'.format(jm_single().bc_interface.get_block_count() - addrvalue['blockheight']) + balance_depth += balance + used = (' used' if k < wallet.index[m][forchange] else 'new') + if options.showprivkey: + privkey = btc.wif_compressed_privkey( + wallet.get_key(m, forchange, k), get_p2pk_vbyte()) + else: + privkey = '' + if (method == 'displayall' or balance > 0 or + (used == 'new' and forchange == 0)): + cus_print(' m/0/%d/%d/%03d %-35s%s %.8f btc %s %s' % + (m, forchange, k, addr, used, balance / 1e8, confs, privkey)) + if m in wallet.imported_privkeys: + cus_print(' import addresses') + for privkey in wallet.imported_privkeys[m]: + addr = btc.privtoaddr(privkey, magicbyte=get_p2pk_vbyte()) + balance = 0.0 + for addrvalue in wallet.unspent.values(): + if addr == addrvalue['address']: + balance += addrvalue['value'] + used = (' used' if balance > 0.0 else 'empty') + balance_depth += balance + if options.showprivkey: + wip_privkey = btc.wif_compressed_privkey( + privkey, get_p2pk_vbyte()) + else: + wip_privkey = '' + cus_print(' ' * 13 + '%-35s%s %.8f btc %s' % ( + addr, used, balance / 1e8, wip_privkey)) + total_balance += balance_depth + print('for mixdepth=%d balance=%.8fbtc' % (m, balance_depth / 1e8)) + print('total balance = %.8fbtc' % (total_balance / 1e8)) + +elif method == 'displayold': def cus_print(s): if method != 'summary': @@ -168,8 +229,7 @@ def cus_print(s): if (method == 'displayall' or balance > 0 or (used == ' new' and forchange == 0)): cus_print(' m/0/%d/%d/%03d %-35s%s %.8f btc %s' % - (m, forchange, k, addr, used, balance / 1e8, - privkey)) + (m, forchange, k, addr, used, balance / 1e8, privkey)) if m in wallet.imported_privkeys: cus_print(' import addresses') for privkey in wallet.imported_privkeys[m]: @@ -469,8 +529,8 @@ def skip_n1_btc(v): )['time'] except JsonRpcError: now = jm_single().bc_interface.rpc('getblock', [bestblockhash])['time'] - print(' %s best block is %s' % (datetime.datetime.fromtimestamp(now) - .strftime("%Y-%m-%d %H:%M"), bestblockhash)) + print(' %s best block is %s' % (datetime.datetime.fromtimestamp(now).strftime("%Y-%m-%d %H:%M"), bestblockhash)) + print('total profit = ' + str(float(balance - sum(deposits)) / float(100000000)) + ' BTC') try: #https://gist.github.com/chris-belcher/647da261ce718fc8ca10 import numpy as np