Browse Source

Choose maker offers based only on our wallet type

Before this commit, the taker would choose offers from
the pit based on the setting of `native` in the `[POLICY]`
section of the config object; however this could lead to
users unwittingly choosing the wrong offer type, i.e. one
that is incompatible with their own wallets, which could
result in coinjoins with mixed address types.
This commit fixes that error by only selecting offers that
are compatible with the return value of `BaseWallet.get_txtype()`.

Also fixes up tests for this wallet type enforcement.
master
Adam Gibson 5 years ago
parent
commit
f69cb37bb3
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 18
      jmclient/jmclient/taker.py
  2. 10
      jmclient/test/test_coinjoin.py
  3. 27
      jmclient/test/test_taker.py

18
jmclient/jmclient/taker.py

@ -250,12 +250,15 @@ class Taker(object):
if sweep: if sweep:
self.orderbook = orderbook #offers choosing deferred to next step self.orderbook = orderbook #offers choosing deferred to next step
else: else:
if jm_single().config.get("POLICY", "segwit") == "false": if self.wallet_service.get_txtype() == "p2pkh":
allowed_types = ["reloffer", "absoffer"] allowed_types = ["reloffer", "absoffer"]
elif jm_single().config.get("POLICY", "native") == "false": elif self.wallet_service.get_txtype() == "p2sh-p2wpkh":
allowed_types = ["swreloffer", "swabsoffer"] allowed_types = ["swreloffer", "swabsoffer"]
else: elif self.wallet_service.get_txtype() == "p2wpkh":
allowed_types = ["sw0reloffer", "sw0absoffer"] allowed_types = ["sw0reloffer", "sw0absoffer"]
else:
jlog.error("Unrecognized wallet type, taker cannot continue.")
return False
self.orderbook, self.total_cj_fee = choose_orders( self.orderbook, self.total_cj_fee = choose_orders(
orderbook, self.cjamount, self.n_counterparties, self.order_chooser, orderbook, self.cjamount, self.n_counterparties, self.order_chooser,
self.ignored_makers, allowed_types=allowed_types, self.ignored_makers, allowed_types=allowed_types,
@ -324,12 +327,15 @@ class Taker(object):
txtype=self.wallet_service.get_txtype()) txtype=self.wallet_service.get_txtype())
jlog.debug("We have a fee estimate: "+str(self.total_txfee)) jlog.debug("We have a fee estimate: "+str(self.total_txfee))
total_value = sum([va['value'] for va in self.input_utxos.values()]) total_value = sum([va['value'] for va in self.input_utxos.values()])
if jm_single().config.get("POLICY", "segwit") == "false": if self.wallet_service.get_txtype() == "p2pkh":
allowed_types = ["reloffer", "absoffer"] allowed_types = ["reloffer", "absoffer"]
elif jm_single().config.get("POLICY", "native") == "false": elif self.wallet_service.get_txtype() == "p2sh-p2wpkh":
allowed_types = ["swreloffer", "swabsoffer"] allowed_types = ["swreloffer", "swabsoffer"]
else: elif self.wallet_service.get_txtype() == "p2wpkh":
allowed_types = ["sw0reloffer", "sw0absoffer"] allowed_types = ["sw0reloffer", "sw0absoffer"]
else:
jlog.error("Unrecognized wallet type, taker cannot continue.")
return False
self.orderbook, self.cjamount, self.total_cj_fee = choose_sweep_orders( self.orderbook, self.cjamount, self.total_cj_fee = choose_sweep_orders(
self.orderbook, total_value, self.total_txfee, self.orderbook, total_value, self.total_txfee,
self.n_counterparties, self.order_chooser, self.n_counterparties, self.order_chooser,

10
jmclient/test/test_coinjoin.py

@ -21,6 +21,10 @@ import jmbitcoin as btc
testdir = os.path.dirname(os.path.realpath(__file__)) testdir = os.path.dirname(os.path.realpath(__file__))
log = get_log() log = get_log()
# map wallet types to offer types:
absoffer_type_map = {LegacyWallet: "absoffer",
SegwitLegacyWallet: "swabsoffer",
SegwitWallet: "sw0absoffer"}
def make_wallets_to_list(make_wallets_data): def make_wallets_to_list(make_wallets_data):
wallets = [None for x in range(len(make_wallets_data))] wallets = [None for x in range(len(make_wallets_data))]
@ -137,7 +141,7 @@ def test_simple_coinjoin(monkeypatch, tmpdir, setup_cj, wallet_cls):
makers = [YieldGeneratorBasic( makers = [YieldGeneratorBasic(
wallet_services[i], wallet_services[i],
[0, 2000, 0, 'sw0absoffer', 10**7]) for i in range(MAKER_NUM)] [0, 2000, 0, absoffer_type_map[wallet_cls], 10**7]) for i in range(MAKER_NUM)]
create_orders(makers) create_orders(makers)
orderbook = create_orderbook(makers) orderbook = create_orderbook(makers)
@ -182,7 +186,7 @@ def test_coinjoin_mixdepth_wrap_taker(monkeypatch, tmpdir, setup_cj):
cj_fee = 2000 cj_fee = 2000
makers = [YieldGeneratorBasic( makers = [YieldGeneratorBasic(
wallet_services[i], wallet_services[i],
[0, cj_fee, 0, 'sw0absoffer', 10**7]) for i in range(MAKER_NUM)] [0, cj_fee, 0, absoffer_type_map[SegwitWallet], 10**7]) for i in range(MAKER_NUM)]
create_orders(makers) create_orders(makers)
orderbook = create_orderbook(makers) orderbook = create_orderbook(makers)
@ -238,7 +242,7 @@ def test_coinjoin_mixdepth_wrap_maker(monkeypatch, tmpdir, setup_cj):
cj_fee = 2000 cj_fee = 2000
makers = [YieldGeneratorBasic( makers = [YieldGeneratorBasic(
wallet_services[i], wallet_services[i],
[0, cj_fee, 0, 'sw0absoffer', 10**7]) for i in range(MAKER_NUM)] [0, cj_fee, 0, absoffer_type_map[SegwitWallet], 10**7]) for i in range(MAKER_NUM)]
create_orders(makers) create_orders(makers)
orderbook = create_orderbook(makers) orderbook = create_orderbook(makers)
assert len(orderbook) == MAKER_NUM assert len(orderbook) == MAKER_NUM

27
jmclient/test/test_taker.py

@ -12,7 +12,7 @@ import struct
from base64 import b64encode from base64 import b64encode
from jmbase import utxostr_to_utxo, hextobin from jmbase import utxostr_to_utxo, hextobin
from jmclient import load_test_config, jm_single, set_commitment_file,\ from jmclient import load_test_config, jm_single, set_commitment_file,\
get_commitment_file, SegwitLegacyWallet, Taker, VolatileStorage,\ get_commitment_file, SegwitWallet, Taker, VolatileStorage,\
get_network, WalletService, NO_ROUNDING, BTC_P2PKH,\ get_network, WalletService, NO_ROUNDING, BTC_P2PKH,\
NotEnoughFundsException NotEnoughFundsException
from taker_test_data import t_utxos_by_mixdepth, t_orderbook,\ from taker_test_data import t_utxos_by_mixdepth, t_orderbook,\
@ -25,7 +25,7 @@ def convert_utxos(utxodict):
return_dict[utxostr_to_utxo(uk)[1]] = val return_dict[utxostr_to_utxo(uk)[1]] = val
return return_dict return return_dict
class DummyWallet(SegwitLegacyWallet): class DummyWallet(SegwitWallet):
def __init__(self): def __init__(self):
storage = VolatileStorage() storage = VolatileStorage()
super().initialize(storage, get_network(), max_mixdepth=5) super().initialize(storage, get_network(), max_mixdepth=5)
@ -88,7 +88,7 @@ class DummyWallet(SegwitLegacyWallet):
"""Return string defining wallet type """Return string defining wallet type
for purposes of transaction size estimates for purposes of transaction size estimates
""" """
return 'p2sh-p2wpkh' return 'p2wpkh'
def get_key_from_addr(self, addr): def get_key_from_addr(self, addr):
"""usable addresses: privkey all 1s, 2s, 3s, ... :""" """usable addresses: privkey all 1s, 2s, 3s, ... :"""
@ -226,10 +226,10 @@ def test_auth_pub_not_found(setup_taker):
#edge case triggers that don't fail #edge case triggers that don't fail
([(0, 0, 4, "mxeLuX8PP7qLkcM8uarHmdZyvP1b5e1Ynf", 0, NO_ROUNDING)], False, False, ([(0, 0, 4, "mxeLuX8PP7qLkcM8uarHmdZyvP1b5e1Ynf", 0, NO_ROUNDING)], False, False,
2, False, None, None), #sweep rounding error case 2, False, None, None), #sweep rounding error case
([(0, 199850001, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)], False, False, ([(0, 199856001, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)], False, False,
2, False, None, None), #trigger sub dust change for taker 2, False, None, None), #trigger sub dust change for taker
#edge case triggers that do fail #edge case triggers that do fail
([(0, 199851000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)], False, False, ([(0, 199857000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)], False, False,
2, False, None, None), #trigger negative change 2, False, None, None), #trigger negative change
([(0, 199599800, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)], False, False, ([(0, 199599800, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)], False, False,
2, False, None, None), #trigger sub dust change for maker 2, False, None, None), #trigger sub dust change for maker
@ -251,11 +251,14 @@ def test_taker_init(setup_taker, schedule, highfee, toomuchcoins, minmakers,
#these tests do not trigger utxo_retries #these tests do not trigger utxo_retries
oldtakerutxoretries = jm_single().config.get("POLICY", "taker_utxo_retries") oldtakerutxoretries = jm_single().config.get("POLICY", "taker_utxo_retries")
oldtakerutxoamtpercent = jm_single().config.get("POLICY", "taker_utxo_amtpercent") oldtakerutxoamtpercent = jm_single().config.get("POLICY", "taker_utxo_amtpercent")
oldtxfees = jm_single().config.get("POLICY", "tx_fees")
jm_single().config.set("POLICY", "taker_utxo_retries", "20") jm_single().config.set("POLICY", "taker_utxo_retries", "20")
jm_single().config.set("POLICY", "tx_fees", "30000")
def clean_up(): def clean_up():
jm_single().config.set("POLICY", "minimum_makers", oldminmakers) jm_single().config.set("POLICY", "minimum_makers", oldminmakers)
jm_single().config.set("POLICY", "taker_utxo_retries", oldtakerutxoretries) jm_single().config.set("POLICY", "taker_utxo_retries", oldtakerutxoretries)
jm_single().config.set("POLICY", "taker_utxo_amtpercent", oldtakerutxoamtpercent) jm_single().config.set("POLICY", "taker_utxo_amtpercent", oldtakerutxoamtpercent)
jm_single().config.set("POLICY", "tx_fees", oldtxfees)
oldminmakers = jm_single().config.get("POLICY", "minimum_makers") oldminmakers = jm_single().config.get("POLICY", "minimum_makers")
jm_single().config.set("POLICY", "minimum_makers", str(minmakers)) jm_single().config.set("POLICY", "minimum_makers", str(minmakers))
taker = get_taker(schedule) taker = get_taker(schedule)
@ -288,19 +291,19 @@ def test_taker_init(setup_taker, schedule, highfee, toomuchcoins, minmakers,
if notauthed: if notauthed:
#Doctor one of the maker response data fields #Doctor one of the maker response data fields
maker_response["J659UPUSLLjHJpaB"][1] = "xx" #the auth pub maker_response["J659UPUSLLjHJpaB"][1] = "xx" #the auth pub
if schedule[0][1] == 199851000: if schedule[0][1] == 199857000:
#triggers negative change #triggers negative change
#((109 + 4*64)*ins + 34 * outs + 8)/4. plug in 9 ins and 8 outs gives # ((10 + 31 * outs + 41 * ins)*4 + 109 * ins)/4. plug in 9 ins and 8 outs gives
#tx size estimate = 1101 bytes. Times 30 ~= 33030. #tx size estimate = 872.25 bytes. Times 30 ~= 26167.5.
#makers offer 3000 txfee, so we pay 30030, plus maker fees = 3*0.0002*200000000 #makers offer 3000 txfee, so we pay 23168, plus maker fees = 3*0.0002*200000000
#roughly, gives required selected = amt + 120k+30k, hence the above = #roughly, gives required selected = amt + 120k+23k, hence the above =
#2btc - 140k sats = 199851000 (tweaked because of aggressive coin selection) #2btc - 143k sats = 199857000 (tweaked because of aggressive coin selection)
#simulate the effect of a maker giving us a lot more utxos #simulate the effect of a maker giving us a lot more utxos
taker.utxos["dummy_for_negative_change"] = [(struct.pack(b"B", a) *32, a+1) for a in range(7,12)] taker.utxos["dummy_for_negative_change"] = [(struct.pack(b"B", a) *32, a+1) for a in range(7,12)]
with pytest.raises(ValueError) as e_info: with pytest.raises(ValueError) as e_info:
res = taker.receive_utxos(maker_response) res = taker.receive_utxos(maker_response)
return clean_up() return clean_up()
if schedule[0][1] == 199850001: if schedule[0][1] == 199856001:
#our own change is greater than zero but less than dust #our own change is greater than zero but less than dust
#use the same edge case as for negative change, don't add dummy inputs #use the same edge case as for negative change, don't add dummy inputs
#(because we need tx creation to complete), but trigger case by #(because we need tx creation to complete), but trigger case by

Loading…
Cancel
Save