upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main_gui.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-05 22:46:52 +0000
commitceae3d076b5ad11ee84729ae43f14e9312896646 (patch)
tree7e9132aa871fdc01cb95268d2f5ea3bb11853ddf /src/main_gui.cpp
parentbe63e83d79e7e00e6b8ca889a70e6a19886aa1d8 (diff)
parent66e0b9158d050c46b1ff2509faeee3f4573cac1c (diff)
Bring main onto the line this project actually ships
main had drifted to a history with no ancestor in common with the tree we build and deploy. It sat at be63e83 (2026-08-09) with 28 commits; the shipped line, deploy/soak-2026-08-21, had 383. The two shared nothing -- git merge-base returns no result at all -- so anyone cloning this repository landed on a default branch that was neither current nor related to the running node. The 28 commits on main were audited before this merge: every one is already present in the tree by content, including the two that are hardest to see because they were rewritten afterwards -- the payout reorg-safety work (PayoutBlockStillOnChain, since evolved to re-check on every retry rather than once at maturity) and the nostr relay certificate verification (SSL_set1_host, now with a Windows root-store path). Nothing on main is lost by this merge. Recorded as a merge rather than a force-push: main only ever moves forward, its old history stays reachable as the first parent, and this is revertible. The tree is taken wholesale from the merged branch, so main's content is now exactly what was built and tested. What this brings onto main: - compactblock: BIP152-style compact block relay ported from upstream 1.2.23, primitives and unit test only, with the RAND_bytes failure path closed and the reconstruction allocation bounded. - upstream/main 17741d9: isolated testnet parameters and genesis, optional 2 MB large pages for RandomX. Mainnet genesis verified byte-identical through upstream's BuildGenesisBlock() refactor. - deploy: every image built on bookworm so binaries run on Debian 12, and the container build now honours the RandomX and libsecp256k1 pins it had been silently ignoring since before 1.2.15. Gates at this tree: 15/15 selftests, compactblock's 17 checks, consensus constants unchanged against e4f898c apart from the pre-existing MAX_BLOCK_SIZE_GEN, mainnet genesis intact, money-log check ok.
Diffstat (limited to 'src/main_gui.cpp')
-rw-r--r--src/main_gui.cpp596
1 files changed, 569 insertions, 27 deletions
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index bee4b95..2e4eb91 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -1,7 +1,10 @@
1// Bitflash entry point -- starts node threads then runs GUI (or headless). 1// Bitflash entry point -- starts node threads then runs GUI (or headless).
2 2
3#include "headers_core.h" 3#include "headers_core.h"
4#include "nostr.h"
5#include "proxy.h"
4#include "selftest.h" 6#include "selftest.h"
7#include "tor.h"
5#include "walletcmd.h" 8#include "walletcmd.h"
6#include <thread> // hardware_concurrency, to sanity-check /genproclimit 9#include <thread> // hardware_concurrency, to sanity-check /genproclimit
7#ifndef _WIN32 10#ifndef _WIN32
@@ -108,9 +111,16 @@ static void PrintUsage()
108 printf(" /help, -help, --help, /?\n"); 111 printf(" /help, -help, --help, /?\n");
109 printf(" /datadir=PATH\n"); 112 printf(" /datadir=PATH\n");
110 printf(" /debug\n"); 113 printf(" /debug\n");
114 printf(" /debugrelay (/debug plus per-message relay chatter and block dumps)\n");
115 printf(" /maxdebugfilesize=N (roll debug.log at N MB, default 1024; 0 = never)\n");
111 printf(" /gen\n"); 116 printf(" /gen\n");
112 printf(" /nogui or /daemon\n"); 117 printf(" /nogui or /daemon\n");
113 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, or wallet-encrypt\n"); 118 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity,\n");
119 printf(" db-env-reopen, wallet-sqlite, wallet-sqlite-migration,\n");
120 printf(" wallet-crypto, wallet-encrypt, wallet-portability,\n");
121 printf(" net-message,\n");
122 printf(" consensus-limits, pool-stratum,\n");
123 printf(" parse-money, network-params, socks5-proxy, or managed-tor\n");
114 printf("\n"); 124 printf("\n");
115 printf("Mining mode:\n"); 125 printf("Mining mode:\n");
116 printf(" /operator\n"); 126 printf(" /operator\n");
@@ -118,22 +128,36 @@ static void PrintUsage()
118 printf(" /payoutaddr=BTF_ADDRESS (participant: pay shares to this address, no wallet needed)\n"); 128 printf(" /payoutaddr=BTF_ADDRESS (participant: pay shares to this address, no wallet needed)\n");
119 printf(" /payoutfile=FILE (participant: rotate payout addresses from file)\n"); 129 printf(" /payoutfile=FILE (participant: rotate payout addresses from file)\n");
120 printf(" /hashthreads=N (participant: mining threads, default cores-1)\n"); 130 printf(" /hashthreads=N (participant: mining threads, default cores-1)\n");
131 printf(" /stratumbridge=POOL_BTF_ADDRESS\n");
132 printf(" /stratumbridgeport=N (default 3333; listen on 127.0.0.1)\n");
121 printf(" /solomine\n"); 133 printf(" /solomine\n");
122 printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n"); 134 printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n");
135 printf(" /nolargepages (do not ask for 2 MB pages for the RandomX\n");
136 printf(" cache, dataset and scratchpads)\n");
123 printf(" /checkblocks=N (blocks re-verified at startup, default 288, 0 = all)\n"); 137 printf(" /checkblocks=N (blocks re-verified at startup, default 288, 0 = all)\n");
124 printf("\n"); 138 printf("\n");
125 printf("Pool operator announcement:\n"); 139 printf("Pool operator announcement:\n");
126 printf(" /poolname=NAME\n"); 140 printf(" /poolname=NAME\n");
127 printf(" /poolfee=PCT\n"); 141 printf(" /poolfee=PCT\n");
128 printf(" /pooldashboard=URL (alias: /pooldash=URL)\n"); 142 printf(" /pooldashboard=URL (alias: /pooldash=URL)\n");
143 printf(" /poolstatusfile=PATH (write pool status JSON for dashboards)\n");
144 printf(" /poolroundsfile=PATH (write public pool round proofs JSON)\n");
129 printf("\n"); 145 printf("\n");
130 printf(".btf and rendezvous:\n"); 146 printf(".btf and rendezvous:\n");
131 printf(" /connectbtf=PEER_BTF_ADDRESS\n"); 147 printf(" /connectbtf=PEER_BTF_ADDRESS\n");
132 printf(" /rvrelay=HOST:PORT\n"); 148 printf(" /rvrelay=HOST:PORT\n");
133 printf(" /announcerelay=HOST:PORT\n"); 149 printf(" /announcerelay=HOST:PORT\n");
150 printf(" /onionservice=HOST.onion:PORT (advertise this node's Tor hidden service)\n");
134 printf("\n"); 151 printf("\n");
135 printf("Network:\n"); 152 printf("Network:\n");
136 printf(" /port=N (P2P listen port, default 8433)\n"); 153 printf(" /testnet (isolated test network genesis, datadir, port, and magic)\n");
154 printf(" /port=N (P2P listen port, default 8433; testnet 18433)\n");
155 printf(" /socks=HOST:PORT (SOCKS5 proxy for Nostr, .btf relay, and onion dials)\n");
156 printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n");
157 printf(" /managedtor[=PATH] (start Tor, create a hidden service, advertise its onion)\n");
158 printf(" /automanagedtor (auto-start a bundled Tor if one sits next to the executable)\n");
159 printf(" /nomanagedtor (disable automatic bundled Tor startup)\n");
160 printf(" /oniononly (do not fall back to rendezvous when a .btf onion dial fails)\n");
137 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n"); 161 printf(" /btfseed=ADDRESS:ENCHEX (extra bootstrap peer, repeatable)\n");
138 printf("\n"); 162 printf("\n");
139 printf("Wallet:\n"); 163 printf("Wallet:\n");
@@ -160,6 +184,26 @@ static void PrintUsage()
160 printf(" /recoveryaudit (show how much spendable balance is\n"); 184 printf(" /recoveryaudit (show how much spendable balance is\n");
161 printf(" covered by the recovery phrase;\n"); 185 printf(" covered by the recovery phrase;\n");
162 printf(" exits 2 when wallet.dat is still needed)\n"); 186 printf(" exits 2 when wallet.dat is still needed)\n");
187 printf(" /walletstorageaudit (count wallet.dat record types without\n");
188 printf(" printing wallet values, then exit)\n");
189 printf(" /walletstorageauditjson=FILE\n");
190 printf(" (write the same audit as deterministic JSON)\n");
191 printf(" /walletstoragecheck (fail closed on inconsistent or unsafe\n");
192 printf(" wallet.dat storage state)\n");
193 printf(" /walletsqliteexport=FILE (copy raw wallet.dat records into an\n");
194 printf(" SQLite store, then exit)\n");
195 printf(" /walletsqliteverify=FILE (compare wallet.dat with a SQLite export,\n");
196 printf(" printing counts only, then exit)\n");
197 printf(" /walletsqliterestore=FILE (rebuild wallet.dat from a SQLite export in\n");
198 printf(" an empty data directory, then exit)\n");
199 printf(" /walletsqliteloadcheck=FILE\n");
200 printf(" (parse a SQLite export like the wallet loader)\n");
201 printf(" /walletsqlite=FILE (stage-load a SQLite wallet export, then exit)\n");
202 printf(" /walletbackend=sqlite (opt-in: run the node on <datadir>/wallet.sqlite\n");
203 printf(" instead of wallet.dat; export one first)\n");
204 printf(" /createwallet (headless only: permit creating a new wallet in a\n");
205 printf(" datadir that has none -- otherwise refused, since\n");
206 printf(" a missing wallet is usually a mount/backup fault)\n");
163 printf(" /rescan (walk the chain for coins this wallet owns\n"); 207 printf(" /rescan (walk the chain for coins this wallet owns\n");
164 printf(" but never recorded, then exit)\n"); 208 printf(" but never recorded, then exit)\n");
165 printf("\n"); 209 printf("\n");
@@ -173,9 +217,60 @@ static void ParseStartupArguments(int argc, char* argv[])
173 if (arg(argc,argv,"/datadir") || arg(argc,argv,"-datadir")) 217 if (arg(argc,argv,"/datadir") || arg(argc,argv,"-datadir"))
174 strSetDataDir = argval2(argc, argv, "/datadir", "-datadir"); 218 strSetDataDir = argval2(argc, argv, "/datadir", "-datadir");
175 219
220 bool fUseTestNet = arg(argc, argv, "/testnet") || arg(argc, argv, "-testnet");
221 SelectChainParams(fUseTestNet);
222
176 if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug")) 223 if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug"))
177 fDebug = true; 224 fDebug = true;
178 225
226 // /debugrelay -- everything /debug shows, plus the per-message firehose
227 // ("relay" and "nostrdiscovery" categories: sending: tx, Relaying wtx,
228 // raw CBlock dumps, per-peer discovery lines). That traffic was ~90% of
229 // a 13-hour production debug.log (issue #35), so plain /debug leaves it
230 // off; the message text itself is unchanged for anyone grepping it.
231 if (arg(argc,argv,"/debugrelay") || arg(argc,argv,"-debugrelay"))
232 {
233 fDebug = true;
234 fDebugRelay = true;
235 }
236
237 // /maxdebugfilesize=N -- roll debug.log to debug.log.old once it passes
238 // N MB, checked on each write. Default is 1024 (see util.cpp); 0 turns
239 // the check off and leaves rotation entirely to logrotate.
240 string strMaxDebugSize = argval2(argc, argv, "/maxdebugfilesize", "-maxdebugfilesize");
241 if (!strMaxDebugSize.empty())
242 {
243 // Parsed with atoi64() before, which is strtoll() with the endptr
244 // thrown away: it returns 0 for anything it cannot read, so
245 // /maxdebugfilesize=1G and =1024MB both came out as 0 -- "never
246 // roll", identical to asking for that on purpose, with nothing said
247 // to the operator. The old code also multiplied without a range
248 // check, so a big enough N wrapped to a negative cap, which the roll
249 // reads as "off" too. Every rejection is now reported and leaves the
250 // default in place; the value is a plain count of MB, no suffix.
251 const char* psz = strMaxDebugSize.c_str();
252 char* pszEnd = NULL;
253 errno = 0;
254 int64 nMB = strtoll(psz, &pszEnd, 10);
255 // INT64_MAX, without depending on which limits header this tree pulls in.
256 const int64 nMaxMB = (int64)((~(uint64)0) >> 1) / (1024 * 1024);
257 const char* pszWhy = NULL;
258 if (pszEnd == psz)
259 pszWhy = "not a number";
260 else if (*pszEnd != '\0')
261 pszWhy = "trailing characters -- the value is a count of MB, with no unit suffix";
262 else if (errno == ERANGE || nMB < 0)
263 pszWhy = "not a size in MB";
264 else if (nMB > nMaxMB)
265 pszWhy = "larger than a file size can be";
266 if (pszWhy)
267 fprintf(stderr, "Ignoring /maxdebugfilesize=%s: %s (keeping %lld MB)\n",
268 strMaxDebugSize.c_str(), pszWhy,
269 (long long)(nMaxDebugFileSize / 1024 / 1024));
270 else
271 nMaxDebugFileSize = nMB * 1024 * 1024;
272 }
273
179 if (arg(argc,argv,"/gen") || arg(argc,argv,"-gen")) 274 if (arg(argc,argv,"/gen") || arg(argc,argv,"-gen"))
180 { 275 {
181 fGenerateBitcoins = 1; 276 fGenerateBitcoins = 1;
@@ -195,6 +290,11 @@ static void ParseStartupArguments(int argc, char* argv[])
195 if (!strCheckBlocks.empty()) 290 if (!strCheckBlocks.empty())
196 nCheckBlocksOnLoad = atoi(strCheckBlocks.c_str()); 291 nCheckBlocksOnLoad = atoi(strCheckBlocks.c_str());
197 292
293 // Must be read before LoadBlockIndex(), which is what first calls
294 // RandomXInit() and fixes how the cache is allocated.
295 if (arg(argc,argv,"/nolargepages") || arg(argc,argv,"-nolargepages"))
296 fRandomXLargePages = false;
297
198 // /genproclimit=N -- threads to hash with. 0 or absent means automatic, 298 // /genproclimit=N -- threads to hash with. 0 or absent means automatic,
199 // which is every core but one. Named after Bitcoin's own option so it reads 299 // which is every core but one. Named after Bitcoin's own option so it reads
200 // familiarly to anyone who has run one of these before. 300 // familiarly to anyone who has run one of these before.
@@ -240,6 +340,22 @@ static void ParseStartupArguments(int argc, char* argv[])
240 if (!hashThreads.empty()) 340 if (!hashThreads.empty())
241 nHashThreads = atoi(hashThreads.c_str()); 341 nHashThreads = atoi(hashThreads.c_str());
242 342
343 if (arg(argc,argv,"/stratumbridge") || arg(argc,argv,"-stratumbridge"))
344 {
345 fStratumBridge = true;
346 string bridgePool = argval2(argc, argv, "/stratumbridge", "-stratumbridge");
347 if (!bridgePool.empty())
348 strParticipantPool = bridgePool;
349 }
350
351 string bridgePort = argval2(argc, argv, "/stratumbridgeport", "-stratumbridgeport");
352 if (!bridgePort.empty())
353 {
354 int nPort = atoi(bridgePort.c_str());
355 if (nPort > 0 && nPort <= 65535)
356 nStratumBridgePort = nPort;
357 }
358
243 string poolName = argval2(argc, argv, "/poolname", "-poolname"); 359 string poolName = argval2(argc, argv, "/poolname", "-poolname");
244 if (!poolName.empty()) 360 if (!poolName.empty())
245 strPoolName = poolName; 361 strPoolName = poolName;
@@ -260,9 +376,18 @@ static void ParseStartupArguments(int argc, char* argv[])
260 if (!poolFee.empty()) 376 if (!poolFee.empty())
261 dPoolFeePercent = atof(poolFee.c_str()); 377 dPoolFeePercent = atof(poolFee.c_str());
262 378
379 string poolStatusFile = argval2(argc, argv, "/poolstatusfile", "-poolstatusfile");
380 if (!poolStatusFile.empty())
381 strPoolStatusFile = poolStatusFile;
382
383 string poolRoundsFile = argval2(argc, argv, "/poolroundsfile", "-poolroundsfile");
384 if (!poolRoundsFile.empty())
385 strPoolRoundsFile = poolRoundsFile;
386
263 string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf"); 387 string btfConnect = argval2(argc, argv, "/connectbtf", "-connectbtf");
264 if (!btfConnect.empty()) 388 if (!btfConnect.empty())
265 strBtfConnect = btfConnect; 389 strBtfConnect = btfConnect;
390 fBtfOnionOnly = arg(argc, argv, "/oniononly") || arg(argc, argv, "-oniononly");
266 391
267 string rvRelay = argval2(argc, argv, "/rvrelay", "-rvrelay"); 392 string rvRelay = argval2(argc, argv, "/rvrelay", "-rvrelay");
268 if (!rvRelay.empty()) 393 if (!rvRelay.empty())
@@ -283,10 +408,102 @@ static void ParseStartupArguments(int argc, char* argv[])
283 } 408 }
284 } 409 }
285 410
411 // net.cpp has described nListenPort as "tunable via /port" since it was
412 // written, but nothing ever read the option, so the port was fixed at 8433
413 // and a second node could not start on a machine already running one. Parse
414 // it before Tor setup so a managed hidden service maps the real listener.
415 string strPort = argval2(argc, argv, "/port", "-port");
416 if (!strPort.empty())
417 {
418 int nPort = atoi(strPort.c_str());
419 if (nPort <= 0 || nPort > 65535)
420 fprintf(stderr, "Ignoring /port=%s: not a port number\n", strPort.c_str());
421 else
422 {
423 nListenPort = htons((unsigned short)nPort);
424 // Or we would announce a port we never bound.
425 addrLocalHost.port = nListenPort;
426 }
427 }
428
286 string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay"); 429 string announceRelay = argval2(argc, argv, "/announcerelay", "-announcerelay");
287 if (!announceRelay.empty()) 430 if (!announceRelay.empty())
288 strBtfAnnounceRelay = announceRelay; 431 strBtfAnnounceRelay = announceRelay;
289 432
433 string onionService = argval2(argc, argv, "/onionservice", "-onionservice");
434 if (!onionService.empty())
435 {
436 string err;
437 if (!BtfSetLocalOnionEndpoint(onionService, err))
438 fprintf(stderr, "Ignoring /onionservice=%s: %s\n", onionService.c_str(), err.c_str());
439 else
440 fprintf(stderr, "Direct onion peer endpoint advertised: %s\n",
441 BtfLocalOnionEndpoint().c_str());
442 }
443
444 bool fManagedTor = arg(argc, argv, "/managedtor") || arg(argc, argv, "-managedtor");
445 bool fNoManagedTor = arg(argc, argv, "/nomanagedtor") || arg(argc, argv, "-nomanagedtor");
446 bool fTorMode = arg(argc, argv, "/tor") || arg(argc, argv, "-tor");
447 string socksProxy = argval2(argc, argv, "/socks", "-socks");
448 string bundledTorPath;
449 // Managed Tor is opt-in on this fork. Upstream 1.2.19 auto-starts a
450 // bundled tor whenever one sits next to the executable, which silently
451 // moves Nostr/.btf traffic onto Tor, creates a hidden service, and starts
452 // publishing descriptors with meeting_node="rendezvous-pending" on nodes
453 // that never asked for it -- against our pinned-relay posture. The
454 // upstream behavior remains available under the explicit /automanagedtor.
455 bool fAutoManagedTor = !fManagedTor && !fNoManagedTor && !fTorMode &&
456 socksProxy.empty() &&
457 (arg(argc, argv, "/automanagedtor") || arg(argc, argv, "-automanagedtor")) &&
458 BtfBundledTorPath(bundledTorPath);
459 if (fManagedTor || fAutoManagedTor)
460 {
461 if (fManagedTor && (fTorMode || !socksProxy.empty()))
462 fprintf(stderr, "Warning: /managedtor takes precedence over /tor and /socks\n");
463
464 string torPath = fManagedTor ? argval2(argc, argv, "/managedtor", "-managedtor") : bundledTorPath;
465 string err;
466 if (!BtfStartManagedTor(torPath, err))
467 {
468 if (fAutoManagedTor)
469 fprintf(stderr, "Bundled Tor not enabled automatically: %s\n", err.c_str());
470 else
471 fprintf(stderr, "Ignoring /managedtor=%s: %s\n", torPath.c_str(), err.c_str());
472 }
473 else
474 fprintf(stderr, "Managed Tor enabled%s: %s\n",
475 fAutoManagedTor ? " automatically" : "",
476 BtfManagedTorStatus().c_str());
477 }
478 else if (fTorMode)
479 {
480 if (!socksProxy.empty())
481 fprintf(stderr, "Warning: /tor takes precedence over /socks=%s\n",
482 socksProxy.c_str());
483
484 string torProxy = argval2(argc, argv, "/tor", "-tor");
485 string err;
486 if (!BtfEnableTorProxy(torProxy, err))
487 fprintf(stderr, "Ignoring /tor=%s: %s\n", torProxy.c_str(), err.c_str());
488 else
489 fprintf(stderr, "Tor mode enabled through SOCKS5 proxy %s\n",
490 BtfSocks5ProxyName().c_str());
491 }
492 else
493 {
494 if (!socksProxy.empty())
495 {
496 string err;
497 if (!BtfSetSocks5Proxy(socksProxy, err))
498 fprintf(stderr, "Ignoring /socks=%s: %s\n", socksProxy.c_str(), err.c_str());
499 else
500 fprintf(stderr, "SOCKS5 proxy enabled for outbound discovery/relay dials: %s\n",
501 BtfSocks5ProxyName().c_str());
502 }
503 }
504 if (fBtfOnionOnly && !BtfSocks5ProxyEnabled())
505 fprintf(stderr, "Warning: /oniononly without /tor, /managedtor, or /socks cannot dial .onion peers\n");
506
290 // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for 507 // /btfseed=ADDRESS:ENCHEX -- extra bootstrap peers, repeatable. Useful for
291 // testing the seed path and for private networks that ship no compiled list. 508 // testing the seed path and for private networks that ship no compiled list.
292 for (int i = 1; i < argc; i++) 509 for (int i = 1; i < argc; i++)
@@ -306,22 +523,6 @@ static void ParseStartupArguments(int argc, char* argv[])
306 vBtfExtraSeeds.push_back(make_pair(val.substr(0, colon), val.substr(colon + 1))); 523 vBtfExtraSeeds.push_back(make_pair(val.substr(0, colon), val.substr(colon + 1)));
307 } 524 }
308 525
309 // net.cpp has described nListenPort as "tunable via /port" since it was
310 // written, but nothing ever read the option, so the port was fixed at 8433
311 // and a second node could not start on a machine already running one.
312 string strPort = argval2(argc, argv, "/port", "-port");
313 if (!strPort.empty())
314 {
315 int nPort = atoi(strPort.c_str());
316 if (nPort <= 0 || nPort > 65535)
317 fprintf(stderr, "Ignoring /port=%s: not a port number\n", strPort.c_str());
318 else
319 {
320 nListenPort = htons((unsigned short)nPort);
321 // Or we would announce a port we never bound.
322 addrLocalHost.port = nListenPort;
323 }
324 }
325} 526}
326 527
327// Report a startup failure somewhere the user will actually see it. 528// Report a startup failure somewhere the user will actually see it.
@@ -368,6 +569,36 @@ static void FatalStartupError(bool fHeadless, const string& strWhat, const strin
368#endif 569#endif
369} 570}
370 571
572#ifdef _WIN32
573// Windows equivalent of the SIGINT/SIGTERM handler the headless loop installs on
574// POSIX. Without it a headless node on Windows had no way to reach the clean
575// StopNode()+DBFlush() path: Ctrl-C, closing the console window, a service stop
576// and OS shutdown all killed it mid-write, and a wallet.dat left unflushed by
577// Berkeley DB will not reopen. For CLOSE/LOGOFF/SHUTDOWN the OS terminates us
578// shortly after the handler returns, so we set fShutdown and then block until the
579// main loop signals the flush is done -- returning any sooner throws the flush
580// away, which is the whole bug. (A `taskkill /F` is still an uncatchable hard
581// kill; nothing can help there, and nothing should pretend to.)
582static volatile bool g_fHeadlessShutdownDone = false;
583static BOOL WINAPI HeadlessConsoleCtrlHandler(DWORD dwCtrlType)
584{
585 switch (dwCtrlType)
586 {
587 case CTRL_C_EVENT:
588 case CTRL_BREAK_EVENT:
589 case CTRL_CLOSE_EVENT:
590 case CTRL_LOGOFF_EVENT:
591 case CTRL_SHUTDOWN_EVENT:
592 fShutdown = true;
593 while (!g_fHeadlessShutdownDone)
594 Sleep(50);
595 return TRUE;
596 default:
597 return FALSE;
598 }
599}
600#endif
601
371int main(int argc, char* argv[]) 602int main(int argc, char* argv[])
372{ 603{
373 if (arg(argc,argv,"/help") || arg(argc,argv,"-help") || 604 if (arg(argc,argv,"/help") || arg(argc,argv,"-help") ||
@@ -384,10 +615,119 @@ int main(int argc, char* argv[])
384 615
385 ParseStartupArguments(argc, argv); 616 ParseStartupArguments(argc, argv);
386 617
618#ifdef _WIN32
619 // Set by the GUI "Restart Now" button: give the previous instance a moment
620 // to release the network port and the block-index files before this one
621 // opens them, so a relaunch does not race the process it is replacing.
622 if (arg(argc, argv, "/restartwait") || arg(argc, argv, "-restartwait"))
623 Sleep(2500);
624#endif
625
626 // Hidden self-test helper. It must run before LoadWallet(): several
627 // storage-sanity scenarios deliberately make wallet.dat unsafe to load.
628 string strSelfTestMutateWallet =
629 argval2(argc, argv, "/selftestmutatewallet", "-selftestmutatewallet");
630 if (!strSelfTestMutateWallet.empty())
631 return RunSelfTestMutateWallet(strSelfTestMutateWallet);
632
387 string strSelfTest = argval2(argc, argv, "/selftest", "-selftest"); 633 string strSelfTest = argval2(argc, argv, "/selftest", "-selftest");
388 if (!strSelfTest.empty()) 634 if (!strSelfTest.empty())
389 return RunSelfTest(strSelfTest); 635 return RunSelfTest(strSelfTest);
390 636
637 // Storage diagnostics must run before LoadWallet(). Their job is to
638 // explain wallet.dat states that the normal wallet loader may refuse.
639 string strWalletStorageAuditJson =
640 argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson");
641 bool fDiagWantsAudit = arg(argc,argv,"/walletstorageaudit") ||
642 arg(argc,argv,"-walletstorageaudit") ||
643 !strWalletStorageAuditJson.empty();
644 bool fDiagWantsCheck = arg(argc,argv,"/walletstoragecheck") ||
645 arg(argc,argv,"-walletstoragecheck");
646
647 // These diagnostics normally read wallet.dat. Under -walletbackend=sqlite
648 // the live wallet is wallet.sqlite, so open it read-only as the active
649 // store and let the same scan walk the right file. This runs before the
650 // normal backend resolution further down, so validate the value here too.
651 string strDiagBackend = argval2(argc, argv, "/walletbackend", "-walletbackend");
652 if ((fDiagWantsAudit || fDiagWantsCheck) && !strDiagBackend.empty() &&
653 strDiagBackend != "sqlite" && strDiagBackend != "bdb")
654 {
655 AttachTerminal();
656 fprintf(stderr, "Unknown wallet backend '%s'. Use -walletbackend=sqlite "
657 "or -walletbackend=bdb.\n", strDiagBackend.c_str());
658 return 1;
659 }
660 bool fDiagSQLite = (strDiagBackend == "sqlite") &&
661 (fDiagWantsAudit || fDiagWantsCheck);
662 if (fDiagSQLite)
663 {
664 AttachTerminal();
665 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
666 string strOpenErr;
667 if (!WalletSQLiteRuntimeOpenReadOnly(strSQLitePath, strOpenErr))
668 {
669 fprintf(stderr, "Cannot open the SQLite wallet for diagnostics: %s\n",
670 strOpenErr.c_str());
671 return 1;
672 }
673 }
674
675 if (fDiagWantsAudit)
676 {
677 int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson);
678 if (fDiagSQLite)
679 WalletSQLiteRuntimeClose();
680 else
681 DBFlush(true);
682 return nRet;
683 }
684
685 if (fDiagWantsCheck)
686 {
687 int nRet = CmdWalletStorageCheck();
688 if (fDiagSQLite)
689 WalletSQLiteRuntimeClose();
690 else
691 DBFlush(true);
692 return nRet;
693 }
694
695 string strWalletSQLiteExport =
696 argval2(argc, argv, "/walletsqliteexport", "-walletsqliteexport");
697 if (!strWalletSQLiteExport.empty())
698 {
699 int nRet = CmdWalletSQLiteExport(strWalletSQLiteExport);
700 DBFlush(true);
701 return nRet;
702 }
703
704 string strWalletSQLiteVerify =
705 argval2(argc, argv, "/walletsqliteverify", "-walletsqliteverify");
706 if (!strWalletSQLiteVerify.empty())
707 {
708 int nRet = CmdWalletSQLiteVerify(strWalletSQLiteVerify);
709 DBFlush(true);
710 return nRet;
711 }
712
713 string strWalletSQLiteRestore =
714 argval2(argc, argv, "/walletsqliterestore", "-walletsqliterestore");
715 if (!strWalletSQLiteRestore.empty())
716 {
717 int nRet = CmdWalletSQLiteRestore(strWalletSQLiteRestore);
718 DBFlush(true);
719 return nRet;
720 }
721
722 string strWalletSQLiteLoadCheck =
723 argval2(argc, argv, "/walletsqliteloadcheck", "-walletsqliteloadcheck");
724 if (!strWalletSQLiteLoadCheck.empty())
725 {
726 int nRet = CmdWalletSQLiteLoadCheck(strWalletSQLiteLoadCheck);
727 DBFlush(true);
728 return nRet;
729 }
730
391 // Berkeley DB reports failure by throwing, and CDB's constructor lets it 731 // Berkeley DB reports failure by throwing, and CDB's constructor lets it
392 // through. Nothing on this path caught anything, so an unreadable 732 // through. Nothing on this path caught anything, so an unreadable
393 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley 733 // blkindex.dat or wallet.dat -- one written by another platform's Berkeley
@@ -432,18 +772,197 @@ int main(int argc, char* argv[])
432 printf("Loading wallet...\n"); 772 printf("Loading wallet...\n");
433 try 773 try
434 { 774 {
435 if (!LoadWallet()) 775 string strWalletBackend =
776 argval2(argc, argv, "/walletbackend", "-walletbackend");
777 string strWalletSQLite =
778 argval2(argc, argv, "/walletsqlite", "-walletsqlite");
779
780 // A misspelled backend must not fall through to Berkeley DB silently --
781 // someone who typed -walletbackend=sqlit meant to run on SQLite, and
782 // opening wallet.dat instead of telling them is the wrong surprise.
783 if (!strWalletBackend.empty() &&
784 strWalletBackend != "sqlite" && strWalletBackend != "bdb")
436 { 785 {
437 // LoadWallet() explains itself into strWalletLoadError when it 786 AttachTerminal();
438 // knows why -- an unsupported wallet format, for one, which is the 787 FatalStartupError(fHeadlessStartup,
439 // case this whole path exists to make visible. 788 strprintf("Unknown wallet backend '%s'.", strWalletBackend.c_str()),
440 FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.", 789 "Use -walletbackend=sqlite for the SQLite backend, or "
441 strWalletLoadError); 790 "-walletbackend=bdb for the Berkeley DB wallet.dat.");
442 return 1; 791 return 1;
443 } 792 }
444 793
794 if (strWalletBackend == "sqlite")
795 {
796 // Explicit opt-in via the flag. The node runs on
797 // <datadir>/wallet.sqlite and never opens wallet.dat, but the user
798 // has to have exported one first -- there is no silent migration,
799 // and wallet.dat is left
800 // exactly where it is so a plain restart goes back to it.
801 AttachTerminal();
802 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
803 if (!FileExists(strSQLitePath.c_str()))
804 {
805 string strGuide = strprintf(
806 "No SQLite wallet was found at\n"
807 " %s\n\n"
808 "The SQLite wallet backend is opt-in and needs an exported\n"
809 "wallet first. Your Berkeley DB wallet.dat is not touched by\n"
810 "any of this.\n\n"
811 " Step 1 Export your existing wallet to SQLite:\n"
812 " bitflash -walletsqliteexport=\"%s\"\n"
813 " Step 2 Start again with the SQLite backend:\n"
814 " bitflash -walletbackend=sqlite\n\n"
815 "To go back to Berkeley DB at any time, just start without\n"
816 "-walletbackend=sqlite. wallet.dat is still your wallet.",
817 strSQLitePath.c_str(), strSQLitePath.c_str());
818 FatalStartupError(fHeadlessStartup,
819 "The SQLite wallet backend needs an exported wallet first.",
820 strGuide);
821 return 1;
822 }
823
824 if (!LoadWalletFromSQLiteRuntime(strSQLitePath))
825 {
826 FatalStartupError(fHeadlessStartup,
827 "Cannot open the SQLite wallet backend.",
828 strWalletLoadError);
829 return 1;
830 }
831
832 fprintf(stderr,
833 "Wallet backend: SQLite\n"
834 " %s\n"
835 "Your Berkeley DB wallet.dat is left untouched. Restart without\n"
836 "-walletbackend=sqlite to go back to it.\n",
837 strSQLitePath.c_str());
838 fflush(stderr);
839 // Fall through and run the node normally: every CWalletDB read,
840 // write, and erase now routes to this SQLite file.
841 }
842 else if (!strWalletSQLite.empty())
843 {
844 AttachTerminal();
845 if (!LoadWalletFromSQLite(strWalletSQLite))
846 {
847 FatalStartupError(fHeadlessStartup,
848 "Cannot open SQLite wallet export.",
849 strWalletLoadError);
850 return 1;
851 }
852
853 fprintf(stderr, "SQLite wallet loaded: read-only staging backend\n");
854 fprintf(stderr, "SQLite runtime writes are not enabled yet; exiting before "
855 "network, mining, GUI, or wallet mutation starts.\n");
856 fflush(stderr);
857 DBFlush(true);
858 return 0;
859 }
860 else if (strWalletBackend == "bdb")
861 {
862 // Explicit -walletbackend=bdb overrides the marker and the default:
863 // force Berkeley DB (LoadWallet creates wallet.dat if none exists).
864 if (!LoadWallet())
865 {
866 FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.",
867 strWalletLoadError);
868 return 1;
869 }
870 }
871 else
872 {
873 // No explicit -walletbackend on the command line. Resolve it from
874 // the persistent marker, then defaults: an existing wallet.sqlite
875 // stays on SQLite, an existing wallet.dat stays on Berkeley DB (the
876 // GUI offers to convert), and a fresh install starts on SQLite --
877 // the new default. wallet.dat is never touched by any of this.
878 string strSQLitePath = GetAppDir() + "/wallet.sqlite";
879 bool fSQLiteExists = FileExists(strSQLitePath.c_str());
880 bool fWalletDatExists = FileExists((GetAppDir() + "/wallet.dat").c_str());
881 string strMarker = ReadWalletBackendMarker();
882
883 bool fUseSQLite = false;
884 bool fCreateSQLite = false;
885 if (strMarker == "sqlite")
886 {
887 if (fSQLiteExists) fUseSQLite = true;
888 else if (fWalletDatExists) fUseSQLite = false; // marker stale; real wallet is BDB
889 else { fUseSQLite = true; fCreateSQLite = true; }
890 }
891 else if (strMarker == "bdb")
892 {
893 fUseSQLite = false;
894 }
895 else // no marker recorded yet
896 {
897 if (fSQLiteExists && !fWalletDatExists) fUseSQLite = true;
898 else if (fWalletDatExists) fUseSQLite = false;
899 else { fUseSQLite = true; fCreateSQLite = true; }
900 }
901
902 // A headless node whose datadir holds no wallet of either kind is
903 // far more often an operations fault -- datadir not mounted, or
904 // wallet.dat caught mid-rename by a backup -- than a first run.
905 // Creating one here is not the harmless default it looks like:
906 // success also writes the backend marker, and from then on
907 // restoring the real wallet.dat no longer takes effect, because the
908 // marker keeps the node on the empty wallet it just made. Under
909 // 1.2.19 the same fault created a wallet.dat that a restore
910 // overwrote, so operators could recover by putting the file back.
911 // Refuse instead, and name the flag that makes it deliberate.
912 if (fCreateSQLite && fHeadlessStartup &&
913 !arg(argc, argv, "/createwallet") && !arg(argc, argv, "-createwallet"))
914 {
915 AttachTerminal();
916 fprintf(stderr,
917 "No wallet found in %s\n"
918 "Refusing to create one. On a headless node an absent wallet is\n"
919 "usually a mount or backup fault, and creating one would record a\n"
920 "backend marker that makes restoring the real wallet a no-op.\n"
921 "Pass -createwallet to start a new one deliberately, or\n"
922 "-walletbackend=bdb to run on a Berkeley DB wallet.dat.\n",
923 GetAppDir().c_str());
924 fflush(stderr);
925 return 1;
926 }
927
928 if (fUseSQLite)
929 {
930 AttachTerminal();
931 bool fOk = fCreateSQLite
932 ? CreateNewSQLiteWallet(strSQLitePath)
933 : LoadWalletFromSQLiteRuntime(strSQLitePath);
934 if (!fOk)
935 {
936 FatalStartupError(fHeadlessStartup,
937 fCreateSQLite ? "Cannot create the SQLite wallet."
938 : "Cannot open the SQLite wallet.",
939 strWalletLoadError);
940 return 1;
941 }
942 // Record the choice so it is stable from here on.
943 if (ReadWalletBackendMarker() != "sqlite")
944 WriteWalletBackendMarker("sqlite");
945 fprintf(stderr, "Wallet backend: SQLite\n %s\n%s",
946 strSQLitePath.c_str(),
947 fCreateSQLite ? "A new wallet was created here.\n" : "");
948 fflush(stderr);
949 // Fall through and run the node; CWalletDB routes to this file.
950 }
951 else if (!LoadWallet())
952 {
953 // LoadWallet() explains itself into strWalletLoadError when it
954 // knows why -- an unsupported wallet format, for one, which is
955 // the case this whole path exists to make visible.
956 FatalStartupError(fHeadlessStartup, "Cannot open wallet.dat.",
957 strWalletLoadError);
958 return 1;
959 }
960 }
961
445 // After the block index, so there is a chain to compare the wallet 962 // After the block index, so there is a chain to compare the wallet
446 // against, and before anything reports a balance. 963 // against, and before anything reports a balance. Works on either
964 // backend: RescanSpentFlags writes corrected flags through CWalletDB,
965 // which routes to SQLite when that backend is active.
447 RescanSpentFlags(); 966 RescanSpentFlags();
448 } 967 }
449 catch (const std::exception& e) 968 catch (const std::exception& e)
@@ -668,7 +1187,12 @@ int main(int argc, char* argv[])
668 return 0; 1187 return 0;
669 } 1188 }
670 1189
671 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } 1190 if (!StartNode(strErrors))
1191 {
1192 fprintf(stderr,"StartNode: %s\n",strErrors.c_str());
1193 BtfStopManagedTor();
1194 return 1;
1195 }
672 1196
673 if (nMineMode == MINE_OPERATOR) { 1197 if (nMineMode == MINE_OPERATOR) {
674 gPoolServerRunning = true; 1198 gPoolServerRunning = true;
@@ -676,6 +1200,10 @@ int main(int argc, char* argv[])
676 if (_beginthread(ThreadRPCServer, 0, NULL) == (uintptr_t)-1) 1200 if (_beginthread(ThreadRPCServer, 0, NULL) == (uintptr_t)-1)
677 printf("Error: _beginthread(ThreadRPCServer) failed\n"); 1201 printf("Error: _beginthread(ThreadRPCServer) failed\n");
678 } 1202 }
1203 if (fStratumBridge) {
1204 if (_beginthread(ThreadStratumBridge, 0, NULL) == (uintptr_t)-1)
1205 printf("Error: _beginthread(ThreadStratumBridge) failed\n");
1206 }
679 if (fGenerateBitcoins) 1207 if (fGenerateBitcoins)
680 StartMinerThreads(); 1208 StartMinerThreads();
681 1209
@@ -688,18 +1216,30 @@ int main(int argc, char* argv[])
688#endif 1216#endif
689 1217
690 if (fHeadless) { 1218 if (fHeadless) {
691#ifndef _WIN32 1219#ifdef _WIN32
1220 SetConsoleCtrlHandler(HeadlessConsoleCtrlHandler, TRUE);
1221#else
692 auto sig=[](int){fShutdown=true;}; 1222 auto sig=[](int){fShutdown=true;};
693 signal(SIGINT,sig); signal(SIGTERM,sig); 1223 signal(SIGINT,sig); signal(SIGTERM,sig);
694#endif 1224#endif
695 printf("Running headless. Ctrl-C to stop.\n"); 1225 printf("Running headless. Ctrl-C to stop.\n");
696 while (!fShutdown) Sleep(500); 1226 while (!fShutdown) Sleep(500);
697 StopNode(); 1227 StopNode();
1228 BtfStopManagedTor();
698 // Checkpoints, releases wallet.dat from this directory's Berkeley DB 1229 // Checkpoints, releases wallet.dat from this directory's Berkeley DB
699 // environment, and closes it. Declared since 2009 and never once 1230 // environment, and closes it. Declared since 2009 and never once
700 // called here, which is why a wallet.dat copied elsewhere would not 1231 // called here, which is why a wallet.dat copied elsewhere would not
701 // open -- issue #40. Not optional. 1232 // open -- issue #40. Not optional.
702 DBFlush(true); 1233 DBFlush(true);
1234 // Checkpoint and close the SQLite wallet backend if one is active. A
1235 // no-op on the Berkeley DB path. Safe to kill before this runs: WAL +
1236 // synchronous=FULL already made every committed write durable.
1237 WalletSQLiteRuntimeClose();
1238#ifdef _WIN32
1239 // wallet.dat is safely flushed and closed now; let a pending console
1240 // control handler (CLOSE/LOGOFF/SHUTDOWN) return so the OS can finish.
1241 g_fHeadlessShutdownDone = true;
1242#endif
703 return 0; 1243 return 0;
704 } 1244 }
705 1245
@@ -709,7 +1249,9 @@ int main(int argc, char* argv[])
709 int ret = RunGUI(argc, argv); 1249 int ret = RunGUI(argc, argv);
710 fShutdown = true; 1250 fShutdown = true;
711 StopNode(); 1251 StopNode();
1252 BtfStopManagedTor();
712 DBFlush(true); 1253 DBFlush(true);
1254 WalletSQLiteRuntimeClose();
713 return ret; 1255 return ret;
714#endif 1256#endif
715} 1257}