Browse Source

For timelock addrs use new pubkey foreach locktime

This commit changes how timelocked addresses are created from the seed and bip32 tree.

A good thing to do would be to have each locktime (e.g. 1st jan 2020,
1st feb 2020, 1st march 2020, etc) actually use a different pubkey from
the HD tree (i.e. ("m/84'/1'/0'/2/0" + 1st jan 2020) ("m/84'/1'/0'/2/1" + 1st feb 2020), etc).

This now means that the sync code doesnt need to know what keys have been associated with
a fidelity bond to scan for the next one. Previously when a user funded a single timelocked
address, the wallet will generate _another_ pubkey and import _another_ ~960 addresses, so
funding one address would actually mean watching and generating ~1920 addresses not ~960.

This should help with the problem found by some people that fidelity bond wallets are slower
to sync. Other optimizations are possible but the structure of fidelity bond wallets will
probably be fixed for decades, so this change is worth doing now.
master
chris-belcher 4 years ago
parent
commit
c70b12f614
No known key found for this signature in database
GPG Key ID: EF734EA677F31129
  1. 29
      jmclient/jmclient/wallet.py
  2. 25
      jmclient/jmclient/wallet_service.py
  3. 38
      jmclient/jmclient/wallet_utils.py
  4. 2
      jmclient/test/test_wallet.py

29
jmclient/jmclient/wallet.py

@ -2182,7 +2182,7 @@ class FidelityBondMixin(object):
For example, if TIMENUMBER_UNIT = 2 (i.e. every time number is two months)
then there are 6 timelocks per year so just 600 possible
addresses per century per pubkey. Easily searchable when recovering a
addresses per century. Easily searchable when recovering a
wallet from seed phrase. Therefore the user doesn't need to store any
dates, the seed phrase is sufficent for recovery.
"""
@ -2196,15 +2196,7 @@ class FidelityBondMixin(object):
MONTHS_IN_YEAR = 12
TIMELOCK_ERA_YEARS = 80
TIMENUMBERS_PER_PUBKEY = TIMELOCK_ERA_YEARS * MONTHS_IN_YEAR // TIMENUMBER_UNIT
"""
As each pubkey corresponds to hundreds of addresses, to reduce load the
given gap limit will be reduced by this factor. Also these timelocked
addresses are never handed out to takers so there wont be a problem of
having many used addresses with no transactions on them.
"""
TIMELOCK_GAP_LIMIT_REDUCTION_FACTOR = 6
TIMENUMBER_COUNT = TIMELOCK_ERA_YEARS * MONTHS_IN_YEAR // TIMENUMBER_UNIT
_TIMELOCK_ENGINE = ENGINES[TYPE_TIMELOCK_P2WSH]
@ -2222,7 +2214,7 @@ class FidelityBondMixin(object):
"""
converts a time number to a unix timestamp
"""
if not 0 <= timenumber < cls.TIMENUMBERS_PER_PUBKEY:
if not 0 <= timenumber < cls.TIMENUMBER_COUNT:
raise ValueError()
year = cls.TIMELOCK_EPOCH_YEAR + (timenumber*cls.TIMENUMBER_UNIT) // cls.MONTHS_IN_YEAR
month = cls.TIMELOCK_EPOCH_MONTH + (timenumber*cls.TIMENUMBER_UNIT) % cls.MONTHS_IN_YEAR
@ -2243,7 +2235,7 @@ class FidelityBondMixin(object):
raise ValueError()
timenumber = (dt.year - cls.TIMELOCK_EPOCH_YEAR)*(cls.MONTHS_IN_YEAR //
cls.TIMENUMBER_UNIT) + ((dt.month - cls.TIMELOCK_EPOCH_MONTH) // cls.TIMENUMBER_UNIT)
if timenumber < 0 or timenumber > cls.TIMENUMBERS_PER_PUBKEY:
if timenumber < 0 or timenumber > cls.TIMENUMBER_COUNT:
raise ValueError("datetime out of range")
return timenumber
@ -2266,13 +2258,12 @@ class FidelityBondMixin(object):
def _populate_script_map(self):
super()._populate_script_map()
for md in self._index_cache:
address_type = self.BIP32_TIMELOCK_ID
for i in range(self._index_cache[md][address_type]):
for timenumber in range(self.TIMENUMBERS_PER_PUBKEY):
path = self.get_path(md, address_type, i, timenumber)
script = self.get_script_from_path(path)
self._script_map[script] = path
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)
script = self.get_script_from_path(path)
self._script_map[script] = path
def add_utxo(self, txid, index, script, value, height=None):
super().add_utxo(txid, index, script, value, height)

25
jmclient/jmclient/wallet_service.py

@ -923,18 +923,8 @@ class WalletService(Service):
if isinstance(self.wallet, FidelityBondMixin):
md = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
saved_indices[md] += [0]
next_unused = self.get_next_unused_index(md, address_type)
for index in range(next_unused):
for timenumber in range(FidelityBondMixin.TIMENUMBERS_PER_PUBKEY):
addresses.add(self.get_addr(md, address_type, index, timenumber))
for index in range(self.gap_limit // FidelityBondMixin.TIMELOCK_GAP_LIMIT_REDUCTION_FACTOR):
index += next_unused
assert self.wallet.get_index_cache_and_increment(md, address_type) == index
for timenumber in range(FidelityBondMixin.TIMENUMBERS_PER_PUBKEY):
self.wallet.get_script_and_update_map(md, address_type, index, timenumber)
addresses.add(self.get_addr(md, address_type, index, timenumber))
self.wallet.set_next_index(md, address_type, next_unused)
for timenumber in range(FidelityBondMixin.TIMENUMBER_COUNT):
addresses.add(self.get_addr(md, address_type, timenumber, timenumber))
return addresses, saved_indices
@ -950,17 +940,6 @@ class WalletService(Service):
addresses.add(self.get_new_addr(md, address_type))
self.set_next_index(md, address_type, old_next)
if isinstance(self.wallet, FidelityBondMixin):
md = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
old_next = self.get_next_unused_index(md, address_type)
for ii in range(gap_limit // FidelityBondMixin.TIMELOCK_GAP_LIMIT_REDUCTION_FACTOR):
index = self.wallet.get_index_cache_and_increment(md, address_type)
for timenumber in range(FidelityBondMixin.TIMENUMBERS_PER_PUBKEY):
self.wallet.get_script_and_update_map(md, address_type, index, timenumber)
addresses.add(self.get_addr(md, address_type, index, timenumber))
self.set_next_index(md, address_type, old_next)
return addresses
def get_external_addr(self, mixdepth):

38
jmclient/jmclient/wallet_utils.py

@ -473,27 +473,23 @@ def wallet_display(wallet_service, showprivkey, displayall=False,
if m == FidelityBondMixin.FIDELITY_BOND_MIXDEPTH and \
isinstance(wallet_service.wallet, FidelityBondMixin):
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
unused_index = wallet_service.get_next_unused_index(m, address_type)
timelocked_gaplimit = (wallet_service.wallet.gap_limit
// FidelityBondMixin.TIMELOCK_GAP_LIMIT_REDUCTION_FACTOR)
entrylist = []
for k in range(unused_index + timelocked_gaplimit):
for timenumber in range(FidelityBondMixin.TIMENUMBERS_PER_PUBKEY):
path = wallet_service.get_path(m, address_type, k, timenumber)
addr = wallet_service.get_address_from_path(path)
timelock = datetime.utcfromtimestamp(path[-1])
balance = sum([utxodata["value"] for utxo, utxodata in
utxos[m].items() if path == utxodata["path"]])
status = timelock.strftime("%Y-%m-%d") + " [" + (
"LOCKED" if datetime.now() < timelock else "UNLOCKED") + "]"
privkey = ""
if showprivkey:
privkey = wallet_service.get_wif_path(path)
if displayall or balance > 0:
entrylist.append(WalletViewEntry(
wallet_service.get_path_repr(path), m, address_type, k,
addr, [balance, balance], priv=privkey, used=status))
for timenumber in range(FidelityBondMixin.TIMENUMBER_COUNT):
path = wallet_service.get_path(m, address_type, timenumber, timenumber)
addr = wallet_service.get_address_from_path(path)
timelock = datetime.utcfromtimestamp(path[-1])
balance = sum([utxodata["value"] for utxo, utxodata in
utxos[m].items() if path == utxodata["path"]])
status = timelock.strftime("%Y-%m-%d") + " [" + (
"LOCKED" if datetime.now() < timelock else "UNLOCKED") + "]"
privkey = ""
if showprivkey:
privkey = wallet_service.get_wif_path(path)
if displayall or balance > 0:
entrylist.append(WalletViewEntry(
wallet_service.get_path_repr(path), m, address_type, k,
addr, [balance, balance], priv=privkey, used=status))
xpub_key = wallet_service.get_bip32_pub_export(m, address_type)
path = wallet_service.get_path_repr(wallet_service.get_path(m, address_type))
branchlist.append(WalletViewBranch(path, m, address_type, entrylist,
@ -1229,10 +1225,10 @@ def wallet_gettimelockaddress(wallet, locktime_string):
m = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
index = wallet.get_next_unused_index(m, address_type)
lock_datetime = datetime.strptime(locktime_string, "%Y-%m")
timenumber = FidelityBondMixin.timestamp_to_time_number(timegm(
lock_datetime.timetuple()))
index = timenumber
path = wallet.get_path(m, address_type, index, timenumber)
jmprint("path = " + wallet.get_path_repr(path), "info")

2
jmclient/test/test_wallet.py

@ -279,7 +279,7 @@ def test_gettimelockaddress_method(setup_wallet, timenumber, locktime_string):
m = FidelityBondMixin.FIDELITY_BOND_MIXDEPTH
address_type = FidelityBondMixin.BIP32_TIMELOCK_ID
index = wallet.get_next_unused_index(m, address_type)
index = timenumber
script = wallet.get_script_and_update_map(m, address_type, index,
timenumber)
addr = wallet.script_to_addr(script)

Loading…
Cancel
Save