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