upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp521
1 files changed, 500 insertions, 21 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 7b3e555..6b8e0de 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"
@@ -64,6 +65,137 @@ CBlockIndex* pindexBest = NULL;
64map<uint256, CBlock*> mapOrphanBlocks; 65map<uint256, CBlock*> mapOrphanBlocks;
65multimap<uint256, CBlock*> mapOrphanBlocksByPrev; 66multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
66 67
68// Block bodies we have sent a getdata for and not yet received, so the
69// failover pass in SendMessages can tell "requested and being served" from
70// "requested and gone quiet" and re-issue the request to a different peer
71// (#33). Keyed by block hash; setAsked is the peers already tried since the
72// round last restarted, so the retry rotates instead of hammering the
73// announcer that is not serving. Guarded by cs_main, which both the message
74// handlers and SendMessages already hold.
75//
76// Capped small on purpose. At the tip there is one block in flight; during
77// initial download there are legitimately hundreds outstanding to one peer,
78// and rotating those would duplicate the whole download to every other peer.
79//
80// The cap is a shared resource and nothing on the path from inv to insertion
81// can tell a real block hash from 32 random bytes -- AlreadyHave only asks
82// whether we already hold the block, and there is no header to check work
83// against. So the table does not refuse a new hash when it is full; it makes
84// room. See MakeRoomInBlockFetch for the rule, which is what stops one peer
85// filling all eight slots and switching failover off for everybody.
86//
87// The initialisers matter: the insertion path uses operator[] and then tests
88// nTimeFirstAsked == 0 to detect a fresh entry. That worked on
89// value-initialisation alone, but only for as long as nobody gave this struct
90// a constructor.
91struct CBlockFetch
92{
93 int64 nTimeFirstAsked = 0; // when the first getdata for this block went out
94 int64 nTimeAsked = 0; // when the latest getdata went out
95 CAddress addrAnnounced; // peer whose announcement put this hash here
96 set<CAddress> setAsked; // peers tried since the round last restarted
97};
98map<uint256, CBlockFetch> mapBlockFetch;
99static const int BLOCK_FETCH_TRACK_MAX = 8;
100// A block nobody has served for this long is not a fetch problem: the hash may
101// have been announced and never existed. Drop the entry rather than getdata
102// forever; a re-announcement recreates it.
103static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60;
104
105// Failover firing at all is worth saying out loud: it means peers are
106// announcing blocks they then do not serve, which is the pathology of #33 and
107// the thing nobody could otherwise see. Naming the peer on every re-request is
108// a different matter. A peer can drive that line as often as it likes by
109// announcing hashes it will not serve, and what it prints is a peer address,
110// which is the one thing the .btf transport otherwise takes trouble not to
111// spread around. So the per-peer detail sits behind the net category and the
112// unconditional line is a count over a window no peer can shorten: the first
113// failover after a quiet spell prints at once, because the change from "blocks
114// arrive" to "blocks are announced and not served" is the event worth seeing,
115// and everything after it is summarised once a minute.
116static const int BLOCK_FETCH_REPORT_SECS = 60;
117static int64 nBlockFetchWindowStart = 0;
118static int nBlockFetchFailovers = 0;
119
120// How stale our own best block may be before we stop calling this the tip.
121// Blocks target two minutes, so this is thirty intervals: far longer than any
122// stall worth failing over from -- the one in #33 ran sixteen minutes -- and
123// far shorter than catching up, which starts hours or days back.
124static const int BLOCK_FETCH_TIP_AGE_SECS = 60 * 60;
125
126// Whether this node is tracking the tip rather than still catching up.
127//
128// Measured off the age of our own best block, not off any height a peer told
129// us. nStartingHeight is optional in the version message and every node
130// released so far sends the short form, so it is -1 for essentially every
131// peer; and where it is filled in later it is filled in from the height of a
132// block that peer served us, which during catch-up tracks our own height and
133// would report "at the tip" for the whole download. Block timestamps are the
134// one clock here that peers cannot move: CheckBlock refuses anything more than
135// two hours ahead and AcceptBlock refuses anything at or before the previous
136// median, so the only way to make this read "at the tip" falsely is to mine,
137// and all that buys is the 15s spacing this code had unconditionally anyway.
138bool IsBlockFetchNearTip()
139{
140 return pindexBest && (int64)pindexBest->nTime > GetTime() - BLOCK_FETCH_TIP_AGE_SECS;
141}
142
143// Drop one entry so a hash announced by addrNew can be tracked.
144//
145// Refusing the insertion instead was a remote off-switch for the whole
146// feature. An inv carrying eight hashes that never existed filled every slot
147// -- nothing between inv and here can tell those from real blocks -- and no
148// genuinely new block was tracked again until the give-up timer expired ten
149// minutes later, re-armable for one message per ten minutes from a single
150// connection.
151//
152// So evict instead, by occupancy: a peer may only take a slot from a peer
153// holding strictly more slots than itself, and otherwise recycles one of its
154// own. That is what keeps eviction from becoming the same attack in a new
155// shape. A peer that has filled the table is by definition the heaviest
156// holder, so its own entries are the only ones it can displace, while a peer
157// announcing its first block holds nothing and therefore always evicts from
158// the heaviest -- a genuine announcement is never refused a slot, whatever the
159// attacker holds or however many connections it holds it from. The one case
160// that costs a real fetch is eight distinct peers holding one entry each,
161// where a ninth takes the oldest; that is a busy healthy node, not an attack,
162// and losing the oldest of eight beats refusing the ninth.
163static void MakeRoomInBlockFetch(const CAddress& addrNew)
164{
165 if (mapBlockFetch.empty())
166 return;
167
168 map<CAddress, int> mapHeld;
169 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi)
170 mapHeld[(*mi).second.addrAnnounced]++;
171
172 int nMine = mapHeld.count(addrNew) ? mapHeld[addrNew] : 0;
173
174 int nMax = 0;
175 CAddress addrVictim;
176 for (map<CAddress, int>::iterator mi = mapHeld.begin(); mi != mapHeld.end(); ++mi)
177 if ((*mi).second > nMax)
178 {
179 nMax = (*mi).second;
180 addrVictim = (*mi).first;
181 }
182
183 // Nobody is holding more than this peer, so it recycles its own oldest
184 // rather than taking a slot off an equal.
185 if (nMax <= nMine)
186 addrVictim = addrNew;
187
188 map<uint256, CBlockFetch>::iterator miVictim = mapBlockFetch.end();
189 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi)
190 if ((*mi).second.addrAnnounced == addrVictim &&
191 (miVictim == mapBlockFetch.end() ||
192 (*mi).second.nTimeFirstAsked < (*miVictim).second.nTimeFirstAsked))
193 miVictim = mi;
194
195 if (miVictim != mapBlockFetch.end())
196 mapBlockFetch.erase(miVictim);
197}
198
67map<uint256, CDataStream*> mapOrphanTransactions; 199map<uint256, CDataStream*> mapOrphanTransactions;
68multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; 200multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
69 201
@@ -1712,7 +1844,7 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb)
1712 uint256 hash = GetHash(); 1844 uint256 hash = GetHash();
1713 if (!txdb.ContainsTx(hash)) 1845 if (!txdb.ContainsTx(hash))
1714 { 1846 {
1715 if (LogAcceptsCategory("net")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); 1847 if (LogAcceptsCategory("relay")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str());
1716 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); 1848 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this);
1717 } 1849 }
1718 } 1850 }
@@ -2678,7 +2810,18 @@ bool LoadBlockIndex(bool fAllowNew)
2678 // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B) 2810 // CTxOut(nValue=50.00000000, scriptPubKey=0x5F1DF16B2B704C8A578D0B)
2679 // vMerkleTree: 4a5e1e 2811 // vMerkleTree: 4a5e1e
2680 2812
2681 // Bitflash genesis block -- own header (fair launch, 2026 stable relaunch) 2813 // Bitflash genesis block -- own header (fair launch)
2814 //
2815 // Do not change pszTimestamp, the scriptPubKey, nTime or nNonce. They
2816 // are the inputs to the genesis hash, and the genesis hash is
2817 // consensus. Changing one on its own trips the assert below, which is
2818 // loud and reverts cleanly; changing one and regenerating
2819 // GENESIS_NONCE and hashGenesisBlock to match -- which is exactly what
2820 // the re-mining branch below prints out and invites -- gives a node
2821 // that starts without complaint, builds its own chain from height 0,
2822 // and rejects every real peer as being on a different chain. That one
2823 // fails silently, and the damage scales with how many people run the
2824 // "fixed" build.
2682 char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine"; 2825 char* pszTimestamp = "Bitflash 24/Jul/2026 Fair launch: one CPU one vote, no premine";
2683 CTransaction txNew; 2826 CTransaction txNew;
2684 txNew.vin.resize(1); 2827 txNew.vin.resize(1);
@@ -2691,7 +2834,14 @@ bool LoadBlockIndex(bool fAllowNew)
2691 block.hashPrevBlock = 0; 2834 block.hashPrevBlock = 0;
2692 block.hashMerkleRoot = block.BuildMerkleTree(); 2835 block.hashMerkleRoot = block.BuildMerkleTree();
2693 block.nVersion = 1; 2836 block.nVersion = 1;
2694 block.nTime = 1753315200; // 2026-07-24 00:00:00 UTC (stable relaunch) 2837 // 1753315200 is 2025-07-24T00:00:00Z. The headline in pszTimestamp
2838 // above reads 2026 and does not match it. This is a known discrepancy
2839 // and not a typo in the constant: the value is only consensus-critical
2840 // as an input to the genesis hash, the block hashes to
2841 // hashGenesisBlock with it, and every node on the network agrees. The
2842 // mismatch is in the launched chain and cannot be corrected -- see the
2843 // warning above before deciding otherwise.
2844 block.nTime = 1753315200;
2695 block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor) 2845 block.nBits = 0x1f0fffff; // = bnProofOfWorkLimit (Bitflash RandomX floor)
2696 block.nNonce = GENESIS_NONCE; 2846 block.nNonce = GENESIS_NONCE;
2697 2847
@@ -2718,6 +2868,11 @@ bool LoadBlockIndex(bool fAllowNew)
2718 printf("powhash = %s\n", block.GetPoWHash().ToString().c_str()); 2868 printf("powhash = %s\n", block.GetPoWHash().ToString().c_str());
2719 printf("hash = %s\n", block.GetHash().ToString().c_str()); 2869 printf("hash = %s\n", block.GetHash().ToString().c_str());
2720 printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str()); 2870 printf("merkle = %s\n", block.hashMerkleRoot.ToString().c_str());
2871 printf("----------------------------------------\n");
2872 printf("These constants bring up a NEW chain. Do not paste them\n");
2873 printf("into a build for this network to make an edited nTime or\n");
2874 printf("pszTimestamp fit -- a node whose genesis differs starts\n");
2875 printf("cleanly on a chain of its own and rejects every real peer.\n");
2721 printf("========================================\n"); 2876 printf("========================================\n");
2722 } 2877 }
2723 2878
@@ -3263,15 +3418,61 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3263 vRecv >> *pblock; 3418 vRecv >> *pblock;
3264 3419
3265 //// debug print 3420 //// debug print
3266 if (LogAcceptsCategory("net")) { printf("received block:\n"); pblock->print(); } 3421 if (LogAcceptsCategory("relay")) { printf("received block:\n"); pblock->print(); }
3267 3422
3268 CInv inv(MSG_BLOCK, pblock->GetHash()); 3423 CInv inv(MSG_BLOCK, pblock->GetHash());
3269 pfrom->AddInventoryKnown(inv); 3424 pfrom->AddInventoryKnown(inv);
3270 3425
3426 // GetHash() covers the 80-byte header and nothing else, so the hash
3427 // coming out right does not make this the block we asked for. Every
3428 // peer that saw X announced holds X's header, and can answer our
3429 // getdata with that header bolted onto a transaction list of its own
3430 // invention -- the reply still hashes to X. hashMerkleRoot is the only
3431 // thing binding the header to the body, so check it here, before the
3432 // mapBlockFetch entry is touched and before CheckBlock spends a
3433 // RandomX hash on a body already known to be forged.
3434 //
3435 // There is no honest way to arrive here. Peer traffic rides the .btf
3436 // channel, which is XChaCha20-Poly1305, so a body that arrives at all
3437 // arrived intact: a mismatch is the peer's own construction, not line
3438 // noise. Drop the peer and leave the fetch outstanding so the failover
3439 // sweep asks somebody else for the real X, rather than recording X as
3440 // delivered on the strength of a header anyone could copy.
3441 if (pblock->hashMerkleRoot != pblock->BuildMerkleTree())
3442 {
3443 if (LogAcceptsCategory("net"))
3444 printf("peer %s served a forged body for %s\n",
3445 pfrom->addr.ToString().c_str(), inv.ToString().c_str());
3446 // One line per connection: ProcessMessages keeps draining the
3447 // receive buffer after fDisconnect is set, so a peer that packs
3448 // many forged bodies into one buffer must not get one log line
3449 // for each of them.
3450 if (!pfrom->fDisconnect)
3451 printf("block fetch: a peer answered the request for %s with a body that is not "
3452 "that block -- dropping it\n", inv.hash.ToString().substr(0,14).c_str());
3453 pfrom->fDisconnect = true;
3454 return error("ProcessMessage() : forged block body for %s",
3455 inv.hash.ToString().substr(0,14).c_str());
3456 }
3457
3271 if (ProcessBlock(pfrom, pblock.release())) 3458 if (ProcessBlock(pfrom, pblock.release()))
3272 { 3459 {
3273 mapAlreadyAskedFor.erase(inv); 3460 mapAlreadyAskedFor.erase(inv);
3274 3461
3462 // The body arrived and validated, so the fetch really is over:
3463 // the block is now in mapBlockIndex, or parked in mapOrphanBlocks,
3464 // and either way AlreadyHave answers for it from here on.
3465 //
3466 // This has to stay inside the success branch, next to the erase
3467 // above. When validation rejects a body nothing is stored,
3468 // AlreadyHave stays false, and the mapAskFor entry was already
3469 // consumed when the getdata went out -- so erasing the tracking
3470 // here would leave the block requested from nobody at all until
3471 // some peer happened to re-announce it. That is exactly the stall
3472 // of #33, and erasing before ProcessBlock made it reachable on
3473 // demand.
3474 mapBlockFetch.erase(inv.hash);
3475
3275 // A peer that just handed us a block is demonstrably at least at 3476 // A peer that just handed us a block is demonstrably at least at
3276 // that block's height. Without this, nStartingHeight is a snapshot 3477 // that block's height. Without this, nStartingHeight is a snapshot
3277 // taken once during the version handshake and never touched again, 3478 // taken once during the version handshake and never touched again,
@@ -3396,12 +3597,119 @@ bool SendMessages(CNode* pto)
3396 const CInv& inv = (*pto->mapAskFor.begin()).second; 3597 const CInv& inv = (*pto->mapAskFor.begin()).second;
3397 if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str()); 3598 if (LogAcceptsCategory("net")) printf("sending getdata: %s\n", inv.ToString().c_str());
3398 if (!AlreadyHave(txdb, inv)) 3599 if (!AlreadyHave(txdb, inv))
3600 {
3399 vAskFor.push_back(inv); 3601 vAskFor.push_back(inv);
3602 // Blocks get their delivery tracked so the failover pass
3603 // below can chase the ones that go quiet.
3604 if (inv.type == MSG_BLOCK)
3605 {
3606 // Full, and this is a hash we are not already tracking:
3607 // make room rather than drop it on the floor, or one peer
3608 // announcing eight hashes turns failover off for everyone.
3609 if (!mapBlockFetch.count(inv.hash) &&
3610 mapBlockFetch.size() >= BLOCK_FETCH_TRACK_MAX)
3611 MakeRoomInBlockFetch(pto->addr);
3612
3613 if (mapBlockFetch.count(inv.hash) ||
3614 mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX)
3615 {
3616 CBlockFetch& fetch = mapBlockFetch[inv.hash];
3617 if (fetch.nTimeFirstAsked == 0)
3618 {
3619 fetch.nTimeFirstAsked = GetTime();
3620 // Who is answerable for this hash existing. Set
3621 // once, on creation: the failover sweep asks other
3622 // peers later, and none of them chose to announce
3623 // it, so none of them should be charged for it.
3624 fetch.addrAnnounced = pto->addr;
3625 }
3626 fetch.nTimeAsked = GetTime();
3627 fetch.setAsked.insert(pto->addr);
3628 }
3629 }
3630 }
3400 pto->mapAskFor.erase(pto->mapAskFor.begin()); 3631 pto->mapAskFor.erase(pto->mapAskFor.begin());
3401 } 3632 }
3402 if (!vAskFor.empty()) 3633 if (!vAskFor.empty())
3403 pto->PushMessage("getdata", vAskFor); 3634 pto->PushMessage("getdata", vAskFor);
3404 3635
3636
3637 //
3638 // Message: getdata (block fetch failover)
3639 //
3640 // A requested block that has not arrived in BLOCK_FETCH_RETRY_SECS is
3641 // not still in transit -- a body is at most 1 MB and somebody
3642 // demonstrably has it -- so ask somebody else instead of waiting on
3643 // whoever was asked first (#33). Each peer the message handler visits
3644 // takes at most one turn per block per round; once every connected
3645 // peer has had its turn and a further interval passes with nobody
3646 // new to ask, the round restarts. A peer that declared itself a
3647 // client cannot serve block bodies and is no use as a target.
3648 if (!pto->fClient)
3649 {
3650 vector<CInv> vFailover;
3651 int64 nNowSecs = GetTime();
3652 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end();)
3653 {
3654 CBlockFetch& fetch = (*mi).second;
3655 CInv inv(MSG_BLOCK, (*mi).first);
3656 if (AlreadyHave(txdb, inv))
3657 {
3658 // Arrived by some other road -- our own miner, an orphan
3659 // parked in mapOrphanBlocks. Nothing left to chase.
3660 mapBlockFetch.erase(mi++);
3661 continue;
3662 }
3663 if (nNowSecs - fetch.nTimeFirstAsked > BLOCK_FETCH_GIVEUP_SECS)
3664 {
3665 printf("block fetch: giving up on %s -- no peer served it in %d s\n",
3666 inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeFirstAsked));
3667 mapBlockFetch.erase(mi++);
3668 continue;
3669 }
3670 if (nNowSecs - fetch.nTimeAsked >= BLOCK_FETCH_RETRY_SECS)
3671 {
3672 // The handler loop visits every peer within ~100ms, so a
3673 // request stale by two full intervals means nobody outside
3674 // setAsked was there to ask. Start the round over.
3675 if (nNowSecs - fetch.nTimeAsked >= 2 * BLOCK_FETCH_RETRY_SECS)
3676 fetch.setAsked.clear();
3677 if (!fetch.setAsked.count(pto->addr))
3678 {
3679 // Which block, how long, and which peer we are trying
3680 // next. Behind the category because it names a peer
3681 // and a peer decides how often it is written.
3682 if (LogAcceptsCategory("net"))
3683 printf("block fetch failover: %s unanswered for %d s, re-requesting from %s\n",
3684 inv.ToString().c_str(), (int)(nNowSecs - fetch.nTimeAsked),
3685 pto->addr.ToString().c_str());
3686
3687 // The unconditional half: no addresses, and at most
3688 // one line per BLOCK_FETCH_REPORT_SECS however hard a
3689 // peer tries. See the counters for the reasoning.
3690 nBlockFetchFailovers++;
3691 if (nNowSecs - nBlockFetchWindowStart >= BLOCK_FETCH_REPORT_SECS)
3692 {
3693 printf("block fetch: %d block request%s went unanswered and %s been "
3694 "re-asked of other peers (%d tracked)\n",
3695 nBlockFetchFailovers, nBlockFetchFailovers == 1 ? "" : "s",
3696 nBlockFetchFailovers == 1 ? "has" : "have",
3697 (int)mapBlockFetch.size());
3698 nBlockFetchWindowStart = nNowSecs;
3699 nBlockFetchFailovers = 0;
3700 }
3701
3702 vFailover.push_back(inv);
3703 fetch.nTimeAsked = nNowSecs;
3704 fetch.setAsked.insert(pto->addr);
3705 }
3706 }
3707 ++mi;
3708 }
3709 if (!vFailover.empty())
3710 pto->PushMessage("getdata", vFailover);
3711 }
3712
3405 } 3713 }
3406 return true; 3714 return true;
3407} 3715}
@@ -3662,6 +3970,14 @@ static void ParticipantHashWorker(ParticipantJobShared* sh, int idx, int nWorker
3662 RandomXDestroyMinerVM(vm); 3970 RandomXDestroyMinerVM(vm);
3663} 3971}
3664 3972
3973// Run one participant session: resolve the pool, open the tunnel, subscribe,
3974// hash, submit shares. Returns true only if the pool actually handed out work
3975// -- at least one well-formed mining.notify arrived. Everything else is false,
3976// including a tunnel that opened and was dropped before any job.
3977//
3978// The caller's reconnect backoff keys off this return value, so any path added
3979// here that leaves the session without work MUST return false. "The session
3980// ended tidily" is not the question being asked.
3665static bool PoolParticipantMiner() 3981static bool PoolParticipantMiner()
3666{ 3982{
3667 // ------------------------------------------------------------------ 3983 // ------------------------------------------------------------------
@@ -3739,6 +4055,33 @@ static bool PoolParticipantMiner()
3739 } 4055 }
3740 LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str()); 4056 LogPrint("worker", "[worker] pool resolved to meeting node %s\n", meetingHostPort.c_str());
3741 4057
4058 // The descriptor names ONE meeting node, but the pool parks a
4059 // registration at every relay it knows (one accept thread per relay,
4060 // rpc.cpp:AcceptOneFn), so any seed relay can pair us. Insisting on the
4061 // advertised entry forever turns one bad path into a miner that idles
4062 // indefinitely while a working path exists (issue #17); after a few
4063 // consecutive tunnel failures, rotate the seed relays into the attempts
4064 // too. Only ONE participant session runs (StartMinerThreads), so these
4065 // statics are single-threaded.
4066 static const int TUNNEL_FAILOVER_AFTER = 3;
4067 static int nTunnelFails = 0; // consecutive tunnel failures, this pool
4068 static size_t iFailoverRelay = 0; // rotation cursor over vBtfMeetingRelays
4069 static std::string strFailPool; // pool the counters belong to
4070 if (strFailPool != strParticipantPool) {
4071 strFailPool = strParticipantPool;
4072 nTunnelFails = 0;
4073 iFailoverRelay = 0;
4074 }
4075 if (nTunnelFails >= TUNNEL_FAILOVER_AFTER && !vBtfMeetingRelays.empty()) {
4076 std::string alt = vBtfMeetingRelays[iFailoverRelay++ % vBtfMeetingRelays.size()];
4077 if (alt != meetingHostPort) {
4078 LogPrint("worker", "[worker] advertised meeting node %s failed %d"
4079 " times in a row -- trying seed relay %s instead\n",
4080 meetingHostPort.c_str(), nTunnelFails, alt.c_str());
4081 meetingHostPort = alt;
4082 }
4083 }
4084
3742 size_t colon = meetingHostPort.rfind(':'); 4085 size_t colon = meetingHostPort.rfind(':');
3743 if (colon == std::string::npos) { 4086 if (colon == std::string::npos) {
3744 LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str()); 4087 LogPrint("worker", "[worker] bad meeting node format '%s'\n", meetingHostPort.c_str());
@@ -3758,15 +4101,18 @@ static bool PoolParticipantMiner()
3758 // Step 2b: open encrypted tunnel to pool 4101 // Step 2b: open encrypted tunnel to pool
3759 // ------------------------------------------------------------------ 4102 // ------------------------------------------------------------------
3760 SetParticipantMiningStatus("opening tunnel"); 4103 SetParticipantMiningStatus("opening tunnel");
4104 btf::TunnelFail tunnelFail = btf::TUNNEL_FAIL_NONE;
3761 s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port, 4105 s = btf::BtfClientTunnel(host.c_str(), (unsigned short)port,
3762 target_pubkey, service_enc_pub); 4106 target_pubkey, service_enc_pub, &tunnelFail);
3763 if (s == INVALID_SOCKET) { 4107 if (s == INVALID_SOCKET) {
3764 LogPrint("worker", "[worker] tunnel connect FAILED via %s" 4108 nTunnelFails++;
3765 " (relay unreachable or pool not listening)\n", 4109 LogPrint("worker", "[worker] tunnel connect FAILED via %s: %s\n",
3766 meetingHostPort.c_str()); 4110 meetingHostPort.c_str(), btf::TunnelFailText(tunnelFail));
3767 SetParticipantMiningStatus("tunnel connect failed"); 4111 SetParticipantMiningStatus(std::string("tunnel connect failed: ")
4112 + btf::TunnelFailText(tunnelFail));
3768 return false; 4113 return false;
3769 } 4114 }
4115 nTunnelFails = 0;
3770 LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str()); 4116 LogPrint("worker", "[worker] tunnel open via %s\n", meetingHostPort.c_str());
3771 SetParticipantMiningStatus("tunnel open"); 4117 SetParticipantMiningStatus("tunnel open");
3772 } 4118 }
@@ -3899,9 +4245,32 @@ connected:
3899 std::vector<ParticipantShareHit> shareHits; 4245 std::vector<ParticipantShareHit> shareHits;
3900 std::mutex shareHitsMtx; 4246 std::mutex shareHitsMtx;
3901 std::vector<std::thread> hashThreads; 4247 std::vector<std::thread> hashThreads;
3902 for (int i = 0; i < nWorkers; i++) 4248 try
3903 hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers, 4249 {
3904 &shareHits, &shareHitsMtx); 4250 for (int i = 0; i < nWorkers; i++)
4251 hashThreads.emplace_back(ParticipantHashWorker, &jobShared, i, nWorkers,
4252 &shareHits, &shareHitsMtx);
4253 }
4254 catch (const std::system_error& e)
4255 {
4256 // A refused pthread_create used to reach std::terminate here and
4257 // kill the worker mid-session -- observed twice on an otherwise
4258 // healthy participant. Stop the workers that did start the same way
4259 // the disconnect path below does, drop this pool connection, and let
4260 // the caller's reconnect loop try again once the pressure clears.
4261 LogPrint("worker", "[worker] cannot spawn hash threads (%s)"
4262 " -- dropping pool connection\n", e.what());
4263 SetParticipantMiningStatus("hash thread spawn failed");
4264 {
4265 std::lock_guard<std::mutex> lk(jobShared.mtx);
4266 jobShared.stop = true;
4267 jobShared.haveJob = false;
4268 }
4269 for (std::thread& t : hashThreads)
4270 t.join();
4271 closesocket(s);
4272 return false;
4273 }
3905 LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n", 4274 LogPrint("worker", "[worker] spawned %d hash threads on shared RandomX dataset\n",
3906 nWorkers); 4275 nWorkers);
3907 const uint64 sessionStartHashes = gParticipantHashes.load(); 4276 const uint64 sessionStartHashes = gParticipantHashes.load();
@@ -3912,8 +4281,17 @@ connected:
3912 std::string jobId; 4281 std::string jobId;
3913 unsigned char header[80] = {}; 4282 unsigned char header[80] = {};
3914 uint256 target; 4283 uint256 target;
4284 // Cached answer to "which block is this job built on", for the
4285 // mining.notify handler below -- see the comment there. Session-local, so
4286 // a reconnect re-resolves rather than trusting an answer obtained before
4287 // a possible change of pool.
4288 uint256 hashPrevResolved; // prevhash the cached answer describes
4289 bool fPrevResolved = false; // whether that answer exists yet
4290 int nPrevHeight = -1; // its height, or -1 == unknown to us
4291 int nBestHeightResolved = -1; // nBestHeight when we last resolved
3915 double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool 4292 double currentDifficulty = 1.0; // updated by mining.set_difficulty from pool
3916 bool haveJob = false; 4293 bool haveJob = false;
4294 bool gotAnyJob = false; // any job at all this session -- the return value
3917 bool gotSubscribeAck = false; 4295 bool gotSubscribeAck = false;
3918 bool gotAuthorizeAck = false; 4296 bool gotAuthorizeAck = false;
3919 int64 authWaitStart = 0; 4297 int64 authWaitStart = 0;
@@ -4030,8 +4408,9 @@ connected:
4030 jobId = newJobId; 4408 jobId = newJobId;
4031 memcpy(header, hdrBytes.data(), 80); 4409 memcpy(header, hdrBytes.data(), 80);
4032 memcpy(&target, tgtBytes.data(), 32); 4410 memcpy(&target, tgtBytes.data(), 32);
4033 haveJob = true; 4411 haveJob = true;
4034 nNonce = 1; 4412 gotAnyJob = true;
4413 nNonce = 1;
4035 { 4414 {
4036 std::lock_guard<std::mutex> lk(jobShared.mtx); 4415 std::lock_guard<std::mutex> lk(jobShared.mtx);
4037 memcpy(jobShared.header, header, 80); 4416 memcpy(jobShared.header, header, 80);
@@ -4040,8 +4419,64 @@ connected:
4040 jobShared.jobSeq++; 4419 jobShared.jobSeq++;
4041 jobShared.haveJob = true; 4420 jobShared.haveJob = true;
4042 } 4421 }
4043 LogPrint("worker", "[pool->worker] new job %s received\n", 4422 // Log the chain position the pool is
4044 jobId.c_str()); 4423 // building on, so a stale or private
4424 // fork is auditable from worker logs.
4425 // The prevhash comes from the header we
4426 // actually hash (== p[2] of the notify);
4427 // a brief cs_main lookup is safe here --
4428 // this thread holds no other lock.
4429 //
4430 // Resolving it is the expensive half, and
4431 // this is the socket-reading thread: block
4432 // validation can hold cs_main for a long
4433 // time, and every moment spent waiting on
4434 // it here is a moment set_difficulty and
4435 // share responses are not being handled.
4436 // Most notifies in a round are a new job
4437 // on the SAME tip, so the answer is cached
4438 // and re-resolved only when it could come
4439 // out differently: a prevhash we have not
4440 // resolved, or one we could NOT resolve
4441 // last time while the chain has since
4442 // moved (unknown -> known). A block's
4443 // height is fixed once mapBlockIndex knows
4444 // it, so a cached "known" never goes
4445 // stale. nBestHeight is read unlocked on
4446 // purpose -- it only decides whether to
4447 // repeat a lookup, never what gets
4448 // printed, so a stale read costs at most
4449 // one deferred or one extra lookup.
4450 // The line still prints in full for every
4451 // job, cached or not.
4452 uint256 hashPrev;
4453 memcpy(&hashPrev, header + 4, 32);
4454 if (LogAcceptsCategory("worker"))
4455 {
4456 int nBestHeightNow = nBestHeight;
4457 if (!fPrevResolved
4458 || hashPrev != hashPrevResolved
4459 || (nPrevHeight < 0 && nBestHeightNow != nBestHeightResolved))
4460 {
4461 nPrevHeight = -1;
4462 CRITICAL_BLOCK(cs_main)
4463 {
4464 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrev);
4465 if (mi != mapBlockIndex.end())
4466 nPrevHeight = (*mi).second->nHeight;
4467 }
4468 hashPrevResolved = hashPrev;
4469 nBestHeightResolved = nBestHeightNow;
4470 fPrevResolved = true;
4471 }
4472 if (nPrevHeight >= 0)
4473 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (known, job height %d)\n",
4474 jobId.c_str(), hashPrev.GetHex().c_str(),
4475 nPrevHeight + 1);
4476 else
4477 LogPrint("worker", "[pool->worker] new job %s received, prevhash %s (unknown to our chain)\n",
4478 jobId.c_str(), hashPrev.GetHex().c_str());
4479 }
4045 SetParticipantMiningStatus("hashing job " + jobId); 4480 SetParticipantMiningStatus("hashing job " + jobId);
4046 } 4481 }
4047 } 4482 }
@@ -4167,7 +4602,11 @@ connected:
4167 (unsigned long long)hashesSinceSample, 4602 (unsigned long long)hashesSinceSample,
4168 (unsigned long long)sessionSharesSent, 4603 (unsigned long long)sessionSharesSent,
4169 (unsigned long long)sessionSharesAccepted); 4604 (unsigned long long)sessionSharesAccepted);
4170 return true; 4605 // Not "we got here", which is true of a tunnel the pool accepted and then
4606 // dropped without ever sending a job -- exactly the case the caller's
4607 // backoff exists for. Reaching the end of the session is not evidence the
4608 // session was worth anything; a job is.
4609 return gotAnyJob;
4171} 4610}
4172 4611
4173// --------------------------------------------------------------------------- 4612// ---------------------------------------------------------------------------
@@ -4364,11 +4803,51 @@ bool BitcoinMiner(int nThreadId)
4364 // Participant mode: connect to external pool instead of mining solo/operator 4803 // Participant mode: connect to external pool instead of mining solo/operator
4365 if (nMineMode == MINE_PARTICIPANT) 4804 if (nMineMode == MINE_PARTICIPANT)
4366 { 4805 {
4806 // An attempt that never got work backs off (10s doubling to 120s)
4807 // instead of hammering the same path every flat 10s, and a streak
4808 // lasting minutes is said at printf level: the identical once-per-10s
4809 // LogPrint line is how a worker idled all night unnoticed (issue #17).
4810 //
4811 // Only a session the pool actually sent a job to resets either. The
4812 // weaker test -- "the session returned" -- was gameable: a pool that
4813 // completes the tunnel and drops without ever sending work leaves by
4814 // the same tidy cleanup path a healthy one does, which would pin the
4815 // retry at 10s forever and defeat the backoff in exactly the scenario
4816 // it exists for.
4817 int nRetrySecs = 10;
4818 int64 nFailingSince = 0;
4367 while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { 4819 while (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) {
4368 PoolParticipantMiner(); 4820 bool fGotWork = PoolParticipantMiner();
4369 if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) { 4821 if (fGenerateBitcoins && nMineMode == MINE_PARTICIPANT && !fShutdown) {
4370 LogPrint("worker", "PoolParticipantMiner: reconnecting in 10s\n"); 4822 if (fGotWork) {
4371 for (int i = 0; i < 10 && !fShutdown; i++) Sleep(1000); 4823 nRetrySecs = 10;
4824 nFailingSince = 0;
4825 } else {
4826 if (nFailingSince == 0)
4827 nFailingSince = GetTime();
4828 int64 nMins = (GetTime() - nFailingSince) / 60;
4829 if (nMins >= 1)
4830 printf("[worker] connection to pool %s has been failing"
4831 " for %lld minutes\n",
4832 strParticipantPool.c_str(), (long long)nMins);
4833 }
4834 LogPrint("worker", "PoolParticipantMiner: reconnecting in %ds\n",
4835 nRetrySecs);
4836 // Poll everything the enclosing while polls, not just
4837 // fShutdown. Raising the ceiling to 120s made a
4838 // shutdown-only predicate expensive: after the operator
4839 // clears Generate or switches to Relay, the session -- and
4840 // the vfThreadRunning[3] slot that gates restarting it --
4841 // would stay held for up to two minutes. Measured 119s
4842 // before, 1s after.
4843 for (int i = 0; i < nRetrySecs && !fShutdown && fGenerateBitcoins
4844 && nMineMode == MINE_PARTICIPANT; i++)
4845 Sleep(1000);
4846 if (!fGotWork && nRetrySecs < 120) {
4847 nRetrySecs *= 2;
4848 if (nRetrySecs > 120)
4849 nRetrySecs = 120;
4850 }
4372 } 4851 }
4373 } 4852 }
4374 return true; 4853 return true;
@@ -4555,7 +5034,7 @@ bool BitcoinMiner(int nThreadId)
4555 //// debug print 5034 //// debug print
4556 if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); 5035 if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n");
4557 if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); 5036 if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
4558 if (LogAcceptsCategory("net")) pblock->print(); 5037 if (LogAcceptsCategory("relay")) pblock->print();
4559 5038
4560 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); 5039 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
4561 CRITICAL_BLOCK(cs_main) 5040 CRITICAL_BLOCK(cs_main)