diff --git a/jmclient/jmclient/blockchaininterface.py b/jmclient/jmclient/blockchaininterface.py index 67b8648..447255c 100644 --- a/jmclient/jmclient/blockchaininterface.py +++ b/jmclient/jmclient/blockchaininterface.py @@ -51,7 +51,7 @@ class BlockchainInterface(object): @staticmethod def get_wallet_name(wallet): - return 'joinmarket-wallet-' + btc.dbl_sha256(wallet.keys[0][0])[:6] + return 'joinmarket-wallet-' + wallet.get_wallet_id() @abc.abstractmethod def sync_addresses(self, wallet): @@ -60,8 +60,7 @@ class BlockchainInterface(object): @abc.abstractmethod def sync_unspent(self, wallet): - """Finds the unspent transaction outputs belonging to this wallet, - sets wallet.unspent """ + """Finds the unspent transaction outputs belonging to this wallet""" def add_tx_notify(self, txd, unconfirmfun, confirmfun, notifyaddr, wallet_name=None, timeoutfun=None, spentfun=None, txid_flag=True, @@ -642,7 +641,7 @@ class BitcoinCoreInterface(BlockchainInterface): def sync_unspent(self, wallet): st = time.time() wallet_name = self.get_wallet_name(wallet) - wallet.unspent = {} + wallet.reset_utxos() listunspent_args = [] if 'listunspent_args' in jm_single().config.options('POLICY'): @@ -655,16 +654,29 @@ class BitcoinCoreInterface(BlockchainInterface): continue if u['account'] != wallet_name: continue + # TODO if u['address'] not in wallet.addr_cache: continue - wallet.unspent[u['txid'] + ':' + str(u['vout'])] = { - 'address': u['address'], - 'value': int(Decimal(str(u['amount'])) * Decimal('1e8')) - } + self._add_unspent_utxo(wallet, u) et = time.time() log.debug('bitcoind sync_unspent took ' + str((et - st)) + 'sec') self.wallet_synced = True + @staticmethod + def _add_unspent_utxo(wallet, utxo): + """ + Add a UTXO as returned by rpc's listunspent call to the wallet. + + params: + wallet: wallet + utxo: single utxo dict as returned by listunspent + """ + txid = binascii.unhexlify(utxo['txid']) + script = binascii.unhexlify(utxo['scriptPubKey']) + value = int(Decimal(str(utxo['amount'])) * Decimal('1e8')) + + wallet.add_utxo(txid, int(utxo['vout']), script, value) + def get_deser_from_gettransaction(self, rpcretval): """Get full transaction deserialization from a call to `gettransaction` diff --git a/jmclient/jmclient/electruminterface.py b/jmclient/jmclient/electruminterface.py index d06c017..cf3cd5b 100644 --- a/jmclient/jmclient/electruminterface.py +++ b/jmclient/jmclient/electruminterface.py @@ -6,11 +6,9 @@ import pprint import random import socket import threading -import time -import sys import ssl -from twisted.python.log import startLogging -from twisted.internet.protocol import ClientFactory, Protocol +import binascii +from twisted.internet.protocol import ClientFactory from twisted.internet.ssl import ClientContextFactory from twisted.protocols.basic import LineReceiver from twisted.internet import reactor, task, defer @@ -328,10 +326,10 @@ class ElectrumInterface(BlockchainInterface): def sync_unspent(self, wallet): # finds utxos in the wallet - wallet.unspent = {} + wallet.reset_utxos() #Prepare list of all used addresses - addrs = [] - for m in range(wallet.max_mix_depth): + addrs = set() + for m in range(wallet.max_mixdepth): for fc in [0, 1]: branch_list = [] for k, v in self.temp_addr_history[m][fc].iteritems(): @@ -339,7 +337,7 @@ class ElectrumInterface(BlockchainInterface): continue if v["used"]: branch_list.append(v["addr"]) - addrs.extend(branch_list) + addrs.update(branch_list) if len(addrs) == 0: log.debug('no tx used') self.wallet_synced = True @@ -348,21 +346,28 @@ class ElectrumInterface(BlockchainInterface): return #make sure to add any addresses during the run (a subset of those #added to the address cache) - addrs = list(set(self.wallet.addr_cache.keys()).union(set(addrs))) - self.listunspent_calls = 0 + for md in range(wallet.max_mixdepth): + for internal in (True, False): + for index in range(wallet.get_next_unused_index(md, internal)): + addrs.add(wallet.get_addr(md, internal, index)) + for path in wallet.get_imported_paths(md): + addrs.add(wallet.get_addr_path(path)) + + self.listunspent_calls = len(addrs) for a in addrs: + # FIXME: update to protocol version 1.1 and use scripthash instead + script = wallet.address_to_script(a) d = self.get_from_electrum('blockchain.address.listunspent', a) - d.addCallback(self.process_listunspent_data, wallet, a, len(addrs)) + d.addCallback(self.process_listunspent_data, wallet, script) - def process_listunspent_data(self, unspent_info, wallet, address, n): - self.listunspent_calls += 1 + def process_listunspent_data(self, unspent_info, wallet, script): res = unspent_info['result'] for u in res: - wallet.unspent[str(u['tx_hash']) + ':' + str( - u['tx_pos'])] = {'address': address, 'value': int(u['value'])} - if self.listunspent_calls == n: - for u in wallet.spent_utxos: - wallet.unspent.pop(u, None) + txid = binascii.unhexlify(u['tx_hash']) + wallet.add_utxo(txid, int(u['tx_pos']), script, int(u['value'])) + + self.listunspent_calls -= 1 + if self.listunspent_calls == 0: self.wallet_synced = True if self.synctype == "sync-only": reactor.stop() diff --git a/scripts/add-utxo.py b/scripts/add-utxo.py index 1c9d6a7..1a1c29a 100644 --- a/scripts/add-utxo.py +++ b/scripts/add-utxo.py @@ -9,6 +9,7 @@ the anti-snooping feature employed by makers. import sys import os import json +import binascii from pprint import pformat from optparse import OptionParser @@ -173,20 +174,16 @@ def main(): #Three options (-w, -r, -R) for loading utxo and privkey pairs from a wallet, #csv file or json file. if options.loadwallet: - # TODO: new wallet has no unspent attribute - raise NotImplementedError("This is not yet implemented.") wallet_path = get_wallet_path(options.loadwallet, None) wallet = open_wallet(wallet_path, gap_limit=options.gaplimit) sync_wallet(wallet, fast=options.fastsync) - unsp = {} - for u, av in wallet.unspent.iteritems(): - addr = av['address'] - key = wallet.get_key_from_addr(addr) - wifkey = btc.wif_compressed_privkey(key, vbyte=get_p2pk_vbyte()) - unsp[u] = {'address': av['address'], - 'value': av['value'], 'privkey': wifkey} - for u, pva in unsp.iteritems(): - utxo_data.append((u, pva['privkey'])) + + for md, utxos in wallet.get_utxos_by_mixdepth_().items(): + for (txid, index), utxo in utxos.items(): + txhex = binascii.hexlify(txid) + ':' + str(index) + wif = wallet.get_wif_path(utxo['path']) + utxo_data.append((txhex, wif)) + elif options.in_file: with open(options.in_file, "rb") as f: utxo_info = f.readlines() diff --git a/scripts/jmtainter.py b/scripts/jmtainter.py index a168a32..24f216c 100644 --- a/scripts/jmtainter.py +++ b/scripts/jmtainter.py @@ -103,18 +103,24 @@ def serialize_derivation(roc, i): return x #======================================================= -def get_privkey_amount_from_utxo(wallet, utxo): + +def get_script_amount_from_utxo(wallet, utxo): """Given a JM wallet and a utxo string, find the corresponding private key and amount controlled in satoshis. """ - for k, v in wallet.unspent.iteritems(): - if k == utxo: - print("Found utxo, its value is: ", v['value']) - return wallet.get_key_from_addr(v['address']), v['value'] - return (None, None) + for md, utxos in wallet.get_utxos_by_mixdepth_().items(): + for (txid, index), utxo in utxos.items(): + txhex = binascii.hexlify(txid) + ':' + str(index) + if txhex != utxo: + continue + script = wallet.get_script_path(utxo['path']) + print("Found utxo, its value is: {}".format(utxo['value'])) + return script, utxo['value'] + return None, None + -def create_single_acp_pair(utxo_in, priv, addr_out, amount, bump, segwit=False): +def create_single_acp_pair(wallet, utxo_in, script, addr_out, amount, bump, segwit=False): """Given a utxo and a signing key for it, and its amout in satoshis, sign a "transaction" consisting of only 1 input and one output, signed with single|acp sighash flags so it can be grafted into a bigger @@ -129,10 +135,9 @@ def create_single_acp_pair(utxo_in, priv, addr_out, amount, bump, segwit=False): assert bump >= 0, "Output of single|acp pair must be bigger than input for safety." out = {"address": addr_out, "value": amount + bump} tx = btc.mktx([utxo_in], [out]) - amt = amount if segwit else None - return btc.sign(tx, 0, priv, - hashcode=btc.SIGHASH_SINGLE|btc.SIGHASH_ANYONECANPAY, - amount=amt) + return wallet.sign_tx(tx, {0: (script, amount)}, + hashcode=btc.SIGHASH_SINGLE|btc.SIGHASH_ANYONECANPAY) + def graft_onto_single_acp(wallet, txhex, amount, destaddr): """Given a serialized txhex which is checked to be of @@ -187,13 +192,15 @@ def graft_onto_single_acp(wallet, txhex, amount, destaddr): df['ins'][0]['script'] = d['ins'][0]['script'] if 'txinwitness' in d['ins'][0]: df['ins'][0]['txinwitness'] = d['ins'][0]['txinwitness'] - fulltx = btc.serialize(df) + for i, iu in enumerate(input_utxos): - priv, inamt = get_privkey_amount_from_utxo(wallet, iu) - print("Signing index: ", i+1, " with privkey: ", priv, " and amount: ", inamt, " for utxo: ", iu) - fulltx = btc.sign(fulltx, i+1, priv, amount=inamt) - return (True, fulltx) - + script, inamt = get_script_amount_from_utxo(wallet, iu) + print("Signing index: ", i+1, " with script: ", script, " and amount: ", inamt, " for utxo: ", iu) + fulltx = wallet.sign_tx(df, {i: (script, inamt)}) + + return True, btc.serialize(fulltx) + + if __name__ == "__main__": parser = get_parser() (options, args) = parser.parse_args() @@ -214,22 +221,22 @@ if __name__ == "__main__": "Use wallet-tool.py method 'showutxos' to select one") exit(0) utxo_in = args[2] - priv, amount = get_privkey_amount_from_utxo(wallet, utxo_in) - if not priv: + script, amount = get_script_amount_from_utxo(wallet, utxo_in) + if not script: print("Failed to find the utxo's private key from the wallet; check " "if this utxo is actually contained in the wallet using " "wallet-tool.py showutxos") exit(0) #destination sourced from wallet addr_out = wallet.get_new_addr((options.mixdepth+1)%options.amtmixdepths, 1) - serialized_single_acp = create_single_acp_pair(utxo_in, priv, addr_out, amount, - options.bump, segwit=True) + single_acp = create_single_acp_pair(wallet, utxo_in, script, addr_out, amount, + options.bump, segwit=True) print("Created the following one-in, one-out transaction, which will not " "be valid to broadcast itself (negative fee). Pass it to your " "counterparty:") - print(pformat(btc.deserialize(serialized_single_acp))) + print(pformat(single_acp)) print("Pass the following raw hex to your counterparty:") - print(serialized_single_acp) + print(btc.serialize(single_acp)) exit(0) elif args[1] == "take": try: diff --git a/test/ygrunner.py b/test/ygrunner.py index d3ed0d6..80957d2 100644 --- a/test/ygrunner.py +++ b/test/ygrunner.py @@ -110,7 +110,7 @@ def test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, mean_amt, print("Seed : " + wallets[num_ygs]['seed']) #useful to see the utxos on screen sometimes sync_wallet(wallet, fast=True) - print(wallet.unspent) + print(wallet.get_utxos_by_mixdepth()) txfee = 1000 cjfee_a = 4200 cjfee_r = '0.001'