diff options
| author | Bitflash <admin@bitflash.network> | 2026-08-14 08:56:40 -0300 |
|---|---|---|
| committer | Bitflash <admin@bitflash.network> | 2026-08-14 08:56:40 -0300 |
| commit | 5306542f6121e0c0b4ed1210e24d55fa4fbb7127 (patch) | |
| tree | 5faf35daa569d987b87dbb161bb18ec170f944b0 /src | |
| parent | 9e39a50d1f110020b0841d31e4b931aa877cc448 (diff) | |
| parent | 805c5757b801d80ea575c6a8b58e2855254f9046 (diff) | |
Merge pull request 'wallet: audit Berkeley DB wallet storage' (#38) from wallet-storage-audit-bdb into main
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 3 | ||||
| -rw-r--r-- | src/main.h | 2 | ||||
| -rw-r--r-- | src/main_gui.cpp | 18 | ||||
| -rw-r--r-- | src/selftest.cpp | 336 | ||||
| -rw-r--r-- | src/walletcmd.cpp | 263 | ||||
| -rw-r--r-- | src/walletcmd.h | 4 |
6 files changed, 620 insertions, 6 deletions
diff --git a/src/main.cpp b/src/main.cpp index f83164f..5bd4028 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -4355,6 +4355,9 @@ WalletRecoveryAudit GetWalletRecoveryAudit() | |||
| 4355 | CRITICAL_BLOCK(cs_keyPool) | 4355 | CRITICAL_BLOCK(cs_keyPool) |
| 4356 | { | 4356 | { |
| 4357 | audit.fHaveSeed = HaveHDSeed(); | 4357 | audit.fHaveSeed = HaveHDSeed(); |
| 4358 | audit.fSeedEncryptedLocked = !audit.fHaveSeed && | ||
| 4359 | (!vchCryptedHDMaster.empty() || | ||
| 4360 | !vchCryptedHDChainCode.empty()); | ||
| 4358 | audit.nSchema = nHDKeySchema; | 4361 | audit.nSchema = nHDKeySchema; |
| 4359 | audit.nDerivedKnown = nHDNext; | 4362 | audit.nDerivedKnown = nHDNext; |
| 4360 | audit.nReceiveNext = nHDReceiveNext; | 4363 | audit.nReceiveNext = nHDReceiveNext; |
| @@ -292,6 +292,7 @@ int64 GetBalance(); | |||
| 292 | struct WalletRecoveryAudit | 292 | struct WalletRecoveryAudit |
| 293 | { | 293 | { |
| 294 | bool fHaveSeed; | 294 | bool fHaveSeed; |
| 295 | bool fSeedEncryptedLocked; | ||
| 295 | bool fDeriveComplete; | 296 | bool fDeriveComplete; |
| 296 | int nSchema; | 297 | int nSchema; |
| 297 | unsigned int nDerivedKnown; | 298 | unsigned int nDerivedKnown; |
| @@ -311,6 +312,7 @@ struct WalletRecoveryAudit | |||
| 311 | WalletRecoveryAudit() | 312 | WalletRecoveryAudit() |
| 312 | { | 313 | { |
| 313 | fHaveSeed = false; | 314 | fHaveSeed = false; |
| 315 | fSeedEncryptedLocked = false; | ||
| 314 | fDeriveComplete = true; | 316 | fDeriveComplete = true; |
| 315 | nSchema = HD_SCHEMA_NONE; | 317 | nSchema = HD_SCHEMA_NONE; |
| 316 | nDerivedKnown = 0; | 318 | nDerivedKnown = 0; |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 3f52e91..395cc0f 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -114,7 +114,8 @@ static void PrintUsage() | |||
| 114 | printf(" /gen\n"); | 114 | printf(" /gen\n"); |
| 115 | printf(" /nogui or /daemon\n"); | 115 | printf(" /nogui or /daemon\n"); |
| 116 | printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); | 116 | printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); |
| 117 | printf(" net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, or managed-tor\n"); | 117 | printf(" wallet-portability, net-message, consensus-limits, pool-stratum,\n"); |
| 118 | printf(" parse-money, socks5-proxy, or managed-tor\n"); | ||
| 118 | printf("\n"); | 119 | printf("\n"); |
| 119 | printf("Mining mode:\n"); | 120 | printf("Mining mode:\n"); |
| 120 | printf(" /operator\n"); | 121 | printf(" /operator\n"); |
| @@ -171,6 +172,10 @@ static void PrintUsage() | |||
| 171 | printf(" /recoveryaudit (show how much spendable balance is\n"); | 172 | printf(" /recoveryaudit (show how much spendable balance is\n"); |
| 172 | printf(" covered by the recovery phrase;\n"); | 173 | printf(" covered by the recovery phrase;\n"); |
| 173 | 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"); | ||
| 177 | printf(" /walletstorageauditjson=FILE\n"); | ||
| 178 | printf(" (write the same audit as deterministic JSON)\n"); | ||
| 174 | printf(" /rescan (walk the chain for coins this wallet owns\n"); | 179 | printf(" /rescan (walk the chain for coins this wallet owns\n"); |
| 175 | printf(" but never recorded, then exit)\n"); | 180 | printf(" but never recorded, then exit)\n"); |
| 176 | printf("\n"); | 181 | printf("\n"); |
| @@ -703,6 +708,17 @@ int main(int argc, char* argv[]) | |||
| 703 | return nRet; | 708 | return nRet; |
| 704 | } | 709 | } |
| 705 | 710 | ||
| 711 | string strWalletStorageAuditJson = | ||
| 712 | argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson"); | ||
| 713 | if (arg(argc,argv,"/walletstorageaudit") || | ||
| 714 | arg(argc,argv,"-walletstorageaudit") || | ||
| 715 | !strWalletStorageAuditJson.empty()) | ||
| 716 | { | ||
| 717 | int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson); | ||
| 718 | DBFlush(true); | ||
| 719 | return nRet; | ||
| 720 | } | ||
| 721 | |||
| 706 | if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) | 722 | if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) |
| 707 | { | 723 | { |
| 708 | int nRet = CmdNewPhrase(); | 724 | int nRet = CmdNewPhrase(); |
diff --git a/src/selftest.cpp b/src/selftest.cpp index 3c93927..73c2935 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp | |||
| @@ -34,6 +34,7 @@ extern int RunPoolStratumSelfTest(); | |||
| 34 | #undef printf | 34 | #undef printf |
| 35 | 35 | ||
| 36 | #include <mutex> | 36 | #include <mutex> |
| 37 | #include <cerrno> | ||
| 37 | #include <stdexcept> | 38 | #include <stdexcept> |
| 38 | #include <thread> | 39 | #include <thread> |
| 39 | 40 | ||
| @@ -925,6 +926,90 @@ static bool WriteTextFile(const string& strPath, const string& strText) | |||
| 925 | return fOk; | 926 | return fOk; |
| 926 | } | 927 | } |
| 927 | 928 | ||
| 929 | static bool MakeDirLocal(const string& strPath) | ||
| 930 | { | ||
| 931 | #ifdef _WIN32 | ||
| 932 | return CreateDirectoryA(strPath.c_str(), NULL) || GetLastError() == ERROR_ALREADY_EXISTS; | ||
| 933 | #else | ||
| 934 | return mkdir(strPath.c_str(), 0700) == 0 || errno == EEXIST; | ||
| 935 | #endif | ||
| 936 | } | ||
| 937 | |||
| 938 | static bool CopyFileLocal(const string& strSrc, const string& strDst) | ||
| 939 | { | ||
| 940 | FILE* in = fopen(strSrc.c_str(), "rb"); | ||
| 941 | if (!in) | ||
| 942 | return false; | ||
| 943 | FILE* out = fopen(strDst.c_str(), "wb"); | ||
| 944 | if (!out) | ||
| 945 | { | ||
| 946 | fclose(in); | ||
| 947 | return false; | ||
| 948 | } | ||
| 949 | |||
| 950 | bool fOk = true; | ||
| 951 | char buf[65536]; | ||
| 952 | size_t n = 0; | ||
| 953 | while ((n = fread(buf, 1, sizeof(buf), in)) > 0) | ||
| 954 | { | ||
| 955 | if (fwrite(buf, 1, n, out) != n) | ||
| 956 | { | ||
| 957 | fOk = false; | ||
| 958 | break; | ||
| 959 | } | ||
| 960 | } | ||
| 961 | if (ferror(in)) | ||
| 962 | fOk = false; | ||
| 963 | if (fclose(out) != 0) | ||
| 964 | fOk = false; | ||
| 965 | fclose(in); | ||
| 966 | return fOk; | ||
| 967 | } | ||
| 968 | |||
| 969 | static bool DirectoryHasFileWithPrefix(const string& strDir, const string& strPrefix) | ||
| 970 | { | ||
| 971 | #ifdef _WIN32 | ||
| 972 | WIN32_FIND_DATAA findData; | ||
| 973 | string pattern = strDir + "\\*"; | ||
| 974 | HANDLE hFind = FindFirstFileA(pattern.c_str(), &findData); | ||
| 975 | if (hFind == INVALID_HANDLE_VALUE) | ||
| 976 | return false; | ||
| 977 | bool fFound = false; | ||
| 978 | do | ||
| 979 | { | ||
| 980 | if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) | ||
| 981 | { | ||
| 982 | string name = findData.cFileName; | ||
| 983 | if (name.compare(0, strPrefix.size(), strPrefix) == 0) | ||
| 984 | { | ||
| 985 | fFound = true; | ||
| 986 | break; | ||
| 987 | } | ||
| 988 | } | ||
| 989 | } | ||
| 990 | while (FindNextFileA(hFind, &findData)); | ||
| 991 | FindClose(hFind); | ||
| 992 | return fFound; | ||
| 993 | #else | ||
| 994 | DIR* dir = opendir(strDir.c_str()); | ||
| 995 | if (!dir) | ||
| 996 | return false; | ||
| 997 | bool fFound = false; | ||
| 998 | struct dirent* ent; | ||
| 999 | while ((ent = readdir(dir)) != NULL) | ||
| 1000 | { | ||
| 1001 | string name = ent->d_name; | ||
| 1002 | if (name.compare(0, strPrefix.size(), strPrefix) == 0) | ||
| 1003 | { | ||
| 1004 | fFound = true; | ||
| 1005 | break; | ||
| 1006 | } | ||
| 1007 | } | ||
| 1008 | closedir(dir); | ||
| 1009 | return fFound; | ||
| 1010 | #endif | ||
| 1011 | } | ||
| 1012 | |||
| 928 | static string QuoteCommandArg(const string& str) | 1013 | static string QuoteCommandArg(const string& str) |
| 929 | { | 1014 | { |
| 930 | string out = "\""; | 1015 | string out = "\""; |
| @@ -941,7 +1026,8 @@ static string QuoteCommandArg(const string& str) | |||
| 941 | 1026 | ||
| 942 | static int RunBitflashChild(const string& strExe, | 1027 | static int RunBitflashChild(const string& strExe, |
| 943 | const vector<string>& vArgs, | 1028 | const vector<string>& vArgs, |
| 944 | const string* pStdinFile = NULL) | 1029 | const string* pStdinFile = NULL, |
| 1030 | const string* pOutputFile = NULL) | ||
| 945 | { | 1031 | { |
| 946 | #ifdef _WIN32 | 1032 | #ifdef _WIN32 |
| 947 | vector<char*> argv; | 1033 | vector<char*> argv; |
| @@ -954,8 +1040,16 @@ static int RunBitflashChild(const string& strExe, | |||
| 954 | int nOldErr = _dup(2); | 1040 | int nOldErr = _dup(2); |
| 955 | int nOldIn = _dup(0); | 1041 | int nOldIn = _dup(0); |
| 956 | int nNull = _open("NUL", _O_WRONLY); | 1042 | int nNull = _open("NUL", _O_WRONLY); |
| 1043 | int nOut = -1; | ||
| 957 | int nIn = -1; | 1044 | int nIn = -1; |
| 958 | if (nNull >= 0) | 1045 | if (pOutputFile) |
| 1046 | nOut = _open(pOutputFile->c_str(), _O_WRONLY|_O_CREAT|_O_TRUNC|_O_BINARY, _S_IREAD|_S_IWRITE); | ||
| 1047 | if (nOut >= 0) | ||
| 1048 | { | ||
| 1049 | _dup2(nOut, 1); | ||
| 1050 | _dup2(nOut, 2); | ||
| 1051 | } | ||
| 1052 | else if (nNull >= 0) | ||
| 959 | { | 1053 | { |
| 960 | _dup2(nNull, 1); | 1054 | _dup2(nNull, 1); |
| 961 | _dup2(nNull, 2); | 1055 | _dup2(nNull, 2); |
| @@ -969,6 +1063,8 @@ static int RunBitflashChild(const string& strExe, | |||
| 969 | int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); | 1063 | int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); |
| 970 | if (nIn >= 0) | 1064 | if (nIn >= 0) |
| 971 | _close(nIn); | 1065 | _close(nIn); |
| 1066 | if (nOut >= 0) | ||
| 1067 | _close(nOut); | ||
| 972 | if (nNull >= 0) | 1068 | if (nNull >= 0) |
| 973 | _close(nNull); | 1069 | _close(nNull); |
| 974 | if (nOldIn >= 0) | 1070 | if (nOldIn >= 0) |
| @@ -993,7 +1089,10 @@ static int RunBitflashChild(const string& strExe, | |||
| 993 | strCmd += " " + QuoteCommandArg(vArgs[i]); | 1089 | strCmd += " " + QuoteCommandArg(vArgs[i]); |
| 994 | if (pStdinFile) | 1090 | if (pStdinFile) |
| 995 | strCmd += " < " + QuoteCommandArg(*pStdinFile); | 1091 | strCmd += " < " + QuoteCommandArg(*pStdinFile); |
| 996 | strCmd += " > /dev/null 2>&1"; | 1092 | if (pOutputFile) |
| 1093 | strCmd += " > " + QuoteCommandArg(*pOutputFile) + " 2>&1"; | ||
| 1094 | else | ||
| 1095 | strCmd += " > /dev/null 2>&1"; | ||
| 997 | return system(strCmd.c_str()); | 1096 | return system(strCmd.c_str()); |
| 998 | #endif | 1097 | #endif |
| 999 | } | 1098 | } |
| @@ -1066,6 +1165,8 @@ static int RunWalletEncryptSelfTest() | |||
| 1066 | string strRightDump = tmp + "/right-pass-dump.txt"; | 1165 | string strRightDump = tmp + "/right-pass-dump.txt"; |
| 1067 | string strLiteralDump = tmp + "/literal-pass-dump.txt"; | 1166 | string strLiteralDump = tmp + "/literal-pass-dump.txt"; |
| 1068 | string strStdinDump = tmp + "/stdin-pass-dump.txt"; | 1167 | string strStdinDump = tmp + "/stdin-pass-dump.txt"; |
| 1168 | string strLockedAudit = tmp + "/locked-recovery-audit.txt"; | ||
| 1169 | string strUnlockedAudit = tmp + "/unlocked-recovery-audit.txt"; | ||
| 1069 | string strWrongPassFile = tmp + "/wrong-pass.txt"; | 1170 | string strWrongPassFile = tmp + "/wrong-pass.txt"; |
| 1070 | string strRightPassFile = tmp + "/right-pass.txt"; | 1171 | string strRightPassFile = tmp + "/right-pass.txt"; |
| 1071 | nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && | 1172 | nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && |
| @@ -1081,6 +1182,17 @@ static int RunWalletEncryptSelfTest() | |||
| 1081 | nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), | 1182 | nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), |
| 1082 | "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; | 1183 | "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; |
| 1083 | 1184 | ||
| 1185 | vector<string> vLockedAuditArgs; | ||
| 1186 | vLockedAuditArgs.push_back("-datadir=" + tmp); | ||
| 1187 | vLockedAuditArgs.push_back("-recoveryaudit"); | ||
| 1188 | vLockedAuditArgs.push_back("-nogui"); | ||
| 1189 | int nLockedAuditRet = RunBitflashChild(strExe, vLockedAuditArgs, NULL, &strLockedAudit); | ||
| 1190 | nFail += Check(nLockedAuditRet == 2 && | ||
| 1191 | FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit") && | ||
| 1192 | FileContainsText(strLockedAudit, "recovery coverage: unavailable while wallet is locked") && | ||
| 1193 | FileContainsText(strLockedAudit, "Unlock the wallet with /walletpassphrase"), | ||
| 1194 | "a locked encrypted wallet reports that phrase coverage needs unlock") ? 0 : 1; | ||
| 1195 | |||
| 1084 | vector<string> vLiteralArgs; | 1196 | vector<string> vLiteralArgs; |
| 1085 | vLiteralArgs.push_back("-datadir=" + tmp); | 1197 | vLiteralArgs.push_back("-datadir=" + tmp); |
| 1086 | vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); | 1198 | vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); |
| @@ -1101,6 +1213,17 @@ static int RunWalletEncryptSelfTest() | |||
| 1101 | nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), | 1213 | nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), |
| 1102 | "the restarted wallet can decrypt and dump the original key") ? 0 : 1; | 1214 | "the restarted wallet can decrypt and dump the original key") ? 0 : 1; |
| 1103 | 1215 | ||
| 1216 | vector<string> vUnlockedAuditArgs; | ||
| 1217 | vUnlockedAuditArgs.push_back("-datadir=" + tmp); | ||
| 1218 | vUnlockedAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile); | ||
| 1219 | vUnlockedAuditArgs.push_back("-recoveryaudit"); | ||
| 1220 | vUnlockedAuditArgs.push_back("-nogui"); | ||
| 1221 | int nUnlockedAuditRet = RunBitflashChild(strExe, vUnlockedAuditArgs, NULL, &strUnlockedAudit); | ||
| 1222 | nFail += Check(nUnlockedAuditRet == 0 && | ||
| 1223 | FileContainsText(strUnlockedAudit, "recovery phrase: present") && | ||
| 1224 | !FileContainsText(strUnlockedAudit, "recovery phrase: not installed"), | ||
| 1225 | "an unlocked encrypted wallet audits the installed recovery phrase") ? 0 : 1; | ||
| 1226 | |||
| 1104 | vector<string> vStdinArgs; | 1227 | vector<string> vStdinArgs; |
| 1105 | vStdinArgs.push_back("-datadir=" + tmp); | 1228 | vStdinArgs.push_back("-datadir=" + tmp); |
| 1106 | vStdinArgs.push_back("-walletpassphrase"); | 1229 | vStdinArgs.push_back("-walletpassphrase"); |
| @@ -1131,6 +1254,209 @@ static int RunWalletEncryptSelfTest() | |||
| 1131 | return nFail == 0 ? 0 : 1; | 1254 | return nFail == 0 ? 0 : 1; |
| 1132 | } | 1255 | } |
| 1133 | 1256 | ||
| 1257 | static int RunWalletPortabilitySelfTest() | ||
| 1258 | { | ||
| 1259 | fflush(stdout); | ||
| 1260 | printf("wallet-portability self-test\n"); | ||
| 1261 | |||
| 1262 | std::string tmp; | ||
| 1263 | std::string cwd; | ||
| 1264 | if (!MakeTempDir(tmp)) | ||
| 1265 | { | ||
| 1266 | printf(" FAIL could not create a temporary data directory\n"); | ||
| 1267 | return 1; | ||
| 1268 | } | ||
| 1269 | if (!GetCurrentDir(cwd)) | ||
| 1270 | { | ||
| 1271 | printf(" FAIL could not read current directory\n"); | ||
| 1272 | RemoveTree(tmp); | ||
| 1273 | return 1; | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | int nFail = 0; | ||
| 1277 | printf(" temp root: %s\n", tmp.c_str()); | ||
| 1278 | |||
| 1279 | try | ||
| 1280 | { | ||
| 1281 | #ifdef _WIN32 | ||
| 1282 | string strExe = cwd + "\\bitflash.exe"; | ||
| 1283 | #else | ||
| 1284 | string strExe = cwd + "/bitflash-node"; | ||
| 1285 | #endif | ||
| 1286 | string strOriginal = tmp + "/original"; | ||
| 1287 | string strRawCopy = tmp + "/raw-copy"; | ||
| 1288 | string strBackupCopy = tmp + "/backup-copy"; | ||
| 1289 | nFail += Check(MakeDirLocal(strOriginal) && | ||
| 1290 | MakeDirLocal(strRawCopy) && | ||
| 1291 | MakeDirLocal(strBackupCopy), | ||
| 1292 | "temporary wallet directories can be created") ? 0 : 1; | ||
| 1293 | |||
| 1294 | string strCreateOut = tmp + "/create.txt"; | ||
| 1295 | vector<string> vCreateArgs; | ||
| 1296 | vCreateArgs.push_back("-datadir=" + strOriginal); | ||
| 1297 | vCreateArgs.push_back("-nomanagedtor"); | ||
| 1298 | vCreateArgs.push_back("-nogui"); | ||
| 1299 | vCreateArgs.push_back("-newphrase"); | ||
| 1300 | int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut); | ||
| 1301 | nFail += Check(nCreateRet == 0 && | ||
| 1302 | FileContainsText(strCreateOut, "Write these twelve words down"), | ||
| 1303 | "a phrase wallet can be created in a fresh datadir") ? 0 : 1; | ||
| 1304 | |||
| 1305 | string strOriginalAudit = tmp + "/original-audit.txt"; | ||
| 1306 | vector<string> vOriginalAuditArgs; | ||
| 1307 | vOriginalAuditArgs.push_back("-datadir=" + strOriginal); | ||
| 1308 | vOriginalAuditArgs.push_back("-nomanagedtor"); | ||
| 1309 | vOriginalAuditArgs.push_back("-nogui"); | ||
| 1310 | vOriginalAuditArgs.push_back("-recoveryaudit"); | ||
| 1311 | int nOriginalAuditRet = RunBitflashChild(strExe, vOriginalAuditArgs, NULL, &strOriginalAudit); | ||
| 1312 | nFail += Check(nOriginalAuditRet == 0 && | ||
| 1313 | FileContainsText(strOriginalAudit, "recovery phrase: present") && | ||
| 1314 | FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"), | ||
| 1315 | "the original wallet reports its recovery phrase") ? 0 : 1; | ||
| 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 | |||
| 1332 | string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json"; | ||
| 1333 | vector<string> vOriginalStorageAuditJsonArgs; | ||
| 1334 | vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal); | ||
| 1335 | vOriginalStorageAuditJsonArgs.push_back("-nomanagedtor"); | ||
| 1336 | vOriginalStorageAuditJsonArgs.push_back("-nogui"); | ||
| 1337 | vOriginalStorageAuditJsonArgs.push_back("-walletstorageauditjson=" + | ||
| 1338 | strOriginalStorageAuditJson); | ||
| 1339 | int nOriginalStorageAuditJsonRet = | ||
| 1340 | RunBitflashChild(strExe, vOriginalStorageAuditJsonArgs); | ||
| 1341 | nFail += Check(nOriginalStorageAuditJsonRet == 0 && | ||
| 1342 | FileContainsText(strOriginalStorageAuditJson, | ||
| 1343 | "\"format\": \"bitflash-wallet-storage-audit-v1\"") && | ||
| 1344 | FileContainsText(strOriginalStorageAuditJson, | ||
| 1345 | "\"plain_hd_seed\": \"complete\"") && | ||
| 1346 | FileContainsText(strOriginalStorageAuditJson, | ||
| 1347 | "\"encrypted_hd_seed\": \"none\""), | ||
| 1348 | "the storage audit can write plaintext-wallet JSON") ? 0 : 1; | ||
| 1349 | |||
| 1350 | string strPortableWallet = tmp + "/portable-wallet.dat"; | ||
| 1351 | string strBackupOut = tmp + "/backupwallet.txt"; | ||
| 1352 | vector<string> vBackupArgs; | ||
| 1353 | vBackupArgs.push_back("-datadir=" + strOriginal); | ||
| 1354 | vBackupArgs.push_back("-nomanagedtor"); | ||
| 1355 | vBackupArgs.push_back("-nogui"); | ||
| 1356 | vBackupArgs.push_back("-backupwallet=" + strPortableWallet); | ||
| 1357 | int nBackupRet = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut); | ||
| 1358 | nFail += Check(nBackupRet == 0 && FileExists(strPortableWallet.c_str()), | ||
| 1359 | "/backupwallet writes a portable wallet.dat") ? 0 : 1; | ||
| 1360 | |||
| 1361 | nFail += Check(CopyFileLocal(strOriginal + "/wallet.dat", | ||
| 1362 | strRawCopy + "/wallet.dat"), | ||
| 1363 | "a raw wallet.dat can be copied without database logs") ? 0 : 1; | ||
| 1364 | string strRawAudit = tmp + "/raw-copy-audit.txt"; | ||
| 1365 | vector<string> vRawAuditArgs; | ||
| 1366 | vRawAuditArgs.push_back("-datadir=" + strRawCopy); | ||
| 1367 | vRawAuditArgs.push_back("-nomanagedtor"); | ||
| 1368 | vRawAuditArgs.push_back("-nogui"); | ||
| 1369 | vRawAuditArgs.push_back("-recoveryaudit"); | ||
| 1370 | int nRawAuditRet = RunBitflashChild(strExe, vRawAuditArgs, NULL, &strRawAudit); | ||
| 1371 | nFail += Check(nRawAuditRet == 0 && | ||
| 1372 | FileContainsText(strRawAudit, "recovery phrase: present") && | ||
| 1373 | !FileContainsText(strRawAudit, "Cannot open wallet.dat"), | ||
| 1374 | "a raw wallet.dat copy opens in a new datadir") ? 0 : 1; | ||
| 1375 | |||
| 1376 | nFail += Check(CopyFileLocal(strPortableWallet, | ||
| 1377 | strBackupCopy + "/wallet.dat"), | ||
| 1378 | "the portable backup can be installed as wallet.dat") ? 0 : 1; | ||
| 1379 | string strBackupAudit = tmp + "/backup-copy-audit.txt"; | ||
| 1380 | vector<string> vBackupAuditArgs; | ||
| 1381 | vBackupAuditArgs.push_back("-datadir=" + strBackupCopy); | ||
| 1382 | vBackupAuditArgs.push_back("-nomanagedtor"); | ||
| 1383 | vBackupAuditArgs.push_back("-nogui"); | ||
| 1384 | vBackupAuditArgs.push_back("-recoveryaudit"); | ||
| 1385 | int nBackupAuditRet = RunBitflashChild(strExe, vBackupAuditArgs, NULL, &strBackupAudit); | ||
| 1386 | nFail += Check(nBackupAuditRet == 0 && | ||
| 1387 | FileContainsText(strBackupAudit, "recovery phrase: present") && | ||
| 1388 | FileContainsText(strBackupAudit, "SLIP-0044 BITFLASH"), | ||
| 1389 | "a /backupwallet copy opens in a new datadir") ? 0 : 1; | ||
| 1390 | |||
| 1391 | string strPassFile = tmp + "/encrypt-pass.txt"; | ||
| 1392 | string strEncryptOut = tmp + "/encryptwallet.txt"; | ||
| 1393 | nFail += Check(WriteTextFile(strPassFile, "portable-test-passphrase\n"), | ||
| 1394 | "an encryption passphrase file can be written") ? 0 : 1; | ||
| 1395 | vector<string> vEncryptArgs; | ||
| 1396 | vEncryptArgs.push_back("-datadir=" + strOriginal); | ||
| 1397 | vEncryptArgs.push_back("-nomanagedtor"); | ||
| 1398 | vEncryptArgs.push_back("-nogui"); | ||
| 1399 | vEncryptArgs.push_back("-encryptwallet=@" + strPassFile); | ||
| 1400 | int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut); | ||
| 1401 | nFail += Check(nEncryptRet == 0 && | ||
| 1402 | !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."), | ||
| 1403 | "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1; | ||
| 1404 | |||
| 1405 | string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt"; | ||
| 1406 | vector<string> vEncryptedStorageAuditArgs; | ||
| 1407 | vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal); | ||
| 1408 | vEncryptedStorageAuditArgs.push_back("-nomanagedtor"); | ||
| 1409 | vEncryptedStorageAuditArgs.push_back("-nogui"); | ||
| 1410 | vEncryptedStorageAuditArgs.push_back("-walletstorageaudit"); | ||
| 1411 | int nEncryptedStorageAuditRet = | ||
| 1412 | RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit); | ||
| 1413 | nFail += Check(nEncryptedStorageAuditRet == 0 && | ||
| 1414 | FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") && | ||
| 1415 | FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") && | ||
| 1416 | FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") && | ||
| 1417 | FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") && | ||
| 1418 | FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") && | ||
| 1419 | FileContainsText(strEncryptedStorageAudit, "malformed records: 0"), | ||
| 1420 | "the storage audit recognizes an encrypted wallet") ? 0 : 1; | ||
| 1421 | |||
| 1422 | string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json"; | ||
| 1423 | vector<string> vEncryptedStorageAuditJsonArgs; | ||
| 1424 | vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal); | ||
| 1425 | vEncryptedStorageAuditJsonArgs.push_back("-nomanagedtor"); | ||
| 1426 | vEncryptedStorageAuditJsonArgs.push_back("-nogui"); | ||
| 1427 | vEncryptedStorageAuditJsonArgs.push_back("-walletstorageauditjson=" + | ||
| 1428 | strEncryptedStorageAuditJson); | ||
| 1429 | int nEncryptedStorageAuditJsonRet = | ||
| 1430 | RunBitflashChild(strExe, vEncryptedStorageAuditJsonArgs); | ||
| 1431 | nFail += Check(nEncryptedStorageAuditJsonRet == 0 && | ||
| 1432 | FileContainsText(strEncryptedStorageAuditJson, | ||
| 1433 | "\"plain_private_keys\": 0") && | ||
| 1434 | FileContainsText(strEncryptedStorageAuditJson, | ||
| 1435 | "\"plain_hd_seed\": \"none\"") && | ||
| 1436 | FileContainsText(strEncryptedStorageAuditJson, | ||
| 1437 | "\"encrypted_hd_seed\": \"complete\"") && | ||
| 1438 | FileContainsText(strEncryptedStorageAuditJson, | ||
| 1439 | "\"wallet_minimum_version\": \"present\""), | ||
| 1440 | "the storage audit can write encrypted-wallet JSON") ? 0 : 1; | ||
| 1441 | } | ||
| 1442 | catch (const std::exception& e) | ||
| 1443 | { | ||
| 1444 | printf(" FAIL exception: %s\n", e.what()); | ||
| 1445 | nFail++; | ||
| 1446 | } | ||
| 1447 | catch (...) | ||
| 1448 | { | ||
| 1449 | printf(" FAIL unknown exception\n"); | ||
| 1450 | nFail++; | ||
| 1451 | } | ||
| 1452 | |||
| 1453 | RemoveTree(tmp); | ||
| 1454 | printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", | ||
| 1455 | nFail, nFail == 1 ? "" : "s"); | ||
| 1456 | fflush(stdout); | ||
| 1457 | return nFail == 0 ? 0 : 1; | ||
| 1458 | } | ||
| 1459 | |||
| 1134 | static int RunNetMessageSelfTest() | 1460 | static int RunNetMessageSelfTest() |
| 1135 | { | 1461 | { |
| 1136 | fflush(stdout); | 1462 | fflush(stdout); |
| @@ -1615,6 +1941,8 @@ int RunSelfTest(const std::string& name) | |||
| 1615 | return RunWalletCryptoSelfTest(); | 1941 | return RunWalletCryptoSelfTest(); |
| 1616 | if (name == "wallet-encrypt") | 1942 | if (name == "wallet-encrypt") |
| 1617 | return RunWalletEncryptSelfTest(); | 1943 | return RunWalletEncryptSelfTest(); |
| 1944 | if (name == "wallet-portability") | ||
| 1945 | return RunWalletPortabilitySelfTest(); | ||
| 1618 | if (name == "net-message") | 1946 | if (name == "net-message") |
| 1619 | return RunNetMessageSelfTest(); | 1947 | return RunNetMessageSelfTest(); |
| 1620 | if (name == "consensus-limits") | 1948 | if (name == "consensus-limits") |
| @@ -1629,6 +1957,6 @@ int RunSelfTest(const std::string& name) | |||
| 1629 | return RunManagedTorSelfTest(); | 1957 | return RunManagedTorSelfTest(); |
| 1630 | 1958 | ||
| 1631 | printf("Unknown self-test '%s'\n", name.c_str()); | 1959 | printf("Unknown self-test '%s'\n", name.c_str()); |
| 1632 | printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); | 1960 | printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); |
| 1633 | return 1; | 1961 | return 1; |
| 1634 | } | 1962 | } |
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp index e8ac4bf..6f8258d 100644 --- a/src/walletcmd.cpp +++ b/src/walletcmd.cpp | |||
| @@ -400,7 +400,12 @@ int CmdRecoveryAudit() | |||
| 400 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; | 400 | int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; |
| 401 | 401 | ||
| 402 | printf("Wallet recovery audit\n"); | 402 | printf("Wallet recovery audit\n"); |
| 403 | printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); | 403 | if (audit.fHaveSeed) |
| 404 | printf(" recovery phrase: present\n"); | ||
| 405 | else if (audit.fSeedEncryptedLocked) | ||
| 406 | printf(" recovery phrase: encrypted, unlock wallet to audit\n"); | ||
| 407 | else | ||
| 408 | printf(" recovery phrase: not installed\n"); | ||
| 404 | if (audit.fHaveSeed) | 409 | if (audit.fHaveSeed) |
| 405 | { | 410 | { |
| 406 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); | 411 | printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); |
| @@ -412,6 +417,15 @@ int CmdRecoveryAudit() | |||
| 412 | if (!audit.fDeriveComplete) | 417 | if (!audit.fDeriveComplete) |
| 413 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); | 418 | printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); |
| 414 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); | 419 | printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); |
| 420 | if (audit.fSeedEncryptedLocked) | ||
| 421 | { | ||
| 422 | printf(" recovery coverage: unavailable while wallet is locked\n"); | ||
| 423 | printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str()); | ||
| 424 | printf("\n"); | ||
| 425 | printf("Unlock the wallet with /walletpassphrase or /walletpassphrase=@FILE to audit phrase coverage.\n"); | ||
| 426 | fflush(stdout); | ||
| 427 | return 2; | ||
| 428 | } | ||
| 415 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", | 429 | printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", |
| 416 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); | 430 | FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); |
| 417 | printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", | 431 | printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", |
| @@ -456,6 +470,253 @@ int CmdRecoveryAudit() | |||
| 456 | return 0; | 470 | return 0; |
| 457 | } | 471 | } |
| 458 | 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 | static std::string WalletStorageAuditJson(const WalletStorageAuditCounts& counts) | ||
| 609 | { | ||
| 610 | std::string strJson; | ||
| 611 | strJson += "{\n"; | ||
| 612 | strJson += " \"format\": \"bitflash-wallet-storage-audit-v1\",\n"; | ||
| 613 | strJson += strprintf(" \"records_total\": %u,\n", counts.nTotal); | ||
| 614 | strJson += strprintf(" \"malformed_records\": %u,\n", counts.nMalformed); | ||
| 615 | strJson += strprintf(" \"unknown_records\": %u,\n", counts.nUnknown); | ||
| 616 | strJson += strprintf(" \"plain_private_keys\": %u,\n", counts.nPlainKeys); | ||
| 617 | strJson += strprintf(" \"encrypted_private_keys\": %u,\n", counts.nEncryptedKeys); | ||
| 618 | strJson += strprintf(" \"encryption_master_keys\": %u,\n", counts.nMasterKeys); | ||
| 619 | strJson += strprintf(" \"default_public_key\": \"%s\",\n", | ||
| 620 | counts.nDefaultKey ? "present" : "none"); | ||
| 621 | strJson += strprintf(" \"plain_hd_seed\": \"%s\",\n", | ||
| 622 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 623 | counts.nPlainHDChainCode).c_str()); | ||
| 624 | strJson += strprintf(" \"encrypted_hd_seed\": \"%s\",\n", | ||
| 625 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 626 | counts.nCryptedHDChainCode).c_str()); | ||
| 627 | strJson += strprintf(" \"wallet_minimum_version\": \"%s\",\n", | ||
| 628 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 629 | strJson += strprintf(" \"keypool_entries\": %u,\n", counts.nPool); | ||
| 630 | strJson += strprintf(" \"wallet_transactions\": %u,\n", counts.nTransactions); | ||
| 631 | strJson += strprintf(" \"address_book_labels\": %u,\n", counts.nNames); | ||
| 632 | strJson += strprintf(" \"settings\": %u,\n", counts.nSettings); | ||
| 633 | strJson += strprintf(" \"hd_metadata_records\": %u,\n", | ||
| 634 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 635 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 636 | strJson += strprintf(" \"database_version_records\": %u\n", counts.nVersion); | ||
| 637 | strJson += "}\n"; | ||
| 638 | return strJson; | ||
| 639 | } | ||
| 640 | |||
| 641 | static bool WriteAuditTextFile(const std::string& strPath, | ||
| 642 | const std::string& strText, | ||
| 643 | std::string& strError) | ||
| 644 | { | ||
| 645 | FILE* pf = fopen(strPath.c_str(), "wb"); | ||
| 646 | if (!pf) | ||
| 647 | { | ||
| 648 | strError = "cannot open output file"; | ||
| 649 | return false; | ||
| 650 | } | ||
| 651 | size_t nWritten = fwrite(strText.data(), 1, strText.size(), pf); | ||
| 652 | bool fCloseOk = fclose(pf) == 0; | ||
| 653 | if (nWritten != strText.size() || !fCloseOk) | ||
| 654 | { | ||
| 655 | strError = "could not write the complete output file"; | ||
| 656 | return false; | ||
| 657 | } | ||
| 658 | return true; | ||
| 659 | } | ||
| 660 | |||
| 661 | int CmdWalletStorageAudit(const std::string& strJsonOut) | ||
| 662 | { | ||
| 663 | AttachTerminal(); | ||
| 664 | |||
| 665 | WalletStorageAuditCounts counts; | ||
| 666 | std::string strError; | ||
| 667 | if (!ReadWalletStorageCounts(counts, strError)) | ||
| 668 | { | ||
| 669 | fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str()); | ||
| 670 | return 1; | ||
| 671 | } | ||
| 672 | |||
| 673 | if (!strJsonOut.empty()) | ||
| 674 | { | ||
| 675 | std::string strWriteError; | ||
| 676 | if (!WriteAuditTextFile(strJsonOut, | ||
| 677 | WalletStorageAuditJson(counts), | ||
| 678 | strWriteError)) | ||
| 679 | { | ||
| 680 | fprintf(stderr, "Cannot write wallet storage audit JSON: %s\n", | ||
| 681 | strWriteError.c_str()); | ||
| 682 | return 1; | ||
| 683 | } | ||
| 684 | printf("Wallet storage audit written to %s\n", strJsonOut.c_str()); | ||
| 685 | fflush(stdout); | ||
| 686 | return counts.nMalformed > 0 ? 2 : 0; | ||
| 687 | } | ||
| 688 | |||
| 689 | printf("Wallet storage audit\n"); | ||
| 690 | printf(" records total: %u\n", counts.nTotal); | ||
| 691 | printf(" malformed records: %u\n", counts.nMalformed); | ||
| 692 | printf(" unknown records: %u\n", counts.nUnknown); | ||
| 693 | printf(" plain private keys: %u\n", counts.nPlainKeys); | ||
| 694 | printf(" encrypted private keys: %u\n", counts.nEncryptedKeys); | ||
| 695 | printf(" encryption master keys: %u\n", counts.nMasterKeys); | ||
| 696 | printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none"); | ||
| 697 | printf(" plain HD seed: %s\n", | ||
| 698 | HDSeedStorageState(counts.nPlainHDMaster, | ||
| 699 | counts.nPlainHDChainCode).c_str()); | ||
| 700 | printf(" encrypted HD seed: %s\n", | ||
| 701 | HDSeedStorageState(counts.nCryptedHDMaster, | ||
| 702 | counts.nCryptedHDChainCode).c_str()); | ||
| 703 | printf(" wallet minimum version: %s\n", | ||
| 704 | counts.nWalletMinVersion ? "present" : "none"); | ||
| 705 | printf(" keypool entries: %u\n", counts.nPool); | ||
| 706 | printf(" wallet transactions: %u\n", counts.nTransactions); | ||
| 707 | printf(" address book labels: %u\n", counts.nNames); | ||
| 708 | printf(" settings: %u\n", counts.nSettings); | ||
| 709 | printf(" HD metadata records: %u\n", | ||
| 710 | counts.nHDNext + counts.nHDSchema + counts.nHDCoinType + | ||
| 711 | counts.nHDReceiveNext + counts.nHDChangeNext); | ||
| 712 | printf(" database version records: %u\n", counts.nVersion); | ||
| 713 | fflush(stdout); | ||
| 714 | |||
| 715 | if (counts.nMalformed > 0) | ||
| 716 | return 2; | ||
| 717 | return 0; | ||
| 718 | } | ||
| 719 | |||
| 459 | int CmdEncryptWallet(const std::string& strPassphrase) | 720 | int CmdEncryptWallet(const std::string& strPassphrase) |
| 460 | { | 721 | { |
| 461 | AttachTerminal(); | 722 | AttachTerminal(); |
diff --git a/src/walletcmd.h b/src/walletcmd.h index 007b8b5..c6342a6 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(const std::string& strJsonOut = ""); | ||
| 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); |