From 28a2656d22a193fcd92d225964da5a01e8183623 Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 20 Aug 2026 15:37:44 -0300 Subject: net: add isolated testnet parameters --- src/Makefile | 1 + src/main.cpp | 16 +++++++++--- src/main_gui.cpp | 7 ++++-- src/makefile.mingw | 1 + src/net.cpp | 32 +++++++++++++++++++++-- src/net.h | 20 +++++++++------ src/readme.txt | 3 ++- src/selftest.cpp | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 138 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/Makefile b/src/Makefile index b83ff58..ab0b307 100644 --- a/src/Makefile +++ b/src/Makefile @@ -193,6 +193,7 @@ tests: test_bip32 bitflash-node ./bitflash-node -selftest=wallet-crypto ./bitflash-node -selftest=wallet-encrypt ./bitflash-node -selftest=net-message + ./bitflash-node -selftest=network-params ./bitflash-node -selftest=consensus-limits ./bitflash-node -selftest=pool-stratum ./bitflash-node -selftest=parse-money diff --git a/src/main.cpp b/src/main.cpp index 5bd4028..6fd8c82 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2497,11 +2497,21 @@ string GetAppDir() { return "."; } - static bool fMkdirDone; - if (!fMkdirDone) + static string strCreatedBaseDir; + if (strCreatedBaseDir != strDir) { - fMkdirDone = true; _mkdir(strDir.c_str()); + strCreatedBaseDir = strDir; + } + if (IsTestNet()) + { + strDir += "/testnet"; + static string strCreatedTestnetDir; + if (strCreatedTestnetDir != strDir) + { + _mkdir(strDir.c_str()); + strCreatedTestnetDir = strDir; + } } return strDir; } diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 943799f..0a961bf 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -118,7 +118,7 @@ static void PrintUsage() printf(" wallet-crypto, wallet-encrypt, wallet-portability,\n"); printf(" net-message,\n"); printf(" consensus-limits, pool-stratum,\n"); - printf(" parse-money, socks5-proxy, or managed-tor\n"); + printf(" parse-money, network-params, socks5-proxy, or managed-tor\n"); printf("\n"); printf("Mining mode:\n"); printf(" /operator\n"); @@ -143,7 +143,8 @@ static void PrintUsage() printf(" /onionservice=HOST.onion:PORT (advertise this node's Tor hidden service)\n"); printf("\n"); printf("Network:\n"); - printf(" /port=N (P2P listen port, default 8433)\n"); + printf(" /testnet (isolated test network datadir, port, and magic)\n"); + printf(" /port=N (P2P listen port, default 8433; testnet 18433)\n"); printf(" /socks=HOST:PORT (SOCKS5 proxy for Nostr, .btf relay, and onion dials)\n"); printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n"); printf(" /managedtor[=PATH] (start Tor, create a hidden service, advertise its onion)\n"); @@ -205,6 +206,8 @@ static void ParseStartupArguments(int argc, char* argv[]) if (arg(argc,argv,"/datadir") || arg(argc,argv,"-datadir")) strSetDataDir = argval2(argc, argv, "/datadir", "-datadir"); + SelectNetworkParams(arg(argc, argv, "/testnet") || arg(argc, argv, "-testnet")); + if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug")) fDebug = true; diff --git a/src/makefile.mingw b/src/makefile.mingw index 55f186b..910e025 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -116,6 +116,7 @@ tests: test_bip32.exe bitflash.exe ./bitflash.exe -selftest=wallet-backend-default ./bitflash.exe -selftest=wallet-convert ./bitflash.exe -selftest=net-message + ./bitflash.exe -selftest=network-params ./bitflash.exe -selftest=consensus-limits ./bitflash.exe -selftest=pool-stratum ./bitflash.exe -selftest=socks5-proxy diff --git a/src/net.cpp b/src/net.cpp index 4603d2d..ecfd565 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -50,10 +50,14 @@ static int BtfPoll(BtfPollFd* pfd, unsigned int nfd, int nTimeoutMs) // // Global state variables // +static const char pchMainnetMessageStart[4] = { 0xbf, 0x20, 0x5c, 0xfd }; +static const char pchTestnetMessageStart[4] = { (char)0xce, (char)0xe2, (char)0xc0, (char)0xff }; +static bool fTestNet = false; +char pchMessageStart[4] = { 0xbf, 0x20, 0x5c, 0xfd }; bool fClient = false; uint64 nLocalServices = (fClient ? 0 : NODE_NETWORK); -CAddress addrLocalHost(0, DEFAULT_PORT, nLocalServices); -unsigned short nListenPort = DEFAULT_PORT; // local P2P port (tunable via /port) +CAddress addrLocalHost(0, GetDefaultPort(), nLocalServices); +unsigned short nListenPort = GetDefaultPort(); // local P2P port (tunable via /port) CNode nodeLocalHost(INVALID_SOCKET, CAddress("127.0.0.1", nLocalServices)); CNode* pnodeLocalHost = &nodeLocalHost; bool fShutdown = false; @@ -90,6 +94,29 @@ static int64 nBtfHandshakeSilent = 0; static string strBtfLastDialFail; static string strBtfLastHandshakeTimeout; +bool IsTestNet() +{ + return fTestNet; +} + +unsigned short GetDefaultPort() +{ + return htons(fTestNet ? TESTNET_PORT : MAINNET_PORT); +} + +void SelectNetworkParams(bool fTestNetIn) +{ + if (fTestNet == fTestNetIn) + return; + + fTestNet = fTestNetIn; + memcpy(pchMessageStart, + fTestNet ? pchTestnetMessageStart : pchMainnetMessageStart, + sizeof(pchMessageStart)); + nListenPort = GetDefaultPort(); + addrLocalHost.port = nListenPort; +} + void BtfChurnNoteResolveAttempt() { CRITICAL_BLOCK(cs_btfChurn) @@ -288,6 +315,7 @@ string GetDiagnosticsText() int nMedian = GetPeerMedianHeight(); str += "Bitflash node diagnostics\n"; + str += strprintf(" network %s\n", IsTestNet() ? "testnet" : "mainnet"); str += strprintf(" uptime %s\n", FormatAge(nNodeStartTime ? nNow - nNodeStartTime : -1).c_str()); if (nMedian < 0) diff --git a/src/net.h b/src/net.h index e3d4a93..56f1341 100644 --- a/src/net.h +++ b/src/net.h @@ -13,7 +13,8 @@ class CBlockIndex; -static const unsigned short DEFAULT_PORT = htons(8433); +static const unsigned short MAINNET_PORT = 8433; +static const unsigned short TESTNET_PORT = 18433; static const unsigned int PUBLISH_HOPS = 5; enum { @@ -26,6 +27,9 @@ enum bool GetMyExternalIP(unsigned int& ipRet); +bool IsTestNet(); +unsigned short GetDefaultPort(); +void SelectNetworkParams(bool fTestNetIn); CNode* ConnectNodeBtf(const string& strBtfAddr); CNode* ConnectNodeBtfResolved(const string& strBtfAddr, const string& strMeeting, const string& strOnion, const unsigned char enc_pub[32], @@ -219,15 +223,17 @@ void CheckForShutdown(int n); // The message start string is designed to be unlikely to occur in normal data. // The characters are rarely used upper ascii, not valid as UTF-8, and produce // a large 4-byte int at any alignment. -// Magic bytes unique to the Bitflash network (0xbf = "BF"). Second byte bumped -// to 0x20 for the 2026 stable relaunch so the old test chain can't interfere. -static const char pchMessageStart[4] = { 0xbf, 0x20, 0x5c, 0xfd }; +// Magic bytes unique to the selected Bitflash network. Mainnet uses 0xbf = +// "BF"; testnet swaps the whole sequence so a test node cannot handshake with +// mainnet before consensus-level testnet activation exists. +extern char pchMessageStart[4]; class CMessageHeader { public: enum { COMMAND_SIZE=12 }; - char pchMessageStart[sizeof(::pchMessageStart)]; + enum { MESSAGE_START_SIZE=4 }; + char pchMessageStart[MESSAGE_START_SIZE]; char pchCommand[COMMAND_SIZE]; unsigned int nMessageSize; @@ -318,7 +324,7 @@ public: nServices = 0; memcpy(pchReserved, pchIPv4, sizeof(pchReserved)); ip = 0; - port = DEFAULT_PORT; + port = GetDefaultPort(); nTime = GetAdjustedTime(); nLastFailed = 0; } @@ -348,7 +354,7 @@ public: nServices = nServicesIn; memcpy(pchReserved, pchIPv4, sizeof(pchReserved)); ip = 0; - port = DEFAULT_PORT; + port = GetDefaultPort(); nTime = GetAdjustedTime(); nLastFailed = 0; diff --git a/src/readme.txt b/src/readme.txt index 752be66..5d62791 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -41,7 +41,8 @@ The resulting executable is src/bitflash.exe. Command-line options (in addition to the original ones) ------------------------------------------------------- - /port=N listen on P2P port N (default 8433) + /testnet use isolated test network datadir, port 18433, and message magic + /port=N listen on P2P port N (default 8433; testnet default 18433) /socks=HOST:PORT route outbound Nostr, .btf rendezvous, and direct onion peer dials through a SOCKS5 proxy, e.g. /socks=127.0.0.1:9050 for local Tor diff --git a/src/selftest.cpp b/src/selftest.cpp index dc9936e..30bd28e 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -3076,6 +3076,76 @@ static int RunNetMessageSelfTest() return nFail == 0 ? 0 : 1; } +static int RunNetworkParamsSelfTest() +{ + fflush(stdout); + printf("network-params self-test\n"); + + int nFail = 0; + std::string strOldDataDir = strSetDataDir; + try + { + SelectNetworkParams(false); + nFail += Check(!IsTestNet(), "starts on mainnet after explicit selection") ? 0 : 1; + nFail += Check(ntohs(GetDefaultPort()) == MAINNET_PORT, + "mainnet default P2P port is 8433") ? 0 : 1; + CAddress mainAddr("127.0.0.1"); + nFail += Check(mainAddr.port == htons(MAINNET_PORT), + "mainnet address parsing uses the mainnet default port") ? 0 : 1; + CMessageHeader mainHeader("ping", 0); + char mainMagic[4]; + memcpy(mainMagic, mainHeader.pchMessageStart, sizeof(mainMagic)); + + SelectNetworkParams(true); + nFail += Check(IsTestNet(), "testnet selection is sticky") ? 0 : 1; + nFail += Check(ntohs(GetDefaultPort()) == TESTNET_PORT, + "testnet default P2P port is 18433") ? 0 : 1; + CAddress testAddr("127.0.0.1"); + nFail += Check(testAddr.port == htons(TESTNET_PORT), + "testnet address parsing uses the testnet default port") ? 0 : 1; + CMessageHeader testHeader("ping", 0); + nFail += Check(memcmp(mainMagic, testHeader.pchMessageStart, sizeof(mainMagic)) != 0, + "testnet message magic differs from mainnet") ? 0 : 1; + + std::string tmp; + if (!MakeTempDir(tmp)) + { + nFail += Check(false, "creates a temporary data directory") ? 0 : 1; + } + else + { + strSetDataDir = tmp; + SelectNetworkParams(false); + std::string mainDir = GetAppDir(); + SelectNetworkParams(true); + std::string testDir = GetAppDir(); + nFail += Check(mainDir == tmp, + "mainnet uses the selected data directory") ? 0 : 1; + nFail += Check(testDir == tmp + "/testnet", + "testnet stores data below a testnet subdirectory") ? 0 : 1; + RemoveTree(tmp); + } + } + catch (const std::exception& e) + { + printf(" FAIL exception: %s\n", e.what()); + nFail++; + } + catch (...) + { + printf(" FAIL unknown exception\n"); + nFail++; + } + + strSetDataDir = strOldDataDir; + SelectNetworkParams(false); + + printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", + nFail, nFail == 1 ? "" : "s"); + fflush(stdout); + return nFail == 0 ? 0 : 1; +} + static CBlock MakeSizedConsensusBlock(size_t nScriptBytes) { CTransaction tx; @@ -3517,6 +3587,8 @@ int RunSelfTest(const std::string& name) return RunWalletPortabilitySelfTest(); if (name == "net-message") return RunNetMessageSelfTest(); + if (name == "network-params") + return RunNetworkParamsSelfTest(); if (name == "consensus-limits") return RunConsensusLimitsSelfTest(); if (name == "pool-stratum") @@ -3529,6 +3601,6 @@ int RunSelfTest(const std::string& name) return RunManagedTorSelfTest(); printf("Unknown self-test '%s'\n", name.c_str()); - printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); + printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-backend-default, wallet-convert, wallet-portability, net-message, network-params, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); return 1; } -- cgit v1.2.3 From 56be66dd66e64e6d21eadd80a808b4627921059f Mon Sep 17 00:00:00 2001 From: Nsable-sh Date: Thu, 20 Aug 2026 17:06:08 -0300 Subject: chain: add isolated testnet genesis --- README.md | 2 +- src/main.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++------------ src/main.h | 2 ++ src/main_gui.cpp | 5 +-- src/readme.txt | 2 +- src/selftest.cpp | 21 +++++++++--- 6 files changed, 105 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 4843f5c..77227fa 100644 --- a/README.md +++ b/README.md @@ -355,7 +355,7 @@ Other options worth knowing: ```bash -datadir=PATH # wallet and chain data elsewhere --testnet # isolated test network datadir, port 18433, and message magic +-testnet # isolated test network genesis, datadir, port 18433, and magic -port=N # P2P listen port, default 8433; testnet default 18433 -socks=HOST:PORT # SOCKS5 for outbound Nostr, .btf relay, and onion peer dials -tor[=HOST:PORT] # Tor mode; default local Tor SOCKS5 proxy is 127.0.0.1:9050 diff --git a/src/main.cpp b/src/main.cpp index 6fd8c82..5d266ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,12 +41,30 @@ unsigned int nTransactionsUpdated = 0; map mapNextTx; map mapBlockIndex; -// Bitflash genesis -- mined under RandomX PoW on 2026-07-22 -// (nTime 1753142400, nBits 0x1f0fffff, RandomX key "Bitflash/RandomX/v1/...") -// Bitflash 2026 stable relaunch genesis (2-min blocks, RandomX). Mined values. -unsigned int GENESIS_NONCE = 3141; -uint256 hashGenesisBlock("0x5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169"); -uint256 hashGenesisMerkleRoot("0x1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325"); +// Bitflash mainnet genesis -- mined under RandomX PoW for the 2026 stable +// relaunch. Testnet gets its own resettable genesis below so block indexes, +// headers, and wallet scans cannot be accidentally mixed across networks. +static const unsigned int MAINNET_GENESIS_TIME = 1753315200; // 2026-07-24 00:00:00 UTC +static const unsigned int MAINNET_GENESIS_NONCE = 3141; +static const char* MAINNET_GENESIS_TIMESTAMP = + "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; +static const char* MAINNET_GENESIS_HASH = + "0x5bd7cb255d814e48cebcdfb72da4dc87b34bd774227f8ceb546c5640f4bdc169"; +static const char* MAINNET_GENESIS_MERKLE = + "0x1a45b4482532abb29b10e234d3f13132230525a339ecea91658ffa675a5b1325"; + +static const unsigned int TESTNET_GENESIS_TIME = 1787184000; // 2026-08-20 00:00:00 UTC +static const unsigned int TESTNET_GENESIS_NONCE = 3199; +static const char* TESTNET_GENESIS_TIMESTAMP = + "Bitflash testnet 20/Aug/2026: resettable public test chain"; +static const char* TESTNET_GENESIS_HASH = + "0xa9d11c6d697bcb7aea0653bc088b12dcf9f44e732df205162cf085fd9d9963eb"; +static const char* TESTNET_GENESIS_MERKLE = + "0x510b293105e3ff85b8d4c3920e357f85d1a86bf199eb4e7d11f04f380030ec5c"; + +unsigned int GENESIS_NONCE = MAINNET_GENESIS_NONCE; +uint256 hashGenesisBlock(MAINNET_GENESIS_HASH); +uint256 hashGenesisMerkleRoot(MAINNET_GENESIS_MERKLE); CBlockIndex* pindexGenesisBlock = NULL; int nBestHeight = -1; uint256 hashBestChain = 0; @@ -117,6 +135,63 @@ static std::atomic gParticipantHashRateX1000{0}; static std::mutex gParticipantStatusMutex; static std::string gParticipantStatus; +void SelectGenesisParams(bool fTestNetIn) +{ + static bool fGenesisParamsSelected = false; + static bool fGenesisParamsTestNet = false; + if (fGenesisParamsSelected && fGenesisParamsTestNet == fTestNetIn) + return; + + fGenesisParamsSelected = true; + fGenesisParamsTestNet = fTestNetIn; + + if (fTestNetIn) + { + GENESIS_NONCE = TESTNET_GENESIS_NONCE; + hashGenesisBlock = uint256(TESTNET_GENESIS_HASH); + hashGenesisMerkleRoot = uint256(TESTNET_GENESIS_MERKLE); + } + else + { + GENESIS_NONCE = MAINNET_GENESIS_NONCE; + hashGenesisBlock = uint256(MAINNET_GENESIS_HASH); + hashGenesisMerkleRoot = uint256(MAINNET_GENESIS_MERKLE); + } +} + +void SelectChainParams(bool fTestNetIn) +{ + SelectNetworkParams(fTestNetIn); + SelectGenesisParams(fTestNetIn); +} + +static CBlock BuildGenesisBlock() +{ + const char* pszTimestamp = IsTestNet() ? + TESTNET_GENESIS_TIMESTAMP : MAINNET_GENESIS_TIMESTAMP; + + CTransaction txNew; + txNew.vin.resize(1); + txNew.vout.resize(1); + txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << + vector((unsigned char*)pszTimestamp, + (unsigned char*)pszTimestamp + strlen(pszTimestamp)); + txNew.vout[0].nValue = 50 * COIN; + txNew.vout[0].scriptPubKey = CScript() << + CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << + OP_CHECKSIG; + + CBlock block; + block.vtx.push_back(txNew); + block.hashPrevBlock = 0; + block.hashMerkleRoot = block.BuildMerkleTree(); + block.nVersion = 1; + block.nTime = IsTestNet() ? TESTNET_GENESIS_TIME : MAINNET_GENESIS_TIME; + block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) + block.nNonce = GENESIS_NONCE; + return block; +} + void SetParticipantMiningStatus(const std::string& status) { // Updates the GUI status string only. Logging is done at each call site @@ -2664,21 +2739,7 @@ bool LoadBlockIndex(bool fAllowNew) // vMerkleTree: 4a5e1e // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) - char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; - CTransaction txNew; - txNew.vin.resize(1); - txNew.vout.resize(1); - txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((unsigned char*)pszTimestamp, (unsigned char*)pszTimestamp + strlen(pszTimestamp)); - txNew.vout[0].nValue = 50 * COIN; - txNew.vout[0].scriptPubKey = CScript() << CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << OP_CHECKSIG; - CBlock block; - block.vtx.push_back(txNew); - block.hashPrevBlock = 0; - block.hashMerkleRoot = block.BuildMerkleTree(); - block.nVersion = 1; - block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) - block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) - block.nNonce = GENESIS_NONCE; + CBlock block = BuildGenesisBlock(); // If the nonce has not been found yet, mine the genesis once (RandomX) // and print the values to be hardcoded in the constants above. diff --git a/src/main.h b/src/main.h index dc5527e..793c4e8 100644 --- a/src/main.h +++ b/src/main.h @@ -59,6 +59,8 @@ extern map mapBlockIndex; extern uint256 hashGenesisBlock; extern uint256 hashGenesisMerkleRoot; extern unsigned int GENESIS_NONCE; +void SelectGenesisParams(bool fTestNetIn); +void SelectChainParams(bool fTestNetIn); extern CBlockIndex* pindexGenesisBlock; extern int nBestHeight; extern uint256 hashBestChain; diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 0a961bf..9f0fec0 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -143,7 +143,7 @@ static void PrintUsage() printf(" /onionservice=HOST.onion:PORT (advertise this node's Tor hidden service)\n"); printf("\n"); printf("Network:\n"); - printf(" /testnet (isolated test network datadir, port, and magic)\n"); + printf(" /testnet (isolated test network genesis, datadir, port, and magic)\n"); printf(" /port=N (P2P listen port, default 8433; testnet 18433)\n"); printf(" /socks=HOST:PORT (SOCKS5 proxy for Nostr, .btf relay, and onion dials)\n"); printf(" /tor[=HOST:PORT] (Tor mode; default SOCKS5 proxy is 127.0.0.1:9050)\n"); @@ -206,7 +206,8 @@ static void ParseStartupArguments(int argc, char* argv[]) if (arg(argc,argv,"/datadir") || arg(argc,argv,"-datadir")) strSetDataDir = argval2(argc, argv, "/datadir", "-datadir"); - SelectNetworkParams(arg(argc, argv, "/testnet") || arg(argc, argv, "-testnet")); + bool fUseTestNet = arg(argc, argv, "/testnet") || arg(argc, argv, "-testnet"); + SelectChainParams(fUseTestNet); if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug")) fDebug = true; diff --git a/src/readme.txt b/src/readme.txt index 5d62791..810e8b7 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -41,7 +41,7 @@ The resulting executable is src/bitflash.exe. Command-line options (in addition to the original ones) ------------------------------------------------------- - /testnet use isolated test network datadir, port 18433, and message magic + /testnet use isolated test network genesis, datadir, port 18433, and message magic /port=N listen on P2P port N (default 8433; testnet default 18433) /socks=HOST:PORT route outbound Nostr, .btf rendezvous, and direct onion peer diff --git a/src/selftest.cpp b/src/selftest.cpp index 30bd28e..1a72e3a 100644 --- a/src/selftest.cpp +++ b/src/selftest.cpp @@ -3085,7 +3085,7 @@ static int RunNetworkParamsSelfTest() std::string strOldDataDir = strSetDataDir; try { - SelectNetworkParams(false); + SelectChainParams(false); nFail += Check(!IsTestNet(), "starts on mainnet after explicit selection") ? 0 : 1; nFail += Check(ntohs(GetDefaultPort()) == MAINNET_PORT, "mainnet default P2P port is 8433") ? 0 : 1; @@ -3095,8 +3095,11 @@ static int RunNetworkParamsSelfTest() CMessageHeader mainHeader("ping", 0); char mainMagic[4]; memcpy(mainMagic, mainHeader.pchMessageStart, sizeof(mainMagic)); + uint256 mainGenesis = hashGenesisBlock; + uint256 mainMerkle = hashGenesisMerkleRoot; + unsigned int mainNonce = GENESIS_NONCE; - SelectNetworkParams(true); + SelectChainParams(true); nFail += Check(IsTestNet(), "testnet selection is sticky") ? 0 : 1; nFail += Check(ntohs(GetDefaultPort()) == TESTNET_PORT, "testnet default P2P port is 18433") ? 0 : 1; @@ -3106,6 +3109,14 @@ static int RunNetworkParamsSelfTest() CMessageHeader testHeader("ping", 0); nFail += Check(memcmp(mainMagic, testHeader.pchMessageStart, sizeof(mainMagic)) != 0, "testnet message magic differs from mainnet") ? 0 : 1; + nFail += Check(hashGenesisBlock != mainGenesis && + hashGenesisMerkleRoot != mainMerkle && + GENESIS_NONCE != mainNonce, + "testnet genesis constants differ from mainnet") ? 0 : 1; + nFail += Check(hashGenesisBlock == uint256("0xa9d11c6d697bcb7aea0653bc088b12dcf9f44e732df205162cf085fd9d9963eb") && + hashGenesisMerkleRoot == uint256("0x510b293105e3ff85b8d4c3920e357f85d1a86bf199eb4e7d11f04f380030ec5c") && + GENESIS_NONCE == 3199, + "testnet genesis constants are pinned") ? 0 : 1; std::string tmp; if (!MakeTempDir(tmp)) @@ -3115,9 +3126,9 @@ static int RunNetworkParamsSelfTest() else { strSetDataDir = tmp; - SelectNetworkParams(false); + SelectChainParams(false); std::string mainDir = GetAppDir(); - SelectNetworkParams(true); + SelectChainParams(true); std::string testDir = GetAppDir(); nFail += Check(mainDir == tmp, "mainnet uses the selected data directory") ? 0 : 1; @@ -3138,7 +3149,7 @@ static int RunNetworkParamsSelfTest() } strSetDataDir = strOldDataDir; - SelectNetworkParams(false); + SelectChainParams(false); printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED", nFail, nFail == 1 ? "" : "s"); -- cgit v1.2.3 From 3a128281cd8fec1c698346191c240a771f80d72c Mon Sep 17 00:00:00 2001 From: mischief Date: Thu, 20 Aug 2026 17:57:27 -0300 Subject: randomx: optional 2 MB large pages for the cache, dataset and scratchpads RandomX reads its 256 MB cache, 2 GB dataset and per-VM scratchpads at random, so most accesses miss the TLB on 4 KB pages. Requesting 2 MB pages recovers roughly a tenth of the hash rate. Each allocation tries large pages first and falls back to normal pages when the pool is not reserved, so nothing regresses when hugepages are unavailable. -nolargepages opts out, and the diagnostics report which allocations got large pages. --- src/main_gui.cpp | 7 ++++ src/net.cpp | 1 + src/randomx_pow.cpp | 106 ++++++++++++++++++++++++++++++++++++++++++++++------ src/randomx_pow.h | 7 ++++ 4 files changed, 110 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 0a961bf..a01bdee 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -127,6 +127,8 @@ static void PrintUsage() printf(" /stratumbridgeport=N (default 3333; listen on 127.0.0.1)\n"); printf(" /solomine\n"); printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n"); + printf(" /nolargepages (do not ask for 2 MB pages for the RandomX\n"); + printf(" cache, dataset and scratchpads)\n"); printf(" /checkblocks=N (blocks re-verified at startup, default 288, 0 = all)\n"); printf("\n"); printf("Pool operator announcement:\n"); @@ -230,6 +232,11 @@ static void ParseStartupArguments(int argc, char* argv[]) if (!strCheckBlocks.empty()) nCheckBlocksOnLoad = atoi(strCheckBlocks.c_str()); + // Must be read before LoadBlockIndex(), which is what first calls + // RandomXInit() and fixes how the cache is allocated. + if (arg(argc,argv,"/nolargepages") || arg(argc,argv,"-nolargepages")) + fRandomXLargePages = false; + // /genproclimit=N -- threads to hash with. 0 or absent means automatic, // which is every core but one. Named after Bitcoin's own option so it reads // familiarly to anyone who has run one of these before. diff --git a/src/net.cpp b/src/net.cpp index ecfd565..287a4bd 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -366,6 +366,7 @@ string GetDiagnosticsText() nMining, (RandomXFastReady() ? 2080 : 256) + 2 * nMining).c_str() : ", not mining"); + str += strprintf(" large pages %s\n", RandomXLargePagesStatus()); str += SockAccountingText(); diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp index 1d058d4..5872090 100644 --- a/src/randomx_pow.cpp +++ b/src/randomx_pow.cpp @@ -23,6 +23,39 @@ static std::mutex g_csInit; static bool g_fInit = false; static bool g_fFast = false; +// RandomX reads a 256 MB cache, a 2 GB dataset and a 2 MB scratchpad per VM +// at random, so most reads miss the TLB on 4 KB pages. 2 MB pages recover +// roughly a tenth of the hash rate. +// +// The pool must be reserved first (vm.nr_hugepages), and RandomX returns NULL +// instead of falling back, so each allocation below retries without the flag. +bool fRandomXLargePages = true; // cleared by -nolargepages +static bool g_fLargeCache = false; +static bool g_fLargeDataset = false; +static bool g_fLargeAny = false; + +static randomx_flags WithLargePages(randomx_flags f) +{ + return (randomx_flags)(f | RANDOMX_FLAG_LARGE_PAGES); +} + +// Ask for a cache in large pages, then plain pages. Sets fLargeOut to say +// which one answered. +static randomx_cache* AllocCache(randomx_flags flags, bool& fLargeOut) +{ + fLargeOut = false; + if (fRandomXLargePages) + { + randomx_cache* c = randomx_alloc_cache(WithLargePages(flags)); + if (c) + { + fLargeOut = true; + return c; + } + } + return randomx_alloc_cache(flags); +} + bool RandomXInit() { @@ -36,28 +69,56 @@ bool RandomXInit() // Detect the best flags for this CPU (JIT, hardware AES, Argon2). g_flags = randomx_get_flags(); - g_cache = randomx_alloc_cache(g_flags); + g_cache = AllocCache(g_flags, g_fLargeCache); if (!g_cache) { // Try without JIT as a fallback g_flags = RANDOMX_FLAG_DEFAULT; - g_cache = randomx_alloc_cache(g_flags); + g_cache = AllocCache(g_flags, g_fLargeCache); if (!g_cache) return error("RandomX: failed to allocate cache (256 MB)"); } randomx_init_cache(g_cache, RANDOMX_KEY, strlen(RANDOMX_KEY)); - // Verification VM in light mode (cache only) - g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL); + // Verification VM in light mode (cache only). The scratchpad is only 2 MB + // and this VM is created once, but it hashes every block the node ever + // validates, so it gets the same treatment. + bool fLargeVerify = false; + if (fRandomXLargePages) + { + g_vmVerify = randomx_create_vm(WithLargePages(g_flags), g_cache, NULL); + fLargeVerify = (g_vmVerify != NULL); + } + if (!g_vmVerify) + g_vmVerify = randomx_create_vm(g_flags, g_cache, NULL); if (!g_vmVerify) return error("RandomX: failed to create verification VM"); + g_fLargeAny = g_fLargeCache || fLargeVerify; g_fInit = true; - printf("RandomX: initialized (cache 256 MB, flags=%d)\n", (int)g_flags); + printf("RandomX: initialized (cache 256 MB, flags=%d, large pages: %s)\n", + (int)g_flags, RandomXLargePagesStatus()); + if (fRandomXLargePages && !g_fLargeCache) + printf("RandomX: large pages unavailable for the cache; " + "reserve them with sysctl vm.nr_hugepages=1280 for about 10%% more hash rate\n"); return true; } +const char* RandomXLargePagesStatus() +{ + if (!fRandomXLargePages) + return "off (-nolargepages)"; + if (g_fLargeCache && g_fLargeDataset) + return "cache + dataset"; + if (g_fLargeCache) + return "cache only"; + if (g_fLargeDataset) + return "dataset only"; + return g_fLargeAny ? "scratchpad only" : "unavailable"; +} + + // One dataset, once, no matter how many miners ask for it. // // Every miner thread calls this at startup. The g_fFast guard below is only @@ -87,8 +148,18 @@ bool RandomXInitDataset(int nThreads) if (g_fFast) return true; - randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); - g_dataset = randomx_alloc_dataset(fastFlags); + // Only RANDOMX_FLAG_LARGE_PAGES means anything to randomx_alloc_dataset; + // the rest of g_flags belongs to the VMs that read it. + if (fRandomXLargePages) + { + g_dataset = randomx_alloc_dataset(WithLargePages(RANDOMX_FLAG_DEFAULT)); + g_fLargeDataset = (g_dataset != NULL); + if (!g_dataset) + printf("RandomX: large pages unavailable for the 2 GB dataset; " + "reserve 1280 with sysctl vm.nr_hugepages for about 10%% more hash rate\n"); + } + if (!g_dataset) + g_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); if (!g_dataset) { printf("RandomX: not enough memory for the 2 GB dataset, staying in light mode\n"); @@ -142,13 +213,26 @@ void* RandomXCreateMinerVM() { if (!g_fInit && !RandomXInit()) return NULL; + + randomx_flags flags = g_flags; + randomx_cache* cache = g_cache; + randomx_dataset* dataset = NULL; if (g_fFast && g_dataset) { - randomx_flags fastFlags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); - return randomx_create_vm(fastFlags, NULL, g_dataset); + flags = (randomx_flags)(g_flags | RANDOMX_FLAG_FULL_MEM); + cache = NULL; + dataset = g_dataset; + } + + // Each thread gets its own 2 MB scratchpad, so the large-page pool can run + // dry partway through a fleet of miners. Threads that miss out still run. + if (fRandomXLargePages) + { + randomx_vm* vm = randomx_create_vm(WithLargePages(flags), cache, dataset); + if (vm) + return vm; } - // Light mode: own VM sharing the cache - return randomx_create_vm(g_flags, g_cache, NULL); + return randomx_create_vm(flags, cache, dataset); } diff --git a/src/randomx_pow.h b/src/randomx_pow.h index 0027571..355b708 100644 --- a/src/randomx_pow.h +++ b/src/randomx_pow.h @@ -41,4 +41,11 @@ uint256 RandomXHashWithVM(void* vm, const void* pHeader, size_t nSize); // True when the 2 GB dataset is ready (fast mode active). bool RandomXFastReady(); +// Ask for large (2 MB) pages for the cache, dataset and scratchpads. Set false +// by -nolargepages. Must be set before the first RandomXInit(). +extern bool fRandomXLargePages; + +// Which allocations actually got large pages, for the status display. +const char* RandomXLargePagesStatus(); + #endif -- cgit v1.2.3 From bfce24bc542927647ae4e5af68b71e3e56e1a291 Mon Sep 17 00:00:00 2001 From: bitflash Date: Thu, 20 Aug 2026 18:03:27 -0300 Subject: randomx: make the large-pages fallback hint OS-specific The reservation instruction printed when a large-page allocation falls back was Linux-only (sysctl vm.nr_hugepages). Route it through a small helper so a Windows build points at the "Lock pages in memory" privilege instead. --- src/randomx_pow.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/randomx_pow.cpp b/src/randomx_pow.cpp index 5872090..4454fd1 100644 --- a/src/randomx_pow.cpp +++ b/src/randomx_pow.cpp @@ -34,6 +34,17 @@ static bool g_fLargeCache = false; static bool g_fLargeDataset = false; static bool g_fLargeAny = false; +// How to reserve the large-page pool, for the hint printed when a large-page +// allocation falls back to normal pages. The mechanism is OS-specific. +static const char* LargePagesHint() +{ +#ifdef _WIN32 + return "enable the \"Lock pages in memory\" privilege and run elevated"; +#else + return "reserve them with: sysctl vm.nr_hugepages=1280"; +#endif +} + static randomx_flags WithLargePages(randomx_flags f) { return (randomx_flags)(f | RANDOMX_FLAG_LARGE_PAGES); @@ -99,8 +110,8 @@ bool RandomXInit() printf("RandomX: initialized (cache 256 MB, flags=%d, large pages: %s)\n", (int)g_flags, RandomXLargePagesStatus()); if (fRandomXLargePages && !g_fLargeCache) - printf("RandomX: large pages unavailable for the cache; " - "reserve them with sysctl vm.nr_hugepages=1280 for about 10%% more hash rate\n"); + printf("RandomX: large pages unavailable for the cache; %s " + "for about 10%% more hash rate\n", LargePagesHint()); return true; } @@ -155,8 +166,8 @@ bool RandomXInitDataset(int nThreads) g_dataset = randomx_alloc_dataset(WithLargePages(RANDOMX_FLAG_DEFAULT)); g_fLargeDataset = (g_dataset != NULL); if (!g_dataset) - printf("RandomX: large pages unavailable for the 2 GB dataset; " - "reserve 1280 with sysctl vm.nr_hugepages for about 10%% more hash rate\n"); + printf("RandomX: large pages unavailable for the 2 GB dataset; %s " + "for about 10%% more hash rate\n", LargePagesHint()); } if (!g_dataset) g_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); -- cgit v1.2.3