From c70b12f61498451fabcad31b31b67274c4214f9f Mon Sep 17 00:00:00 2001 From: chris-belcher Date: Fri, 16 Jul 2021 14:57:48 +0100 Subject: [PATCH] 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. --- jmclient/jmclient/wallet.py | 29 ++++++++-------------- jmclient/jmclient/wallet_service.py | 25 ++----------------- jmclient/jmclient/wallet_utils.py | 38 +++++++++++++---------------- jmclient/test/test_wallet.py | 2 +- 4 files changed, 30 insertions(+), 64 deletions(-) diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 30e0927..39e21c5 100644 --- a/jmclient/jmclient/wallet.py +++ b/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) diff --git a/jmclient/jmclient/wallet_service.py b/jmclient/jmclient/wallet_service.py index 7cb5854..c4312b5 100644 --- a/jmclient/jmclient/wallet_service.py +++ b/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): diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index 5c1d3f8..ed1b2a1 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/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") diff --git a/jmclient/test/test_wallet.py b/jmclient/test/test_wallet.py index 5a68269..693b3a6 100644 --- a/jmclient/test/test_wallet.py +++ b/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)