diff options
| author | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
|---|---|---|
| committer | bitflash-maintainer <noreply@bitflash.invalid> | 2026-09-05 22:46:52 +0000 |
| commit | ceae3d076b5ad11ee84729ae43f14e9312896646 (patch) | |
| tree | 7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/walletcmd.cpp | |
| parent | be63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff) | |
| parent | 66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff) | |
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we
build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped
line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git
merge-base returns no result at all -- so anyone cloning this repository
landed on a default branch that was neither current nor related to the
running node.
The 28 commits on main were audited before this merge: every one is already
present in the tree by content, including the two that are hardest to see
because they were rewritten afterwards -- the payout reorg-safety work
(PayoutBlockStillOnChain, since evolved to re-check on every retry rather
than once at maturity) and the nostr relay certificate verification
(SSL_set1_host, now with a Windows root-store path). Nothing on main is lost
by this merge.
Recorded as a merge rather than a force-push: main only ever moves forward,
its old history stays reachable as the first parent, and this is revertible.
The tree is taken wholesale from the merged branch, so main's content is now
exactly what was built and tested.
What this brings onto main:
- compactblock: BIP152-style compact block relay ported from upstream 1.2.23,
primitives and unit test only, with the RAND_bytes failure path closed and
the reconstruction allocation bounded.
- upstream/main 17741d9: isolated testnet parameters and genesis, optional
2 MB large pages for RandomX. Mainnet genesis verified byte-identical
through upstream's BuildGenesisBlock() refactor.
- deploy: every image built on bookworm so binaries run on Debian 12, and the
container build now honours the RandomX and libsecp256k1 pins it had been
silently ignoring since before 1.2.15.
Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus
constants unchanged against e4f898c apart from the pre-existing
MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/walletcmd.cpp')
| -rw-r--r-- | src/walletcmd.cpp | 1039 |
1 files changed, 1027 insertions, 12 deletions
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index 06f35a5..f59fc0c 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "headers_core.h" | 12 | #include "headers_core.h" |
| 13 | #include "bip32.h" | 13 | #include "bip32.h" |
| 14 | #include "walletcmd.h" | 14 | #include "walletcmd.h" |
| 15 | #include "wallet_sqlite.h" | ||
| 15 | 16 | ||
| 16 | #undef printf | 17 | #undef printf |
| 17 | 18 | ||
| @@ -54,6 +55,37 @@ bool RestoreScanReachedDepth(int nSchema, | |||
| 54 | return nLegacyNext >= nDepth; | 55 | return nLegacyNext >= nDepth; |
| 55 | } | 56 | } |
| 56 | 57 | ||
| 58 | unsigned int WalletLastUsedPubKeyIndexNext( | ||
| 59 | const std::map<unsigned int, std::vector<unsigned char> >& mapPubKeysByIndex) | ||
| 60 | { | ||
| 61 | std::map<std::vector<unsigned char>, unsigned int> mapIndexByPubKey; | ||
| 62 | for (std::map<unsigned int, std::vector<unsigned char> >::const_iterator it = | ||
| 63 | mapPubKeysByIndex.begin(); | ||
| 64 | it != mapPubKeysByIndex.end(); ++it) | ||
| 65 | mapIndexByPubKey[it->second] = it->first; | ||
| 66 | |||
| 67 | unsigned int nNext = 0; | ||
| 68 | CRITICAL_BLOCK(cs_mapWallet) | ||
| 69 | { | ||
| 70 | for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); | ||
| 71 | it != mapWallet.end(); ++it) | ||
| 72 | { | ||
| 73 | CWalletTx& wtx = (*it).second; | ||
| 74 | for (int i = 0; i < (int)wtx.vout.size(); i++) | ||
| 75 | { | ||
| 76 | vector<unsigned char> vchPubKey; | ||
| 77 | if (!ExtractPubKey(wtx.vout[i].scriptPubKey, false, vchPubKey)) | ||
| 78 | continue; | ||
| 79 | std::map<std::vector<unsigned char>, unsigned int>::const_iterator mi = | ||
| 80 | mapIndexByPubKey.find(vchPubKey); | ||
| 81 | if (mi != mapIndexByPubKey.end() && mi->second + 1 > nNext) | ||
| 82 | nNext = mi->second + 1; | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | return nNext; | ||
| 87 | } | ||
| 88 | |||
| 57 | int CmdNewPhrase() | 89 | int CmdNewPhrase() |
| 58 | { | 90 | { |
| 59 | AttachTerminal(); | 91 | AttachTerminal(); |
| @@ -132,7 +164,10 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 132 | // Derive forward in batches, scanning after each, until a whole batch turns | 164 | // Derive forward in batches, scanning after each, until a whole batch turns |
| 133 | // up nothing. Every derived key is written to the wallet before the scan, | 165 | // up nothing. Every derived key is written to the wallet before the scan, |
| 134 | // because the scan asks the wallet what belongs to it. | 166 | // because the scan asks the wallet what belongs to it. |
| 135 | int nTotalDerived = nHDKeySchema == HD_SCHEMA_BIP44 | 167 | const bool fRestoreBIP44 = nHDKeySchema == HD_SCHEMA_BIP44; |
| 168 | unsigned int nLegacyScanNext = nHDNext; | ||
| 169 | std::map<unsigned int, vector<unsigned char> > mapLegacyScanPubKeys; | ||
| 170 | int nTotalDerived = fRestoreBIP44 | ||
| 136 | ? (int)(nHDReceiveNext + nHDChangeNext) | 171 | ? (int)(nHDReceiveNext + nHDChangeNext) |
| 137 | : (int)nHDNext; | 172 | : (int)nHDNext; |
| 138 | int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); | 173 | int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); |
| @@ -151,7 +186,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 151 | nWalletBefore = mapWallet.size(); | 186 | nWalletBefore = mapWallet.size(); |
| 152 | 187 | ||
| 153 | std::string strDeriveError; | 188 | std::string strDeriveError; |
| 154 | if (nHDKeySchema == HD_SCHEMA_BIP44) | 189 | if (fRestoreBIP44) |
| 155 | { | 190 | { |
| 156 | for (int i = 0; i < RESTORE_BATCH; i++) | 191 | for (int i = 0; i < RESTORE_BATCH; i++) |
| 157 | { | 192 | { |
| @@ -182,15 +217,15 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 182 | for (int i = 0; i < RESTORE_BATCH; i++) | 217 | for (int i = 0; i < RESTORE_BATCH; i++) |
| 183 | { | 218 | { |
| 184 | CKey key; | 219 | CKey key; |
| 185 | if (!DeriveHDKey(nHDNext, key, strDeriveError)) | 220 | if (!DeriveHDKey(nLegacyScanNext, key, strDeriveError)) |
| 186 | break; | 221 | break; |
| 187 | if (!AddKey(key)) | 222 | if (!AddKey(key)) |
| 188 | break; | 223 | break; |
| 189 | nHDNext++; | 224 | mapLegacyScanPubKeys[nLegacyScanNext] = key.GetPubKey(); |
| 225 | nLegacyScanNext++; | ||
| 190 | nTotalDerived++; | 226 | nTotalDerived++; |
| 191 | } | 227 | } |
| 192 | nHDKeySchema = nSavedSchema; | 228 | nHDKeySchema = nSavedSchema; |
| 193 | CWalletDB().WriteHDNext(nHDNext); | ||
| 194 | } | 229 | } |
| 195 | else | 230 | else |
| 196 | { | 231 | { |
| @@ -209,6 +244,22 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 209 | 244 | ||
| 210 | ScanForWalletTransactions(pindexGenesisBlock); | 245 | ScanForWalletTransactions(pindexGenesisBlock); |
| 211 | 246 | ||
| 247 | if (fRestoreBIP44) | ||
| 248 | { | ||
| 249 | // The legacy branch is scanned only for compatibility with phrases | ||
| 250 | // created before BIP44. Persist the last used legacy index + 1, not | ||
| 251 | // the lookahead depth. Otherwise a restore with no legacy hits can | ||
| 252 | // leave hdnext at RESTORE_MAX and make future compatibility scans | ||
| 253 | // look exhausted even though no legacy key was ever used. | ||
| 254 | unsigned int nLegacyUsedNext = | ||
| 255 | WalletLastUsedPubKeyIndexNext(mapLegacyScanPubKeys); | ||
| 256 | if (nLegacyUsedNext != nHDNext) | ||
| 257 | { | ||
| 258 | nHDNext = nLegacyUsedNext; | ||
| 259 | CWalletDB().WriteHDNext(nHDNext); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 212 | size_t nWalletAfter = 0; | 263 | size_t nWalletAfter = 0; |
| 213 | CRITICAL_BLOCK(cs_mapWallet) | 264 | CRITICAL_BLOCK(cs_mapWallet) |
| 214 | nWalletAfter = mapWallet.size(); | 265 | nWalletAfter = mapWallet.size(); |
| @@ -239,7 +290,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic, | |||
| 239 | RestoreScanReachedDepth(nHDKeySchema, | 290 | RestoreScanReachedDepth(nHDKeySchema, |
| 240 | nHDReceiveNext, | 291 | nHDReceiveNext, |
| 241 | nHDChangeNext, | 292 | nHDChangeNext, |
| 242 | nHDNext, | 293 | fRestoreBIP44 ? nLegacyScanNext : nHDNext, |
| 243 | nStopDepth)) | 294 | nStopDepth)) |
| 244 | break; | 295 | break; |
| 245 | } | 296 | } |
| @@ -350,11 +401,16 @@ int CmdRecoveryAudit() | |||
| 350 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; | 401 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; |
| 351 | 402 | ||
| 352 | printf("Wallet recovery audit\n"); | 403 | printf("Wallet recovery audit\n"); |
| 353 | printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); | 404 | if (audit.fHaveSeed) |
| 405 | printf(" recovery phrase: present\n"); | ||
| 406 | else if (audit.fSeedEncryptedLocked) | ||
| 407 | printf(" recovery phrase: encrypted, unlock wallet to audit\n"); | ||
| 408 | else | ||
| 409 | printf(" recovery phrase: not installed\n"); | ||
| 354 | if (audit.fHaveSeed) | 410 | if (audit.fHaveSeed) |
| 355 | { | 411 | { |
| 356 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); | 412 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); |
| 357 | printf(" BIP44 coin type: %u (provisional BITFLASH)\n", audit.nCoinType); | 413 | printf(" BIP44 coin type: %u (SLIP-0044 BITFLASH)\n", audit.nCoinType); |
| 358 | printf(" derived keys known to this wallet: %u\n", audit.nDerivedKnown); | 414 | printf(" derived keys known to this wallet: %u\n", audit.nDerivedKnown); |
| 359 | printf(" receive/change counters: %u/%u\n", | 415 | printf(" receive/change counters: %u/%u\n", |
| 360 | audit.nReceiveNext, audit.nChangeNext); | 416 | audit.nReceiveNext, audit.nChangeNext); |
| @@ -362,15 +418,24 @@ int CmdRecoveryAudit() | |||
| 362 | if (!audit.fDeriveComplete) | 418 | if (!audit.fDeriveComplete) |
| 363 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); | 419 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); |
| 364 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); | 420 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); |
| 421 | if (audit.fSeedEncryptedLocked) | ||
| 422 | { | ||
| 423 | printf(" recovery coverage: unavailable while wallet is locked\n"); | ||
| 424 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); | ||
| 425 | printf("\n"); | ||
| 426 | printf("Unlock the wallet with /walletpassphrase or /walletpassphrase=@FILE to audit phrase coverage.\n"); | ||
| 427 | fflush(stdout); | ||
| 428 | return 2; | ||
| 429 | } | ||
| 365 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", | 430 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", |
| 366 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); | 431 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); |
| 367 | printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", | 432 | printf(" not phrase-backed balance: %s BTF (%d transaction(s))\n", |
| 368 | FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); | 433 | FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx); |
| 369 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); | 434 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); |
| 370 | printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", | 435 | printf(" phrase-backed immature: %s BTF (%d transaction(s))\n", |
| 371 | FormatMoney(audit.nRecoverableImmatureCredit).c_str(), | 436 | FormatMoney(audit.nRecoverableImmatureCredit).c_str(), |
| 372 | audit.nRecoverableImmatureTx); | 437 | audit.nRecoverableImmatureTx); |
| 373 | printf(" wallet.dat-only immature: %s BTF (%d transaction(s))\n", | 438 | printf(" not phrase-backed immature: %s BTF (%d transaction(s))\n", |
| 374 | FormatMoney(audit.nLegacyImmatureCredit).c_str(), | 439 | FormatMoney(audit.nLegacyImmatureCredit).c_str(), |
| 375 | audit.nLegacyImmatureTx); | 440 | audit.nLegacyImmatureTx); |
| 376 | 441 | ||
| @@ -392,7 +457,7 @@ int CmdRecoveryAudit() | |||
| 392 | { | 457 | { |
| 393 | printf("\n"); | 458 | printf("\n"); |
| 394 | printf("Warning: some coins are on keys the phrase does not reproduce.\n"); | 459 | printf("Warning: some coins are on keys the phrase does not reproduce.\n"); |
| 395 | printf("Keep wallet.dat backups until that balance has been moved to a phrase-backed address.\n"); | 460 | printf("Keep a file backup of this wallet until that balance has been moved to a phrase-backed address.\n"); |
| 396 | fflush(stdout); | 461 | fflush(stdout); |
| 397 | return 2; | 462 | return 2; |
| 398 | } | 463 | } |
| @@ -406,10 +471,952 @@ int CmdRecoveryAudit() | |||
| 406 | return 0; | 471 | return 0; |
| 407 | } | 472 | } |
| 408 | 473 | ||
| 474 | struct WalletStorageAuditCounts | ||
| 475 | { | ||
| 476 | unsigned int nTotal = 0; | ||
| 477 | unsigned int nMalformed = 0; | ||
| 478 | unsigned int nUnknown = 0; | ||
| 479 | unsigned int nVersion = 0; | ||
| 480 | unsigned int nNames = 0; | ||
| 481 | unsigned int nTransactions = 0; | ||
| 482 | unsigned int nPlainKeys = 0; | ||
| 483 | unsigned int nEncryptedKeys = 0; | ||
| 484 | unsigned int nMasterKeys = 0; | ||
| 485 | unsigned int nDefaultKey = 0; | ||
| 486 | unsigned int nPlainHDMaster = 0; | ||
| 487 | unsigned int nPlainHDChainCode = 0; | ||
| 488 | unsigned int nCryptedHDMaster = 0; | ||
| 489 | unsigned int nCryptedHDChainCode = 0; | ||
| 490 | unsigned int nWalletMinVersion = 0; | ||
| 491 | unsigned int nPool = 0; | ||
| 492 | unsigned int nSettings = 0; | ||
| 493 | unsigned int nHDNext = 0; | ||
| 494 | unsigned int nHDSchema = 0; | ||
| 495 | unsigned int nHDCoinType = 0; | ||
| 496 | unsigned int nHDReceiveNext = 0; | ||
| 497 | unsigned int nHDChangeNext = 0; | ||
| 498 | }; | ||
| 499 | |||
| 500 | static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain) | ||
| 501 | { | ||
| 502 | if (nMaster == 0 && nChain == 0) | ||
| 503 | return "none"; | ||
| 504 | if (nMaster > 0 && nChain > 0) | ||
| 505 | return "complete"; | ||
| 506 | return "incomplete"; | ||
| 507 | } | ||
| 508 | |||
| 509 | static void CountWalletStorageType(const std::string& strType, | ||
| 510 | WalletStorageAuditCounts& c) | ||
| 511 | { | ||
| 512 | if (strType == "version") | ||
| 513 | c.nVersion++; | ||
| 514 | else if (strType == "name") | ||
| 515 | c.nNames++; | ||
| 516 | else if (strType == "tx") | ||
| 517 | c.nTransactions++; | ||
| 518 | else if (strType == "key") | ||
| 519 | c.nPlainKeys++; | ||
| 520 | else if (strType == "ckey") | ||
| 521 | c.nEncryptedKeys++; | ||
| 522 | else if (strType == "mkey") | ||
| 523 | c.nMasterKeys++; | ||
| 524 | else if (strType == "defaultkey") | ||
| 525 | c.nDefaultKey++; | ||
| 526 | else if (strType == "hdmaster") | ||
| 527 | c.nPlainHDMaster++; | ||
| 528 | else if (strType == "hdchaincode") | ||
| 529 | c.nPlainHDChainCode++; | ||
| 530 | else if (strType == "cryptedhdmaster") | ||
| 531 | c.nCryptedHDMaster++; | ||
| 532 | else if (strType == "cryptedhdchaincode") | ||
| 533 | c.nCryptedHDChainCode++; | ||
| 534 | else if (strType == "walletminversion") | ||
| 535 | c.nWalletMinVersion++; | ||
| 536 | else if (strType == "pool") | ||
| 537 | c.nPool++; | ||
| 538 | else if (strType == "setting") | ||
| 539 | c.nSettings++; | ||
| 540 | else if (strType == "hdnext") | ||
| 541 | c.nHDNext++; | ||
| 542 | else if (strType == "hdschema") | ||
| 543 | c.nHDSchema++; | ||
| 544 | else if (strType == "hdcointype") | ||
| 545 | c.nHDCoinType++; | ||
| 546 | else if (strType == "hdreceivenext") | ||
| 547 | c.nHDReceiveNext++; | ||
| 548 | else if (strType == "hdchangenext") | ||
| 549 | c.nHDChangeNext++; | ||
| 550 | else | ||
| 551 | c.nUnknown++; | ||
| 552 | } | ||
| 553 | |||
| 554 | static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts, | ||
| 555 | std::string& strError) | ||
| 556 | { | ||
| 557 | class CWalletStorageCountVisitor : public CWalletRecordVisitor | ||
| 558 | { | ||
| 559 | public: | ||
| 560 | WalletStorageAuditCounts& counts; | ||
| 561 | |||
| 562 | explicit CWalletStorageCountVisitor(WalletStorageAuditCounts& countsIn) | ||
| 563 | : counts(countsIn) { } | ||
| 564 | |||
| 565 | bool VisitWalletRecord(const CDataStream& ssKeyIn, | ||
| 566 | const CDataStream& ssValue, | ||
| 567 | std::string& strErrorRet) | ||
| 568 | { | ||
| 569 | (void)ssValue; | ||
| 570 | (void)strErrorRet; | ||
| 571 | counts.nTotal++; | ||
| 572 | try | ||
| 573 | { | ||
| 574 | CDataStream ssKey = ssKeyIn; | ||
| 575 | std::string strType; | ||
| 576 | ssKey >> strType; | ||
| 577 | if (ssKey.fail()) | ||
| 578 | { | ||
| 579 | counts.nMalformed++; | ||
| 580 | return true; | ||
| 581 | } | ||
| 582 | CountWalletStorageType(strType, counts); | ||
| 583 | } | ||
| 584 | catch (...) | ||
| 585 | { | ||
| 586 | counts.nMalformed++; | ||
| 587 | } | ||
| 588 | return true; | ||
| 589 | } | ||
| 590 | }; | ||
| 591 | |||
| 592 | CWalletStorageCountVisitor visitor(counts); | ||
| 593 | return ScanWalletRecords(visitor, strError); | ||
| 594 | } | ||
| 595 | |||
| 596 | static std::string WalletStorageAuditJson(const WalletStorageAuditCounts& counts) | ||
| 597 | { | ||
| 598 | std::string strJson; | ||
| 599 | strJson += "{\n"; | ||
| 600 | strJson += " \"format\": \"bitflash-wallet-storage-audit-v1\",\n"; | ||
| 601 | strJson += strprintf(" \"records_total\": %u,\n", counts.nTotal); | ||
| 602 | strJson += strprintf(" \"malformed_records\": %u,\n", counts.nMalformed); | ||
| 603 | strJson += strprintf(" \"unknown_records\": %u,\n", counts.nUnknown); | ||
| 604 | strJson += strprintf(" \"plain_private_keys\": %u,\n", counts.nPlainKeys); | ||
| 605 | strJson += strprintf(" \"encrypted_private_keys\": %u,\n", counts.nEncryptedKeys); | ||
| 606 | strJson += strprintf(" \"encryption_master_keys\": %u,\n", counts.nMasterKeys); | ||
| 607 | strJson += strprintf(" \"default_public_key\": \"%s\",\n", | ||
| 608 | counts.nDefaultKey ? "present" : "none"); | ||
| 609 | strJson += strprintf(" \"plain_hd_seed\": \"%s\",\n", | ||
| 610 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 611 | counts.nPlainHDChainCode).c_str()); | ||
| 612 | strJson += strprintf(" \"encrypted_hd_seed\": \"%s\",\n", | ||
| 613 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 614 | counts.nCryptedHDChainCode).c_str()); | ||
| 615 | strJson += strprintf(" \"wallet_minimum_version\": \"%s\",\n", | ||
| 616 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 617 | strJson += strprintf(" \"keypool_entries\": %u,\n", counts.nPool); | ||
| 618 | strJson += strprintf(" \"wallet_transactions\": %u,\n", counts.nTransactions); | ||
| 619 | strJson += strprintf(" \"address_book_labels\": %u,\n", counts.nNames); | ||
| 620 | strJson += strprintf(" \"settings\": %u,\n", counts.nSettings); | ||
| 621 | strJson += strprintf(" \"hd_metadata_records\": %u,\n", | ||
| 622 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 623 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 624 | strJson += strprintf(" \"database_version_records\": %u\n", counts.nVersion); | ||
| 625 | strJson += "}\n"; | ||
| 626 | return strJson; | ||
| 627 | } | ||
| 628 | |||
| 629 | static bool WriteAuditTextFile(const std::string& strPath, | ||
| 630 | const std::string& strText, | ||
| 631 | std::string& strError) | ||
| 632 | { | ||
| 633 | FILE* pf = fopen(strPath.c_str(), "wb"); | ||
| 634 | if (!pf) | ||
| 635 | { | ||
| 636 | strError = "cannot open output file"; | ||
| 637 | return false; | ||
| 638 | } | ||
| 639 | size_t nWritten = fwrite(strText.data(), 1, strText.size(), pf); | ||
| 640 | bool fCloseOk = fclose(pf) == 0; | ||
| 641 | if (nWritten != strText.size() || !fCloseOk) | ||
| 642 | { | ||
| 643 | strError = "could not write the complete output file"; | ||
| 644 | return false; | ||
| 645 | } | ||
| 646 | return true; | ||
| 647 | } | ||
| 648 | |||
| 649 | static void AddWalletStorageFailure(std::vector<std::string>& vFailures, | ||
| 650 | const std::string& strFailure) | ||
| 651 | { | ||
| 652 | vFailures.push_back(strFailure); | ||
| 653 | } | ||
| 654 | |||
| 655 | static void BuildWalletStorageSanityFailures(const WalletStorageAuditCounts& counts, | ||
| 656 | std::vector<std::string>& vFailures) | ||
| 657 | { | ||
| 658 | bool fPlainHD = counts.nPlainHDMaster > 0 || counts.nPlainHDChainCode > 0; | ||
| 659 | bool fCryptedHD = counts.nCryptedHDMaster > 0 || counts.nCryptedHDChainCode > 0; | ||
| 660 | bool fEncryptedRecords = counts.nEncryptedKeys > 0 || | ||
| 661 | counts.nMasterKeys > 0 || | ||
| 662 | fCryptedHD; | ||
| 663 | |||
| 664 | if (counts.nMalformed > 0) | ||
| 665 | AddWalletStorageFailure(vFailures, "wallet.dat contains malformed records"); | ||
| 666 | if (counts.nUnknown > 0) | ||
| 667 | AddWalletStorageFailure(vFailures, "wallet.dat contains unknown records"); | ||
| 668 | if ((counts.nPlainHDMaster > 0) != (counts.nPlainHDChainCode > 0)) | ||
| 669 | AddWalletStorageFailure(vFailures, "plain HD seed is incomplete"); | ||
| 670 | if ((counts.nCryptedHDMaster > 0) != (counts.nCryptedHDChainCode > 0)) | ||
| 671 | AddWalletStorageFailure(vFailures, "encrypted HD seed is incomplete"); | ||
| 672 | if (fPlainHD && fCryptedHD) | ||
| 673 | AddWalletStorageFailure(vFailures, "wallet.dat contains both plain and encrypted HD seed records"); | ||
| 674 | if (counts.nDefaultKey > 1) | ||
| 675 | AddWalletStorageFailure(vFailures, "wallet.dat contains more than one default public key record"); | ||
| 676 | if (counts.nWalletMinVersion > 1) | ||
| 677 | AddWalletStorageFailure(vFailures, "wallet.dat contains more than one minimum-version record"); | ||
| 678 | |||
| 679 | if (fEncryptedRecords) | ||
| 680 | { | ||
| 681 | if (counts.nPlainKeys > 0) | ||
| 682 | AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain private key records"); | ||
| 683 | if (fPlainHD) | ||
| 684 | AddWalletStorageFailure(vFailures, "encrypted wallet still contains plain HD seed records"); | ||
| 685 | if (counts.nMasterKeys == 0) | ||
| 686 | AddWalletStorageFailure(vFailures, "encrypted wallet has no encryption master key record"); | ||
| 687 | if (counts.nWalletMinVersion == 0) | ||
| 688 | AddWalletStorageFailure(vFailures, "encrypted wallet has no minimum-version record"); | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | int CmdWalletStorageCheck() | ||
| 693 | { | ||
| 694 | AttachTerminal(); | ||
| 695 | |||
| 696 | WalletStorageAuditCounts counts; | ||
| 697 | std::string strError; | ||
| 698 | if (!ReadWalletStorageCounts(counts, strError)) | ||
| 699 | { | ||
| 700 | fprintf(stderr, "Cannot check wallet storage: %s\n", strError.c_str()); | ||
| 701 | return 1; | ||
| 702 | } | ||
| 703 | |||
| 704 | std::vector<std::string> vFailures; | ||
| 705 | BuildWalletStorageSanityFailures(counts, vFailures); | ||
| 706 | |||
| 707 | printf("Wallet storage sanity check\n"); | ||
| 708 | printf(" records total: %u\n", counts.nTotal); | ||
| 709 | printf(" encrypted records: %s\n", | ||
| 710 | (counts.nEncryptedKeys || counts.nMasterKeys || | ||
| 711 | counts.nCryptedHDMaster || counts.nCryptedHDChainCode) ? "yes" : "no"); | ||
| 712 | printf(" plain private keys: %u\n", counts.nPlainKeys); | ||
| 713 | printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); | ||
| 714 | printf(" plain HD seed: %s\n", | ||
| 715 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 716 | counts.nPlainHDChainCode).c_str()); | ||
| 717 | printf(" encrypted HD seed: %s\n", | ||
| 718 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 719 | counts.nCryptedHDChainCode).c_str()); | ||
| 720 | if (vFailures.empty()) | ||
| 721 | { | ||
| 722 | printf(" storage sanity: ok\n"); | ||
| 723 | fflush(stdout); | ||
| 724 | return 0; | ||
| 725 | } | ||
| 726 | |||
| 727 | printf(" storage sanity: failed\n"); | ||
| 728 | for (std::vector<std::string>::const_iterator it = vFailures.begin(); | ||
| 729 | it != vFailures.end(); ++it) | ||
| 730 | printf(" failure: %s\n", it->c_str()); | ||
| 731 | fflush(stdout); | ||
| 732 | return 2; | ||
| 733 | } | ||
| 734 | |||
| 735 | int CmdWalletStorageAudit(const std::string& strJsonOut) | ||
| 736 | { | ||
| 737 | AttachTerminal(); | ||
| 738 | |||
| 739 | WalletStorageAuditCounts counts; | ||
| 740 | std::string strError; | ||
| 741 | if (!ReadWalletStorageCounts(counts, strError)) | ||
| 742 | { | ||
| 743 | fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str()); | ||
| 744 | return 1; | ||
| 745 | } | ||
| 746 | |||
| 747 | if (!strJsonOut.empty()) | ||
| 748 | { | ||
| 749 | std::string strWriteError; | ||
| 750 | if (!WriteAuditTextFile(strJsonOut, | ||
| 751 | WalletStorageAuditJson(counts), | ||
| 752 | strWriteError)) | ||
| 753 | { | ||
| 754 | fprintf(stderr, "Cannot write wallet storage audit JSON: %s\n", | ||
| 755 | strWriteError.c_str()); | ||
| 756 | return 1; | ||
| 757 | } | ||
| 758 | printf("Wallet storage audit written to %s\n", strJsonOut.c_str()); | ||
| 759 | fflush(stdout); | ||
| 760 | return counts.nMalformed > 0 ? 2 : 0; | ||
| 761 | } | ||
| 762 | |||
| 763 | printf("Wallet storage audit\n"); | ||
| 764 | printf(" records total: %u\n", counts.nTotal); | ||
| 765 | printf(" malformed records: %u\n", counts.nMalformed); | ||
| 766 | printf(" unknown records: %u\n", counts.nUnknown); | ||
| 767 | printf(" plain private keys: %u\n", counts.nPlainKeys); | ||
| 768 | printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); | ||
| 769 | printf(" encryption master keys: %u\n", counts.nMasterKeys); | ||
| 770 | printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none"); | ||
| 771 | printf(" plain HD seed: %s\n", | ||
| 772 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 773 | counts.nPlainHDChainCode).c_str()); | ||
| 774 | printf(" encrypted HD seed: %s\n", | ||
| 775 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 776 | counts.nCryptedHDChainCode).c_str()); | ||
| 777 | printf(" wallet minimum version: %s\n", | ||
| 778 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 779 | printf(" keypool entries: %u\n", counts.nPool); | ||
| 780 | printf(" wallet transactions: %u\n", counts.nTransactions); | ||
| 781 | printf(" address book labels: %u\n", counts.nNames); | ||
| 782 | printf(" settings: %u\n", counts.nSettings); | ||
| 783 | printf(" HD metadata records: %u\n", | ||
| 784 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 785 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 786 | printf(" database version records: %u\n", counts.nVersion); | ||
| 787 | fflush(stdout); | ||
| 788 | |||
| 789 | if (counts.nMalformed > 0) | ||
| 790 | return 2; | ||
| 791 | return 0; | ||
| 792 | } | ||
| 793 | |||
| 794 | static vector<unsigned char> DataStreamBytesLocal(const CDataStream& ss) | ||
| 795 | { | ||
| 796 | return vector<unsigned char>(ss.begin(), ss.end()); | ||
| 797 | } | ||
| 798 | |||
| 799 | namespace { | ||
| 800 | |||
| 801 | static void RemoveSQLiteExportFiles(const string& strPath) | ||
| 802 | { | ||
| 803 | remove(strPath.c_str()); | ||
| 804 | remove((strPath + "-wal").c_str()); | ||
| 805 | remove((strPath + "-shm").c_str()); | ||
| 806 | } | ||
| 807 | |||
| 808 | static void RemoveFailedRestoreWallet(const string& strWalletPath) | ||
| 809 | { | ||
| 810 | DBFlush(true); | ||
| 811 | remove(strWalletPath.c_str()); | ||
| 812 | } | ||
| 813 | |||
| 814 | class CWalletSQLiteExportVisitor : public CWalletRecordVisitor | ||
| 815 | { | ||
| 816 | public: | ||
| 817 | CWalletDBSQLite& db; | ||
| 818 | int& nCopied; | ||
| 819 | |||
| 820 | CWalletSQLiteExportVisitor(CWalletDBSQLite& dbIn, int& nCopiedIn) | ||
| 821 | : db(dbIn), nCopied(nCopiedIn) { } | ||
| 822 | |||
| 823 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 824 | const CDataStream& ssValue, | ||
| 825 | string& strErrorRet) | ||
| 826 | { | ||
| 827 | if (!db.WriteRecord(DataStreamBytesLocal(ssKey), | ||
| 828 | DataStreamBytesLocal(ssValue), | ||
| 829 | strErrorRet)) | ||
| 830 | return false; | ||
| 831 | nCopied++; | ||
| 832 | return true; | ||
| 833 | } | ||
| 834 | }; | ||
| 835 | |||
| 836 | class CWalletRecordMapVisitor : public CWalletRecordVisitor | ||
| 837 | { | ||
| 838 | public: | ||
| 839 | std::map<vector<unsigned char>, vector<unsigned char> >& mapRecords; | ||
| 840 | |||
| 841 | explicit CWalletRecordMapVisitor( | ||
| 842 | std::map<vector<unsigned char>, vector<unsigned char> >& mapRecordsIn) | ||
| 843 | : mapRecords(mapRecordsIn) { } | ||
| 844 | |||
| 845 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 846 | const CDataStream& ssValue, | ||
| 847 | string& strErrorRet) | ||
| 848 | { | ||
| 849 | vector<unsigned char> vchKey = DataStreamBytesLocal(ssKey); | ||
| 850 | if (mapRecords.count(vchKey)) | ||
| 851 | { | ||
| 852 | strErrorRet = "duplicate serialized wallet key"; | ||
| 853 | return false; | ||
| 854 | } | ||
| 855 | mapRecords[vchKey] = DataStreamBytesLocal(ssValue); | ||
| 856 | return true; | ||
| 857 | } | ||
| 858 | }; | ||
| 859 | |||
| 860 | class CWalletRawRestoreDB : public CWalletDB | ||
| 861 | { | ||
| 862 | public: | ||
| 863 | CWalletRawRestoreDB(const char* pszMode="cr+", bool fTxn=true) | ||
| 864 | : CWalletDB(pszMode, fTxn) { } | ||
| 865 | |||
| 866 | bool WriteRaw(CDataStream ssKey, CDataStream ssValue) | ||
| 867 | { | ||
| 868 | if (!pdb || ssKey.empty()) | ||
| 869 | return false; | ||
| 870 | |||
| 871 | Dbt datKey((void*)&ssKey[0], ssKey.size()); | ||
| 872 | Dbt datValue(ssValue.empty() ? NULL : (void*)&ssValue[0], | ||
| 873 | ssValue.size()); | ||
| 874 | int ret = pdb->put(GetTxn(), &datKey, &datValue, 0); | ||
| 875 | |||
| 876 | memset(datKey.get_data(), 0, datKey.get_size()); | ||
| 877 | if (datValue.get_data()) | ||
| 878 | memset(datValue.get_data(), 0, datValue.get_size()); | ||
| 879 | return ret == 0; | ||
| 880 | } | ||
| 881 | }; | ||
| 882 | |||
| 883 | class CWalletSQLiteRestoreVisitor : public CWalletRecordVisitor | ||
| 884 | { | ||
| 885 | public: | ||
| 886 | CWalletRawRestoreDB& db; | ||
| 887 | int& nCopied; | ||
| 888 | |||
| 889 | CWalletSQLiteRestoreVisitor(CWalletRawRestoreDB& dbIn, int& nCopiedIn) | ||
| 890 | : db(dbIn), nCopied(nCopiedIn) { } | ||
| 891 | |||
| 892 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 893 | const CDataStream& ssValue, | ||
| 894 | string& strErrorRet) | ||
| 895 | { | ||
| 896 | if (!db.WriteRaw(ssKey, ssValue)) | ||
| 897 | { | ||
| 898 | strErrorRet = "could not write raw wallet record"; | ||
| 899 | return false; | ||
| 900 | } | ||
| 901 | nCopied++; | ||
| 902 | return true; | ||
| 903 | } | ||
| 904 | }; | ||
| 905 | |||
| 906 | class CWalletSQLiteLoadCheckVisitor : public CWalletRecordVisitor | ||
| 907 | { | ||
| 908 | public: | ||
| 909 | int nRecords; | ||
| 910 | int nUnknown; | ||
| 911 | string strFailure; | ||
| 912 | |||
| 913 | CWalletSQLiteLoadCheckVisitor() : nRecords(0), nUnknown(0) { } | ||
| 914 | |||
| 915 | bool Fail(const string& strMessage, string& strErrorRet) | ||
| 916 | { | ||
| 917 | strFailure = strMessage; | ||
| 918 | strErrorRet = strMessage; | ||
| 919 | return false; | ||
| 920 | } | ||
| 921 | |||
| 922 | bool VisitWalletRecord(const CDataStream& ssKeyIn, | ||
| 923 | const CDataStream& ssValueIn, | ||
| 924 | string& strErrorRet) | ||
| 925 | { | ||
| 926 | nRecords++; | ||
| 927 | try | ||
| 928 | { | ||
| 929 | CDataStream ssKey = ssKeyIn; | ||
| 930 | CDataStream ssValue = ssValueIn; | ||
| 931 | string strType; | ||
| 932 | ssKey >> strType; | ||
| 933 | if (ssKey.fail()) | ||
| 934 | return Fail("malformed wallet record key", strErrorRet); | ||
| 935 | |||
| 936 | if (strType == "name") | ||
| 937 | { | ||
| 938 | string strAddress; | ||
| 939 | string strName; | ||
| 940 | ssKey >> strAddress; | ||
| 941 | ssValue >> strName; | ||
| 942 | } | ||
| 943 | else if (strType == "tx") | ||
| 944 | { | ||
| 945 | uint256 hash; | ||
| 946 | CWalletTx wtx; | ||
| 947 | ssKey >> hash; | ||
| 948 | ssValue >> wtx; | ||
| 949 | } | ||
| 950 | else if (strType == "key") | ||
| 951 | { | ||
| 952 | vector<unsigned char> vchPubKey; | ||
| 953 | CPrivKey vchPrivKey; | ||
| 954 | ssKey >> vchPubKey; | ||
| 955 | ssValue >> vchPrivKey; | ||
| 956 | } | ||
| 957 | else if (strType == "mkey") | ||
| 958 | { | ||
| 959 | unsigned int nID = 0; | ||
| 960 | CWalletMasterKey kMasterKey; | ||
| 961 | ssKey >> nID; | ||
| 962 | ssValue >> kMasterKey; | ||
| 963 | } | ||
| 964 | else if (strType == "ckey") | ||
| 965 | { | ||
| 966 | vector<unsigned char> vchPubKey; | ||
| 967 | vector<unsigned char> vchCryptedSecret; | ||
| 968 | ssKey >> vchPubKey; | ||
| 969 | ssValue >> vchCryptedSecret; | ||
| 970 | } | ||
| 971 | else if (strType == "defaultkey" || | ||
| 972 | strType == "hdmaster" || | ||
| 973 | strType == "hdchaincode" || | ||
| 974 | strType == "cryptedhdmaster" || | ||
| 975 | strType == "cryptedhdchaincode") | ||
| 976 | { | ||
| 977 | vector<unsigned char> vch; | ||
| 978 | ssValue >> vch; | ||
| 979 | } | ||
| 980 | else if (strType == "hdnext" || | ||
| 981 | strType == "hdreceivenext" || | ||
| 982 | strType == "hdchangenext" || | ||
| 983 | strType == "hdcointype") | ||
| 984 | { | ||
| 985 | unsigned int nValue = 0; | ||
| 986 | ssValue >> nValue; | ||
| 987 | } | ||
| 988 | else if (strType == "hdschema") | ||
| 989 | { | ||
| 990 | int nSchema = 0; | ||
| 991 | ssValue >> nSchema; | ||
| 992 | if (!ssValue.fail() && | ||
| 993 | nSchema != HD_SCHEMA_NONE && | ||
| 994 | nSchema != HD_SCHEMA_LEGACY && | ||
| 995 | nSchema != HD_SCHEMA_BIP44) | ||
| 996 | return Fail("unsupported deterministic wallet schema", | ||
| 997 | strErrorRet); | ||
| 998 | } | ||
| 999 | else if (strType == "walletminversion") | ||
| 1000 | { | ||
| 1001 | int nMinVersion = 0; | ||
| 1002 | ssValue >> nMinVersion; | ||
| 1003 | if (!ssValue.fail() && nMinVersion > WALLET_FORMAT_SUPPORTED) | ||
| 1004 | return Fail("unsupported future wallet format", strErrorRet); | ||
| 1005 | } | ||
| 1006 | else if (strType == "pool") | ||
| 1007 | { | ||
| 1008 | int64 nIndex = 0; | ||
| 1009 | vector<unsigned char> vchPubKey; | ||
| 1010 | ssKey >> nIndex; | ||
| 1011 | ssValue >> vchPubKey; | ||
| 1012 | } | ||
| 1013 | else if (strType == "setting") | ||
| 1014 | { | ||
| 1015 | string strKey; | ||
| 1016 | ssKey >> strKey; | ||
| 1017 | if (strKey == "nTransactionFee") | ||
| 1018 | { | ||
| 1019 | int64 nValue = 0; | ||
| 1020 | ssValue >> nValue; | ||
| 1021 | } | ||
| 1022 | else if (strKey == "addrIncoming") | ||
| 1023 | { | ||
| 1024 | CAddress addr; | ||
| 1025 | ssValue >> addr; | ||
| 1026 | } | ||
| 1027 | else if (strKey == "nMineMode") | ||
| 1028 | { | ||
| 1029 | int nValue = 0; | ||
| 1030 | ssValue >> nValue; | ||
| 1031 | } | ||
| 1032 | else if (strKey == "fGenerateBitcoins") | ||
| 1033 | { | ||
| 1034 | int fValue = 0; | ||
| 1035 | ssValue >> fValue; | ||
| 1036 | } | ||
| 1037 | else if (strKey == "strParticipantPool") | ||
| 1038 | { | ||
| 1039 | string strValue; | ||
| 1040 | ssValue >> strValue; | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | else if (strType == "version") | ||
| 1044 | { | ||
| 1045 | int nVersion = 0; | ||
| 1046 | ssValue >> nVersion; | ||
| 1047 | } | ||
| 1048 | else | ||
| 1049 | { | ||
| 1050 | nUnknown++; | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | if (ssKey.fail()) | ||
| 1054 | return Fail("malformed wallet record key", strErrorRet); | ||
| 1055 | if (ssValue.fail()) | ||
| 1056 | return Fail("malformed wallet record value", strErrorRet); | ||
| 1057 | } | ||
| 1058 | catch (const std::exception& e) | ||
| 1059 | { | ||
| 1060 | return Fail(strprintf("wallet record parse exception: %s", e.what()), | ||
| 1061 | strErrorRet); | ||
| 1062 | } | ||
| 1063 | catch (...) | ||
| 1064 | { | ||
| 1065 | return Fail("wallet record parse exception", strErrorRet); | ||
| 1066 | } | ||
| 1067 | return true; | ||
| 1068 | } | ||
| 1069 | }; | ||
| 1070 | |||
| 1071 | } // namespace | ||
| 1072 | |||
| 1073 | // Export the currently-loaded wallet to a SQLite file. Shared by the | ||
| 1074 | // -walletsqliteexport command and the GUI conversion wizard: opens the | ||
| 1075 | // destination, copies every record inside one transaction, checkpoints, and | ||
| 1076 | // verifies the stored count matches. Returns false with strErrorRet set on any | ||
| 1077 | // failure and leaves no partial file behind; the source wallet is only read. | ||
| 1078 | bool ExportActiveWalletToSQLite(const std::string& strDest, int& nCopiedRet, | ||
| 1079 | std::string& strErrorRet) | ||
| 1080 | { | ||
| 1081 | nCopiedRet = 0; | ||
| 1082 | if (strDest.empty()) | ||
| 1083 | { | ||
| 1084 | strErrorRet = "no SQLite export path was given"; | ||
| 1085 | return false; | ||
| 1086 | } | ||
| 1087 | if (FileExists(strDest.c_str())) | ||
| 1088 | { | ||
| 1089 | strErrorRet = strprintf("refusing to overwrite existing file: %s", strDest.c_str()); | ||
| 1090 | return false; | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | CWalletDBSQLite db; | ||
| 1094 | if (!db.Open(strDest, strErrorRet)) | ||
| 1095 | { | ||
| 1096 | RemoveSQLiteExportFiles(strDest); | ||
| 1097 | return false; | ||
| 1098 | } | ||
| 1099 | if (!db.BeginTransaction(strErrorRet)) | ||
| 1100 | { | ||
| 1101 | db.Close(); | ||
| 1102 | RemoveSQLiteExportFiles(strDest); | ||
| 1103 | return false; | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | CWalletSQLiteExportVisitor visitor(db, nCopiedRet); | ||
| 1107 | if (!ScanWalletRecords(visitor, strErrorRet)) | ||
| 1108 | { | ||
| 1109 | string strRollbackError; | ||
| 1110 | db.RollbackTransaction(strRollbackError); | ||
| 1111 | db.Close(); | ||
| 1112 | RemoveSQLiteExportFiles(strDest); | ||
| 1113 | return false; | ||
| 1114 | } | ||
| 1115 | if (!db.CommitTransaction(strErrorRet) || !db.Checkpoint(strErrorRet)) | ||
| 1116 | { | ||
| 1117 | db.Close(); | ||
| 1118 | RemoveSQLiteExportFiles(strDest); | ||
| 1119 | return false; | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | int nCount = 0; | ||
| 1123 | if (!db.CountRecords(nCount, strErrorRet)) | ||
| 1124 | { | ||
| 1125 | db.Close(); | ||
| 1126 | RemoveSQLiteExportFiles(strDest); | ||
| 1127 | return false; | ||
| 1128 | } | ||
| 1129 | if (nCount != nCopiedRet) | ||
| 1130 | { | ||
| 1131 | strErrorRet = strprintf("export count mismatch: copied %d, stored %d", | ||
| 1132 | nCopiedRet, nCount); | ||
| 1133 | db.Close(); | ||
| 1134 | RemoveSQLiteExportFiles(strDest); | ||
| 1135 | return false; | ||
| 1136 | } | ||
| 1137 | db.Close(); | ||
| 1138 | return true; | ||
| 1139 | } | ||
| 1140 | |||
| 1141 | // Convert the running Berkeley DB wallet to SQLite for the GUI wizard: export | ||
| 1142 | // wallet.dat to <datadir>/wallet.sqlite, then record the backend marker so the | ||
| 1143 | // next start opens SQLite. wallet.dat is never modified -- it stays as the | ||
| 1144 | // fallback. Refuses if already on SQLite or if a wallet.sqlite already exists. | ||
| 1145 | bool DoConvertWalletToSQLite(int& nCopiedRet, std::string& strErrorRet) | ||
| 1146 | { | ||
| 1147 | nCopiedRet = 0; | ||
| 1148 | if (WalletSQLiteRuntimeActive()) | ||
| 1149 | { | ||
| 1150 | strErrorRet = "this wallet is already on the SQLite backend"; | ||
| 1151 | return false; | ||
| 1152 | } | ||
| 1153 | string strDest = GetAppDir() + "/wallet.sqlite"; | ||
| 1154 | if (FileExists(strDest.c_str())) | ||
| 1155 | { | ||
| 1156 | strErrorRet = strprintf("a SQLite wallet already exists at %s", strDest.c_str()); | ||
| 1157 | return false; | ||
| 1158 | } | ||
| 1159 | if (!ExportActiveWalletToSQLite(strDest, nCopiedRet, strErrorRet)) | ||
| 1160 | return false; | ||
| 1161 | if (!WriteWalletBackendMarker("sqlite")) | ||
| 1162 | { | ||
| 1163 | strErrorRet = "the wallet was exported but the backend choice could not be saved"; | ||
| 1164 | return false; | ||
| 1165 | } | ||
| 1166 | return true; | ||
| 1167 | } | ||
| 1168 | |||
| 1169 | int CmdWalletSQLiteExport(const std::string& strDest) | ||
| 1170 | { | ||
| 1171 | AttachTerminal(); | ||
| 1172 | |||
| 1173 | // Keep the command's own specific messages for the two conditions the | ||
| 1174 | // shared helper folds into a generic error, so scripts and the tests that | ||
| 1175 | // read this output see the same wording as before. | ||
| 1176 | if (strDest.empty()) | ||
| 1177 | { | ||
| 1178 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1179 | return 1; | ||
| 1180 | } | ||
| 1181 | if (FileExists(strDest.c_str())) | ||
| 1182 | { | ||
| 1183 | fprintf(stderr, "Refusing to overwrite existing SQLite wallet export: %s\n", | ||
| 1184 | strDest.c_str()); | ||
| 1185 | return 1; | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | int nCopied = 0; | ||
| 1189 | string strError; | ||
| 1190 | if (!ExportActiveWalletToSQLite(strDest, nCopied, strError)) | ||
| 1191 | { | ||
| 1192 | fprintf(stderr, "Cannot export wallet to SQLite: %s\n", strError.c_str()); | ||
| 1193 | return 1; | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | printf("SQLite wallet export written to %s\n", strDest.c_str()); | ||
| 1197 | printf(" records copied: %d\n", nCopied); | ||
| 1198 | fflush(stdout); | ||
| 1199 | return 0; | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | int CmdWalletSQLiteVerify(const std::string& strPath) | ||
| 1203 | { | ||
| 1204 | AttachTerminal(); | ||
| 1205 | |||
| 1206 | if (strPath.empty()) | ||
| 1207 | { | ||
| 1208 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1209 | return 1; | ||
| 1210 | } | ||
| 1211 | if (!FileExists(strPath.c_str())) | ||
| 1212 | { | ||
| 1213 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1214 | strPath.c_str()); | ||
| 1215 | return 1; | ||
| 1216 | } | ||
| 1217 | |||
| 1218 | std::map<vector<unsigned char>, vector<unsigned char> > mapBDB; | ||
| 1219 | std::map<vector<unsigned char>, vector<unsigned char> > mapSQLite; | ||
| 1220 | string strError; | ||
| 1221 | |||
| 1222 | CWalletRecordMapVisitor bdbVisitor(mapBDB); | ||
| 1223 | if (!ScanWalletRecords(bdbVisitor, strError)) | ||
| 1224 | { | ||
| 1225 | fprintf(stderr, "Cannot scan wallet.dat records: %s\n", strError.c_str()); | ||
| 1226 | return 1; | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | CWalletDBSQLite db; | ||
| 1230 | if (!db.Open(strPath, strError)) | ||
| 1231 | { | ||
| 1232 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1233 | return 1; | ||
| 1234 | } | ||
| 1235 | CWalletRecordMapVisitor sqliteVisitor(mapSQLite); | ||
| 1236 | if (!db.ScanRecords(sqliteVisitor, strError)) | ||
| 1237 | { | ||
| 1238 | fprintf(stderr, "Cannot scan SQLite wallet export: %s\n", strError.c_str()); | ||
| 1239 | return 1; | ||
| 1240 | } | ||
| 1241 | |||
| 1242 | int nMissing = 0; | ||
| 1243 | int nMismatched = 0; | ||
| 1244 | for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it = | ||
| 1245 | mapBDB.begin(); it != mapBDB.end(); ++it) | ||
| 1246 | { | ||
| 1247 | std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator sit = | ||
| 1248 | mapSQLite.find(it->first); | ||
| 1249 | if (sit == mapSQLite.end()) | ||
| 1250 | nMissing++; | ||
| 1251 | else if (sit->second != it->second) | ||
| 1252 | nMismatched++; | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | int nExtra = 0; | ||
| 1256 | for (std::map<vector<unsigned char>, vector<unsigned char> >::const_iterator it = | ||
| 1257 | mapSQLite.begin(); it != mapSQLite.end(); ++it) | ||
| 1258 | { | ||
| 1259 | if (!mapBDB.count(it->first)) | ||
| 1260 | nExtra++; | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | printf("SQLite wallet export verification\n"); | ||
| 1264 | printf(" wallet.dat records: %u\n", (unsigned int)mapBDB.size()); | ||
| 1265 | printf(" SQLite records: %u\n", (unsigned int)mapSQLite.size()); | ||
| 1266 | printf(" missing records: %d\n", nMissing); | ||
| 1267 | printf(" extra records: %d\n", nExtra); | ||
| 1268 | printf(" record value mismatches: %d\n", nMismatched); | ||
| 1269 | if (nMissing == 0 && nExtra == 0 && nMismatched == 0) | ||
| 1270 | { | ||
| 1271 | printf(" verification: ok\n"); | ||
| 1272 | fflush(stdout); | ||
| 1273 | return 0; | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | printf(" verification: failed\n"); | ||
| 1277 | fflush(stdout); | ||
| 1278 | return 2; | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | int CmdWalletSQLiteRestore(const std::string& strPath) | ||
| 1282 | { | ||
| 1283 | AttachTerminal(); | ||
| 1284 | |||
| 1285 | if (strPath.empty()) | ||
| 1286 | { | ||
| 1287 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1288 | return 1; | ||
| 1289 | } | ||
| 1290 | if (!FileExists(strPath.c_str())) | ||
| 1291 | { | ||
| 1292 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1293 | strPath.c_str()); | ||
| 1294 | return 1; | ||
| 1295 | } | ||
| 1296 | |||
| 1297 | string strWalletPath = GetAppDir() + "/wallet.dat"; | ||
| 1298 | if (FileExists(strWalletPath.c_str())) | ||
| 1299 | { | ||
| 1300 | fprintf(stderr, "Refusing to overwrite existing wallet.dat: %s\n", | ||
| 1301 | strWalletPath.c_str()); | ||
| 1302 | return 1; | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | string strError; | ||
| 1306 | CWalletDBSQLite sqlite; | ||
| 1307 | if (!sqlite.Open(strPath, strError)) | ||
| 1308 | { | ||
| 1309 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1310 | return 1; | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | int nSQLiteRecords = 0; | ||
| 1314 | if (!sqlite.CountRecords(nSQLiteRecords, strError)) | ||
| 1315 | { | ||
| 1316 | fprintf(stderr, "Cannot count SQLite wallet export records: %s\n", | ||
| 1317 | strError.c_str()); | ||
| 1318 | return 1; | ||
| 1319 | } | ||
| 1320 | if (nSQLiteRecords <= 0) | ||
| 1321 | { | ||
| 1322 | fprintf(stderr, "SQLite wallet export contains no records.\n"); | ||
| 1323 | return 2; | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | CWalletRawRestoreDB bdb("cr+", true); | ||
| 1327 | if (!bdb.TxnBegin()) | ||
| 1328 | { | ||
| 1329 | fprintf(stderr, "Cannot begin Berkeley DB restore transaction.\n"); | ||
| 1330 | return 1; | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | int nCopied = 0; | ||
| 1334 | CWalletSQLiteRestoreVisitor visitor(bdb, nCopied); | ||
| 1335 | if (!sqlite.ScanRecords(visitor, strError)) | ||
| 1336 | { | ||
| 1337 | bdb.TxnAbort(); | ||
| 1338 | bdb.Close(); | ||
| 1339 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1340 | fprintf(stderr, "Cannot restore SQLite wallet export: %s\n", | ||
| 1341 | strError.c_str()); | ||
| 1342 | return 1; | ||
| 1343 | } | ||
| 1344 | if (nCopied != nSQLiteRecords) | ||
| 1345 | { | ||
| 1346 | bdb.TxnAbort(); | ||
| 1347 | bdb.Close(); | ||
| 1348 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1349 | fprintf(stderr, "SQLite wallet restore copied %d records, expected %d.\n", | ||
| 1350 | nCopied, nSQLiteRecords); | ||
| 1351 | return 2; | ||
| 1352 | } | ||
| 1353 | if (!bdb.TxnCommit()) | ||
| 1354 | { | ||
| 1355 | bdb.Close(); | ||
| 1356 | RemoveFailedRestoreWallet(strWalletPath); | ||
| 1357 | fprintf(stderr, "Cannot commit Berkeley DB restore transaction.\n"); | ||
| 1358 | return 1; | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | printf("SQLite wallet export restored to wallet.dat\n"); | ||
| 1362 | printf(" records restored: %d\n", nCopied); | ||
| 1363 | fflush(stdout); | ||
| 1364 | return 0; | ||
| 1365 | } | ||
| 1366 | |||
| 1367 | int CmdWalletSQLiteLoadCheck(const std::string& strPath) | ||
| 1368 | { | ||
| 1369 | AttachTerminal(); | ||
| 1370 | |||
| 1371 | if (strPath.empty()) | ||
| 1372 | { | ||
| 1373 | fprintf(stderr, "Missing SQLite wallet export path.\n"); | ||
| 1374 | return 1; | ||
| 1375 | } | ||
| 1376 | if (!FileExists(strPath.c_str())) | ||
| 1377 | { | ||
| 1378 | fprintf(stderr, "SQLite wallet export does not exist: %s\n", | ||
| 1379 | strPath.c_str()); | ||
| 1380 | return 1; | ||
| 1381 | } | ||
| 1382 | |||
| 1383 | string strError; | ||
| 1384 | CWalletDBSQLite sqlite; | ||
| 1385 | if (!sqlite.Open(strPath, strError)) | ||
| 1386 | { | ||
| 1387 | fprintf(stderr, "Cannot open SQLite wallet export: %s\n", strError.c_str()); | ||
| 1388 | return 1; | ||
| 1389 | } | ||
| 1390 | |||
| 1391 | CWalletSQLiteLoadCheckVisitor visitor; | ||
| 1392 | if (!sqlite.ScanRecords(visitor, strError)) | ||
| 1393 | { | ||
| 1394 | printf("SQLite wallet load check\n"); | ||
| 1395 | printf(" records checked: %d\n", visitor.nRecords); | ||
| 1396 | printf(" unknown records: %d\n", visitor.nUnknown); | ||
| 1397 | printf(" load check: failed\n"); | ||
| 1398 | printf(" failure: %s\n", strError.c_str()); | ||
| 1399 | fflush(stdout); | ||
| 1400 | return 2; | ||
| 1401 | } | ||
| 1402 | |||
| 1403 | printf("SQLite wallet load check\n"); | ||
| 1404 | printf(" records checked: %d\n", visitor.nRecords); | ||
| 1405 | printf(" unknown records: %d\n", visitor.nUnknown); | ||
| 1406 | printf(" load check: ok\n"); | ||
| 1407 | fflush(stdout); | ||
| 1408 | return 0; | ||
| 1409 | } | ||
| 1410 | |||
| 409 | int CmdEncryptWallet(const std::string& strPassphrase) | 1411 | int CmdEncryptWallet(const std::string& strPassphrase) |
| 410 | { | 1412 | { |
| 411 | AttachTerminal(); | 1413 | AttachTerminal(); |
| 412 | 1414 | ||
| 1415 | // Capture the backend now: under SQLite, EncryptWallet() closes the runtime | ||
| 1416 | // store to swap the file, so WalletSQLiteRuntimeActive() reads false by the | ||
| 1417 | // time we would decide whether to purge Berkeley DB logs below. | ||
| 1418 | bool fSQLiteBackend = WalletSQLiteRuntimeActive(); | ||
| 1419 | |||
| 413 | string strBackup; | 1420 | string strBackup; |
| 414 | string strError; | 1421 | string strError; |
| 415 | if (!EncryptWallet(strPassphrase, strBackup, strError)) | 1422 | if (!EncryptWallet(strPassphrase, strBackup, strError)) |
| @@ -420,6 +1427,8 @@ int CmdEncryptWallet(const std::string& strPassphrase) | |||
| 420 | } | 1427 | } |
| 421 | 1428 | ||
| 422 | printf("Wallet encrypted.\n"); | 1429 | printf("Wallet encrypted.\n"); |
| 1430 | if (fSQLiteBackend) | ||
| 1431 | printf("Wallet backend: SQLite (wallet.sqlite).\n"); | ||
| 423 | printf("The old unencrypted wallet was moved to:\n"); | 1432 | printf("The old unencrypted wallet was moved to:\n"); |
| 424 | printf(" %s\n", strBackup.c_str()); | 1433 | printf(" %s\n", strBackup.c_str()); |
| 425 | printf("That backup still contains private keys in plain text. Move it offline or\n"); | 1434 | printf("That backup still contains private keys in plain text. Move it offline or\n"); |
| @@ -432,7 +1441,13 @@ int CmdEncryptWallet(const std::string& strPassphrase) | |||
| 432 | // them. Closing the environment and dropping the logs is the last step of | 1441 | // them. Closing the environment and dropping the logs is the last step of |
| 433 | // encrypting, not an optimisation -- our own backup advice is to copy the | 1442 | // encrypting, not an optimisation -- our own backup advice is to copy the |
| 434 | // whole directory, and following it otherwise carries the keys along. | 1443 | // whole directory, and following it otherwise carries the keys along. |
| 435 | PurgeDbEnvironmentLogs(); | 1444 | // |
| 1445 | // The SQLite backend has no Berkeley DB environment behind the wallet, and | ||
| 1446 | // EncryptWallet() already closed and swapped the SQLite store; there is | ||
| 1447 | // nothing to purge, and calling into the environment here would only open | ||
| 1448 | // one needlessly. | ||
| 1449 | if (!fSQLiteBackend) | ||
| 1450 | PurgeDbEnvironmentLogs(); | ||
| 436 | return 0; | 1451 | return 0; |
| 437 | } | 1452 | } |
| 438 | 1453 | ||