]>
Commit | Line | Data |
---|---|---|
5fee401f PW |
1 | // Copyright (c) 2012 Pieter Wuille |
2 | // Distributed under the MIT/X11 software license, see the accompanying | |
3a25a2b9 | 3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5fee401f PW |
4 | |
5 | #include "addrman.h" | |
6 | ||
7 | using namespace std; | |
8 | ||
9 | int CAddrInfo::GetTriedBucket(const std::vector<unsigned char> &nKey) const | |
10 | { | |
6b6aaa16 | 11 | CDataStream ss1(SER_GETHASH, 0); |
5fee401f PW |
12 | std::vector<unsigned char> vchKey = GetKey(); |
13 | ss1 << nKey << vchKey; | |
14 | uint64 hash1 = Hash(ss1.begin(), ss1.end()).Get64(); | |
15 | ||
6b6aaa16 | 16 | CDataStream ss2(SER_GETHASH, 0); |
5fee401f PW |
17 | std::vector<unsigned char> vchGroupKey = GetGroup(); |
18 | ss2 << nKey << vchGroupKey << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP); | |
19 | uint64 hash2 = Hash(ss2.begin(), ss2.end()).Get64(); | |
20 | return hash2 % ADDRMAN_TRIED_BUCKET_COUNT; | |
21 | } | |
22 | ||
23 | int CAddrInfo::GetNewBucket(const std::vector<unsigned char> &nKey, const CNetAddr& src) const | |
24 | { | |
6b6aaa16 | 25 | CDataStream ss1(SER_GETHASH, 0); |
5fee401f PW |
26 | std::vector<unsigned char> vchGroupKey = GetGroup(); |
27 | std::vector<unsigned char> vchSourceGroupKey = src.GetGroup(); | |
28 | ss1 << nKey << vchGroupKey << vchSourceGroupKey; | |
29 | uint64 hash1 = Hash(ss1.begin(), ss1.end()).Get64(); | |
30 | ||
6b6aaa16 | 31 | CDataStream ss2(SER_GETHASH, 0); |
5fee401f PW |
32 | ss2 << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP); |
33 | uint64 hash2 = Hash(ss2.begin(), ss2.end()).Get64(); | |
34 | return hash2 % ADDRMAN_NEW_BUCKET_COUNT; | |
35 | } | |
36 | ||
37 | bool CAddrInfo::IsTerrible(int64 nNow) const | |
38 | { | |
39 | if (nLastTry && nLastTry >= nNow-60) // never remove things tried the last minute | |
40 | return false; | |
41 | ||
42 | if (nTime > nNow + 10*60) // came in a flying DeLorean | |
43 | return true; | |
44 | ||
45 | if (nTime==0 || nNow-nTime > ADDRMAN_HORIZON_DAYS*86400) // not seen in over a month | |
46 | return true; | |
47 | ||
48 | if (nLastSuccess==0 && nAttempts>=ADDRMAN_RETRIES) // tried three times and never a success | |
49 | return true; | |
50 | ||
51 | if (nNow-nLastSuccess > ADDRMAN_MIN_FAIL_DAYS*86400 && nAttempts>=ADDRMAN_MAX_FAILURES) // 10 successive failures in the last week | |
52 | return true; | |
53 | ||
54 | return false; | |
55 | } | |
56 | ||
57 | double CAddrInfo::GetChance(int64 nNow) const | |
58 | { | |
59 | double fChance = 1.0; | |
60 | ||
61 | int64 nSinceLastSeen = nNow - nTime; | |
62 | int64 nSinceLastTry = nNow - nLastTry; | |
63 | ||
64 | if (nSinceLastSeen < 0) nSinceLastSeen = 0; | |
65 | if (nSinceLastTry < 0) nSinceLastTry = 0; | |
66 | ||
67 | fChance *= 600.0 / (600.0 + nSinceLastSeen); | |
68 | ||
69 | // deprioritize very recent attempts away | |
70 | if (nSinceLastTry < 60*10) | |
71 | fChance *= 0.01; | |
72 | ||
73 | // deprioritize 50% after each failed attempt | |
74 | for (int n=0; n<nAttempts; n++) | |
75 | fChance /= 1.5; | |
76 | ||
77 | return fChance; | |
78 | } | |
79 | ||
80 | CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int *pnId) | |
81 | { | |
82 | std::map<CNetAddr, int>::iterator it = mapAddr.find(addr); | |
83 | if (it == mapAddr.end()) | |
84 | return NULL; | |
85 | if (pnId) | |
86 | *pnId = (*it).second; | |
87 | std::map<int, CAddrInfo>::iterator it2 = mapInfo.find((*it).second); | |
88 | if (it2 != mapInfo.end()) | |
89 | return &(*it2).second; | |
90 | return NULL; | |
91 | } | |
92 | ||
93 | CAddrInfo* CAddrMan::Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId) | |
94 | { | |
95 | int nId = nIdCount++; | |
96 | mapInfo[nId] = CAddrInfo(addr, addrSource); | |
97 | mapAddr[addr] = nId; | |
98 | mapInfo[nId].nRandomPos = vRandom.size(); | |
99 | vRandom.push_back(nId); | |
100 | if (pnId) | |
101 | *pnId = nId; | |
102 | return &mapInfo[nId]; | |
103 | } | |
104 | ||
f621326c | 105 | void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2) |
5fee401f PW |
106 | { |
107 | if (nRndPos1 == nRndPos2) | |
108 | return; | |
109 | ||
29a86a17 PW |
110 | assert(nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size()); |
111 | ||
5fee401f PW |
112 | int nId1 = vRandom[nRndPos1]; |
113 | int nId2 = vRandom[nRndPos2]; | |
114 | ||
29a86a17 PW |
115 | assert(mapInfo.count(nId1) == 1); |
116 | assert(mapInfo.count(nId2) == 1); | |
117 | ||
5fee401f PW |
118 | mapInfo[nId1].nRandomPos = nRndPos2; |
119 | mapInfo[nId2].nRandomPos = nRndPos1; | |
120 | ||
121 | vRandom[nRndPos1] = nId2; | |
122 | vRandom[nRndPos2] = nId1; | |
123 | } | |
124 | ||
125 | int CAddrMan::SelectTried(int nKBucket) | |
126 | { | |
127 | std::vector<int> &vTried = vvTried[nKBucket]; | |
128 | ||
129 | // random shuffle the first few elements (using the entire list) | |
130 | // find the least recently tried among them | |
131 | int64 nOldest = -1; | |
56f1e912 | 132 | int nOldestPos = -1; |
c376ac35 | 133 | for (unsigned int i = 0; i < ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT && i < vTried.size(); i++) |
5fee401f PW |
134 | { |
135 | int nPos = GetRandInt(vTried.size() - i) + i; | |
136 | int nTemp = vTried[nPos]; | |
137 | vTried[nPos] = vTried[i]; | |
138 | vTried[i] = nTemp; | |
29a86a17 | 139 | assert(nOldest == -1 || mapInfo.count(nTemp) == 1); |
56f1e912 | 140 | if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) { |
5fee401f | 141 | nOldest = nTemp; |
56f1e912 PW |
142 | nOldestPos = nPos; |
143 | } | |
5fee401f PW |
144 | } |
145 | ||
56f1e912 | 146 | return nOldestPos; |
5fee401f PW |
147 | } |
148 | ||
149 | int CAddrMan::ShrinkNew(int nUBucket) | |
150 | { | |
f621326c | 151 | assert(nUBucket >= 0 && (unsigned int)nUBucket < vvNew.size()); |
5fee401f PW |
152 | std::set<int> &vNew = vvNew[nUBucket]; |
153 | ||
154 | // first look for deletable items | |
155 | for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) | |
156 | { | |
29a86a17 | 157 | assert(mapInfo.count(*it)); |
5fee401f PW |
158 | CAddrInfo &info = mapInfo[*it]; |
159 | if (info.IsTerrible()) | |
160 | { | |
161 | if (--info.nRefCount == 0) | |
162 | { | |
163 | SwapRandom(info.nRandomPos, vRandom.size()-1); | |
164 | vRandom.pop_back(); | |
165 | mapAddr.erase(info); | |
166 | mapInfo.erase(*it); | |
167 | nNew--; | |
168 | } | |
169 | vNew.erase(it); | |
170 | return 0; | |
171 | } | |
172 | } | |
173 | ||
174 | // otherwise, select four randomly, and pick the oldest of those to replace | |
175 | int n[4] = {GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size())}; | |
176 | int nI = 0; | |
177 | int nOldest = -1; | |
178 | for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) | |
179 | { | |
180 | if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3]) | |
181 | { | |
29a86a17 | 182 | assert(nOldest == -1 || mapInfo.count(*it) == 1); |
5fee401f PW |
183 | if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime) |
184 | nOldest = *it; | |
185 | } | |
186 | nI++; | |
187 | } | |
29a86a17 | 188 | assert(mapInfo.count(nOldest) == 1); |
5fee401f | 189 | CAddrInfo &info = mapInfo[nOldest]; |
ea0796bd | 190 | if (--info.nRefCount == 0) |
5fee401f PW |
191 | { |
192 | SwapRandom(info.nRandomPos, vRandom.size()-1); | |
193 | vRandom.pop_back(); | |
194 | mapAddr.erase(info); | |
195 | mapInfo.erase(nOldest); | |
196 | nNew--; | |
197 | } | |
198 | vNew.erase(nOldest); | |
199 | ||
200 | return 1; | |
201 | } | |
202 | ||
203 | void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) | |
204 | { | |
29a86a17 PW |
205 | assert(vvNew[nOrigin].count(nId) == 1); |
206 | ||
5fee401f PW |
207 | // remove the entry from all new buckets |
208 | for (std::vector<std::set<int> >::iterator it = vvNew.begin(); it != vvNew.end(); it++) | |
209 | { | |
210 | if ((*it).erase(nId)) | |
211 | info.nRefCount--; | |
212 | } | |
213 | nNew--; | |
214 | ||
29a86a17 PW |
215 | assert(info.nRefCount == 0); |
216 | ||
5fee401f PW |
217 | // what tried bucket to move the entry to |
218 | int nKBucket = info.GetTriedBucket(nKey); | |
219 | std::vector<int> &vTried = vvTried[nKBucket]; | |
220 | ||
221 | // first check whether there is place to just add it | |
222 | if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) | |
223 | { | |
224 | vTried.push_back(nId); | |
225 | nTried++; | |
226 | info.fInTried = true; | |
227 | return; | |
228 | } | |
229 | ||
230 | // otherwise, find an item to evict | |
231 | int nPos = SelectTried(nKBucket); | |
232 | ||
233 | // find which new bucket it belongs to | |
29a86a17 | 234 | assert(mapInfo.count(vTried[nPos]) == 1); |
5fee401f PW |
235 | int nUBucket = mapInfo[vTried[nPos]].GetNewBucket(nKey); |
236 | std::set<int> &vNew = vvNew[nUBucket]; | |
237 | ||
238 | // remove the to-be-replaced tried entry from the tried set | |
239 | CAddrInfo& infoOld = mapInfo[vTried[nPos]]; | |
240 | infoOld.fInTried = false; | |
241 | infoOld.nRefCount = 1; | |
242 | // do not update nTried, as we are going to move something else there immediately | |
243 | ||
ea0796bd | 244 | // check whether there is place in that one, |
5fee401f PW |
245 | if (vNew.size() < ADDRMAN_NEW_BUCKET_SIZE) |
246 | { | |
247 | // if so, move it back there | |
248 | vNew.insert(vTried[nPos]); | |
249 | } else { | |
250 | // otherwise, move it to the new bucket nId came from (there is certainly place there) | |
251 | vvNew[nOrigin].insert(vTried[nPos]); | |
252 | } | |
253 | nNew++; | |
254 | ||
255 | vTried[nPos] = nId; | |
256 | // we just overwrote an entry in vTried; no need to update nTried | |
257 | info.fInTried = true; | |
258 | return; | |
259 | } | |
260 | ||
261 | void CAddrMan::Good_(const CService &addr, int64 nTime) | |
262 | { | |
263 | // printf("Good: addr=%s\n", addr.ToString().c_str()); | |
264 | ||
265 | int nId; | |
266 | CAddrInfo *pinfo = Find(addr, &nId); | |
267 | ||
268 | // if not found, bail out | |
269 | if (!pinfo) | |
270 | return; | |
271 | ||
272 | CAddrInfo &info = *pinfo; | |
273 | ||
274 | // check whether we are talking about the exact same CService (including same port) | |
275 | if (info != addr) | |
276 | return; | |
277 | ||
278 | // update info | |
279 | info.nLastSuccess = nTime; | |
280 | info.nLastTry = nTime; | |
281 | info.nTime = nTime; | |
282 | info.nAttempts = 0; | |
283 | ||
284 | // if it is already in the tried set, don't do anything else | |
285 | if (info.fInTried) | |
286 | return; | |
287 | ||
288 | // find a bucket it is in now | |
289 | int nRnd = GetRandInt(vvNew.size()); | |
290 | int nUBucket = -1; | |
c376ac35 | 291 | for (unsigned int n = 0; n < vvNew.size(); n++) |
5fee401f PW |
292 | { |
293 | int nB = (n+nRnd) % vvNew.size(); | |
294 | std::set<int> &vNew = vvNew[nB]; | |
295 | if (vNew.count(nId)) | |
296 | { | |
297 | nUBucket = nB; | |
298 | break; | |
299 | } | |
300 | } | |
301 | ||
302 | // if no bucket is found, something bad happened; | |
303 | // TODO: maybe re-add the node, but for now, just bail out | |
304 | if (nUBucket == -1) return; | |
305 | ||
306 | printf("Moving %s to tried\n", addr.ToString().c_str()); | |
307 | ||
308 | // move nId to the tried tables | |
309 | MakeTried(info, nId, nUBucket); | |
310 | } | |
311 | ||
312 | bool CAddrMan::Add_(const CAddress &addr, const CNetAddr& source, int64 nTimePenalty) | |
313 | { | |
314 | if (!addr.IsRoutable()) | |
315 | return false; | |
316 | ||
317 | bool fNew = false; | |
318 | int nId; | |
319 | CAddrInfo *pinfo = Find(addr, &nId); | |
320 | ||
321 | if (pinfo) | |
322 | { | |
323 | // periodically update nTime | |
324 | bool fCurrentlyOnline = (GetAdjustedTime() - addr.nTime < 24 * 60 * 60); | |
325 | int64 nUpdateInterval = (fCurrentlyOnline ? 60 * 60 : 24 * 60 * 60); | |
326 | if (addr.nTime && (!pinfo->nTime || pinfo->nTime < addr.nTime - nUpdateInterval - nTimePenalty)) | |
327 | pinfo->nTime = max((int64)0, addr.nTime - nTimePenalty); | |
328 | ||
329 | // add services | |
330 | pinfo->nServices |= addr.nServices; | |
331 | ||
332 | // do not update if no new information is present | |
6642ffb7 | 333 | if (!addr.nTime || (pinfo->nTime && addr.nTime <= pinfo->nTime)) |
5fee401f PW |
334 | return false; |
335 | ||
336 | // do not update if the entry was already in the "tried" table | |
337 | if (pinfo->fInTried) | |
338 | return false; | |
339 | ||
340 | // do not update if the max reference count is reached | |
341 | if (pinfo->nRefCount == ADDRMAN_NEW_BUCKETS_PER_ADDRESS) | |
342 | return false; | |
343 | ||
344 | // stochastic test: previous nRefCount == N: 2^N times harder to increase it | |
345 | int nFactor = 1; | |
346 | for (int n=0; n<pinfo->nRefCount; n++) | |
347 | nFactor *= 2; | |
348 | if (nFactor > 1 && (GetRandInt(nFactor) != 0)) | |
349 | return false; | |
350 | } else { | |
351 | pinfo = Create(addr, source, &nId); | |
352 | pinfo->nTime = max((int64)0, (int64)pinfo->nTime - nTimePenalty); | |
353 | // printf("Added %s [nTime=%fhr]\n", pinfo->ToString().c_str(), (GetAdjustedTime() - pinfo->nTime) / 3600.0); | |
354 | nNew++; | |
355 | fNew = true; | |
356 | } | |
357 | ||
358 | int nUBucket = pinfo->GetNewBucket(nKey, source); | |
359 | std::set<int> &vNew = vvNew[nUBucket]; | |
360 | if (!vNew.count(nId)) | |
361 | { | |
362 | pinfo->nRefCount++; | |
363 | if (vNew.size() == ADDRMAN_NEW_BUCKET_SIZE) | |
364 | ShrinkNew(nUBucket); | |
365 | vvNew[nUBucket].insert(nId); | |
366 | } | |
367 | return fNew; | |
368 | } | |
369 | ||
370 | void CAddrMan::Attempt_(const CService &addr, int64 nTime) | |
371 | { | |
372 | CAddrInfo *pinfo = Find(addr); | |
373 | ||
374 | // if not found, bail out | |
375 | if (!pinfo) | |
376 | return; | |
377 | ||
378 | CAddrInfo &info = *pinfo; | |
379 | ||
380 | // check whether we are talking about the exact same CService (including same port) | |
381 | if (info != addr) | |
382 | return; | |
383 | ||
384 | // update info | |
385 | info.nLastTry = nTime; | |
386 | info.nAttempts++; | |
387 | } | |
388 | ||
389 | CAddress CAddrMan::Select_(int nUnkBias) | |
390 | { | |
391 | if (size() == 0) | |
392 | return CAddress(); | |
393 | ||
394 | double nCorTried = sqrt(nTried) * (100.0 - nUnkBias); | |
395 | double nCorNew = sqrt(nNew) * nUnkBias; | |
396 | if ((nCorTried + nCorNew)*GetRandInt(1<<30)/(1<<30) < nCorTried) | |
397 | { | |
398 | // use a tried node | |
399 | double fChanceFactor = 1.0; | |
400 | while(1) | |
401 | { | |
402 | int nKBucket = GetRandInt(vvTried.size()); | |
403 | std::vector<int> &vTried = vvTried[nKBucket]; | |
404 | if (vTried.size() == 0) continue; | |
405 | int nPos = GetRandInt(vTried.size()); | |
29a86a17 | 406 | assert(mapInfo.count(vTried[nPos]) == 1); |
5fee401f PW |
407 | CAddrInfo &info = mapInfo[vTried[nPos]]; |
408 | if (GetRandInt(1<<30) < fChanceFactor*info.GetChance()*(1<<30)) | |
409 | return info; | |
410 | fChanceFactor *= 1.2; | |
411 | } | |
412 | } else { | |
30c8a408 | 413 | // use a new node |
5fee401f PW |
414 | double fChanceFactor = 1.0; |
415 | while(1) | |
416 | { | |
417 | int nUBucket = GetRandInt(vvNew.size()); | |
418 | std::set<int> &vNew = vvNew[nUBucket]; | |
419 | if (vNew.size() == 0) continue; | |
420 | int nPos = GetRandInt(vNew.size()); | |
421 | std::set<int>::iterator it = vNew.begin(); | |
422 | while (nPos--) | |
423 | it++; | |
29a86a17 | 424 | assert(mapInfo.count(*it) == 1); |
5fee401f PW |
425 | CAddrInfo &info = mapInfo[*it]; |
426 | if (GetRandInt(1<<30) < fChanceFactor*info.GetChance()*(1<<30)) | |
427 | return info; | |
428 | fChanceFactor *= 1.2; | |
429 | } | |
430 | } | |
431 | } | |
432 | ||
433 | #ifdef DEBUG_ADDRMAN | |
434 | int CAddrMan::Check_() | |
435 | { | |
436 | std::set<int> setTried; | |
437 | std::map<int, int> mapNew; | |
438 | ||
439 | if (vRandom.size() != nTried + nNew) return -7; | |
440 | ||
441 | for (std::map<int, CAddrInfo>::iterator it = mapInfo.begin(); it != mapInfo.end(); it++) | |
442 | { | |
443 | int n = (*it).first; | |
444 | CAddrInfo &info = (*it).second; | |
445 | if (info.fInTried) | |
446 | { | |
447 | ||
448 | if (!info.nLastSuccess) return -1; | |
449 | if (info.nRefCount) return -2; | |
450 | setTried.insert(n); | |
451 | } else { | |
452 | if (info.nRefCount < 0 || info.nRefCount > ADDRMAN_NEW_BUCKETS_PER_ADDRESS) return -3; | |
453 | if (!info.nRefCount) return -4; | |
454 | mapNew[n] = info.nRefCount; | |
455 | } | |
456 | if (mapAddr[info] != n) return -5; | |
457 | if (info.nRandomPos<0 || info.nRandomPos>=vRandom.size() || vRandom[info.nRandomPos] != n) return -14; | |
458 | if (info.nLastTry < 0) return -6; | |
459 | if (info.nLastSuccess < 0) return -8; | |
460 | } | |
461 | ||
462 | if (setTried.size() != nTried) return -9; | |
463 | if (mapNew.size() != nNew) return -10; | |
464 | ||
465 | for (int n=0; n<vvTried.size(); n++) | |
466 | { | |
467 | std::vector<int> &vTried = vvTried[n]; | |
468 | for (std::vector<int>::iterator it = vTried.begin(); it != vTried.end(); it++) | |
469 | { | |
470 | if (!setTried.count(*it)) return -11; | |
471 | setTried.erase(*it); | |
472 | } | |
473 | } | |
474 | ||
475 | for (int n=0; n<vvNew.size(); n++) | |
476 | { | |
477 | std::set<int> &vNew = vvNew[n]; | |
478 | for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) | |
479 | { | |
480 | if (!mapNew.count(*it)) return -12; | |
481 | if (--mapNew[*it] == 0) | |
482 | mapNew.erase(*it); | |
483 | } | |
484 | } | |
485 | ||
486 | if (setTried.size()) return -13; | |
487 | if (mapNew.size()) return -15; | |
488 | ||
489 | return 0; | |
490 | } | |
491 | #endif | |
492 | ||
493 | void CAddrMan::GetAddr_(std::vector<CAddress> &vAddr) | |
494 | { | |
495 | int nNodes = ADDRMAN_GETADDR_MAX_PCT*vRandom.size()/100; | |
496 | if (nNodes > ADDRMAN_GETADDR_MAX) | |
497 | nNodes = ADDRMAN_GETADDR_MAX; | |
498 | ||
499 | // perform a random shuffle over the first nNodes elements of vRandom (selecting from all) | |
500 | for (int n = 0; n<nNodes; n++) | |
501 | { | |
502 | int nRndPos = GetRandInt(vRandom.size() - n) + n; | |
503 | SwapRandom(n, nRndPos); | |
29a86a17 | 504 | assert(mapInfo.count(vRandom[n]) == 1); |
5fee401f PW |
505 | vAddr.push_back(mapInfo[vRandom[n]]); |
506 | } | |
507 | } | |
508 | ||
509 | void CAddrMan::Connected_(const CService &addr, int64 nTime) | |
510 | { | |
511 | CAddrInfo *pinfo = Find(addr); | |
512 | ||
513 | // if not found, bail out | |
514 | if (!pinfo) | |
515 | return; | |
516 | ||
517 | CAddrInfo &info = *pinfo; | |
518 | ||
519 | // check whether we are talking about the exact same CService (including same port) | |
520 | if (info != addr) | |
521 | return; | |
522 | ||
523 | // update info | |
524 | int64 nUpdateInterval = 20 * 60; | |
525 | if (nTime - info.nTime > nUpdateInterval) | |
526 | info.nTime = nTime; | |
527 | } |