diff options
| author | bitflash <dev@bitflash.network> | 2026-08-18 14:33:40 -0300 |
|---|---|---|
| committer | bitflash <dev@bitflash.network> | 2026-08-18 14:33:40 -0300 |
| commit | 440d7f393d74f309d29e1d10ba1f11c41c869226 (patch) | |
| tree | 40c881b79ba7a2571cfd2bcfbb274031a5739071 /src/selftest.cpp | |
| parent | e58c4022e0ddd137b93cdb3464753bef5df1d456 (diff) | |
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.
Diffstat (limited to 'src/selftest.cpp')
| -rw-r--r-- | src/selftest.cpp | 37 |
1 files changed, 37 insertions, 0 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp index fc10947..7ad5a53 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -1231,6 +1231,43 @@ static int RunWalletSQLiteSelfTest() | |||
| 1231 | nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()), | 1231 | nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()), |
| 1232 | "active SQLite runtime does not create wallet.dat") ? 0 : 1; | 1232 | "active SQLite runtime does not create wallet.dat") ? 0 : 1; |
| 1233 | 1233 | ||
| 1234 | // Transaction bracket: a rolled-back group leaves nothing, a | ||
| 1235 | // committed group persists. This is the all-or-nothing that | ||
| 1236 | // WriteHDMaster relies on so a half-written seed cannot survive | ||
| 1237 | // a failure between its two records. | ||
| 1238 | bool fTxnBegan = WalletSQLiteRuntimeBeginTxn(); | ||
| 1239 | bool fTxnWrote = fTxnBegan && | ||
| 1240 | CWalletDB().WriteSetting("runtime-txn-rollback", (int64)111); | ||
| 1241 | if (fTxnBegan) | ||
| 1242 | WalletSQLiteRuntimeRollbackTxn(); | ||
| 1243 | nFail += Check(fTxnBegan && fTxnWrote, | ||
| 1244 | "a SQLite runtime transaction opens and rolls back") ? 0 : 1; | ||
| 1245 | int64 nRolledBack = 0; | ||
| 1246 | nFail += Check(!CWalletDB("r").ReadSetting("runtime-txn-rollback", | ||
| 1247 | nRolledBack), | ||
| 1248 | "a rolled-back SQLite runtime write leaves no record") ? 0 : 1; | ||
| 1249 | |||
| 1250 | bool fCommitOk = WalletSQLiteRuntimeBeginTxn() && | ||
| 1251 | CWalletDB().WriteSetting("runtime-txn-commit", (int64)222) && | ||
| 1252 | WalletSQLiteRuntimeCommitTxn(); | ||
| 1253 | int64 nCommitted = 0; | ||
| 1254 | nFail += Check(fCommitOk && | ||
| 1255 | CWalletDB("r").ReadSetting("runtime-txn-commit", | ||
| 1256 | nCommitted) && | ||
| 1257 | nCommitted == 222, | ||
| 1258 | "a committed SQLite runtime write persists") ? 0 : 1; | ||
| 1259 | |||
| 1260 | // WriteHDMaster brackets its two records in that transaction; | ||
| 1261 | // both halves of the seed must land. | ||
| 1262 | vector<unsigned char> vchTxnMaster(32, 0x5a); | ||
| 1263 | vector<unsigned char> vchTxnChainCode(32, 0xa5); | ||
| 1264 | vector<unsigned char> vchReadMaster, vchReadChainCode; | ||
| 1265 | nFail += Check(CWalletDB().WriteHDMaster(vchTxnMaster, vchTxnChainCode) && | ||
| 1266 | CWalletDB("r").ReadHDMaster(vchReadMaster, vchReadChainCode) && | ||
| 1267 | vchReadMaster == vchTxnMaster && | ||
| 1268 | vchReadChainCode == vchTxnChainCode, | ||
| 1269 | "WriteHDMaster commits both halves of the seed under SQLite") ? 0 : 1; | ||
| 1270 | |||
| 1234 | // Durability: close the backend (checkpoints the WAL), reopen | 1271 | // Durability: close the backend (checkpoints the WAL), reopen |
| 1235 | // the same file, and confirm what CWalletDB wrote is still | 1272 | // the same file, and confirm what CWalletDB wrote is still |
| 1236 | // there. This is the write-survives-a-restart property the real | 1273 | // there. This is the write-survives-a-restart property the real |