Anoncoin  0.9.4
P2P Digital Currency
addrman.cpp
Go to the documentation of this file.
1 // Copyright (c) 2012 Pieter Wuille
2 // Distributed under the MIT/X11 software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
5 #include "addrman.h"
6 
7 #include "hash.h"
8 #include "serialize.h"
9 
10 using namespace std;
11 
12 int CAddrInfo::GetTriedBucket(const std::vector<unsigned char> &nKey) const
13 {
14  CDataStream ss1(SER_GETHASH, 0);
15  std::vector<unsigned char> vchKey = GetKey();
16  ss1 << nKey << vchKey;
17  uint64_t hash1 = Hash(ss1.begin(), ss1.end()).GetLow64();
18 
19  CDataStream ss2(SER_GETHASH, 0);
20  std::vector<unsigned char> vchGroupKey = GetGroup();
21  ss2 << nKey << vchGroupKey << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP);
22  uint64_t hash2 = Hash(ss2.begin(), ss2.end()).GetLow64();
23  return hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
24 }
25 
26 int CAddrInfo::GetNewBucket(const std::vector<unsigned char> &nKey, const CNetAddr& src) const
27 {
28  CDataStream ss1(SER_GETHASH, 0);
29  std::vector<unsigned char> vchGroupKey = GetGroup();
30  std::vector<unsigned char> vchSourceGroupKey = src.GetGroup();
31  ss1 << nKey << vchGroupKey << vchSourceGroupKey;
32  uint64_t hash1 = Hash(ss1.begin(), ss1.end()).GetLow64();
33 
34  CDataStream ss2(SER_GETHASH, 0);
35  ss2 << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP);
36  uint64_t hash2 = Hash(ss2.begin(), ss2.end()).GetLow64();
37  return hash2 % ADDRMAN_NEW_BUCKET_COUNT;
38 }
39 
40 bool CAddrInfo::IsTerrible(int64_t nNow) const
41 {
42  if (nLastTry && nLastTry >= nNow-60) // never remove things tried the last minute
43  return false;
44 
45  if (nTime > nNow + 10*60) // came in a flying DeLorean
46  return true;
47 
48  if (nTime==0 || nNow-nTime > ADDRMAN_HORIZON_DAYS*86400) // not seen in over a month
49  return true;
50 
51  if (nLastSuccess==0 && nAttempts>=ADDRMAN_RETRIES) // tried three times and never a success
52  return true;
53 
54  if (nNow-nLastSuccess > ADDRMAN_MIN_FAIL_DAYS*86400 && nAttempts>=ADDRMAN_MAX_FAILURES) // 10 successive failures in the last week
55  return true;
56 
57  return false;
58 }
59 
60 double CAddrInfo::GetChance(int64_t nNow) const
61 {
62  double fChance = 1.0;
63 
64  int64_t nSinceLastSeen = nNow - nTime;
65  int64_t nSinceLastTry = nNow - nLastTry;
66 
67  if (nSinceLastSeen < 0) nSinceLastSeen = 0;
68  if (nSinceLastTry < 0) nSinceLastTry = 0;
69 
70  fChance *= 600.0 / (600.0 + nSinceLastSeen);
71 
72  // deprioritize very recent attempts away
73  if (nSinceLastTry < 60*10)
74  fChance *= 0.01;
75 
76  // deprioritize 50% after each failed attempt
77  for (int n=0; n<nAttempts; n++)
78  fChance /= 1.5;
79 
80  return fChance;
81 }
82 
83 CAddrInfo* CAddrMan::Find(const CNetAddr& addr, int *pnId)
84 {
85  std::map<CNetAddr, int>::iterator it = mapAddr.find(addr);
86  if (it == mapAddr.end())
87  return NULL;
88  if (pnId)
89  *pnId = (*it).second;
90  std::map<int, CAddrInfo>::iterator it2 = mapInfo.find((*it).second);
91  if (it2 != mapInfo.end())
92  return &(*it2).second;
93  return NULL;
94 }
95 
96 CAddrInfo* CAddrMan::Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId)
97 {
98  int nId = nIdCount++;
99  mapInfo[nId] = CAddrInfo(addr, addrSource);
100  mapAddr[addr] = nId;
101  mapInfo[nId].nRandomPos = vRandom.size();
102  vRandom.push_back(nId);
103  if (pnId)
104  *pnId = nId;
105  return &mapInfo[nId];
106 }
107 
108 void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2)
109 {
110  if (nRndPos1 == nRndPos2)
111  return;
112 
113  assert(nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size());
114 
115  int nId1 = vRandom[nRndPos1];
116  int nId2 = vRandom[nRndPos2];
117 
118  assert(mapInfo.count(nId1) == 1);
119  assert(mapInfo.count(nId2) == 1);
120 
121  mapInfo[nId1].nRandomPos = nRndPos2;
122  mapInfo[nId2].nRandomPos = nRndPos1;
123 
124  vRandom[nRndPos1] = nId2;
125  vRandom[nRndPos2] = nId1;
126 }
127 
128 int CAddrMan::SelectTried(int nKBucket)
129 {
130  std::vector<int> &vTried = vvTried[nKBucket];
131 
132  // random shuffle the first few elements (using the entire list)
133  // find the least recently tried among them
134  int64_t nOldest = -1;
135  int nOldestPos = -1;
136  for (unsigned int i = 0; i < ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT && i < vTried.size(); i++)
137  {
138  int nPos = GetRandInt(vTried.size() - i) + i;
139  int nTemp = vTried[nPos];
140  vTried[nPos] = vTried[i];
141  vTried[i] = nTemp;
142  assert(nOldest == -1 || mapInfo.count(nTemp) == 1);
143  if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) {
144  nOldest = nTemp;
145  nOldestPos = nPos;
146  }
147  }
148 
149  return nOldestPos;
150 }
151 
152 int CAddrMan::ShrinkNew(int nUBucket)
153 {
154  assert(nUBucket >= 0 && (unsigned int)nUBucket < vvNew.size());
155  std::set<int> &vNew = vvNew[nUBucket];
156 
157  // first look for deletable items
158  for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++)
159  {
160  assert(mapInfo.count(*it));
161  CAddrInfo &info = mapInfo[*it];
162  if (info.IsTerrible())
163  {
164  if (--info.nRefCount == 0)
165  {
166  SwapRandom(info.nRandomPos, vRandom.size()-1);
167  vRandom.pop_back();
168  mapAddr.erase(info);
169  mapInfo.erase(*it);
170  nNew--;
171  }
172  vNew.erase(it);
173  return 0;
174  }
175  }
176 
177  // otherwise, select four randomly, and pick the oldest of those to replace
178  int n[4] = {GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size())};
179  int nI = 0;
180  int nOldest = -1;
181  for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++)
182  {
183  if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3])
184  {
185  assert(nOldest == -1 || mapInfo.count(*it) == 1);
186  if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime)
187  nOldest = *it;
188  }
189  nI++;
190  }
191  assert(mapInfo.count(nOldest) == 1);
192  CAddrInfo &info = mapInfo[nOldest];
193  if (--info.nRefCount == 0)
194  {
195  SwapRandom(info.nRandomPos, vRandom.size()-1);
196  vRandom.pop_back();
197  mapAddr.erase(info);
198  mapInfo.erase(nOldest);
199  nNew--;
200  }
201  vNew.erase(nOldest);
202 
203  return 1;
204 }
205 
206 void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin)
207 {
208  assert(vvNew[nOrigin].count(nId) == 1);
209 
210  // remove the entry from all new buckets
211  for (std::vector<std::set<int> >::iterator it = vvNew.begin(); it != vvNew.end(); it++)
212  {
213  if ((*it).erase(nId))
214  info.nRefCount--;
215  }
216  nNew--;
217 
218  assert(info.nRefCount == 0);
219 
220  // what tried bucket to move the entry to
221  int nKBucket = info.GetTriedBucket(nKey);
222  std::vector<int> &vTried = vvTried[nKBucket];
223 
224  // first check whether there is place to just add it
225  if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE)
226  {
227  vTried.push_back(nId);
228  nTried++;
229  info.fInTried = true;
230  return;
231  }
232 
233  // otherwise, find an item to evict
234  int nPos = SelectTried(nKBucket);
235 
236  // find which new bucket it belongs to
237  assert(mapInfo.count(vTried[nPos]) == 1);
238  int nUBucket = mapInfo[vTried[nPos]].GetNewBucket(nKey);
239  std::set<int> &vNew = vvNew[nUBucket];
240 
241  // remove the to-be-replaced tried entry from the tried set
242  CAddrInfo& infoOld = mapInfo[vTried[nPos]];
243  infoOld.fInTried = false;
244  infoOld.nRefCount = 1;
245  // do not update nTried, as we are going to move something else there immediately
246 
247  // check whether there is place in that one,
248  if (vNew.size() < ADDRMAN_NEW_BUCKET_SIZE)
249  {
250  // if so, move it back there
251  vNew.insert(vTried[nPos]);
252  } else {
253  // otherwise, move it to the new bucket nId came from (there is certainly place there)
254  vvNew[nOrigin].insert(vTried[nPos]);
255  }
256  nNew++;
257 
258  vTried[nPos] = nId;
259  // we just overwrote an entry in vTried; no need to update nTried
260  info.fInTried = true;
261  return;
262 }
263 
264 void CAddrMan::Good_(const CService &addr, int64_t nTime)
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;
292  for (unsigned int n = 0; n < vvNew.size(); n++)
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  LogPrint("addrman", "Moving %s to tried\n", addr.ToString());
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_t 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_t nUpdateInterval = (fCurrentlyOnline ? 60 * 60 : 24 * 60 * 60);
327  if (addr.nTime && (!pinfo->nTime || pinfo->nTime < addr.nTime - nUpdateInterval - nTimePenalty))
328  pinfo->nTime = max((int64_t)0, addr.nTime - nTimePenalty);
329 
330  // add services
331  pinfo->nServices |= addr.nServices;
332 
333  // do not update if no new information is present
334  if (!addr.nTime || (pinfo->nTime && addr.nTime <= pinfo->nTime))
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
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_t)0, (int64_t)pinfo->nTime - nTimePenalty);
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_t 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 
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());
406  assert(mapInfo.count(vTried[nPos]) == 1);
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 {
413  // use a new node
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++;
424  assert(mapInfo.count(*it) == 1);
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  unsigned int nNodes = ADDRMAN_GETADDR_MAX_PCT * vRandom.size() / 100;
496  if (nNodes > ADDRMAN_GETADDR_MAX)
497  nNodes = ADDRMAN_GETADDR_MAX;
498 
499  // gather a list of random nodes, skipping those of low quality
500  for (unsigned int n = 0; n < vRandom.size(); n++)
501  {
502  if (vAddr.size() >= nNodes)
503  break;
504 
505  int nRndPos = GetRandInt(vRandom.size() - n) + n;
506  SwapRandom(n, nRndPos);
507  assert(mapInfo.count(vRandom[n]) == 1);
508 
509  const CAddrInfo& ai = mapInfo[vRandom[n]];
510  if (!ai.IsTerrible())
511  vAddr.push_back(ai);
512  }
513 }
514 
515 void CAddrMan::Connected_(const CService &addr, int64_t nTime)
516 {
517  CAddrInfo *pinfo = Find(addr);
518 
519  // if not found, bail out
520  if (!pinfo)
521  return;
522 
523  CAddrInfo &info = *pinfo;
524 
525  // check whether we are talking about the exact same CService (including same port)
526  if (info != addr)
527  return;
528 
529  // update info
530  int64_t nUpdateInterval = 20 * 60;
531  if (nTime - info.nTime > nUpdateInterval)
532  info.nTime = nTime;
533 }
int nRefCount
Definition: addrman.h:37
int GetNewBucket(const std::vector< unsigned char > &nKey, const CNetAddr &src) const
Definition: addrman.cpp:26
uint64_t nServices
Definition: protocol.h:118
const_iterator begin() const
Definition: serialize.h:933
CAddrInfo * Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId=NULL)
Definition: addrman.cpp:96
#define ADDRMAN_TRIED_BUCKETS_PER_GROUP
Definition: addrman.h:137
#define ADDRMAN_MIN_FAIL_DAYS
Definition: addrman.h:158
int nAttempts
Definition: addrman.h:34
CAddress Select_(int nUnkBias)
Definition: addrman.cpp:389
#define ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT
Definition: addrman.h:146
Double ended buffer combining vector and stream-like interfaces.
Definition: serialize.h:848
int nRandomPos
Definition: addrman.h:43
#define ADDRMAN_TRIED_BUCKET_COUNT
Definition: addrman.h:125
double GetChance(int64_t nNow=GetAdjustedTime()) const
Definition: addrman.cpp:60
bool fInTried
Definition: addrman.h:40
CAddrInfo * Find(const CNetAddr &addr, int *pnId=NULL)
Definition: addrman.cpp:83
int GetRandInt(int nMax)
Definition: util.cpp:201
Extended statistics about a CAddress.
Definition: addrman.h:21
const char * source
Definition: rpcconsole.cpp:52
int64_t GetAdjustedTime()
Definition: util.cpp:1241
int SelectTried(int nKBucket)
Definition: addrman.cpp:128
#define ADDRMAN_RETRIES
Definition: addrman.h:152
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netbase.h:109
void MakeTried(CAddrInfo &info, int nId, int nOrigin)
Definition: addrman.cpp:206
int64_t nLastTry
Definition: protocol.h:124
A CService with information about it as peer.
Definition: protocol.h:91
void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2)
Definition: addrman.cpp:108
std::string ToString() const
Definition: netbase.cpp:1325
#define ADDRMAN_NEW_BUCKET_SIZE
Definition: addrman.h:134
uint256 Hash(const T1 pbegin, const T1 pend)
Definition: hash.h:20
int ShrinkNew(int nUBucket)
Definition: addrman.cpp:152
bool IsRoutable() const
Definition: netbase.cpp:855
std::vector< unsigned char > GetGroup() const
Definition: netbase.cpp:959
#define ADDRMAN_TRIED_BUCKET_SIZE
Definition: addrman.h:128
IP address (IPv6, or IPv4 using mapped IPv6 range (::FFFF:0:0/96))
Definition: netbase.h:45
#define ADDRMAN_GETADDR_MAX
Definition: addrman.h:164
void Good_(const CService &addr, int64_t nTime)
Definition: addrman.cpp:264
unsigned int nTime
Definition: protocol.h:121
#define ADDRMAN_NEW_BUCKET_COUNT
Definition: addrman.h:131
int GetTriedBucket(const std::vector< unsigned char > &nKey) const
Definition: addrman.cpp:12
bool Add_(const CAddress &addr, const CNetAddr &source, int64_t nTimePenalty)
Definition: addrman.cpp:313
void Connected_(const CService &addr, int64_t nTime)
Definition: addrman.cpp:515
void GetAddr_(std::vector< CAddress > &vAddr)
Definition: addrman.cpp:493
#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP
Definition: addrman.h:140
#define ADDRMAN_HORIZON_DAYS
Definition: addrman.h:149
void Attempt_(const CService &addr, int64_t nTime)
Definition: addrman.cpp:370
#define ADDRMAN_MAX_FAILURES
Definition: addrman.h:155
int64_t nLastSuccess
Definition: addrman.h:28
bool IsTerrible(int64_t nNow=GetAdjustedTime()) const
Definition: addrman.cpp:40
#define ADDRMAN_GETADDR_MAX_PCT
Definition: addrman.h:161
#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS
Definition: addrman.h:143
const_iterator end() const
Definition: serialize.h:935