From 5ce650382352c1503f40abf08129ada69402af55 Mon Sep 17 00:00:00 2001 From: bitflash-coin Date: Tue, 4 Aug 2026 06:34:32 -0700 Subject: public commit --- src/main_gui.cpp | 657 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100644 src/main_gui.cpp (limited to 'src/main_gui.cpp') diff --git a/src/main_gui.cpp b/src/main_gui.cpp new file mode 100644 index 0000000..875988f --- /dev/null +++ b/src/main_gui.cpp @@ -0,0 +1,657 @@ +// Bitflash entry point -- starts node threads then runs GUI (or headless). + +#include "headers_core.h" +#include "selftest.h" +#include "walletcmd.h" +#include // hardware_concurrency, to sanity-check /genproclimit +#ifndef _WIN32 +#include +#endif + +// BITFLASH_NO_GUI builds the same entry point without ImGui, GLFW or OpenGL, +// so the binary does not need libGL present just to start. See headless.cpp. +#ifndef BITFLASH_NO_GUI +int RunGUI(int argc, char* argv[]); +#endif + +// Global definitions (were in ui.cpp, now here) +map mapAddressBook; +bool gPoolServerRunning = false; + +static bool arg(int argc, char* argv[], const char* key) +{ + for (int i=1;i 0 && n > (int)nCores * 4) + // Well past diminishing returns, and every thread still costs a + // scratchpad. Refuse rather than quietly thrash the machine. + fprintf(stderr, "Ignoring /genproclimit=%d: this machine has %u core(s)\n", + n, nCores); + else + nMinerThreads = n; // 0 stays automatic + } + + if (arg(argc,argv,"/operator") || arg(argc,argv,"-operator")) + { + nMineMode = MINE_OPERATOR; + fMineModeFromCommandLine = true; + } + + if (arg(argc,argv,"/participant") || arg(argc,argv,"-participant")) + { + nMineMode = MINE_PARTICIPANT; + strParticipantPool = argval2(argc, argv, "/participant", "-participant"); + fMineModeFromCommandLine = true; + } + + string poolName = argval2(argc, argv, "/poolname", "-poolname"); + if (!poolName.empty()) + strPoolName = poolName; + + string poolDash = argval2(argc, argv, "/pooldashboard", "-pooldashboard"); + if (poolDash.empty()) + poolDash = argval2(argc, argv, "/pooldash", "-pooldash"); + if (!poolDash.empty()) + strPoolDashboardUrl = poolDash; + + string poolFee = argval2(argc, argv, "/poolfee", "-poolfee"); + if (!poolFee.empty()) + dPoolFeePercent = atof(poolFee.c_str()); + + string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf"); + if (!btfConnect.empty()) + strBtfConnect = btfConnect; + + string rvRelay = argval2(argc, argv, "/rvrelay", "-rvrelay"); + if (!rvRelay.empty()) + { + vBtfMeetingRelays.clear(); + vBtfMeetingRelays.push_back(rvRelay); + } + + string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay"); + if (!announceRelay.empty()) + strBtfAnnounceRelay = announceRelay; + + // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for + // testing the seed path and for private networks that ship no compiled list. + for (int i = 1; i < argc; i++) + { + string s = argv[i]; + size_t eq = s.find('='); + if (eq == string::npos) continue; + string key = s.substr(0, eq); + if (key != "/btfseed" && key != "-btfseed") continue; + string val = s.substr(eq + 1); + size_t colon = val.rfind(':'); + if (colon == string::npos || colon + 1 >= val.size()) + { + fprintf(stderr, "Ignoring %s: expected ADDRESS:ENCHEX\n", s.c_str()); + continue; + } + vBtfExtraSeeds.push_back(make_pair(val.substr(0, colon), val.substr(colon + 1))); + } + + // net.cpp has described nListenPort as "tunable via /port" since it was + // written, but nothing ever read the option, so the port was fixed at 8433 + // and a second node could not start on a machine already running one. + string strPort = argval2(argc, argv, "/port", "-port"); + if (!strPort.empty()) + { + int nPort = atoi(strPort.c_str()); + if (nPort <= 0 || nPort > 65535) + fprintf(stderr, "Ignoring /port=%s: not a port number\n", strPort.c_str()); + else + { + nListenPort = htons((unsigned short)nPort); + // Or we would announce a port we never bound. + addrLocalHost.port = nListenPort; + } + } +} + +// Report a startup failure somewhere the user will actually see it. +// +// Every fatal path here wrote to stderr and to printf, and on the platform most +// people use, neither reaches anybody. printf is remapped to OutputDebugStringF, +// so it lands in debug.log inside the data directory. A -mwindows binary +// launched by double-clicking has no stderr at all, and AttachTerminal() cannot +// help: it attaches to a *parent* console, which a double-clicked exe does not +// have. +// +// So the program exited silently. Someone whose wallet.dat needs a newer build +// -- which is exactly what the wallet format guard was added to tell them -- +// double-clicked Bitflash and watched nothing happen. +// +// The box is deliberately not shown for a headless node: those run under +// scheduled tasks and services, where a modal dialog waits forever for a click +// nobody is there to make. Headless already has a console when it has one, and +// its output goes to stderr and the log as before. +static void FatalStartupError(bool fHeadless, const string& strWhat, const string& strDetail) +{ + printf("FATAL: %s\n", strWhat.c_str()); + if (!strDetail.empty()) + printf("FATAL: %s\n", strDetail.c_str()); + + AttachTerminal(); + fprintf(stderr, "%s\n", strWhat.c_str()); + if (!strDetail.empty()) + fprintf(stderr, "%s\n", strDetail.c_str()); + fflush(stderr); + +#ifdef _WIN32 + if (!fHeadless && GetConsoleWindow() == NULL) + { + string strBody = strWhat; + if (!strDetail.empty()) + strBody += "\n\n" + strDetail; + strBody += "\n\nNothing was changed. More detail is in debug.log, " + "inside the data directory."; + MessageBoxA(NULL, strBody.c_str(), "Bitflash", MB_OK | MB_ICONERROR | MB_SETFOREGROUND); + } +#else + (void)fHeadless; +#endif +} + +int main(int argc, char* argv[]) +{ + if (arg(argc,argv,"/help") || arg(argc,argv,"-help") || + arg(argc,argv,"--help") || arg(argc,argv,"/?")) + { + // Without this the help prints into nothing. A -mwindows binary starts + // with no console, so every one of those printfs went nowhere and the + // one command whose entire job is to tell you something told you + // nothing at all -- exit code 0, not a byte of output. + AttachTerminal(); + PrintUsage(); + return 0; + } + + ParseStartupArguments(argc, argv); + + string strSelfTest = argval2(argc, argv, "/selftest", "-selftest"); + if (!strSelfTest.empty()) + return RunSelfTest(strSelfTest); + + // Berkeley DB reports failure by throwing, and CDB's constructor lets it + // through. Nothing on this path caught anything, so an unreadable + // blkindex.dat or wallet.dat -- one written by another platform's Berkeley + // DB, a truncated file, a version mismatch -- unwound out of main() into + // std::terminate and abort(). On Windows that surfaces as + // STATUS_STACK_BUFFER_OVERRUN (0xC0000409) inside ucrtbase.dll: no message, + // no log line past "Loading wallet...", and a faulting module with nothing + // to do with the real problem. Confirmed by stack trace: + // + // libdb_cxx-6.2.dll -> CDB::CDB(...) -> LoadBlockIndex(...) -> main() + // + // The program knew what had gone wrong and threw the reason away. + // Needed here, long before the headless branch runs, because whether a + // startup failure may pop a dialog depends on it. +#ifdef BITFLASH_NO_GUI + bool fHeadlessStartup = true; +#else + bool fHeadlessStartup = arg(argc,argv,"/nogui") || arg(argc,argv,"-nogui") || + arg(argc,argv,"/daemon") || arg(argc,argv,"-daemon"); +#endif + + string strErrors; + printf("Loading block index...\n"); + try + { + if (!LoadBlockIndex()) + { + FatalStartupError(fHeadlessStartup, "Cannot load the block index.", ""); + return 1; + } + } + catch (const std::exception& e) + { + FatalStartupError(fHeadlessStartup, + strprintf("Cannot read the block index: %s", e.what()), + "blkindex.dat and blk0001.dat may be from another machine or " + "incomplete. Deleting both is safe -- they are re-downloaded -- " + "but never delete wallet.dat, which holds your keys."); + return 1; + } + + printf("Loading wallet...\n"); + try + { + if (!LoadWallet()) + { + // LoadWallet() explains itself into strWalletLoadError when it + // knows why -- an unsupported wallet format, for one, which is the + // case this whole path exists to make visible. + FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", + strWalletLoadError); + return 1; + } + + // After the block index, so there is a chain to compare the wallet + // against, and before anything reports a balance. + RescanSpentFlags(); + } + catch (const std::exception& e) + { + FatalStartupError(fHeadlessStartup, + strprintf("Cannot read wallet.dat: %s", e.what()), + "The file was left untouched. A wallet.dat written by a " + "different platform's Berkeley DB is the usual cause; back it " + "up before trying anything else."); + return 1; + } + printf("Height=%d\n", nBestHeight); + + bool fEncryptWallet = arg(argc, argv, "/encryptwallet") || arg(argc, argv, "-encryptwallet"); + string strEncryptWallet = argval2(argc, argv, "/encryptwallet", "-encryptwallet"); + if (fEncryptWallet) + { + string strPassphrase; + string strPassphraseError; + if (!ReadPassphraseArgument(strEncryptWallet, strPassphrase, strPassphraseError)) + { + fprintf(stderr, "Cannot read encryption passphrase: %s\n", strPassphraseError.c_str()); + DBFlush(true); + return 1; + } + int nRet = CmdEncryptWallet(strPassphrase); + if (nRet != 0) + DBFlush(true); + return nRet; + } + + bool fWalletPassphrase = arg(argc, argv, "/walletpassphrase") || arg(argc, argv, "-walletpassphrase"); + string strWalletPassphrase = argval2(argc, argv, "/walletpassphrase", "-walletpassphrase"); + if (fWalletPassphrase) + { + string strPassphrase; + string strUnlockError; + if (!ReadPassphraseArgument(strWalletPassphrase, strPassphrase, strUnlockError) || + !UnlockWallet(strPassphrase, strUnlockError)) + { + fprintf(stderr, "Cannot unlock wallet: %s\n", strUnlockError.c_str()); + DBFlush(true); + return 1; + } + } + + // Before the node opens sockets or touches anything: the wallet is loaded, + // which is all a backup needs, and finishing here means the copy is taken + // from a quiet directory rather than from under a running node. + string strBackup = argval2(argc, argv, "/backupwallet", "-backupwallet"); + if (!strBackup.empty()) + { + bool fOk = BackupWallet(strBackup); + if (fOk) + { + printf("Backed up to %s\n", strBackup.c_str()); + printf("This file opens on its own -- it does not need the database/ " + "directory beside it.\n"); + printf("It is a snapshot: coins paid to addresses created after now are " + "not in it, so back up again whenever you receive to a new address.\n"); + } + else + fprintf(stderr, "Backup failed -- nothing was written.\n"); + DBFlush(true); + return fOk ? 0 : 1; + } + + string strDump = argval2(argc, argv, "/dumpwallet", "-dumpwallet"); + if (!strDump.empty()) + { + bool fOk = DumpWallet(strDump); + if (fOk) + { + printf("Wrote %s\n", strDump.c_str()); + printf("It holds your private keys as readable text. Anyone with the file " + "can spend these coins -- keep it off shared storage and delete it " + "once you have it somewhere safe.\n"); + } + else + fprintf(stderr, "Export failed -- nothing was written.\n"); + DBFlush(true); + return fOk ? 0 : 1; + } + + string strImport = argval2(argc, argv, "/importwallet", "-importwallet"); + if (!strImport.empty()) + { + int nAdded = 0, nSkipped = 0; + bool fOk = ImportWallet(strImport, nAdded, nSkipped); + if (fOk) + { + printf("Imported %d key(s); %d were already here.\n", nAdded, nSkipped); + // Importing a key without looking for its coins leaves the user + // holding a wallet that says zero about money that is on the + // chain. That was the old behaviour and it looks exactly like the + // import having failed. + if (nAdded > 0) + { + string strScanError; + if (!CanScanWalletTransactions(strScanError)) + { + fprintf(stderr, "Cannot scan for imported coins: %s\n", + strScanError.c_str()); + fprintf(stderr, "The key import was written, but the wallet " + "balance was not proven against the chain.\n"); + DBFlush(true); + return 1; + } + printf("Looking through the chain for coins belonging to the imported key(s)...\n"); + int nFound = ScanForWalletTransactions(pindexGenesisBlock); + printf("Found %d transaction(s). Start the node normally to see the balance.\n", nFound); + } + } + else + fprintf(stderr, "Import failed.\n"); + DBFlush(true); + return fOk ? 0 : 1; + } + + // /rescan -- walk the chain and pick up anything the wallet's keys own but + // the wallet never recorded. Cheap to offer and the only recourse when a + // balance is wrong for this reason. + string strShow = argval2(argc, argv, "/showderived", "-showderived"); + if (!strShow.empty()) + { + int nRet = CmdShowDerived(atoi(strShow.c_str())); + DBFlush(true); + return nRet; + } + + if (arg(argc,argv,"/newaddress") || arg(argc,argv,"-newaddress")) + { + int nRet = CmdNewAddress(); + DBFlush(true); + return nRet; + } + + // -sendto -- the counterpart to -newaddress. Without it a headless node can + // be paid and can never pay: SendMoney() has been here since 0.1.0 and only + // the window ever reached it. + string strSendTo = argval2(argc, argv, "/sendto", "-sendto"); + if (!strSendTo.empty()) + { + int nRet = CmdSendTo(strSendTo); + DBFlush(true); + return nRet; + } + + if (arg(argc,argv,"/recoveryaudit") || arg(argc,argv,"-recoveryaudit")) + { + int nRet = CmdRecoveryAudit(); + DBFlush(true); + return nRet; + } + + if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) + { + int nRet = CmdNewPhrase(); + DBFlush(true); + return nRet; + } + + string strRestore = argval2(argc, argv, "/restorephrase", "-restorephrase"); + if (!strRestore.empty()) + { + string strDepth = argval2(argc, argv, "/restoredepth", "-restoredepth"); + int nMinDepth = strDepth.empty() ? 0 : atoi(strDepth.c_str()); + int nRet = CmdRestorePhrase(strRestore, nMinDepth); + DBFlush(true); + return nRet; + } + + if (arg(argc,argv,"/rescan") || arg(argc,argv,"-rescan")) + { + string strScanError; + if (!CanScanWalletTransactions(strScanError)) + { + fprintf(stderr, "Cannot rescan wallet transactions: %s\n", + strScanError.c_str()); + DBFlush(true); + return 1; + } + printf("Rescanning the chain for this wallet's transactions...\n"); + int nFound = ScanForWalletTransactions(pindexGenesisBlock); + printf("Rescan done: %d transaction(s) added or updated.\n", nFound); + DBFlush(true); + return 0; + } + + if (fGenerateBitcoins && IsWalletLocked()) + { + fprintf(stderr, "Cannot mine while the encrypted wallet is locked. " + "Start with /walletpassphrase or /walletpassphrase=@FILE, or disable mining.\n"); + DBFlush(true); + return 1; + } + + ReacceptWalletTransactions(); + + if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } + + if (nMineMode == MINE_OPERATOR) { + gPoolServerRunning = true; + gPoolRunning = true; + if (_beginthread(ThreadRPCServer, 0, NULL) == (uintptr_t)-1) + printf("Error: _beginthread(ThreadRPCServer) failed\n"); + } + if (fGenerateBitcoins) + StartMinerThreads(); + +#ifdef BITFLASH_NO_GUI + // Nothing else this binary can do; /nogui is accepted and redundant. + bool fHeadless = true; +#else + bool fHeadless = arg(argc,argv,"/nogui") || arg(argc,argv,"-nogui") || + arg(argc,argv,"/daemon") || arg(argc,argv,"-daemon"); +#endif + + if (fHeadless) { +#ifndef _WIN32 + auto sig=[](int){fShutdown=true;}; + signal(SIGINT,sig); signal(SIGTERM,sig); +#endif + printf("Running headless. Ctrl-C to stop.\n"); + while (!fShutdown) Sleep(500); + StopNode(); + // Checkpoints, releases wallet.dat from this directory's Berkeley DB + // environment, and closes it. Declared since 2009 and never once + // called here, which is why a wallet.dat copied elsewhere would not + // open -- issue #40. Not optional. + DBFlush(true); + return 0; + } + +#ifdef BITFLASH_NO_GUI + return 0; // unreachable: fHeadless is always true in this build +#else + int ret = RunGUI(argc, argv); + fShutdown = true; + StopNode(); + DBFlush(true); + return ret; +#endif +} -- cgit v1.2.3