diff options
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 561 |
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; | |||
| 87 | string strSetDataDir; | 87 | string strSetDataDir; |
| 88 | int nDropMessagesTest = 0; | 88 | int nDropMessagesTest = 0; |
| 89 | bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test) | 89 | bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test) |
| 90 | bool fPoolNoAnnounce = false; // /noannounce: operator serves miners but never publishes pool announcements | ||
| 91 | bool fPoolMineBlind = false; // /poolmineblind: issue jobs even when no peer has reported a height | ||
| 90 | 92 | ||
| 91 | // Settings | 93 | // Settings |
| 92 | int fGenerateBitcoins; | 94 | int fGenerateBitcoins; |
| @@ -102,6 +104,9 @@ CAddress addrIncoming; | |||
| 102 | int nMineMode = MINE_RELAY; | 104 | int nMineMode = MINE_RELAY; |
| 103 | bool fMineModeFromCommandLine = false; | 105 | bool fMineModeFromCommandLine = false; |
| 104 | string strParticipantPool; // participant mode: pool .btf address | 106 | string strParticipantPool; // participant mode: pool .btf address |
| 107 | string strPayoutAddress; // participant: /payoutaddr override (mine without a wallet) | ||
| 108 | string strPayoutFile; // participant: /payoutfile with one address per line, rotated per reconnect | ||
| 109 | int nHashThreads = 0; // participant: /hashthreads override (0 = cores-1) | ||
| 105 | string strPoolName = "Bitflash Pool"; | 110 | string strPoolName = "Bitflash Pool"; |
| 106 | string strPoolDashboardUrl; | 111 | string strPoolDashboardUrl; |
| 107 | string strPoolStatusFile; | 112 | string 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. | ||
| 187 | int 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 | |||
| 172 | static void SecureClearBytes(vector<unsigned char>& vch); | 197 | static void SecureClearBytes(vector<unsigned char>& vch); |
| 173 | static void SecureClearKeyingMaterial(CKeyingMaterial& vch); | 198 | static 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 | ||
| 2942 | bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) | 3006 | bool 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 |
| 3503 | static const int64 STUCK_NO_JOB_TIMEOUT_SECS = 120; | 3567 | static 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 | // ------------------------------------------------------------------ | ||
| 3577 | struct 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 | |||
| 3592 | struct ParticipantShareHit | ||
| 3593 | { | ||
| 3594 | uint64 jobSeq; | ||
| 3595 | std::string jobId; | ||
| 3596 | unsigned int nonce; | ||
| 3597 | }; | ||
| 3598 | |||
| 3599 | static 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 | |||
| 3505 | static bool PoolParticipantMiner() | 3665 | static 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 | |||
| 3774 | connected: | ||
| 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()); |