1
0

Merge pull request #10031 from SomberNight/202507_walletdb_add_configvar_partial_writes_4

wallet_db: add configvar for partial_writes, disable by default
This commit is contained in:
ThomasV
2025-07-15 16:28:33 +02:00
committed by GitHub
7 changed files with 36 additions and 13 deletions

View File

@@ -507,7 +507,7 @@ class Daemon(Logger):
config: SimpleConfig,
) -> Optional[Abstract_Wallet]:
path = standardize_path(path)
storage = WalletStorage(path)
storage = WalletStorage(path, allow_partial_writes=config.WALLET_PARTIAL_WRITES)
if not storage.file_exists():
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), path)
if storage.is_encrypted():

View File

@@ -421,11 +421,7 @@ class JsonDB(Logger):
@locked
def write(self):
if (
not self.storage.file_exists()
or self.storage.is_encrypted()
or self.storage.needs_consolidation()
):
if self.storage.should_do_full_write_next():
self.write_and_force_consolidation()
else:
self._append_pending_changes()

View File

@@ -687,6 +687,11 @@ class SimpleConfig(Logger):
This can eliminate a serious privacy issue where a malicious user can track your spends by sending small payments
to a previously-paid address of yours that would then be included with unrelated inputs in your future payments."""),
)
WALLET_PARTIAL_WRITES = ConfigVar(
'wallet_partial_writes', default=False, type_=bool,
long_desc=lambda: _("""Allows partial updates to be written to disk for the wallet DB.
If disabled, the full wallet file is written to disk for every change. Experimental."""),
)
FX_USE_EXCHANGE_RATE = ConfigVar('use_exchange_rate', default=False, type_=bool)
FX_CURRENCY = ConfigVar('currency', default='EUR', type_=str)

View File

@@ -64,11 +64,17 @@ class WalletStorage(Logger):
# TODO maybe split this into separate create() and open() classmethods, to prevent some bugs.
# Until then, the onus is on the caller to check file_exists().
def __init__(self, path):
def __init__(
self,
path,
*,
allow_partial_writes: bool = False,
):
Logger.__init__(self)
self.path = standardize_path(path)
self._file_exists = bool(self.path and os.path.exists(self.path))
self.logger.info(f"wallet path {self.path}")
self._allow_partial_writes = allow_partial_writes
self.pubkey = None
self.decrypted = ''
try:
@@ -112,6 +118,7 @@ class WalletStorage(Logger):
def append(self, data: str) -> None:
""" append data to file. for the moment, only non-encrypted file"""
assert self._allow_partial_writes
assert not self.is_encrypted()
with open(self.path, "rb+") as f:
pos = f.seek(0, os.SEEK_END)
@@ -122,9 +129,18 @@ class WalletStorage(Logger):
f.flush()
os.fsync(f.fileno())
def needs_consolidation(self):
def _needs_consolidation(self):
return self.pos > 2 * self.init_pos
def should_do_full_write_next(self) -> bool:
"""If false, next action can be a partial-write ('append')."""
return (
not self.file_exists()
or self.is_encrypted()
or self._needs_consolidation()
or not self._allow_partial_writes
)
def file_exists(self) -> bool:
return self._file_exists

View File

@@ -4189,7 +4189,7 @@ def create_new_wallet(
gap_limit: Optional[int] = None
) -> dict:
"""Create a new wallet"""
storage = WalletStorage(path)
storage = WalletStorage(path, allow_partial_writes=config.WALLET_PARTIAL_WRITES)
if storage.file_exists():
raise UserFacingException("Remove the existing wallet first!")
db = WalletDB('', storage=storage, upgrade=True)
@@ -4226,7 +4226,7 @@ def restore_wallet_from_text(
if path is None: # create wallet in-memory
storage = None
else:
storage = WalletStorage(path)
storage = WalletStorage(path, allow_partial_writes=config.WALLET_PARTIAL_WRITES)
if storage.file_exists():
raise UserFacingException("Remove the existing wallet first!")
if encrypt_file is None:

View File

@@ -1289,7 +1289,13 @@ class WalletDB(JsonDB):
storage: Optional['WalletStorage'] = None,
upgrade: bool = False,
):
JsonDB.__init__(self, s, storage=storage, encoder=MyEncoder, upgrader=partial(upgrade_wallet_db, do_upgrade=upgrade))
JsonDB.__init__(
self,
s,
storage=storage,
encoder=MyEncoder,
upgrader=partial(upgrade_wallet_db, do_upgrade=upgrade),
)
# create pointers
self.load_transactions()
# load plugins that are conditional on wallet type