From 440d7f393d74f309d29e1d10ba1f11c41c869226 Mon Sep 17 00:00:00 2001 From: bitflash Date: Tue, 18 Aug 2026 14:33:40 -0300 Subject: wallet: bracket WriteHDMaster in a SQLite runtime transaction The SQLite runtime wrote each record as its own autocommit, so a group that must land together -- hdmaster and hdchaincode, the two halves of an HD seed -- could be left half-written by a failure or crash between them, the incomplete-seed state -walletstoragecheck exists to catch. Berkeley DB was no worse (it is not transactional here either) but no better. Add WalletSQLiteRuntimeBeginTxn/CommitTxn/RollbackTxn -- no-ops that succeed when the SQLite backend is not active, so Berkeley DB keeps its existing per-write behaviour -- and wrap WriteHDMaster in them so the seed lands all-or-nothing under SQLite. The one BDB-atomic wallet rewrite, the encrypt path, is already matched by its own transaction in the SQLite encrypt branch; every other wallet multi-write is ordering-based on both backends by design. wallet-sqlite self-test now proves the bracket: a rolled-back runtime write leaves no record, a committed one persists, and WriteHDMaster lands both halves of the seed. --- src/db.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'src/db.h') diff --git a/src/db.h b/src/db.h index fee5a82..1a272b9 100644 --- a/src/db.h +++ b/src/db.h @@ -365,6 +365,9 @@ bool WalletSQLiteRuntimeWriteRecord(const vector& vchKey, const vector& vchValue, bool fOverwrite); bool WalletSQLiteRuntimeEraseRecord(const vector& vchKey); +bool WalletSQLiteRuntimeBeginTxn(); +bool WalletSQLiteRuntimeCommitTxn(); +void WalletSQLiteRuntimeRollbackTxn(); @@ -531,8 +534,21 @@ public: bool WriteHDMaster(const vector& vchMaster, const vector& vchChainCode) { - return WriteWalletRecord(string("hdmaster"), vchMaster) - && WriteWalletRecord(string("hdchaincode"), vchChainCode); + // hdmaster and hdchaincode are halves of one seed: a wallet with one but + // not the other is an incomplete HD wallet (-walletstoragecheck flags + // exactly that). Under the SQLite runtime write them in one transaction + // so a failure or crash between the two cannot leave that half-state. + // Berkeley DB keeps its existing per-write behaviour -- the bracket is a + // no-op there. + if (!WalletSQLiteRuntimeBeginTxn()) + return false; + if (!WriteWalletRecord(string("hdmaster"), vchMaster) || + !WriteWalletRecord(string("hdchaincode"), vchChainCode)) + { + WalletSQLiteRuntimeRollbackTxn(); + return false; + } + return WalletSQLiteRuntimeCommitTxn(); } bool WriteWalletMinVersion(int nVersion) -- cgit v1.2.3