def get_address_generator(script_pre, script_post, vbyte): counter = 0 while True: script = script_pre + struct.pack('=LQQ', 0, 0, counter) + script_post addr = btc.script_to_address(script, vbyte) yield addr, binascii.hexlify(script) counter += 1
def wallet_fetch_history(wallet, options): # sort txes in a db because python can be really bad with large lists con = sqlite3.connect(":memory:") con.row_factory = sqlite3.Row tx_db = con.cursor() tx_db.execute("CREATE TABLE transactions(txid TEXT, " "blockhash TEXT, blocktime INTEGER);") jm_single().debug_silence[0] = True wallet_name = jm_single().bc_interface.get_wallet_name(wallet) for wn in [wallet_name, ""]: buf = range(1000) t = 0 while len(buf) == 1000: buf = jm_single().bc_interface.rpc('listtransactions', [wn, 1000, t, True]) t += len(buf) tx_data = ((tx['txid'], tx['blockhash'], tx['blocktime']) for tx in buf if 'txid' in tx and 'blockhash' in tx and 'blocktime' in tx) tx_db.executemany('INSERT INTO transactions VALUES(?, ?, ?);', tx_data) txes = tx_db.execute('SELECT DISTINCT txid, blockhash, blocktime ' 'FROM transactions ORDER BY blocktime').fetchall() wallet_addr_cache = wallet.addr_cache wallet_addr_set = set(wallet_addr_cache.keys()) def s(): return ',' if options.csv else ' ' def sat_to_str(sat): return '%.8f'%(sat/1e8) def sat_to_str_p(sat): return '%+.8f'%(sat/1e8) def skip_n1(v): return '% 2s'%(str(v)) if v != -1 else ' #' def skip_n1_btc(v): return sat_to_str(v) if v != -1 else '#' + ' '*10 def print_row(index, time, tx_type, amount, delta, balance, cj_n, miner_fees, utxo_count, mixdepth_src, mixdepth_dst, txid): data = [index, datetime.fromtimestamp(time).strftime("%Y-%m-%d %H:%M"), tx_type, sat_to_str(amount), sat_to_str_p(delta), sat_to_str(balance), skip_n1(cj_n), sat_to_str(miner_fees), '% 3d' % utxo_count, skip_n1(mixdepth_src), skip_n1(mixdepth_dst)] if options.verbosity % 2 == 0: data += [txid] print(s().join(map('"{}"'.format, data))) field_names = ['tx#', 'timestamp', 'type', 'amount/btc', 'balance-change/btc', 'balance/btc', 'coinjoin-n', 'total-fees', 'utxo-count', 'mixdepth-from', 'mixdepth-to'] if options.verbosity % 2 == 0: field_names += ['txid'] if options.csv: print('Bumping verbosity level to 4 due to --csv flag') options.verbosity = 1 if options.verbosity > 0: print(s().join(field_names)) if options.verbosity <= 2: cj_batch = [0]*8 + [[]]*2 balance = 0 utxo_count = 0 deposits = [] deposit_times = [] for i, tx in enumerate(txes): rpctx = jm_single().bc_interface.rpc('gettransaction', [tx['txid']]) txhex = str(rpctx['hex']) txd = btc.deserialize(txhex) output_addr_values = dict(((btc.script_to_address(sv['script'], get_p2sh_vbyte()), sv['value']) for sv in txd['outs'])) our_output_addrs = wallet_addr_set.intersection( output_addr_values.keys()) from collections import Counter value_freq_list = sorted(Counter(output_addr_values.values()) .most_common(), key=lambda x: -x[1]) non_cj_freq = 0 if len(value_freq_list)==1 else sum(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] rpc_inputs = [] for ins in txd['ins']: try: wallet_tx = jm_single().bc_interface.rpc('gettransaction', [ins['outpoint']['hash']]) except JsonRpcError: continue input_dict = btc.deserialize(str(wallet_tx['hex']))['outs'][ins[ 'outpoint']['index']] rpc_inputs.append(input_dict) rpc_input_addrs = set((btc.script_to_address(ind['script'], get_p2sh_vbyte()) for ind in rpc_inputs)) our_input_addrs = wallet_addr_set.intersection(rpc_input_addrs) our_input_values = [ind['value'] for ind in rpc_inputs if btc. script_to_address(ind['script'], get_p2sh_vbyte()) in our_input_addrs] our_input_value = sum(our_input_values) utxos_consumed = len(our_input_values) tx_type = None amount = 0 delta_balance = 0 fees = 0 mixdepth_src = -1 mixdepth_dst = -1 #TODO this seems to assume all the input addresses are from the same # mixdepth, which might not be true if len(our_input_addrs) == 0 and len(our_output_addrs) > 0: #payment to us amount = sum([output_addr_values[a] for a in our_output_addrs]) tx_type = 'deposit ' cj_n = -1 delta_balance = amount mixdepth_dst = tuple(wallet_addr_cache[a][0] for a in our_output_addrs) if len(mixdepth_dst) == 1: mixdepth_dst = mixdepth_dst[0] elif len(our_input_addrs) == 0 and len(our_output_addrs) == 0: continue # skip those that don't belong to our wallet elif len(our_input_addrs) > 0 and len(our_output_addrs) == 0: # we swept coins elsewhere if is_coinjoin: tx_type = 'cj sweepout' amount = cj_amount fees = our_input_value - cj_amount else: tx_type = 'sweep out ' amount = sum([v for v in output_addr_values.values()]) fees = our_input_value - amount delta_balance = -our_input_value mixdepth_src = wallet_addr_cache[list(our_input_addrs)[0]][0] elif len(our_input_addrs) > 0 and len(our_output_addrs) == 1: # payment to somewhere with our change address getting the remaining change_value = output_addr_values[list(our_output_addrs)[0]] if is_coinjoin: tx_type = 'cj withdraw' amount = cj_amount else: tx_type = 'withdraw' #TODO does tx_fee go here? not my_tx_fee only? amount = our_input_value - change_value cj_n = -1 delta_balance = change_value - our_input_value fees = our_input_value - change_value - cj_amount mixdepth_src = wallet_addr_cache[list(our_input_addrs)[0]][0] elif len(our_input_addrs) > 0 and len(our_output_addrs) == 2: #payment to self out_value = sum([output_addr_values[a] for a in our_output_addrs]) if not is_coinjoin: print('this is wrong TODO handle non-coinjoin internal') tx_type = 'cj internal' amount = cj_amount delta_balance = out_value - our_input_value mixdepth_src = wallet_addr_cache[list(our_input_addrs)[0]][0] cj_addr = list(set([a for a,v in output_addr_values.iteritems() if v == cj_amount]).intersection(our_output_addrs))[0] mixdepth_dst = wallet_addr_cache[cj_addr][0] else: tx_type = 'unknown type' print('our utxos: ' + str(len(our_input_addrs)) \ + ' in, ' + str(len(our_output_addrs)) + ' out') balance += delta_balance utxo_count += (len(our_output_addrs) - utxos_consumed) index = '% 4d'%(i) 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[2] += amount cj_batch[3] += delta_balance cj_batch[4] = balance cj_batch[5] += cj_n cj_batch[6] += fees cj_batch[7] += utxo_count cj_batch[8] += [mixdepth_src] cj_batch[9] += [mixdepth_dst] elif tx_type != 'unknown type': if n > 0: # print the previously-accumulated batch print_row('N='+"%2d"%n, cj_batch[1]/n, 'cj batch ', cj_batch[2], cj_batch[3], cj_batch[4], cj_batch[5]/n, cj_batch[6], cj_batch[7]/n, 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, 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, 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']) # we could have a leftover batch! if options.verbosity <= 2: n = cj_batch[0] if n > 0: print_row('N='+"%2d"%n, cj_batch[1]/n, 'cj batch ', cj_batch[2], cj_batch[3], cj_batch[4], cj_batch[5]/n, cj_batch[6], cj_batch[7]/n, min(cj_batch[8]), max(cj_batch[9]), '...') bestblockhash = jm_single().bc_interface.rpc('getbestblockhash', []) try: #works with pruning enabled, but only after v0.12 now = jm_single().bc_interface.rpc('getblockheader', [bestblockhash] )['time'] except JsonRpcError: now = jm_single().bc_interface.rpc('getblock', [bestblockhash])['time'] print(' %s best block is %s' % (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 from scipy.optimize import brentq deposit_times = np.array(deposit_times) now -= deposit_times[0] deposit_times -= deposit_times[0] deposits = np.array(deposits) def f(r, deposits, deposit_times, now, final_balance): return np.sum(np.exp((now - deposit_times) / 60.0 / 60 / 24 / 365)**r * deposits) - final_balance r = brentq(f, a=1, b=-1, args=(deposits, deposit_times, now, balance)) print('continuously compounded equivalent annual interest rate = ' + str(r * 100) + ' %') print('(as if yield generator was a bank account)') except ImportError: print('numpy/scipy not installed, unable to calculate effective ' + 'interest rate') total_wallet_balance = sum(wallet.get_balance_by_mixdepth().values()) if balance != total_wallet_balance: print(('BUG ERROR: wallet balance (%s) does not match balance from ' + 'history (%s)') % (sat_to_str(total_wallet_balance), sat_to_str(balance))) if utxo_count != len(wallet.unspent): print(('BUG ERROR: wallet utxo count (%d) does not match utxo count from ' + 'history (%s)') % (len(wallet.unspent), utxo_count))
'mixdepth-from', 'mixdepth-to' ] if options.csv: field_names += ['txid'] l = s().join(field_names) print(l) balance = 0 utxo_count = 0 deposits = [] deposit_times = [] for i, tx in enumerate(txes): rpctx = jm_single().bc_interface.rpc('gettransaction', [tx['txid']]) txhex = str(rpctx['hex']) txd = btc.deserialize(txhex) output_addr_values = dict( ((btc.script_to_address(sv['script'], get_p2pk_vbyte()), sv['value']) for sv in txd['outs'])) our_output_addrs = wallet_addr_set.intersection( output_addr_values.keys()) from collections import Counter value_freq_list = sorted(Counter( output_addr_values.values()).most_common(), key=lambda x: -x[1]) non_cj_freq = 0 if len(value_freq_list) == 1 else sum( 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]