diff options
| author | Bitflash <btf01@proton.me> | 2026-07-29 17:41:10 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-29 17:41:10 -0300 |
| commit | 72683da2b01286030fd61228168712c84aea79d4 (patch) | |
| tree | 0b299e830282e483a1642e3b2ca38fb68507e646 /src/db.cpp | |
| parent | c404e1c7f5d3f04d07e7e5b35a7a8ffcc998d855 (diff) | |
db: catch BDB exceptions so unreadable files fail cleanly (#34) (#38)
Berkeley DB throws when it cannot open a file written by
another platform's build (e.g. BDB 5.3 wallet read by 6.2).
Nothing on the call path caught these, so the exception
unwound into std::terminate -- surfacing on Windows as
STATUS_STACK_BUFFER_OVERRUN (0xC0000409) with no useful
message.
Three layers of defense:
1. main_gui.cpp: try/catch around LoadBlockIndex() and
LoadWallet(), printing the reason and exiting cleanly.
2. db.cpp LoadWallet: try/catch around the record-reading
loop (Satoshi's 2009 TODO), tracking the last record type
so the error message says what it choked on.
3. db.cpp cleanup paths: every dbenv.close(), pdb->close(),
txn_checkpoint() and log_archive() wrapped in try/catch
so the environment teardown after a failed open does not
throw a second exception into std::terminate.
Tested: cross-platform wallet (Linux BDB 5.3 → Windows 6.2)
now prints a clear message and exits with code 1, wallet
left untouched.
Closes #34
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/db.cpp')
| -rw-r--r-- | src/db.cpp | 47 |
1 files changed, 41 insertions, 6 deletions
| @@ -28,7 +28,8 @@ public: | |||
| 28 | { | 28 | { |
| 29 | if (fDbEnvInit) | 29 | if (fDbEnvInit) |
| 30 | { | 30 | { |
| 31 | dbenv.close(0); | 31 | try { dbenv.close(0); } |
| 32 | catch (...) { } | ||
| 32 | fDbEnvInit = false; | 33 | fDbEnvInit = false; |
| 33 | } | 34 | } |
| 34 | } | 35 | } |
| @@ -118,10 +119,10 @@ void CDB::Close() | |||
| 118 | if (!vTxn.empty()) | 119 | if (!vTxn.empty()) |
| 119 | vTxn.front()->abort(); | 120 | vTxn.front()->abort(); |
| 120 | vTxn.clear(); | 121 | vTxn.clear(); |
| 121 | pdb->close(0); | 122 | try { pdb->close(0); } catch (...) { } |
| 122 | delete pdb; | 123 | delete pdb; |
| 123 | pdb = NULL; | 124 | pdb = NULL; |
| 124 | dbenv.txn_checkpoint(0, 0, 0); | 125 | try { dbenv.txn_checkpoint(0, 0, 0); } catch (...) { } |
| 125 | 126 | ||
| 126 | CRITICAL_BLOCK(cs_db) | 127 | CRITICAL_BLOCK(cs_db) |
| 127 | --mapFileUseCount[strFile]; | 128 | --mapFileUseCount[strFile]; |
| @@ -154,8 +155,8 @@ void DBFlush(bool fShutdown) | |||
| 154 | { | 155 | { |
| 155 | char** listp; | 156 | char** listp; |
| 156 | if (mapFileUseCount.empty()) | 157 | if (mapFileUseCount.empty()) |
| 157 | dbenv.log_archive(&listp, DB_ARCH_REMOVE); | 158 | try { dbenv.log_archive(&listp, DB_ARCH_REMOVE); } catch (...) { } |
| 158 | dbenv.close(0); | 159 | try { dbenv.close(0); } catch (...) { } |
| 159 | fDbEnvInit = false; | 160 | fDbEnvInit = false; |
| 160 | } | 161 | } |
| 161 | } | 162 | } |
| @@ -427,7 +428,25 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | |||
| 427 | { | 428 | { |
| 428 | vchDefaultKeyRet.clear(); | 429 | vchDefaultKeyRet.clear(); |
| 429 | 430 | ||
| 430 | //// todo: shouldn't we catch exceptions and try to recover and continue? | 431 | // Satoshi's "todo: shouldn't we catch exceptions" sat here since 2009, and |
| 432 | // it was not a nicety. Nothing on this path caught anything, so a record | ||
| 433 | // this build could not read -- a wallet from another platform's Berkeley | ||
| 434 | // DB, a truncated file, a field it does not understand -- threw out of | ||
| 435 | // LoadWallet, out of main(), into std::terminate and abort(). On Windows | ||
| 436 | // that surfaces as STATUS_STACK_BUFFER_OVERRUN (0xC0000409) in | ||
| 437 | // ucrtbase.dll: no message, no log line past "Loading wallet...", and a | ||
| 438 | // faulting module that has nothing to do with the actual problem. | ||
| 439 | // | ||
| 440 | // The program knew exactly what had gone wrong and threw the reason away. | ||
| 441 | // Now it says so and stops cleanly, which is the difference between "your | ||
| 442 | // wallet is unreadable and here is why" and a crash nobody can act on. | ||
| 443 | // | ||
| 444 | // Deliberately a hard failure rather than skipping the bad record: a | ||
| 445 | // partially loaded wallet is worse than one that refuses to open, because | ||
| 446 | // the balance looks plausible while keys or transactions are missing. | ||
| 447 | string strLastType; | ||
| 448 | try | ||
| 449 | { | ||
| 431 | CRITICAL_BLOCK(cs_mapKeys) | 450 | CRITICAL_BLOCK(cs_mapKeys) |
| 432 | CRITICAL_BLOCK(cs_mapWallet) | 451 | CRITICAL_BLOCK(cs_mapWallet) |
| 433 | { | 452 | { |
| @@ -452,6 +471,7 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | |||
| 452 | // is just the two items serialized one after the other | 471 | // is just the two items serialized one after the other |
| 453 | string strType; | 472 | string strType; |
| 454 | ssKey >> strType; | 473 | ssKey >> strType; |
| 474 | strLastType = strType; | ||
| 455 | if (strType == "name") | 475 | if (strType == "name") |
| 456 | { | 476 | { |
| 457 | string strAddress; | 477 | string strAddress; |
| @@ -509,6 +529,21 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | |||
| 509 | } | 529 | } |
| 510 | } | 530 | } |
| 511 | } | 531 | } |
| 532 | } | ||
| 533 | catch (const std::exception& e) | ||
| 534 | { | ||
| 535 | printf("LoadWallet: wallet.dat could not be read. Failed while handling " | ||
| 536 | "a '%s' record: %s\n", strLastType.c_str(), e.what()); | ||
| 537 | printf("LoadWallet: the file was left untouched. A wallet.dat written by " | ||
| 538 | "a different platform's Berkeley DB is the usual cause.\n"); | ||
| 539 | return false; | ||
| 540 | } | ||
| 541 | catch (...) | ||
| 542 | { | ||
| 543 | printf("LoadWallet: wallet.dat could not be read. Unknown failure while " | ||
| 544 | "handling a '%s' record.\n", strLastType.c_str()); | ||
| 545 | return false; | ||
| 546 | } | ||
| 512 | 547 | ||
| 513 | printf("nTransactionFee = %lld\n", nTransactionFee); | 548 | printf("nTransactionFee = %lld\n", nTransactionFee); |
| 514 | printf("addrIncoming = %s\n", addrIncoming.ToString().c_str()); | 549 | printf("addrIncoming = %s\n", addrIncoming.ToString().c_str()); |