upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/util.h
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-07-30 11:27:49 -0300
committerGitHub <noreply@github.com>2026-07-30 11:27:49 -0300
commit99f7c1dfebb1a15dc6f77a338a8b0aaa3962564b (patch)
tree1e5a5bae21f75600d945efb3f56de68fdd33bce1 /src/util.h
parentde884e493145bd5cbd1d7a049968edcd110391af (diff)
wallet: make wallet.dat a file you can actually back up (#46)
* wallet: make wallet.dat a file you can actually back up Issue #40: a wallet.dat copied out on its own will not open anywhere else. The file is intact -- same bytes, same keys -- and the node refuses it with "Db::put: Invalid argument", thrown from a path that aborted the process. The reporter believes they lost funds to this, having saved the one file anybody would think to save. The chain, from the bottom: _endthread() is pthread_exit() on POSIX, and glibc implements that by throwing abi::__forced_unwind through the stack. That is not a real exception; catching it without rethrowing is a fatal error by contract. CATCH_PRINT_EXCEPTION ended in a bare catch(...), so every Linux shutdown ended: UNKNOWN EXCEPTION bitflash-node in ThreadSocketHandler() FATAL: exception not rethrown An abort, mid-shutdown, so nothing after StopNode() ran. Which mattered because Berkeley DB stamps every page with a log sequence number belonging to the environment that wrote it. Carried elsewhere, those numbers point at logs that do not exist and BDB refuses to write. DBFlush(true) calls lsn_reset, which is precisely the cure -- and DBFlush has been declared since 2009 and called from nowhere. Even had it been called, the abort stood in front of it. So the wallet was never released from its directory, and the one file users copy was the one file that could not travel. Three fixes, each covering a different case: - Rethrow abi::__forced_unwind, and call DBFlush(true) on both shutdown paths. Shutdown completes, and wallet.dat is left portable by construction. - Adopt orphaned files on open: a freshly created database environment beside pre-existing .dat files means they came from elsewhere, so reset their sequence numbers. This is what recovers wallets already stranded by older builds -- including, if they still hold the file, the reporter's. - BackupWallet(), reachable as /backupwallet=FILE, which checkpoints, copies, and clears the copy's sequence numbers. Bitcoin 0.1.0 had a backup command; this tree lost it in the wxWidgets removal and has shipped with no way to take a backup since. DBFlush's own txn_checkpoint and lsn_reset are wrapped too -- they throw, and throwing on the way out is how this started. Verified on Linux and Windows: shutdown exits 0 rather than 134, DBFlush runs, and a lone wallet.dat opens in an empty directory with no exceptions and byte-identical key records (defaultkey, key, name, setting, version) before and after. Not addressed here, and the larger half of #40: keys are generated as they are needed rather than derived from a seed, so any backup is a snapshot and coins paid to addresses created after it are not in it. /backupwallet says so on every run. The real answer is deterministic derivation, which is a wallet format change and its own piece of work. * README: document what a wallet backup actually requires The PR description claimed this section existed and it did not. Covers the two things that cost money and are not obvious: a lone wallet.dat is refused elsewhere because Berkeley DB ties it to the database/ directory beside it, and every backup is a snapshot because keys are made on demand rather than derived from a seed.
Diffstat (limited to 'src/util.h')
-rw-r--r--src/util.h32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/util.h b/src/util.h
index 797ed27..25ef118 100644
--- a/src/util.h
+++ b/src/util.h
@@ -3,6 +3,11 @@
3// file license.txt or http://www.opensource.org/licenses/mit-license.php. 3// file license.txt or http://www.opensource.org/licenses/mit-license.php.
4 4
5 5
6// For abi::__forced_unwind, which CATCH_PRINT_EXCEPTION below must let past.
7#ifndef _WIN32
8#include <cxxabi.h>
9#endif
10
6#if defined(_MSC_VER) || defined(__BORLANDC__) 11#if defined(_MSC_VER) || defined(__BORLANDC__)
7typedef __int64 int64; 12typedef __int64 int64;
8typedef unsigned __int64 uint64; 13typedef unsigned __int64 uint64;
@@ -419,7 +424,34 @@ inline void heapchk()
419 } \ 424 } \
420 } 425 }
421 426
427// The forced-unwind arm has to come first, and it has to rethrow.
428//
429// On POSIX, _endthread() is pthread_exit(), and glibc implements that by
430// throwing abi::__forced_unwind through the stack. It is not a real exception
431// and catching it without rethrowing is a fatal error by contract: glibc
432// notices and calls abort().
433//
434// The bare catch(...) below did exactly that. Every shutdown on Linux died with
435//
436// UNKNOWN EXCEPTION bitflash-node in ThreadSocketHandler()
437// FATAL: exception not rethrown
438//
439// which looked like nothing worse than an ugly exit, and was not: the process
440// aborted partway through shutting down, so nothing after StopNode() ran. See
441// main_gui.cpp, where DBFlush(true) is now called -- it could never have
442// worked while this abort stood in front of it. Windows is unaffected; there
443// _endthread() is the CRT's and unwinds nothing.
444#ifdef _WIN32
445#define CATCH_FORCED_UNWIND
446#else
447#define CATCH_FORCED_UNWIND \
448 catch (abi::__forced_unwind&) { \
449 throw; \
450 }
451#endif
452
422#define CATCH_PRINT_EXCEPTION(pszFn) \ 453#define CATCH_PRINT_EXCEPTION(pszFn) \
454 CATCH_FORCED_UNWIND \
423 catch (std::exception& e) { \ 455 catch (std::exception& e) { \
424 PrintException(&e, (pszFn)); \ 456 PrintException(&e, (pszFn)); \
425 } catch (...) { \ 457 } catch (...) { \