diff --git a/jmclient/jmclient/maker.py b/jmclient/jmclient/maker.py index 82dbe91..2213598 100644 --- a/jmclient/jmclient/maker.py +++ b/jmclient/jmclient/maker.py @@ -1,7 +1,7 @@ -#! /usr/bin/env python import base64 import sys import abc +import atexit import jmbitcoin as btc from jmbase import bintohex, hexbin, get_log, EXIT_FAILURE, stop_reactor @@ -38,6 +38,7 @@ class Maker(object): """ if not self.wallet_service.synced: return + self.freeze_timelocked_utxos() self.offerlist = self.create_my_orders() self.fidelity_bond = self.get_fidelity_bond_template() self.sync_wait_loop.stop() @@ -253,6 +254,32 @@ class Maker(object): self.offerlist.remove(oldorder_s[0]) self.offerlist += to_announce + def freeze_timelocked_utxos(self): + """ + Freeze all wallet's timelocked UTXOs. These cannot be spent in a + coinjoin because of protocol limitations. + """ + if not hasattr(self.wallet_service.wallet, 'FIDELITY_BOND_MIXDEPTH'): + return + + frozen_utxos = [] + md_utxos = self.wallet_service.get_utxos_by_mixdepth() + for tx, details \ + in md_utxos[self.wallet_service.FIDELITY_BOND_MIXDEPTH].items(): + if self.wallet_service.is_timelocked_path(details['path']): + self.wallet_service.disable_utxo(*tx) + frozen_utxos.append(tx) + path_repr = self.wallet_service.get_path_repr(details['path']) + jlog.info( + f"Timelocked UTXO at '{path_repr}' has been " + f"auto-frozen. They cannot be spent by makers.") + + def unfreeze(): + for tx in frozen_utxos: + self.wallet_service.disable_utxo(*tx, disable=False) + + atexit.register(unfreeze) + @abc.abstractmethod def create_my_orders(self): """Must generate a set of orders to be displayed diff --git a/jmclient/test/test_maker.py b/jmclient/test/test_maker.py index 031779a..b3a6b93 100644 --- a/jmclient/test/test_maker.py +++ b/jmclient/test/test_maker.py @@ -1,7 +1,9 @@ #!/usr/bin/env python +import datetime import jmbitcoin as btc -from jmclient import Maker, load_test_config, jm_single, WalletService +from jmclient import Maker, load_test_config, jm_single, WalletService, VolatileStorage, \ + SegwitWalletFidelityBonds, get_network import jmclient from commontest import DummyBlockchainInterface from test_taker import DummyWallet @@ -164,6 +166,24 @@ def test_verify_unsigned_tx_nonsw_valid(setup_env_nodeps): assert maker.verify_unsigned_tx(tx, offerlist) == (True, None), "nonsw cj with only p2sh outputs" +def test_freeze_timelocked_utxos(setup_env_nodeps): + storage = VolatileStorage() + SegwitWalletFidelityBonds.initialize(storage, get_network()) + wallet = SegwitWalletFidelityBonds(storage) + ts = wallet.datetime_to_time_number( + datetime.datetime.strptime("2021-07", "%Y-%m")) + tl_path = wallet.get_path( + wallet.FIDELITY_BOND_MIXDEPTH, wallet.BIP32_TIMELOCK_ID, ts) + tl_script = wallet.get_script_from_path(tl_path) + utxo = (b'a'*32, 0) + wallet.add_utxo(utxo[0], utxo[1], tl_script, 100000000) + assert not wallet._utxos.is_disabled(*utxo) + + maker = OfflineMaker(WalletService(wallet)) + maker.freeze_timelocked_utxos() + assert wallet._utxos.is_disabled(*utxo) + + @pytest.fixture def setup_env_nodeps(monkeypatch): monkeypatch.setattr(jmclient.configure, 'get_blockchain_interface_instance',