Skip to content

Commit d15598b

Browse files
authored
Merge pull request #10313 from SomberNight/202511_keystore_lrucache
keystore: fix memory leak for LRU cache
2 parents bdb7a35 + c919d49 commit d15598b

File tree

2 files changed

+18
-8
lines changed

2 files changed

+18
-8
lines changed

.cirrus.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ task:
225225
# - https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes
226226
# - https://github.com/PyCQA/flake8-bugbear/tree/8c0e7eb04217494d48d0ab093bf5b31db0921989#list-of-warnings
227227
ELECTRUM_LINTERS: E9,E101,E129,E273,E274,E703,E71,E722,F5,F6,F7,F8,W191,W29,B,B909
228-
ELECTRUM_LINTERS_IGNORE: B007,B009,B010,B019,B036,B042,F541,F841
228+
ELECTRUM_LINTERS_IGNORE: B007,B009,B010,B036,B042,F541,F841
229229
- name: "linter: Flake8 Non-Mandatory"
230230
env:
231231
ELECTRUM_LINTERS: E,F,W,C90,B

electrum/keystore.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import re
3030
import copy
3131
from typing import Tuple, TYPE_CHECKING, Union, Sequence, Optional, Dict, List, NamedTuple, Any, Type
32-
from functools import lru_cache, wraps
32+
from functools import wraps
3333
from abc import ABC, abstractmethod
3434

3535
import electrum_ecc as ecc
@@ -52,6 +52,7 @@
5252
from .mnemonic import Mnemonic, Wordlist, calc_seed_type, is_seed
5353
from .plugin import run_hook
5454
from .logging import Logger
55+
from .lrucache import LRUCache
5556

5657
if TYPE_CHECKING:
5758
from .gui.common_qt.util import TaskThread
@@ -398,6 +399,9 @@ def get_passphrase(self, password) -> str:
398399

399400
class MasterPublicKeyMixin(ABC):
400401

402+
def __init__(self):
403+
self._pubkey_cache = LRUCache(maxsize=10**4) # type: LRUCache[Sequence[int], bytes] # path->pubkey
404+
401405
@abstractmethod
402406
def get_master_public_key(self) -> str:
403407
pass
@@ -434,8 +438,14 @@ def get_fp_and_derivation_to_be_used_in_partial_tx(
434438
def get_key_origin_info(self) -> Optional[KeyOriginInfo]:
435439
return None
436440

437-
@abstractmethod
438441
def derive_pubkey(self, for_change: int, n: int) -> bytes:
442+
key = (for_change, n)
443+
if key not in self._pubkey_cache:
444+
self._pubkey_cache[key] = self._derive_pubkey(*key)
445+
return self._pubkey_cache[key]
446+
447+
@abstractmethod
448+
def _derive_pubkey(self, for_change: int, n: int) -> bytes:
439449
"""Returns pubkey at given path.
440450
May raise CannotDerivePubkey.
441451
"""
@@ -501,6 +511,7 @@ def test_der_suffix_against_pubkey(der_suffix: Sequence[int], pubkey: bytes) ->
501511
class Xpub(MasterPublicKeyMixin):
502512

503513
def __init__(self, *, derivation_prefix: str = None, root_fingerprint: str = None):
514+
MasterPublicKeyMixin.__init__(self)
504515
self.xpub = None
505516
self.xpub_receive = None
506517
self.xpub_change = None
@@ -608,8 +619,7 @@ def add_key_origin(self, *, derivation_prefix: str = None, root_fingerprint: str
608619
self._derivation_prefix = derivation_prefix
609620
self.is_requesting_to_be_rewritten_to_wallet_file = True
610621

611-
@lru_cache(maxsize=None)
612-
def derive_pubkey(self, for_change: int, n: int) -> bytes:
622+
def _derive_pubkey(self, for_change: int, n: int) -> bytes:
613623
for_change = int(for_change)
614624
if for_change not in (0, 1):
615625
raise CannotDerivePubkey("forbidden path")
@@ -624,7 +634,7 @@ def derive_pubkey(self, for_change: int, n: int) -> bytes:
624634
return self.get_pubkey_from_xpub(xpub, (n,))
625635

626636
@classmethod
627-
def get_pubkey_from_xpub(self, xpub: str, sequence) -> bytes:
637+
def get_pubkey_from_xpub(cls, xpub: str, sequence) -> bytes:
628638
node = BIP32Node.from_xkey(xpub).subkey_at_public_derivation(sequence)
629639
return node.eckey.get_public_key_bytes(compressed=True)
630640

@@ -729,6 +739,7 @@ class Old_KeyStore(MasterPublicKeyMixin, Deterministic_KeyStore):
729739
type = 'old'
730740

731741
def __init__(self, d: dict):
742+
MasterPublicKeyMixin.__init__(self)
732743
Deterministic_KeyStore.__init__(self, d)
733744
self.mpk = d.get('mpk') # type: Optional[str]
734745
self._root_fingerprint = None
@@ -807,8 +818,7 @@ def get_pubkey_from_mpk(cls, mpk: str, for_change: int, n: int) -> bytes:
807818
public_key = master_public_key + z*ecc.GENERATOR
808819
return public_key.get_public_key_bytes(compressed=False)
809820

810-
@lru_cache(maxsize=None)
811-
def derive_pubkey(self, for_change, n) -> bytes:
821+
def _derive_pubkey(self, for_change, n) -> bytes:
812822
for_change = int(for_change)
813823
if for_change not in (0, 1):
814824
raise CannotDerivePubkey("forbidden path")

0 commit comments

Comments
 (0)