upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-18 15:48:17 -0300
committerBitflash <admin@bitflash.network>2026-08-18 15:48:17 -0300
commitf3fb7ce6759ac98a7cc39d586fc2824982c74acd (patch)
tree23e39a26b340324a7a33a115aded3b70b5332816
parentbeb5c1435a0d85b7b24d4d371933758ebed90907 (diff)
parent84a60275569dca5d1fb12db38ce184caf7487ed6 (diff)
Merge pull request 'wallet: complete SQLite backend activation (encrypt, atomicity, one-shot commands)' (#61) from wallet-sqlite-encrypt into main
-rw-r--r--src/db.cpp356
-rw-r--r--src/db.h22
-rw-r--r--src/main_gui.cpp50
-rw-r--r--src/makefile.mingw1
-rw-r--r--src/selftest.cpp286
-rw-r--r--src/walletcmd.cpp15
6 files changed, 717 insertions, 13 deletions
diff --git a/src/db.cpp b/src/db.cpp
index 771a4ba..aafc0f9 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -164,6 +164,72 @@ bool WalletSQLiteRuntimeEraseRecord(const vector<unsigned char>& vchKey)
164 return pWalletSQLiteRuntime->EraseRecord(vchKey, strError); 164 return pWalletSQLiteRuntime->EraseRecord(vchKey, strError);
165} 165}
166 166
167// Transaction bracket for the SQLite runtime. When the SQLite backend is not
168// active these are no-ops that succeed, so a caller can wrap a group of related
169// writes and Berkeley DB keeps its existing per-write behaviour unchanged while
170// SQLite gets all-or-nothing.
171bool WalletSQLiteRuntimeBeginTxn()
172{
173 if (!pWalletSQLiteRuntime)
174 return true;
175 string strError;
176 if (pWalletSQLiteRuntime->BeginTransaction(strError))
177 return true;
178 printf("WalletSQLiteRuntimeBeginTxn() : %s\n", strError.c_str());
179 return false;
180}
181
182bool WalletSQLiteRuntimeCommitTxn()
183{
184 if (!pWalletSQLiteRuntime)
185 return true;
186 string strError;
187 if (pWalletSQLiteRuntime->CommitTransaction(strError))
188 return true;
189 printf("WalletSQLiteRuntimeCommitTxn() : %s\n", strError.c_str());
190 return false;
191}
192
193void WalletSQLiteRuntimeRollbackTxn()
194{
195 if (!pWalletSQLiteRuntime)
196 return;
197 string strError;
198 pWalletSQLiteRuntime->RollbackTransaction(strError);
199}
200
201// Open the SQLite wallet read-only as the active runtime. Used by the one-shot
202// storage diagnostics, which run before the normal wallet load and must be able
203// to inspect a wallet that the loader would refuse -- so this opens the file for
204// scanning without loading or validating its records.
205bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet)
206{
207 strErrorRet.clear();
208 if (!FileExists(strPath.c_str()))
209 {
210 strErrorRet = strprintf("SQLite wallet does not exist: %s", strPath.c_str());
211 return false;
212 }
213
214 WalletSQLiteRuntimeClose();
215 std::auto_ptr<CWalletDBSQLite> db(new CWalletDBSQLite());
216 if (!db->OpenReadOnly(strPath, strErrorRet))
217 return false;
218
219 pWalletSQLiteRuntime = db.release();
220 return true;
221}
222
223bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet)
224{
225 if (!pWalletSQLiteRuntime)
226 {
227 strErrorRet = "SQLite wallet backend is not active";
228 return false;
229 }
230 return pWalletSQLiteRuntime->Checkpoint(strErrorRet);
231}
232
167 233
168CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) 234CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL)
169{ 235{
@@ -1320,6 +1386,16 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
1320 1386
1321bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet) 1387bool ScanWalletRecords(CWalletRecordVisitor& visitor, string& strErrorRet)
1322{ 1388{
1389 // Under the SQLite backend there is no Berkeley DB cursor to walk; scan the
1390 // active SQLite store instead. This is the single choke point every "read
1391 // every wallet record" caller goes through (storage audit and check among
1392 // them), so routing it here makes those honour -walletbackend=sqlite.
1393 if (WalletSQLiteRuntimeActive())
1394 {
1395 strErrorRet.clear();
1396 return pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet);
1397 }
1398
1323 class CWalletScanDB : public CWalletDB 1399 class CWalletScanDB : public CWalletDB
1324 { 1400 {
1325 public: 1401 public:
@@ -1550,11 +1626,81 @@ bool LoadWalletFromSQLiteRuntime(const string& strPath)
1550// this file was written are not spendable from it. Backing up once is not 1626// this file was written are not spendable from it. Backing up once is not
1551// enough, and that is a property of the wallet format rather than of this 1627// enough, and that is a property of the wallet format rather than of this
1552// function. 1628// function.
1629// Back up the active SQLite wallet. The store is checkpointed so the WAL is
1630// folded into the main file, then the single self-contained wallet.sqlite is
1631// copied through a temp file and renamed into place (same temp/old/rename dance
1632// as the Berkeley DB path, for Windows' non-overwriting rename). No lsn_reset:
1633// a checkpointed SQLite file needs nothing beside it to open.
1634static bool BackupWalletSQLite(const string& strDest)
1635{
1636 string strSrc = GetAppDir() + "/wallet.sqlite";
1637 if (!FileExists(strSrc.c_str()))
1638 return error("BackupWallet() : %s does not exist\n", strSrc.c_str());
1639
1640 string strCheckpointError;
1641 if (!WalletSQLiteRuntimeCheckpoint(strCheckpointError))
1642 return error("BackupWallet() : checkpoint failed: %s\n", strCheckpointError.c_str());
1643
1644 string strTemp = strprintf("%s.tmp.%lld", strDest.c_str(), GetTime());
1645 for (int i = 1; FileExists(strTemp.c_str()); i++)
1646 strTemp = strprintf("%s.tmp.%lld.%d", strDest.c_str(), GetTime(), i);
1647 string strOld = strprintf("%s.old.%lld", strDest.c_str(), GetTime());
1648 for (int i = 1; FileExists(strOld.c_str()); i++)
1649 strOld = strprintf("%s.old.%lld.%d", strDest.c_str(), GetTime(), i);
1650
1651 FILE* pfIn = fopen(strSrc.c_str(), "rb");
1652 if (!pfIn)
1653 return error("BackupWallet() : cannot read %s\n", strSrc.c_str());
1654 FILE* pfOut = fopen(strTemp.c_str(), "wb");
1655 if (!pfOut)
1656 {
1657 fclose(pfIn);
1658 return error("BackupWallet() : cannot write %s\n", strTemp.c_str());
1659 }
1660
1661 char buf[65536];
1662 size_t n;
1663 bool fOk = true;
1664 while ((n = fread(buf, 1, sizeof(buf), pfIn)) > 0)
1665 if (fwrite(buf, 1, n, pfOut) != n) { fOk = false; break; }
1666 if (ferror(pfIn))
1667 fOk = false;
1668 fclose(pfIn);
1669 if (fclose(pfOut) != 0)
1670 fOk = false;
1671 if (!fOk)
1672 {
1673 remove(strTemp.c_str()); // half a wallet is worse than none
1674 return error("BackupWallet() : copy to %s failed\n", strTemp.c_str());
1675 }
1676
1677 bool fHadOldDest = FileExists(strDest.c_str());
1678 if (fHadOldDest && rename(strDest.c_str(), strOld.c_str()) != 0)
1679 {
1680 remove(strTemp.c_str());
1681 return error("BackupWallet() : could not move old %s aside\n", strDest.c_str());
1682 }
1683 if (rename(strTemp.c_str(), strDest.c_str()) != 0)
1684 {
1685 remove(strTemp.c_str());
1686 if (fHadOldDest)
1687 rename(strOld.c_str(), strDest.c_str());
1688 return error("BackupWallet() : could not install %s\n", strDest.c_str());
1689 }
1690 if (fHadOldDest)
1691 remove(strOld.c_str());
1692 return true;
1693}
1694
1553bool BackupWallet(const string& strDest) 1695bool BackupWallet(const string& strDest)
1554{ 1696{
1555 if (strDest.empty()) 1697 if (strDest.empty())
1556 return error("BackupWallet() : no destination given\n"); 1698 return error("BackupWallet() : no destination given\n");
1557 1699
1700 // Under the SQLite backend the live wallet is wallet.sqlite, not wallet.dat.
1701 if (WalletSQLiteRuntimeActive())
1702 return BackupWalletSQLite(strDest);
1703
1558 string strSrc = GetAppDir() + "/wallet.dat"; 1704 string strSrc = GetAppDir() + "/wallet.dat";
1559 if (!FileExists(strSrc.c_str())) 1705 if (!FileExists(strSrc.c_str()))
1560 return error("BackupWallet() : %s does not exist\n", strSrc.c_str()); 1706 return error("BackupWallet() : %s does not exist\n", strSrc.c_str());
@@ -1974,9 +2120,9 @@ static bool CopyPublicWalletRecordsTo(CWalletRewriteDB& dbTo,
1974 return ScanWalletRecords(visitor, strErrorRet); 2120 return ScanWalletRecords(visitor, strErrorRet);
1975} 2121}
1976 2122
1977static string WalletEncryptBackupPath() 2123static string WalletEncryptBackupPathFor(const char* pszBaseName)
1978{ 2124{
1979 string strBase = GetAppDir() + "/wallet.dat.before-encrypt."; 2125 string strBase = GetAppDir() + "/" + pszBaseName + ".before-encrypt.";
1980 string strPath = strBase + i64tostr(GetTime()) + ".bak"; 2126 string strPath = strBase + i64tostr(GetTime()) + ".bak";
1981 for (int i = 1; FileExists(strPath.c_str()); i++) 2127 for (int i = 1; FileExists(strPath.c_str()); i++)
1982 strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak"; 2128 strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak";
@@ -2006,6 +2152,192 @@ static void SecureClearBytes(vector<unsigned char>& vch)
2006 vch.clear(); 2152 vch.clear();
2007} 2153}
2008 2154
2155// The SQLite counterpart of CWalletPublicRecordCopyVisitor: same skip list,
2156// same byte-for-byte copy, but the destination is a CWalletDBSQLite record
2157// store rather than a Berkeley DB file.
2158class CWalletPublicRecordCopyToSQLiteVisitor : public CWalletRecordVisitor
2159{
2160public:
2161 CWalletDBSQLite& dbTo;
2162 int& nCopied;
2163
2164 CWalletPublicRecordCopyToSQLiteVisitor(CWalletDBSQLite& dbToIn, int& nCopiedIn)
2165 : dbTo(dbToIn), nCopied(nCopiedIn) { }
2166
2167 bool VisitWalletRecord(const CDataStream& ssKey,
2168 const CDataStream& ssValue,
2169 string& strErrorRet)
2170 {
2171 string strType;
2172 try
2173 {
2174 CDataStream ssType = ssKey;
2175 ssType >> strType;
2176 }
2177 catch (...)
2178 {
2179 strErrorRet = "SQLite wallet has a record whose key type cannot be read";
2180 return false;
2181 }
2182
2183 if (IsWalletEncryptionPrivateRecord(strType))
2184 return true;
2185
2186 vector<unsigned char> vchKey(ssKey.begin(), ssKey.end());
2187 vector<unsigned char> vchValue(ssValue.begin(), ssValue.end());
2188 bool fOk = dbTo.WriteRecord(vchKey, vchValue, strErrorRet, true);
2189 if (!vchKey.empty())
2190 memset(&vchKey[0], 0, vchKey.size());
2191 if (!vchValue.empty())
2192 memset(&vchValue[0], 0, vchValue.size());
2193 if (!fOk)
2194 {
2195 strErrorRet = strprintf("could not copy wallet record '%s': %s",
2196 strType.c_str(), strErrorRet.c_str());
2197 return false;
2198 }
2199 nCopied++;
2200 return true;
2201 }
2202};
2203
2204// Persist an encrypted wallet under the SQLite backend. Everything the caller
2205// computed -- the master key, the re-encrypted private keys, the fresh crypted
2206// key pool, the encrypted HD seed -- lands in a brand new wallet.sqlite built
2207// in one transaction, which is then swapped in for the plaintext original.
2208//
2209// A fresh file rather than an in-place rewrite for two reasons: it keeps the
2210// whole change atomic (a crash before the rename leaves the old wallet whole),
2211// and it guarantees no plaintext key survives in a freed page the way an
2212// in-place DELETE would leave behind until a VACUUM.
2213static bool EncryptWalletPersistSQLite(
2214 const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vCryptedKeys,
2215 const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vNewPoolKeys,
2216 const vector<pair<int64, vector<unsigned char> > >& vNewPool,
2217 const CWalletMasterKey& kMasterKey,
2218 const vector<unsigned char>& vchCryptedHDMasterNew,
2219 const vector<unsigned char>& vchCryptedHDChainCodeNew,
2220 unsigned int nHDNextNew,
2221 int& nCopiedRet,
2222 string& strBackupRet,
2223 string& strErrorRet)
2224{
2225 nCopiedRet = 0;
2226 if (!pWalletSQLiteRuntime)
2227 {
2228 strErrorRet = "SQLite wallet backend is not active";
2229 return false;
2230 }
2231
2232 string strWalletPath = GetAppDir() + "/wallet.sqlite";
2233 string strTempPath = GetAppDir() + "/" +
2234 strprintf("wallet.sqlite.encrypting.%lld", GetTime());
2235 remove(strTempPath.c_str());
2236 remove((strTempPath + "-wal").c_str());
2237 remove((strTempPath + "-shm").c_str());
2238
2239 {
2240 CWalletDBSQLite dbTmp;
2241 if (!dbTmp.Open(strTempPath, strErrorRet))
2242 {
2243 remove(strTempPath.c_str());
2244 return false;
2245 }
2246 if (!dbTmp.BeginTransaction(strErrorRet))
2247 {
2248 dbTmp.Close();
2249 remove(strTempPath.c_str());
2250 return false;
2251 }
2252
2253 // The public records come from the live SQLite store -- under this
2254 // backend ScanWalletRecords() has no Berkeley DB behind it to read.
2255 CWalletPublicRecordCopyToSQLiteVisitor visitor(dbTmp, nCopiedRet);
2256 bool fOk = pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet);
2257
2258 // Encrypted metadata, serialized byte-for-byte the way the Berkeley DB
2259 // path (CWalletRewriteDB) writes it, so a wallet encrypted under either
2260 // backend is the same set of records. ckey uses INSERT-only to match
2261 // WriteCryptedKey; the rest overwrite.
2262 fOk = fOk &&
2263 dbTmp.WriteTypedRecord(string("walletminversion"),
2264 (int)WALLET_FORMAT_ENCRYPTED, strErrorRet, true) &&
2265 dbTmp.WriteTypedRecord(make_pair(string("mkey"),
2266 (unsigned int)(nWalletMasterKeyMaxID + 1)),
2267 kMasterKey, strErrorRet, true);
2268 for (size_t i = 0; fOk && i < vCryptedKeys.size(); i++)
2269 fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vCryptedKeys[i].first),
2270 vCryptedKeys[i].second, strErrorRet, false);
2271 for (size_t i = 0; fOk && i < vNewPoolKeys.size(); i++)
2272 fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vNewPoolKeys[i].first),
2273 vNewPoolKeys[i].second, strErrorRet, false);
2274 if (fOk && !vchCryptedHDMasterNew.empty())
2275 fOk = dbTmp.WriteTypedRecord(string("cryptedhdmaster"),
2276 vchCryptedHDMasterNew, strErrorRet, true) &&
2277 dbTmp.WriteTypedRecord(string("cryptedhdchaincode"),
2278 vchCryptedHDChainCodeNew, strErrorRet, true);
2279 if (fOk)
2280 fOk = dbTmp.WriteTypedRecord(string("hdnext"), nHDNextNew, strErrorRet, true);
2281 for (size_t i = 0; fOk && i < vNewPool.size(); i++)
2282 fOk = dbTmp.WriteTypedRecord(make_pair(string("pool"), vNewPool[i].first),
2283 vNewPool[i].second, strErrorRet, true);
2284
2285 if (!fOk)
2286 {
2287 string strIgnore;
2288 dbTmp.RollbackTransaction(strIgnore);
2289 dbTmp.Close();
2290 remove(strTempPath.c_str());
2291 remove((strTempPath + "-wal").c_str());
2292 remove((strTempPath + "-shm").c_str());
2293 return false;
2294 }
2295
2296 if (!dbTmp.CommitTransaction(strErrorRet))
2297 {
2298 dbTmp.Close();
2299 remove(strTempPath.c_str());
2300 remove((strTempPath + "-wal").c_str());
2301 remove((strTempPath + "-shm").c_str());
2302 return false;
2303 }
2304 string strCheckpointError;
2305 dbTmp.Checkpoint(strCheckpointError);
2306 dbTmp.Close();
2307 }
2308
2309 // The rebuilt wallet is committed and self-contained. Close the live handle
2310 // so the file can be swapped: Windows will not rename a file that still has
2311 // an open handle, and the node exits right after this.
2312 WalletSQLiteRuntimeClose();
2313
2314 string strBackupPath = WalletEncryptBackupPathFor("wallet.sqlite");
2315 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0)
2316 {
2317 strErrorRet = strprintf("could not move the original SQLite wallet to %s",
2318 strBackupPath.c_str());
2319 remove(strTempPath.c_str());
2320 return false;
2321 }
2322 // The moved-aside wallet owns any leftover write-ahead log or shared-memory
2323 // sidecar; drop stale ones so they cannot shadow the new wallet.
2324 remove((strWalletPath + "-wal").c_str());
2325 remove((strWalletPath + "-shm").c_str());
2326
2327 if (rename(strTempPath.c_str(), strWalletPath.c_str()) != 0)
2328 {
2329 rename(strBackupPath.c_str(), strWalletPath.c_str());
2330 strErrorRet = "could not install the encrypted SQLite wallet; the original was restored";
2331 remove(strTempPath.c_str());
2332 return false;
2333 }
2334 remove((strTempPath + "-wal").c_str());
2335 remove((strTempPath + "-shm").c_str());
2336
2337 strBackupRet = strBackupPath;
2338 return true;
2339}
2340
2009bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) 2341bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet)
2010{ 2342{
2011 strBackupRet.clear(); 2343 strBackupRet.clear();
@@ -2144,6 +2476,24 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st
2144 vNewPool.push_back(make_pair((int64)i + 1, vchPubKey)); 2476 vNewPool.push_back(make_pair((int64)i + 1, vchPubKey));
2145 } 2477 }
2146 2478
2479 // Every ciphertext is computed; nothing has touched the store yet. Under
2480 // the SQLite backend, persist by rebuilding wallet.sqlite in one atomic
2481 // transaction and swapping it in. The Berkeley DB path below is unchanged.
2482 if (WalletSQLiteRuntimeActive())
2483 {
2484 int nCopiedSQLite = 0;
2485 if (!EncryptWalletPersistSQLite(vCryptedKeys, vNewPoolKeys, vNewPool,
2486 kMasterKey, vchCryptedHDMasterNew,
2487 vchCryptedHDChainCodeNew, nHDNextNew,
2488 nCopiedSQLite, strBackupRet, strErrorRet))
2489 return false;
2490 printf("EncryptWallet() : copied %d public record(s), encrypted %u key(s), "
2491 "rebuilt %d key-pool entry(s) [SQLite]\n",
2492 nCopiedSQLite, (unsigned int)(vCryptedKeys.size() + vNewPoolKeys.size()),
2493 (int)vNewPool.size());
2494 return true;
2495 }
2496
2147 string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime()); 2497 string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime());
2148 string strTempPath = GetAppDir() + "/" + strTempFile; 2498 string strTempPath = GetAppDir() + "/" + strTempFile;
2149 remove(strTempPath.c_str()); 2499 remove(strTempPath.c_str());
@@ -2211,7 +2561,7 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st
2211 DBFlush(true); 2561 DBFlush(true);
2212 2562
2213 string strWalletPath = GetAppDir() + "/wallet.dat"; 2563 string strWalletPath = GetAppDir() + "/wallet.dat";
2214 string strBackupPath = WalletEncryptBackupPath(); 2564 string strBackupPath = WalletEncryptBackupPathFor("wallet.dat");
2215 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) 2565 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0)
2216 { 2566 {
2217 strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str()); 2567 strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str());
diff --git a/src/db.h b/src/db.h
index fee5a82..5bc94b1 100644
--- a/src/db.h
+++ b/src/db.h
@@ -365,6 +365,11 @@ bool WalletSQLiteRuntimeWriteRecord(const vector<unsigned char>& vchKey,
365 const vector<unsigned char>& vchValue, 365 const vector<unsigned char>& vchValue,
366 bool fOverwrite); 366 bool fOverwrite);
367bool WalletSQLiteRuntimeEraseRecord(const vector<unsigned char>& vchKey); 367bool WalletSQLiteRuntimeEraseRecord(const vector<unsigned char>& vchKey);
368bool WalletSQLiteRuntimeBeginTxn();
369bool WalletSQLiteRuntimeCommitTxn();
370void WalletSQLiteRuntimeRollbackTxn();
371bool WalletSQLiteRuntimeOpenReadOnly(const string& strPath, string& strErrorRet);
372bool WalletSQLiteRuntimeCheckpoint(string& strErrorRet);
368 373
369 374
370 375
@@ -531,8 +536,21 @@ public:
531 536
532 bool WriteHDMaster(const vector<unsigned char>& vchMaster, const vector<unsigned char>& vchChainCode) 537 bool WriteHDMaster(const vector<unsigned char>& vchMaster, const vector<unsigned char>& vchChainCode)
533 { 538 {
534 return WriteWalletRecord(string("hdmaster"), vchMaster) 539 // hdmaster and hdchaincode are halves of one seed: a wallet with one but
535 && WriteWalletRecord(string("hdchaincode"), vchChainCode); 540 // not the other is an incomplete HD wallet (-walletstoragecheck flags
541 // exactly that). Under the SQLite runtime write them in one transaction
542 // so a failure or crash between the two cannot leave that half-state.
543 // Berkeley DB keeps its existing per-write behaviour -- the bracket is a
544 // no-op there.
545 if (!WalletSQLiteRuntimeBeginTxn())
546 return false;
547 if (!WriteWalletRecord(string("hdmaster"), vchMaster) ||
548 !WriteWalletRecord(string("hdchaincode"), vchChainCode))
549 {
550 WalletSQLiteRuntimeRollbackTxn();
551 return false;
552 }
553 return WalletSQLiteRuntimeCommitTxn();
536 } 554 }
537 555
538 bool WriteWalletMinVersion(int nVersion) 556 bool WriteWalletMinVersion(int nVersion)
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 4be4995..af6053f 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -524,19 +524,57 @@ int main(int argc, char* argv[])
524 // explain wallet.dat states that the normal wallet loader may refuse. 524 // explain wallet.dat states that the normal wallet loader may refuse.
525 string strWalletStorageAuditJson = 525 string strWalletStorageAuditJson =
526 argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson"); 526 argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson");
527 if (arg(argc,argv,"/walletstorageaudit") || 527 bool fDiagWantsAudit = arg(argc,argv,"/walletstorageaudit") ||
528 arg(argc,argv,"-walletstorageaudit") || 528 arg(argc,argv,"-walletstorageaudit") ||
529 !strWalletStorageAuditJson.empty()) 529 !strWalletStorageAuditJson.empty();
530 bool fDiagWantsCheck = arg(argc,argv,"/walletstoragecheck") ||
531 arg(argc,argv,"-walletstoragecheck");
532
533 // These diagnostics normally read wallet.dat. Under -walletbackend=sqlite
534 // the live wallet is wallet.sqlite, so open it read-only as the active
535 // store and let the same scan walk the right file. This runs before the
536 // normal backend resolution further down, so validate the value here too.
537 string strDiagBackend = argval2(argc, argv, "/walletbackend", "-walletbackend");
538 if ((fDiagWantsAudit || fDiagWantsCheck) && !strDiagBackend.empty() &&
539 strDiagBackend != "sqlite" && strDiagBackend != "bdb")
540 {
541 AttachTerminal();
542 fprintf(stderr, "Unknown wallet backend '%s'. Use -walletbackend=sqlite "
543 "or -walletbackend=bdb.\n", strDiagBackend.c_str());
544 return 1;
545 }
546 bool fDiagSQLite = (strDiagBackend == "sqlite") &&
547 (fDiagWantsAudit || fDiagWantsCheck);
548 if (fDiagSQLite)
549 {
550 AttachTerminal();
551 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
552 string strOpenErr;
553 if (!WalletSQLiteRuntimeOpenReadOnly(strSQLitePath, strOpenErr))
554 {
555 fprintf(stderr, "Cannot open the SQLite wallet for diagnostics: %s\n",
556 strOpenErr.c_str());
557 return 1;
558 }
559 }
560
561 if (fDiagWantsAudit)
530 { 562 {
531 int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson); 563 int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson);
532 DBFlush(true); 564 if (fDiagSQLite)
565 WalletSQLiteRuntimeClose();
566 else
567 DBFlush(true);
533 return nRet; 568 return nRet;
534 } 569 }
535 570
536 if (arg(argc,argv,"/walletstoragecheck") || arg(argc,argv,"-walletstoragecheck")) 571 if (fDiagWantsCheck)
537 { 572 {
538 int nRet = CmdWalletStorageCheck(); 573 int nRet = CmdWalletStorageCheck();
539 DBFlush(true); 574 if (fDiagSQLite)
575 WalletSQLiteRuntimeClose();
576 else
577 DBFlush(true);
540 return nRet; 578 return nRet;
541 } 579 }
542 580
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 14e6b32..1cb47fb 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -112,6 +112,7 @@ tests: test_bip32.exe bitflash.exe
112 ./bitflash.exe -selftest=wallet-sqlite-migration 112 ./bitflash.exe -selftest=wallet-sqlite-migration
113 ./bitflash.exe -selftest=wallet-crypto 113 ./bitflash.exe -selftest=wallet-crypto
114 ./bitflash.exe -selftest=wallet-encrypt 114 ./bitflash.exe -selftest=wallet-encrypt
115 ./bitflash.exe -selftest=wallet-sqlite-encrypt
115 ./bitflash.exe -selftest=net-message 116 ./bitflash.exe -selftest=net-message
116 ./bitflash.exe -selftest=consensus-limits 117 ./bitflash.exe -selftest=consensus-limits
117 ./bitflash.exe -selftest=pool-stratum 118 ./bitflash.exe -selftest=pool-stratum
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 098eed6..90a30d2 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1231,6 +1231,43 @@ static int RunWalletSQLiteSelfTest()
1231 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()), 1231 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()),
1232 "active SQLite runtime does not create wallet.dat") ? 0 : 1; 1232 "active SQLite runtime does not create wallet.dat") ? 0 : 1;
1233 1233
1234 // Transaction bracket: a rolled-back group leaves nothing, a
1235 // committed group persists. This is the all-or-nothing that
1236 // WriteHDMaster relies on so a half-written seed cannot survive
1237 // a failure between its two records.
1238 bool fTxnBegan = WalletSQLiteRuntimeBeginTxn();
1239 bool fTxnWrote = fTxnBegan &&
1240 CWalletDB().WriteSetting("runtime-txn-rollback", (int64)111);
1241 if (fTxnBegan)
1242 WalletSQLiteRuntimeRollbackTxn();
1243 nFail += Check(fTxnBegan && fTxnWrote,
1244 "a SQLite runtime transaction opens and rolls back") ? 0 : 1;
1245 int64 nRolledBack = 0;
1246 nFail += Check(!CWalletDB("r").ReadSetting("runtime-txn-rollback",
1247 nRolledBack),
1248 "a rolled-back SQLite runtime write leaves no record") ? 0 : 1;
1249
1250 bool fCommitOk = WalletSQLiteRuntimeBeginTxn() &&
1251 CWalletDB().WriteSetting("runtime-txn-commit", (int64)222) &&
1252 WalletSQLiteRuntimeCommitTxn();
1253 int64 nCommitted = 0;
1254 nFail += Check(fCommitOk &&
1255 CWalletDB("r").ReadSetting("runtime-txn-commit",
1256 nCommitted) &&
1257 nCommitted == 222,
1258 "a committed SQLite runtime write persists") ? 0 : 1;
1259
1260 // WriteHDMaster brackets its two records in that transaction;
1261 // both halves of the seed must land.
1262 vector<unsigned char> vchTxnMaster(32, 0x5a);
1263 vector<unsigned char> vchTxnChainCode(32, 0xa5);
1264 vector<unsigned char> vchReadMaster, vchReadChainCode;
1265 nFail += Check(CWalletDB().WriteHDMaster(vchTxnMaster, vchTxnChainCode) &&
1266 CWalletDB("r").ReadHDMaster(vchReadMaster, vchReadChainCode) &&
1267 vchReadMaster == vchTxnMaster &&
1268 vchReadChainCode == vchTxnChainCode,
1269 "WriteHDMaster commits both halves of the seed under SQLite") ? 0 : 1;
1270
1234 // Durability: close the backend (checkpoints the WAL), reopen 1271 // Durability: close the backend (checkpoints the WAL), reopen
1235 // the same file, and confirm what CWalletDB wrote is still 1272 // the same file, and confirm what CWalletDB wrote is still
1236 // there. This is the write-survives-a-restart property the real 1273 // there. This is the write-survives-a-restart property the real
@@ -2252,6 +2289,251 @@ static int RunWalletEncryptSelfTest()
2252 return nFail == 0 ? 0 : 1; 2289 return nFail == 0 ? 0 : 1;
2253} 2290}
2254 2291
2292// Encrypting a wallet while it runs on the SQLite backend. Unlike the Berkeley
2293// DB encrypt test above, this drives the real command-line path end to end in
2294// child processes: build a Berkeley DB wallet, export it to SQLite, then run
2295// -walletbackend=sqlite -encryptwallet and prove the SQLite wallet came out
2296// encrypted, unlockable, and with wallet.dat left untouched.
2297static int RunWalletSQLiteEncryptSelfTest()
2298{
2299 fflush(stdout);
2300 printf("wallet-sqlite-encrypt self-test\n");
2301
2302 std::string tmp;
2303 std::string cwd;
2304 if (!MakeTempDir(tmp))
2305 {
2306 printf(" FAIL could not create a temporary data directory\n");
2307 return 1;
2308 }
2309 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2310 {
2311 printf(" FAIL could not move into the temporary data directory\n");
2312 RemoveTree(tmp);
2313 return 1;
2314 }
2315
2316 int nFail = 0;
2317 strSetDataDir = tmp;
2318 printf(" temp datadir: %s\n", tmp.c_str());
2319
2320 try
2321 {
2322 // Build a normal Berkeley DB wallet with a recovery phrase, a key pool,
2323 // and a couple of public records, then release it. Everything after this
2324 // runs the real command-line paths in child processes.
2325 if (!LoadWallet())
2326 throw std::runtime_error("LoadWallet failed");
2327
2328 string strError;
2329 const string strMnemonic =
2330 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
2331 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
2332 "a phrase can be installed before encryption") ? 0 : 1;
2333 TopUpKeyPool();
2334
2335 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2336 CKey publicLabelKey;
2337 publicLabelKey.MakeNewKey();
2338 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2339 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2340 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2341 "public wallet records can be written before encryption") ? 0 : 1;
2342
2343 CPrivKey vchDefaultPrivKey;
2344 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2345 "the default private key is readable before encryption") ? 0 : 1;
2346 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
2347 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
2348 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
2349
2350 // Release wallet.dat so a child process can read it for the export.
2351 DBFlush(true);
2352
2353#ifdef _WIN32
2354 string strExe = cwd + "\\bitflash.exe";
2355#else
2356 string strExe = cwd + "/bitflash-node";
2357#endif
2358 string strSQLitePath = tmp + "/wallet.sqlite";
2359 string strWalletDatPath = tmp + "/wallet.dat";
2360 string strRightPassFile = tmp + "/right-pass.txt";
2361 string strWrongPassFile = tmp + "/wrong-pass.txt";
2362 nFail += Check(WriteTextFile(strRightPassFile, "btf-test-passphrase\n") &&
2363 WriteTextFile(strWrongPassFile, "wrong-passphrase\n"),
2364 "passphrase files can be written for restarted commands") ? 0 : 1;
2365
2366 // Step 1: export the Berkeley DB wallet to SQLite.
2367 vector<string> vExportArgs;
2368 vExportArgs.push_back("-datadir=" + tmp);
2369 vExportArgs.push_back("-walletsqliteexport=" + strSQLitePath);
2370 vExportArgs.push_back("-nogui");
2371 int nExportRet = RunBitflashChild(strExe, vExportArgs);
2372 nFail += Check(nExportRet == 0 && FileExists(strSQLitePath.c_str()),
2373 "the wallet exports to a SQLite file") ? 0 : 1;
2374 nFail += Check(FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2375 "the exported SQLite wallet holds the plaintext key before encryption") ? 0 : 1;
2376
2377 // Step 2: encrypt through the real SQLite backend command path.
2378 string strEncryptOut = tmp + "/sqlite-encrypt-out.txt";
2379 vector<string> vEncryptArgs;
2380 vEncryptArgs.push_back("-datadir=" + tmp);
2381 vEncryptArgs.push_back("-walletbackend=sqlite");
2382 vEncryptArgs.push_back("-encryptwallet=@" + strRightPassFile);
2383 vEncryptArgs.push_back("-nogui");
2384 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2385 nFail += Check(nEncryptRet == 0,
2386 "the SQLite wallet can be encrypted through -walletbackend=sqlite") ? 0 : 1;
2387 nFail += Check(FileContainsText(strEncryptOut, "Wallet backend: SQLite"),
2388 "encryption took the SQLite persistence path") ? 0 : 1;
2389 nFail += Check(FileContainsText(strEncryptOut, "wallet.sqlite.before-encrypt"),
2390 "the plaintext SQLite wallet is preserved as an explicit backup") ? 0 : 1;
2391
2392 // The encrypted SQLite wallet must no longer hold the plaintext secrets.
2393 nFail += Check(!FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2394 "the encrypted SQLite wallet no longer contains the default private key bytes") ? 0 : 1;
2395 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDMaster),
2396 "the encrypted SQLite wallet no longer contains the HD master key bytes") ? 0 : 1;
2397 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDChainCode),
2398 "the encrypted SQLite wallet no longer contains the HD chain-code bytes") ? 0 : 1;
2399
2400 // The Berkeley DB wallet.dat is a separate file: the SQLite encrypt path
2401 // must never touch it, so its plaintext key is still exactly there.
2402 nFail += Check(FileContainsBytes(strWalletDatPath, vchDefaultPrivBytes),
2403 "the original Berkeley DB wallet.dat is left untouched by the SQLite encrypt") ? 0 : 1;
2404
2405 // Step 3: a locked encrypted SQLite wallet reports that phrase coverage
2406 // needs an unlock, exactly as the Berkeley DB path does (exit code 2).
2407 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2408 vector<string> vLockedArgs;
2409 vLockedArgs.push_back("-datadir=" + tmp);
2410 vLockedArgs.push_back("-walletbackend=sqlite");
2411 vLockedArgs.push_back("-recoveryaudit");
2412 vLockedArgs.push_back("-nogui");
2413 int nLockedRet = RunBitflashChild(strExe, vLockedArgs, NULL, &strLockedAudit);
2414 nFail += Check(nLockedRet == 2 &&
2415 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit"),
2416 "a locked encrypted SQLite wallet reports that phrase coverage needs unlock") ? 0 : 1;
2417
2418 // The wrong passphrase is rejected and reveals nothing.
2419 string strWrongDump = tmp + "/wrong-dump.txt";
2420 vector<string> vWrongArgs;
2421 vWrongArgs.push_back("-datadir=" + tmp);
2422 vWrongArgs.push_back("-walletbackend=sqlite");
2423 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
2424 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
2425 vWrongArgs.push_back("-nogui");
2426 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
2427 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
2428 "a restarted SQLite wallet rejects the wrong passphrase") ? 0 : 1;
2429
2430 // The right passphrase unlocks and can dump the original key.
2431 string strRightDump = tmp + "/right-dump.txt";
2432 vector<string> vRightArgs;
2433 vRightArgs.push_back("-datadir=" + tmp);
2434 vRightArgs.push_back("-walletbackend=sqlite");
2435 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2436 vRightArgs.push_back("-dumpwallet=" + strRightDump);
2437 vRightArgs.push_back("-nogui");
2438 int nRightRet = RunBitflashChild(strExe, vRightArgs);
2439 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
2440 "a restarted SQLite wallet unlocks with the right passphrase") ? 0 : 1;
2441 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
2442 "the unlocked SQLite wallet decrypts and dumps the original key") ? 0 : 1;
2443
2444 // The recovery phrase survives the encryption.
2445 string strAudit = tmp + "/recovery-audit.txt";
2446 vector<string> vAuditArgs;
2447 vAuditArgs.push_back("-datadir=" + tmp);
2448 vAuditArgs.push_back("-walletbackend=sqlite");
2449 vAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2450 vAuditArgs.push_back("-recoveryaudit");
2451 vAuditArgs.push_back("-nogui");
2452 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2453 nFail += Check(nAuditRet == 0 &&
2454 FileContainsText(strAudit, "recovery phrase: present"),
2455 "the encrypted SQLite wallet still has its recovery phrase") ? 0 : 1;
2456
2457 // One-shot storage commands honour -walletbackend. The SQLite wallet is
2458 // encrypted; the wallet.dat beside it is still plaintext. Same datadir,
2459 // opposite answers -> the flag routes the diagnostic to the right file.
2460 string strSqliteAuditJson = tmp + "/sqlite-audit.json";
2461 vector<string> vSqliteAuditArgs;
2462 vSqliteAuditArgs.push_back("-datadir=" + tmp);
2463 vSqliteAuditArgs.push_back("-walletbackend=sqlite");
2464 vSqliteAuditArgs.push_back("-walletstorageauditjson=" + strSqliteAuditJson);
2465 vSqliteAuditArgs.push_back("-nogui");
2466 int nSqliteAuditRet = RunBitflashChild(strExe, vSqliteAuditArgs);
2467 nFail += Check(nSqliteAuditRet == 0 &&
2468 FileContainsText(strSqliteAuditJson, "\"encrypted_hd_seed\": \"complete\"") &&
2469 FileContainsText(strSqliteAuditJson, "\"plain_hd_seed\": \"none\""),
2470 "-walletstorageaudit under SQLite reads the encrypted wallet.sqlite") ? 0 : 1;
2471
2472 string strBdbAuditJson = tmp + "/bdb-audit.json";
2473 vector<string> vBdbAuditArgs;
2474 vBdbAuditArgs.push_back("-datadir=" + tmp);
2475 vBdbAuditArgs.push_back("-walletstorageauditjson=" + strBdbAuditJson);
2476 vBdbAuditArgs.push_back("-nogui");
2477 int nBdbAuditRet = RunBitflashChild(strExe, vBdbAuditArgs);
2478 nFail += Check(nBdbAuditRet == 0 &&
2479 FileContainsText(strBdbAuditJson, "\"plain_hd_seed\": \"complete\"") &&
2480 FileContainsText(strBdbAuditJson, "\"encrypted_hd_seed\": \"none\""),
2481 "-walletstorageaudit without the flag still reads the plaintext wallet.dat") ? 0 : 1;
2482
2483 string strSqliteCheckOut = tmp + "/sqlite-check.txt";
2484 vector<string> vSqliteCheckArgs;
2485 vSqliteCheckArgs.push_back("-datadir=" + tmp);
2486 vSqliteCheckArgs.push_back("-walletbackend=sqlite");
2487 vSqliteCheckArgs.push_back("-walletstoragecheck");
2488 vSqliteCheckArgs.push_back("-nogui");
2489 int nSqliteCheckRet = RunBitflashChild(strExe, vSqliteCheckArgs, NULL, &strSqliteCheckOut);
2490 nFail += Check(nSqliteCheckRet == 0,
2491 "-walletstoragecheck under SQLite passes on the encrypted wallet.sqlite") ? 0 : 1;
2492
2493 // Backup under SQLite copies wallet.sqlite, not the plaintext wallet.dat.
2494 // The copy is a standalone SQLite wallet (loadcheck accepts it) and holds
2495 // none of the plaintext key that wallet.dat still contains.
2496 string strBackupDest = tmp + "/backup.sqlite";
2497 vector<string> vBackupArgs;
2498 vBackupArgs.push_back("-datadir=" + tmp);
2499 vBackupArgs.push_back("-walletbackend=sqlite");
2500 vBackupArgs.push_back("-backupwallet=" + strBackupDest);
2501 vBackupArgs.push_back("-nogui");
2502 int nBackupRet = RunBitflashChild(strExe, vBackupArgs);
2503 nFail += Check(nBackupRet == 0 && FileExists(strBackupDest.c_str()),
2504 "-backupwallet under SQLite writes a backup file") ? 0 : 1;
2505 nFail += Check(!FileContainsBytes(strBackupDest, vchDefaultPrivBytes),
2506 "the SQLite backup holds no plaintext key (it copied wallet.sqlite, not wallet.dat)") ? 0 : 1;
2507
2508 string strBackupLoadCheck = tmp + "/backup-loadcheck.txt";
2509 vector<string> vLoadCheckArgs;
2510 vLoadCheckArgs.push_back("-datadir=" + tmp);
2511 vLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strBackupDest);
2512 vLoadCheckArgs.push_back("-nogui");
2513 int nBackupLoadRet = RunBitflashChild(strExe, vLoadCheckArgs, NULL, &strBackupLoadCheck);
2514 nFail += Check(nBackupLoadRet == 0,
2515 "the SQLite backup is a valid standalone SQLite wallet") ? 0 : 1;
2516 }
2517 catch (const std::exception& e)
2518 {
2519 printf(" FAIL exception: %s\n", e.what());
2520 nFail++;
2521 }
2522 catch (...)
2523 {
2524 printf(" FAIL unknown exception\n");
2525 nFail++;
2526 }
2527
2528 DBFlush(true);
2529 SetCurrentDir(cwd);
2530 RemoveTree(tmp);
2531 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2532 nFail, nFail == 1 ? "" : "s");
2533 fflush(stdout);
2534 return nFail == 0 ? 0 : 1;
2535}
2536
2255static int RunWalletPortabilitySelfTest() 2537static int RunWalletPortabilitySelfTest()
2256{ 2538{
2257 fflush(stdout); 2539 fflush(stdout);
@@ -2981,6 +3263,8 @@ int RunSelfTest(const std::string& name)
2981 return RunWalletCryptoSelfTest(); 3263 return RunWalletCryptoSelfTest();
2982 if (name == "wallet-encrypt") 3264 if (name == "wallet-encrypt")
2983 return RunWalletEncryptSelfTest(); 3265 return RunWalletEncryptSelfTest();
3266 if (name == "wallet-sqlite-encrypt")
3267 return RunWalletSQLiteEncryptSelfTest();
2984 if (name == "wallet-portability") 3268 if (name == "wallet-portability")
2985 return RunWalletPortabilitySelfTest(); 3269 return RunWalletPortabilitySelfTest();
2986 if (name == "net-message") 3270 if (name == "net-message")
@@ -2997,6 +3281,6 @@ int RunSelfTest(const std::string& name)
2997 return RunManagedTorSelfTest(); 3281 return RunManagedTorSelfTest();
2998 3282
2999 printf("Unknown self-test '%s'\n", name.c_str()); 3283 printf("Unknown self-test '%s'\n", name.c_str());
3000 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); 3284 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n");
3001 return 1; 3285 return 1;
3002} 3286}
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 4dda8dd..374b2cc 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -1365,6 +1365,11 @@ int CmdEncryptWallet(const std::string& strPassphrase)
1365{ 1365{
1366 AttachTerminal(); 1366 AttachTerminal();
1367 1367
1368 // Capture the backend now: under SQLite, EncryptWallet() closes the runtime
1369 // store to swap the file, so WalletSQLiteRuntimeActive() reads false by the
1370 // time we would decide whether to purge Berkeley DB logs below.
1371 bool fSQLiteBackend = WalletSQLiteRuntimeActive();
1372
1368 string strBackup; 1373 string strBackup;
1369 string strError; 1374 string strError;
1370 if (!EncryptWallet(strPassphrase, strBackup, strError)) 1375 if (!EncryptWallet(strPassphrase, strBackup, strError))
@@ -1375,6 +1380,8 @@ int CmdEncryptWallet(const std::string& strPassphrase)
1375 } 1380 }
1376 1381
1377 printf("Wallet encrypted.\n"); 1382 printf("Wallet encrypted.\n");
1383 if (fSQLiteBackend)
1384 printf("Wallet backend: SQLite (wallet.sqlite).\n");
1378 printf("The old unencrypted wallet was moved to:\n"); 1385 printf("The old unencrypted wallet was moved to:\n");
1379 printf(" %s\n", strBackup.c_str()); 1386 printf(" %s\n", strBackup.c_str());
1380 printf("That backup still contains private keys in plain text. Move it offline or\n"); 1387 printf("That backup still contains private keys in plain text. Move it offline or\n");
@@ -1387,7 +1394,13 @@ int CmdEncryptWallet(const std::string& strPassphrase)
1387 // them. Closing the environment and dropping the logs is the last step of 1394 // them. Closing the environment and dropping the logs is the last step of
1388 // encrypting, not an optimisation -- our own backup advice is to copy the 1395 // encrypting, not an optimisation -- our own backup advice is to copy the
1389 // whole directory, and following it otherwise carries the keys along. 1396 // whole directory, and following it otherwise carries the keys along.
1390 PurgeDbEnvironmentLogs(); 1397 //
1398 // The SQLite backend has no Berkeley DB environment behind the wallet, and
1399 // EncryptWallet() already closed and swapped the SQLite store; there is
1400 // nothing to purge, and calling into the environment here would only open
1401 // one needlessly.
1402 if (!fSQLiteBackend)
1403 PurgeDbEnvironmentLogs();
1391 return 0; 1404 return 0;
1392} 1405}
1393 1406