diff options
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 1177 |
1 files changed, 1033 insertions, 144 deletions
diff --git a/src/main.cpp b/src/main.cpp index 5d266ff..0fa6b89 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -14,6 +14,7 @@ | |||
| 14 | #include <atomic> | 14 | #include <atomic> |
| 15 | #include <mutex> | 15 | #include <mutex> |
| 16 | #include <thread> | 16 | #include <thread> |
| 17 | #include <system_error> | ||
| 17 | #include "bip32.h" | 18 | #include "bip32.h" |
| 18 | #include "btfaddr.h" | 19 | #include "btfaddr.h" |
| 19 | #include "btftunnel.h" | 20 | #include "btftunnel.h" |
| @@ -82,6 +83,137 @@ CBlockIndex* pindexBest = NULL; | |||
| 82 | map<uint256, CBlock*> mapOrphanBlocks; | 83 | map<uint256, CBlock*> mapOrphanBlocks; |
| 83 | multimap<uint256, CBlock*> mapOrphanBlocksByPrev; | 84 | multimap<uint256, CBlock*> mapOrphanBlocksByPrev; |
| 84 | 85 | ||
| 86 | // Block bodies we have sent a getdata for and not yet received, so the | ||
| 87 | // failover pass in SendMessages can tell "requested and being served" from | ||
| 88 | // "requested and gone quiet" and re-issue the request to a different peer | ||
| 89 | // (#33). Keyed by block hash; setAsked is the peers already tried since the | ||
| 90 | // round last restarted, so the retry rotates instead of hammering the | ||
| 91 | // announcer that is not serving. Guarded by cs_main, which both the message | ||
| 92 | // handlers and SendMessages already hold. | ||
| 93 | // | ||
| 94 | // Capped small on purpose. At the tip there is one block in flight; during | ||
| 95 | // initial download there are legitimately hundreds outstanding to one peer, | ||
| 96 | // and rotating those would duplicate the whole download to every other peer. | ||
| 97 | // | ||
| 98 | // The cap is a shared resource and nothing on the path from inv to insertion | ||
| 99 | // can tell a real block hash from 32 random bytes -- AlreadyHave only asks | ||
| 100 | // whether we already hold the block, and there is no header to check work | ||
| 101 | // against. So the table does not refuse a new hash when it is full; it makes | ||
| 102 | // room. See MakeRoomInBlockFetch for the rule, which is what stops one peer | ||
| 103 | // filling all eight slots and switching failover off for everybody. | ||
| 104 | // | ||
| 105 | // The initialisers matter: the insertion path uses operator[] and then tests | ||
| 106 | // nTimeFirstAsked == 0 to detect a fresh entry. That worked on | ||
| 107 | // value-initialisation alone, but only for as long as nobody gave this struct | ||
| 108 | // a constructor. | ||
| 109 | struct CBlockFetch | ||
| 110 | { | ||
| 111 | int64 nTimeFirstAsked = 0; // when the first getdata for this block went out | ||
| 112 | int64 nTimeAsked = 0; // when the latest getdata went out | ||
| 113 | CAddress addrAnnounced; // peer whose announcement put this hash here | ||
| 114 | set<CAddress> setAsked; // peers tried since the round last restarted | ||
| 115 | }; | ||
| 116 | map<uint256, CBlockFetch> mapBlockFetch; | ||
| 117 | static const int BLOCK_FETCH_TRACK_MAX = 8; | ||
| 118 | // A block nobody has served for this long is not a fetch problem: the hash may | ||
| 119 | // have been announced and never existed. Drop the entry rather than getdata | ||
| 120 | // forever; a re-announcement recreates it. | ||
| 121 | static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60; | ||
| 122 | |||
| 123 | // Failover firing at all is worth saying out loud: it means peers are | ||
| 124 | // announcing blocks they then do not serve, which is the pathology of #33 and | ||
| 125 | // the thing nobody could otherwise see. Naming the peer on every re-request is | ||
| 126 | // a different matter. A peer can drive that line as often as it likes by | ||
| 127 | // announcing hashes it will not serve, and what it prints is a peer address, | ||
| 128 | // which is the one thing the .btf transport otherwise takes trouble not to | ||
| 129 | // spread around. So the per-peer detail sits behind the net category and the | ||
| 130 | // unconditional line is a count over a window no peer can shorten: the first | ||
| 131 | // failover after a quiet spell prints at once, because the change from "blocks | ||
| 132 | // arrive" to "blocks are announced and not served" is the event worth seeing, | ||
| 133 | // and everything after it is summarised once a minute. | ||
| 134 | static const int BLOCK_FETCH_REPORT_SECS = 60; | ||
| 135 | static int64 nBlockFetchWindowStart = 0; | ||
| 136 | static int nBlockFetchFailovers = 0; | ||
| 137 | |||
| 138 | // How stale our own best block may be before we stop calling this the tip. | ||
| 139 | // Blocks target two minutes, so this is thirty intervals: far longer than any | ||
| 140 | // stall worth failing over from -- the one in #33 ran sixteen minutes -- and | ||
| 141 | // far shorter than catching up, which starts hours or days back. | ||
| 142 | static const int BLOCK_FETCH_TIP_AGE_SECS = 60 * 60; | ||
| 143 | |||
| 144 | // Whether this node is tracking the tip rather than still catching up. | ||
| 145 | // | ||
| 146 | // Measured off the age of our own best block, not off any height a peer told | ||
| 147 | // us. nStartingHeight is optional in the version message and every node | ||
| 148 | // released so far sends the short form, so it is -1 for essentially every | ||
| 149 | // peer; and where it is filled in later it is filled in from the height of a | ||
| 150 | // block that peer served us, which during catch-up tracks our own height and | ||
| 151 | // would report "at the tip" for the whole download. Block timestamps are the | ||
| 152 | // one clock here that peers cannot move: CheckBlock refuses anything more than | ||
| 153 | // two hours ahead and AcceptBlock refuses anything at or before the previous | ||
| 154 | // median, so the only way to make this read "at the tip" falsely is to mine, | ||
| 155 | // and all that buys is the 15s spacing this code had unconditionally anyway. | ||
| 156 | bool IsBlockFetchNearTip() | ||
| 157 | { | ||
| 158 | return pindexBest && (int64)pindexBest->nTime > GetTime() - BLOCK_FETCH_TIP_AGE_SECS; | ||
| 159 | } | ||
| 160 | |||
| 161 | // Drop one entry so a hash announced by addrNew can be tracked. | ||
| 162 | // | ||
| 163 | // Refusing the insertion instead was a remote off-switch for the whole | ||
| 164 | // feature. An inv carrying eight hashes that never existed filled every slot | ||
| 165 | // -- nothing between inv and here can tell those from real blocks -- and no | ||
| 166 | // genuinely new block was tracked again until the give-up timer expired ten | ||
| 167 | // minutes later, re-armable for one message per ten minutes from a single | ||
| 168 | // connection. | ||
| 169 | // | ||
| 170 | // So evict instead, by occupancy: a peer may only take a slot from a peer | ||
| 171 | // holding strictly more slots than itself, and otherwise recycles one of its | ||
| 172 | // own. That is what keeps eviction from becoming the same attack in a new | ||
| 173 | // shape. A peer that has filled the table is by definition the heaviest | ||
| 174 | // holder, so its own entries are the only ones it can displace, while a peer | ||
| 175 | // announcing its first block holds nothing and therefore always evicts from | ||
| 176 | // the heaviest -- a genuine announcement is never refused a slot, whatever the | ||
| 177 | // attacker holds or however many connections it holds it from. The one case | ||
| 178 | // that costs a real fetch is eight distinct peers holding one entry each, | ||
| 179 | // where a ninth takes the oldest; that is a busy healthy node, not an attack, | ||
| 180 | // and losing the oldest of eight beats refusing the ninth. | ||
| 181 | static void MakeRoomInBlockFetch(const CAddress& addrNew) | ||
| 182 | { | ||
| 183 | if (mapBlockFetch.empty()) | ||
| 184 | return; | ||
| 185 | |||
| 186 | map<CAddress, int> mapHeld; | ||
| 187 | for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi) | ||
| 188 | mapHeld[(*mi).second.addrAnnounced]++; | ||
| 189 | |||
| 190 | int nMine = mapHeld.count(addrNew) ? mapHeld[addrNew] : 0; | ||
| 191 | |||
| 192 | int nMax = 0; | ||
| 193 | CAddress addrVictim; | ||
| 194 | for (map<CAddress, int>::iterator mi = mapHeld.begin(); mi != mapHeld.end(); ++mi) | ||
| 195 | if ((*mi).second > nMax) | ||
| 196 | { | ||
| 197 | nMax = (*mi).second; | ||
| 198 | addrVictim = (*mi).first; | ||
| 199 | } | ||
| 200 | |||
| 201 | // Nobody is holding more than this peer, so it recycles its own oldest | ||
| 202 | // rather than taking a slot off an equal. | ||
| 203 | if (nMax <= nMine) | ||
| 204 | addrVictim = addrNew; | ||
| 205 | |||
| 206 | map<uint256, CBlockFetch>::iterator miVictim = mapBlockFetch.end(); | ||
| 207 | for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi) | ||
| 208 | if ((*mi).second.addrAnnounced == addrVictim && | ||
| 209 | (miVictim == mapBlockFetch.end() || | ||
| 210 | (*mi).second.nTimeFirstAsked < (*miVictim).second.nTimeFirstAsked)) | ||
| 211 | miVictim = mi; | ||
| 212 | |||
| 213 | if (miVictim != mapBlockFetch.end()) | ||
| 214 | mapBlockFetch.erase(miVictim); | ||
| 215 | } | ||
| 216 | |||
| 85 | map<uint256, CDataStream*> mapOrphanTransactions; | 217 | map<uint256, CDataStream*> mapOrphanTransactions; |
| 86 | multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; | 218 | multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; |
| 87 | 219 | ||
| @@ -105,6 +237,8 @@ CKey keyUser; | |||
| 105 | string strSetDataDir; | 237 | string strSetDataDir; |
| 106 | int nDropMessagesTest = 0; | 238 | int nDropMessagesTest = 0; |
| 107 | bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test) | 239 | bool fSoloMineTest = false; // /solomine: mine without requiring a peer (local test) |
| 240 | bool fPoolNoAnnounce = false; // /noannounce: operator serves miners but never publishes pool announcements | ||
| 241 | bool fPoolMineBlind = false; // /poolmineblind: issue jobs even when no peer has reported a height | ||
| 108 | 242 | ||
| 109 | // Settings | 243 | // Settings |
| 110 | int fGenerateBitcoins; | 244 | int fGenerateBitcoins; |
| @@ -120,6 +254,9 @@ CAddress addrIncoming; | |||
| 120 | int nMineMode = MINE_RELAY; | 254 | int nMineMode = MINE_RELAY; |
| 121 | bool fMineModeFromCommandLine = false; | 255 | bool fMineModeFromCommandLine = false; |
| 122 | string strParticipantPool; // participant mode: pool .btf address | 256 | string strParticipantPool; // participant mode: pool .btf address |
| 257 | string strPayoutAddress; // participant: /payoutaddr override (mine without a wallet) | ||
| 258 | string strPayoutFile; // participant: /payoutfile with one address per line, rotated per reconnect | ||
| 259 | int nHashThreads = 0; // participant: /hashthreads override (0 = cores-1) | ||
| 123 | string strPoolName = "Bitflash Pool"; | 260 | string strPoolName = "Bitflash Pool"; |
| 124 | string strPoolDashboardUrl; | 261 | string strPoolDashboardUrl; |
| 125 | string strPoolStatusFile; | 262 | string strPoolStatusFile; |
| @@ -238,12 +375,32 @@ int MinerThreadCount() | |||
| 238 | { | 375 | { |
| 239 | if (nMinerThreads > 0) | 376 | if (nMinerThreads > 0) |
| 240 | return nMinerThreads; | 377 | return nMinerThreads; |
| 241 | unsigned int nCores = std::thread::hardware_concurrency(); | 378 | unsigned int nCores = AvailableCoreCount(); |
| 242 | if (nCores <= 1) | 379 | if (nCores <= 1) |
| 243 | return 1; | 380 | return 1; |
| 244 | return (int)(nCores - 1); | 381 | return (int)(nCores - 1); |
| 245 | } | 382 | } |
| 246 | 383 | ||
| 384 | // Hash threads one participant session runs. | ||
| 385 | // | ||
| 386 | // Explicit /hashthreads=N is honored exactly, including 1 -- a miner who says | ||
| 387 | // one means one. Automatic leaves a core to the node, the Stratum I/O loop and | ||
| 388 | // everything else the process still has to do, for the same reason | ||
| 389 | // MinerThreadCount() does. | ||
| 390 | // | ||
| 391 | // Lives here, next to that function, because the two are easy to confuse and | ||
| 392 | // StartMinerThreads() needs to be able to tell them apart: only ONE session | ||
| 393 | // runs in participant mode, and this is what sizes it. | ||
| 394 | int ParticipantHashThreadCount() | ||
| 395 | { | ||
| 396 | if (nHashThreads > 0) | ||
| 397 | return nHashThreads; | ||
| 398 | int nWorkers = (int)AvailableCoreCount(); | ||
| 399 | if (nWorkers < 2) | ||
| 400 | nWorkers = 2; | ||
| 401 | return nWorkers - 1; | ||
| 402 | } | ||
| 403 | |||
| 247 | static void SecureClearBytes(vector<unsigned char>& vch); | 404 | static void SecureClearBytes(vector<unsigned char>& vch); |
| 248 | static void SecureClearKeyingMaterial(CKeyingMaterial& vch); | 405 | static void SecureClearKeyingMaterial(CKeyingMaterial& vch); |
| 249 | 406 | ||
| @@ -1626,7 +1783,7 @@ int CMerkleTx::GetBlocksToMaturity() const | |||
| 1626 | { | 1783 | { |
| 1627 | if (!IsCoinBase()) | 1784 | if (!IsCoinBase()) |
| 1628 | return 0; | 1785 | return 0; |
| 1629 | return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain()); | 1786 | return max(0, COINBASE_SPENDABLE_DEPTH - GetDepthInMainChain()); |
| 1630 | } | 1787 | } |
| 1631 | 1788 | ||
| 1632 | 1789 | ||
| @@ -1762,7 +1919,7 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb) | |||
| 1762 | uint256 hash = GetHash(); | 1919 | uint256 hash = GetHash(); |
| 1763 | if (!txdb.ContainsTx(hash)) | 1920 | if (!txdb.ContainsTx(hash)) |
| 1764 | { | 1921 | { |
| 1765 | if (LogAcceptsCategory("net")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); | 1922 | if (LogAcceptsCategory("relay")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); |
| 1766 | RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); | 1923 | RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); |
| 1767 | } | 1924 | } |
| 1768 | } | 1925 | } |
| @@ -2738,7 +2895,35 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2738 | // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) | 2895 | // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) |
| 2739 | // vMerkleTree: 4a5e1e | 2896 | // vMerkleTree: 4a5e1e |
| 2740 | 2897 | ||
| 2741 | // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) | 2898 | // Bitflash genesis block -- own header (fair launch) |
| 2899 | // | ||
| 2900 | // Upstream moved the construction into BuildGenesisBlock() so mainnet | ||
| 2901 | // and the isolated testnet can share it. The mainnet inputs it uses are | ||
| 2902 | // byte-identical to what this block built inline before the merge -- | ||
| 2903 | // timestamp string, nTime, nNonce, scriptPubKey, nBits and nVersion all | ||
| 2904 | // verified equal against MAINNET_GENESIS_* -- so the mainnet genesis | ||
| 2905 | // hash is unchanged. The warning below is why that was checked rather | ||
| 2906 | // than assumed, and it still applies to those constants: | ||
| 2907 | // | ||
| 2908 | // Do not change MAINNET_GENESIS_TIMESTAMP, the scriptPubKey, | ||
| 2909 | // MAINNET_GENESIS_TIME or MAINNET_GENESIS_NONCE. They are the inputs to | ||
| 2910 | // the genesis hash, and the genesis hash is consensus. Changing one on | ||
| 2911 | // its own trips the assert below, which is loud and reverts cleanly; | ||
| 2912 | // changing one and regenerating GENESIS_NONCE and hashGenesisBlock to | ||
| 2913 | // match -- which is exactly what the re-mining branch below prints out | ||
| 2914 | // and invites -- gives a node that starts without complaint, builds its | ||
| 2915 | // own chain from height 0, and rejects every real peer as being on a | ||
| 2916 | // different chain. That one fails silently, and the damage scales with | ||
| 2917 | // how many people run the "fixed" build. | ||
| 2918 | // | ||
| 2919 | // MAINNET_GENESIS_TIME is 1753315200, which is 2025-07-24T00:00:00Z, | ||
| 2920 | // while the headline in MAINNET_GENESIS_TIMESTAMP reads 2026 -- as does | ||
| 2921 | // the comment upstream put on that constant, which is wrong about the | ||
| 2922 | // year. This is a known discrepancy and not a typo in the value: it is | ||
| 2923 | // only consensus-critical as an input to the genesis hash, the block | ||
| 2924 | // hashes to hashGenesisBlock with it, and every node on the network | ||
| 2925 | // agrees. The mismatch is in the launched chain and cannot be corrected | ||
| 2926 | // -- see the warning above before deciding otherwise. | ||
| 2742 | CBlock block = BuildGenesisBlock(); | 2927 | CBlock block = BuildGenesisBlock(); |
| 2743 | 2928 | ||
| 2744 | // If the nonce has not been found yet, mine the genesis once (RandomX) | 2929 | // If the nonce has not been found yet, mine the genesis once (RandomX) |
| @@ -2764,6 +2949,11 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 2764 | printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); | 2949 | printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); |
| 2765 | printf("hash = %s\n", block.GetHash().ToString().c_str()); | 2950 | printf("hash = %s\n", block.GetHash().ToString().c_str()); |
| 2766 | printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); | 2951 | printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); |
| 2952 | printf("----------------------------------------\n"); | ||
| 2953 | printf("These constants bring up a NEW chain. Do not paste them\n"); | ||
| 2954 | printf("into a build for this network to make an edited nTime or\n"); | ||
| 2955 | printf("pszTimestamp fit -- a node whose genesis differs starts\n"); | ||
| 2956 | printf("cleanly on a chain of its own and rejects every real peer.\n"); | ||
| 2767 | printf("========================================\n"); | 2957 | printf("========================================\n"); |
| 2768 | } | 2958 | } |
| 2769 | 2959 | ||
| @@ -2890,9 +3080,10 @@ bool AlreadyHave(CTxDB& txdb, const CInv& inv) | |||
| 2890 | { | 3080 | { |
| 2891 | case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); | 3081 | case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); |
| 2892 | case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); | 3082 | case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); |
| 2893 | case MSG_REVIEW: return true; | ||
| 2894 | case MSG_PRODUCT: return mapProducts.count(inv.hash); | ||
| 2895 | } | 3083 | } |
| 3084 | // MSG_REVIEW / MSG_PRODUCT / MSG_TABLE are the 2009 marketplace inventory | ||
| 3085 | // types. Nothing announces or serves them any more (see ProcessMessage), | ||
| 3086 | // so they fall through to the default and are never requested. | ||
| 2896 | // Don't know what it is, just say we already got one | 3087 | // Don't know what it is, just say we already got one |
| 2897 | return true; | 3088 | return true; |
| 2898 | } | 3089 | } |
| @@ -2935,11 +3126,13 @@ bool ProcessMessages(CNode* pfrom) | |||
| 2935 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); | 3126 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE SKIPPED %d BYTES\n\n", (int)(pstart - vRecv.begin())); |
| 2936 | vRecv.erase(vRecv.begin(), pstart); | 3127 | vRecv.erase(vRecv.begin(), pstart); |
| 2937 | 3128 | ||
| 2938 | // Read the header from a copy first. The old path consumed the header, | 3129 | // Read the header from a copy first, and do not consume it until the |
| 2939 | // noticed the payload was incomplete, inserted the header back at the | 3130 | // whole body is here. The old path consumed the header, noticed the |
| 2940 | // front of the receive buffer, then slept inside the shared message | 3131 | // payload was incomplete, inserted the header back at the front of the |
| 2941 | // thread. A peer could announce a large payload and drip bytes forever, | 3132 | // receive buffer, then slept inside the shared message thread. A peer |
| 2942 | // making every pass pay for an O(n) insert and a 100 ms stall. | 3133 | // could announce a large payload and drip bytes forever, making every |
| 3134 | // pass pay for an O(n) insert and a 100 ms stall -- a cheap way for one | ||
| 3135 | // peer to slow the thread that serves all of them. (upstream #109) | ||
| 2943 | CMessageHeader hdr; | 3136 | CMessageHeader hdr; |
| 2944 | CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader), | 3137 | CDataStream vHeader(vRecv.begin(), vRecv.begin() + sizeof(CMessageHeader), |
| 2945 | vRecv.nType, vRecv.nVersion); | 3138 | vRecv.nType, vRecv.nVersion); |
| @@ -2949,7 +3142,10 @@ bool ProcessMessages(CNode* pfrom) | |||
| 2949 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); | 3142 | if (LogAcceptsCategory("net")) printf("\n\nPROCESSMESSAGE: ERRORS IN HEADER %s\n\n\n", hdr.GetCommand().c_str()); |
| 2950 | if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE) | 3143 | if (hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_SIZE) |
| 2951 | pfrom->fDisconnect = true; | 3144 | pfrom->fDisconnect = true; |
| 2952 | pfrom->nIncompleteMessageStart = 0; | 3145 | pfrom->nMsgWaitSince = 0; |
| 3146 | pfrom->nMsgWaitSize = 0; | ||
| 3147 | // The header was read from a copy, so it is still in vRecv. Skip it | ||
| 3148 | // explicitly or this loop never advances. | ||
| 2953 | vRecv.ignore(sizeof(CMessageHeader)); | 3149 | vRecv.ignore(sizeof(CMessageHeader)); |
| 2954 | continue; | 3150 | continue; |
| 2955 | } | 3151 | } |
| @@ -2960,30 +3156,63 @@ bool ProcessMessages(CNode* pfrom) | |||
| 2960 | unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader); | 3156 | unsigned int nPayloadAvailable = vRecv.size() - sizeof(CMessageHeader); |
| 2961 | if (nMessageSize > nPayloadAvailable) | 3157 | if (nMessageSize > nPayloadAvailable) |
| 2962 | { | 3158 | { |
| 2963 | if (pfrom->nIncompleteMessageStart == 0 || | 3159 | // Wait for the rest of the message. The header stays in vRecv. |
| 2964 | pfrom->nIncompleteMessageSize != nMessageSize || | 3160 | // |
| 2965 | pfrom->strIncompleteMessageCommand != strCommand) | 3161 | // This is the mechanism the original note asked for: "need a |
| 3162 | // mechanism to give up waiting for overlong message size error". | ||
| 3163 | // Without one the wait is unbounded, and unbounded here means | ||
| 3164 | // permanent. The header stays at the front of vRecv and the loop | ||
| 3165 | // breaks; if the body never completes, every later pass re-reads | ||
| 3166 | // that same header and breaks in the same place. The peer is never | ||
| 3167 | // parsed again -- no blocks, no inv, not even its version message, | ||
| 3168 | // so it never reports a height either, which is why a wedged node | ||
| 3169 | // has peers and GetPeerMedianHeight() still returns -1. | ||
| 3170 | // | ||
| 3171 | // Nothing rescues it. A header could claim up to 256 MB and still | ||
| 3172 | // pass CMessageHeader::IsValid() -- now capped at | ||
| 3173 | // MAX_PROTOCOL_MESSAGE_SIZE -- and any byte that arrives | ||
| 3174 | // refreshes nLastRecv, so the inactivity timeout that drops a | ||
| 3175 | // silent peer never fires on one that is merely unreadable. The | ||
| 3176 | // socket stays ESTABLISHED and select() keeps offering it. | ||
| 3177 | // Measured on a wedged pool: this branch ran 2014 times in the | ||
| 3178 | // deaf window, against 607 in the healthy window before it. | ||
| 3179 | if (LogAcceptsCategory("net")) printf("MESSAGE-BREAK 2\n"); | ||
| 3180 | // | ||
| 3181 | // Time the STALL, not the wait. A big message on a slow link is | ||
| 3182 | // still arriving, and dropping that peer would be a bug of our own | ||
| 3183 | // making; what must be caught is a buffer that has stopped growing | ||
| 3184 | // while we go on expecting more. So the clock restarts whenever | ||
| 3185 | // even one more byte has landed since the last look. | ||
| 3186 | // | ||
| 3187 | // Upstream #109 times this differently: it restarts the clock only | ||
| 3188 | // when the (size, command) pair changes, which drops a peer sending | ||
| 3189 | // one big message slowly but honestly. Progress is the better test, | ||
| 3190 | // so that half is kept as it was. | ||
| 3191 | int64 nNow = GetTime(); | ||
| 3192 | if (pfrom->nMsgWaitSince == 0 || nPayloadAvailable > pfrom->nMsgWaitSize) | ||
| 2966 | { | 3193 | { |
| 2967 | pfrom->nIncompleteMessageStart = GetTime(); | 3194 | pfrom->nMsgWaitSince = nNow; |
| 2968 | pfrom->nIncompleteMessageSize = nMessageSize; | 3195 | pfrom->nMsgWaitSize = nPayloadAvailable; |
| 2969 | pfrom->strIncompleteMessageCommand = strCommand; | ||
| 2970 | } | 3196 | } |
| 2971 | else if (GetTime() - pfrom->nIncompleteMessageStart > | 3197 | else if (nNow - pfrom->nMsgWaitSince > BTF_MSG_WAIT_MAX_SECS) |
| 2972 | BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS) | ||
| 2973 | { | 3198 | { |
| 2974 | LogPrint("net", | 3199 | // Deliberately not behind a debug category. This is the node |
| 2975 | "disconnecting %s: incomplete %s message, %u of %u bytes after %d seconds\n", | 3200 | // going deaf to a peer, which is the thing nobody could see. |
| 2976 | pfrom->addr.ToString().c_str(), strCommand.c_str(), | 3201 | printf("peer %s announced a %u byte '%s' and has sent nothing more " |
| 2977 | nPayloadAvailable, nMessageSize, | 3202 | "for %d s (%d of %u payload bytes) -- dropping, this peer is unreadable\n", |
| 2978 | BTF_INCOMPLETE_MESSAGE_TIMEOUT_SECS); | 3203 | pfrom->addr.ToString().c_str(), nMessageSize, |
| 3204 | strCommand.c_str(), (int)(nNow - pfrom->nMsgWaitSince), | ||
| 3205 | (int)nPayloadAvailable, nMessageSize); | ||
| 2979 | pfrom->fDisconnect = true; | 3206 | pfrom->fDisconnect = true; |
| 2980 | } | 3207 | } |
| 3208 | // No re-insert, no Sleep(): the header was never consumed, so there | ||
| 3209 | // is nothing to put back, and sleeping here stalls every peer. | ||
| 2981 | break; | 3210 | break; |
| 2982 | } | 3211 | } |
| 2983 | pfrom->nIncompleteMessageStart = 0; | ||
| 2984 | pfrom->nIncompleteMessageSize = 0; | ||
| 2985 | pfrom->strIncompleteMessageCommand.clear(); | ||
| 2986 | vRecv.ignore(sizeof(CMessageHeader)); | 3212 | vRecv.ignore(sizeof(CMessageHeader)); |
| 3213 | // The body arrived, so this peer is readable again. | ||
| 3214 | pfrom->nMsgWaitSince = 0; | ||
| 3215 | pfrom->nMsgWaitSize = 0; | ||
| 2987 | 3216 | ||
| 2988 | // Copy message to its own buffer | 3217 | // Copy message to its own buffer |
| 2989 | CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); | 3218 | CDataStream vMsg(vRecv.begin(), vRecv.begin() + nMessageSize, vRecv.nType, vRecv.nVersion); |
| @@ -3012,7 +3241,6 @@ bool ProcessMessages(CNode* pfrom) | |||
| 3012 | 3241 | ||
| 3013 | bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) | 3242 | bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) |
| 3014 | { | 3243 | { |
| 3015 | static map<unsigned int, vector<unsigned char> > mapReuseKey; | ||
| 3016 | 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"); } | 3244 | 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"); } |
| 3017 | if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) | 3245 | if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) |
| 3018 | { | 3246 | { |
| @@ -3251,22 +3479,18 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) | |||
| 3251 | } | 3479 | } |
| 3252 | 3480 | ||
| 3253 | 3481 | ||
| 3254 | else if (strCommand == "review") | 3482 | // "review" used to be handled here. It took a CReview off the wire from an |
| 3255 | { | 3483 | // unauthenticated peer, checked a signature the sender had made with a key |
| 3256 | CDataStream vMsg(vRecv); | 3484 | // of its own choosing -- which proves possession of that key and authorises |
| 3257 | CReview review; | 3485 | // nothing -- and then appended the message to reviews.dat by reading the |
| 3258 | vRecv >> review; | 3486 | // whole vector for the target hash, pushing one entry, and writing all of |
| 3259 | 3487 | // it back. That is quadratic disk for a stream an attacker controls, on a | |
| 3260 | CInv inv(MSG_REVIEW, review.GetHash()); | 3488 | // record whose mapValue is an unbounded map<string,string> read straight |
| 3261 | pfrom->AddInventoryKnown(inv); | 3489 | // from the socket, followed by AddAtomsAndPropagate walking a link graph |
| 3262 | 3490 | // the same peer supplied, and finally RelayMessage forwarding it to every | |
| 3263 | if (review.AcceptReview()) | 3491 | // other node. Unbounded remote disk growth with network amplification and |
| 3264 | { | 3492 | // no authorisation anywhere in the path. The marketplace it belonged to has |
| 3265 | // Relay the original message as-is in case it's a higher version than we know how to parse | 3493 | // not existed since 2009; the handler is gone rather than bounded. |
| 3266 | RelayMessage(inv, vMsg); | ||
| 3267 | mapAlreadyAskedFor.erase(inv); | ||
| 3268 | } | ||
| 3269 | } | ||
| 3270 | 3494 | ||
| 3271 | 3495 | ||
| 3272 | else if (strCommand == "block") | 3496 | else if (strCommand == "block") |
| @@ -3275,47 +3499,98 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) | |||
| 3275 | vRecv >> *pblock; | 3499 | vRecv >> *pblock; |
| 3276 | 3500 | ||
| 3277 | //// debug print | 3501 | //// debug print |
| 3278 | if (LogAcceptsCategory("net")) { printf("received block:\n"); pblock->print(); } | 3502 | if (LogAcceptsCategory("relay")) { printf("received block:\n"); pblock->print(); } |
| 3279 | 3503 | ||
| 3280 | CInv inv(MSG_BLOCK, pblock->GetHash()); | 3504 | CInv inv(MSG_BLOCK, pblock->GetHash()); |
| 3281 | pfrom->AddInventoryKnown(inv); | 3505 | pfrom->AddInventoryKnown(inv); |
| 3282 | 3506 | ||
| 3507 | // GetHash() covers the 80-byte header and nothing else, so the hash | ||
| 3508 | // coming out right does not make this the block we asked for. Every | ||
| 3509 | // peer that saw X announced holds X's header, and can answer our | ||
| 3510 | // getdata with that header bolted onto a transaction list of its own | ||
| 3511 | // invention -- the reply still hashes to X. hashMerkleRoot is the only | ||
| 3512 | // thing binding the header to the body, so check it here, before the | ||
| 3513 | // mapBlockFetch entry is touched and before CheckBlock spends a | ||
| 3514 | // RandomX hash on a body already known to be forged. | ||
| 3515 | // | ||
| 3516 | // There is no honest way to arrive here. Peer traffic rides the .btf | ||
| 3517 | // channel, which is XChaCha20-Poly1305, so a body that arrives at all | ||
| 3518 | // arrived intact: a mismatch is the peer's own construction, not line | ||
| 3519 | // noise. Drop the peer and leave the fetch outstanding so the failover | ||
| 3520 | // sweep asks somebody else for the real X, rather than recording X as | ||
| 3521 | // delivered on the strength of a header anyone could copy. | ||
| 3522 | if (pblock->hashMerkleRoot != pblock->BuildMerkleTree()) | ||
| 3523 | { | ||
| 3524 | if (LogAcceptsCategory("net")) | ||
| 3525 | printf("peer %s served a forged body for %s\n", | ||
| 3526 | pfrom->addr.ToString().c_str(), inv.ToString().c_str()); | ||
| 3527 | // One line per connection: ProcessMessages keeps draining the | ||
| 3528 | // receive buffer after fDisconnect is set, so a peer that packs | ||
| 3529 | // many forged bodies into one buffer must not get one log line | ||
| 3530 | // for each of them. | ||
| 3531 | if (!pfrom->fDisconnect) | ||
| 3532 | printf("block fetch: a peer answered the request for %s with a body that is not " | ||
| 3533 | "that block -- dropping it\n", inv.hash.ToString().substr(0,14).c_str()); | ||
| 3534 | pfrom->fDisconnect = true; | ||
| 3535 | return error("ProcessMessage() : forged block body for %s", | ||
| 3536 | inv.hash.ToString().substr(0,14).c_str()); | ||
| 3537 | } | ||
| 3538 | |||
| 3283 | if (ProcessBlock(pfrom, pblock.release())) | 3539 | if (ProcessBlock(pfrom, pblock.release())) |
| 3540 | { | ||
| 3284 | mapAlreadyAskedFor.erase(inv); | 3541 | mapAlreadyAskedFor.erase(inv); |
| 3285 | } | ||
| 3286 | |||
| 3287 | 3542 | ||
| 3288 | else if (strCommand == "checkorder" || strCommand == "submitorder") | 3543 | // The body arrived and validated, so the fetch really is over: |
| 3289 | { | 3544 | // the block is now in mapBlockIndex, or parked in mapOrphanBlocks, |
| 3290 | // Legacy Bitcoin 0.1 marketplace messages are not part of Bitflash's | 3545 | // and either way AlreadyHave answers for it from here on. |
| 3291 | // supported protocol. They used to let a remote peer make this wallet | 3546 | // |
| 3292 | // reserve a key or import/broadcast a wallet transaction. Keep the | 3547 | // This has to stay inside the success branch, next to the erase |
| 3293 | // command names explicit so old peers get a quiet no-op instead of a | 3548 | // above. When validation rejects a body nothing is stored, |
| 3294 | // wallet side effect. | 3549 | // AlreadyHave stays false, and the mapAskFor entry was already |
| 3295 | if (LogAcceptsCategory("net")) | 3550 | // consumed when the getdata went out -- so erasing the tracking |
| 3296 | printf("ProcessMessage(%s) : disabled legacy order message\n", strCommand.c_str()); | 3551 | // here would leave the block requested from nobody at all until |
| 3297 | vRecv.clear(); | 3552 | // some peer happened to re-announce it. That is exactly the stall |
| 3553 | // of #33, and erasing before ProcessBlock made it reachable on | ||
| 3554 | // demand. | ||
| 3555 | mapBlockFetch.erase(inv.hash); | ||
| 3556 | |||
| 3557 | // A peer that just handed us a block is demonstrably at least at | ||
| 3558 | // that block's height. Without this, nStartingHeight is a snapshot | ||
| 3559 | // taken once during the version handshake and never touched again, | ||
| 3560 | // so on a long-lived connection it is the peer's height from | ||
| 3561 | // however many hours ago it happened to connect. The pool's | ||
| 3562 | // behind-the-network check compares against the median of those | ||
| 3563 | // numbers, so as connections age it drifts toward "everyone is | ||
| 3564 | // behind us" and quietly stops firing -- the opposite of its job. | ||
| 3565 | map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(inv.hash); | ||
| 3566 | if (mi != mapBlockIndex.end() && mi->second) | ||
| 3567 | pfrom->nStartingHeight = max(pfrom->nStartingHeight, mi->second->nHeight); | ||
| 3568 | } | ||
| 3298 | } | 3569 | } |
| 3299 | 3570 | ||
| 3300 | 3571 | ||
| 3301 | else if (strCommand == "reply") | 3572 | // "checkorder", "submitorder" and "reply" -- the 2009 buy-from-a-merchant |
| 3302 | { | 3573 | // exchange -- used to be handled here. All three are gone. |
| 3303 | uint256 hashReply; | 3574 | // |
| 3304 | vRecv >> hashReply; | 3575 | // "checkorder" called GenerateNewKey() for any peer that asked, which is |
| 3305 | 3576 | // AddKey() and a wallet.dat write, and handed the pubkey back. The comment | |
| 3306 | CRequestTracker tracker; | 3577 | // said "keep giving the same key to the same ip until they use it", and on |
| 3307 | CRITICAL_BLOCK(pfrom->cs_mapRequests) | 3578 | // a 2009 network of routable IPs that held. It does not hold here: every |
| 3308 | { | 3579 | // inbound peer arrives through a rendezvous tunnel and is tagged with |
| 3309 | map<uint256, CRequestTracker>::iterator mi = pfrom->mapRequests.find(hashReply); | 3580 | // BtfMarkerAddr() over three random bytes (net.cpp), so addr.ip is freshly |
| 3310 | if (mi != pfrom->mapRequests.end()) | 3581 | // random per connection and the cache never hits. Reconnect in a loop and |
| 3311 | { | 3582 | // the node mints a key per connection -- unbounded wallet growth, unbounded |
| 3312 | tracker = (*mi).second; | 3583 | // growth of the static map that held them, and every one of those keys |
| 3313 | pfrom->mapRequests.erase(mi); | 3584 | // missing from every backup taken beforehand, which for this pre-BIP32 |
| 3314 | } | 3585 | // wallet is how coins are lost. |
| 3315 | } | 3586 | // |
| 3316 | if (!tracker.IsNull()) | 3587 | // "submitorder" took a CWalletTx from an unauthenticated peer and, if it |
| 3317 | tracker.fn(tracker.param1, vRecv); | 3588 | // was merely a valid transaction, AddToWallet()'d it. Valid is not the same |
| 3318 | } | 3589 | // as ours: any peer could write chosen transactions into wallet.dat. |
| 3590 | // | ||
| 3591 | // "reply" dispatched to a CRequestTracker callback. Nothing in this tree | ||
| 3592 | // has ever called PushRequest, so mapRequests was permanently empty and the | ||
| 3593 | // handler could only ever be a parser for hostile input. | ||
| 3319 | 3594 | ||
| 3320 | 3595 | ||
| 3321 | else if (strCommand == "ping") | 3596 | else if (strCommand == "ping") |
| @@ -3403,12 +3678,119 @@ bool SendMessages(CNode* pto) | |||
| 3403 | const CInv& inv = (*pto->mapAskFor.begin()).second; | 3678 | const CInv& inv = (*pto->mapAskFor.begin()).second; |
| 3404 | if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); | 3679 | if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); |
| 3405 | if (!AlreadyHave(txdb, inv)) | 3680 | if (!AlreadyHave(txdb, inv)) |
| 3681 | { | ||
| 3406 | vAskFor.push_back(inv); | 3682 | vAskFor.push_back(inv); |
| 3683 | // Blocks get their delivery tracked so the failover pass | ||
| 3684 | // below can chase the ones that go quiet. | ||
| 3685 | if (inv.type == MSG_BLOCK) | ||
| 3686 | { | ||
| 3687 | // Full, and this is a hash we are not already tracking: | ||
| 3688 | // make room rather than drop it on the floor, or one peer | ||
| 3689 | // announcing eight hashes turns failover off for everyone. | ||
| 3690 | if (!mapBlockFetch.count(inv.hash) && | ||
| 3691 | mapBlockFetch.size() >= BLOCK_FETCH_TRACK_MAX) | ||
| 3692 | MakeRoomInBlockFetch(pto->addr); | ||
| 3693 | |||
| 3694 | if (mapBlockFetch.count(inv.hash) || | ||
| 3695 | mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX) | ||
| 3696 | { | ||
| 3697 | CBlockFetch& fetch = mapBlockFetch[inv.hash]; | ||
| 3698 | if (fetch.nTimeFirstAsked == 0) | ||
| 3699 | { | ||
| 3700 | fetch.nTimeFirstAsked = GetTime(); | ||
| 3701 | // Who is answerable for this hash existing. Set | ||
| 3702 | // once, on creation: the failover sweep asks other | ||
| 3703 | // peers later, and none of them chose to announce | ||
| 3704 | // it, so none of them should be charged for it. | ||
| 3705 | fetch.addrAnnounced = pto->addr; | ||
| 3706 | } | ||
| 3707 | fetch.nTimeAsked = GetTime(); | ||
| 3708 | fetch.setAsked.insert(pto->addr); | ||
| 3709 | } | ||
| 3710 | } | ||
| 3711 | } | ||
| 3407 | pto->mapAskFor.erase(pto->mapAskFor.begin()); | 3712 | pto->mapAskFor.erase(pto->mapAskFor.begin()); |
| 3408 | } | 3713 | } |
| 3409 | if (!vAskFor.empty()) | 3714 | if (!vAskFor.empty()) |
| 3410 | pto->PushMessage("getdata", vAskFor); | 3715 | pto->PushMessage("getdata", vAskFor); |
| 3411 | 3716 | ||
| 3717 | |||
| 3718 | // | ||
| 3719 | // Message: getdata (block fetch failover) | ||
| 3720 | // | ||
| 3721 | // A requested block that has not arrived in BLOCK_FETCH_RETRY_SECS is | ||
| 3722 | // not still in transit -- a body is at most 1 MB and somebody | ||
| 3723 | // demonstrably has it -- so ask somebody else instead of waiting on | ||
| 3724 | // whoever was asked first (#33). Each peer the message handler visits | ||
| 3725 | // takes at most one turn per block per round; once every connected | ||
| 3726 | // peer has had its turn and a further interval passes with nobody | ||
| 3727 | // new to ask, the round restarts. A peer that declared itself a | ||
| 3728 | // client cannot serve block bodies and is no use as a target. | ||
| 3729 | if (!pto->fClient) | ||
| 3730 | { | ||
| 3731 | vector<CInv> vFailover; | ||
| 3732 | int64 nNowSecs = GetTime(); | ||
| 3733 | for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end();) | ||
| 3734 | { | ||
| 3735 | CBlockFetch& fetch = (*mi).second; | ||
| 3736 | CInv inv(MSG_BLOCK, (*mi).first); | ||
| 3737 | if (AlreadyHave(txdb, inv)) | ||
| 3738 | { | ||
| 3739 | // Arrived by some other road -- our own miner, an orphan | ||
| 3740 | // parked in mapOrphanBlocks. Nothing left to chase. | ||
| 3741 | mapBlockFetch.erase(mi++); | ||
| 3742 | continue; | ||
| 3743 | } | ||
| 3744 | if (nNowSecs - fetch.nTimeFirstAsked > BLOCK_FETCH_GIVEUP_SECS) | ||
| 3745 | { | ||
| 3746 | printf("block fetch: giving up on %s -- no peer served it in %d s\n", | ||
| 3747 | inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeFirstAsked)); | ||
| 3748 | mapBlockFetch.erase(mi++); | ||
| 3749 | continue; | ||
| 3750 | } | ||
| 3751 | if (nNowSecs - fetch.nTimeAsked >= BLOCK_FETCH_RETRY_SECS) | ||
| 3752 | { | ||
| 3753 | // The handler loop visits every peer within ~100ms, so a | ||
| 3754 | // request stale by two full intervals means nobody outside | ||
| 3755 | // setAsked was there to ask. Start the round over. | ||
| 3756 | if (nNowSecs - fetch.nTimeAsked >= 2 * BLOCK_FETCH_RETRY_SECS) | ||
| 3757 | fetch.setAsked.clear(); | ||
| 3758 | if (!fetch.setAsked.count(pto->addr)) | ||
| 3759 | { | ||
| 3760 | // Which block, how long, and which peer we are trying | ||
| 3761 | // next. Behind the category because it names a peer | ||
| 3762 | // and a peer decides how often it is written. | ||
| 3763 | if (LogAcceptsCategory("net")) | ||
| 3764 | printf("block fetch failover: %s unanswered for %d s, re-requesting from %s\n", | ||
| 3765 | inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeAsked), | ||
| 3766 | pto->addr.ToString().c_str()); | ||
| 3767 | |||
| 3768 | // The unconditional half: no addresses, and at most | ||
| 3769 | // one line per BLOCK_FETCH_REPORT_SECS however hard a | ||
| 3770 | // peer tries. See the counters for the reasoning. | ||
| 3771 | nBlockFetchFailovers++; | ||
| 3772 | if (nNowSecs - nBlockFetchWindowStart >= BLOCK_FETCH_REPORT_SECS) | ||
| 3773 | { | ||
| 3774 | printf("block fetch: %d block request%s went unanswered and %s been " | ||
| 3775 | "re-asked of other peers (%d tracked)\n", | ||
| 3776 | nBlockFetchFailovers, nBlockFetchFailovers == 1 ? "" : "s", | ||
| 3777 | nBlockFetchFailovers == 1 ? "has" : "have", | ||
| 3778 | (int)mapBlockFetch.size()); | ||
| 3779 | nBlockFetchWindowStart = nNowSecs; | ||
| 3780 | nBlockFetchFailovers = 0; | ||
| 3781 | } | ||
| 3782 | |||
| 3783 | vFailover.push_back(inv); | ||
| 3784 | fetch.nTimeAsked = nNowSecs; | ||
| 3785 | fetch.setAsked.insert(pto->addr); | ||
| 3786 | } | ||
| 3787 | } | ||
| 3788 | ++mi; | ||
| 3789 | } | ||
| 3790 | if (!vFailover.empty()) | ||
| 3791 | pto->PushMessage("getdata", vFailover); | ||
| 3792 | } | ||
| 3793 | |||
| 3412 | } | 3794 | } |
| 3413 | return true; | 3795 | return true; |
| 3414 | } | 3796 | } |
| @@ -3573,6 +3955,110 @@ static bool StratumRecvLine(SOCKET s, std::string& buf, | |||
| 3573 | // How long to wait for a mining.notify after authorize before giving up | 3955 | // How long to wait for a mining.notify after authorize before giving up |
| 3574 | static const int64 STUCK_NO_JOB_TIMEOUT_SECS = 120; | 3956 | static const int64 STUCK_NO_JOB_TIMEOUT_SECS = 120; |
| 3575 | 3957 | ||
| 3958 | // ------------------------------------------------------------------ | ||
| 3959 | // Multi-threaded pool worker. The original loop hashed a single nonce | ||
| 3960 | // per blocking 50ms socket poll, capping one instance at ~20 H/s no | ||
| 3961 | // matter how many cores the machine has. The socket and job bookkeeping | ||
| 3962 | // stay on the calling thread; N hash threads grind nonce ranges on their | ||
| 3963 | // own RandomX VMs (all sharing the one ~2 GB dataset) and drop found | ||
| 3964 | // shares on a queue the I/O thread drains and submits. | ||
| 3965 | // ------------------------------------------------------------------ | ||
| 3966 | struct ParticipantJobShared | ||
| 3967 | { | ||
| 3968 | std::mutex mtx; | ||
| 3969 | unsigned char header[80]; | ||
| 3970 | uint256 target; | ||
| 3971 | std::string jobId; | ||
| 3972 | uint64 jobSeq; | ||
| 3973 | bool haveJob; | ||
| 3974 | bool stop; | ||
| 3975 | ParticipantJobShared() : jobSeq(0), haveJob(false), stop(false) | ||
| 3976 | { | ||
| 3977 | memset(header, 0, sizeof(header)); | ||
| 3978 | } | ||
| 3979 | }; | ||
| 3980 | |||
| 3981 | struct ParticipantShareHit | ||
| 3982 | { | ||
| 3983 | uint64 jobSeq; | ||
| 3984 | std::string jobId; | ||
| 3985 | unsigned int nonce; | ||
| 3986 | }; | ||
| 3987 | |||
| 3988 | static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorkers, | ||
| 3989 | std::vector<ParticipantShareHit>* hits, | ||
| 3990 | std::mutex* hitsMtx) | ||
| 3991 | { | ||
| 3992 | void* vm = RandomXCreateMinerVM(); | ||
| 3993 | if (!vm) | ||
| 3994 | { | ||
| 3995 | LogPrint("worker", "[worker] hash thread %d: RandomX VM creation failed\n", idx); | ||
| 3996 | return; | ||
| 3997 | } | ||
| 3998 | |||
| 3999 | unsigned char header[80]; | ||
| 4000 | uint256 target; | ||
| 4001 | std::string jobId; | ||
| 4002 | uint64 mySeq = 0; | ||
| 4003 | bool active = false; | ||
| 4004 | unsigned int nNonce = 0; | ||
| 4005 | uint64 localHashes = 0; | ||
| 4006 | |||
| 4007 | while (!fShutdown) | ||
| 4008 | { | ||
| 4009 | { | ||
| 4010 | std::lock_guard<std::mutex> lk(sh->mtx); | ||
| 4011 | if (sh->stop) | ||
| 4012 | break; | ||
| 4013 | if (sh->haveJob && sh->jobSeq != mySeq) | ||
| 4014 | { | ||
| 4015 | memcpy(header, sh->header, 80); | ||
| 4016 | target = sh->target; | ||
| 4017 | jobId = sh->jobId; | ||
| 4018 | mySeq = sh->jobSeq; | ||
| 4019 | nNonce = (unsigned int)(idx + 1); | ||
| 4020 | active = true; | ||
| 4021 | } | ||
| 4022 | } | ||
| 4023 | if (!active) | ||
| 4024 | { | ||
| 4025 | Sleep(50); | ||
| 4026 | continue; | ||
| 4027 | } | ||
| 4028 | |||
| 4029 | memcpy(header + 76, &nNonce, 4); | ||
| 4030 | uint256 hash = RandomXHashWithVM(vm, header, 80); | ||
| 4031 | localHashes++; | ||
| 4032 | if ((localHashes & 0x3ff) == 0) | ||
| 4033 | gParticipantHashes.fetch_add(0x400); | ||
| 4034 | |||
| 4035 | if (hash <= target) | ||
| 4036 | { | ||
| 4037 | LogPrint("worker", "[worker] share found! nonce=%u job=%s (thread %d)\n", | ||
| 4038 | nNonce, jobId.c_str(), idx); | ||
| 4039 | std::lock_guard<std::mutex> lk(*hitsMtx); | ||
| 4040 | hits->push_back(ParticipantShareHit{mySeq, jobId, nNonce}); | ||
| 4041 | } | ||
| 4042 | |||
| 4043 | unsigned int next = nNonce + (unsigned int)nWorkers; | ||
| 4044 | if (next < nNonce) // wrapped past 2^32: range exhausted, wait for next job | ||
| 4045 | active = false; | ||
| 4046 | else | ||
| 4047 | nNonce = next; | ||
| 4048 | } | ||
| 4049 | |||
| 4050 | gParticipantHashes.fetch_add(localHashes & 0x3ff); | ||
| 4051 | RandomXDestroyMinerVM(vm); | ||
| 4052 | } | ||
| 4053 | |||
| 4054 | // Run one participant session: resolve the pool, open the tunnel, subscribe, | ||
| 4055 | // hash, submit shares. Returns true only if the pool actually handed out work | ||
| 4056 | // -- at least one well-formed mining.notify arrived. Everything else is false, | ||
| 4057 | // including a tunnel that opened and was dropped before any job. | ||
| 4058 | // | ||
| 4059 | // The caller's reconnect backoff keys off this return value, so any path added | ||
| 4060 | // here that leaves the session without work MUST return false. "The session | ||
| 4061 | // ended tidily" is not the question being asked. | ||
| 3576 | static bool PoolParticipantMiner() | 4062 | static bool PoolParticipantMiner() |
| 3577 | { | 4063 | { |
| 3578 | // ------------------------------------------------------------------ | 4064 | // ------------------------------------------------------------------ |
| @@ -3587,6 +4073,51 @@ static bool PoolParticipantMiner() | |||
| 3587 | LogPrint("worker", "[worker] connecting to pool %s\n", strParticipantPool.c_str()); | 4073 | LogPrint("worker", "[worker] connecting to pool %s\n", strParticipantPool.c_str()); |
| 3588 | SetParticipantMiningStatus("resolving pool address"); | 4074 | SetParticipantMiningStatus("resolving pool address"); |
| 3589 | 4075 | ||
| 4076 | // ------------------------------------------------------------------ | ||
| 4077 | // Steps 1-2: connect to the pool. Two address forms: | ||
| 4078 | // tcp:HOST:PORT -- plain TCP straight to the pool's stratum port | ||
| 4079 | // (e.g. our own operator node on this machine), | ||
| 4080 | // no .btf resolution or rendezvous tunnel involved | ||
| 4081 | // <name>.btf -- resolve via Nostr, dial through a rendezvous relay | ||
| 4082 | // ------------------------------------------------------------------ | ||
| 4083 | btf_socket_t s = INVALID_SOCKET; | ||
| 4084 | if (strParticipantPool.compare(0, 4, "tcp:") == 0) | ||
| 4085 | { | ||
| 4086 | std::string hp = strParticipantPool.substr(4); | ||
| 4087 | size_t colon = hp.rfind(':'); | ||
| 4088 | if (colon == std::string::npos) { | ||
| 4089 | LogPrint("worker", "[worker] bad tcp pool address '%s' (want tcp:host:port)\n", | ||
| 4090 | strParticipantPool.c_str()); | ||
| 4091 | SetParticipantMiningStatus("bad pool address"); | ||
| 4092 | return false; | ||
| 4093 | } | ||
| 4094 | std::string host = hp.substr(0, colon); | ||
| 4095 | int port = atoi(hp.substr(colon + 1).c_str()); | ||
| 4096 | unsigned long ip = inet_addr(host.c_str()); | ||
| 4097 | if (port <= 0 || port > 65535 || ip == INADDR_NONE) { | ||
| 4098 | LogPrint("worker", "[worker] bad tcp pool endpoint '%s'\n", hp.c_str()); | ||
| 4099 | SetParticipantMiningStatus("bad pool endpoint"); | ||
| 4100 | return false; | ||
| 4101 | } | ||
| 4102 | SetParticipantMiningStatus("connecting to pool (direct tcp)"); | ||
| 4103 | struct sockaddr_in sa; | ||
| 4104 | memset(&sa, 0, sizeof(sa)); | ||
| 4105 | sa.sin_family = AF_INET; | ||
| 4106 | sa.sin_port = htons((unsigned short)port); | ||
| 4107 | sa.sin_addr.s_addr = ip; | ||
| 4108 | SOCKET cs = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
| 4109 | if (cs == INVALID_SOCKET || connect(cs, (struct sockaddr*)&sa, sizeof(sa)) == SOCKET_ERROR) { | ||
| 4110 | LogPrint("worker", "[worker] direct connect to %s FAILED\n", hp.c_str()); | ||
| 4111 | SetParticipantMiningStatus("pool connect failed"); | ||
| 4112 | if (cs != INVALID_SOCKET) closesocket(cs); | ||
| 4113 | return false; | ||
| 4114 | } | ||
| 4115 | LogPrint("worker", "[worker] connected directly to pool %s\n", hp.c_str()); | ||
| 4116 | s = (btf_socket_t)cs; | ||
| 4117 | goto connected; | ||
| 4118 | } | ||
| 4119 | |||
| 4120 | { | ||
| 3590 | unsigned char target_pubkey[32]; | 4121 | unsigned char target_pubkey[32]; |
| 3591 | if (!btf::ParseAddress(strParticipantPool, target_pubkey)) { | 4122 | if (!btf::ParseAddress(strParticipantPool, target_pubkey)) { |
| 3592 | LogPrint("worker", "[worker] invalid .btf address: %s\n", strParticipantPool.c_str()); | 4123 | LogPrint("worker", "[worker] invalid .btf address: %s\n", strParticipantPool.c_str()); |
| @@ -3605,6 +4136,33 @@ static bool PoolParticipantMiner() | |||
| 3605 | } | 4136 | } |
| 3606 | LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); | 4137 | LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); |
| 3607 | 4138 | ||
| 4139 | // The descriptor names ONE meeting node, but the pool parks a | ||
| 4140 | // registration at every relay it knows (one accept thread per relay, | ||
| 4141 | // rpc.cpp:AcceptOneFn), so any seed relay can pair us. Insisting on the | ||
| 4142 | // advertised entry forever turns one bad path into a miner that idles | ||
| 4143 | // indefinitely while a working path exists (issue #17); after a few | ||
| 4144 | // consecutive tunnel failures, rotate the seed relays into the attempts | ||
| 4145 | // too. Only ONE participant session runs (StartMinerThreads), so these | ||
| 4146 | // statics are single-threaded. | ||
| 4147 | static const int TUNNEL_FAILOVER_AFTER = 3; | ||
| 4148 | static int nTunnelFails = 0; // consecutive tunnel failures, this pool | ||
| 4149 | static size_t iFailoverRelay = 0; // rotation cursor over vBtfMeetingRelays | ||
| 4150 | static std::string strFailPool; // pool the counters belong to | ||
| 4151 | if (strFailPool != strParticipantPool) { | ||
| 4152 | strFailPool = strParticipantPool; | ||
| 4153 | nTunnelFails = 0; | ||
| 4154 | iFailoverRelay = 0; | ||
| 4155 | } | ||
| 4156 | if (nTunnelFails >= TUNNEL_FAILOVER_AFTER && !vBtfMeetingRelays.empty()) { | ||
| 4157 | std::string alt = vBtfMeetingRelays[iFailoverRelay++ % vBtfMeetingRelays.size()]; | ||
| 4158 | if (alt != meetingHostPort) { | ||
| 4159 | LogPrint("worker", "[worker] advertised meeting node %s failed %d" | ||
| 4160 | " times in a row -- trying seed relay %s instead\n", | ||
| 4161 | meetingHostPort.c_str(), nTunnelFails, alt.c_str()); | ||
| 4162 | meetingHostPort = alt; | ||
| 4163 | } | ||
| 4164 | } | ||
| 4165 | |||
| 3608 | size_t colon = meetingHostPort.rfind(':'); | 4166 | size_t colon = meetingHostPort.rfind(':'); |
| 3609 | if (colon == std::string::npos) { | 4167 | if (colon == std::string::npos) { |
| 3610 | LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); | 4168 | LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); |
| @@ -3621,25 +4179,65 @@ static bool PoolParticipantMiner() | |||
| 3621 | } | 4179 | } |
| 3622 | 4180 | ||
| 3623 | // ------------------------------------------------------------------ | 4181 | // ------------------------------------------------------------------ |
| 3624 | // Step 2: open encrypted tunnel to pool | 4182 | // Step 2b: open encrypted tunnel to pool |
| 3625 | // ------------------------------------------------------------------ | 4183 | // ------------------------------------------------------------------ |
| 3626 | SetParticipantMiningStatus("opening tunnel"); | 4184 | SetParticipantMiningStatus("opening tunnel"); |
| 3627 | btf_socket_t s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, | 4185 | btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE; |
| 3628 | target_pubkey, service_enc_pub); | 4186 | s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, |
| 4187 | target_pubkey, service_enc_pub, &tunnelFail); | ||
| 3629 | if (s == INVALID_SOCKET) { | 4188 | if (s == INVALID_SOCKET) { |
| 3630 | LogPrint("worker", "[worker] tunnel connect FAILED via %s" | 4189 | nTunnelFails++; |
| 3631 | " (relay unreachable or pool not listening)\n", | 4190 | LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n", |
| 3632 | meetingHostPort.c_str()); | 4191 | meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail)); |
| 3633 | SetParticipantMiningStatus("tunnel connect failed"); | 4192 | SetParticipantMiningStatus(std::string("tunnel connect failed: ") |
| 4193 | + btf::TunnelFailText(tunnelFail)); | ||
| 3634 | return false; | 4194 | return false; |
| 3635 | } | 4195 | } |
| 4196 | nTunnelFails = 0; | ||
| 3636 | LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); | 4197 | LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); |
| 3637 | SetParticipantMiningStatus("tunnel open"); | 4198 | SetParticipantMiningStatus("tunnel open"); |
| 4199 | } | ||
| 4200 | |||
| 4201 | connected: | ||
| 3638 | 4202 | ||
| 3639 | // ------------------------------------------------------------------ | 4203 | // ------------------------------------------------------------------ |
| 3640 | // Step 3: determine our payout address (mining username) | 4204 | // Step 3: determine our payout address (mining username). /payoutaddr |
| 4205 | // wins -- it lets a machine mine to an address whose keys live | ||
| 4206 | // elsewhere, so no wallet file is needed on the mining machine. | ||
| 4207 | // /payoutfile rotates through a list of such addresses: one is picked | ||
| 4208 | // per (re)connection, and the session self-limits (see rotation break | ||
| 4209 | // in the main loop) so consecutive rounds land on fresh addresses. | ||
| 3641 | // ------------------------------------------------------------------ | 4210 | // ------------------------------------------------------------------ |
| 3642 | std::string username; | 4211 | static std::vector<std::string> vPayoutAddrs; |
| 4212 | static size_t nPayoutRot = 0; | ||
| 4213 | if (!strPayoutFile.empty() && vPayoutAddrs.empty()) | ||
| 4214 | { | ||
| 4215 | FILE* f = fopen(strPayoutFile.c_str(), "r"); | ||
| 4216 | if (f) | ||
| 4217 | { | ||
| 4218 | char line[256]; | ||
| 4219 | while (fgets(line, sizeof(line), f)) | ||
| 4220 | { | ||
| 4221 | std::string a = line; | ||
| 4222 | while (!a.empty() && (a.back() == '\n' || a.back() == '\r' || | ||
| 4223 | a.back() == ' ' || a.back() == '\t')) | ||
| 4224 | a.pop_back(); | ||
| 4225 | if (!a.empty()) | ||
| 4226 | vPayoutAddrs.push_back(a); | ||
| 4227 | } | ||
| 4228 | fclose(f); | ||
| 4229 | LogPrint("worker", "[worker] loaded %zu payout addresses from %s\n", | ||
| 4230 | vPayoutAddrs.size(), strPayoutFile.c_str()); | ||
| 4231 | } | ||
| 4232 | else | ||
| 4233 | LogPrint("worker", "[worker] cannot open payout file %s\n", | ||
| 4234 | strPayoutFile.c_str()); | ||
| 4235 | } | ||
| 4236 | |||
| 4237 | std::string username = strPayoutAddress; | ||
| 4238 | if (username.empty() && !vPayoutAddrs.empty()) | ||
| 4239 | username = vPayoutAddrs[nPayoutRot++ % vPayoutAddrs.size()]; | ||
| 4240 | if (username.empty()) | ||
| 3643 | { | 4241 | { |
| 3644 | std::vector<unsigned char> vchPubKey; | 4242 | std::vector<unsigned char> vchPubKey; |
| 3645 | if (CWalletDB("r").ReadDefaultKey(vchPubKey)) | 4243 | if (CWalletDB("r").ReadDefaultKey(vchPubKey)) |
| @@ -3677,16 +4275,14 @@ static bool PoolParticipantMiner() | |||
| 3677 | // ------------------------------------------------------------------ | 4275 | // ------------------------------------------------------------------ |
| 3678 | if (!RandomXFastReady()) { | 4276 | if (!RandomXFastReady()) { |
| 3679 | SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); | 4277 | SetParticipantMiningStatus("building RandomX dataset (~2GB, one-time)"); |
| 3680 | unsigned int nThreads = std::thread::hardware_concurrency(); | 4278 | // Fill with the cores we are permitted to use, not the ones the host |
| 4279 | // happens to have. More threads than the cpuset allows does not build | ||
| 4280 | // the dataset any faster -- the CPU budget is the same either way -- | ||
| 4281 | // it just oversubscribes, and this runs alongside the pool node. | ||
| 4282 | unsigned int nThreads = AvailableCoreCount(); | ||
| 3681 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); | 4283 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); |
| 3682 | } | 4284 | } |
| 3683 | void* rxvm = RandomXCreateMinerVM(); | 4285 | LogPrint("worker", "[worker] RandomX dataset ready (%s)\n", |
| 3684 | if (!rxvm) { | ||
| 3685 | LogPrint("worker", "[worker] failed to create RandomX VM\n"); | ||
| 3686 | SetParticipantMiningStatus("RandomX init failed"); | ||
| 3687 | closesocket(s); return false; | ||
| 3688 | } | ||
| 3689 | LogPrint("worker", "[worker] RandomX VM ready (%s)\n", | ||
| 3690 | RandomXFastReady() ? "fast 2GB" : "light 256MB"); | 4286 | RandomXFastReady() ? "fast 2GB" : "light 256MB"); |
| 3691 | 4287 | ||
| 3692 | // ------------------------------------------------------------------ | 4288 | // ------------------------------------------------------------------ |
| @@ -3704,7 +4300,6 @@ static bool PoolParticipantMiner() | |||
| 3704 | {"params",json::array()}})) { | 4300 | {"params",json::array()}})) { |
| 3705 | LogPrint("worker", "[worker] subscribe send FAILED -- pool dropped connection\n"); | 4301 | LogPrint("worker", "[worker] subscribe send FAILED -- pool dropped connection\n"); |
| 3706 | SetParticipantMiningStatus("subscribe send failed"); | 4302 | SetParticipantMiningStatus("subscribe send failed"); |
| 3707 | RandomXDestroyMinerVM(rxvm); | ||
| 3708 | closesocket(s); return false; | 4303 | closesocket(s); return false; |
| 3709 | } | 4304 | } |
| 3710 | 4305 | ||
| @@ -3714,18 +4309,70 @@ static bool PoolParticipantMiner() | |||
| 3714 | {"params",json::array({username,"x"})}})) { | 4309 | {"params",json::array({username,"x"})}})) { |
| 3715 | LogPrint("worker", "[worker] authorize send FAILED -- pool dropped connection\n"); | 4310 | LogPrint("worker", "[worker] authorize send FAILED -- pool dropped connection\n"); |
| 3716 | SetParticipantMiningStatus("authorize send failed"); | 4311 | SetParticipantMiningStatus("authorize send failed"); |
| 3717 | RandomXDestroyMinerVM(rxvm); | ||
| 3718 | closesocket(s); return false; | 4312 | closesocket(s); return false; |
| 3719 | } | 4313 | } |
| 3720 | 4314 | ||
| 3721 | // ------------------------------------------------------------------ | 4315 | // ------------------------------------------------------------------ |
| 4316 | // Step 6: spawn hash threads (each with its own RandomX VM on the | ||
| 4317 | // shared dataset), then run the I/O loop below: receive pool messages, | ||
| 4318 | // publish jobs to the threads, submit the shares they find. | ||
| 4319 | // ------------------------------------------------------------------ | ||
| 4320 | // One count, decided in one place (main.cpp:MinerThreadCount's neighbour), | ||
| 4321 | // so that what StartMinerThreads() reports at startup and what actually | ||
| 4322 | // hashes here can never drift apart. Explicit /hashthreads=N is honored | ||
| 4323 | // exactly, including 1; automatic leaves one core for node + I/O threads. | ||
| 4324 | int nWorkers = ParticipantHashThreadCount(); | ||
| 4325 | ParticipantJobShared jobShared; | ||
| 4326 | std::vector<ParticipantShareHit> shareHits; | ||
| 4327 | std::mutex shareHitsMtx; | ||
| 4328 | std::vector<std::thread> hashThreads; | ||
| 4329 | try | ||
| 4330 | { | ||
| 4331 | for (int i = 0; i < nWorkers; i++) | ||
| 4332 | hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, | ||
| 4333 | &shareHits, &shareHitsMtx); | ||
| 4334 | } | ||
| 4335 | catch (const std::system_error& e) | ||
| 4336 | { | ||
| 4337 | // A refused pthread_create used to reach std::terminate here and | ||
| 4338 | // kill the worker mid-session -- observed twice on an otherwise | ||
| 4339 | // healthy participant. Stop the workers that did start the same way | ||
| 4340 | // the disconnect path below does, drop this pool connection, and let | ||
| 4341 | // the caller's reconnect loop try again once the pressure clears. | ||
| 4342 | LogPrint("worker", "[worker] cannot spawn hash threads (%s)" | ||
| 4343 | " -- dropping pool connection\n", e.what()); | ||
| 4344 | SetParticipantMiningStatus("hash thread spawn failed"); | ||
| 4345 | { | ||
| 4346 | std::lock_guard<std::mutex> lk(jobShared.mtx); | ||
| 4347 | jobShared.stop = true; | ||
| 4348 | jobShared.haveJob = false; | ||
| 4349 | } | ||
| 4350 | for (std::thread& t : hashThreads) | ||
| 4351 | t.join(); | ||
| 4352 | closesocket(s); | ||
| 4353 | return false; | ||
| 4354 | } | ||
| 4355 | LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", | ||
| 4356 | nWorkers); | ||
| 4357 | const uint64 sessionStartHashes = gParticipantHashes.load(); | ||
| 4358 | |||
| 4359 | // ------------------------------------------------------------------ | ||
| 3722 | // Step 6: main loop -- receive messages, hash, submit shares | 4360 | // Step 6: main loop -- receive messages, hash, submit shares |
| 3723 | // ------------------------------------------------------------------ | 4361 | // ------------------------------------------------------------------ |
| 3724 | std::string jobId; | 4362 | std::string jobId; |
| 3725 | unsigned char header[80] = {}; | 4363 | unsigned char header[80] = {}; |
| 3726 | uint256 target; | 4364 | uint256 target; |
| 4365 | // Cached answer to "which block is this job built on", for the | ||
| 4366 | // mining.notify handler below -- see the comment there. Session-local, so | ||
| 4367 | // a reconnect re-resolves rather than trusting an answer obtained before | ||
| 4368 | // a possible change of pool. | ||
| 4369 | uint256 hashPrevResolved; // prevhash the cached answer describes | ||
| 4370 | bool fPrevResolved = false; // whether that answer exists yet | ||
| 4371 | int nPrevHeight = -1; // its height, or -1 == unknown to us | ||
| 4372 | int nBestHeightResolved = -1; // nBestHeight when we last resolved | ||
| 3727 | double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool | 4373 | double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool |
| 3728 | bool haveJob = false; | 4374 | bool haveJob = false; |
| 4375 | bool gotAnyJob = false; // any job at all this session -- the return value | ||
| 3729 | bool gotSubscribeAck = false; | 4376 | bool gotSubscribeAck = false; |
| 3730 | bool gotAuthorizeAck = false; | 4377 | bool gotAuthorizeAck = false; |
| 3731 | int64 authWaitStart = 0; | 4378 | int64 authWaitStart = 0; |
| @@ -3735,6 +4382,8 @@ static bool PoolParticipantMiner() | |||
| 3735 | uint64 sessionSharesSent = 0; // this connection only | 4382 | uint64 sessionSharesSent = 0; // this connection only |
| 3736 | uint64 sessionSharesAccepted = 0; | 4383 | uint64 sessionSharesAccepted = 0; |
| 3737 | int64 sampleStart = GetTime(); | 4384 | int64 sampleStart = GetTime(); |
| 4385 | int64 connStart = GetTime(); | ||
| 4386 | const int64 PAYOUT_ROTATE_SECS = 300; // reconnect with next /payoutfile address | ||
| 3738 | 4387 | ||
| 3739 | SetParticipantMiningStatus("waiting for pool response"); | 4388 | SetParticipantMiningStatus("waiting for pool response"); |
| 3740 | 4389 | ||
| @@ -3840,10 +4489,75 @@ static bool PoolParticipantMiner() | |||
| 3840 | jobId = newJobId; | 4489 | jobId = newJobId; |
| 3841 | memcpy(header, hdrBytes.data(), 80); | 4490 | memcpy(header, hdrBytes.data(), 80); |
| 3842 | memcpy(&target, tgtBytes.data(), 32); | 4491 | memcpy(&target, tgtBytes.data(), 32); |
| 3843 | haveJob = true; | 4492 | haveJob = true; |
| 3844 | nNonce = 1; | 4493 | gotAnyJob = true; |
| 3845 | LogPrint("worker", "[pool->worker] new job %s received\n", | 4494 | nNonce = 1; |
| 3846 | jobId.c_str()); | 4495 | { |
| 4496 | std::lock_guard<std::mutex> lk(jobShared.mtx); | ||
| 4497 | memcpy(jobShared.header, header, 80); | ||
| 4498 | jobShared.target = target; | ||
| 4499 | jobShared.jobId = jobId; | ||
| 4500 | jobShared.jobSeq++; | ||
| 4501 | jobShared.haveJob = true; | ||
| 4502 | } | ||
| 4503 | // Log the chain position the pool is | ||
| 4504 | // building on, so a stale or private | ||
| 4505 | // fork is auditable from worker logs. | ||
| 4506 | // The prevhash comes from the header we | ||
| 4507 | // actually hash (== p[2] of the notify); | ||
| 4508 | // a brief cs_main lookup is safe here -- | ||
| 4509 | // this thread holds no other lock. | ||
| 4510 | // | ||
| 4511 | // Resolving it is the expensive half, and | ||
| 4512 | // this is the socket-reading thread: block | ||
| 4513 | // validation can hold cs_main for a long | ||
| 4514 | // time, and every moment spent waiting on | ||
| 4515 | // it here is a moment set_difficulty and | ||
| 4516 | // share responses are not being handled. | ||
| 4517 | // Most notifies in a round are a new job | ||
| 4518 | // on the SAME tip, so the answer is cached | ||
| 4519 | // and re-resolved only when it could come | ||
| 4520 | // out differently: a prevhash we have not | ||
| 4521 | // resolved, or one we could NOT resolve | ||
| 4522 | // last time while the chain has since | ||
| 4523 | // moved (unknown -> known). A block's | ||
| 4524 | // height is fixed once mapBlockIndex knows | ||
| 4525 | // it, so a cached "known" never goes | ||
| 4526 | // stale. nBestHeight is read unlocked on | ||
| 4527 | // purpose -- it only decides whether to | ||
| 4528 | // repeat a lookup, never what gets | ||
| 4529 | // printed, so a stale read costs at most | ||
| 4530 | // one deferred or one extra lookup. | ||
| 4531 | // The line still prints in full for every | ||
| 4532 | // job, cached or not. | ||
| 4533 | uint256 hashPrev; | ||
| 4534 | memcpy(&hashPrev, header + 4, 32); | ||
| 4535 | if (LogAcceptsCategory("worker")) | ||
| 4536 | { | ||
| 4537 | int nBestHeightNow = nBestHeight; | ||
| 4538 | if (!fPrevResolved | ||
| 4539 | || hashPrev != hashPrevResolved | ||
| 4540 | || (nPrevHeight < 0 && nBestHeightNow != nBestHeightResolved)) | ||
| 4541 | { | ||
| 4542 | nPrevHeight = -1; | ||
| 4543 | CRITICAL_BLOCK(cs_main) | ||
| 4544 | { | ||
| 4545 | map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrev); | ||
| 4546 | if (mi != mapBlockIndex.end()) | ||
| 4547 | nPrevHeight = (*mi).second->nHeight; | ||
| 4548 | } | ||
| 4549 | hashPrevResolved = hashPrev; | ||
| 4550 | nBestHeightResolved = nBestHeightNow; | ||
| 4551 | fPrevResolved = true; | ||
| 4552 | } | ||
| 4553 | if (nPrevHeight >= 0) | ||
| 4554 | LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n", | ||
| 4555 | jobId.c_str(), hashPrev.GetHex().c_str(), | ||
| 4556 | nPrevHeight + 1); | ||
| 4557 | else | ||
| 4558 | LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n", | ||
| 4559 | jobId.c_str(), hashPrev.GetHex().c_str()); | ||
| 4560 | } | ||
| 3847 | SetParticipantMiningStatus("hashing job " + jobId); | 4561 | SetParticipantMiningStatus("hashing job " + jobId); |
| 3848 | } | 4562 | } |
| 3849 | } | 4563 | } |
| @@ -3897,46 +4611,71 @@ static bool PoolParticipantMiner() | |||
| 3897 | } | 4611 | } |
| 3898 | } | 4612 | } |
| 3899 | 4613 | ||
| 4614 | // -- Payout address rotation: end this session cleanly so the outer | ||
| 4615 | // loop reconnects and authorizes the next /payoutfile address -- | ||
| 4616 | if (!vPayoutAddrs.empty() && GetTime() - connStart > PAYOUT_ROTATE_SECS) | ||
| 4617 | { | ||
| 4618 | LogPrint("worker", "[worker] payout rotation interval reached" | ||
| 4619 | " -- reconnecting with next address\n"); | ||
| 4620 | break; | ||
| 4621 | } | ||
| 4622 | |||
| 3900 | if (!haveJob) { Sleep(100); continue; } | 4623 | if (!haveJob) { Sleep(100); continue; } |
| 3901 | 4624 | ||
| 3902 | // -- Hash one nonce -- | 4625 | // -- Hash rate accounting (hashing happens on the worker threads) -- |
| 3903 | memcpy(header+76, &nNonce, 4); | 4626 | hashesSinceSample = gParticipantHashes.load() - sessionStartHashes; |
| 3904 | uint256 hash = RandomXHashWithVM(rxvm, header, 80); | 4627 | UpdateParticipantHashRate(hashesSinceSample, sampleStart); |
| 3905 | hashesSinceSample++; | ||
| 3906 | gParticipantHashes.fetch_add(1); | ||
| 3907 | if ((hashesSinceSample & 0x3ff) == 0) | ||
| 3908 | UpdateParticipantHashRate(hashesSinceSample, sampleStart); | ||
| 3909 | |||
| 3910 | if (hash <= target) { | ||
| 3911 | LogPrint("worker", "[worker] share found! nonce=%u job=%s difficulty=%.4f\n", | ||
| 3912 | nNonce, jobId.c_str(), currentDifficulty); | ||
| 3913 | SetParticipantMiningStatus("share found; submitting"); | ||
| 3914 | int submitId = msgId++; | ||
| 3915 | pendingSubmitIds.insert(submitId); | ||
| 3916 | gParticipantSharesSent.fetch_add(1); | ||
| 3917 | sessionSharesSent++; | ||
| 3918 | json submit = {{"id",submitId},{"method","mining.submit"}, | ||
| 3919 | {"params",json::array({username, jobId, ToHexStr(&nNonce,4)})}}; | ||
| 3920 | if (!StratumSendLine(s, submit)) { | ||
| 3921 | LogPrint("worker", "[worker] share submit send FAILED" | ||
| 3922 | " -- pool connection lost\n"); | ||
| 3923 | break; | ||
| 3924 | } | ||
| 3925 | } | ||
| 3926 | 4628 | ||
| 3927 | nNonce++; | 4629 | // -- Submit any shares the hash threads found -- |
| 3928 | if (nNonce == 0) { | 4630 | bool submitFailed = false; |
| 3929 | LogPrint("worker", "[worker] nonce space exhausted for job %s" | 4631 | { |
| 3930 | " -- waiting for new job\n", jobId.c_str()); | 4632 | std::vector<ParticipantShareHit> pending; |
| 3931 | haveJob = false; | 4633 | { |
| 4634 | std::lock_guard<std::mutex> lk(shareHitsMtx); | ||
| 4635 | pending.swap(shareHits); | ||
| 4636 | } | ||
| 4637 | for (ParticipantShareHit& hit : pending) | ||
| 4638 | { | ||
| 4639 | if (hit.jobId != jobId) | ||
| 4640 | { | ||
| 4641 | LogPrint("worker", "[worker] dropping stale share for old job %s\n", | ||
| 4642 | hit.jobId.c_str()); | ||
| 4643 | continue; | ||
| 4644 | } | ||
| 4645 | LogPrint("worker", "[worker] share found! nonce=%u job=%s difficulty=%.4f\n", | ||
| 4646 | hit.nonce, hit.jobId.c_str(), currentDifficulty); | ||
| 4647 | SetParticipantMiningStatus("share found; submitting"); | ||
| 4648 | int submitId = msgId++; | ||
| 4649 | pendingSubmitIds.insert(submitId); | ||
| 4650 | gParticipantSharesSent.fetch_add(1); | ||
| 4651 | sessionSharesSent++; | ||
| 4652 | json submit = {{"id",submitId},{"method","mining.submit"}, | ||
| 4653 | {"params",json::array({username, hit.jobId, | ||
| 4654 | ToHexStr(&hit.nonce,4)})}}; | ||
| 4655 | if (!StratumSendLine(s, submit)) { | ||
| 4656 | LogPrint("worker", "[worker] share submit send FAILED" | ||
| 4657 | " -- pool connection lost\n"); | ||
| 4658 | submitFailed = true; | ||
| 4659 | break; | ||
| 4660 | } | ||
| 4661 | } | ||
| 3932 | } | 4662 | } |
| 4663 | if (submitFailed) | ||
| 4664 | break; | ||
| 3933 | } | 4665 | } |
| 3934 | 4666 | ||
| 3935 | // ------------------------------------------------------------------ | 4667 | // ------------------------------------------------------------------ |
| 3936 | // Cleanup | 4668 | // Cleanup: stop the hash threads, then drop the pool connection |
| 3937 | // ------------------------------------------------------------------ | 4669 | // ------------------------------------------------------------------ |
| 3938 | RandomXDestroyMinerVM(rxvm); | 4670 | { |
| 4671 | std::lock_guard<std::mutex> lk(jobShared.mtx); | ||
| 4672 | jobShared.stop = true; | ||
| 4673 | jobShared.haveJob = false; | ||
| 4674 | } | ||
| 4675 | for (std::thread& t : hashThreads) | ||
| 4676 | t.join(); | ||
| 3939 | closesocket(s); | 4677 | closesocket(s); |
| 4678 | hashesSinceSample = gParticipantHashes.load() - sessionStartHashes; | ||
| 3940 | UpdateParticipantHashRate(hashesSinceSample, sampleStart); | 4679 | UpdateParticipantHashRate(hashesSinceSample, sampleStart); |
| 3941 | SetParticipantMiningStatus("disconnected"); | 4680 | SetParticipantMiningStatus("disconnected"); |
| 3942 | LogPrint("worker", "[worker] disconnected from pool" | 4681 | LogPrint("worker", "[worker] disconnected from pool" |
| @@ -3944,7 +4683,11 @@ static bool PoolParticipantMiner() | |||
| 3944 | (unsigned long long)hashesSinceSample, | 4683 | (unsigned long long)hashesSinceSample, |
| 3945 | (unsigned long long)sessionSharesSent, | 4684 | (unsigned long long)sessionSharesSent, |
| 3946 | (unsigned long long)sessionSharesAccepted); | 4685 | (unsigned long long)sessionSharesAccepted); |
| 3947 | return true; | 4686 | // Not "we got here", which is true of a tunnel the pool accepted and then |
| 4687 | // dropped without ever sending a job -- exactly the case the caller's | ||
| 4688 | // backoff exists for. Reaching the end of the session is not evidence the | ||
| 4689 | // session was worth anything; a job is. | ||
| 4690 | return gotAnyJob; | ||
| 3948 | } | 4691 | } |
| 3949 | 4692 | ||
| 3950 | // --------------------------------------------------------------------------- | 4693 | // --------------------------------------------------------------------------- |
| @@ -4141,11 +4884,51 @@ bool BitcoinMiner(int nThreadId) | |||
| 4141 | // Participant mode: connect to external pool instead of mining solo/operator | 4884 | // Participant mode: connect to external pool instead of mining solo/operator |
| 4142 | if (nMineMode == MINE_PARTICIPANT) | 4885 | if (nMineMode == MINE_PARTICIPANT) |
| 4143 | { | 4886 | { |
| 4887 | // An attempt that never got work backs off (10s doubling to 120s) | ||
| 4888 | // instead of hammering the same path every flat 10s, and a streak | ||
| 4889 | // lasting minutes is said at printf level: the identical once-per-10s | ||
| 4890 | // LogPrint line is how a worker idled all night unnoticed (issue #17). | ||
| 4891 | // | ||
| 4892 | // Only a session the pool actually sent a job to resets either. The | ||
| 4893 | // weaker test -- "the session returned" -- was gameable: a pool that | ||
| 4894 | // completes the tunnel and drops without ever sending work leaves by | ||
| 4895 | // the same tidy cleanup path a healthy one does, which would pin the | ||
| 4896 | // retry at 10s forever and defeat the backoff in exactly the scenario | ||
| 4897 | // it exists for. | ||
| 4898 | int nRetrySecs = 10; | ||
| 4899 | int64 nFailingSince = 0; | ||
| 4144 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | 4900 | while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { |
| 4145 | PoolParticipantMiner(); | 4901 | bool fGotWork = PoolParticipantMiner(); |
| 4146 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { | 4902 | if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { |
| 4147 | LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); | 4903 | if (fGotWork) { |
| 4148 | for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); | 4904 | nRetrySecs = 10; |
| 4905 | nFailingSince = 0; | ||
| 4906 | } else { | ||
| 4907 | if (nFailingSince == 0) | ||
| 4908 | nFailingSince = GetTime(); | ||
| 4909 | int64 nMins = (GetTime() - nFailingSince) / 60; | ||
| 4910 | if (nMins >= 1) | ||
| 4911 | printf("[worker] connection to pool %s has been failing" | ||
| 4912 | " for %lld minutes\n", | ||
| 4913 | strParticipantPool.c_str(), (long long)nMins); | ||
| 4914 | } | ||
| 4915 | LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n", | ||
| 4916 | nRetrySecs); | ||
| 4917 | // Poll everything the enclosing while polls, not just | ||
| 4918 | // fShutdown. Raising the ceiling to 120s made a | ||
| 4919 | // shutdown-only predicate expensive: after the operator | ||
| 4920 | // clears Generate or switches to Relay, the session -- and | ||
| 4921 | // the vfThreadRunning[3] slot that gates restarting it -- | ||
| 4922 | // would stay held for up to two minutes. Measured 119s | ||
| 4923 | // before, 1s after. | ||
| 4924 | for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins | ||
| 4925 | && nMineMode == MINE_PARTICIPANT; i++) | ||
| 4926 | Sleep(1000); | ||
| 4927 | if (!fGotWork && nRetrySecs < 120) { | ||
| 4928 | nRetrySecs *= 2; | ||
| 4929 | if (nRetrySecs > 120) | ||
| 4930 | nRetrySecs = 120; | ||
| 4931 | } | ||
| 4149 | } | 4932 | } |
| 4150 | } | 4933 | } |
| 4151 | return true; | 4934 | return true; |
| @@ -4161,7 +4944,7 @@ bool BitcoinMiner(int nThreadId) | |||
| 4161 | // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're | 4944 | // Build the fast (~2 GB dataset) RandomX mode once, up front, so we're |
| 4162 | // not stuck mining in the much slower light/cache-only mode forever. | 4945 | // not stuck mining in the much slower light/cache-only mode forever. |
| 4163 | if (!RandomXFastReady()) { | 4946 | if (!RandomXFastReady()) { |
| 4164 | unsigned int nThreads = std::thread::hardware_concurrency(); | 4947 | unsigned int nThreads = AvailableCoreCount(); |
| 4165 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); | 4948 | RandomXInitDataset(nThreads > 0 ? (int)nThreads : 1); |
| 4166 | } | 4949 | } |
| 4167 | 4950 | ||
| @@ -4247,8 +5030,40 @@ bool BitcoinMiner(int nThreadId) | |||
| 4247 | map<uint256, CTxIndex> mapTestPool; | 5030 | map<uint256, CTxIndex> mapTestPool; |
| 4248 | vector<char> vfAlreadyAdded(mapTransactions.size()); | 5031 | vector<char> vfAlreadyAdded(mapTransactions.size()); |
| 4249 | bool fFoundSomething = true; | 5032 | bool fFoundSomething = true; |
| 4250 | unsigned int nBlockSize = 0; | 5033 | // Assembly must stop below the consensus cap that CheckBlock() |
| 4251 | while (fFoundSomething && nBlockSize < MAX_SIZE/2) | 5034 | // enforces, or we mine a block our own node then rejects. |
| 5035 | // | ||
| 5036 | // This used to read MAX_SIZE/2 (16 MB), which was consistent while | ||
| 5037 | // CheckBlock() also tested against MAX_SIZE (32 MB). Adding | ||
| 5038 | // MAX_BLOCK_SIZE inverted it: assembly could fill 16 MB and | ||
| 5039 | // validation would refuse anything over 1 MB, so a full mempool | ||
| 5040 | // meant solving a block and throwing it away -- with this pool at | ||
| 5041 | // ~90% of hashrate, that is the network stalling, not just us. | ||
| 5042 | // | ||
| 5043 | // The budget test then sat only on this outer while, and the | ||
| 5044 | // comment claimed that bounded the overshoot to a single | ||
| 5045 | // transaction. It did not. The inner for walks all of | ||
| 5046 | // mapTransactions with no test of its own, so one pass could append | ||
| 5047 | // the entire mempool: the overshoot was bounded by mempool size, | ||
| 5048 | // which is to say not bounded at all. Ask per transaction, before | ||
| 5049 | // appending, and the overshoot is zero rather than small. | ||
| 5050 | // | ||
| 5051 | // Seeded with what the block already weighs -- header, tx count, | ||
| 5052 | // coinbase -- because CheckBlock() weighs the whole serialized | ||
| 5053 | // block and not just the transactions we chose to add to it. | ||
| 5054 | unsigned int nBlockSize = ::GetSerializeSize(*pblock, SER_DISK); | ||
| 5055 | // Sigops are a budget too, and CheckBlock() enforces one that no | ||
| 5056 | // assembler here counted against. Bytes do not bound validation | ||
| 5057 | // cost: every OP_CHECKSIG is an elliptic-curve verification, so a | ||
| 5058 | // template well inside the size limit can still be refused for | ||
| 5059 | // demanding more work than a block is allowed to. Counted with | ||
| 5060 | // CTransaction::GetSigOpCount, the function CheckBlock() itself | ||
| 5061 | // sums, so the two cannot disagree about what a transaction costs. | ||
| 5062 | // | ||
| 5063 | // Seeded from the coinbase because CheckBlock() sums over all of | ||
| 5064 | // vtx, coinbase included. | ||
| 5065 | unsigned int nBlockSigOps = pblock->vtx[0].GetSigOpCount(); | ||
| 5066 | while (fFoundSomething && nBlockSize < MAX_BLOCK_SIZE_GEN) | ||
| 4252 | { | 5067 | { |
| 4253 | fFoundSomething = false; | 5068 | fFoundSomething = false; |
| 4254 | unsigned int n = 0; | 5069 | unsigned int n = 0; |
| @@ -4260,6 +5075,29 @@ bool BitcoinMiner(int nThreadId) | |||
| 4260 | if (tx.IsCoinBase() || !tx.IsFinal()) | 5075 | if (tx.IsCoinBase() || !tx.IsFinal()) |
| 4261 | continue; | 5076 | continue; |
| 4262 | 5077 | ||
| 5078 | // Skip, not break: a transaction too big for the room left | ||
| 5079 | // says nothing about the next one, which may still fit. | ||
| 5080 | // | ||
| 5081 | // This is also the only ceiling on a single transaction's | ||
| 5082 | // size anywhere on the path into a block. CheckTransaction | ||
| 5083 | // does not cap it and neither does AcceptTransaction, so | ||
| 5084 | // the mempool will hold one of any size the wire allows; | ||
| 5085 | // without this test a lone 1.5 MB transaction offering a | ||
| 5086 | // fat fee is selected, and every template built from that | ||
| 5087 | // mempool is one CheckBlock() throws away. The fee is never | ||
| 5088 | // paid, because the block never confirms. | ||
| 5089 | // | ||
| 5090 | // Deliberately before ConnectInputs and not after: | ||
| 5091 | // ConnectInputs adds this transaction's fee to nFees by | ||
| 5092 | // reference on success. Passing it and then declining the | ||
| 5093 | // transaction would credit the coinbase for a fee no | ||
| 5094 | // transaction in the block pays, and the block would be | ||
| 5095 | // rejected for a wholly different reason. | ||
| 5096 | unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK); | ||
| 5097 | unsigned int nTxSigOps = tx.GetSigOpCount(); | ||
| 5098 | if (!BlockTemplateFits(nBlockSize, nBlockSigOps, nTxSize, nTxSigOps)) | ||
| 5099 | continue; | ||
| 5100 | |||
| 4263 | // Transaction fee requirements, mainly only needed for flood control | 5101 | // Transaction fee requirements, mainly only needed for flood control |
| 4264 | // Under 10K (about 80 inputs) is free for first 100 transactions | 5102 | // Under 10K (about 80 inputs) is free for first 100 transactions |
| 4265 | // Base rate is 0.01 per KB | 5103 | // Base rate is 0.01 per KB |
| @@ -4271,7 +5109,8 @@ bool BitcoinMiner(int nThreadId) | |||
| 4271 | swap(mapTestPool, mapTestPoolTmp); | 5109 | swap(mapTestPool, mapTestPoolTmp); |
| 4272 | 5110 | ||
| 4273 | pblock->vtx.push_back(tx); | 5111 | pblock->vtx.push_back(tx); |
| 4274 | nBlockSize += ::GetSerializeSize(tx, SER_NETWORK); | 5112 | nBlockSize += nTxSize; |
| 5113 | nBlockSigOps += nTxSigOps; | ||
| 4275 | vfAlreadyAdded[n] = true; | 5114 | vfAlreadyAdded[n] = true; |
| 4276 | fFoundSomething = true; | 5115 | fFoundSomething = true; |
| 4277 | } | 5116 | } |
| @@ -4320,7 +5159,7 @@ bool BitcoinMiner(int nThreadId) | |||
| 4320 | //// debug print | 5159 | //// debug print |
| 4321 | if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); | 5160 | if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); |
| 4322 | if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); | 5161 | if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); |
| 4323 | if (LogAcceptsCategory("net")) pblock->print(); | 5162 | if (LogAcceptsCategory("relay")) pblock->print(); |
| 4324 | 5163 | ||
| 4325 | SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); | 5164 | SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); |
| 4326 | CRITICAL_BLOCK(cs_main) | 5165 | CRITICAL_BLOCK(cs_main) |
| @@ -4675,9 +5514,17 @@ bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet) | |||
| 4675 | 5514 | ||
| 4676 | 5515 | ||
| 4677 | 5516 | ||
| 4678 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet) | 5517 | // presultRet, when given, says WHY this returned false: the caller cannot work |
| 5518 | // it out afterwards. "Coin selection came up short" and "the wallet could not | ||
| 5519 | // build the transaction" are indistinguishable from the outside -- both are | ||
| 5520 | // just `false` -- and the difference decides whether a pool defers a payment | ||
| 5521 | // or counts it against the payment's retry budget. See WalletSendResult. | ||
| 5522 | bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet, | ||
| 5523 | WalletSendResult* presultRet) | ||
| 4679 | { | 5524 | { |
| 4680 | nFeeRequiredRet = 0; | 5525 | nFeeRequiredRet = 0; |
| 5526 | if (presultRet) | ||
| 5527 | *presultRet = WALLET_SEND_FAILED; | ||
| 4681 | CRITICAL_BLOCK(cs_main) | 5528 | CRITICAL_BLOCK(cs_main) |
| 4682 | { | 5529 | { |
| 4683 | // txdb must be opened before the mapWallet lock | 5530 | // txdb must be opened before the mapWallet lock |
| @@ -4708,10 +5555,21 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 4708 | // fixed this in 107d9e288. | 5555 | // fixed this in 107d9e288. |
| 4709 | int64 nTotalValue = nValue + nFee; | 5556 | int64 nTotalValue = nValue + nFee; |
| 4710 | 5557 | ||
| 4711 | // Choose coins to use | 5558 | // Choose coins to use. |
| 5559 | // | ||
| 5560 | // Failing here is a funding shortfall and nothing else: there | ||
| 5561 | // is not enough spendable coin for nValue plus the fee this | ||
| 5562 | // pass is working with. On the second and later passes that | ||
| 5563 | // fee is GetMinFee() of the transaction the first pass built, | ||
| 5564 | // so this is also how a wallet that can afford the amount but | ||
| 5565 | // not the 11 * CENT charged at 10,000 bytes reports itself. | ||
| 4712 | set<CWalletTx*> setCoins; | 5566 | set<CWalletTx*> setCoins; |
| 4713 | if (!SelectCoins(nTotalValue, setCoins)) | 5567 | if (!SelectCoins(nTotalValue, setCoins)) |
| 5568 | { | ||
| 5569 | if (presultRet) | ||
| 5570 | *presultRet = WALLET_SEND_INSUFFICIENT_FUNDS; | ||
| 4714 | return false; | 5571 | return false; |
| 5572 | } | ||
| 4715 | int64 nValueIn = 0; | 5573 | int64 nValueIn = 0; |
| 4716 | foreach(CWalletTx* pcoin, setCoins) | 5574 | foreach(CWalletTx* pcoin, setCoins) |
| 4717 | nValueIn += pcoin->GetCredit(); | 5575 | nValueIn += pcoin->GetCredit(); |
| @@ -4781,6 +5639,8 @@ bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, in | |||
| 4781 | } | 5639 | } |
| 4782 | } | 5640 | } |
| 4783 | } | 5641 | } |
| 5642 | if (presultRet) | ||
| 5643 | *presultRet = WALLET_SEND_OK; | ||
| 4784 | return true; | 5644 | return true; |
| 4785 | } | 5645 | } |
| 4786 | 5646 | ||
| @@ -4843,16 +5703,38 @@ bool CommitTransactionSpent(const CWalletTx& wtxNew) | |||
| 4843 | 5703 | ||
| 4844 | 5704 | ||
| 4845 | 5705 | ||
| 4846 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | 5706 | // outcomeRet reports what happened rather than only whether it worked. A |
| 5707 | // caller that has to decide what a failure MEANS -- the pool payout thread -- | ||
| 5708 | // cannot reconstruct it from a balance reading afterwards, because the fee | ||
| 5709 | // that was actually required depends on the size of a transaction that was | ||
| 5710 | // never built. See WalletSendResult. | ||
| 5711 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, | ||
| 5712 | SendMoneyOutcome& outcomeRet) | ||
| 4847 | { | 5713 | { |
| 5714 | outcomeRet.result = WALLET_SEND_FAILED; | ||
| 5715 | outcomeRet.nFeeRequired = 0; | ||
| 4848 | CRITICAL_BLOCK(cs_main) | 5716 | CRITICAL_BLOCK(cs_main) |
| 4849 | { | 5717 | { |
| 4850 | int64 nFeeRequired; | 5718 | int64 nFeeRequired; |
| 4851 | if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)) | 5719 | WalletSendResult createResult = WALLET_SEND_FAILED; |
| 5720 | if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired, &createResult)) | ||
| 4852 | { | 5721 | { |
| 5722 | outcomeRet.result = createResult; | ||
| 5723 | outcomeRet.nFeeRequired = nFeeRequired; | ||
| 4853 | string strError; | 5724 | string strError; |
| 4854 | if (nValue + nFeeRequired > GetBalance()) | 5725 | if (nValue + nFeeRequired > GetBalance()) |
| 4855 | strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); | 5726 | // Not an oversized transaction -- this branch is simply "the |
| 5727 | // spendable balance is too small". Saying otherwise (with a fee | ||
| 5728 | // of 0.00, because CreateTransaction failed before setting one) | ||
| 5729 | // sends anyone reading the log after a failed payout hunting a | ||
| 5730 | // transaction-size problem that does not exist. | ||
| 5731 | strError = strprintf("Error: insufficient spendable balance:" | ||
| 5732 | " need %s (plus fee %s) but only %s is spendable" | ||
| 5733 | " -- mined coin counts as zero until it is %d deep", | ||
| 5734 | FormatMoney(nValue).c_str(), | ||
| 5735 | FormatMoney(nFeeRequired).c_str(), | ||
| 5736 | FormatMoney(GetBalance()).c_str(), | ||
| 5737 | COINBASE_SPENDABLE_DEPTH); | ||
| 4856 | else | 5738 | else |
| 4857 | strError = "Error: Transaction creation failed "; | 5739 | strError = "Error: Transaction creation failed "; |
| 4858 | return error("SendMoney() : %s\n", strError.c_str()); | 5740 | return error("SendMoney() : %s\n", strError.c_str()); |
| @@ -4876,6 +5758,13 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | |||
| 4876 | } | 5758 | } |
| 4877 | wtxNew.RelayWalletTransaction(); | 5759 | wtxNew.RelayWalletTransaction(); |
| 4878 | } | 5760 | } |
| 5761 | outcomeRet.result = WALLET_SEND_OK; | ||
| 4879 | MainFrameRepaint(); | 5762 | MainFrameRepaint(); |
| 4880 | return true; | 5763 | return true; |
| 4881 | } | 5764 | } |
| 5765 | |||
| 5766 | bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) | ||
| 5767 | { | ||
| 5768 | SendMoneyOutcome ignored; | ||
| 5769 | return SendMoney(scriptPubKey, nValue, wtxNew, ignored); | ||
| 5770 | } | ||