Skip to content

Commit 9ae716a

Browse files
authored
Merge pull request #10114 from SomberNight/202508_change_gap_limit_for_change
wallet: add internal support for changing gap_limit_for_change, and use it in tests
2 parents 971e3f1 + 4f1cc8b commit 9ae716a

File tree

10 files changed

+178
-136
lines changed

10 files changed

+178
-136
lines changed

electrum/wallet.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
391391
"""
392392

393393
max_change_outputs = 3
394-
gap_limit_for_change = 10
394+
gap_limit_for_change = None # type: int | None
395395

396396
txin_type: str
397397
wallet_type: str
@@ -1840,19 +1840,7 @@ def get_change_addresses_for_new_transaction(
18401840
else:
18411841
change_addrs = [preferred_change_addr]
18421842
elif self.use_change:
1843-
# Recalc and get unused change addresses
1844-
addrs = self.calc_unused_change_addresses()
1845-
# New change addresses are created only after a few
1846-
# confirmations.
1847-
if addrs:
1848-
# if there are any unused, select all
1849-
change_addrs = addrs
1850-
else:
1851-
# if there are none, take one randomly from the last few
1852-
if not allow_reusing_used_change_addrs:
1853-
return []
1854-
addrs = self.get_change_addresses(slice_start=-self.gap_limit_for_change)
1855-
change_addrs = [random.choice(addrs)] if addrs else []
1843+
change_addrs = self._get_change_addresses_we_can_use_now(allow_reuse=allow_reusing_used_change_addrs)
18561844
for addr in change_addrs:
18571845
assert is_address(addr), f"not valid bitcoin address: {addr}"
18581846
# note that change addresses are not necessarily ismine
@@ -1872,21 +1860,38 @@ def get_single_change_address_for_new_transaction(
18721860
return addrs[0]
18731861
return None
18741862

1875-
@check_returned_address_for_corruption
18761863
def get_new_sweep_address_for_channel(self) -> str:
1864+
addrs = self._get_change_addresses_we_can_use_now(allow_reuse=True)
1865+
if addrs:
1866+
return addrs[0]
1867+
# fallback for e.g. imported wallets
1868+
return self.get_receiving_address()
1869+
1870+
def _get_change_addresses_we_can_use_now(
1871+
self,
1872+
*,
1873+
allow_reuse: bool = True,
1874+
) -> Sequence[str]:
18771875
# Recalc and get unused change addresses
18781876
addrs = self.calc_unused_change_addresses()
1877+
# New change addresses are created only after a few
1878+
# confirmations.
18791879
if addrs:
1880-
selected_addr = addrs[0]
1880+
# if there are any unused, select all
1881+
change_addrs = addrs
18811882
else:
18821883
# if there are none, take one randomly from the last few
1883-
addrs = self.get_change_addresses(slice_start=-self.gap_limit_for_change)
1884-
if addrs:
1885-
selected_addr = random.choice(addrs)
1886-
else: # fallback for e.g. imported wallets
1887-
selected_addr = self.get_receiving_address()
1888-
assert is_address(selected_addr), f"not valid bitcoin address: {selected_addr}"
1889-
return selected_addr
1884+
if not allow_reuse:
1885+
return []
1886+
gap_limit = self.gap_limit_for_change or 0
1887+
addrs = self.get_change_addresses(slice_start=-gap_limit)
1888+
change_addrs = [random.choice(addrs)] if addrs else []
1889+
for addr in change_addrs:
1890+
assert is_address(addr), f"not valid bitcoin address: {addr}"
1891+
# note that change addresses are not necessarily ismine
1892+
# in which case this is a no-op
1893+
self.check_address_for_corruption(addr)
1894+
return change_addrs
18901895

18911896
def should_keep_reserve_utxo(
18921897
self,
@@ -3805,11 +3810,13 @@ def decrypt_message(self, pubkey: str, message, password) -> bytes:
38053810

38063811

38073812
class Deterministic_Wallet(Abstract_Wallet):
3813+
gap_limit_for_change: int
38083814

38093815
def __init__(self, db, *, config):
38103816
self._ephemeral_addr_to_addr_index = {} # type: Dict[str, Sequence[int]]
38113817
Abstract_Wallet.__init__(self, db, config=config)
38123818
self.gap_limit = db.get('gap_limit', 20)
3819+
self.gap_limit_for_change = db.get('gap_limit_for_change', 10)
38133820
# generate addresses now. note that without libsecp this might block
38143821
# for a few seconds!
38153822
self.synchronize()
@@ -4228,7 +4235,8 @@ def create_new_wallet(
42284235
password: Optional[str] = None,
42294236
encrypt_file: bool = True,
42304237
seed_type: Optional[str] = None,
4231-
gap_limit: Optional[int] = None
4238+
gap_limit: Optional[int] = None,
4239+
gap_limit_for_change: Optional[int] = None,
42324240
) -> dict:
42334241
"""Create a new wallet"""
42344242
storage = WalletStorage(path, allow_partial_writes=config.WALLET_PARTIAL_WRITES)
@@ -4244,6 +4252,8 @@ def create_new_wallet(
42444252
db.put('lightning_xprv', k.get_lightning_xprv(None))
42454253
if gap_limit is not None:
42464254
db.put('gap_limit', gap_limit)
4255+
if gap_limit_for_change is not None:
4256+
db.put('gap_limit_for_change', gap_limit_for_change)
42474257
wallet = Wallet(db, config=config)
42484258
wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
42494259
wallet.synchronize()
@@ -4261,6 +4271,7 @@ def restore_wallet_from_text(
42614271
password: Optional[str] = None,
42624272
encrypt_file: Optional[bool] = None,
42634273
gap_limit: Optional[int] = None,
4274+
gap_limit_for_change: Optional[int] = None,
42644275
) -> dict:
42654276
"""Restore a wallet from text. Text can be a seed phrase, a master
42664277
public key, a master private key, a list of bitcoin addresses
@@ -4304,6 +4315,8 @@ def restore_wallet_from_text(
43044315
db.put('wallet_type', 'standard')
43054316
if gap_limit is not None:
43064317
db.put('gap_limit', gap_limit)
4318+
if gap_limit_for_change is not None:
4319+
db.put('gap_limit_for_change', gap_limit_for_change)
43074320
wallet = Wallet(db, config=config)
43084321
if db.storage:
43094322
assert not db.storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"

tests/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
import threading
55
import tempfile
66
import shutil
7+
import functools
78

89
import electrum
910
import electrum.logging
1011
from electrum import constants
1112
from electrum import util
1213
from electrum.logging import Logger
14+
from electrum.wallet import restore_wallet_from_text
1315

1416

1517
# Set this locally to make the test suite run faster.
@@ -92,3 +94,14 @@ def run_test(*args, **kwargs):
9294
finally:
9395
constants.net = old_net
9496
return run_test
97+
98+
99+
@functools.wraps(restore_wallet_from_text)
100+
def restore_wallet_from_text__for_unittest(*args, gap_limit=2, gap_limit_for_change=1, **kwargs):
101+
"""much lower default gap limits (to save compute time)"""
102+
return restore_wallet_from_text(
103+
*args,
104+
gap_limit=gap_limit,
105+
gap_limit_for_change=gap_limit_for_change,
106+
**kwargs,
107+
)

0 commit comments

Comments
 (0)