diff options
Diffstat (limited to 'src/main_gui.cpp')
| -rw-r--r-- | src/main_gui.cpp | 596 |
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.) | ||
| 582 | static volatile bool g_fHeadlessShutdownDone = false; | ||
| 583 | static 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 | |||
| 371 | int main(int argc, char* argv[]) | 602 | int 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 | } |