Browse Source

Merge #965: lock timelocked utxos on maker startup

ab5b45e lock timelocked utxos on maker startup (undeath)
dc13038 reuse index for timenumber in FidelityBonds wallet (undeath)
master
Adam Gibson 4 years ago
parent
commit
d884c576a8
No known key found for this signature in database
GPG Key ID: 141001A1AF77F20B
  1. 29
      jmclient/jmclient/maker.py
  2. 17
      jmclient/jmclient/wallet.py
  3. 2
      jmclient/jmclient/wallet_service.py
  4. 5
      jmclient/jmclient/wallet_utils.py
  5. 22
      jmclient/test/test_maker.py
  6. 26
      jmclient/test/test_wallet.py

29
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

17
jmclient/jmclient/wallet.py

@ -2319,7 +2319,7 @@ class FidelityBondMixin(object):
md = self.FIDELITY_BOND_MIXDEPTH
address_type = self.BIP32_TIMELOCK_ID
for timenumber in range(self.TIMENUMBER_COUNT):
path = self.get_path(md, address_type, timenumber, timenumber)
path = self.get_path(md, address_type, timenumber)
script = self.get_script_from_path(path)
self._script_map[script] = path
@ -2355,13 +2355,14 @@ class FidelityBondMixin(object):
else:
return super()._get_key_from_path(path)
def get_path(self, mixdepth=None, address_type=None, index=None, timenumber=None):
def get_path(self, mixdepth=None, address_type=None, index=None):
if address_type == None or address_type in (self.BIP32_EXT_ID, self.BIP32_INT_ID,
self.BIP32_BURN_ID) or index == None:
return super().get_path(mixdepth, address_type, index)
elif address_type == self.BIP32_TIMELOCK_ID:
assert timenumber != None
timestamp = self._time_number_to_timestamp(timenumber)
# index is re-purposed as timenumber
assert index is not None
timestamp = self._time_number_to_timestamp(index)
return tuple(chain(self._get_bip32_export_path(mixdepth, address_type),
(index, timestamp)))
else:
@ -2399,12 +2400,12 @@ class FidelityBondMixin(object):
def _get_default_used_indices(self):
return {x: [0, 0, 0, 0] for x in range(self.max_mixdepth + 1)}
def get_script(self, mixdepth, address_type, index, timenumber=None):
path = self.get_path(mixdepth, address_type, index, timenumber)
def get_script(self, mixdepth, address_type, index):
path = self.get_path(mixdepth, address_type, index)
return self.get_script_from_path(path)
def get_addr(self, mixdepth, address_type, index, timenumber=None):
script = self.get_script(mixdepth, address_type, index, timenumber)
def get_addr(self, mixdepth, address_type, index):
script = self.get_script(mixdepth, address_type, index)
return self.script_to_addr(script)
def add_burner_output(self, path, txhex, block_height, merkle_branch,

2
jmclient/jmclient/wallet_service.py

@ -925,7 +925,7 @@ class WalletService(Service):
md = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
for timenumber in range(FidelityBondMixin.TIMENUMBER_COUNT):
addresses.add(self.get_addr(md, address_type, timenumber, timenumber))
addresses.add(self.get_addr(md, address_type, timenumber))
return addresses, saved_indices

5
jmclient/jmclient/wallet_utils.py

@ -474,7 +474,7 @@ def wallet_display(wallet_service, showprivkey, displayall=False,
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
entrylist = []
for timenumber in range(FidelityBondMixin.TIMENUMBER_COUNT):
path = wallet_service.get_path(m, address_type, timenumber, timenumber)
path = wallet_service.get_path(m, address_type, timenumber)
addr = wallet_service.get_address_from_path(path)
timelock = datetime.utcfromtimestamp(0) + timedelta(seconds=path[-1])
@ -1226,9 +1226,8 @@ def wallet_gettimelockaddress(wallet, locktime_string):
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
lock_datetime = datetime.strptime(locktime_string, "%Y-%m")
timenumber = FidelityBondMixin.datetime_to_time_number(lock_datetime)
index = timenumber
path = wallet.get_path(m, address_type, index, timenumber)
path = wallet.get_path(m, address_type, timenumber)
jmprint("path = " + wallet.get_path_repr(path), "info")
jmprint("Coins sent to this address will be not be spendable until "
+ lock_datetime.strftime("%B %Y") + ". Full date: "

22
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',

26
jmclient/test/test_wallet.py

@ -242,13 +242,12 @@ def test_bip32_addresses_p2sh_p2wpkh(setup_wallet, mixdepth, internal, index, ad
assert wif == wallet.get_wif(mixdepth, internal, index)
assert address == wallet.get_addr(mixdepth, internal, index)
@pytest.mark.parametrize('index,timenumber,address,wif', [
[0, 0, 'bcrt1qgysu2eynn6klarz200ctgev7gqhhp7hwsdaaec3c7h0ltmc3r68q87c2d3', 'cVASAS6bpC5yctGmnsKaDz7D8CxEwccUtpjSNBQzeV2fw8ox8RR9'],
[0, 50, 'bcrt1qyrdhyqzj87vq20e853x7gzhx9lp8ta6cd8mwp8haqex8r4vrg2wsf7rcxm', 'cVASAS6bpC5yctGmnsKaDz7D8CxEwccUtpjSNBQzeV2fw8ox8RR9'],
[5, 0, 'bcrt1quunmmsudhpsuksa2ke8m6aj7757mst966mqq50nckx9wdrs4y6fs9gjuww', 'cUgT5jRjYi6i8Fc7TirJrrvhbs7ceSqJ6USKboVrLYghJKDzEQHQ'],
[9, 1, 'bcrt1qvpgmrn5a7yc0h2j6fp8jhtwzd8eetlt7hsu3cn098qftzp4t2h6sp5p35p', 'cW7H2pv6Rr5NWaTAnDC6r7bviHwDsAwyqh4XdZTE4xf2H2DB2hmb']
@pytest.mark.parametrize('timenumber,address,wif', [
[0, 'bcrt1qgysu2eynn6klarz200ctgev7gqhhp7hwsdaaec3c7h0ltmc3r68q87c2d3', 'cVASAS6bpC5yctGmnsKaDz7D8CxEwccUtpjSNBQzeV2fw8ox8RR9'],
[50, 'bcrt1q0cnscj0hlf6xqzlqwk7swngd3kmvd6unn49j9h4zgg68kg8fd7gq0r87lf', 'cMtnaLzC2EW3URnmAapRnPQECGwGruxqXJpAnuRjKup3pkWfrxRE'],
[1, 'bcrt1q26vw0q28rz2r2ktehp8w5yfzkzskrc4fxqdhzjy0f88kzhjvlfrs7fyas6', 'cU8G1YAAxGZMqNsXxApBAahb8pbxhxryDshFdX5eRT9FV4gHNVXT']
])
def test_bip32_timelocked_addresses(setup_wallet, index, timenumber, address, wif):
def test_bip32_timelocked_addresses(setup_wallet, timenumber, address, wif):
jm_single().config.set('BLOCKCHAIN', 'network', 'testnet')
entropy = unhexlify('2e0339ba89b4a1272cdf78b27ee62669ee01992a59e836e2807051be128ca817')
@ -260,10 +259,10 @@ def test_bip32_timelocked_addresses(setup_wallet, index, timenumber, address, wi
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
#wallet needs to know about the script beforehand
wallet.get_script_and_update_map(mixdepth, address_type, index, timenumber)
wallet.get_script_and_update_map(mixdepth, address_type, timenumber)
assert address == wallet.get_addr(mixdepth, address_type, index, timenumber)
assert wif == wallet.get_wif_path(wallet.get_path(mixdepth, address_type, index, timenumber))
assert address == wallet.get_addr(mixdepth, address_type, timenumber)
assert wif == wallet.get_wif_path(wallet.get_path(mixdepth, address_type, timenumber))
@pytest.mark.parametrize('timenumber,locktime_string', [
[0, "2020-01"],
@ -279,9 +278,7 @@ def test_gettimelockaddress_method(setup_wallet, timenumber, locktime_string):
m = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
index = timenumber
script = wallet.get_script_and_update_map(m, address_type, index,
timenumber)
script = wallet.get_script_and_update_map(m, address_type, timenumber)
addr = wallet.script_to_addr(script)
addr_from_method = wallet_gettimelockaddress(wallet, locktime_string)
@ -405,11 +402,10 @@ def test_timelocked_output_signing(setup_wallet):
SegwitWalletFidelityBonds.initialize(storage, get_network())
wallet = SegwitWalletFidelityBonds(storage)
index = 0
timenumber = 0
script = wallet.get_script_and_update_map(
FidelityBondMixin.FIDELITY_BOND_MIXDEPTH,
FidelityBondMixin.BIP32_TIMELOCK_ID, index, timenumber)
FidelityBondMixin.BIP32_TIMELOCK_ID, timenumber)
utxo = fund_wallet_addr(wallet, wallet.script_to_addr(script))
timestamp = wallet._time_number_to_timestamp(timenumber)
@ -931,7 +927,7 @@ def test_is_standard_wallet_script_nonstandard(setup_wallet):
assert wallet.is_standard_wallet_script(import_path)
ts = wallet.datetime_to_time_number(
datetime.datetime.strptime("2021-07", "%Y-%m"))
tl_path = wallet.get_path(0, wallet.BIP32_TIMELOCK_ID, 0, ts)
tl_path = wallet.get_path(0, wallet.BIP32_TIMELOCK_ID, ts)
assert not wallet.is_standard_wallet_script(tl_path)

Loading…
Cancel
Save