upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md31
-rw-r--r--src/db.cpp134
-rw-r--r--src/db.h6
-rw-r--r--src/main_gui.cpp32
-rw-r--r--src/util.h32
5 files changed, 232 insertions, 3 deletions
diff --git a/README.md b/README.md
index 1720c46..da8183a 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,37 @@ backup — it holds your keys.
69 69
70--- 70---
71 71
72## Backing up your wallet
73
74Two things about this wallet will cost you money if you do not know them. Both
75are consequences of the 0.1.0 wallet format, and neither is obvious.
76
77**Copying `wallet.dat` on its own is not a backup.** Berkeley DB ties the file
78to the environment in the `database/` subdirectory beside it, so a lone
79`wallet.dat` will not open elsewhere — the keys are all still in there, and the
80file is refused anyway. Use the built-in command, which writes a copy that
81stands on its own:
82
83```
84bitflash -backupwallet=/path/to/wallet-backup.dat
85```
86
87It loads the wallet, writes the copy, and exits without starting the node. If
88you would rather copy by hand, shut the node down first and take the **whole**
89data directory, not just `wallet.dat`.
90
91**Every backup is a snapshot, so repeat it.** Keys are generated when they are
92needed rather than derived from a seed, so a backup contains only the keys that
93existed the moment it was taken. Mine a block or receive to a new address
94afterwards, and those coins sit on-chain at an address whose private key is in
95no backup you hold. Back up again whenever you use a new address.
96
97The second point is the pre-BIP32 problem that deterministic wallets were
98invented to solve; see [#47](https://github.com/Bitflash-sh/bitflash/issues/47).
99Until that lands, repetition is the only defence.
100
101---
102
72## Mining 103## Mining
73 104
74Open **Options** from the menu bar. Under Mining Mode: 105Open **Options** from the menu bar. Under Mining Mode:
diff --git a/src/db.cpp b/src/db.cpp
index d43e4ef..fc5f180 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -59,7 +59,12 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
59 { 59 {
60 string strAppDir = GetAppDir(); 60 string strAppDir = GetAppDir();
61 string strLogDir = strAppDir + "/database"; 61 string strLogDir = strAppDir + "/database";
62 _mkdir(strLogDir.c_str()); 62
63 // _mkdir returns 0 only when it actually created the directory, so
64 // this is also the test for "this environment did not exist until
65 // now". See the recovery block after dbenv.open() for why we care.
66 bool fFreshEnv = (_mkdir(strLogDir.c_str()) == 0);
67
63 printf("dbenv.open strAppDir=%s\n", strAppDir.c_str()); 68 printf("dbenv.open strAppDir=%s\n", strAppDir.c_str());
64 69
65 dbenv.set_lg_dir(strLogDir.c_str()); 70 dbenv.set_lg_dir(strLogDir.c_str());
@@ -81,6 +86,41 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
81 if (ret > 0) 86 if (ret > 0)
82 throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); 87 throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret));
83 fDbEnvInit = true; 88 fDbEnvInit = true;
89
90 // A Berkeley DB file records, in every page header, a log sequence
91 // number belonging to the environment that wrote it. Carried into a
92 // different environment those numbers refer to logs that do not
93 // exist, and BDB refuses to write: "Db::put: Invalid argument",
94 // thrown from a path that used to abort the process.
95 //
96 // That is what happens when someone backs up wallet.dat on its own.
97 // It is the obviously important file, it is the only one people
98 // copy, and the environment it is welded to lives in a database/
99 // subdirectory nobody has reason to suspect. The file is intact --
100 // same bytes, same keys -- and the wallet still will not open.
101 // Issue #40 reports exactly this, after the fact.
102 //
103 // lsn_reset() exists for precisely this migration, so do it for
104 // them. A fresh environment beside pre-existing .dat files means
105 // those files came from somewhere else; in the normal case the
106 // environment is already there and none of this runs.
107 if (fFreshEnv)
108 {
109 static const char* pszBdbFiles[] = { "wallet.dat", "blkindex.dat" };
110 for (int i = 0; i < (int)(sizeof(pszBdbFiles)/sizeof(pszBdbFiles[0])); i++)
111 {
112 const char* pszName = pszBdbFiles[i];
113 if (!FileExists((strAppDir + "/" + pszName).c_str()))
114 continue;
115 try
116 {
117 if (dbenv.lsn_reset(pszName, 0) == 0)
118 printf("CDB() : adopted %s from another environment "
119 "(log sequence numbers reset)\n", pszName);
120 }
121 catch (...) { } // best effort; the open below reports real trouble
122 }
123 }
84 } 124 }
85 125
86 strFile = pszFile; 126 strFile = pszFile;
@@ -137,7 +177,14 @@ void DBFlush(bool fShutdown)
137 printf("DBFlush(%s)\n", fShutdown ? "true" : "false"); 177 printf("DBFlush(%s)\n", fShutdown ? "true" : "false");
138 CRITICAL_BLOCK(cs_db) 178 CRITICAL_BLOCK(cs_db)
139 { 179 {
140 dbenv.txn_checkpoint(0, 0, 0); 180 // This runs on the way out. Anything that throws here and is not caught
181 // takes the process down before the wallet has been put down cleanly,
182 // which is the difference between a portable wallet.dat and one welded
183 // to this directory -- the lsn_reset just below is what frees it.
184 try { dbenv.txn_checkpoint(0, 0, 0); }
185 catch (const std::exception& e)
186 { printf("DBFlush() : checkpoint failed: %s\n", e.what()); }
187
141 map<string, int>::iterator mi = mapFileUseCount.begin(); 188 map<string, int>::iterator mi = mapFileUseCount.begin();
142 while (mi != mapFileUseCount.end()) 189 while (mi != mapFileUseCount.end())
143 { 190 {
@@ -145,7 +192,9 @@ void DBFlush(bool fShutdown)
145 int nRefCount = (*mi).second; 192 int nRefCount = (*mi).second;
146 if (nRefCount == 0) 193 if (nRefCount == 0)
147 { 194 {
148 dbenv.lsn_reset(strFile.c_str(), 0); 195 try { dbenv.lsn_reset(strFile.c_str(), 0); }
196 catch (const std::exception& e)
197 { printf("DBFlush() : lsn_reset(%s) failed: %s\n", strFile.c_str(), e.what()); }
149 mapFileUseCount.erase(mi++); 198 mapFileUseCount.erase(mi++);
150 } 199 }
151 else 200 else
@@ -580,3 +629,82 @@ bool LoadWallet()
580 629
581 return true; 630 return true;
582} 631}
632
633
634// Write a copy of wallet.dat that can be opened anywhere.
635//
636// A plain file copy is not a backup here. Berkeley DB stamps every page with a
637// log sequence number tied to the environment that wrote it, so the copy only
638// works beside the database/ directory it grew up with -- which is why backing
639// up "just wallet.dat", the one file anybody would think to save, produces
640// something that will not open. See the recovery block in CDB::CDB and #40.
641//
642// So: flush outstanding writes, copy, then clear the copy's log sequence
643// numbers. What lands on disk is a wallet.dat that stands on its own.
644//
645// This is still a point-in-time snapshot. Keys are generated as they are
646// needed, not derived from a seed, so coins paid to an address created after
647// this file was written are not spendable from it. Backing up once is not
648// enough, and that is a property of the wallet format rather than of this
649// function.
650bool BackupWallet(const string& strDest)
651{
652 if (strDest.empty())
653 return error("BackupWallet() : no destination given\n");
654
655 string strSrc = GetAppDir() + "/wallet.dat";
656 if (!FileExists(strSrc.c_str()))
657 return error("BackupWallet() : %s does not exist\n", strSrc.c_str());
658
659 CRITICAL_BLOCK(cs_db)
660 {
661 // Committing first, so the copy is not missing the newest records.
662 // Both calls can throw, and a failed backup must not take the node
663 // down with it.
664 try { dbenv.txn_checkpoint(0, 0, 0); }
665 catch (const std::exception& e)
666 { return error("BackupWallet() : checkpoint failed: %s\n", e.what()); }
667
668 try
669 {
670 FILE* pfIn = fopen(strSrc.c_str(), "rb");
671 if (!pfIn)
672 return error("BackupWallet() : cannot read %s\n", strSrc.c_str());
673 FILE* pfOut = fopen(strDest.c_str(), "wb");
674 if (!pfOut)
675 {
676 fclose(pfIn);
677 return error("BackupWallet() : cannot write %s\n", strDest.c_str());
678 }
679
680 char buf[65536];
681 size_t n;
682 bool fOk = true;
683 while ((n = fread(buf, 1, sizeof(buf), pfIn)) > 0)
684 if (fwrite(buf, 1, n, pfOut) != n) { fOk = false; break; }
685 if (ferror(pfIn))
686 fOk = false;
687 fclose(pfIn);
688 // Flushed and closed before lsn_reset touches it.
689 if (fclose(pfOut) != 0)
690 fOk = false;
691
692 if (!fOk)
693 {
694 remove(strDest.c_str()); // half a wallet is worse than none
695 return error("BackupWallet() : copy to %s failed\n", strDest.c_str());
696 }
697
698 // Without this the copy is welded to this node's database/ dir.
699 int ret = dbenv.lsn_reset(strDest.c_str(), 0);
700 if (ret != 0)
701 printf("BackupWallet() : warning -- lsn_reset returned %d; the copy "
702 "may only open beside this node's database/ directory\n", ret);
703 }
704 catch (const std::exception& e)
705 { return error("BackupWallet() : %s\n", e.what()); }
706 }
707
708 printf("BackupWallet() : wrote %s\n", strDest.c_str());
709 return true;
710}
diff --git a/src/db.h b/src/db.h
index 6dc3a4f..987786c 100644
--- a/src/db.h
+++ b/src/db.h
@@ -403,6 +403,12 @@ public:
403 403
404bool LoadWallet(); 404bool LoadWallet();
405 405
406// Writes a wallet.dat that opens on its own, anywhere -- see the definition in
407// db.cpp for why a plain file copy does not. Still a point-in-time snapshot:
408// keys are made as needed rather than derived from a seed, so coins paid to an
409// address created after this call are not spendable from the file it writes.
410bool BackupWallet(const string& strDest);
411
406inline bool SetAddressBookName(const string& strAddress, const string& strName) 412inline bool SetAddressBookName(const string& strAddress, const string& strName)
407{ 413{
408 return CWalletDB().WriteName(strAddress, strName); 414 return CWalletDB().WriteName(strAddress, strName);
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index c64dca9..976cd42 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -67,6 +67,10 @@ static void PrintUsage()
67 printf(" /port=N (P2P listen port, default 8433)\n"); 67 printf(" /port=N (P2P listen port, default 8433)\n");
68 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); 68 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n");
69 printf("\n"); 69 printf("\n");
70 printf("Wallet:\n");
71 printf(" /backupwallet=FILE (write a wallet.dat that opens on its own,\n");
72 printf(" then exit; run it again after new addresses)\n");
73 printf("\n");
70 printf("Each option also accepts '-' instead of '/'.\n"); 74 printf("Each option also accepts '-' instead of '/'.\n");
71} 75}
72 76
@@ -219,6 +223,28 @@ int main(int argc, char* argv[])
219 return 1; 223 return 1;
220 } 224 }
221 printf("Height=%d\n", nBestHeight); 225 printf("Height=%d\n", nBestHeight);
226
227 // Before the node opens sockets or touches anything: the wallet is loaded,
228 // which is all a backup needs, and finishing here means the copy is taken
229 // from a quiet directory rather than from under a running node.
230 string strBackup = argval2(argc, argv, "/backupwallet", "-backupwallet");
231 if (!strBackup.empty())
232 {
233 bool fOk = BackupWallet(strBackup);
234 if (fOk)
235 {
236 printf("Backed up to %s\n", strBackup.c_str());
237 printf("This file opens on its own -- it does not need the database/ "
238 "directory beside it.\n");
239 printf("It is a snapshot: coins paid to addresses created after now are "
240 "not in it, so back up again whenever you receive to a new address.\n");
241 }
242 else
243 fprintf(stderr, "Backup failed -- nothing was written.\n");
244 DBFlush(true);
245 return fOk ? 0 : 1;
246 }
247
222 ReacceptWalletTransactions(); 248 ReacceptWalletTransactions();
223 249
224 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } 250 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; }
@@ -249,6 +275,11 @@ int main(int argc, char* argv[])
249 printf("Running headless. Ctrl-C to stop.\n"); 275 printf("Running headless. Ctrl-C to stop.\n");
250 while (!fShutdown) Sleep(500); 276 while (!fShutdown) Sleep(500);
251 StopNode(); 277 StopNode();
278 // Checkpoints, releases wallet.dat from this directory's Berkeley DB
279 // environment, and closes it. Declared since 2009 and never once
280 // called here, which is why a wallet.dat copied elsewhere would not
281 // open -- issue #40. Not optional.
282 DBFlush(true);
252 return 0; 283 return 0;
253 } 284 }
254 285
@@ -258,6 +289,7 @@ int main(int argc, char* argv[])
258 int ret = RunGUI(argc, argv); 289 int ret = RunGUI(argc, argv);
259 fShutdown = true; 290 fShutdown = true;
260 StopNode(); 291 StopNode();
292 DBFlush(true);
261 return ret; 293 return ret;
262#endif 294#endif
263} 295}
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 (...) { \