upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp561
1 files changed, 439 insertions, 122 deletions
diff --git a/src/main.cpp b/src/main.cpp
index f83164f..252911b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -87,6 +87,8 @@ CKey keyUser;
87string strSetDataDir; 87string strSetDataDir;
88int nDropMessagesTest = 0; 88int nDropMessagesTest = 0;
89bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test) 89bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test)
90bool fPoolNoAnnounce = false; // /noannounce: operator serves miners but never publishes pool announcements
91bool fPoolMineBlind = false; // /poolmineblind: issue jobs even when no peer has reported a height
90 92
91// Settings 93// Settings
92int fGenerateBitcoins; 94int fGenerateBitcoins;
@@ -102,6 +104,9 @@ CAddress addrIncoming;
102int nMineMode = MINE_RELAY; 104int nMineMode = MINE_RELAY;
103bool fMineModeFromCommandLine = false; 105bool fMineModeFromCommandLine = false;
104string strParticipantPool; // participant mode: pool .btf address 106string strParticipantPool; // participant mode: pool .btf address
107string strPayoutAddress; // participant: /payoutaddr override (mine without a wallet)
108string strPayoutFile; // participant: /payoutfile with one address per line, rotated per reconnect
109int nHashThreads = 0; // participant: /hashthreads override (0 = cores-1)
105string strPoolName = "Bitflash Pool"; 110string strPoolName = "Bitflash Pool";
106string strPoolDashboardUrl; 111string strPoolDashboardUrl;
107string strPoolStatusFile; 112string strPoolStatusFile;
@@ -163,12 +168,32 @@ int MinerThreadCount()
163{ 168{
164 if (nMinerThreads > 0) 169 if (nMinerThreads > 0)
165 return nMinerThreads; 170 return nMinerThreads;
166 unsigned int nCores = std::thread::hardware_concurrency(); 171 unsigned int nCores = AvailableCoreCount();
167 if (nCores <= 1) 172 if (nCores <= 1)
168 return 1; 173 return 1;
169 return (int)(nCores - 1); 174 return (int)(nCores - 1);
170} 175}
171 176
177// Hash threads one participant session runs.
178//
179// Explicit /hashthreads=N is honored exactly, including 1 -- a miner who says
180// one means one. Automatic leaves a core to the node, the Stratum I/O loop and
181// everything else the process still has to do, for the same reason
182// MinerThreadCount() does.
183//
184// Lives here, next to that function, because the two are easy to confuse and
185// StartMinerThreads() needs to be able to tell them apart: only ONE session
186// runs in participant mode, and this is what sizes it.
187int ParticipantHashThreadCount()
188{
189 if (nHashThreads > 0)
190 return nHashThreads;
191 int nWorkers = (int)AvailableCoreCount();
192 if (nWorkers < 2)
193 nWorkers = 2;
194 return nWorkers - 1;
195}
196
172static void SecureClearBytes(vector<unsigned char>& vch); 197static void SecureClearBytes(vector<unsigned char>& vch);
173static void SecureClearKeyingMaterial(CKeyingMaterial& vch); 198static void SecureClearKeyingMaterial(CKeyingMaterial& vch);
174 199
@@ -1551,7 +1576,7 @@ int CMerkleTx::GetBlocksToMaturity() const
1551{ 1576{
1552 if (!IsCoinBase()) 1577 if (!IsCoinBase())
1553 return 0; 1578 return 0;
1554 return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); 1579 return max(0, COINBASE_SPENDABLE_DEPTH - GetDepthInMainChain());
1555} 1580}
1556 1581
1557 1582
@@ -2819,9 +2844,10 @@ bool AlreadyHave(CTxDB& txdb, const CInv& inv)
2819 { 2844 {
2820 case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); 2845 case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash);
2821 case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); 2846 case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash);
2822 case MSG_REVIEW: return true;
2823 case MSG_PRODUCT: return mapProducts.count(inv.hash);
2824 } 2847 }
2848 // MSG_REVIEW / MSG_PRODUCT / MSG_TABLE are the 2009 marketplace inventory
2849 // types. Nothing announces or serves them any more (see ProcessMessage),
2850 // so they fall through to the default and are never requested.
2825 // Don't know what it is, just say we already got one 2851 // Don't know what it is, just say we already got one
2826 return true; 2852 return true;
2827} 2853}
@@ -2864,11 +2890,13 @@ bool ProcessMessages(CNode* pfrom)
2864 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); 2890 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin()));
2865 vRecv.erase(vRecv.begin(), pstart); 2891 vRecv.erase(vRecv.begin(), pstart);
2866 2892
2867 // Read the header from a copy first. The old path consumed the header, 2893 // Read the header from a copy first, and do not consume it until the
2868 // noticed the payload was incomplete, inserted the header back at the 2894 // whole body is here. The old path consumed the header, noticed the
2869 // front of the receive buffer, then slept inside the shared message 2895 // payload was incomplete, inserted the header back at the front of the
2870 // thread. A peer could announce a large payload and drip bytes forever, 2896 // receive buffer, then slept inside the shared message thread. A peer
2871 // making every pass pay for an O(n) insert and a 100 ms stall. 2897 // could announce a large payload and drip bytes forever, making every
2898 // pass pay for an O(n) insert and a 100 ms stall -- a cheap way for one
2899 // peer to slow the thread that serves all of them. (upstream #109)
2872 CMessageHeader hdr; 2900 CMessageHeader hdr;
2873 CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader), 2901 CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader),
2874 vRecv.nType, vRecv.nVersion); 2902 vRecv.nType, vRecv.nVersion);
@@ -2878,7 +2906,10 @@ bool ProcessMessages(CNode* pfrom)
2878 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); 2906 if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str());
2879 if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE) 2907 if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE)
2880 pfrom->fDisconnect = true; 2908 pfrom->fDisconnect = true;
2881 pfrom->nIncompleteMessageStart = 0; 2909 pfrom->nMsgWaitSince = 0;
2910 pfrom->nMsgWaitSize = 0;
2911 // The header was read from a copy, so it is still in vRecv. Skip it
2912 // explicitly or this loop never advances.
2882 vRecv.ignore(sizeof(CMessageHeader)); 2913 vRecv.ignore(sizeof(CMessageHeader));
2883 continue; 2914 continue;
2884 } 2915 }
@@ -2889,30 +2920,63 @@ bool ProcessMessages(CNode* pfrom)
2889 unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader); 2920 unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader);
2890 if (nMessageSize > nPayloadAvailable) 2921 if (nMessageSize > nPayloadAvailable)
2891 { 2922 {
2892 if (pfrom->nIncompleteMessageStart == 0 || 2923 // Wait for the rest of the message. The header stays in vRecv.
2893 pfrom->nIncompleteMessageSize != nMessageSize || 2924 //
2894 pfrom->strIncompleteMessageCommand != strCommand) 2925 // This is the mechanism the original note asked for: "need a
2926 // mechanism to give up waiting for overlong message size error".
2927 // Without one the wait is unbounded, and unbounded here means
2928 // permanent. The header stays at the front of vRecv and the loop
2929 // breaks; if the body never completes, every later pass re-reads
2930 // that same header and breaks in the same place. The peer is never
2931 // parsed again -- no blocks, no inv, not even its version message,
2932 // so it never reports a height either, which is why a wedged node
2933 // has peers and GetPeerMedianHeight() still returns -1.
2934 //
2935 // Nothing rescues it. A header could claim up to 256 MB and still
2936 // pass CMessageHeader::IsValid() -- now capped at
2937 // MAX_PROTOCOL_MESSAGE_SIZE -- and any byte that arrives
2938 // refreshes nLastRecv, so the inactivity timeout that drops a
2939 // silent peer never fires on one that is merely unreadable. The
2940 // socket stays ESTABLISHED and select() keeps offering it.
2941 // Measured on a wedged pool: this branch ran 2014 times in the
2942 // deaf window, against 607 in the healthy window before it.
2943 if (LogAcceptsCategory("net")) printf("MESSAGE-BREAK 2\n");
2944 //
2945 // Time the STALL, not the wait. A big message on a slow link is
2946 // still arriving, and dropping that peer would be a bug of our own
2947 // making; what must be caught is a buffer that has stopped growing
2948 // while we go on expecting more. So the clock restarts whenever
2949 // even one more byte has landed since the last look.
2950 //
2951 // Upstream #109 times this differently: it restarts the clock only
2952 // when the (size, command) pair changes, which drops a peer sending
2953 // one big message slowly but honestly. Progress is the better test,
2954 // so that half is kept as it was.
2955 int64 nNow = GetTime();
2956 if (pfrom->nMsgWaitSince == 0 || nPayloadAvailable > pfrom->nMsgWaitSize)
2895 { 2957 {
2896 pfrom->nIncompleteMessageStart = GetTime(); 2958 pfrom->nMsgWaitSince = nNow;
2897 pfrom->nIncompleteMessageSize = nMessageSize; 2959 pfrom->nMsgWaitSize = nPayloadAvailable;
2898 pfrom->strIncompleteMessageCommand = strCommand;
2899 } 2960 }
2900 else if (GetTime() - pfrom->nIncompleteMessageStart > 2961 else if (nNow - pfrom->nMsgWaitSince > BTF_MSG_WAIT_MAX_SECS)
2901 BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS)
2902 { 2962 {
2903 LogPrint("net", 2963 // Deliberately not behind a debug category. This is the node
2904 "disconnecting %s: incomplete %s message, %u of %u bytes after %d seconds\n", 2964 // going deaf to a peer, which is the thing nobody could see.
2905 pfrom->addr.ToString().c_str(), strCommand.c_str(), 2965 printf("peer %s announced a %u byte '%s' and has sent nothing more "
2906 nPayloadAvailable, nMessageSize, 2966 "for %d s (%d of %u payload bytes) -- dropping, this peer is unreadable\n",
2907 BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS); 2967 pfrom->addr.ToString().c_str(), nMessageSize,
2968 strCommand.c_str(), (int)(nNow - pfrom->nMsgWaitSince),
2969 (int)nPayloadAvailable, nMessageSize);
2908 pfrom->fDisconnect = true; 2970 pfrom->fDisconnect = true;
2909 } 2971 }
2972 // No re-insert, no Sleep(): the header was never consumed, so there
2973 // is nothing to put back, and sleeping here stalls every peer.
2910 break; 2974 break;
2911 } 2975 }
2912 pfrom->nIncompleteMessageStart = 0;
2913 pfrom->nIncompleteMessageSize = 0;
2914 pfrom->strIncompleteMessageCommand.clear();
2915 vRecv.ignore(sizeof(CMessageHeader)); 2976 vRecv.ignore(sizeof(CMessageHeader));
2977 // The body arrived, so this peer is readable again.
2978 pfrom->nMsgWaitSince = 0;
2979 pfrom->nMsgWaitSize = 0;
2916 2980
2917 // Copy message to its own buffer 2981 // Copy message to its own buffer
2918 CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); 2982 CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion);
@@ -2941,7 +3005,6 @@ bool ProcessMessages(CNode* pfrom)
2941 3005
2942bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) 3006bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
2943{ 3007{
2944 static map<unsigned int, vector<unsigned char> > mapReuseKey;
2945 if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), (int)vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); } 3008 if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), (int)vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); }
2946 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) 3009 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0)
2947 { 3010 {
@@ -3180,22 +3243,18 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3180 } 3243 }
3181 3244
3182 3245
3183 else if (strCommand == "review") 3246 // "review" used to be handled here. It took a CReview off the wire from an
3184 { 3247 // unauthenticated peer, checked a signature the sender had made with a key
3185 CDataStream vMsg(vRecv); 3248 // of its own choosing -- which proves possession of that key and authorises
3186 CReview review; 3249 // nothing -- and then appended the message to reviews.dat by reading the
3187 vRecv >> review; 3250 // whole vector for the target hash, pushing one entry, and writing all of
3188 3251 // it back. That is quadratic disk for a stream an attacker controls, on a
3189 CInv inv(MSG_REVIEW, review.GetHash()); 3252 // record whose mapValue is an unbounded map<string,string> read straight
3190 pfrom->AddInventoryKnown(inv); 3253 // from the socket, followed by AddAtomsAndPropagate walking a link graph
3191 3254 // the same peer supplied, and finally RelayMessage forwarding it to every
3192 if (review.AcceptReview()) 3255 // other node. Unbounded remote disk growth with network amplification and
3193 { 3256 // no authorisation anywhere in the path. The marketplace it belonged to has
3194 // Relay the original message as-is in case it's a higher version than we know how to parse 3257 // not existed since 2009; the handler is gone rather than bounded.
3195 RelayMessage(inv, vMsg);
3196 mapAlreadyAskedFor.erase(inv);
3197 }
3198 }
3199 3258
3200 3259
3201 else if (strCommand == "block") 3260 else if (strCommand == "block")
@@ -3210,41 +3269,46 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3210 pfrom->AddInventoryKnown(inv); 3269 pfrom->AddInventoryKnown(inv);
3211 3270
3212 if (ProcessBlock(pfrom, pblock.release())) 3271 if (ProcessBlock(pfrom, pblock.release()))
3272 {
3213 mapAlreadyAskedFor.erase(inv); 3273 mapAlreadyAskedFor.erase(inv);
3214 }
3215
3216 3274
3217 else if (strCommand == "checkorder" || strCommand == "submitorder") 3275 // A peer that just handed us a block is demonstrably at least at
3218 { 3276 // that block's height. Without this, nStartingHeight is a snapshot
3219 // Legacy Bitcoin 0.1 marketplace messages are not part of Bitflash's 3277 // taken once during the version handshake and never touched again,
3220 // supported protocol. They used to let a remote peer make this wallet 3278 // so on a long-lived connection it is the peer's height from
3221 // reserve a key or import/broadcast a wallet transaction. Keep the 3279 // however many hours ago it happened to connect. The pool's
3222 // command names explicit so old peers get a quiet no-op instead of a 3280 // behind-the-network check compares against the median of those
3223 // wallet side effect. 3281 // numbers, so as connections age it drifts toward "everyone is
3224 if (LogAcceptsCategory("net")) 3282 // behind us" and quietly stops firing -- the opposite of its job.
3225 printf("ProcessMessage(%s) : disabled legacy order message\n", strCommand.c_str()); 3283 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(inv.hash);
3226 vRecv.clear(); 3284 if (mi != mapBlockIndex.end() && mi->second)
3285 pfrom->nStartingHeight = max(pfrom->nStartingHeight, mi->second->nHeight);
3286 }
3227 } 3287 }
3228 3288
3229 3289
3230 else if (strCommand == "reply") 3290 // "checkorder", "submitorder" and "reply" -- the 2009 buy-from-a-merchant
3231 { 3291 // exchange -- used to be handled here. All three are gone.
3232 uint256 hashReply; 3292 //
3233 vRecv >> hashReply; 3293 // "checkorder" called GenerateNewKey() for any peer that asked, which is
3234 3294 // AddKey() and a wallet.dat write, and handed the pubkey back. The comment
3235 CRequestTracker tracker; 3295 // said "keep giving the same key to the same ip until they use it", and on
3236 CRITICAL_BLOCK(pfrom->cs_mapRequests) 3296 // a 2009 network of routable IPs that held. It does not hold here: every
3237 { 3297 // inbound peer arrives through a rendezvous tunnel and is tagged with
3238 map<uint256, CRequestTracker>::iterator mi = pfrom->mapRequests.find(hashReply); 3298 // BtfMarkerAddr() over three random bytes (net.cpp), so addr.ip is freshly
3239 if (mi != pfrom->mapRequests.end()) 3299 // random per connection and the cache never hits. Reconnect in a loop and
3240 { 3300 // the node mints a key per connection -- unbounded wallet growth, unbounded
3241 tracker = (*mi).second; 3301 // growth of the static map that held them, and every one of those keys
3242 pfrom->mapRequests.erase(mi); 3302 // missing from every backup taken beforehand, which for this pre-BIP32
3243 } 3303 // wallet is how coins are lost.
3244 } 3304 //
3245 if (!tracker.IsNull()) 3305 // "submitorder" took a CWalletTx from an unauthenticated peer and, if it
3246 tracker.fn(tracker.param1, vRecv); 3306 // was merely a valid transaction, AddToWallet()'d it. Valid is not the same
3247 } 3307 // as ours: any peer could write chosen transactions into wallet.dat.
3308 //
3309 // "reply" dispatched to a CRequestTracker callback. Nothing in this tree
3310 // has ever called PushRequest, so mapRequests was permanently empty and the
3311 // handler could only ever be a parser for hostile input.
3248 3312
3249 3313
3250 else if (strCommand == "ping") 3314 else if (strCommand == "ping")
@@ -3502,6 +3566,102 @@ static bool StratumRecvLine(SOCKET s, std::string& buf,
3502// How long to wait for a mining.notify after authorize before giving up 3566// How long to wait for a mining.notify after authorize before giving up
3503static const int64 STUCK_NO_JOB_TIMEOUT_SECS = 120; 3567static const int64 STUCK_NO_JOB_TIMEOUT_SECS = 120;
3504 3568
3569// ------------------------------------------------------------------
3570// Multi-threaded pool worker. The original loop hashed a single nonce
3571// per blocking 50ms socket poll, capping one instance at ~20 H/s no
3572// matter how many cores the machine has. The socket and job bookkeeping
3573// stay on the calling thread; N hash threads grind nonce ranges on their
3574// own RandomX VMs (all sharing the one ~2 GB dataset) and drop found
3575// shares on a queue the I/O thread drains and submits.
3576// ------------------------------------------------------------------
3577struct ParticipantJobShared
3578{
3579 std::mutex mtx;
3580 unsigned char header[80];
3581 uint256 target;
3582 std::string jobId;
3583 uint64 jobSeq;
3584 bool haveJob;
3585 bool stop;
3586 ParticipantJobShared() : jobSeq(0), haveJob(false), stop(false)
3587 {
3588 memset(header, 0, sizeof(header));
3589 }
3590};
3591
3592struct ParticipantShareHit
3593{
3594 uint64 jobSeq;
3595 std::string jobId;
3596 unsigned int nonce;
3597};
3598
3599static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorkers,
3600 std::vector<ParticipantShareHit>* hits,
3601 std::mutex* hitsMtx)
3602{
3603 void* vm = RandomXCreateMinerVM();
3604 if (!vm)
3605 {
3606 LogPrint("worker", "[worker] hash thread %d: RandomX VM creation failed\n", idx);
3607 return;
3608 }
3609
3610 unsigned char header[80];
3611 uint256 target;
3612 std::string jobId;
3613 uint64 mySeq = 0;
3614 bool active = false;
3615 unsigned int nNonce = 0;
3616 uint64 localHashes = 0;
3617
3618 while (!fShutdown)
3619 {
3620 {
3621 std::lock_guard<std::mutex> lk(sh->mtx);
3622 if (sh->stop)
3623 break;
3624 if (sh->haveJob && sh->jobSeq != mySeq)
3625 {
3626 memcpy(header, sh->header, 80);
3627 target = sh->target;
3628 jobId = sh->jobId;
3629 mySeq = sh->jobSeq;
3630 nNonce = (unsigned int)(idx + 1);
3631 active = true;
3632 }
3633 }
3634 if (!active)
3635 {
3636 Sleep(50);
3637 continue;
3638 }
3639
3640 memcpy(header + 76, &nNonce, 4);
3641 uint256 hash = RandomXHashWithVM(vm, header, 80);
3642 localHashes++;
3643 if ((localHashes & 0x3ff) == 0)
3644 gParticipantHashes.fetch_add(0x400);
3645
3646 if (hash <= target)
3647 {
3648 LogPrint("worker", "[worker] share found! nonce=%u job=%s (thread %d)\n",
3649 nNonce, jobId.c_str(), idx);
3650 std::lock_guard<std::mutex> lk(*hitsMtx);
3651 hits->push_back(ParticipantShareHit{mySeq, jobId, nNonce});
3652 }
3653
3654 unsigned int next = nNonce + (unsigned int)nWorkers;
3655 if (next < nNonce) // wrapped past 2^32: range exhausted, wait for next job
3656 active = false;
3657 else
3658 nNonce = next;
3659 }
3660
3661 gParticipantHashes.fetch_add(localHashes & 0x3ff);
3662 RandomXDestroyMinerVM(vm);
3663}
3664
3505static bool PoolParticipantMiner() 3665static bool PoolParticipantMiner()
3506{ 3666{
3507 // ------------------------------------------------------------------ 3667 // ------------------------------------------------------------------
@@ -3516,6 +3676,51 @@ static bool PoolParticipantMiner()
3516 LogPrint("worker", "[worker] connecting to pool %s\n", strParticipantPool.c_str()); 3676 LogPrint("worker", "[worker] connecting to pool %s\n", strParticipantPool.c_str());
3517 SetParticipantMiningStatus("resolving pool address"); 3677 SetParticipantMiningStatus("resolving pool address");
3518 3678
3679 // ------------------------------------------------------------------
3680 // Steps 1-2: connect to the pool. Two address forms:
3681 // tcp:HOST:PORT -- plain TCP straight to the pool's stratum port
3682 // (e.g. our own operator node on this machine),
3683 // no .btf resolution or rendezvous tunnel involved
3684 // <name>.btf -- resolve via Nostr, dial through a rendezvous relay
3685 // ------------------------------------------------------------------
3686 btf_socket_t s = INVALID_SOCKET;
3687 if (strParticipantPool.compare(0, 4, "tcp:") == 0)
3688 {
3689 std::string hp = strParticipantPool.substr(4);
3690 size_t colon = hp.rfind(':');
3691 if (colon == std::string::npos) {
3692 LogPrint("worker", "[worker] bad tcp pool address '%s' (want tcp:host:port)\n",
3693 strParticipantPool.c_str());
3694 SetParticipantMiningStatus("bad pool address");
3695 return false;
3696 }
3697 std::string host = hp.substr(0, colon);
3698 int port = atoi(hp.substr(colon + 1).c_str());
3699 unsigned long ip = inet_addr(host.c_str());
3700 if (port <= 0 || port > 65535 || ip == INADDR_NONE) {
3701 LogPrint("worker", "[worker] bad tcp pool endpoint '%s'\n", hp.c_str());
3702 SetParticipantMiningStatus("bad pool endpoint");
3703 return false;
3704 }
3705 SetParticipantMiningStatus("connecting to pool (direct tcp)");
3706 struct sockaddr_in sa;
3707 memset(&sa, 0, sizeof(sa));
3708 sa.sin_family = AF_INET;
3709 sa.sin_port = htons((unsigned short)port);
3710 sa.sin_addr.s_addr = ip;
3711 SOCKET cs = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
3712 if (cs == INVALID_SOCKET || connect(cs, (struct sockaddr*)&sa, sizeof(sa)) == SOCKET_ERROR) {
3713 LogPrint("worker", "[worker] direct connect to %s FAILED\n", hp.c_str());
3714 SetParticipantMiningStatus("pool connect failed");
3715 if (cs != INVALID_SOCKET) closesocket(cs);
3716 return false;
3717 }
3718 LogPrint("worker", "[worker] connected directly to pool %s\n", hp.c_str());
3719 s = (btf_socket_t)cs;
3720 goto connected;
3721 }
3722
3723 {
3519 unsigned char target_pubkey[32]; 3724 unsigned char target_pubkey[32];
3520 if (!btf::ParseAddress(strParticipantPool, target_pubkey)) { 3725 if (!btf::ParseAddress(strParticipantPool, target_pubkey)) {
3521 LogPrint("worker", "[worker] invalid .btf address: %s\n", strParticipantPool.c_str()); 3726 LogPrint("worker", "[worker] invalid .btf address: %s\n", strParticipantPool.c_str());
@@ -3550,11 +3755,11 @@ static bool PoolParticipantMiner()
3550 } 3755 }
3551 3756
3552 // ------------------------------------------------------------------ 3757 // ------------------------------------------------------------------
3553 // Step 2: open encrypted tunnel to pool 3758 // Step 2b: open encrypted tunnel to pool
3554 // ------------------------------------------------------------------ 3759 // ------------------------------------------------------------------
3555 SetParticipantMiningStatus("opening tunnel"); 3760 SetParticipantMiningStatus("opening tunnel");
3556 btf_socket_t s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, 3761 s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port,
3557 target_pubkey, service_enc_pub); 3762 target_pubkey, service_enc_pub);
3558 if (s == INVALID_SOCKET) { 3763 if (s == INVALID_SOCKET) {
3559 LogPrint("worker", "[worker] tunnel connect FAILED via %s" 3764 LogPrint("worker", "[worker] tunnel connect FAILED via %s"
3560 " (relay unreachable or pool not listening)\n", 3765 " (relay unreachable or pool not listening)\n",
@@ -3564,11 +3769,48 @@ static bool PoolParticipantMiner()
3564 } 3769 }
3565 LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); 3770 LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str());
3566 SetParticipantMiningStatus("tunnel open"); 3771 SetParticipantMiningStatus("tunnel open");
3772 }
3773
3774connected:
3567 3775
3568 // ------------------------------------------------------------------ 3776 // ------------------------------------------------------------------
3569 // Step 3: determine our payout address (mining username) 3777 // Step 3: determine our payout address (mining username). /payoutaddr
3778 // wins -- it lets a machine mine to an address whose keys live
3779 // elsewhere, so no wallet file is needed on the mining machine.
3780 // /payoutfile rotates through a list of such addresses: one is picked
3781 // per (re)connection, and the session self-limits (see rotation break
3782 // in the main loop) so consecutive rounds land on fresh addresses.
3570 // ------------------------------------------------------------------ 3783 // ------------------------------------------------------------------
3571 std::string username; 3784 static std::vector<std::string> vPayoutAddrs;
3785 static size_t nPayoutRot = 0;
3786 if (!strPayoutFile.empty() && vPayoutAddrs.empty())
3787 {
3788 FILE* f = fopen(strPayoutFile.c_str(), "r");
3789 if (f)
3790 {
3791 char line[256];
3792 while (fgets(line, sizeof(line), f))
3793 {
3794 std::string a = line;
3795 while (!a.empty() && (a.back() == '\n' || a.back() == '\r' ||
3796 a.back() == ' ' || a.back() == '\t'))
3797 a.pop_back();
3798 if (!a.empty())
3799 vPayoutAddrs.push_back(a);
3800 }
3801 fclose(f);
3802 LogPrint("worker", "[worker] loaded %zu payout addresses from %s\n",
3803 vPayoutAddrs.size(), strPayoutFile.c_str());
3804 }
3805 else
3806 LogPrint("worker", "[worker] cannot open payout file %s\n",
3807 strPayoutFile.c_str());
3808 }
3809
3810 std::string username = strPayoutAddress;
3811 if (username.empty() && !vPayoutAddrs.empty())
3812 username = vPayoutAddrs[nPayoutRot++ % vPayoutAddrs.size()];
3813 if (username.empty())
3572 { 3814 {
3573 std::vector<unsigned char> vchPubKey; 3815 std::vector<unsigned char> vchPubKey;
3574 if (CWalletDB("r").ReadDefaultKey(vchPubKey)) 3816 if (CWalletDB("r").ReadDefaultKey(vchPubKey))
@@ -3606,16 +3848,14 @@ static bool PoolParticipantMiner()
3606 // ------------------------------------------------------------------ 3848 // ------------------------------------------------------------------
3607 if (!RandomXFastReady()) { 3849 if (!RandomXFastReady()) {
3608 SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); 3850 SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)");
3609 unsigned int nThreads = std::thread::hardware_concurrency(); 3851 // Fill with the cores we are permitted to use, not the ones the host
3852 // happens to have. More threads than the cpuset allows does not build
3853 // the dataset any faster -- the CPU budget is the same either way --
3854 // it just oversubscribes, and this runs alongside the pool node.
3855 unsigned int nThreads = AvailableCoreCount();
3610 RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); 3856 RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1);
3611 } 3857 }
3612 void* rxvm = RandomXCreateMinerVM(); 3858 LogPrint("worker", "[worker] RandomX dataset ready (%s)\n",
3613 if (!rxvm) {
3614 LogPrint("worker", "[worker] failed to create RandomX VM\n");
3615 SetParticipantMiningStatus("RandomX init failed");
3616 closesocket(s); return false;
3617 }
3618 LogPrint("worker", "[worker] RandomX VM ready (%s)\n",
3619 RandomXFastReady() ? "fast 2GB" : "light 256MB"); 3859 RandomXFastReady() ? "fast 2GB" : "light 256MB");
3620 3860
3621 // ------------------------------------------------------------------ 3861 // ------------------------------------------------------------------
@@ -3633,7 +3873,6 @@ static bool PoolParticipantMiner()
3633 {"params",json::array()}})) { 3873 {"params",json::array()}})) {
3634 LogPrint("worker", "[worker] subscribe send FAILED -- pool dropped connection\n"); 3874 LogPrint("worker", "[worker] subscribe send FAILED -- pool dropped connection\n");
3635 SetParticipantMiningStatus("subscribe send failed"); 3875 SetParticipantMiningStatus("subscribe send failed");
3636 RandomXDestroyMinerVM(rxvm);
3637 closesocket(s); return false; 3876 closesocket(s); return false;
3638 } 3877 }
3639 3878
@@ -3643,11 +3882,31 @@ static bool PoolParticipantMiner()
3643 {"params",json::array({username,"x"})}})) { 3882 {"params",json::array({username,"x"})}})) {
3644 LogPrint("worker", "[worker] authorize send FAILED -- pool dropped connection\n"); 3883 LogPrint("worker", "[worker] authorize send FAILED -- pool dropped connection\n");
3645 SetParticipantMiningStatus("authorize send failed"); 3884 SetParticipantMiningStatus("authorize send failed");
3646 RandomXDestroyMinerVM(rxvm);
3647 closesocket(s); return false; 3885 closesocket(s); return false;
3648 } 3886 }
3649 3887
3650 // ------------------------------------------------------------------ 3888 // ------------------------------------------------------------------
3889 // Step 6: spawn hash threads (each with its own RandomX VM on the
3890 // shared dataset), then run the I/O loop below: receive pool messages,
3891 // publish jobs to the threads, submit the shares they find.
3892 // ------------------------------------------------------------------
3893 // One count, decided in one place (main.cpp:MinerThreadCount's neighbour),
3894 // so that what StartMinerThreads() reports at startup and what actually
3895 // hashes here can never drift apart. Explicit /hashthreads=N is honored
3896 // exactly, including 1; automatic leaves one core for node + I/O threads.
3897 int nWorkers = ParticipantHashThreadCount();
3898 ParticipantJobShared jobShared;
3899 std::vector<ParticipantShareHit> shareHits;
3900 std::mutex shareHitsMtx;
3901 std::vector<std::thread> hashThreads;
3902 for (int i = 0; i < nWorkers; i++)
3903 hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers,
3904 &shareHits, &shareHitsMtx);
3905 LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n",
3906 nWorkers);
3907 const uint64 sessionStartHashes = gParticipantHashes.load();
3908
3909 // ------------------------------------------------------------------
3651 // Step 6: main loop -- receive messages, hash, submit shares 3910 // Step 6: main loop -- receive messages, hash, submit shares
3652 // ------------------------------------------------------------------ 3911 // ------------------------------------------------------------------
3653 std::string jobId; 3912 std::string jobId;
@@ -3664,6 +3923,8 @@ static bool PoolParticipantMiner()
3664 uint64 sessionSharesSent = 0; // this connection only 3923 uint64 sessionSharesSent = 0; // this connection only
3665 uint64 sessionSharesAccepted = 0; 3924 uint64 sessionSharesAccepted = 0;
3666 int64 sampleStart = GetTime(); 3925 int64 sampleStart = GetTime();
3926 int64 connStart = GetTime();
3927 const int64 PAYOUT_ROTATE_SECS = 300; // reconnect with next /payoutfile address
3667 3928
3668 SetParticipantMiningStatus("waiting for pool response"); 3929 SetParticipantMiningStatus("waiting for pool response");
3669 3930
@@ -3771,6 +4032,14 @@ static bool PoolParticipantMiner()
3771 memcpy(&target, tgtBytes.data(), 32); 4032 memcpy(&target, tgtBytes.data(), 32);
3772 haveJob = true; 4033 haveJob = true;
3773 nNonce = 1; 4034 nNonce = 1;
4035 {
4036 std::lock_guard<std::mutex> lk(jobShared.mtx);
4037 memcpy(jobShared.header, header, 80);
4038 jobShared.target = target;
4039 jobShared.jobId = jobId;
4040 jobShared.jobSeq++;
4041 jobShared.haveJob = true;
4042 }
3774 LogPrint("worker", "[pool->worker] new job %s received\n", 4043 LogPrint("worker", "[pool->worker] new job %s received\n",
3775 jobId.c_str()); 4044 jobId.c_str());
3776 SetParticipantMiningStatus("hashing job " + jobId); 4045 SetParticipantMiningStatus("hashing job " + jobId);
@@ -3826,46 +4095,71 @@ static bool PoolParticipantMiner()
3826 } 4095 }
3827 } 4096 }
3828 4097
4098 // -- Payout address rotation: end this session cleanly so the outer
4099 // loop reconnects and authorizes the next /payoutfile address --
4100 if (!vPayoutAddrs.empty() && GetTime() - connStart > PAYOUT_ROTATE_SECS)
4101 {
4102 LogPrint("worker", "[worker] payout rotation interval reached"
4103 " -- reconnecting with next address\n");
4104 break;
4105 }
4106
3829 if (!haveJob) { Sleep(100); continue; } 4107 if (!haveJob) { Sleep(100); continue; }
3830 4108
3831 // -- Hash one nonce -- 4109 // -- Hash rate accounting (hashing happens on the worker threads) --
3832 memcpy(header+76, &nNonce, 4); 4110 hashesSinceSample = gParticipantHashes.load() - sessionStartHashes;
3833 uint256 hash = RandomXHashWithVM(rxvm, header, 80); 4111 UpdateParticipantHashRate(hashesSinceSample, sampleStart);
3834 hashesSinceSample++;
3835 gParticipantHashes.fetch_add(1);
3836 if ((hashesSinceSample & 0x3ff) == 0)
3837 UpdateParticipantHashRate(hashesSinceSample, sampleStart);
3838
3839 if (hash <= target) {
3840 LogPrint("worker", "[worker] share found! nonce=%u job=%s difficulty=%.4f\n",
3841 nNonce, jobId.c_str(), currentDifficulty);
3842 SetParticipantMiningStatus("share found; submitting");
3843 int submitId = msgId++;
3844 pendingSubmitIds.insert(submitId);
3845 gParticipantSharesSent.fetch_add(1);
3846 sessionSharesSent++;
3847 json submit = {{"id",submitId},{"method","mining.submit"},
3848 {"params",json::array({username, jobId, ToHexStr(&nNonce,4)})}};
3849 if (!StratumSendLine(s, submit)) {
3850 LogPrint("worker", "[worker] share submit send FAILED"
3851 " -- pool connection lost\n");
3852 break;
3853 }
3854 }
3855 4112
3856 nNonce++; 4113 // -- Submit any shares the hash threads found --
3857 if (nNonce == 0) { 4114 bool submitFailed = false;
3858 LogPrint("worker", "[worker] nonce space exhausted for job %s" 4115 {
3859 " -- waiting for new job\n", jobId.c_str()); 4116 std::vector<ParticipantShareHit> pending;
3860 haveJob = false; 4117 {
4118 std::lock_guard<std::mutex> lk(shareHitsMtx);
4119 pending.swap(shareHits);
4120 }
4121 for (ParticipantShareHit& hit : pending)
4122 {
4123 if (hit.jobId != jobId)
4124 {
4125 LogPrint("worker", "[worker] dropping stale share for old job %s\n",
4126 hit.jobId.c_str());
4127 continue;
4128 }
4129 LogPrint("worker", "[worker] share found! nonce=%u job=%s difficulty=%.4f\n",
4130 hit.nonce, hit.jobId.c_str(), currentDifficulty);
4131 SetParticipantMiningStatus("share found; submitting");
4132 int submitId = msgId++;
4133 pendingSubmitIds.insert(submitId);
4134 gParticipantSharesSent.fetch_add(1);
4135 sessionSharesSent++;
4136 json submit = {{"id",submitId},{"method","mining.submit"},
4137 {"params",json::array({username, hit.jobId,
4138 ToHexStr(&hit.nonce,4)})}};
4139 if (!StratumSendLine(s, submit)) {
4140 LogPrint("worker", "[worker] share submit send FAILED"
4141 " -- pool connection lost\n");
4142 submitFailed = true;
4143 break;
4144 }
4145 }
3861 } 4146 }
4147 if (submitFailed)
4148 break;
3862 } 4149 }
3863 4150
3864 // ------------------------------------------------------------------ 4151 // ------------------------------------------------------------------
3865 // Cleanup 4152 // Cleanup: stop the hash threads, then drop the pool connection
3866 // ------------------------------------------------------------------ 4153 // ------------------------------------------------------------------
3867 RandomXDestroyMinerVM(rxvm); 4154 {
4155 std::lock_guard<std::mutex> lk(jobShared.mtx);
4156 jobShared.stop = true;
4157 jobShared.haveJob = false;
4158 }
4159 for (std::thread& t : hashThreads)
4160 t.join();
3868 closesocket(s); 4161 closesocket(s);
4162 hashesSinceSample = gParticipantHashes.load() - sessionStartHashes;
3869 UpdateParticipantHashRate(hashesSinceSample, sampleStart); 4163 UpdateParticipantHashRate(hashesSinceSample, sampleStart);
3870 SetParticipantMiningStatus("disconnected"); 4164 SetParticipantMiningStatus("disconnected");
3871 LogPrint("worker", "[worker] disconnected from pool" 4165 LogPrint("worker", "[worker] disconnected from pool"
@@ -4090,7 +4384,7 @@ bool BitcoinMiner(int nThreadId)
4090 // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're 4384 // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're
4091 // not stuck mining in the much slower light/cache-only mode forever. 4385 // not stuck mining in the much slower light/cache-only mode forever.
4092 if (!RandomXFastReady()) { 4386 if (!RandomXFastReady()) {
4093 unsigned int nThreads = std::thread::hardware_concurrency(); 4387 unsigned int nThreads = AvailableCoreCount();
4094 RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); 4388 RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1);
4095 } 4389 }
4096 4390
@@ -4177,7 +4471,19 @@ bool BitcoinMiner(int nThreadId)
4177 vector<char> vfAlreadyAdded(mapTransactions.size()); 4471 vector<char> vfAlreadyAdded(mapTransactions.size());
4178 bool fFoundSomething = true; 4472 bool fFoundSomething = true;
4179 unsigned int nBlockSize = 0; 4473 unsigned int nBlockSize = 0;
4180 while (fFoundSomething && nBlockSize < MAX_SIZE/2) 4474 // Assembly must stop below the consensus cap that CheckBlock()
4475 // enforces, or we mine a block our own node then rejects.
4476 //
4477 // This used to read MAX_SIZE/2 (16 MB), which was consistent while
4478 // CheckBlock() also tested against MAX_SIZE (32 MB). Adding
4479 // MAX_BLOCK_SIZE inverted it: assembly could fill 16 MB and
4480 // validation would refuse anything over 1 MB, so a full mempool
4481 // meant solving a block and throwing it away -- with this pool at
4482 // ~90% of hashrate, that is the network stalling, not just us.
4483 //
4484 // Half the cap, keeping the original margin: the test runs before
4485 // a transaction is added, so the loop can overshoot by one tx.
4486 while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE/2)
4181 { 4487 {
4182 fFoundSomething = false; 4488 fFoundSomething = false;
4183 unsigned int n = 0; 4489 unsigned int n = 0;
@@ -4778,7 +5084,18 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew)
4778 { 5084 {
4779 string strError; 5085 string strError;
4780 if (nValue + nFeeRequired > GetBalance()) 5086 if (nValue + nFeeRequired > GetBalance())
4781 strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); 5087 // Not an oversized transaction -- this branch is simply "the
5088 // spendable balance is too small". Saying otherwise (with a fee
5089 // of 0.00, because CreateTransaction failed before setting one)
5090 // sends anyone reading the log after a failed payout hunting a
5091 // transaction-size problem that does not exist.
5092 strError = strprintf("Error: insufficient spendable balance:"
5093 " need %s (plus fee %s) but only %s is spendable"
5094 " -- mined coin counts as zero until it is %d deep",
5095 FormatMoney(nValue).c_str(),
5096 FormatMoney(nFeeRequired).c_str(),
5097 FormatMoney(GetBalance()).c_str(),
5098 COINBASE_SPENDABLE_DEPTH);
4782 else 5099 else
4783 strError = "Error: Transaction creation failed "; 5100 strError = "Error: Transaction creation failed ";
4784 return error("SendMoney() : %s\n", strError.c_str()); 5101 return error("SendMoney() : %s\n", strError.c_str());