wallet_db: add configvar for partial_writes, disable by default
Adds a new configvar `WALLET_PARTIAL_WRITES` to enable/disable partial writes for the walletDB. This is a further restriction on top of the existing restrictions, e.g. wallet files still need to have file encryption disabled for partial writes. It defaults to off, so even for unencrypted wallets we disable partial writes for now. This is used as a stopgap measure until we fix the issues found with the partial writes impl (see https://github.com/spesmilo/electrum/issues/10000).
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -151,7 +151,7 @@ def init_cmdline(config_options, wallet_path, *, rpcserver: bool, config: 'Simpl
|
||||
sys_exit(1)
|
||||
|
||||
# instantiate wallet for command-line
|
||||
storage = WalletStorage(wallet_path) if wallet_path else None
|
||||
storage = WalletStorage(wallet_path, allow_partial_writes=config.WALLET_PARTIAL_WRITES) if wallet_path else None
|
||||
|
||||
if cmd.requires_wallet and not storage.file_exists():
|
||||
print_msg("Error: Wallet file not found.")
|
||||
@@ -234,7 +234,7 @@ async def run_offline_command(config: 'SimpleConfig', config_options: dict, wall
|
||||
if 'wallet_path' in cmd.options and config_options.get('wallet_path') is None:
|
||||
config_options['wallet_path'] = wallet_path
|
||||
if cmd.requires_wallet:
|
||||
storage = WalletStorage(wallet_path)
|
||||
storage = WalletStorage(wallet_path, allow_partial_writes=config.WALLET_PARTIAL_WRITES)
|
||||
if storage.is_encrypted():
|
||||
if storage.is_encrypted_with_hw_device():
|
||||
password = get_password_for_hw_device_encrypted_storage(plugins)
|
||||
|
||||
Reference in New Issue
Block a user