Skip to content

Commit a3e1ba3

Browse files
committed
Default 3rd argument of gettxout should be True
Fixes #1294. Before this commit, calls to query_utxo_set with default arguments would ignore the mempool and thus return utxos which were spent in unconfirmed transactions. Thus, takers would continue negotiation of coinjoins with makers who sent them already-spent utxos, leading to failure at broadcast time. This was not intended behaviour; we want takers to reject utxos that are double spent in the mempool. This commit changes that default argument to True so that utxo set changes in the mempool are accounted for. It also switches the name of the includeunconf argument, which was misleading, to include_mempool, with appropriately updated docstring. Finally, in this commit we also ensure that callers of this function check, where necessary, the returned confirmations field to disallow unconfirmed utxos where that is necessary.
1 parent b066097 commit a3e1ba3

File tree

6 files changed

+26
-19
lines changed

6 files changed

+26
-19
lines changed

jmclient/jmclient/blockchaininterface.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def pushtx(self, txhex):
3939
"""pushes tx to the network, returns False if failed"""
4040

4141
@abc.abstractmethod
42-
def query_utxo_set(self, txouts, includeconf=False):
42+
def query_utxo_set(self, txouts, includeconfs=False):
4343
"""
4444
takes a utxo or a list of utxos
4545
returns None if they are spend or unconfirmed
@@ -109,7 +109,7 @@ def pushtx(self, txhex, timeout=10):
109109
log.debug("Pushed via Electrum successfully, hash: " + tx_hash)
110110
return True
111111

112-
def query_utxo_set(self, txout, includeconf=False):
112+
def query_utxo_set(self, txout, includeconfs=False):
113113
"""Behaves as for Core; TODO make it faster if possible.
114114
Note in particular a failed connection should result in
115115
a result list containing at least one "None" which the
@@ -141,7 +141,7 @@ def query_utxo_set(self, txout, includeconf=False):
141141
'address': address,
142142
'script': btc.address_to_script(address)
143143
}
144-
if includeconf:
144+
if includeconfs:
145145
if int(u['height']) in [0, -1]:
146146
#-1 means unconfirmed inputs
147147
r['confirms'] = 0
@@ -389,15 +389,17 @@ def pushtx(self, txbin):
389389
return False
390390
return True
391391

392-
def query_utxo_set(self, txout, includeconf=False, includeunconf=False):
392+
def query_utxo_set(self, txout, includeconfs=False, include_mempool=True):
393393
"""If txout is either (a) a single utxo in (txidbin, n) form,
394394
or a list of the same, returns, as a list for each txout item,
395395
the result of gettxout from the bitcoind rpc for those utxos;
396396
if any utxo is invalid, None is returned.
397-
includeconf: if this is True, the current number of confirmations
397+
includeconfs: if this is True, the current number of confirmations
398398
of the prescribed utxo is included in the returned result dict.
399-
includeunconf: if True, utxos which currently have zero confirmations
400-
are included in the result set.
399+
include_mempool: if True, the contents of the mempool are included;
400+
this *both* means that utxos that are spent in in-mempool transactions
401+
are *not* returned, *and* means that utxos that are created in the
402+
mempool but have zero confirmations *are* returned.
401403
If the utxo is of a non-standard type such that there is no address,
402404
the address field in the dict is None.
403405
"""
@@ -416,14 +418,14 @@ def query_utxo_set(self, txout, includeconf=False, includeunconf=False):
416418
log.warn("Invalid utxo format, ignoring: {}".format(txo))
417419
result.append(None)
418420
continue
419-
ret = self._rpc('gettxout', [txo_hex, txo_idx, includeunconf])
421+
ret = self._rpc('gettxout', [txo_hex, txo_idx, include_mempool])
420422
if ret is None:
421423
result.append(None)
422424
else:
423425
result_dict = {'value': int(Decimal(str(ret['value'])) *
424426
Decimal('1e8')),
425427
'script': hextobin(ret['scriptPubKey']['hex'])}
426-
if includeconf:
428+
if includeconfs:
427429
result_dict['confirms'] = int(ret['confirmations'])
428430
result.append(result_dict)
429431
return result

jmclient/jmclient/maker.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def reject(msg):
8282
#finally, check that the proffered utxo is real, old enough, large enough,
8383
#and corresponds to the pubkey
8484
res = jm_single().bc_interface.query_utxo_set([cr_dict['utxo']],
85-
includeconf=True)
85+
includeconfs=True)
8686
if len(res) != 1 or not res[0]:
8787
reason = "authorizing utxo is not valid"
8888
return reject(reason)

jmclient/jmclient/taker.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,8 @@ def _verify_ioauth_data(self, ioauth_data):
558558
return verified_data
559559

560560
def _verify_ioauth_inputs(self, nick, utxo_list, auth_pub):
561-
utxo_data = jm_single().bc_interface.query_utxo_set(utxo_list)
561+
utxo_data = jm_single().bc_interface.query_utxo_set(utxo_list,
562+
includeconfs=True)
562563
if None in utxo_data:
563564
raise IoauthInputVerificationError([
564565
"ERROR: outputs unconfirmed or already spent. utxo_data="
@@ -570,6 +571,10 @@ def _verify_ioauth_inputs(self, nick, utxo_list, auth_pub):
570571
# Construct the Bitcoin address for the auth_pub field
571572
# Ensure that at least one address from utxos corresponds.
572573
for inp in utxo_data:
574+
if inp["confirms"] <= 0:
575+
raise IoauthInputVerificationError([
576+
f"maker's ({nick}) proposed utxo is not confirmed, "
577+
"rejecting."])
573578
try:
574579
if self.wallet_service.pubkey_has_script(
575580
auth_pub, inp['script']):
@@ -756,7 +761,7 @@ def make_commitment(self):
756761

757762
def filter_by_coin_age_amt(utxos, age, amt):
758763
results = jm_single().bc_interface.query_utxo_set(utxos,
759-
includeconf=True)
764+
includeconfs=True)
760765
newresults = []
761766
too_new = []
762767
too_small = []

jmclient/jmclient/wallet.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2564,7 +2564,7 @@ def calculate_timelocked_fidelity_bond_value(cls, utxo_value, confirmation_time,
25642564
def get_validated_timelocked_fidelity_bond_utxo(cls, utxo, utxo_pubkey, locktime,
25652565
cert_expiry, current_block_height):
25662566

2567-
utxo_data = jm_single().bc_interface.query_utxo_set(utxo, includeconf=True)
2567+
utxo_data = jm_single().bc_interface.query_utxo_set(utxo, includeconfs=True)
25682568
if utxo_data[0] == None:
25692569
return None
25702570
if utxo_data[0]["confirms"] <= 0:

jmclient/test/commontest.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def set_confs(self, confs_utxos):
7272
def reset_confs(self):
7373
self.confs_for_qus = {}
7474

75-
def query_utxo_set(self, txouts, includeconf=False):
75+
def query_utxo_set(self, txouts, includeconfs=False):
7676
if self.qusfail:
7777
#simulate failure to find the utxo
7878
return [None]
@@ -97,8 +97,8 @@ def query_utxo_set(self, txouts, includeconf=False):
9797
'fd9711a2ef340750db21efb761f5f7d665d94b312332dc354e252c77e9c48349:0': [50000000, 6]}
9898
wallet_outs = dictchanger(wallet_outs)
9999

100-
if includeconf and set(txouts).issubset(set(wallet_outs)):
101-
#includeconf used as a trigger for a podle check;
100+
if includeconfs and set(txouts).issubset(set(wallet_outs)):
101+
#includeconfs used as a trigger for a podle check;
102102
#here we simulate a variety of amount/age returns
103103
results = []
104104
for to in txouts:
@@ -116,7 +116,7 @@ def query_utxo_set(self, txouts, includeconf=False):
116116
result_dict = {'value': 200000000,
117117
'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ",
118118
'script': hextobin('76a91479b000887626b294a914501a4cd226b58b23598388ac')}
119-
if includeconf:
119+
if includeconfs:
120120
if t in self.confs_for_qus:
121121
confs = self.confs_for_qus[t]
122122
else:

jmclient/test/test_wallets.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ def test_query_utxo_set(setup_wallets):
4444
txid2 = do_tx(wallet_service, 20000000)
4545
print("Got txs: ", txid, txid2)
4646
res1 = jm_single().bc_interface.query_utxo_set(
47-
(txid, 0), includeunconf=True)
47+
(txid, 0), include_mempool=True)
4848
res2 = jm_single().bc_interface.query_utxo_set(
4949
[(txid, 0), (txid2, 1)],
50-
includeconf=True, includeunconf=True)
50+
includeconfs=True, include_mempool=True)
5151
assert len(res1) == 1
5252
assert len(res2) == 2
5353
assert all([x in res1[0] for x in ['script', 'value']])

0 commit comments

Comments
 (0)