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.cpp284
1 files changed, 284 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
index dded83a..681673c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -65,6 +65,137 @@ CBlockIndex* pindexBest = NULL;
65map<uint256, CBlock*> mapOrphanBlocks; 65map<uint256, CBlock*> mapOrphanBlocks;
66multimap<uint256, CBlock*> mapOrphanBlocksByPrev; 66multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
67 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
68map<uint256, CDataStream*> mapOrphanTransactions; 199map<uint256, CDataStream*> mapOrphanTransactions;
69multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; 200multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
70 201
@@ -3292,10 +3423,56 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3292 CInv inv(MSG_BLOCK, pblock->GetHash()); 3423 CInv inv(MSG_BLOCK, pblock->GetHash());
3293 pfrom->AddInventoryKnown(inv); 3424 pfrom->AddInventoryKnown(inv);
3294 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
3295 if (ProcessBlock(pfrom, pblock.release())) 3458 if (ProcessBlock(pfrom, pblock.release()))
3296 { 3459 {
3297 mapAlreadyAskedFor.erase(inv); 3460 mapAlreadyAskedFor.erase(inv);
3298 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
3299 // 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
3300 // that block's height. Without this, nStartingHeight is a snapshot 3477 // that block's height. Without this, nStartingHeight is a snapshot
3301 // taken once during the version handshake and never touched again, 3478 // taken once during the version handshake and never touched again,
@@ -3420,12 +3597,119 @@ bool SendMessages(CNode* pto)
3420 const CInv& inv = (*pto->mapAskFor.begin()).second; 3597 const CInv& inv = (*pto->mapAskFor.begin()).second;
3421 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());
3422 if (!AlreadyHave(txdb, inv)) 3599 if (!AlreadyHave(txdb, inv))
3600 {
3423 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 }
3424 pto->mapAskFor.erase(pto->mapAskFor.begin()); 3631 pto->mapAskFor.erase(pto->mapAskFor.begin());
3425 } 3632 }
3426 if (!vAskFor.empty()) 3633 if (!vAskFor.empty())
3427 pto->PushMessage("getdata", vAskFor); 3634 pto->PushMessage("getdata", vAskFor);
3428 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
3429 } 3713 }
3430 return true; 3714 return true;
3431} 3715}