diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 17:14:12 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-01 17:14:12 +0000 |
| commit | b4f0bc693ef7175702f82bcb2110b627922e8cbd (patch) | |
| tree | 73435feb1a076dfba94d8143d7d7b7784908c82f /src/db.cpp | |
| parent | a1d867ecbcfc9e218408d0a11863a450243a01f9 (diff) | |
| parent | 6aa3dade77632f13189d2a3ef29897f29a5f9572 (diff) | |
Merge upstream v1.2.20 into the fork
Merges upstream tag v1.2.20 (6aa3dade) into our 1.2.19+sec tree. The merge
base is bc3de30's descendant 9e39a50 -- upstream's own v1.2.19 -- so this is
an ordinary three-way merge, not a replay. Five conflict blocks in four
files, all resolved below.
No consensus change. CheckBlock, ConnectBlock, DisconnectBlock, ConnectInputs,
AcceptBlock, AcceptTransaction, GetNextWorkRequired and GetBlockValue hash
identical to v1.2.19; script.cpp and script.h are byte-identical on both
sides. main.cpp is +3 lines and main.h +2, all wallet-backend plumbing.
rpc.cpp is untouched, so the payout ledger arrives unchanged.
Makefile, VERSION. OURS, renumbered. 1.2.20+sec, for the same reason 1.2.19
carried +sec: this tree is not the upstream release of that number and the
string names every release artifact.
Makefile, AppImage recipe. OURS for the AppDir path, THEIRS for the bundled
library. Upstream's hunk reverts the icon paths to a hardcoded
/tmp/bitflash.AppDir, which is the local-user pre-placement hazard the fork
moved out of /tmp in the first place; taking their side wholesale would have
reintroduced it silently, inside a conflict about something else. The AppDir
stays $(APPDIR), and libsqlite3.so.0 joins the bundled list, which the new
backend genuinely needs.
src/Makefile, src/makefile.mingw, HEADERS. BOTH. Ours drops market.h,
theirs adds wallet_sqlite.h; the resolution does both. NODE_OBJS auto-merged
correctly in both files -- obj/wallet_sqlite.o present, obj/market.o still
gone -- and was verified rather than assumed, because that line has trapped
this fork before.
src/selftest.cpp, known-tests list. BOTH, union. Upstream adds eight
wallet-storage tests, we keep payout-ledger. The dispatch chain itself
auto-merged and still routes payout-ledger to RunPayoutLedgerSelfTest.
Hardening verified intact after merge: the TLS 1.2 floor in nostr.cpp, the
/rvrelay pin, opt-in managed Tor, strict sig3-on-onion descriptor
verification, and the marketplace removal. Upstream shipped no build logs or
__pycache__ this time; the three added files are docs/storage.md and
src/wallet_sqlite.{cpp,h}.
The SQLite backend this release introduces is NOT enabled here. An existing
wallet.dat resolves to Berkeley DB on every branch of the new selection
logic, and the conversion wizard is GUI-only and not compiled into the
headless node. The review findings that gate ever turning it on -- the
non-atomic backend marker, the connection-wide WriteHDMaster transaction,
and the encrypt-swap crash window -- are recorded on the tracker, not fixed
here.
Diffstat (limited to 'src/db.cpp')
| -rw-r--r-- | src/db.cpp | 1251 |
1 files changed, 1141 insertions, 110 deletions
| @@ -3,6 +3,7 @@ | |||
| 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 | #include "headers.h" | 5 | #include "headers.h" |
| 6 | #include "wallet_sqlite.h" | ||
| 6 | 7 | ||
| 7 | 8 | ||
| 8 | 9 | ||
| @@ -52,8 +53,34 @@ static bool fDbEnvInit = false; | |||
| 52 | // Why LoadWallet() refused, in words meant for the person who has to act on it. | 53 | // Why LoadWallet() refused, in words meant for the person who has to act on it. |
| 53 | // Empty unless the reason is one we can name. | 54 | // Empty unless the reason is one we can name. |
| 54 | string strWalletLoadError; | 55 | string strWalletLoadError; |
| 55 | DbEnv dbenv(0u); | 56 | static DbEnv* pdbenv = NULL; |
| 57 | static FILE* pdbErrFile = NULL; | ||
| 56 | static map<string, int> mapFileUseCount; | 58 | static map<string, int> mapFileUseCount; |
| 59 | static CWalletDBSQLite* pWalletSQLiteRuntime = NULL; | ||
| 60 | |||
| 61 | DbEnv& GetDbEnv() | ||
| 62 | { | ||
| 63 | if (!pdbenv) | ||
| 64 | pdbenv = new DbEnv(0u); | ||
| 65 | return *pdbenv; | ||
| 66 | } | ||
| 67 | |||
| 68 | static void CloseDbEnv() | ||
| 69 | { | ||
| 70 | if (pdbenv) | ||
| 71 | { | ||
| 72 | try { pdbenv->close(0); } | ||
| 73 | catch (...) { } | ||
| 74 | delete pdbenv; | ||
| 75 | pdbenv = NULL; | ||
| 76 | } | ||
| 77 | if (pdbErrFile) | ||
| 78 | { | ||
| 79 | fclose(pdbErrFile); | ||
| 80 | pdbErrFile = NULL; | ||
| 81 | } | ||
| 82 | fDbEnvInit = false; | ||
| 83 | } | ||
| 57 | 84 | ||
| 58 | class CDBInit | 85 | class CDBInit |
| 59 | { | 86 | { |
| @@ -64,15 +91,189 @@ public: | |||
| 64 | ~CDBInit() | 91 | ~CDBInit() |
| 65 | { | 92 | { |
| 66 | if (fDbEnvInit) | 93 | if (fDbEnvInit) |
| 67 | { | 94 | CloseDbEnv(); |
| 68 | try { dbenv.close(0); } | ||
| 69 | catch (...) { } | ||
| 70 | fDbEnvInit = false; | ||
| 71 | } | ||
| 72 | } | 95 | } |
| 73 | } | 96 | } |
| 74 | instance_of_cdbinit; | 97 | instance_of_cdbinit; |
| 75 | 98 | ||
| 99 | bool WalletSQLiteRuntimeActive() | ||
| 100 | { | ||
| 101 | return pWalletSQLiteRuntime != NULL; | ||
| 102 | } | ||
| 103 | |||
| 104 | bool WalletSQLiteRuntimeOpen(const string& strPath, string& strErrorRet, bool fMustExist) | ||
| 105 | { | ||
| 106 | strErrorRet.clear(); | ||
| 107 | // Callers that load an existing wallet want the clear "does not exist" | ||
| 108 | // message rather than silently creating a blank one; the create path passes | ||
| 109 | // fMustExist=false so Open() (SQLITE_OPEN_CREATE) can make a fresh file. | ||
| 110 | if (fMustExist && !FileExists(strPath.c_str())) | ||
| 111 | { | ||
| 112 | strErrorRet = strprintf("SQLite wallet does not exist: %s", | ||
| 113 | strPath.c_str()); | ||
| 114 | return false; | ||
| 115 | } | ||
| 116 | |||
| 117 | WalletSQLiteRuntimeClose(); | ||
| 118 | std::auto_ptr<CWalletDBSQLite> db(new CWalletDBSQLite()); | ||
| 119 | if (!db->Open(strPath, strErrorRet)) | ||
| 120 | return false; | ||
| 121 | |||
| 122 | pWalletSQLiteRuntime = db.release(); | ||
| 123 | return true; | ||
| 124 | } | ||
| 125 | |||
| 126 | void WalletSQLiteRuntimeClose() | ||
| 127 | { | ||
| 128 | if (pWalletSQLiteRuntime) | ||
| 129 | { | ||
| 130 | // Fold the write-ahead log into the main file on the way out. WAL + | ||
| 131 | // synchronous=FULL already makes every committed write durable across a | ||
| 132 | // kill, so this is housekeeping, not the thing that protects the data: | ||
| 133 | // if it fails, or the node is killed before it runs, the wallet is | ||
| 134 | // still whole. Best effort, never throws. | ||
| 135 | string strCheckpointError; | ||
| 136 | pWalletSQLiteRuntime->Checkpoint(strCheckpointError); | ||
| 137 | delete pWalletSQLiteRuntime; | ||
| 138 | pWalletSQLiteRuntime = NULL; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | bool WalletSQLiteRuntimeReadRecord(const vector<unsigned char>& vchKey, | ||
| 143 | vector<unsigned char>& vchValueRet) | ||
| 144 | { | ||
| 145 | if (!pWalletSQLiteRuntime) | ||
| 146 | return false; | ||
| 147 | string strError; | ||
| 148 | return pWalletSQLiteRuntime->ReadRecord(vchKey, vchValueRet, strError); | ||
| 149 | } | ||
| 150 | |||
| 151 | bool WalletSQLiteRuntimeWriteRecord(const vector<unsigned char>& vchKey, | ||
| 152 | const vector<unsigned char>& vchValue, | ||
| 153 | bool fOverwrite) | ||
| 154 | { | ||
| 155 | if (!pWalletSQLiteRuntime) | ||
| 156 | return false; | ||
| 157 | string strError; | ||
| 158 | return pWalletSQLiteRuntime->WriteRecord(vchKey, vchValue, strError, | ||
| 159 | fOverwrite); | ||
| 160 | } | ||
| 161 | |||
| 162 | bool WalletSQLiteRuntimeEraseRecord(const vector<unsigned char>& vchKey) | ||
| 163 | { | ||
| 164 | if (!pWalletSQLiteRuntime) | ||
| 165 | return false; | ||
| 166 | string strError; | ||
| 167 | return pWalletSQLiteRuntime->EraseRecord(vchKey, strError); | ||
| 168 | } | ||
| 169 | |||
| 170 | // Transaction bracket for the SQLite runtime. When the SQLite backend is not | ||
| 171 | // active these are no-ops that succeed, so a caller can wrap a group of related | ||
| 172 | // writes and Berkeley DB keeps its existing per-write behaviour unchanged while | ||
| 173 | // SQLite gets all-or-nothing. | ||
| 174 | bool WalletSQLiteRuntimeBeginTxn() | ||
| 175 | { | ||
| 176 | if (!pWalletSQLiteRuntime) | ||
| 177 | return true; | ||
| 178 | string strError; | ||
| 179 | if (pWalletSQLiteRuntime->BeginTransaction(strError)) | ||
| 180 | return true; | ||
| 181 | printf("WalletSQLiteRuntimeBeginTxn() : %s\n", strError.c_str()); | ||
| 182 | return false; | ||
| 183 | } | ||
| 184 | |||
| 185 | bool WalletSQLiteRuntimeCommitTxn() | ||
| 186 | { | ||
| 187 | if (!pWalletSQLiteRuntime) | ||
| 188 | return true; | ||
| 189 | string strError; | ||
| 190 | if (pWalletSQLiteRuntime->CommitTransaction(strError)) | ||
| 191 | return true; | ||
| 192 | printf("WalletSQLiteRuntimeCommitTxn() : %s\n", strError.c_str()); | ||
| 193 | return false; | ||
| 194 | } | ||
| 195 | |||
| 196 | void WalletSQLiteRuntimeRollbackTxn() | ||
| 197 | { | ||
| 198 | if (!pWalletSQLiteRuntime) | ||
| 199 | return; | ||
| 200 | string strError; | ||
| 201 | pWalletSQLiteRuntime->RollbackTransaction(strError); | ||
| 202 | } | ||
| 203 | |||
| 204 | // Open the SQLite wallet read-only as the active runtime. Used by the one-shot | ||
| 205 | // storage diagnostics, which run before the normal wallet load and must be able | ||
| 206 | // to inspect a wallet that the loader would refuse -- so this opens the file for | ||
| 207 | // scanning without loading or validating its records. | ||
| 208 | bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet) | ||
| 209 | { | ||
| 210 | strErrorRet.clear(); | ||
| 211 | if (!FileExists(strPath.c_str())) | ||
| 212 | { | ||
| 213 | strErrorRet = strprintf("SQLite wallet does not exist: %s", strPath.c_str()); | ||
| 214 | return false; | ||
| 215 | } | ||
| 216 | |||
| 217 | WalletSQLiteRuntimeClose(); | ||
| 218 | std::auto_ptr<CWalletDBSQLite> db(new CWalletDBSQLite()); | ||
| 219 | if (!db->OpenReadOnly(strPath, strErrorRet)) | ||
| 220 | return false; | ||
| 221 | |||
| 222 | pWalletSQLiteRuntime = db.release(); | ||
| 223 | return true; | ||
| 224 | } | ||
| 225 | |||
| 226 | bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet) | ||
| 227 | { | ||
| 228 | if (!pWalletSQLiteRuntime) | ||
| 229 | { | ||
| 230 | strErrorRet = "SQLite wallet backend is not active"; | ||
| 231 | return false; | ||
| 232 | } | ||
| 233 | return pWalletSQLiteRuntime->Checkpoint(strErrorRet); | ||
| 234 | } | ||
| 235 | |||
| 236 | // The persistent record of which backend this datadir uses, so the choice | ||
| 237 | // survives a restart without a command-line flag. A plain text file holding | ||
| 238 | // "sqlite" or "bdb" -- visible and hand-editable on purpose, no hidden state. | ||
| 239 | static string WalletBackendMarkerPath() | ||
| 240 | { | ||
| 241 | return GetAppDir() + "/wallet-backend"; | ||
| 242 | } | ||
| 243 | |||
| 244 | string ReadWalletBackendMarker() | ||
| 245 | { | ||
| 246 | FILE* pf = fopen(WalletBackendMarkerPath().c_str(), "rb"); | ||
| 247 | if (!pf) | ||
| 248 | return ""; | ||
| 249 | char buf[16] = {0}; | ||
| 250 | size_t n = fread(buf, 1, sizeof(buf) - 1, pf); | ||
| 251 | fclose(pf); | ||
| 252 | string s(buf, n); | ||
| 253 | while (!s.empty() && isspace((unsigned char)s[s.size() - 1])) | ||
| 254 | s.erase(s.size() - 1); | ||
| 255 | while (!s.empty() && isspace((unsigned char)s[0])) | ||
| 256 | s.erase(0, 1); | ||
| 257 | if (s == "sqlite" || s == "bdb") | ||
| 258 | return s; | ||
| 259 | return ""; | ||
| 260 | } | ||
| 261 | |||
| 262 | bool WriteWalletBackendMarker(const string& strBackend) | ||
| 263 | { | ||
| 264 | if (strBackend != "sqlite" && strBackend != "bdb") | ||
| 265 | return false; | ||
| 266 | FILE* pf = fopen(WalletBackendMarkerPath().c_str(), "wb"); | ||
| 267 | if (!pf) | ||
| 268 | return false; | ||
| 269 | bool fOk = fwrite(strBackend.c_str(), 1, strBackend.size(), pf) == strBackend.size(); | ||
| 270 | if (fputc('\n', pf) == EOF) | ||
| 271 | fOk = false; | ||
| 272 | if (fclose(pf) != 0) | ||
| 273 | fOk = false; | ||
| 274 | return fOk; | ||
| 275 | } | ||
| 276 | |||
| 76 | 277 | ||
| 77 | CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | 278 | CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) |
| 78 | { | 279 | { |
| @@ -104,24 +305,30 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | |||
| 104 | 305 | ||
| 105 | printf("dbenv.open strAppDir=%s\n", strAppDir.c_str()); | 306 | printf("dbenv.open strAppDir=%s\n", strAppDir.c_str()); |
| 106 | 307 | ||
| 107 | dbenv.set_lg_dir(strLogDir.c_str()); | 308 | DbEnv& env = GetDbEnv(); |
| 108 | dbenv.set_lg_max(10000000); | 309 | env.set_lg_dir(strLogDir.c_str()); |
| 109 | dbenv.set_lk_max_locks(10000); | 310 | env.set_lg_max(10000000); |
| 110 | dbenv.set_lk_max_objects(10000); | 311 | env.set_lk_max_locks(10000); |
| 111 | dbenv.set_errfile(fopen("db.log", "a")); /// debug | 312 | env.set_lk_max_objects(10000); |
| 112 | ///dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption | 313 | pdbErrFile = fopen("db.log", "a"); /// debug |
| 113 | ret = dbenv.open(strAppDir.c_str(), | 314 | if (pdbErrFile) |
| 114 | DB_CREATE | | 315 | env.set_errfile(pdbErrFile); |
| 115 | DB_INIT_LOCK | | 316 | ///env.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption |
| 116 | DB_INIT_LOG | | 317 | ret = env.open(strAppDir.c_str(), |
| 117 | DB_INIT_MPOOL | | 318 | DB_CREATE | |
| 118 | DB_INIT_TXN | | 319 | DB_INIT_LOCK | |
| 119 | DB_THREAD | | 320 | DB_INIT_LOG | |
| 120 | DB_PRIVATE | | 321 | DB_INIT_MPOOL | |
| 121 | DB_RECOVER, | 322 | DB_INIT_TXN | |
| 122 | 0); | 323 | DB_THREAD | |
| 324 | DB_PRIVATE | | ||
| 325 | DB_RECOVER, | ||
| 326 | 0); | ||
| 123 | if (ret > 0) | 327 | if (ret > 0) |
| 328 | { | ||
| 329 | CloseDbEnv(); | ||
| 124 | throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); | 330 | throw runtime_error(strprintf("CDB() : error %d opening database environment\n", ret)); |
| 331 | } | ||
| 125 | fDbEnvInit = true; | 332 | fDbEnvInit = true; |
| 126 | 333 | ||
| 127 | // A Berkeley DB file records, in every page header, a log sequence | 334 | // A Berkeley DB file records, in every page header, a log sequence |
| @@ -151,7 +358,7 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | |||
| 151 | continue; | 358 | continue; |
| 152 | try | 359 | try |
| 153 | { | 360 | { |
| 154 | if (dbenv.lsn_reset(pszName, 0) == 0) | 361 | if (env.lsn_reset(pszName, 0) == 0) |
| 155 | printf("CDB() : adopted %s from another environment " | 362 | printf("CDB() : adopted %s from another environment " |
| 156 | "(log sequence numbers reset)\n", pszName); | 363 | "(log sequence numbers reset)\n", pszName); |
| 157 | } | 364 | } |
| @@ -164,7 +371,7 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) | |||
| 164 | ++mapFileUseCount[strFile]; | 371 | ++mapFileUseCount[strFile]; |
| 165 | } | 372 | } |
| 166 | 373 | ||
| 167 | pdb = new Db(&dbenv, 0); | 374 | pdb = new Db(&GetDbEnv(), 0); |
| 168 | 375 | ||
| 169 | ret = pdb->open(NULL, // Txn pointer | 376 | ret = pdb->open(NULL, // Txn pointer |
| 170 | pszFile, // Filename | 377 | pszFile, // Filename |
| @@ -199,7 +406,8 @@ void CDB::Close() | |||
| 199 | try { pdb->close(0); } catch (...) { } | 406 | try { pdb->close(0); } catch (...) { } |
| 200 | delete pdb; | 407 | delete pdb; |
| 201 | pdb = NULL; | 408 | pdb = NULL; |
| 202 | try { dbenv.txn_checkpoint(0, 0, 0); } catch (...) { } | 409 | if (fDbEnvInit) |
| 410 | try { GetDbEnv().txn_checkpoint(0, 0, 0); } catch (...) { } | ||
| 203 | 411 | ||
| 204 | CRITICAL_BLOCK(cs_db) | 412 | CRITICAL_BLOCK(cs_db) |
| 205 | --mapFileUseCount[strFile]; | 413 | --mapFileUseCount[strFile]; |
| @@ -279,7 +487,8 @@ void DBFlush(bool fShutdown) | |||
| 279 | // takes the process down before the wallet has been put down cleanly, | 487 | // takes the process down before the wallet has been put down cleanly, |
| 280 | // which is the difference between a portable wallet.dat and one welded | 488 | // which is the difference between a portable wallet.dat and one welded |
| 281 | // to this directory -- the lsn_reset just below is what frees it. | 489 | // to this directory -- the lsn_reset just below is what frees it. |
| 282 | try { dbenv.txn_checkpoint(0, 0, 0); } | 490 | DbEnv& env = GetDbEnv(); |
| 491 | try { env.txn_checkpoint(0, 0, 0); } | ||
| 283 | catch (const std::exception& e) | 492 | catch (const std::exception& e) |
| 284 | { printf("DBFlush() : checkpoint failed: %s\n", e.what()); } | 493 | { printf("DBFlush() : checkpoint failed: %s\n", e.what()); } |
| 285 | 494 | ||
| @@ -290,7 +499,7 @@ void DBFlush(bool fShutdown) | |||
| 290 | int nRefCount = (*mi).second; | 499 | int nRefCount = (*mi).second; |
| 291 | if (nRefCount == 0) | 500 | if (nRefCount == 0) |
| 292 | { | 501 | { |
| 293 | try { dbenv.lsn_reset(strFile.c_str(), 0); } | 502 | try { env.lsn_reset(strFile.c_str(), 0); } |
| 294 | catch (const std::exception& e) | 503 | catch (const std::exception& e) |
| 295 | { printf("DBFlush() : lsn_reset(%s) failed: %s\n", strFile.c_str(), e.what()); } | 504 | { printf("DBFlush() : lsn_reset(%s) failed: %s\n", strFile.c_str(), e.what()); } |
| 296 | mapFileUseCount.erase(mi++); | 505 | mapFileUseCount.erase(mi++); |
| @@ -310,9 +519,8 @@ void DBFlush(bool fShutdown) | |||
| 310 | // Found while making wallet encryption clean up after itself: | 519 | // Found while making wallet encryption clean up after itself: |
| 311 | // calling DBFlush(true) a second time crashed here every time. | 520 | // calling DBFlush(true) a second time crashed here every time. |
| 312 | if (mapFileUseCount.empty()) | 521 | if (mapFileUseCount.empty()) |
| 313 | try { dbenv.log_archive(NULL, DB_ARCH_REMOVE); } catch (...) { } | 522 | try { env.log_archive(NULL, DB_ARCH_REMOVE); } catch (...) { } |
| 314 | try { dbenv.close(0); } catch (...) { } | 523 | CloseDbEnv(); |
| 315 | fDbEnvInit = false; | ||
| 316 | } | 524 | } |
| 317 | } | 525 | } |
| 318 | } | 526 | } |
| @@ -614,6 +822,294 @@ bool CTxDB::LoadBlockIndex() | |||
| 614 | // CWalletDB | 822 | // CWalletDB |
| 615 | // | 823 | // |
| 616 | 824 | ||
| 825 | CWalletDB::CWalletDB(const char* pszMode, bool fTxn) : | ||
| 826 | CDB(WalletSQLiteRuntimeActive() ? NULL : "wallet.dat", pszMode, fTxn) | ||
| 827 | { | ||
| 828 | } | ||
| 829 | |||
| 830 | class CSQLiteWalletRuntimeLoadVisitor : public CWalletRecordVisitor | ||
| 831 | { | ||
| 832 | public: | ||
| 833 | bool fHaveStoredMineMode; | ||
| 834 | bool fHaveStoredHDCoinType; | ||
| 835 | string strLastType; | ||
| 836 | |||
| 837 | CSQLiteWalletRuntimeLoadVisitor() : | ||
| 838 | fHaveStoredMineMode(false), | ||
| 839 | fHaveStoredHDCoinType(false) | ||
| 840 | { | ||
| 841 | } | ||
| 842 | |||
| 843 | bool VisitWalletRecord(const CDataStream& ssKeyIn, | ||
| 844 | const CDataStream& ssValueIn, | ||
| 845 | string& strErrorRet) | ||
| 846 | { | ||
| 847 | CDataStream ssKey = ssKeyIn; | ||
| 848 | CDataStream ssValue = ssValueIn; | ||
| 849 | string strType; | ||
| 850 | ssKey >> strType; | ||
| 851 | strLastType = strType; | ||
| 852 | |||
| 853 | if (strType == "name") | ||
| 854 | { | ||
| 855 | string strAddress; | ||
| 856 | ssKey >> strAddress; | ||
| 857 | ssValue >> mapAddressBook[strAddress]; | ||
| 858 | } | ||
| 859 | else if (strType == "tx") | ||
| 860 | { | ||
| 861 | uint256 hash; | ||
| 862 | ssKey >> hash; | ||
| 863 | CWalletTx& wtx = mapWallet[hash]; | ||
| 864 | ssValue >> wtx; | ||
| 865 | |||
| 866 | if (wtx.GetHash() != hash) | ||
| 867 | printf("Error in SQLite wallet export, hash mismatch\n"); | ||
| 868 | } | ||
| 869 | else if (strType == "key") | ||
| 870 | { | ||
| 871 | vector<unsigned char> vchPubKey; | ||
| 872 | ssKey >> vchPubKey; | ||
| 873 | CPrivKey vchPrivKey; | ||
| 874 | ssValue >> vchPrivKey; | ||
| 875 | |||
| 876 | mapKeys[vchPubKey] = vchPrivKey; | ||
| 877 | mapPubKeys[Hash160(vchPubKey)] = vchPubKey; | ||
| 878 | } | ||
| 879 | else if (strType == "mkey") | ||
| 880 | { | ||
| 881 | unsigned int nID = 0; | ||
| 882 | ssKey >> nID; | ||
| 883 | CWalletMasterKey kMasterKey; | ||
| 884 | ssValue >> kMasterKey; | ||
| 885 | mapMasterKeys[nID] = kMasterKey; | ||
| 886 | nWalletMasterKeyMaxID = max(nWalletMasterKeyMaxID, nID); | ||
| 887 | fWalletEncrypted = true; | ||
| 888 | fWalletLocked = true; | ||
| 889 | } | ||
| 890 | else if (strType == "ckey") | ||
| 891 | { | ||
| 892 | vector<unsigned char> vchPubKey; | ||
| 893 | ssKey >> vchPubKey; | ||
| 894 | vector<unsigned char> vchCryptedSecret; | ||
| 895 | ssValue >> vchCryptedSecret; | ||
| 896 | if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) | ||
| 897 | { | ||
| 898 | strErrorRet = "SQLite wallet export has an unreadable encrypted key record"; | ||
| 899 | return false; | ||
| 900 | } | ||
| 901 | } | ||
| 902 | else if (strType == "defaultkey") | ||
| 903 | { | ||
| 904 | ssValue >> vchDefaultKey; | ||
| 905 | } | ||
| 906 | else if (strType == "hdmaster") | ||
| 907 | { | ||
| 908 | ssValue >> vchHDMaster; | ||
| 909 | } | ||
| 910 | else if (strType == "hdchaincode") | ||
| 911 | { | ||
| 912 | ssValue >> vchHDChainCode; | ||
| 913 | } | ||
| 914 | else if (strType == "hdnext") | ||
| 915 | { | ||
| 916 | ssValue >> nHDNext; | ||
| 917 | } | ||
| 918 | else if (strType == "hdschema") | ||
| 919 | { | ||
| 920 | ssValue >> nHDKeySchema; | ||
| 921 | } | ||
| 922 | else if (strType == "hdcointype") | ||
| 923 | { | ||
| 924 | ssValue >> nHDCoinType; | ||
| 925 | fHaveStoredHDCoinType = true; | ||
| 926 | } | ||
| 927 | else if (strType == "hdreceivenext") | ||
| 928 | { | ||
| 929 | ssValue >> nHDReceiveNext; | ||
| 930 | } | ||
| 931 | else if (strType == "hdchangenext") | ||
| 932 | { | ||
| 933 | ssValue >> nHDChangeNext; | ||
| 934 | } | ||
| 935 | else if (strType == "cryptedhdmaster") | ||
| 936 | { | ||
| 937 | ssValue >> vchCryptedHDMaster; | ||
| 938 | fWalletEncrypted = true; | ||
| 939 | fWalletLocked = true; | ||
| 940 | } | ||
| 941 | else if (strType == "cryptedhdchaincode") | ||
| 942 | { | ||
| 943 | ssValue >> vchCryptedHDChainCode; | ||
| 944 | fWalletEncrypted = true; | ||
| 945 | fWalletLocked = true; | ||
| 946 | } | ||
| 947 | else if (strType == "walletminversion") | ||
| 948 | { | ||
| 949 | int nMinVersion = 0; | ||
| 950 | ssValue >> nMinVersion; | ||
| 951 | if (nMinVersion > WALLET_FORMAT_SUPPORTED) | ||
| 952 | { | ||
| 953 | strWalletLoadError = strprintf( | ||
| 954 | "This SQLite wallet export was written by a newer version of " | ||
| 955 | "Bitflash (wallet format %d; this build understands %d). " | ||
| 956 | "Upgrade Bitflash before opening it.", | ||
| 957 | nMinVersion, WALLET_FORMAT_SUPPORTED); | ||
| 958 | strErrorRet = strWalletLoadError; | ||
| 959 | return false; | ||
| 960 | } | ||
| 961 | } | ||
| 962 | else if (strType == "pool") | ||
| 963 | { | ||
| 964 | int64 nIndex; | ||
| 965 | ssKey >> nIndex; | ||
| 966 | vector<unsigned char> vchPubKey; | ||
| 967 | ssValue >> vchPubKey; | ||
| 968 | mapKeyPool[nIndex] = vchPubKey; | ||
| 969 | } | ||
| 970 | else if (strType == "setting") | ||
| 971 | { | ||
| 972 | string strKey; | ||
| 973 | ssKey >> strKey; | ||
| 974 | if (strKey == "nTransactionFee") ssValue >> nTransactionFee; | ||
| 975 | if (strKey == "addrIncoming") ssValue >> addrIncoming; | ||
| 976 | |||
| 977 | if (!fMineModeFromCommandLine) | ||
| 978 | { | ||
| 979 | if (strKey == "nMineMode") { ssValue >> nMineMode; fHaveStoredMineMode = true; } | ||
| 980 | if (strKey == "fGenerateBitcoins") ssValue >> fGenerateBitcoins; | ||
| 981 | if (strKey == "strParticipantPool") ssValue >> strParticipantPool; | ||
| 982 | } | ||
| 983 | } | ||
| 984 | |||
| 985 | if (ssKey.fail()) | ||
| 986 | { | ||
| 987 | strErrorRet = strprintf("malformed SQLite wallet record key while handling '%s'", | ||
| 988 | strLastType.c_str()); | ||
| 989 | return false; | ||
| 990 | } | ||
| 991 | if (ssValue.fail()) | ||
| 992 | { | ||
| 993 | strErrorRet = strprintf("malformed SQLite wallet record value while handling '%s'", | ||
| 994 | strLastType.c_str()); | ||
| 995 | return false; | ||
| 996 | } | ||
| 997 | return true; | ||
| 998 | } | ||
| 999 | |||
| 1000 | vector<unsigned char> vchDefaultKey; | ||
| 1001 | }; | ||
| 1002 | |||
| 1003 | static bool FinishLoadedWallet(const vector<unsigned char>& vchDefaultKey, | ||
| 1004 | bool fMayCreateDefaultKey) | ||
| 1005 | { | ||
| 1006 | if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey))) | ||
| 1007 | { | ||
| 1008 | keyUser.SetPubKey(vchDefaultKey); | ||
| 1009 | if (mapKeys.count(vchDefaultKey)) | ||
| 1010 | keyUser.SetPrivKey(mapKeys[vchDefaultKey]); | ||
| 1011 | } | ||
| 1012 | else if (IsWalletEncrypted()) | ||
| 1013 | { | ||
| 1014 | printf("LoadWallet: encrypted wallet has no usable default public key. " | ||
| 1015 | "The file was left untouched.\n"); | ||
| 1016 | return false; | ||
| 1017 | } | ||
| 1018 | else | ||
| 1019 | { | ||
| 1020 | if (!fMayCreateDefaultKey) | ||
| 1021 | { | ||
| 1022 | strWalletLoadError = | ||
| 1023 | "SQLite wallet export has no usable default public key"; | ||
| 1024 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1025 | return false; | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | keyUser.MakeNewKey(); | ||
| 1029 | if (!AddKey(keyUser)) | ||
| 1030 | return false; | ||
| 1031 | if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address")) | ||
| 1032 | return false; | ||
| 1033 | CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); | ||
| 1034 | } | ||
| 1035 | |||
| 1036 | return true; | ||
| 1037 | } | ||
| 1038 | |||
| 1039 | static bool ReconcileLoadedWalletMetadata(bool fHaveStoredMineMode, | ||
| 1040 | bool fHaveStoredHDCoinType) | ||
| 1041 | { | ||
| 1042 | // An encrypted wallet has a seed; it just has not been decrypted yet. | ||
| 1043 | // | ||
| 1044 | // The reset in the else branch was written when "no seed in memory after | ||
| 1045 | // loading" could only mean "this wallet has no seed". With encryption that | ||
| 1046 | // is no longer true, and taking the else branch here wiped the schema and | ||
| 1047 | // the derivation counters that had just been read correctly out of the | ||
| 1048 | // file. Unlocking later restores the seed but nothing restores those, so a | ||
| 1049 | // BIP44 wallet came back deriving legacy addresses -- different addresses | ||
| 1050 | // from the ones its own recovery phrase produces, while the audit reported | ||
| 1051 | // the phrase as covering nothing. | ||
| 1052 | if (HaveHDSeed() || !vchCryptedHDMaster.empty()) | ||
| 1053 | { | ||
| 1054 | if (nHDKeySchema == HD_SCHEMA_NONE) | ||
| 1055 | { | ||
| 1056 | nHDKeySchema = HD_SCHEMA_LEGACY; | ||
| 1057 | printf("LoadWallet: deterministic seed has no schema metadata; " | ||
| 1058 | "treating it as legacy m/index'\n"); | ||
| 1059 | } | ||
| 1060 | else if (nHDKeySchema != HD_SCHEMA_LEGACY && | ||
| 1061 | nHDKeySchema != HD_SCHEMA_BIP44) | ||
| 1062 | { | ||
| 1063 | printf("LoadWallet: deterministic wallet schema %d (%s) is not " | ||
| 1064 | "supported by this build\n", | ||
| 1065 | nHDKeySchema, HDKeySchemaName(nHDKeySchema).c_str()); | ||
| 1066 | return false; | ||
| 1067 | } | ||
| 1068 | if (!fHaveStoredHDCoinType) | ||
| 1069 | { | ||
| 1070 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH; | ||
| 1071 | printf("LoadWallet: deterministic seed has no BIP44 coin type metadata; " | ||
| 1072 | "using Bitflash coin type %u\n", nHDCoinType); | ||
| 1073 | } | ||
| 1074 | } | ||
| 1075 | else | ||
| 1076 | { | ||
| 1077 | nHDKeySchema = HD_SCHEMA_NONE; | ||
| 1078 | nHDNext = 0; | ||
| 1079 | nHDReceiveNext = 0; | ||
| 1080 | nHDChangeNext = 0; | ||
| 1081 | nHDCoinType = HD_BIP44_COIN_TYPE_BITFLASH; | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | // fGenerateBitcoins and nMineMode only mean anything together, and a | ||
| 1085 | // wallet.dat can easily hold one without the other: Bitcoin 0.1.0 already | ||
| 1086 | // wrote fGenerateBitcoins when mining was toggled, years before this fork | ||
| 1087 | // had a mode at all. Restoring that lone flag put a node into the one state | ||
| 1088 | // the miner cannot act on -- generate on, mode relay -- so every miner | ||
| 1089 | // thread started and returned at the relay guard, and the machine sat there | ||
| 1090 | // looking like it was mining while hashing nothing. That happened to a | ||
| 1091 | // 32-core node on the first 1.2.11 start. | ||
| 1092 | // | ||
| 1093 | // So the flag is only believed when the mode it belongs to was stored with | ||
| 1094 | // it, and the pair is made coherent either way. | ||
| 1095 | if (!fMineModeFromCommandLine) | ||
| 1096 | { | ||
| 1097 | if (!fHaveStoredMineMode) | ||
| 1098 | { | ||
| 1099 | if (fGenerateBitcoins) | ||
| 1100 | printf("LoadWallet: ignoring a stored fGenerateBitcoins with no mining mode " | ||
| 1101 | "beside it -- it predates this setting and cannot be read on its own\n"); | ||
| 1102 | fGenerateBitcoins = 0; | ||
| 1103 | } | ||
| 1104 | else | ||
| 1105 | { | ||
| 1106 | fGenerateBitcoins = (nMineMode != MINE_RELAY) ? 1 : 0; | ||
| 1107 | } | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | return true; | ||
| 1111 | } | ||
| 1112 | |||
| 617 | bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | 1113 | bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) |
| 618 | { | 1114 | { |
| 619 | // Whether wallet.dat actually carried a mining mode, as opposed to just the | 1115 | // Whether wallet.dat actually carried a mining mode, as opposed to just the |
| @@ -924,34 +1420,269 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet) | |||
| 924 | return true; | 1420 | return true; |
| 925 | } | 1421 | } |
| 926 | 1422 | ||
| 1423 | bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet) | ||
| 1424 | { | ||
| 1425 | // Under the SQLite backend there is no Berkeley DB cursor to walk; scan the | ||
| 1426 | // active SQLite store instead. This is the single choke point every "read | ||
| 1427 | // every wallet record" caller goes through (storage audit and check among | ||
| 1428 | // them), so routing it here makes those honour -walletbackend=sqlite. | ||
| 1429 | if (WalletSQLiteRuntimeActive()) | ||
| 1430 | { | ||
| 1431 | strErrorRet.clear(); | ||
| 1432 | return pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet); | ||
| 1433 | } | ||
| 1434 | |||
| 1435 | class CWalletScanDB : public CWalletDB | ||
| 1436 | { | ||
| 1437 | public: | ||
| 1438 | CWalletScanDB() : CWalletDB("r") { } | ||
| 1439 | using CDB::GetCursor; | ||
| 1440 | using CDB::ReadAtCursor; | ||
| 1441 | }; | ||
| 1442 | |||
| 1443 | strErrorRet.clear(); | ||
| 1444 | CWalletScanDB dbWallet; | ||
| 1445 | Dbc* pcursor = dbWallet.GetCursor(); | ||
| 1446 | if (!pcursor) | ||
| 1447 | { | ||
| 1448 | strErrorRet = "could not open a wallet cursor"; | ||
| 1449 | return false; | ||
| 1450 | } | ||
| 1451 | CAutoCursor cursorGuard(pcursor); | ||
| 1452 | |||
| 1453 | loop | ||
| 1454 | { | ||
| 1455 | CDataStream ssKey(SER_DISK); | ||
| 1456 | CDataStream ssValue(SER_DISK); | ||
| 1457 | int ret = dbWallet.ReadAtCursor(pcursor, ssKey, ssValue); | ||
| 1458 | if (ret == DB_NOTFOUND) | ||
| 1459 | break; | ||
| 1460 | if (ret != 0) | ||
| 1461 | { | ||
| 1462 | strErrorRet = strprintf("could not read wallet record: Berkeley DB error %d", ret); | ||
| 1463 | return false; | ||
| 1464 | } | ||
| 1465 | if (!visitor.VisitWalletRecord(ssKey, ssValue, strErrorRet)) | ||
| 1466 | return false; | ||
| 1467 | } | ||
| 1468 | |||
| 1469 | return true; | ||
| 1470 | } | ||
| 1471 | |||
| 927 | bool LoadWallet() | 1472 | bool LoadWallet() |
| 928 | { | 1473 | { |
| 929 | vector<unsigned char> vchDefaultKey; | 1474 | vector<unsigned char> vchDefaultKey; |
| 930 | if (!CWalletDB("cr").LoadWallet(vchDefaultKey)) | 1475 | if (!CWalletDB("cr").LoadWallet(vchDefaultKey)) |
| 931 | return false; | 1476 | return false; |
| 932 | 1477 | ||
| 933 | if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey))) | 1478 | return FinishLoadedWallet(vchDefaultKey, true); |
| 1479 | } | ||
| 1480 | |||
| 1481 | bool LoadWalletFromSQLite(const string& strPath) | ||
| 1482 | { | ||
| 1483 | strWalletLoadError.clear(); | ||
| 1484 | |||
| 1485 | if (!FileExists(strPath.c_str())) | ||
| 934 | { | 1486 | { |
| 935 | // Set keyUser | 1487 | strWalletLoadError = strprintf("SQLite wallet export does not exist: %s", |
| 936 | keyUser.SetPubKey(vchDefaultKey); | 1488 | strPath.c_str()); |
| 937 | if (mapKeys.count(vchDefaultKey)) | 1489 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); |
| 938 | keyUser.SetPrivKey(mapKeys[vchDefaultKey]); | 1490 | return false; |
| 939 | } | 1491 | } |
| 940 | else if (IsWalletEncrypted()) | 1492 | |
| 1493 | CWalletDBSQLite sqlite; | ||
| 1494 | string strError; | ||
| 1495 | if (!sqlite.OpenReadOnly(strPath, strError)) | ||
| 941 | { | 1496 | { |
| 942 | printf("LoadWallet: encrypted wallet has no usable default public key. " | 1497 | strWalletLoadError = strError; |
| 943 | "The file was left untouched.\n"); | 1498 | printf("LoadWallet: cannot open SQLite wallet export: %s\n", |
| 1499 | strError.c_str()); | ||
| 944 | return false; | 1500 | return false; |
| 945 | } | 1501 | } |
| 946 | else | 1502 | |
| 1503 | CSQLiteWalletRuntimeLoadVisitor visitor; | ||
| 1504 | try | ||
| 947 | { | 1505 | { |
| 948 | // Create new keyUser and set as default key | 1506 | CRITICAL_BLOCK(cs_mapKeys) |
| 949 | keyUser.MakeNewKey(); | 1507 | CRITICAL_BLOCK(cs_mapWallet) |
| 950 | if (!AddKey(keyUser)) | 1508 | { |
| 951 | return false; | 1509 | if (!sqlite.ScanRecords(visitor, strError)) |
| 952 | if (!SetAddressBookName(PubKeyToAddress(keyUser.GetPubKey()), "Your Address")) | 1510 | { |
| 953 | return false; | 1511 | if (strWalletLoadError.empty()) |
| 954 | CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); | 1512 | strWalletLoadError = strError; |
| 1513 | printf("LoadWallet: SQLite wallet export could not be read. " | ||
| 1514 | "Failed while handling a '%s' record: %s\n", | ||
| 1515 | visitor.strLastType.c_str(), strError.c_str()); | ||
| 1516 | return false; | ||
| 1517 | } | ||
| 1518 | } | ||
| 1519 | } | ||
| 1520 | catch (const std::exception& e) | ||
| 1521 | { | ||
| 1522 | strWalletLoadError = strprintf( | ||
| 1523 | "SQLite wallet export could not be read while handling a '%s' record: %s", | ||
| 1524 | visitor.strLastType.c_str(), e.what()); | ||
| 1525 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1526 | return false; | ||
| 1527 | } | ||
| 1528 | catch (...) | ||
| 1529 | { | ||
| 1530 | strWalletLoadError = strprintf( | ||
| 1531 | "SQLite wallet export could not be read while handling a '%s' record", | ||
| 1532 | visitor.strLastType.c_str()); | ||
| 1533 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1534 | return false; | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode, | ||
| 1538 | visitor.fHaveStoredHDCoinType)) | ||
| 1539 | { | ||
| 1540 | if (strWalletLoadError.empty()) | ||
| 1541 | strWalletLoadError = "SQLite wallet metadata is not supported by this build"; | ||
| 1542 | return false; | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | return FinishLoadedWallet(visitor.vchDefaultKey, false); | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | // Open a SQLite wallet read-write and keep it open as the active runtime | ||
| 1549 | // backend, then load its records into memory the same way LoadWalletFromSQLite | ||
| 1550 | // does. Unlike the read-only staging path, this leaves pWalletSQLiteRuntime set | ||
| 1551 | // so every later CWalletDB read/write/erase routes to this file for the life of | ||
| 1552 | // the process. The Berkeley DB wallet.dat is never opened. | ||
| 1553 | bool LoadWalletFromSQLiteRuntime(const string& strPath) | ||
| 1554 | { | ||
| 1555 | strWalletLoadError.clear(); | ||
| 1556 | |||
| 1557 | if (!FileExists(strPath.c_str())) | ||
| 1558 | { | ||
| 1559 | strWalletLoadError = strprintf("SQLite wallet does not exist: %s", | ||
| 1560 | strPath.c_str()); | ||
| 1561 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1562 | return false; | ||
| 1563 | } | ||
| 1564 | |||
| 1565 | string strError; | ||
| 1566 | if (!WalletSQLiteRuntimeOpen(strPath, strError)) | ||
| 1567 | { | ||
| 1568 | strWalletLoadError = strError; | ||
| 1569 | printf("LoadWallet: cannot open SQLite wallet backend: %s\n", | ||
| 1570 | strError.c_str()); | ||
| 1571 | return false; | ||
| 1572 | } | ||
| 1573 | |||
| 1574 | CSQLiteWalletRuntimeLoadVisitor visitor; | ||
| 1575 | try | ||
| 1576 | { | ||
| 1577 | CRITICAL_BLOCK(cs_mapKeys) | ||
| 1578 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 1579 | { | ||
| 1580 | if (!pWalletSQLiteRuntime->ScanRecords(visitor, strError)) | ||
| 1581 | { | ||
| 1582 | WalletSQLiteRuntimeClose(); | ||
| 1583 | if (strWalletLoadError.empty()) | ||
| 1584 | strWalletLoadError = strError; | ||
| 1585 | printf("LoadWallet: SQLite wallet backend could not be read. " | ||
| 1586 | "Failed while handling a '%s' record: %s\n", | ||
| 1587 | visitor.strLastType.c_str(), strError.c_str()); | ||
| 1588 | return false; | ||
| 1589 | } | ||
| 1590 | } | ||
| 1591 | } | ||
| 1592 | catch (const std::exception& e) | ||
| 1593 | { | ||
| 1594 | WalletSQLiteRuntimeClose(); | ||
| 1595 | strWalletLoadError = strprintf( | ||
| 1596 | "SQLite wallet backend could not be read while handling a '%s' record: %s", | ||
| 1597 | visitor.strLastType.c_str(), e.what()); | ||
| 1598 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1599 | return false; | ||
| 1600 | } | ||
| 1601 | catch (...) | ||
| 1602 | { | ||
| 1603 | WalletSQLiteRuntimeClose(); | ||
| 1604 | strWalletLoadError = strprintf( | ||
| 1605 | "SQLite wallet backend could not be read while handling a '%s' record", | ||
| 1606 | visitor.strLastType.c_str()); | ||
| 1607 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1608 | return false; | ||
| 1609 | } | ||
| 1610 | |||
| 1611 | // Refuse an empty or non-wallet SQLite file up front, with a message that | ||
| 1612 | // names the likely cause. FinishLoadedWallet below already rejects a | ||
| 1613 | // missing default key (it is the fMayCreateDefaultKey=false path), but its | ||
| 1614 | // wording -- "no usable default public key" -- reads as corruption when the | ||
| 1615 | // real story is usually "this file was never an exported wallet". A blank | ||
| 1616 | // wallet.sqlite left by a typo or a half-run export should say so. | ||
| 1617 | if (visitor.vchDefaultKey.empty()) | ||
| 1618 | { | ||
| 1619 | WalletSQLiteRuntimeClose(); | ||
| 1620 | strWalletLoadError = | ||
| 1621 | "SQLite wallet has no default key record; it may be empty or not a " | ||
| 1622 | "wallet export. Create it with -walletsqliteexport."; | ||
| 1623 | printf("LoadWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1624 | return false; | ||
| 1625 | } | ||
| 1626 | |||
| 1627 | if (!ReconcileLoadedWalletMetadata(visitor.fHaveStoredMineMode, | ||
| 1628 | visitor.fHaveStoredHDCoinType)) | ||
| 1629 | { | ||
| 1630 | WalletSQLiteRuntimeClose(); | ||
| 1631 | if (strWalletLoadError.empty()) | ||
| 1632 | strWalletLoadError = "SQLite wallet metadata is not supported by this build"; | ||
| 1633 | return false; | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | // Never fabricate a default key: an export always carries one, and a file | ||
| 1637 | // that does not is refused with the wallet left open to no writes rather | ||
| 1638 | // than quietly minted into a new identity. | ||
| 1639 | if (!FinishLoadedWallet(visitor.vchDefaultKey, false)) | ||
| 1640 | { | ||
| 1641 | WalletSQLiteRuntimeClose(); | ||
| 1642 | return false; | ||
| 1643 | } | ||
| 1644 | |||
| 1645 | return true; | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | // Create a brand-new wallet directly in the SQLite backend. For a fresh install | ||
| 1649 | // where SQLite is the default there is no wallet.dat to export from, so open a | ||
| 1650 | // new wallet.sqlite (Open() creates the file and its schema) and let | ||
| 1651 | // FinishLoadedWallet mint the first default key straight into it -- the same | ||
| 1652 | // call the Berkeley DB create path uses, so a new wallet is identical either | ||
| 1653 | // way. Refuses to touch an existing file, and cleans up on failure so a broken | ||
| 1654 | // half-initialised wallet.sqlite is never left for the next start to load. | ||
| 1655 | bool CreateNewSQLiteWallet(const string& strPath) | ||
| 1656 | { | ||
| 1657 | strWalletLoadError.clear(); | ||
| 1658 | |||
| 1659 | if (FileExists(strPath.c_str())) | ||
| 1660 | { | ||
| 1661 | strWalletLoadError = strprintf("SQLite wallet already exists: %s", | ||
| 1662 | strPath.c_str()); | ||
| 1663 | printf("CreateNewSQLiteWallet: %s\n", strWalletLoadError.c_str()); | ||
| 1664 | return false; | ||
| 1665 | } | ||
| 1666 | |||
| 1667 | string strError; | ||
| 1668 | if (!WalletSQLiteRuntimeOpen(strPath, strError, false)) | ||
| 1669 | { | ||
| 1670 | strWalletLoadError = strError; | ||
| 1671 | printf("CreateNewSQLiteWallet: cannot create SQLite wallet: %s\n", | ||
| 1672 | strError.c_str()); | ||
| 1673 | return false; | ||
| 1674 | } | ||
| 1675 | |||
| 1676 | vector<unsigned char> vchNoDefaultKey; | ||
| 1677 | if (!FinishLoadedWallet(vchNoDefaultKey, true)) | ||
| 1678 | { | ||
| 1679 | WalletSQLiteRuntimeClose(); | ||
| 1680 | remove(strPath.c_str()); | ||
| 1681 | remove((strPath + "-wal").c_str()); | ||
| 1682 | remove((strPath + "-shm").c_str()); | ||
| 1683 | if (strWalletLoadError.empty()) | ||
| 1684 | strWalletLoadError = "could not initialise the new SQLite wallet"; | ||
| 1685 | return false; | ||
| 955 | } | 1686 | } |
| 956 | 1687 | ||
| 957 | return true; | 1688 | return true; |
| @@ -974,21 +1705,99 @@ bool LoadWallet() | |||
| 974 | // this file was written are not spendable from it. Backing up once is not | 1705 | // this file was written are not spendable from it. Backing up once is not |
| 975 | // enough, and that is a property of the wallet format rather than of this | 1706 | // enough, and that is a property of the wallet format rather than of this |
| 976 | // function. | 1707 | // function. |
| 1708 | // Back up the active SQLite wallet. The store is checkpointed so the WAL is | ||
| 1709 | // folded into the main file, then the single self-contained wallet.sqlite is | ||
| 1710 | // copied through a temp file and renamed into place (same temp/old/rename dance | ||
| 1711 | // as the Berkeley DB path, for Windows' non-overwriting rename). No lsn_reset: | ||
| 1712 | // a checkpointed SQLite file needs nothing beside it to open. | ||
| 1713 | static bool BackupWalletSQLite(const string& strDest) | ||
| 1714 | { | ||
| 1715 | string strSrc = GetAppDir() + "/wallet.sqlite"; | ||
| 1716 | if (!FileExists(strSrc.c_str())) | ||
| 1717 | return error("BackupWallet() : %s does not exist\n", strSrc.c_str()); | ||
| 1718 | |||
| 1719 | string strCheckpointError; | ||
| 1720 | if (!WalletSQLiteRuntimeCheckpoint(strCheckpointError)) | ||
| 1721 | return error("BackupWallet() : checkpoint failed: %s\n", strCheckpointError.c_str()); | ||
| 1722 | |||
| 1723 | string strTemp = strprintf("%s.tmp.%lld", strDest.c_str(), GetTime()); | ||
| 1724 | for (int i = 1; FileExists(strTemp.c_str()); i++) | ||
| 1725 | strTemp = strprintf("%s.tmp.%lld.%d", strDest.c_str(), GetTime(), i); | ||
| 1726 | string strOld = strprintf("%s.old.%lld", strDest.c_str(), GetTime()); | ||
| 1727 | for (int i = 1; FileExists(strOld.c_str()); i++) | ||
| 1728 | strOld = strprintf("%s.old.%lld.%d", strDest.c_str(), GetTime(), i); | ||
| 1729 | |||
| 1730 | FILE* pfIn = fopen(strSrc.c_str(), "rb"); | ||
| 1731 | if (!pfIn) | ||
| 1732 | return error("BackupWallet() : cannot read %s\n", strSrc.c_str()); | ||
| 1733 | FILE* pfOut = fopen(strTemp.c_str(), "wb"); | ||
| 1734 | if (!pfOut) | ||
| 1735 | { | ||
| 1736 | fclose(pfIn); | ||
| 1737 | return error("BackupWallet() : cannot write %s\n", strTemp.c_str()); | ||
| 1738 | } | ||
| 1739 | |||
| 1740 | char buf[65536]; | ||
| 1741 | size_t n; | ||
| 1742 | bool fOk = true; | ||
| 1743 | while ((n = fread(buf, 1, sizeof(buf), pfIn)) > 0) | ||
| 1744 | if (fwrite(buf, 1, n, pfOut) != n) { fOk = false; break; } | ||
| 1745 | if (ferror(pfIn)) | ||
| 1746 | fOk = false; | ||
| 1747 | fclose(pfIn); | ||
| 1748 | if (fclose(pfOut) != 0) | ||
| 1749 | fOk = false; | ||
| 1750 | if (!fOk) | ||
| 1751 | { | ||
| 1752 | remove(strTemp.c_str()); // half a wallet is worse than none | ||
| 1753 | return error("BackupWallet() : copy to %s failed\n", strTemp.c_str()); | ||
| 1754 | } | ||
| 1755 | |||
| 1756 | bool fHadOldDest = FileExists(strDest.c_str()); | ||
| 1757 | if (fHadOldDest && rename(strDest.c_str(), strOld.c_str()) != 0) | ||
| 1758 | { | ||
| 1759 | remove(strTemp.c_str()); | ||
| 1760 | return error("BackupWallet() : could not move old %s aside\n", strDest.c_str()); | ||
| 1761 | } | ||
| 1762 | if (rename(strTemp.c_str(), strDest.c_str()) != 0) | ||
| 1763 | { | ||
| 1764 | remove(strTemp.c_str()); | ||
| 1765 | if (fHadOldDest) | ||
| 1766 | rename(strOld.c_str(), strDest.c_str()); | ||
| 1767 | return error("BackupWallet() : could not install %s\n", strDest.c_str()); | ||
| 1768 | } | ||
| 1769 | if (fHadOldDest) | ||
| 1770 | remove(strOld.c_str()); | ||
| 1771 | return true; | ||
| 1772 | } | ||
| 1773 | |||
| 977 | bool BackupWallet(const string& strDest) | 1774 | bool BackupWallet(const string& strDest) |
| 978 | { | 1775 | { |
| 979 | if (strDest.empty()) | 1776 | if (strDest.empty()) |
| 980 | return error("BackupWallet() : no destination given\n"); | 1777 | return error("BackupWallet() : no destination given\n"); |
| 981 | 1778 | ||
| 1779 | // Under the SQLite backend the live wallet is wallet.sqlite, not wallet.dat. | ||
| 1780 | if (WalletSQLiteRuntimeActive()) | ||
| 1781 | return BackupWalletSQLite(strDest); | ||
| 1782 | |||
| 982 | string strSrc = GetAppDir() + "/wallet.dat"; | 1783 | string strSrc = GetAppDir() + "/wallet.dat"; |
| 983 | if (!FileExists(strSrc.c_str())) | 1784 | if (!FileExists(strSrc.c_str())) |
| 984 | return error("BackupWallet() : %s does not exist\n", strSrc.c_str()); | 1785 | return error("BackupWallet() : %s does not exist\n", strSrc.c_str()); |
| 985 | 1786 | ||
| 1787 | string strTemp = strprintf("%s.tmp.%lld", strDest.c_str(), GetTime()); | ||
| 1788 | for (int i = 1; FileExists(strTemp.c_str()); i++) | ||
| 1789 | strTemp = strprintf("%s.tmp.%lld.%d", strDest.c_str(), GetTime(), i); | ||
| 1790 | string strOld = strprintf("%s.old.%lld", strDest.c_str(), GetTime()); | ||
| 1791 | for (int i = 1; FileExists(strOld.c_str()); i++) | ||
| 1792 | strOld = strprintf("%s.old.%lld.%d", strDest.c_str(), GetTime(), i); | ||
| 1793 | |||
| 986 | CRITICAL_BLOCK(cs_db) | 1794 | CRITICAL_BLOCK(cs_db) |
| 987 | { | 1795 | { |
| 988 | // Committing first, so the copy is not missing the newest records. | 1796 | // Committing first, so the copy is not missing the newest records. |
| 989 | // Both calls can throw, and a failed backup must not take the node | 1797 | // Both calls can throw, and a failed backup must not take the node |
| 990 | // down with it. | 1798 | // down with it. |
| 991 | try { dbenv.txn_checkpoint(0, 0, 0); } | 1799 | DbEnv& env = GetDbEnv(); |
| 1800 | try { env.txn_checkpoint(0, 0, 0); } | ||
| 992 | catch (const std::exception& e) | 1801 | catch (const std::exception& e) |
| 993 | { return error("BackupWallet() : checkpoint failed: %s\n", e.what()); } | 1802 | { return error("BackupWallet() : checkpoint failed: %s\n", e.what()); } |
| 994 | 1803 | ||
| @@ -997,11 +1806,11 @@ bool BackupWallet(const string& strDest) | |||
| 997 | FILE* pfIn = fopen(strSrc.c_str(), "rb"); | 1806 | FILE* pfIn = fopen(strSrc.c_str(), "rb"); |
| 998 | if (!pfIn) | 1807 | if (!pfIn) |
| 999 | return error("BackupWallet() : cannot read %s\n", strSrc.c_str()); | 1808 | return error("BackupWallet() : cannot read %s\n", strSrc.c_str()); |
| 1000 | FILE* pfOut = fopen(strDest.c_str(), "wb"); | 1809 | FILE* pfOut = fopen(strTemp.c_str(), "wb"); |
| 1001 | if (!pfOut) | 1810 | if (!pfOut) |
| 1002 | { | 1811 | { |
| 1003 | fclose(pfIn); | 1812 | fclose(pfIn); |
| 1004 | return error("BackupWallet() : cannot write %s\n", strDest.c_str()); | 1813 | return error("BackupWallet() : cannot write %s\n", strTemp.c_str()); |
| 1005 | } | 1814 | } |
| 1006 | 1815 | ||
| 1007 | char buf[65536]; | 1816 | char buf[65536]; |
| @@ -1018,18 +1827,39 @@ bool BackupWallet(const string& strDest) | |||
| 1018 | 1827 | ||
| 1019 | if (!fOk) | 1828 | if (!fOk) |
| 1020 | { | 1829 | { |
| 1021 | remove(strDest.c_str()); // half a wallet is worse than none | 1830 | remove(strTemp.c_str()); // half a wallet is worse than none |
| 1022 | return error("BackupWallet() : copy to %s failed\n", strDest.c_str()); | 1831 | return error("BackupWallet() : copy to %s failed\n", strTemp.c_str()); |
| 1023 | } | 1832 | } |
| 1024 | 1833 | ||
| 1025 | // Without this the copy is welded to this node's database/ dir. | 1834 | // Without this the copy is welded to this node's database/ dir. |
| 1026 | int ret = dbenv.lsn_reset(strDest.c_str(), 0); | 1835 | int ret = env.lsn_reset(strTemp.c_str(), 0); |
| 1027 | if (ret != 0) | 1836 | if (ret != 0) |
| 1028 | printf("BackupWallet() : warning -- lsn_reset returned %d; the copy " | 1837 | printf("BackupWallet() : warning -- lsn_reset returned %d; the copy " |
| 1029 | "may only open beside this node's database/ directory\n", ret); | 1838 | "may only open beside this node's database/ directory\n", ret); |
| 1839 | |||
| 1840 | bool fHadOldDest = FileExists(strDest.c_str()); | ||
| 1841 | if (fHadOldDest && rename(strDest.c_str(), strOld.c_str()) != 0) | ||
| 1842 | { | ||
| 1843 | remove(strTemp.c_str()); | ||
| 1844 | return error("BackupWallet() : could not move old %s aside\n", strDest.c_str()); | ||
| 1845 | } | ||
| 1846 | if (rename(strTemp.c_str(), strDest.c_str()) != 0) | ||
| 1847 | { | ||
| 1848 | remove(strTemp.c_str()); | ||
| 1849 | if (fHadOldDest) | ||
| 1850 | rename(strOld.c_str(), strDest.c_str()); | ||
| 1851 | return error("BackupWallet() : could not install %s\n", strDest.c_str()); | ||
| 1852 | } | ||
| 1853 | if (fHadOldDest) | ||
| 1854 | remove(strOld.c_str()); | ||
| 1030 | } | 1855 | } |
| 1031 | catch (const std::exception& e) | 1856 | catch (const std::exception& e) |
| 1032 | { return error("BackupWallet() : %s\n", e.what()); } | 1857 | { |
| 1858 | remove(strTemp.c_str()); | ||
| 1859 | if (FileExists(strOld.c_str()) && !FileExists(strDest.c_str())) | ||
| 1860 | rename(strOld.c_str(), strDest.c_str()); | ||
| 1861 | return error("BackupWallet() : %s\n", e.what()); | ||
| 1862 | } | ||
| 1033 | } | 1863 | } |
| 1034 | 1864 | ||
| 1035 | // Recorded here rather than by whoever called, so that a backup taken with | 1865 | // Recorded here rather than by whoever called, so that a backup taken with |
| @@ -1307,73 +2137,71 @@ public: | |||
| 1307 | { | 2137 | { |
| 1308 | return Write(make_pair(string("pool"), nIndex), vchPubKey); | 2138 | return Write(make_pair(string("pool"), nIndex), vchPubKey); |
| 1309 | } | 2139 | } |
| 2140 | }; | ||
| 2141 | |||
| 2142 | static bool IsWalletEncryptionPrivateRecord(const string& strType) | ||
| 2143 | { | ||
| 2144 | return strType == "key" || | ||
| 2145 | strType == "pool" || | ||
| 2146 | strType == "hdmaster" || | ||
| 2147 | strType == "hdchaincode" || | ||
| 2148 | strType == "mkey" || | ||
| 2149 | strType == "ckey" || | ||
| 2150 | strType == "cryptedhdmaster" || | ||
| 2151 | strType == "cryptedhdchaincode" || | ||
| 2152 | strType == "walletminversion"; | ||
| 2153 | } | ||
| 2154 | |||
| 2155 | class CWalletPublicRecordCopyVisitor : public CWalletRecordVisitor | ||
| 2156 | { | ||
| 2157 | public: | ||
| 2158 | CWalletRewriteDB& dbTo; | ||
| 2159 | int& nCopied; | ||
| 1310 | 2160 | ||
| 1311 | bool CopyPublicRecordsTo(CWalletRewriteDB& dbTo, int& nCopiedRet, string& strErrorRet) | 2161 | CWalletPublicRecordCopyVisitor(CWalletRewriteDB& dbToIn, int& nCopiedIn) |
| 2162 | : dbTo(dbToIn), nCopied(nCopiedIn) { } | ||
| 2163 | |||
| 2164 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 2165 | const CDataStream& ssValue, | ||
| 2166 | string& strErrorRet) | ||
| 1312 | { | 2167 | { |
| 1313 | nCopiedRet = 0; | 2168 | string strType; |
| 1314 | Dbc* pcursor = GetCursor(); | 2169 | try |
| 1315 | if (!pcursor) | ||
| 1316 | { | 2170 | { |
| 1317 | strErrorRet = "could not open a wallet cursor"; | 2171 | CDataStream ssType = ssKey; |
| 1318 | return false; | 2172 | ssType >> strType; |
| 1319 | } | 2173 | } |
| 1320 | 2174 | catch (...) | |
| 1321 | unsigned int fFlags = DB_NEXT; | ||
| 1322 | loop | ||
| 1323 | { | 2175 | { |
| 1324 | CDataStream ssKey; | 2176 | strErrorRet = "wallet.dat has a record whose key type cannot be read"; |
| 1325 | CDataStream ssValue; | 2177 | return false; |
| 1326 | int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags); | 2178 | } |
| 1327 | if (ret == DB_NOTFOUND) | ||
| 1328 | break; | ||
| 1329 | if (ret != 0) | ||
| 1330 | { | ||
| 1331 | pcursor->close(); | ||
| 1332 | strErrorRet = strprintf("could not read wallet record: Berkeley DB error %d", ret); | ||
| 1333 | return false; | ||
| 1334 | } | ||
| 1335 | |||
| 1336 | string strType; | ||
| 1337 | try | ||
| 1338 | { | ||
| 1339 | CDataStream ssType = ssKey; | ||
| 1340 | ssType >> strType; | ||
| 1341 | } | ||
| 1342 | catch (...) | ||
| 1343 | { | ||
| 1344 | pcursor->close(); | ||
| 1345 | strErrorRet = "wallet.dat has a record whose key type cannot be read"; | ||
| 1346 | return false; | ||
| 1347 | } | ||
| 1348 | 2179 | ||
| 1349 | if (strType == "key" || | 2180 | if (IsWalletEncryptionPrivateRecord(strType)) |
| 1350 | strType == "pool" || | 2181 | return true; |
| 1351 | strType == "hdmaster" || | ||
| 1352 | strType == "hdchaincode" || | ||
| 1353 | strType == "mkey" || | ||
| 1354 | strType == "ckey" || | ||
| 1355 | strType == "cryptedhdmaster" || | ||
| 1356 | strType == "cryptedhdchaincode" || | ||
| 1357 | strType == "walletminversion") | ||
| 1358 | continue; | ||
| 1359 | 2182 | ||
| 1360 | if (!dbTo.WriteRaw(ssKey, ssValue)) | 2183 | if (!dbTo.WriteRaw(ssKey, ssValue)) |
| 1361 | { | 2184 | { |
| 1362 | pcursor->close(); | 2185 | strErrorRet = strprintf("could not copy wallet record '%s'", strType.c_str()); |
| 1363 | strErrorRet = strprintf("could not copy wallet record '%s'", strType.c_str()); | 2186 | return false; |
| 1364 | return false; | ||
| 1365 | } | ||
| 1366 | nCopiedRet++; | ||
| 1367 | } | 2187 | } |
| 1368 | 2188 | nCopied++; | |
| 1369 | pcursor->close(); | ||
| 1370 | return true; | 2189 | return true; |
| 1371 | } | 2190 | } |
| 1372 | }; | 2191 | }; |
| 1373 | 2192 | ||
| 1374 | static string WalletEncryptBackupPath() | 2193 | static bool CopyPublicWalletRecordsTo(CWalletRewriteDB& dbTo, |
| 2194 | int& nCopiedRet, | ||
| 2195 | string& strErrorRet) | ||
| 1375 | { | 2196 | { |
| 1376 | string strBase = GetAppDir() + "/wallet.dat.before-encrypt."; | 2197 | nCopiedRet = 0; |
| 2198 | CWalletPublicRecordCopyVisitor visitor(dbTo, nCopiedRet); | ||
| 2199 | return ScanWalletRecords(visitor, strErrorRet); | ||
| 2200 | } | ||
| 2201 | |||
| 2202 | static string WalletEncryptBackupPathFor(const char* pszBaseName) | ||
| 2203 | { | ||
| 2204 | string strBase = GetAppDir() + "/" + pszBaseName + ".before-encrypt."; | ||
| 1377 | string strPath = strBase + i64tostr(GetTime()) + ".bak"; | 2205 | string strPath = strBase + i64tostr(GetTime()) + ".bak"; |
| 1378 | for (int i = 1; FileExists(strPath.c_str()); i++) | 2206 | for (int i = 1; FileExists(strPath.c_str()); i++) |
| 1379 | strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak"; | 2207 | strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak"; |
| @@ -1403,6 +2231,192 @@ static void SecureClearBytes(vector<unsigned char>& vch) | |||
| 1403 | vch.clear(); | 2231 | vch.clear(); |
| 1404 | } | 2232 | } |
| 1405 | 2233 | ||
| 2234 | // The SQLite counterpart of CWalletPublicRecordCopyVisitor: same skip list, | ||
| 2235 | // same byte-for-byte copy, but the destination is a CWalletDBSQLite record | ||
| 2236 | // store rather than a Berkeley DB file. | ||
| 2237 | class CWalletPublicRecordCopyToSQLiteVisitor : public CWalletRecordVisitor | ||
| 2238 | { | ||
| 2239 | public: | ||
| 2240 | CWalletDBSQLite& dbTo; | ||
| 2241 | int& nCopied; | ||
| 2242 | |||
| 2243 | CWalletPublicRecordCopyToSQLiteVisitor(CWalletDBSQLite& dbToIn, int& nCopiedIn) | ||
| 2244 | : dbTo(dbToIn), nCopied(nCopiedIn) { } | ||
| 2245 | |||
| 2246 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 2247 | const CDataStream& ssValue, | ||
| 2248 | string& strErrorRet) | ||
| 2249 | { | ||
| 2250 | string strType; | ||
| 2251 | try | ||
| 2252 | { | ||
| 2253 | CDataStream ssType = ssKey; | ||
| 2254 | ssType >> strType; | ||
| 2255 | } | ||
| 2256 | catch (...) | ||
| 2257 | { | ||
| 2258 | strErrorRet = "SQLite wallet has a record whose key type cannot be read"; | ||
| 2259 | return false; | ||
| 2260 | } | ||
| 2261 | |||
| 2262 | if (IsWalletEncryptionPrivateRecord(strType)) | ||
| 2263 | return true; | ||
| 2264 | |||
| 2265 | vector<unsigned char> vchKey(ssKey.begin(), ssKey.end()); | ||
| 2266 | vector<unsigned char> vchValue(ssValue.begin(), ssValue.end()); | ||
| 2267 | bool fOk = dbTo.WriteRecord(vchKey, vchValue, strErrorRet, true); | ||
| 2268 | if (!vchKey.empty()) | ||
| 2269 | memset(&vchKey[0], 0, vchKey.size()); | ||
| 2270 | if (!vchValue.empty()) | ||
| 2271 | memset(&vchValue[0], 0, vchValue.size()); | ||
| 2272 | if (!fOk) | ||
| 2273 | { | ||
| 2274 | strErrorRet = strprintf("could not copy wallet record '%s': %s", | ||
| 2275 | strType.c_str(), strErrorRet.c_str()); | ||
| 2276 | return false; | ||
| 2277 | } | ||
| 2278 | nCopied++; | ||
| 2279 | return true; | ||
| 2280 | } | ||
| 2281 | }; | ||
| 2282 | |||
| 2283 | // Persist an encrypted wallet under the SQLite backend. Everything the caller | ||
| 2284 | // computed -- the master key, the re-encrypted private keys, the fresh crypted | ||
| 2285 | // key pool, the encrypted HD seed -- lands in a brand new wallet.sqlite built | ||
| 2286 | // in one transaction, which is then swapped in for the plaintext original. | ||
| 2287 | // | ||
| 2288 | // A fresh file rather than an in-place rewrite for two reasons: it keeps the | ||
| 2289 | // whole change atomic (a crash before the rename leaves the old wallet whole), | ||
| 2290 | // and it guarantees no plaintext key survives in a freed page the way an | ||
| 2291 | // in-place DELETE would leave behind until a VACUUM. | ||
| 2292 | static bool EncryptWalletPersistSQLite( | ||
| 2293 | const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vCryptedKeys, | ||
| 2294 | const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vNewPoolKeys, | ||
| 2295 | const vector<pair<int64, vector<unsigned char> > >& vNewPool, | ||
| 2296 | const CWalletMasterKey& kMasterKey, | ||
| 2297 | const vector<unsigned char>& vchCryptedHDMasterNew, | ||
| 2298 | const vector<unsigned char>& vchCryptedHDChainCodeNew, | ||
| 2299 | unsigned int nHDNextNew, | ||
| 2300 | int& nCopiedRet, | ||
| 2301 | string& strBackupRet, | ||
| 2302 | string& strErrorRet) | ||
| 2303 | { | ||
| 2304 | nCopiedRet = 0; | ||
| 2305 | if (!pWalletSQLiteRuntime) | ||
| 2306 | { | ||
| 2307 | strErrorRet = "SQLite wallet backend is not active"; | ||
| 2308 | return false; | ||
| 2309 | } | ||
| 2310 | |||
| 2311 | string strWalletPath = GetAppDir() + "/wallet.sqlite"; | ||
| 2312 | string strTempPath = GetAppDir() + "/" + | ||
| 2313 | strprintf("wallet.sqlite.encrypting.%lld", GetTime()); | ||
| 2314 | remove(strTempPath.c_str()); | ||
| 2315 | remove((strTempPath + "-wal").c_str()); | ||
| 2316 | remove((strTempPath + "-shm").c_str()); | ||
| 2317 | |||
| 2318 | { | ||
| 2319 | CWalletDBSQLite dbTmp; | ||
| 2320 | if (!dbTmp.Open(strTempPath, strErrorRet)) | ||
| 2321 | { | ||
| 2322 | remove(strTempPath.c_str()); | ||
| 2323 | return false; | ||
| 2324 | } | ||
| 2325 | if (!dbTmp.BeginTransaction(strErrorRet)) | ||
| 2326 | { | ||
| 2327 | dbTmp.Close(); | ||
| 2328 | remove(strTempPath.c_str()); | ||
| 2329 | return false; | ||
| 2330 | } | ||
| 2331 | |||
| 2332 | // The public records come from the live SQLite store -- under this | ||
| 2333 | // backend ScanWalletRecords() has no Berkeley DB behind it to read. | ||
| 2334 | CWalletPublicRecordCopyToSQLiteVisitor visitor(dbTmp, nCopiedRet); | ||
| 2335 | bool fOk = pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet); | ||
| 2336 | |||
| 2337 | // Encrypted metadata, serialized byte-for-byte the way the Berkeley DB | ||
| 2338 | // path (CWalletRewriteDB) writes it, so a wallet encrypted under either | ||
| 2339 | // backend is the same set of records. ckey uses INSERT-only to match | ||
| 2340 | // WriteCryptedKey; the rest overwrite. | ||
| 2341 | fOk = fOk && | ||
| 2342 | dbTmp.WriteTypedRecord(string("walletminversion"), | ||
| 2343 | (int)WALLET_FORMAT_ENCRYPTED, strErrorRet, true) && | ||
| 2344 | dbTmp.WriteTypedRecord(make_pair(string("mkey"), | ||
| 2345 | (unsigned int)(nWalletMasterKeyMaxID + 1)), | ||
| 2346 | kMasterKey, strErrorRet, true); | ||
| 2347 | for (size_t i = 0; fOk && i < vCryptedKeys.size(); i++) | ||
| 2348 | fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vCryptedKeys[i].first), | ||
| 2349 | vCryptedKeys[i].second, strErrorRet, false); | ||
| 2350 | for (size_t i = 0; fOk && i < vNewPoolKeys.size(); i++) | ||
| 2351 | fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vNewPoolKeys[i].first), | ||
| 2352 | vNewPoolKeys[i].second, strErrorRet, false); | ||
| 2353 | if (fOk && !vchCryptedHDMasterNew.empty()) | ||
| 2354 | fOk = dbTmp.WriteTypedRecord(string("cryptedhdmaster"), | ||
| 2355 | vchCryptedHDMasterNew, strErrorRet, true) && | ||
| 2356 | dbTmp.WriteTypedRecord(string("cryptedhdchaincode"), | ||
| 2357 | vchCryptedHDChainCodeNew, strErrorRet, true); | ||
| 2358 | if (fOk) | ||
| 2359 | fOk = dbTmp.WriteTypedRecord(string("hdnext"), nHDNextNew, strErrorRet, true); | ||
| 2360 | for (size_t i = 0; fOk && i < vNewPool.size(); i++) | ||
| 2361 | fOk = dbTmp.WriteTypedRecord(make_pair(string("pool"), vNewPool[i].first), | ||
| 2362 | vNewPool[i].second, strErrorRet, true); | ||
| 2363 | |||
| 2364 | if (!fOk) | ||
| 2365 | { | ||
| 2366 | string strIgnore; | ||
| 2367 | dbTmp.RollbackTransaction(strIgnore); | ||
| 2368 | dbTmp.Close(); | ||
| 2369 | remove(strTempPath.c_str()); | ||
| 2370 | remove((strTempPath + "-wal").c_str()); | ||
| 2371 | remove((strTempPath + "-shm").c_str()); | ||
| 2372 | return false; | ||
| 2373 | } | ||
| 2374 | |||
| 2375 | if (!dbTmp.CommitTransaction(strErrorRet)) | ||
| 2376 | { | ||
| 2377 | dbTmp.Close(); | ||
| 2378 | remove(strTempPath.c_str()); | ||
| 2379 | remove((strTempPath + "-wal").c_str()); | ||
| 2380 | remove((strTempPath + "-shm").c_str()); | ||
| 2381 | return false; | ||
| 2382 | } | ||
| 2383 | string strCheckpointError; | ||
| 2384 | dbTmp.Checkpoint(strCheckpointError); | ||
| 2385 | dbTmp.Close(); | ||
| 2386 | } | ||
| 2387 | |||
| 2388 | // The rebuilt wallet is committed and self-contained. Close the live handle | ||
| 2389 | // so the file can be swapped: Windows will not rename a file that still has | ||
| 2390 | // an open handle, and the node exits right after this. | ||
| 2391 | WalletSQLiteRuntimeClose(); | ||
| 2392 | |||
| 2393 | string strBackupPath = WalletEncryptBackupPathFor("wallet.sqlite"); | ||
| 2394 | if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) | ||
| 2395 | { | ||
| 2396 | strErrorRet = strprintf("could not move the original SQLite wallet to %s", | ||
| 2397 | strBackupPath.c_str()); | ||
| 2398 | remove(strTempPath.c_str()); | ||
| 2399 | return false; | ||
| 2400 | } | ||
| 2401 | // The moved-aside wallet owns any leftover write-ahead log or shared-memory | ||
| 2402 | // sidecar; drop stale ones so they cannot shadow the new wallet. | ||
| 2403 | remove((strWalletPath + "-wal").c_str()); | ||
| 2404 | remove((strWalletPath + "-shm").c_str()); | ||
| 2405 | |||
| 2406 | if (rename(strTempPath.c_str(), strWalletPath.c_str()) != 0) | ||
| 2407 | { | ||
| 2408 | rename(strBackupPath.c_str(), strWalletPath.c_str()); | ||
| 2409 | strErrorRet = "could not install the encrypted SQLite wallet; the original was restored"; | ||
| 2410 | remove(strTempPath.c_str()); | ||
| 2411 | return false; | ||
| 2412 | } | ||
| 2413 | remove((strTempPath + "-wal").c_str()); | ||
| 2414 | remove((strTempPath + "-shm").c_str()); | ||
| 2415 | |||
| 2416 | strBackupRet = strBackupPath; | ||
| 2417 | return true; | ||
| 2418 | } | ||
| 2419 | |||
| 1406 | bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) | 2420 | bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) |
| 1407 | { | 2421 | { |
| 1408 | strBackupRet.clear(); | 2422 | strBackupRet.clear(); |
| @@ -1541,15 +2555,32 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st | |||
| 1541 | vNewPool.push_back(make_pair((int64)i + 1, vchPubKey)); | 2555 | vNewPool.push_back(make_pair((int64)i + 1, vchPubKey)); |
| 1542 | } | 2556 | } |
| 1543 | 2557 | ||
| 2558 | // Every ciphertext is computed; nothing has touched the store yet. Under | ||
| 2559 | // the SQLite backend, persist by rebuilding wallet.sqlite in one atomic | ||
| 2560 | // transaction and swapping it in. The Berkeley DB path below is unchanged. | ||
| 2561 | if (WalletSQLiteRuntimeActive()) | ||
| 2562 | { | ||
| 2563 | int nCopiedSQLite = 0; | ||
| 2564 | if (!EncryptWalletPersistSQLite(vCryptedKeys, vNewPoolKeys, vNewPool, | ||
| 2565 | kMasterKey, vchCryptedHDMasterNew, | ||
| 2566 | vchCryptedHDChainCodeNew, nHDNextNew, | ||
| 2567 | nCopiedSQLite, strBackupRet, strErrorRet)) | ||
| 2568 | return false; | ||
| 2569 | printf("EncryptWallet() : copied %d public record(s), encrypted %u key(s), " | ||
| 2570 | "rebuilt %d key-pool entry(s) [SQLite]\n", | ||
| 2571 | nCopiedSQLite, (unsigned int)(vCryptedKeys.size() + vNewPoolKeys.size()), | ||
| 2572 | (int)vNewPool.size()); | ||
| 2573 | return true; | ||
| 2574 | } | ||
| 2575 | |||
| 1544 | string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime()); | 2576 | string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime()); |
| 1545 | string strTempPath = GetAppDir() + "/" + strTempFile; | 2577 | string strTempPath = GetAppDir() + "/" + strTempFile; |
| 1546 | remove(strTempPath.c_str()); | 2578 | remove(strTempPath.c_str()); |
| 1547 | 2579 | ||
| 1548 | int nCopied = 0; | 2580 | int nCopied = 0; |
| 1549 | { | 2581 | { |
| 1550 | CWalletRewriteDB dbSource("wallet.dat", "r"); | ||
| 1551 | CWalletRewriteDB dbTarget(strTempFile.c_str(), "c+"); | 2582 | CWalletRewriteDB dbTarget(strTempFile.c_str(), "c+"); |
| 1552 | if (!dbSource.CopyPublicRecordsTo(dbTarget, nCopied, strErrorRet)) | 2583 | if (!CopyPublicWalletRecordsTo(dbTarget, nCopied, strErrorRet)) |
| 1553 | { | 2584 | { |
| 1554 | remove(strTempPath.c_str()); | 2585 | remove(strTempPath.c_str()); |
| 1555 | return false; | 2586 | return false; |
| @@ -1609,7 +2640,7 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st | |||
| 1609 | DBFlush(true); | 2640 | DBFlush(true); |
| 1610 | 2641 | ||
| 1611 | string strWalletPath = GetAppDir() + "/wallet.dat"; | 2642 | string strWalletPath = GetAppDir() + "/wallet.dat"; |
| 1612 | string strBackupPath = WalletEncryptBackupPath(); | 2643 | string strBackupPath = WalletEncryptBackupPathFor("wallet.dat"); |
| 1613 | if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) | 2644 | if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) |
| 1614 | { | 2645 | { |
| 1615 | strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str()); | 2646 | strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str()); |