Skip to content

Commit

Permalink
For timelock addrs use new pubkey foreach locktime
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chris-belcher committed Jul 20, 2021
1 parent 3dc8d86 commit c70b12f
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 64 deletions.
29 changes: 10 additions & 19 deletions jmclient/jmclient/wallet.py
Expand Up @@ -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.
"""
Expand All @@ -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]

Expand All @@ -2222,7 +2214,7 @@ def _time_number_to_timestamp(cls, timenumber):
"""
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
Expand All @@ -2243,7 +2235,7 @@ def timestamp_to_time_number(cls, timestamp):
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

Expand All @@ -2266,13 +2258,12 @@ def get_xpub_from_fidelity_bond_master_pub_key(cls, mpk):

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)
Expand Down
25 changes: 2 additions & 23 deletions jmclient/jmclient/wallet_service.py
Expand Up @@ -923,18 +923,8 @@ def collect_addresses_init(self):
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

Expand All @@ -950,17 +940,6 @@ def collect_addresses_gap(self, gap_limit=None):
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):
Expand Down
38 changes: 17 additions & 21 deletions jmclient/jmclient/wallet_utils.py
Expand Up @@ -473,27 +473,23 @@ def get_addr_status(addr_path, utxos, is_new, is_internal):
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,
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion jmclient/test/test_wallet.py
Expand Up @@ -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)
Expand Down

0 comments on commit c70b12f

Please sign in to comment.