From 4e4b15b166972736ebd4d86de1c72e9002812b42 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Wed, 14 Apr 2021 10:00:16 +0100 Subject: [PATCH] add tests cases for commitment sourcing --- jmclient/test/commontest.py | 23 +++++++-- jmclient/test/test_taker.py | 100 +++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 18 deletions(-) diff --git a/jmclient/test/commontest.py b/jmclient/test/commontest.py index 7c61fd7..0ae4249 100644 --- a/jmclient/test/commontest.py +++ b/jmclient/test/commontest.py @@ -36,6 +36,8 @@ class DummyBlockchainInterface(BlockchainInterface): self.fake_query_results = None self.qusfail = False self.cbh = 1 + self.default_confs = 20 + self.confs_for_qus = {} def rpc(self, a, b): return None @@ -59,7 +61,16 @@ class DummyBlockchainInterface(BlockchainInterface): def setQUSFail(self, state): self.qusfail = state - + + def set_confs(self, confs_utxos): + # we hook specific confirmation results + # for specific utxos so that query_utxo_set + # can return a non-constant fake value. + self.confs_for_qus.update(confs_utxos) + + def reset_confs(self): + self.confs_for_qus = {} + def query_utxo_set(self, txouts, includeconf=False): if self.qusfail: #simulate failure to find the utxo @@ -99,13 +110,17 @@ class DummyBlockchainInterface(BlockchainInterface): return [{'value': 200000000, 'address': addr, 'script': scr, - 'confirms': 20}] + 'confirms': self.default_confs}] for t in txouts: - result_dict = {'value': 10000000000, + result_dict = {'value': 200000000, 'address': "mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ", 'script': hextobin('76a91479b000887626b294a914501a4cd226b58b23598388ac')} if includeconf: - result_dict['confirms'] = 20 + if t in self.confs_for_qus: + confs = self.confs_for_qus[t] + else: + confs = self.default_confs + result_dict['confirms'] = confs result.append(result_dict) return result diff --git a/jmclient/test/test_taker.py b/jmclient/test/test_taker.py index 9464515..1d3e4a2 100644 --- a/jmclient/test/test_taker.py +++ b/jmclient/test/test_taker.py @@ -31,6 +31,7 @@ class DummyWallet(SegwitWallet): super().initialize(storage, get_network(), max_mixdepth=5) super().__init__(storage) self._add_utxos() + self.ex_utxos = {} self.inject_addr_get_failure = False def _add_utxos(self): @@ -43,6 +44,26 @@ class DummyWallet(SegwitWallet): script = self._ENGINE.address_to_script(data['address']) self._script_map[script] = path + def add_extra_utxo(self, txid, index, value, md, + address="mrcNu71ztWjAQA6ww9kHiW3zBWSQidHXTQ", + i=0): + # note branch and index, path will be ignored in these test cases, + # the tree is not real. + # if we have extra utxos that have been added for some test, + # we will need to return a script and an address, although it + # won't be used; note we can't use base class get_utxos_by_mixdepth + # because the paths are fake. + if md not in self.ex_utxos: + self.ex_utxos[md] = {} + self.ex_utxos[md].update({(txid, index): {"mixdepth": md, + "address": address, + "value": value, + "script": self._ENGINE.address_to_script(address), + "path": (b'dummy', md, i)}}) + + def remove_extra_utxo(self, txid, index, md): + del self.ex_utxos[(txid, index)] + def get_utxos_by_mixdepth(self, include_disabled=False, verbose=True, includeheight=False): # utxostr conversion routines because taker_test_data uses hex: @@ -53,6 +74,8 @@ class DummyWallet(SegwitWallet): retval[mixdepth][utxostr_to_utxo(utxo)[1]] = val val["script"] = self._ENGINE.address_to_script(val['address']) val["path"] = (b'dummy', mixdepth, i) + for md, u in self.ex_utxos.items(): + retval[md].update(u) return retval def select_utxos(self, mixdepth, amount, utxo_filter=None, select_fn=None, @@ -149,39 +172,88 @@ def test_filter_rejection(setup_taker): assert not res[0] @pytest.mark.parametrize( - "failquery, external", + "mixdepth, cjamt, failquery, external, expected_success, amtpercent, age, mixdepth_extras", [ - (False, False), - (True, False), - (False, True), + (0, 110000000, False, False, True, 0, 0, {}), + (0, 110000000, True, False, True, 0, 0, {}), + (0, 110000000, False, True, True, 0, 0, {}), + # this will fail to source from mixdepth 1 just because 2 < 50% of 5.5: + (1, 550000000, False, False, False, 50, 5, {}), + # this must fail to source even though the size in mixdepth 0 is enough: + (1, 550000000, False, False, False, 50, 5, {0: [600000000]}), + # this should succeed in sourcing because even though there are 9 utxos + # in mixdepth 0, one of them is more than 20% (the original 2BTC): + (0, 900000000, False, False, True, 20, 5, {0:[100000000]*8}), + # this case must fail since the utxos are all at 20 confs and too new: + (0, 110000000, False, False, False, 20, 25, {}), + # make the confs in the spending mixdepth insufficient, while those + # in another mixdepth are OK; must fail: + (0, 110000000, False, False, False, 20, 5, {"confchange": {0: 1}}), ]) -def test_make_commitment(setup_taker, failquery, external): +def test_make_commitment(setup_taker, mixdepth, cjamt, failquery, external, + expected_success, amtpercent, age, mixdepth_extras): def clean_up(): jm_single().config.set("POLICY", "taker_utxo_age", old_taker_utxo_age) jm_single().config.set("POLICY", "taker_utxo_amtpercent", old_taker_utxo_amtpercent) set_commitment_file(old_commitment_file) jm_single().bc_interface.setQUSFail(False) + jm_single().bc_interface.reset_confs() os.remove('dummyext') old_commitment_file = get_commitment_file() with open('dummyext', 'wb') as f: f.write(json.dumps(t_dummy_ext, indent=4).encode('utf-8')) if external: set_commitment_file('dummyext') + + # define the appropriate podle acceptance parameters in the global config: old_taker_utxo_age = jm_single().config.get("POLICY", "taker_utxo_age") old_taker_utxo_amtpercent = jm_single().config.get("POLICY", "taker_utxo_amtpercent") - jm_single().config.set("POLICY", "taker_utxo_age", "5") - jm_single().config.set("POLICY", "taker_utxo_amtpercent", "20") - mixdepth = 0 - amount = 110000000 - taker = get_taker([(mixdepth, amount, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", NO_ROUNDING)]) - taker.cjamount = amount - taker.input_utxos = convert_utxos(t_utxos_by_mixdepth[0]) + if expected_success: + # set to defaults for mainnet + newtua = "5" + newtuap = "20" + else: + newtua = str(age) + newtuap = str(amtpercent) + jm_single().config.set("POLICY", "taker_utxo_age", newtua) + jm_single().config.set("POLICY", "taker_utxo_amtpercent", newtuap) + + taker = get_taker([(mixdepth, cjamt, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", NO_ROUNDING)]) + + # modify or add any extra utxos for this run: + for k, v in mixdepth_extras.items(): + if k == "confchange": + for k2, v2 in v.items(): + # set the utxos in mixdepth k2 to have confs v2: + cdict = taker.wallet_service.get_utxos_by_mixdepth()[k2] + jm_single().bc_interface.set_confs({utxo: v2 for utxo in cdict.keys()}) + else: + for value in v: + taker.wallet_service.add_extra_utxo( + os.urandom(32), 0, value, k) + + taker.cjamount = cjamt + taker.input_utxos = taker.wallet_service.get_utxos_by_mixdepth()[mixdepth] taker.mixdepth = mixdepth if failquery: jm_single().bc_interface.setQUSFail(True) - taker.make_commitment() + comm, revelation, msg = taker.make_commitment() + if expected_success and failquery: + # for manual tests, show the error message: + print("Failure case due to QUS fail: ") + print("Erromsg: ", msg) + assert not comm + elif expected_success: + assert comm, "podle was not generated but should have been." + else: + # in these cases we have set the podle acceptance + # parameters such that our in-mixdepth utxos are not good + # enough. + # for manual tests, show the errormsg: + print("Failure case, errormsg: ", msg) + assert not comm, "podle was generated but should not have been." clean_up() - + def test_not_found_maker_utxos(setup_taker): taker = get_taker([(0, 20000000, 3, "mnsquzxrHXpFsZeL42qwbKdCP2y1esN3qw", 0, NO_ROUNDING)]) orderbook = copy.deepcopy(t_orderbook)