diff options
Diffstat (limited to 'src/main.cpp')
| -rw-r--r-- | src/main.cpp | 110 |
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. | ||
| 79 | struct CBlockFetch | 90 | struct 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 | }; |
| 85 | map<uint256, CBlockFetch> mapBlockFetch; | 97 | map<uint256, CBlockFetch> mapBlockFetch; |
| 86 | static const int BLOCK_FETCH_TRACK_MAX = 8; | 98 | static 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. |
| 90 | static const int BLOCK_FETCH_GIVEUP_SECS = 10 * 60; | 102 | static 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. | ||
| 124 | static 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 | |||
| 92 | map<uint256, CDataStream*> mapOrphanTransactions; | 160 | map<uint256, CDataStream*> mapOrphanTransactions; |
| 93 | multimap<uint256, CDataStream*> mapOrphanTransactionsByPrev; | 161 | multimap<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()); |