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.cpp110
1 files changed, 97 insertions, 13 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 8926d57..9ab1f44 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -75,12 +75,24 @@ multimap<uint256, CBlock*> mapOrphanBlocksByPrev;
75// Capped small on purpose. At the tip there is one block in flight; during 75// Capped small on purpose. At the tip there is one block in flight; during
76// initial download there are legitimately hundreds outstanding to one peer, 76// initial download there are legitimately hundreds outstanding to one peer,
77// and rotating those would duplicate the whole download to every other peer. 77// and rotating those would duplicate the whole download to every other peer.
78// Anything past the cap is simply not tracked and behaves as before. 78//
79// The cap is a shared resource and nothing on the path from inv to insertion
80// can tell a real block hash from 32 random bytes -- AlreadyHave only asks
81// whether we already hold the block, and there is no header to check work
82// against. So the table does not refuse a new hash when it is full; it makes
83// room. See MakeRoomInBlockFetch for the rule, which is what stops one peer
84// filling all eight slots and switching failover off for everybody.
85//
86// The initialisers matter: the insertion path uses operator[] and then tests
87// nTimeFirstAsked == 0 to detect a fresh entry. That worked on
88// value-initialisation alone, but only for as long as nobody gave this struct
89// a constructor.
79struct CBlockFetch 90struct CBlockFetch
80{ 91{
81 int64 nTimeFirstAsked; // when the first getdata for this block went out 92 int64 nTimeFirstAsked = 0; // when the first getdata for this block went out
82 int64 nTimeAsked; // when the latest getdata went out 93 int64 nTimeAsked = 0; // when the latest getdata went out
83 set<CAddress> setAsked; // peers tried since the round last restarted 94 CAddress addrAnnounced; // peer whose announcement put this hash here
95 set<CAddress> setAsked; // peers tried since the round last restarted
84}; 96};
85map<uint256, CBlockFetch> mapBlockFetch; 97map<uint256, CBlockFetch> mapBlockFetch;
86static const int BLOCK_FETCH_TRACK_MAX = 8; 98static const int BLOCK_FETCH_TRACK_MAX = 8;
@@ -89,6 +101,62 @@ static const int BLOCK_FETCH_TRACK_MAX = 8;
89// forever; a re-announcement recreates it. 101// forever; a re-announcement recreates it.
90static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60; 102static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60;
91 103
104// Drop one entry so a hash announced by addrNew can be tracked.
105//
106// Refusing the insertion instead was a remote off-switch for the whole
107// feature. An inv carrying eight hashes that never existed filled every slot
108// -- nothing between inv and here can tell those from real blocks -- and no
109// genuinely new block was tracked again until the give-up timer expired ten
110// minutes later, re-armable for one message per ten minutes from a single
111// connection.
112//
113// So evict instead, by occupancy: a peer may only take a slot from a peer
114// holding strictly more slots than itself, and otherwise recycles one of its
115// own. That is what keeps eviction from becoming the same attack in a new
116// shape. A peer that has filled the table is by definition the heaviest
117// holder, so its own entries are the only ones it can displace, while a peer
118// announcing its first block holds nothing and therefore always evicts from
119// the heaviest -- a genuine announcement is never refused a slot, whatever the
120// attacker holds or however many connections it holds it from. The one case
121// that costs a real fetch is eight distinct peers holding one entry each,
122// where a ninth takes the oldest; that is a busy healthy node, not an attack,
123// and losing the oldest of eight beats refusing the ninth.
124static void MakeRoomInBlockFetch(const CAddress& addrNew)
125{
126 if (mapBlockFetch.empty())
127 return;
128
129 map<CAddress, int> mapHeld;
130 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi)
131 mapHeld[(*mi).second.addrAnnounced]++;
132
133 int nMine = mapHeld.count(addrNew) ? mapHeld[addrNew] : 0;
134
135 int nMax = 0;
136 CAddress addrVictim;
137 for (map<CAddress, int>::iterator mi = mapHeld.begin(); mi != mapHeld.end(); ++mi)
138 if ((*mi).second > nMax)
139 {
140 nMax = (*mi).second;
141 addrVictim = (*mi).first;
142 }
143
144 // Nobody is holding more than this peer, so it recycles its own oldest
145 // rather than taking a slot off an equal.
146 if (nMax <= nMine)
147 addrVictim = addrNew;
148
149 map<uint256, CBlockFetch>::iterator miVictim = mapBlockFetch.end();
150 for (map<uint256, CBlockFetch>::iterator mi = mapBlockFetch.begin(); mi != mapBlockFetch.end(); ++mi)
151 if ((*mi).second.addrAnnounced == addrVictim &&
152 (miVictim == mapBlockFetch.end() ||
153 (*mi).second.nTimeFirstAsked < (*miVictim).second.nTimeFirstAsked))
154 miVictim = mi;
155
156 if (miVictim != mapBlockFetch.end())
157 mapBlockFetch.erase(miVictim);
158}
159
92map<uint256, CDataStream*> mapOrphanTransactions; 160map<uint256, CDataStream*> mapOrphanTransactions;
93multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; 161multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev;
94 162
@@ -3470,16 +3538,32 @@ bool SendMessages(CNode* pto)
3470 { 3538 {
3471 vAskFor.push_back(inv); 3539 vAskFor.push_back(inv);
3472 // Blocks get their delivery tracked so the failover pass 3540 // Blocks get their delivery tracked so the failover pass
3473 // below can chase the ones that go quiet. See mapBlockFetch 3541 // below can chase the ones that go quiet.
3474 // for why anything past the cap is left untracked. 3542 if (inv.type == MSG_BLOCK)
3475 if (inv.type == MSG_BLOCK &&
3476 (mapBlockFetch.count(inv.hash) || mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX))
3477 { 3543 {
3478 CBlockFetch& fetch = mapBlockFetch[inv.hash]; 3544 // Full, and this is a hash we are not already tracking:
3479 if (fetch.nTimeFirstAsked == 0) 3545 // make room rather than drop it on the floor, or one peer
3480 fetch.nTimeFirstAsked = GetTime(); 3546 // announcing eight hashes turns failover off for everyone.
3481 fetch.nTimeAsked = GetTime(); 3547 if (!mapBlockFetch.count(inv.hash) &&
3482 fetch.setAsked.insert(pto->addr); 3548 mapBlockFetch.size() >= BLOCK_FETCH_TRACK_MAX)
3549 MakeRoomInBlockFetch(pto->addr);
3550
3551 if (mapBlockFetch.count(inv.hash) ||
3552 mapBlockFetch.size() < BLOCK_FETCH_TRACK_MAX)
3553 {
3554 CBlockFetch& fetch = mapBlockFetch[inv.hash];
3555 if (fetch.nTimeFirstAsked == 0)
3556 {
3557 fetch.nTimeFirstAsked = GetTime();
3558 // Who is answerable for this hash existing. Set
3559 // once, on creation: the failover sweep asks other
3560 // peers later, and none of them chose to announce
3561 // it, so none of them should be charged for it.
3562 fetch.addrAnnounced = pto->addr;
3563 }
3564 fetch.nTimeAsked = GetTime();
3565 fetch.setAsked.insert(pto->addr);
3566 }
3483 } 3567 }
3484 } 3568 }
3485 pto->mapAskFor.erase(pto->mapAskFor.begin()); 3569 pto->mapAskFor.erase(pto->mapAskFor.begin());