Feathercoin  0.5.0
P2P Digital Currency
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
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 #include "hash.h"
7 
8 using namespace std;
9 
10 int CAddrInfo::GetTriedBucket(const std::vector<unsigned char> &nKey) const
11 {
12  CDataStream ss1(SER_GETHASH, 0);
13  std::vector<unsigned char> vchKey = GetKey();
14  ss1 << nKey << vchKey;
15  uint64 hash1 = Hash(ss1.begin(), ss1.end()).Get64();
16 
17  CDataStream ss2(SER_GETHASH, 0);
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 {
26  CDataStream ss1(SER_GETHASH, 0);
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 
32  CDataStream ss2(SER_GETHASH, 0);
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 
106 void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2)
107 {
108  if (nRndPos1 == nRndPos2)
109  return;
110 
111  assert(nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size());
112 
113  int nId1 = vRandom[nRndPos1];
114  int nId2 = vRandom[nRndPos2];
115 
116  assert(mapInfo.count(nId1) == 1);
117  assert(mapInfo.count(nId2) == 1);
118 
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;
133  int nOldestPos = -1;
134  for (unsigned int i = 0; i < ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT && i < vTried.size(); i++)
135  {
136  int nPos = GetRandInt(vTried.size() - i) + i;
137  int nTemp = vTried[nPos];
138  vTried[nPos] = vTried[i];
139  vTried[i] = nTemp;
140  assert(nOldest == -1 || mapInfo.count(nTemp) == 1);
141  if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) {
142  nOldest = nTemp;
143  nOldestPos = nPos;
144  }
145  }
146 
147  return nOldestPos;
148 }
149 
150 int CAddrMan::ShrinkNew(int nUBucket)
151 {
152  assert(nUBucket >= 0 && (unsigned int)nUBucket < vvNew.size());
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  {
158  assert(mapInfo.count(*it));
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  {
183  assert(nOldest == -1 || mapInfo.count(*it) == 1);
184  if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime)
185  nOldest = *it;
186  }
187  nI++;
188  }
189  assert(mapInfo.count(nOldest) == 1);
190  CAddrInfo &info = mapInfo[nOldest];
191  if (--info.nRefCount == 0)
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 {
206  assert(vvNew[nOrigin].count(nId) == 1);
207 
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 
216  assert(info.nRefCount == 0);
217 
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
235  assert(mapInfo.count(vTried[nPos]) == 1);
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 
245  // check whether there is place in that one,
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;
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  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
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)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 
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());
407  assert(mapInfo.count(vTried[nPos]) == 1);
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 {
414  // use a new node
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++;
425  assert(mapInfo.count(*it) == 1);
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);
505  assert(mapInfo.count(vRandom[n]) == 1);
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 }
int nRefCount
Definition: addrman.h:36
int GetNewBucket(const std::vector< unsigned char > &nKey, const CNetAddr &src) const
Definition: addrman.cpp:24
bool Add_(const CAddress &addr, const CNetAddr &source, int64 nTimePenalty)
Definition: addrman.cpp:313
uint64 nServices
Definition: protocol.h:103
const_iterator begin() const
Definition: serialize.h:884
CAddrInfo * Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId=NULL)
Definition: addrman.cpp:94
#define ADDRMAN_TRIED_BUCKETS_PER_GROUP
Definition: addrman.h:135
#define ADDRMAN_MIN_FAIL_DAYS
Definition: addrman.h:156
int nAttempts
Definition: addrman.h:33
CAddress Select_(int nUnkBias)
Definition: addrman.cpp:390
#define ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT
Definition: addrman.h:144
int64 nLastSuccess
Definition: addrman.h:27
Double ended buffer combining vector and stream-like interfaces.
Definition: serialize.h:799
int nRandomPos
Definition: addrman.h:42
unsigned long long uint64
Definition: serialize.h:26
#define ADDRMAN_TRIED_BUCKET_COUNT
Definition: addrman.h:123
bool fInTried
Definition: addrman.h:39
CAddrInfo * Find(const CNetAddr &addr, int *pnId=NULL)
Definition: addrman.cpp:81
int GetRandInt(int nMax)
Definition: util.cpp:190
Extended statistics about a CAddress.
Definition: addrman.h:20
const char * source
Definition: rpcconsole.cpp:27
int SelectTried(int nKBucket)
Definition: addrman.cpp:126
#define ADDRMAN_RETRIES
Definition: addrman.h:150
#define printf
Definition: rpcdump.cpp:12
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netbase.h:90
void MakeTried(CAddrInfo &info, int nId, int nOrigin)
Definition: addrman.cpp:204
int64 GetAdjustedTime()
Definition: util.cpp:1317
A CService with information about it as peer.
Definition: protocol.h:76
double GetChance(int64 nNow=GetAdjustedTime()) const
Definition: addrman.cpp:58
void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2)
Definition: addrman.cpp:106
int64 nLastTry
Definition: protocol.h:109
std::string ToString() const
Definition: netbase.cpp:1126
#define ADDRMAN_NEW_BUCKET_SIZE
Definition: addrman.h:132
uint256 Hash(const T1 pbegin, const T1 pend)
Definition: hash.h:16
void Connected_(const CService &addr, int64 nTime)
Definition: addrman.cpp:510
int ShrinkNew(int nUBucket)
Definition: addrman.cpp:150
bool IsRoutable() const
Definition: netbase.cpp:732
std::vector< unsigned char > GetGroup() const
Definition: netbase.cpp:815
#define ADDRMAN_TRIED_BUCKET_SIZE
Definition: addrman.h:126
IP address (IPv6, or IPv4 using mapped IPv6 range (::FFFF:0:0/96))
Definition: netbase.h:34
#define ADDRMAN_GETADDR_MAX
Definition: addrman.h:162
unsigned int nTime
Definition: protocol.h:106
void Good_(const CService &addr, int64 nTime)
Definition: addrman.cpp:262
bool IsTerrible(int64 nNow=GetAdjustedTime()) const
Definition: addrman.cpp:38
#define ADDRMAN_NEW_BUCKET_COUNT
Definition: addrman.h:129
int GetTriedBucket(const std::vector< unsigned char > &nKey) const
Definition: addrman.cpp:10
void GetAddr_(std::vector< CAddress > &vAddr)
Definition: addrman.cpp:494
void Attempt_(const CService &addr, int64 nTime)
Definition: addrman.cpp:371
#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP
Definition: addrman.h:138
#define ADDRMAN_HORIZON_DAYS
Definition: addrman.h:147
#define ADDRMAN_MAX_FAILURES
Definition: addrman.h:153
#define ADDRMAN_GETADDR_MAX_PCT
Definition: addrman.h:159
#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS
Definition: addrman.h:141
const_iterator end() const
Definition: serialize.h:886
long long int64
Definition: serialize.h:25