upleb.uk

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

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gui.cpp54
-rw-r--r--src/main.cpp87
-rw-r--r--src/main.h31
-rw-r--r--src/main_gui.cpp12
-rw-r--r--src/selftest.cpp36
-rw-r--r--src/walletcmd.cpp59
-rw-r--r--src/walletcmd.h4
7 files changed, 280 insertions, 3 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index e3a8499..2335bf9 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -65,6 +65,8 @@ static std::string g_sendStatus;
65static char g_backupPath[512] = {}; 65static char g_backupPath[512] = {};
66static std::string g_backupStatus; 66static std::string g_backupStatus;
67static int64 g_lastWalletBackup = 0; 67static int64 g_lastWalletBackup = 0;
68static WalletRecoveryAudit g_recoveryAudit;
69static int64 g_recoveryAuditTime = 0;
68static bool g_walletSafetyLoaded = false; 70static bool g_walletSafetyLoaded = false;
69static char g_participantPool[256] = {}; 71static char g_participantPool[256] = {};
70static char g_poolName[128] = {}; 72static char g_poolName[128] = {};
@@ -184,6 +186,15 @@ static void LoadWalletSafetyState()
184 g_walletSafetyLoaded = true; 186 g_walletSafetyLoaded = true;
185} 187}
186 188
189static void RefreshRecoveryAudit(bool fForce=false)
190{
191 int64 nNow = GetTime();
192 if (!fForce && g_recoveryAuditTime != 0 && nNow - g_recoveryAuditTime < 5)
193 return;
194 g_recoveryAudit = GetWalletRecoveryAudit();
195 g_recoveryAuditTime = nNow;
196}
197
187static int KeyPoolCount() 198static int KeyPoolCount()
188{ 199{
189 int n = 0; 200 int n = 0;
@@ -267,7 +278,11 @@ static void RefreshWallet()
267 g_lastWalletRefresh = GetTime(); 278 g_lastWalletRefresh = GetTime();
268} 279}
269 280
270void MainFrameRepaint() { g_needRefresh = true; } 281void MainFrameRepaint()
282{
283 g_needRefresh = true;
284 g_recoveryAuditTime = 0;
285}
271 286
272// DateTimeStr moved to util.cpp: main.cpp logs block times with it, so it is 287// DateTimeStr moved to util.cpp: main.cpp logs block times with it, so it is
273// needed by builds that have no GUI at all. 288// needed by builds that have no GUI at all.
@@ -967,6 +982,7 @@ static void RestoreThread(std::string strPhrase)
967 "Check the words and their order.", nDerived); 982 "Check the words and their order.", nDerived);
968 } 983 }
969 g_restoreRunning.store(false); 984 g_restoreRunning.store(false);
985 g_recoveryAuditTime = 0;
970 g_needRefresh = true; 986 g_needRefresh = true;
971} 987}
972 988
@@ -1021,6 +1037,7 @@ static void DrawCreatePhraseDialog()
1021 if (SetHDSeedFromMnemonic(g_pendingMnemonic, strError)) 1037 if (SetHDSeedFromMnemonic(g_pendingMnemonic, strError))
1022 { 1038 {
1023 TopUpKeyPool(); 1039 TopUpKeyPool();
1040 g_recoveryAuditTime = 0;
1024 g_phraseStatus = "Recovery phrase created. New addresses come from it."; 1041 g_phraseStatus = "Recovery phrase created. New addresses come from it.";
1025 } 1042 }
1026 else 1043 else
@@ -1141,8 +1158,9 @@ static void DrawWalletSafetyDialog()
1141{ 1158{
1142 if (!g_showWalletSafety) return; 1159 if (!g_showWalletSafety) return;
1143 LoadWalletSafetyState(); 1160 LoadWalletSafetyState();
1161 RefreshRecoveryAudit();
1144 1162
1145 ImGui::SetNextWindowSize(ImVec2(650.0f, 390.0f), ImGuiCond_Always); 1163 ImGui::SetNextWindowSize(ImVec2(650.0f, 450.0f), ImGuiCond_Always);
1146 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), 1164 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1147 ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 1165 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
1148 if (ImGui::Begin("Wallet Safety", &g_showWalletSafety, 1166 if (ImGui::Begin("Wallet Safety", &g_showWalletSafety,
@@ -1229,6 +1247,38 @@ static void DrawWalletSafetyDialog()
1229 ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.25f, 1.0f), 1247 ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.25f, 1.0f),
1230 "No recovery phrase. Only a file backup can rebuild this wallet."); 1248 "No recovery phrase. Only a file backup can rebuild this wallet.");
1231 1249
1250 ImGui::Text("Phrase-backed spendable balance: %s BTF",
1251 FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str());
1252 ImGui::Text("Wallet.dat-only spendable balance: %s BTF",
1253 FmtMoney(g_recoveryAudit.nLegacyCredit).c_str());
1254 ImGui::Text("Phrase-backed immature mining rewards: %s BTF",
1255 FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str());
1256 ImGui::Text("Wallet.dat-only immature mining rewards: %s BTF",
1257 FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str());
1258 if (!g_recoveryAudit.fDeriveComplete)
1259 {
1260 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f));
1261 ImGui::TextWrapped("%s", g_recoveryAudit.strDeriveError.c_str());
1262 ImGui::PopStyleColor();
1263 }
1264 if (g_recoveryAudit.nLegacyCredit > 0 ||
1265 g_recoveryAudit.nLegacyImmatureCredit > 0)
1266 {
1267 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f));
1268 ImGui::TextWrapped(
1269 "Some coins are on keys the phrase does not reproduce. "
1270 "Keep wallet.dat backups until that balance has been moved to a "
1271 "phrase-backed address.");
1272 ImGui::PopStyleColor();
1273 }
1274 else if (HaveHDSeed() &&
1275 g_recoveryAudit.nRecoverableCredit +
1276 g_recoveryAudit.nRecoverableImmatureCredit > 0)
1277 {
1278 ImGui::TextColored(ImVec4(0.55f, 1.0f, 0.6f, 1.0f),
1279 "All known wallet balance is covered by the phrase.");
1280 }
1281
1232 if (!HaveHDSeed()) 1282 if (!HaveHDSeed())
1233 { 1283 {
1234 if (ImGui::Button("Create recovery phrase", ImVec2(200.0f, 0.0f))) 1284 if (ImGui::Button("Create recovery phrase", ImVec2(200.0f, 0.0f)))
diff --git a/src/main.cpp b/src/main.cpp
index 1b09a1a..75647ba 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3660,6 +3660,93 @@ bool BitcoinMiner(int nThreadId)
3660// 3660//
3661 3661
3662 3662
3663WalletRecoveryAudit GetWalletRecoveryAudit()
3664{
3665 WalletRecoveryAudit audit;
3666 set<vector<unsigned char> > setDerivedPubKeys;
3667
3668 CRITICAL_BLOCK(cs_keyPool)
3669 {
3670 audit.fHaveSeed = HaveHDSeed();
3671 audit.nDerivedKnown = nHDNext;
3672 if (audit.fHaveSeed)
3673 {
3674 string strError;
3675 for (unsigned int i = 0; i < nHDNext; i++)
3676 {
3677 CKey key;
3678 if (!DeriveHDKey(i, key, strError))
3679 {
3680 audit.fDeriveComplete = false;
3681 audit.strDeriveError = strprintf("derivation failed at index %u: %s",
3682 i, strError.c_str());
3683 break;
3684 }
3685 setDerivedPubKeys.insert(key.GetPubKey());
3686 }
3687 }
3688 }
3689
3690 CRITICAL_BLOCK(cs_mapWallet)
3691 {
3692 for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
3693 {
3694 CWalletTx& wtx = (*it).second;
3695 if (!wtx.IsFinal() || wtx.fSpent)
3696 continue;
3697 bool fImmature = wtx.IsCoinBase() && wtx.GetBlocksToMaturity() > 0;
3698
3699 bool fTxRecoverable = false;
3700 bool fTxLegacy = false;
3701 for (int i = 0; i < (int)wtx.vout.size(); i++)
3702 {
3703 const CTxOut& txout = wtx.vout[i];
3704 if (!txout.IsMine())
3705 continue;
3706
3707 vector<unsigned char> vchPubKey;
3708 bool fRecoverable = false;
3709 if (audit.fHaveSeed && ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
3710 fRecoverable = setDerivedPubKeys.count(vchPubKey) > 0;
3711
3712 if (fRecoverable)
3713 {
3714 if (fImmature)
3715 audit.nRecoverableImmatureCredit += txout.nValue;
3716 else
3717 audit.nRecoverableCredit += txout.nValue;
3718 fTxRecoverable = true;
3719 }
3720 else
3721 {
3722 if (fImmature)
3723 audit.nLegacyImmatureCredit += txout.nValue;
3724 else
3725 audit.nLegacyCredit += txout.nValue;
3726 fTxLegacy = true;
3727 }
3728 }
3729
3730 if (fTxRecoverable)
3731 {
3732 if (fImmature)
3733 audit.nRecoverableImmatureTx++;
3734 else
3735 audit.nRecoverableTx++;
3736 }
3737 if (fTxLegacy)
3738 {
3739 if (fImmature)
3740 audit.nLegacyImmatureTx++;
3741 else
3742 audit.nLegacyTx++;
3743 }
3744 }
3745 }
3746
3747 return audit;
3748}
3749
3663int64 GetBalance() 3750int64 GetBalance()
3664{ 3751{
3665 int64 nStart, nEnd; 3752 int64 nStart, nEnd;
diff --git a/src/main.h b/src/main.h
index 768f1c7..4886f55 100644
--- a/src/main.h
+++ b/src/main.h
@@ -217,6 +217,37 @@ extern CCriticalSection cs_mapTransactions;
217bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv); 217bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv);
218bool SendMessages(CNode* pto); 218bool SendMessages(CNode* pto);
219int64 GetBalance(); 219int64 GetBalance();
220struct WalletRecoveryAudit
221{
222 bool fHaveSeed;
223 bool fDeriveComplete;
224 unsigned int nDerivedKnown;
225 int nRecoverableTx;
226 int nLegacyTx;
227 int nRecoverableImmatureTx;
228 int nLegacyImmatureTx;
229 int64 nRecoverableCredit;
230 int64 nLegacyCredit;
231 int64 nRecoverableImmatureCredit;
232 int64 nLegacyImmatureCredit;
233 string strDeriveError;
234
235 WalletRecoveryAudit()
236 {
237 fHaveSeed = false;
238 fDeriveComplete = true;
239 nDerivedKnown = 0;
240 nRecoverableTx = 0;
241 nLegacyTx = 0;
242 nRecoverableImmatureTx = 0;
243 nLegacyImmatureTx = 0;
244 nRecoverableCredit = 0;
245 nLegacyCredit = 0;
246 nRecoverableImmatureCredit = 0;
247 nLegacyImmatureCredit = 0;
248 }
249};
250WalletRecoveryAudit GetWalletRecoveryAudit();
220bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& txNew, int64& nFeeRequiredRet); 251bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& txNew, int64& nFeeRequiredRet);
221bool CommitTransactionSpent(const CWalletTx& wtxNew); 252bool CommitTransactionSpent(const CWalletTx& wtxNew);
222bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew); 253bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew);
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 32fcdef..1b72813 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -93,7 +93,10 @@ static void PrintUsage()
93 printf(" /restoredepth=N (with /restorephrase: derive at least N\n"); 93 printf(" /restoredepth=N (with /restorephrase: derive at least N\n");
94 printf(" addresses before giving up)\n"); 94 printf(" addresses before giving up)\n");
95 printf(" /showderived=N (list the first N addresses a phrase\n"); 95 printf(" /showderived=N (list the first N addresses a phrase\n");
96 printf(" produces, without installing it)\n"); 96 printf(" installed in this wallet derives)\n");
97 printf(" /recoveryaudit (show how much spendable balance is\n");
98 printf(" covered by the recovery phrase;\n");
99 printf(" exits 2 when wallet.dat is still needed)\n");
97 printf(" /rescan (walk the chain for coins this wallet owns\n"); 100 printf(" /rescan (walk the chain for coins this wallet owns\n");
98 printf(" but never recorded, then exit)\n"); 101 printf(" but never recorded, then exit)\n");
99 printf("\n"); 102 printf("\n");
@@ -385,6 +388,13 @@ int main(int argc, char* argv[])
385 return nRet; 388 return nRet;
386 } 389 }
387 390
391 if (arg(argc,argv,"/recoveryaudit") || arg(argc,argv,"-recoveryaudit"))
392 {
393 int nRet = CmdRecoveryAudit();
394 DBFlush(true);
395 return nRet;
396 }
397
388 if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) 398 if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase"))
389 { 399 {
390 int nRet = CmdNewPhrase(); 400 int nRet = CmdNewPhrase();
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 67f96f8..df55bcd 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -336,6 +336,7 @@ static int RunWalletHDSelfTest()
336 336
337 // The address the wallet was showing before any of this. It has to 337 // The address the wallet was showing before any of this. It has to
338 // survive under a name that says it is not covered by the phrase. 338 // survive under a name that says it is not covered by the phrase.
339 std::vector<unsigned char> vchPreSeedKey = keyUser.GetPubKey();
339 std::string strPreSeedAddr = PubKeyToAddress(keyUser.GetPubKey()); 340 std::string strPreSeedAddr = PubKeyToAddress(keyUser.GetPubKey());
340 341
341 nFail += Check(SetHDSeedFromMnemonic(strPhraseA, strError), 342 nFail += Check(SetHDSeedFromMnemonic(strPhraseA, strError),
@@ -449,6 +450,41 @@ static int RunWalletHDSelfTest()
449 } 450 }
450 nFail += Check(fStored, "every derived pooled key is stored in wallet.dat") ? 0 : 1; 451 nFail += Check(fStored, "every derived pooled key is stored in wallet.dat") ? 0 : 1;
451 nFail += Check(fFromSeed, "every pooled key came from the seed, not from chance") ? 0 : 1; 452 nFail += Check(fFromSeed, "every pooled key came from the seed, not from chance") ? 0 : 1;
453
454 // A recovery phrase is only useful if the wallet can tell the user
455 // what today's spendable balance would actually come back from it.
456 CKey keyAuditDerived;
457 if (!DeriveHDKey(0, keyAuditDerived, strError))
458 throw std::runtime_error("audit derivation failed: " + strError);
459
460 CWalletTx wtxLegacy;
461 wtxLegacy.vout.push_back(CTxOut(5 * COIN, CScript() << vchPreSeedKey << OP_CHECKSIG));
462 CWalletTx wtxDerived;
463 wtxDerived.vout.push_back(CTxOut(7 * COIN, CScript() << keyAuditDerived.GetPubKey() << OP_CHECKSIG));
464 CWalletTx wtxImmatureLegacy;
465 wtxImmatureLegacy.vin.push_back(CTxIn());
466 wtxImmatureLegacy.vout.push_back(CTxOut(11 * COIN, CScript() << vchPreSeedKey << OP_CHECKSIG));
467
468 CRITICAL_BLOCK(cs_mapWallet)
469 {
470 mapWallet.clear();
471 mapWallet[wtxLegacy.GetHash()] = wtxLegacy;
472 mapWallet[wtxDerived.GetHash()] = wtxDerived;
473 mapWallet[wtxImmatureLegacy.GetHash()] = wtxImmatureLegacy;
474 }
475
476 WalletRecoveryAudit audit = GetWalletRecoveryAudit();
477 nFail += Check(audit.fHaveSeed, "the recovery audit reports the phrase") ? 0 : 1;
478 nFail += Check(audit.nLegacyCredit == 5 * COIN,
479 "the recovery audit finds wallet.dat-only balance") ? 0 : 1;
480 nFail += Check(audit.nRecoverableCredit == 7 * COIN,
481 "the recovery audit finds phrase-backed balance") ? 0 : 1;
482 nFail += Check(audit.nLegacyTx == 1 && audit.nRecoverableTx == 1,
483 "the recovery audit counts legacy and phrase-backed transactions") ? 0 : 1;
484 nFail += Check(audit.nLegacyImmatureCredit == 11 * COIN,
485 "the recovery audit finds wallet.dat-only immature mining rewards") ? 0 : 1;
486 nFail += Check(audit.nLegacyImmatureTx == 1,
487 "the recovery audit counts wallet.dat-only immature mining rewards") ? 0 : 1;
452 } 488 }
453 catch (const std::exception& e) 489 catch (const std::exception& e)
454 { 490 {
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 6d5f32b..41b4ed2 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -272,6 +272,65 @@ int CmdShowDerived(int nCount)
272 return 0; 272 return 0;
273} 273}
274 274
275int CmdRecoveryAudit()
276{
277 AttachTerminal();
278
279 WalletRecoveryAudit audit = GetWalletRecoveryAudit();
280 int64 nTotal = audit.nRecoverableCredit + audit.nLegacyCredit;
281 int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit;
282
283 printf("Wallet recovery audit\n");
284 printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed");
285 if (audit.fHaveSeed)
286 printf(" derived keys known to this wallet: %u\n", audit.nDerivedKnown);
287 if (!audit.fDeriveComplete)
288 printf(" derivation warning: %s\n", audit.strDeriveError.c_str());
289 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str());
290 printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n",
291 FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx);
292 printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n",
293 FormatMoney(audit.nLegacyCredit).c_str(), audit.nLegacyTx);
294 printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str());
295 printf(" phrase-backed immature: %s BTF (%d transaction(s))\n",
296 FormatMoney(audit.nRecoverableImmatureCredit).c_str(),
297 audit.nRecoverableImmatureTx);
298 printf(" wallet.dat-only immature: %s BTF (%d transaction(s))\n",
299 FormatMoney(audit.nLegacyImmatureCredit).c_str(),
300 audit.nLegacyImmatureTx);
301
302 if (!audit.fHaveSeed)
303 {
304 printf("\n");
305 printf("This wallet has no recovery phrase. A file backup is the only backup.\n");
306 fflush(stdout);
307 return nTotal + nImmatureTotal > 0 ? 2 : 0;
308 }
309 if (!audit.fDeriveComplete)
310 {
311 printf("\n");
312 printf("Warning: the audit could not derive every known phrase key, so coverage is incomplete.\n");
313 fflush(stdout);
314 return 2;
315 }
316 if (audit.nLegacyCredit > 0 || audit.nLegacyImmatureCredit > 0)
317 {
318 printf("\n");
319 printf("Warning: some coins are on keys the phrase does not reproduce.\n");
320 printf("Keep wallet.dat backups until that balance has been moved to a phrase-backed address.\n");
321 fflush(stdout);
322 return 2;
323 }
324
325 printf("\n");
326 if (nTotal + nImmatureTotal > 0)
327 printf("All known wallet balance is covered by the recovery phrase.\n");
328 else
329 printf("No wallet balance found yet.\n");
330 fflush(stdout);
331 return 0;
332}
333
275// Spend, from the command line. 334// Spend, from the command line.
276// 335//
277// SendMoney() has been in this tree since 0.1.0 and only the window ever called 336// SendMoney() has been in this tree since 0.1.0 and only the window ever called
diff --git a/src/walletcmd.h b/src/walletcmd.h
index 89834d8..796356b 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -40,4 +40,8 @@ int CmdNewAddress();
40// Diagnostic: print the first nCount addresses this wallet's phrase derives. 40// Diagnostic: print the first nCount addresses this wallet's phrase derives.
41int CmdShowDerived(int nCount); 41int CmdShowDerived(int nCount);
42 42
43// Diagnostic: print whether the currently spendable wallet balance is covered
44// by the installed recovery phrase or still depends on wallet.dat-only keys.
45int CmdRecoveryAudit();
46
43#endif 47#endif