diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 17:36:09 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 17:59:25 -0300 |
| commit | 8e62e362e2fbe6fd6c5d66ddb354ec544f8cf0db (patch) | |
| tree | d9773fd4b52f3ae7c316c6ff11fc2ab4d924a122 | |
| parent | c0f886f0683ebd1321751355ddc0c757496320c9 (diff) | |
wallet: add storage audit command
| -rw-r--r-- | src/main_gui.cpp | 9 | ||||
| -rw-r--r-- | src/selftest.cpp | 32 | ||||
| -rw-r--r-- | src/walletcmd.cpp | 178 | ||||
| -rw-r--r-- | src/walletcmd.h | 4 |
4 files changed, 223 insertions, 0 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 50c4f34..532bc3b 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -172,6 +172,8 @@ static void PrintUsage() | |||
| 172 | printf(" /recoveryaudit (show how much spendable balance is\n"); | 172 | printf(" /recoveryaudit (show how much spendable balance is\n"); |
| 173 | printf(" covered by the recovery phrase;\n"); | 173 | printf(" covered by the recovery phrase;\n"); |
| 174 | printf(" exits 2 when wallet.dat is still needed)\n"); | 174 | printf(" exits 2 when wallet.dat is still needed)\n"); |
| 175 | printf(" /walletstorageaudit (count wallet.dat record types without\n"); | ||
| 176 | printf(" printing wallet values, then exit)\n"); | ||
| 175 | printf(" /rescan (walk the chain for coins this wallet owns\n"); | 177 | printf(" /rescan (walk the chain for coins this wallet owns\n"); |
| 176 | printf(" but never recorded, then exit)\n"); | 178 | printf(" but never recorded, then exit)\n"); |
| 177 | printf("\n"); | 179 | printf("\n"); |
| @@ -704,6 +706,13 @@ int main(int argc, char* argv[]) | |||
| 704 | return nRet; | 706 | return nRet; |
| 705 | } | 707 | } |
| 706 | 708 | ||
| 709 | if (arg(argc,argv,"/walletstorageaudit") || arg(argc,argv,"-walletstorageaudit")) | ||
| 710 | { | ||
| 711 | int nRet = CmdWalletStorageAudit(); | ||
| 712 | DBFlush(true); | ||
| 713 | return nRet; | ||
| 714 | } | ||
| 715 | |||
| 707 | if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) | 716 | if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) |
| 708 | { | 717 | { |
| 709 | int nRet = CmdNewPhrase(); | 718 | int nRet = CmdNewPhrase(); |
diff --git a/src/selftest.cpp b/src/selftest.cpp index 8a448f0..b112238 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -1314,6 +1314,21 @@ static int RunWalletPortabilitySelfTest() | |||
| 1314 | FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"), | 1314 | FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"), |
| 1315 | "the original wallet reports its recovery phrase") ? 0 : 1; | 1315 | "the original wallet reports its recovery phrase") ? 0 : 1; |
| 1316 | 1316 | ||
| 1317 | string strOriginalStorageAudit = tmp + "/original-storage-audit.txt"; | ||
| 1318 | vector<string> vOriginalStorageAuditArgs; | ||
| 1319 | vOriginalStorageAuditArgs.push_back("-datadir=" + strOriginal); | ||
| 1320 | vOriginalStorageAuditArgs.push_back("-nomanagedtor"); | ||
| 1321 | vOriginalStorageAuditArgs.push_back("-nogui"); | ||
| 1322 | vOriginalStorageAuditArgs.push_back("-walletstorageaudit"); | ||
| 1323 | int nOriginalStorageAuditRet = | ||
| 1324 | RunBitflashChild(strExe, vOriginalStorageAuditArgs, NULL, &strOriginalStorageAudit); | ||
| 1325 | nFail += Check(nOriginalStorageAuditRet == 0 && | ||
| 1326 | FileContainsText(strOriginalStorageAudit, "Wallet storage audit") && | ||
| 1327 | FileContainsText(strOriginalStorageAudit, "plain HD seed: complete") && | ||
| 1328 | FileContainsText(strOriginalStorageAudit, "encrypted HD seed: none") && | ||
| 1329 | FileContainsText(strOriginalStorageAudit, "malformed records: 0"), | ||
| 1330 | "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1; | ||
| 1331 | |||
| 1317 | string strPortableWallet = tmp + "/portable-wallet.dat"; | 1332 | string strPortableWallet = tmp + "/portable-wallet.dat"; |
| 1318 | string strBackupOut = tmp + "/backupwallet.txt"; | 1333 | string strBackupOut = tmp + "/backupwallet.txt"; |
| 1319 | vector<string> vBackupArgs; | 1334 | vector<string> vBackupArgs; |
| @@ -1368,6 +1383,23 @@ static int RunWalletPortabilitySelfTest() | |||
| 1368 | nFail += Check(nEncryptRet == 0 && | 1383 | nFail += Check(nEncryptRet == 0 && |
| 1369 | !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."), | 1384 | !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."), |
| 1370 | "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1; | 1385 | "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1; |
| 1386 | |||
| 1387 | string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt"; | ||
| 1388 | vector<string> vEncryptedStorageAuditArgs; | ||
| 1389 | vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal); | ||
| 1390 | vEncryptedStorageAuditArgs.push_back("-nomanagedtor"); | ||
| 1391 | vEncryptedStorageAuditArgs.push_back("-nogui"); | ||
| 1392 | vEncryptedStorageAuditArgs.push_back("-walletstorageaudit"); | ||
| 1393 | int nEncryptedStorageAuditRet = | ||
| 1394 | RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit); | ||
| 1395 | nFail += Check(nEncryptedStorageAuditRet == 0 && | ||
| 1396 | FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") && | ||
| 1397 | FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") && | ||
| 1398 | FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") && | ||
| 1399 | FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") && | ||
| 1400 | FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") && | ||
| 1401 | FileContainsText(strEncryptedStorageAudit, "malformed records: 0"), | ||
| 1402 | "the storage audit recognizes an encrypted wallet") ? 0 : 1; | ||
| 1371 | } | 1403 | } |
| 1372 | catch (const std::exception& e) | 1404 | catch (const std::exception& e) |
| 1373 | { | 1405 | { |
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index eccb9b3..896d593 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -470,6 +470,184 @@ int CmdRecoveryAudit() | |||
| 470 | return 0; | 470 | return 0; |
| 471 | } | 471 | } |
| 472 | 472 | ||
| 473 | struct WalletStorageAuditCounts | ||
| 474 | { | ||
| 475 | unsigned int nTotal; | ||
| 476 | unsigned int nMalformed; | ||
| 477 | unsigned int nUnknown; | ||
| 478 | unsigned int nVersion; | ||
| 479 | unsigned int nNames; | ||
| 480 | unsigned int nTransactions; | ||
| 481 | unsigned int nPlainKeys; | ||
| 482 | unsigned int nEncryptedKeys; | ||
| 483 | unsigned int nMasterKeys; | ||
| 484 | unsigned int nDefaultKey; | ||
| 485 | unsigned int nPlainHDMaster; | ||
| 486 | unsigned int nPlainHDChainCode; | ||
| 487 | unsigned int nCryptedHDMaster; | ||
| 488 | unsigned int nCryptedHDChainCode; | ||
| 489 | unsigned int nWalletMinVersion; | ||
| 490 | unsigned int nPool; | ||
| 491 | unsigned int nSettings; | ||
| 492 | unsigned int nHDNext; | ||
| 493 | unsigned int nHDSchema; | ||
| 494 | unsigned int nHDCoinType; | ||
| 495 | unsigned int nHDReceiveNext; | ||
| 496 | unsigned int nHDChangeNext; | ||
| 497 | |||
| 498 | WalletStorageAuditCounts() | ||
| 499 | { | ||
| 500 | memset(this, 0, sizeof(*this)); | ||
| 501 | } | ||
| 502 | }; | ||
| 503 | |||
| 504 | static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain) | ||
| 505 | { | ||
| 506 | if (nMaster == 0 && nChain == 0) | ||
| 507 | return "none"; | ||
| 508 | if (nMaster > 0 && nChain > 0) | ||
| 509 | return "complete"; | ||
| 510 | return "incomplete"; | ||
| 511 | } | ||
| 512 | |||
| 513 | static void CountWalletStorageType(const std::string& strType, | ||
| 514 | WalletStorageAuditCounts& c) | ||
| 515 | { | ||
| 516 | if (strType == "version") | ||
| 517 | c.nVersion++; | ||
| 518 | else if (strType == "name") | ||
| 519 | c.nNames++; | ||
| 520 | else if (strType == "tx") | ||
| 521 | c.nTransactions++; | ||
| 522 | else if (strType == "key") | ||
| 523 | c.nPlainKeys++; | ||
| 524 | else if (strType == "ckey") | ||
| 525 | c.nEncryptedKeys++; | ||
| 526 | else if (strType == "mkey") | ||
| 527 | c.nMasterKeys++; | ||
| 528 | else if (strType == "defaultkey") | ||
| 529 | c.nDefaultKey++; | ||
| 530 | else if (strType == "hdmaster") | ||
| 531 | c.nPlainHDMaster++; | ||
| 532 | else if (strType == "hdchaincode") | ||
| 533 | c.nPlainHDChainCode++; | ||
| 534 | else if (strType == "cryptedhdmaster") | ||
| 535 | c.nCryptedHDMaster++; | ||
| 536 | else if (strType == "cryptedhdchaincode") | ||
| 537 | c.nCryptedHDChainCode++; | ||
| 538 | else if (strType == "walletminversion") | ||
| 539 | c.nWalletMinVersion++; | ||
| 540 | else if (strType == "pool") | ||
| 541 | c.nPool++; | ||
| 542 | else if (strType == "setting") | ||
| 543 | c.nSettings++; | ||
| 544 | else if (strType == "hdnext") | ||
| 545 | c.nHDNext++; | ||
| 546 | else if (strType == "hdschema") | ||
| 547 | c.nHDSchema++; | ||
| 548 | else if (strType == "hdcointype") | ||
| 549 | c.nHDCoinType++; | ||
| 550 | else if (strType == "hdreceivenext") | ||
| 551 | c.nHDReceiveNext++; | ||
| 552 | else if (strType == "hdchangenext") | ||
| 553 | c.nHDChangeNext++; | ||
| 554 | else | ||
| 555 | c.nUnknown++; | ||
| 556 | } | ||
| 557 | |||
| 558 | static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts, | ||
| 559 | std::string& strError) | ||
| 560 | { | ||
| 561 | class CWalletAuditDB : public CWalletDB | ||
| 562 | { | ||
| 563 | public: | ||
| 564 | CWalletAuditDB() : CWalletDB("r") { } | ||
| 565 | using CDB::GetCursor; | ||
| 566 | using CDB::ReadAtCursor; | ||
| 567 | }; | ||
| 568 | |||
| 569 | CWalletAuditDB walletdb; | ||
| 570 | Dbc* pcursor = walletdb.GetCursor(); | ||
| 571 | if (!pcursor) | ||
| 572 | { | ||
| 573 | strError = "cannot open wallet.dat cursor"; | ||
| 574 | return false; | ||
| 575 | } | ||
| 576 | |||
| 577 | for (;;) | ||
| 578 | { | ||
| 579 | CDataStream ssKey(SER_DISK); | ||
| 580 | CDataStream ssValue(SER_DISK); | ||
| 581 | int ret = walletdb.ReadAtCursor(pcursor, ssKey, ssValue); | ||
| 582 | if (ret == DB_NOTFOUND) | ||
| 583 | break; | ||
| 584 | if (ret != 0) | ||
| 585 | { | ||
| 586 | pcursor->close(); | ||
| 587 | strError = strprintf("Berkeley DB cursor read failed (%d)", ret); | ||
| 588 | return false; | ||
| 589 | } | ||
| 590 | |||
| 591 | counts.nTotal++; | ||
| 592 | try | ||
| 593 | { | ||
| 594 | std::string strType; | ||
| 595 | ssKey >> strType; | ||
| 596 | CountWalletStorageType(strType, counts); | ||
| 597 | } | ||
| 598 | catch (...) | ||
| 599 | { | ||
| 600 | counts.nMalformed++; | ||
| 601 | } | ||
| 602 | } | ||
| 603 | |||
| 604 | pcursor->close(); | ||
| 605 | return true; | ||
| 606 | } | ||
| 607 | |||
| 608 | int CmdWalletStorageAudit() | ||
| 609 | { | ||
| 610 | AttachTerminal(); | ||
| 611 | |||
| 612 | WalletStorageAuditCounts counts; | ||
| 613 | std::string strError; | ||
| 614 | if (!ReadWalletStorageCounts(counts, strError)) | ||
| 615 | { | ||
| 616 | fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str()); | ||
| 617 | return 1; | ||
| 618 | } | ||
| 619 | |||
| 620 | printf("Wallet storage audit\n"); | ||
| 621 | printf(" records total: %u\n", counts.nTotal); | ||
| 622 | printf(" malformed records: %u\n", counts.nMalformed); | ||
| 623 | printf(" unknown records: %u\n", counts.nUnknown); | ||
| 624 | printf(" plain private keys: %u\n", counts.nPlainKeys); | ||
| 625 | printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); | ||
| 626 | printf(" encryption master keys: %u\n", counts.nMasterKeys); | ||
| 627 | printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none"); | ||
| 628 | printf(" plain HD seed: %s\n", | ||
| 629 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 630 | counts.nPlainHDChainCode).c_str()); | ||
| 631 | printf(" encrypted HD seed: %s\n", | ||
| 632 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 633 | counts.nCryptedHDChainCode).c_str()); | ||
| 634 | printf(" wallet minimum version: %s\n", | ||
| 635 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 636 | printf(" keypool entries: %u\n", counts.nPool); | ||
| 637 | printf(" wallet transactions: %u\n", counts.nTransactions); | ||
| 638 | printf(" address book labels: %u\n", counts.nNames); | ||
| 639 | printf(" settings: %u\n", counts.nSettings); | ||
| 640 | printf(" HD metadata records: %u\n", | ||
| 641 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 642 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 643 | printf(" database version records: %u\n", counts.nVersion); | ||
| 644 | fflush(stdout); | ||
| 645 | |||
| 646 | if (counts.nMalformed > 0) | ||
| 647 | return 2; | ||
| 648 | return 0; | ||
| 649 | } | ||
| 650 | |||
| 473 | int CmdEncryptWallet(const std::string& strPassphrase) | 651 | int CmdEncryptWallet(const std::string& strPassphrase) |
| 474 | { | 652 | { |
| 475 | AttachTerminal(); | 653 | AttachTerminal(); |
diff --git a/src/walletcmd.h b/src/walletcmd.h index 007b8b5..4a703db 100644 --- a/src/walletcmd.h +++ b/src/walletcmd.h | |||
| @@ -62,6 +62,10 @@ int CmdShowDerived(int nCount); | |||
| 62 | // by the installed recovery phrase or still depends on wallet.dat-only keys. | 62 | // by the installed recovery phrase or still depends on wallet.dat-only keys. |
| 63 | int CmdRecoveryAudit(); | 63 | int CmdRecoveryAudit(); |
| 64 | 64 | ||
| 65 | // Diagnostic: count wallet.dat record types without printing keys, | ||
| 66 | // addresses, transaction ids, labels, or other wallet values. | ||
| 67 | int CmdWalletStorageAudit(); | ||
| 68 | |||
| 65 | // Rewrite wallet.dat so private keys and the HD seed are encrypted with the | 69 | // Rewrite wallet.dat so private keys and the HD seed are encrypted with the |
| 66 | // given passphrase. Returns 0 on success and exits without starting the node. | 70 | // given passphrase. Returns 0 on success and exits without starting the node. |
| 67 | int CmdEncryptWallet(const std::string& strPassphrase); | 71 | int CmdEncryptWallet(const std::string& strPassphrase); |