upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/gui.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui.cpp')
-rw-r--r--src/gui.cpp275
1 files changed, 263 insertions, 12 deletions
diff --git a/src/gui.cpp b/src/gui.cpp
index 8f1d204..ff6059a 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -11,10 +11,16 @@
11#include <ctime> 11#include <ctime>
12#include <cstdio> 12#include <cstdio>
13#include <sstream> 13#include <sstream>
14#ifdef _WIN32
15#include <shellapi.h> // ShellExecuteA, to open the website in the browser
16#else
17#include <unistd.h>
18#endif
14#include <iomanip> 19#include <iomanip>
15#include <atomic> 20#include <atomic>
16#include <mutex> 21#include <mutex>
17#include <thread> 22#include <thread>
23#include <system_error>
18#include "bip32.h" 24#include "bip32.h"
19#include "walletcmd.h" 25#include "walletcmd.h"
20#include "font_roboto.h" 26#include "font_roboto.h"
@@ -172,7 +178,11 @@ static bool PathIsInsideDataDir(const std::string& strPath)
172 178
173static void SetDefaultBackupPath() 179static void SetDefaultBackupPath()
174{ 180{
175 std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + ".dat"; 181 // Match the extension to the live backend: BackupWallet copies wallet.sqlite
182 // under the SQLite backend and wallet.dat under Berkeley DB, so a ".dat"
183 // default name would put SQLite contents in a misleadingly-named file.
184 const char* pszExt = WalletSQLiteRuntimeActive() ? ".sqlite" : ".dat";
185 std::string path = DefaultBackupDir() + "/wallet-backup-" + BackupTimeSuffix() + pszExt;
176 strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1); 186 strncpy(g_backupPath, path.c_str(), sizeof(g_backupPath)-1);
177 g_backupPath[sizeof(g_backupPath)-1] = '\0'; 187 g_backupPath[sizeof(g_backupPath)-1] = '\0';
178} 188}
@@ -910,8 +920,16 @@ static void DrawOptionsDialog()
910 920
911 if (nMineMode == MINE_OPERATOR) { 921 if (nMineMode == MINE_OPERATOR) {
912 if (!gPoolServerRunning) { 922 if (!gPoolServerRunning) {
913 gPoolServerRunning = true; 923 // Claim the server is up only once it is. The flag used to
914 _beginthread(ThreadRPCServer, 0, NULL); 924 // be set first and the result never looked at, so a refused
925 // spawn left an operator with a pool that reports itself
926 // running, no RPC server for miners to reach, and nothing
927 // anywhere saying why. The flag also latches: with it set,
928 // this branch is never retried.
929 if (_beginthread(ThreadRPCServer, 0, NULL) == (uintptr_t)-1)
930 printf("Error: _beginthread(ThreadRPCServer) failed -- pool server not started\n");
931 else
932 gPoolServerRunning = true;
915 } else if (!gPoolRunning) { 933 } else if (!gPoolRunning) {
916 gPoolRunning = true; 934 gPoolRunning = true;
917 } 935 }
@@ -1124,11 +1142,28 @@ static void DrawRestorePhraseDialog()
1124 } 1142 }
1125 g_restoreDerived.store(0); 1143 g_restoreDerived.store(0);
1126 g_restoreRecovered.store(0); 1144 g_restoreRecovered.store(0);
1145 // Set before the spawn, not after: RestoreThread clears this
1146 // flag itself when it finishes, and a short restore can beat
1147 // us back here and have its `false` overwritten, leaving the
1148 // dialog disabled forever.
1127 g_restoreRunning.store(true); 1149 g_restoreRunning.store(true);
1128 // On its own thread: a restore walks the whole chain once per 1150 // On its own thread: a restore walks the whole chain once per
1129 // batch and would otherwise freeze the window for as long as it 1151 // batch and would otherwise freeze the window for as long as it
1130 // takes. 1152 // takes. A refused spawn is one restore that does not happen,
1131 std::thread(RestoreThread, std::string(g_restoreInput)).detach(); 1153 // not a reason to take the window down with an uncaught
1154 // std::system_error -- and the flag has to come back off, or
1155 // the dialog sits greyed out reporting a search nobody is
1156 // running.
1157 try
1158 {
1159 std::thread(RestoreThread, std::string(g_restoreInput)).detach();
1160 }
1161 catch (const std::system_error& e)
1162 {
1163 g_restoreRunning.store(false);
1164 std::lock_guard<std::mutex> lock(g_restoreResultMutex);
1165 g_restoreResult = std::string("Could not start the search: ") + e.what();
1166 }
1132 } 1167 }
1133 } 1168 }
1134 if (fBusy) 1169 if (fBusy)
@@ -1250,15 +1285,15 @@ static void DrawWalletSafetyDialog()
1250 if (g_recoveryAudit.fHaveSeed) 1285 if (g_recoveryAudit.fHaveSeed)
1251 { 1286 {
1252 ImGui::Text("Derivation schema: %s", HDKeySchemaName(g_recoveryAudit.nSchema).c_str()); 1287 ImGui::Text("Derivation schema: %s", HDKeySchemaName(g_recoveryAudit.nSchema).c_str());
1253 ImGui::Text("BIP44 coin type: %u (provisional BITFLASH)", g_recoveryAudit.nCoinType); 1288 ImGui::Text("BIP44 coin type: %u (SLIP-0044 BITFLASH)", g_recoveryAudit.nCoinType);
1254 } 1289 }
1255 ImGui::Text("Phrase-backed spendable balance: %s BTF", 1290 ImGui::Text("Phrase-backed spendable balance: %s BTF",
1256 FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str()); 1291 FmtMoney(g_recoveryAudit.nRecoverableCredit).c_str());
1257 ImGui::Text("Wallet.dat-only spendable balance: %s BTF", 1292 ImGui::Text("Not phrase-backed spendable balance: %s BTF",
1258 FmtMoney(g_recoveryAudit.nLegacyCredit).c_str()); 1293 FmtMoney(g_recoveryAudit.nLegacyCredit).c_str());
1259 ImGui::Text("Phrase-backed immature mining rewards: %s BTF", 1294 ImGui::Text("Phrase-backed immature mining rewards: %s BTF",
1260 FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str()); 1295 FmtMoney(g_recoveryAudit.nRecoverableImmatureCredit).c_str());
1261 ImGui::Text("Wallet.dat-only immature mining rewards: %s BTF", 1296 ImGui::Text("Not phrase-backed immature mining rewards: %s BTF",
1262 FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str()); 1297 FmtMoney(g_recoveryAudit.nLegacyImmatureCredit).c_str());
1263 if (!g_recoveryAudit.fDeriveComplete) 1298 if (!g_recoveryAudit.fDeriveComplete)
1264 { 1299 {
@@ -1272,8 +1307,8 @@ static void DrawWalletSafetyDialog()
1272 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f)); 1307 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.75f, 0.25f, 1.0f));
1273 ImGui::TextWrapped( 1308 ImGui::TextWrapped(
1274 "Some coins are on keys the phrase does not reproduce. " 1309 "Some coins are on keys the phrase does not reproduce. "
1275 "Keep wallet.dat backups until that balance has been moved to a " 1310 "Keep a file backup of this wallet until that balance has been "
1276 "phrase-backed address."); 1311 "moved to a phrase-backed address.");
1277 ImGui::PopStyleColor(); 1312 ImGui::PopStyleColor();
1278 } 1313 }
1279 else if (HaveHDSeed() && 1314 else if (HaveHDSeed() &&
@@ -1369,16 +1404,38 @@ static void DrawDiagnosticsDialog()
1369 ImGui::End(); 1404 ImGui::End();
1370} 1405}
1371 1406
1407static void OpenURL(const char* pszUrl)
1408{
1409#ifdef _WIN32
1410 ShellExecuteA(NULL, "open", pszUrl, NULL, NULL, SW_SHOWNORMAL);
1411#else
1412 std::string cmd = std::string("xdg-open '") + pszUrl + "' >/dev/null 2>&1 &";
1413 int r = system(cmd.c_str());
1414 (void)r;
1415#endif
1416}
1417
1372static void DrawAboutDialog() 1418static void DrawAboutDialog()
1373{ 1419{
1374 if (!g_showAbout) return; 1420 if (!g_showAbout) return;
1375 ImGui::SetNextWindowSize(ImVec2(380.0f, 170.0f), ImGuiCond_Always); 1421 ImGui::SetNextWindowSize(ImVec2(380.0f, 205.0f), ImGuiCond_Always);
1376 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), 1422 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1377 ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 1423 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
1378 if (ImGui::Begin("About Bitflash", &g_showAbout, 1424 if (ImGui::Begin("About Bitflash", &g_showAbout,
1379 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) 1425 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
1380 { 1426 {
1381 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f), "Bitflash BTF v1.1.0"); 1427 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
1428 "Bitflash BTF v" BITFLASH_VERSION_STRING);
1429 ImGui::Spacing();
1430 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.40f, 0.70f, 1.0f, 1.0f));
1431 ImGui::TextUnformatted("https://bitflash.network");
1432 ImGui::PopStyleColor();
1433 if (ImGui::IsItemHovered())
1434 {
1435 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
1436 if (ImGui::IsMouseClicked(0))
1437 OpenURL("https://bitflash.network");
1438 }
1382 ImGui::Spacing(); 1439 ImGui::Spacing();
1383 ImGui::TextWrapped( 1440 ImGui::TextWrapped(
1384 "CPU-only cryptocurrency. RandomX proof of work. " 1441 "CPU-only cryptocurrency. RandomX proof of work. "
@@ -1391,6 +1448,184 @@ static void DrawAboutDialog()
1391 ImGui::End(); 1448 ImGui::End();
1392} 1449}
1393 1450
1451// First-run offer to move a Berkeley DB wallet to the SQLite backend. Only the
1452// button actions touch anything: "Convert" exports wallet.dat to wallet.sqlite
1453// and records the backend marker (wallet.dat is left untouched, so a restart
1454// picks up SQLite while the old file stays as a fallback); "Keep Berkeley DB"
1455// just records that choice so the offer does not return; "Decide later" leaves
1456// no marker and asks again next start.
1457static bool g_showBackendWizard = false;
1458static std::string g_wizardStatus;
1459static bool g_wizardConverted = false;
1460static int g_restartArgc = 0;
1461static char** g_restartArgv = NULL;
1462
1463#ifdef _WIN32
1464// Quote one argument so CommandLineToArgvW parses it back to exactly this
1465// string: only backslashes that immediately precede a double quote (including
1466// the closing one) are doubled, so ordinary Windows paths are not mangled.
1467static std::string QuoteArgWin(const std::string& a)
1468{
1469 if (!a.empty() && a.find_first_of(" \t\"") == std::string::npos)
1470 return a;
1471 std::string r = "\"";
1472 size_t bs = 0;
1473 for (size_t i = 0; i < a.size(); i++)
1474 {
1475 if (a[i] == '\\') { bs++; continue; }
1476 if (a[i] == '"') { r.append(bs * 2 + 1, '\\'); r += '"'; bs = 0; continue; }
1477 r.append(bs, '\\'); bs = 0; r += a[i];
1478 }
1479 r.append(bs * 2, '\\');
1480 r += "\"";
1481 return r;
1482}
1483#endif
1484
1485// Relaunch Bitflash with the same arguments and datadir, then ask this instance
1486// to exit -- but only once the relaunch has actually started, so a failure never
1487// leaves the user with no node running. The new instance waits (-restartwait)
1488// before it opens the network port and the block index, so this one is gone
1489// first and they never fight over the same files.
1490static void RestartApplication()
1491{
1492 bool fLaunched = false;
1493#ifdef _WIN32
1494 char exePath[MAX_PATH];
1495 if (GetModuleFileNameA(NULL, exePath, MAX_PATH) == 0)
1496 return;
1497 std::string strCmd = QuoteArgWin(exePath);
1498 bool fHasDataDir = false;
1499 for (int i = 1; i < g_restartArgc; i++)
1500 {
1501 std::string a = g_restartArgv[i] ? g_restartArgv[i] : "";
1502 if (a.find("-datadir=") == 0 || a.find("/datadir=") == 0)
1503 fHasDataDir = true;
1504 strCmd += " " + QuoteArgWin(a);
1505 }
1506 if (!fHasDataDir)
1507 strCmd += " " + QuoteArgWin("-datadir=" + GetAppDir());
1508 strCmd += " -restartwait";
1509
1510 std::vector<char> cmdBuf(strCmd.begin(), strCmd.end());
1511 cmdBuf.push_back('\0');
1512 STARTUPINFOA si; memset(&si, 0, sizeof(si)); si.cb = sizeof(si);
1513 PROCESS_INFORMATION pi; memset(&pi, 0, sizeof(pi));
1514 if (CreateProcessA(NULL, &cmdBuf[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
1515 {
1516 CloseHandle(pi.hProcess);
1517 CloseHandle(pi.hThread);
1518 fLaunched = true;
1519 }
1520#else
1521 pid_t pid = fork();
1522 if (pid == 0)
1523 {
1524 // Child: wait for the parent to release the port and files, then re-exec.
1525 // /proc/self/exe is the reliable absolute path; argv[0] may be relative
1526 // or a bare name that execv() cannot resolve.
1527 sleep(2);
1528 char exePath[4096];
1529 ssize_t len = readlink("/proc/self/exe", exePath, sizeof(exePath) - 1);
1530 if (len > 0)
1531 {
1532 exePath[len] = '\0';
1533 execv(exePath, g_restartArgv);
1534 }
1535 if (g_restartArgv)
1536 execv(g_restartArgv[0], g_restartArgv);
1537 _exit(127);
1538 }
1539 fLaunched = (pid > 0);
1540#endif
1541
1542 if (fLaunched)
1543 {
1544 fShutdown = true;
1545 if (glfwGetCurrentContext())
1546 glfwSetWindowShouldClose(glfwGetCurrentContext(), true);
1547 }
1548 else
1549 g_wizardStatus = "Could not relaunch automatically -- please restart "
1550 "Bitflash yourself to switch to the SQLite wallet.";
1551}
1552
1553static void DrawBackendWizardDialog()
1554{
1555 if (!g_showBackendWizard) return;
1556 ImGui::SetNextWindowSize(ImVec2(560.0f, 0.0f), ImGuiCond_Always);
1557 ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(),
1558 ImGuiCond_Always, ImVec2(0.5f, 0.5f));
1559 if (ImGui::Begin("Wallet Storage", &g_showBackendWizard,
1560 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse))
1561 {
1562 if (g_wizardConverted)
1563 {
1564 ImGui::TextColored(ImVec4(0.35f, 1.0f, 0.45f, 1.0f), "Converted to SQLite.");
1565 ImGui::TextWrapped("%s", g_wizardStatus.c_str());
1566 ImGui::Spacing();
1567 ImGui::TextWrapped(
1568 "The switch takes effect when Bitflash restarts. Until then it is "
1569 "still running on the old Berkeley DB wallet. Your wallet.dat is "
1570 "kept untouched as a fallback.");
1571 ImGui::Spacing();
1572 if (ImGui::Button("Restart Now", ImVec2(140.0f, 0.0f)))
1573 RestartApplication();
1574 ImGui::SameLine();
1575 if (ImGui::Button("Later", ImVec2(120.0f, 0.0f)))
1576 g_showBackendWizard = false;
1577 ImGui::Spacing();
1578 ImGui::TextDisabled(
1579 "If you keep mining before restarting, new coins go to the old "
1580 "wallet until you switch.");
1581 }
1582 else
1583 {
1584 ImGui::TextWrapped(
1585 "Bitflash can store your wallet in a new SQLite format: a single "
1586 "file that is easier to back up and does not depend on the "
1587 "database/ directory beside it.");
1588 ImGui::Spacing();
1589 ImGui::TextWrapped(
1590 "Your current Berkeley DB wallet (wallet.dat) is kept exactly as it "
1591 "is -- nothing is deleted, and you can go back to it at any time. "
1592 "The change takes effect after you restart.");
1593 ImGui::Spacing();
1594 if (ImGui::Button("Convert to SQLite", ImVec2(180.0f, 0.0f)))
1595 {
1596 int nCopied = 0;
1597 std::string strError;
1598 if (DoConvertWalletToSQLite(nCopied, strError))
1599 {
1600 g_wizardStatus =
1601 strprintf("%d record(s) copied to wallet.sqlite.", nCopied);
1602 g_wizardConverted = true;
1603 }
1604 else
1605 g_wizardStatus = "Conversion failed: " + strError;
1606 }
1607 ImGui::SameLine();
1608 if (ImGui::Button("Keep Berkeley DB", ImVec2(160.0f, 0.0f)))
1609 {
1610 WriteWalletBackendMarker("bdb");
1611 g_showBackendWizard = false;
1612 }
1613 ImGui::SameLine();
1614 if (ImGui::Button("Decide later", ImVec2(120.0f, 0.0f)))
1615 g_showBackendWizard = false;
1616
1617 if (!g_wizardStatus.empty())
1618 {
1619 ImGui::Spacing();
1620 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.45f, 0.35f, 1.0f));
1621 ImGui::TextWrapped("%s", g_wizardStatus.c_str());
1622 ImGui::PopStyleColor();
1623 }
1624 }
1625 }
1626 ImGui::End();
1627}
1628
1394// --------------------------------------------------------------------------- 1629// ---------------------------------------------------------------------------
1395// Entry point 1630// Entry point
1396// --------------------------------------------------------------------------- 1631// ---------------------------------------------------------------------------
@@ -1457,6 +1692,21 @@ int RunGUI(int argc, char* argv[])
1457 g_mineRadio = nMineMode; 1692 g_mineRadio = nMineMode;
1458 strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1); 1693 strncpy(g_participantPool, strParticipantPool.c_str(), sizeof(g_participantPool)-1);
1459 1694
1695 // Kept for the wizard's "Restart Now": relaunch with the same command line.
1696 g_restartArgc = argc;
1697 g_restartArgv = argv;
1698
1699 // First run on a Berkeley DB wallet with no recorded choice yet: offer to
1700 // convert to SQLite. Never when already on SQLite, once wallet.sqlite exists,
1701 // or once a choice has been recorded in the backend marker.
1702 if (!WalletSQLiteRuntimeActive() &&
1703 FileExists((GetAppDir() + "/wallet.dat").c_str()) &&
1704 !FileExists((GetAppDir() + "/wallet.sqlite").c_str()) &&
1705 ReadWalletBackendMarker().empty())
1706 {
1707 g_showBackendWizard = true;
1708 }
1709
1460 int frame = 0; 1710 int frame = 0;
1461 while (!glfwWindowShouldClose(window) && !fShutdown) 1711 while (!glfwWindowShouldClose(window) && !fShutdown)
1462 { 1712 {
@@ -1480,6 +1730,7 @@ int RunGUI(int argc, char* argv[])
1480 DrawRestorePhraseDialog(); 1730 DrawRestorePhraseDialog();
1481 DrawDiagnosticsDialog(); 1731 DrawDiagnosticsDialog();
1482 DrawAboutDialog(); 1732 DrawAboutDialog();
1733 DrawBackendWizardDialog();
1483 DrawStatusBar(); 1734 DrawStatusBar();
1484 1735
1485 ImGui::Render(); 1736 ImGui::Render();