]> Git Repo - VerusCoin.git/blob - src/addrman.h
Merge pull request #6022
[VerusCoin.git] / src / addrman.h
1 // Copyright (c) 2012 Pieter Wuille
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #ifndef BITCOIN_ADDRMAN_H
6 #define BITCOIN_ADDRMAN_H
7
8 #include "netbase.h"
9 #include "protocol.h"
10 #include "random.h"
11 #include "sync.h"
12 #include "timedata.h"
13 #include "util.h"
14
15 #include <map>
16 #include <set>
17 #include <stdint.h>
18 #include <vector>
19
20 /** 
21  * Extended statistics about a CAddress 
22  */
23 class CAddrInfo : public CAddress
24 {
25 public:
26     //! last try whatsoever by us (memory only)
27     int64_t nLastTry;
28
29 private:
30     //! where knowledge about this address first came from
31     CNetAddr source;
32
33     //! last successful connection by us
34     int64_t nLastSuccess;
35
36     //! connection attempts since last successful attempt
37     int nAttempts;
38
39     //! reference count in new sets (memory only)
40     int nRefCount;
41
42     //! in tried set? (memory only)
43     bool fInTried;
44
45     //! position in vRandom
46     int nRandomPos;
47
48     friend class CAddrMan;
49
50 public:
51
52     ADD_SERIALIZE_METHODS;
53
54     template <typename Stream, typename Operation>
55     inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
56         READWRITE(*(CAddress*)this);
57         READWRITE(source);
58         READWRITE(nLastSuccess);
59         READWRITE(nAttempts);
60     }
61
62     void Init()
63     {
64         nLastSuccess = 0;
65         nLastTry = 0;
66         nAttempts = 0;
67         nRefCount = 0;
68         fInTried = false;
69         nRandomPos = -1;
70     }
71
72     CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource)
73     {
74         Init();
75     }
76
77     CAddrInfo() : CAddress(), source()
78     {
79         Init();
80     }
81
82     //! Calculate in which "tried" bucket this entry belongs
83     int GetTriedBucket(const uint256 &nKey) const;
84
85     //! Calculate in which "new" bucket this entry belongs, given a certain source
86     int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const;
87
88     //! Calculate in which "new" bucket this entry belongs, using its default source
89     int GetNewBucket(const uint256 &nKey) const
90     {
91         return GetNewBucket(nKey, source);
92     }
93
94     //! Calculate in which position of a bucket to store this entry.
95     int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const;
96
97     //! Determine whether the statistics about this entry are bad enough so that it can just be deleted
98     bool IsTerrible(int64_t nNow = GetAdjustedTime()) const;
99
100     //! Calculate the relative chance this entry should be given when selecting nodes to connect to
101     double GetChance(int64_t nNow = GetAdjustedTime()) const;
102
103 };
104
105 /** Stochastic address manager
106  *
107  * Design goals:
108  *  * Keep the address tables in-memory, and asynchronously dump the entire to able in peers.dat.
109  *  * Make sure no (localized) attacker can fill the entire table with his nodes/addresses.
110  *
111  * To that end:
112  *  * Addresses are organized into buckets.
113  *    * Address that have not yet been tried go into 1024 "new" buckets.
114  *      * Based on the address range (/16 for IPv4) of source of the information, 64 buckets are selected at random
115  *      * The actual bucket is chosen from one of these, based on the range the address itself is located.
116  *      * One single address can occur in up to 8 different buckets, to increase selection chances for addresses that
117  *        are seen frequently. The chance for increasing this multiplicity decreases exponentially.
118  *      * When adding a new address to a full bucket, a randomly chosen entry (with a bias favoring less recently seen
119  *        ones) is removed from it first.
120  *    * Addresses of nodes that are known to be accessible go into 256 "tried" buckets.
121  *      * Each address range selects at random 8 of these buckets.
122  *      * The actual bucket is chosen from one of these, based on the full address.
123  *      * When adding a new good address to a full bucket, a randomly chosen entry (with a bias favoring less recently
124  *        tried ones) is evicted from it, back to the "new" buckets.
125  *    * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not
126  *      be observable by adversaries.
127  *    * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN will introduce frequent (and expensive)
128  *      consistency checks for the entire data structure.
129  */
130
131 //! total number of buckets for tried addresses
132 #define ADDRMAN_TRIED_BUCKET_COUNT 256
133
134 //! total number of buckets for new addresses
135 #define ADDRMAN_NEW_BUCKET_COUNT 1024
136
137 //! maximum allowed number of entries in buckets for new and tried addresses
138 #define ADDRMAN_BUCKET_SIZE 64
139
140 //! over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread
141 #define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8
142
143 //! over how many buckets entries with new addresses originating from a single group are spread
144 #define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64
145
146 //! in how many buckets for entries with new addresses a single address may occur
147 #define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8
148
149 //! how old addresses can maximally be
150 #define ADDRMAN_HORIZON_DAYS 30
151
152 //! after how many failed attempts we give up on a new node
153 #define ADDRMAN_RETRIES 3
154
155 //! how many successive failures are allowed ...
156 #define ADDRMAN_MAX_FAILURES 10
157
158 //! ... in at least this many days
159 #define ADDRMAN_MIN_FAIL_DAYS 7
160
161 //! the maximum percentage of nodes to return in a getaddr call
162 #define ADDRMAN_GETADDR_MAX_PCT 23
163
164 //! the maximum number of nodes to return in a getaddr call
165 #define ADDRMAN_GETADDR_MAX 2500
166
167 /** 
168  * Stochastical (IP) address manager 
169  */
170 class CAddrMan
171 {
172 private:
173     //! critical section to protect the inner data structures
174     mutable CCriticalSection cs;
175
176     //! secret key to randomize bucket select with
177     uint256 nKey;
178
179     //! last used nId
180     int nIdCount;
181
182     //! table with information about all nIds
183     std::map<int, CAddrInfo> mapInfo;
184
185     //! find an nId based on its network address
186     std::map<CNetAddr, int> mapAddr;
187
188     //! randomly-ordered vector of all nIds
189     std::vector<int> vRandom;
190
191     // number of "tried" entries
192     int nTried;
193
194     //! list of "tried" buckets
195     int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE];
196
197     //! number of (unique) "new" entries
198     int nNew;
199
200     //! list of "new" buckets
201     int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE];
202
203 protected:
204
205     //! Find an entry.
206     CAddrInfo* Find(const CNetAddr& addr, int *pnId = NULL);
207
208     //! find an entry, creating it if necessary.
209     //! nTime and nServices of the found node are updated, if necessary.
210     CAddrInfo* Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = NULL);
211
212     //! Swap two elements in vRandom.
213     void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2);
214
215     //! Move an entry from the "new" table(s) to the "tried" table
216     void MakeTried(CAddrInfo& info, int nId);
217
218     //! Delete an entry. It must not be in tried, and have refcount 0.
219     void Delete(int nId);
220
221     //! Clear a position in a "new" table. This is the only place where entries are actually deleted.
222     void ClearNew(int nUBucket, int nUBucketPos);
223
224     //! Mark an entry "good", possibly moving it from "new" to "tried".
225     void Good_(const CService &addr, int64_t nTime);
226
227     //! Add an entry to the "new" table.
228     bool Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty);
229
230     //! Mark an entry as attempted to connect.
231     void Attempt_(const CService &addr, int64_t nTime);
232
233     //! Select an address to connect to.
234     //! nUnkBias determines how much to favor new addresses over tried ones (min=0, max=100)
235     CAddrInfo Select_();
236
237 #ifdef DEBUG_ADDRMAN
238     //! Perform consistency check. Returns an error code or zero.
239     int Check_();
240 #endif
241
242     //! Select several addresses at once.
243     void GetAddr_(std::vector<CAddress> &vAddr);
244
245     //! Mark an entry as currently-connected-to.
246     void Connected_(const CService &addr, int64_t nTime);
247
248 public:
249     /**
250      * serialized format:
251      * * version byte (currently 1)
252      * * 0x20 + nKey (serialized as if it were a vector, for backward compatibility)
253      * * nNew
254      * * nTried
255      * * number of "new" buckets XOR 2**30
256      * * all nNew addrinfos in vvNew
257      * * all nTried addrinfos in vvTried
258      * * for each bucket:
259      *   * number of elements
260      *   * for each element: index
261      *
262      * 2**30 is xorred with the number of buckets to make addrman deserializer v0 detect it
263      * as incompatible. This is necessary because it did not check the version number on
264      * deserialization.
265      *
266      * Notice that vvTried, mapAddr and vVector are never encoded explicitly;
267      * they are instead reconstructed from the other information.
268      *
269      * vvNew is serialized, but only used if ADDRMAN_UNKOWN_BUCKET_COUNT didn't change,
270      * otherwise it is reconstructed as well.
271      *
272      * This format is more complex, but significantly smaller (at most 1.5 MiB), and supports
273      * changes to the ADDRMAN_ parameters without breaking the on-disk structure.
274      *
275      * We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has
276      * very little in common.
277      */
278     template<typename Stream>
279     void Serialize(Stream &s, int nType, int nVersionDummy) const
280     {
281         LOCK(cs);
282
283         unsigned char nVersion = 1;
284         s << nVersion;
285         s << ((unsigned char)32);
286         s << nKey;
287         s << nNew;
288         s << nTried;
289
290         int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
291         s << nUBuckets;
292         std::map<int, int> mapUnkIds;
293         int nIds = 0;
294         for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) {
295             mapUnkIds[(*it).first] = nIds;
296             const CAddrInfo &info = (*it).second;
297             if (info.nRefCount) {
298                 assert(nIds != nNew); // this means nNew was wrong, oh ow
299                 s << info;
300                 nIds++;
301             }
302         }
303         nIds = 0;
304         for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) {
305             const CAddrInfo &info = (*it).second;
306             if (info.fInTried) {
307                 assert(nIds != nTried); // this means nTried was wrong, oh ow
308                 s << info;
309                 nIds++;
310             }
311         }
312         for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
313             int nSize = 0;
314             for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
315                 if (vvNew[bucket][i] != -1)
316                     nSize++;
317             }
318             s << nSize;
319             for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
320                 if (vvNew[bucket][i] != -1) {
321                     int nIndex = mapUnkIds[vvNew[bucket][i]];
322                     s << nIndex;
323                 }
324             }
325         }
326     }
327
328     template<typename Stream>
329     void Unserialize(Stream& s, int nType, int nVersionDummy)
330     {
331         LOCK(cs);
332
333         Clear();
334
335         unsigned char nVersion;
336         s >> nVersion;
337         unsigned char nKeySize;
338         s >> nKeySize;
339         if (nKeySize != 32) throw std::ios_base::failure("Incorrect keysize in addrman deserialization");
340         s >> nKey;
341         s >> nNew;
342         s >> nTried;
343         int nUBuckets = 0;
344         s >> nUBuckets;
345         if (nVersion != 0) {
346             nUBuckets ^= (1 << 30);
347         }
348
349         // Deserialize entries from the new table.
350         for (int n = 0; n < nNew; n++) {
351             CAddrInfo &info = mapInfo[n];
352             s >> info;
353             mapAddr[info] = n;
354             info.nRandomPos = vRandom.size();
355             vRandom.push_back(n);
356             if (nVersion != 1 || nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) {
357                 // In case the new table data cannot be used (nVersion unknown, or bucket count wrong),
358                 // immediately try to give them a reference based on their primary source address.
359                 int nUBucket = info.GetNewBucket(nKey);
360                 int nUBucketPos = info.GetBucketPosition(nKey, true, nUBucket);
361                 if (vvNew[nUBucket][nUBucketPos] == -1) {
362                     vvNew[nUBucket][nUBucketPos] = n;
363                     info.nRefCount++;
364                 }
365             }
366         }
367         nIdCount = nNew;
368
369         // Deserialize entries from the tried table.
370         int nLost = 0;
371         for (int n = 0; n < nTried; n++) {
372             CAddrInfo info;
373             s >> info;
374             int nKBucket = info.GetTriedBucket(nKey);
375             int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
376             if (vvTried[nKBucket][nKBucketPos] == -1) {
377                 info.nRandomPos = vRandom.size();
378                 info.fInTried = true;
379                 vRandom.push_back(nIdCount);
380                 mapInfo[nIdCount] = info;
381                 mapAddr[info] = nIdCount;
382                 vvTried[nKBucket][nKBucketPos] = nIdCount;
383                 nIdCount++;
384             } else {
385                 nLost++;
386             }
387         }
388         nTried -= nLost;
389
390         // Deserialize positions in the new table (if possible).
391         for (int bucket = 0; bucket < nUBuckets; bucket++) {
392             int nSize = 0;
393             s >> nSize;
394             for (int n = 0; n < nSize; n++) {
395                 int nIndex = 0;
396                 s >> nIndex;
397                 if (nIndex >= 0 && nIndex < nNew) {
398                     CAddrInfo &info = mapInfo[nIndex];
399                     int nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
400                     if (nVersion == 1 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) {
401                         info.nRefCount++;
402                         vvNew[bucket][nUBucketPos] = nIndex;
403                     }
404                 }
405             }
406         }
407
408         // Prune new entries with refcount 0 (as a result of collisions).
409         int nLostUnk = 0;
410         for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) {
411             if (it->second.fInTried == false && it->second.nRefCount == 0) {
412                 std::map<int, CAddrInfo>::const_iterator itCopy = it++;
413                 Delete(itCopy->first);
414                 nLostUnk++;
415             } else {
416                 it++;
417             }
418         }
419         if (nLost + nLostUnk > 0) {
420             LogPrint("addrman", "addrman lost %i new and %i tried addresses due to collisions\n", nLostUnk, nLost);
421         }
422
423         Check();
424     }
425
426     unsigned int GetSerializeSize(int nType, int nVersion) const
427     {
428         return (CSizeComputer(nType, nVersion) << *this).size();
429     }
430
431     void Clear()
432     {
433         std::vector<int>().swap(vRandom);
434         nKey = GetRandHash();
435         for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
436             for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) {
437                 vvNew[bucket][entry] = -1;
438             }
439         }
440         for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; bucket++) {
441             for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) {
442                 vvTried[bucket][entry] = -1;
443             }
444         }
445
446         nIdCount = 0;
447         nTried = 0;
448         nNew = 0;
449     }
450
451     CAddrMan()
452     {
453         Clear();
454     }
455
456     ~CAddrMan()
457     {
458         nKey.SetNull();
459     }
460
461     //! Return the number of (unique) addresses in all tables.
462     int size()
463     {
464         return vRandom.size();
465     }
466
467     //! Consistency check
468     void Check()
469     {
470 #ifdef DEBUG_ADDRMAN
471         {
472             LOCK(cs);
473             int err;
474             if ((err=Check_()))
475                 LogPrintf("ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err);
476         }
477 #endif
478     }
479
480     //! Add a single address.
481     bool Add(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty = 0)
482     {
483         bool fRet = false;
484         {
485             LOCK(cs);
486             Check();
487             fRet |= Add_(addr, source, nTimePenalty);
488             Check();
489         }
490         if (fRet)
491             LogPrint("addrman", "Added %s from %s: %i tried, %i new\n", addr.ToStringIPPort(), source.ToString(), nTried, nNew);
492         return fRet;
493     }
494
495     //! Add multiple addresses.
496     bool Add(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0)
497     {
498         int nAdd = 0;
499         {
500             LOCK(cs);
501             Check();
502             for (std::vector<CAddress>::const_iterator it = vAddr.begin(); it != vAddr.end(); it++)
503                 nAdd += Add_(*it, source, nTimePenalty) ? 1 : 0;
504             Check();
505         }
506         if (nAdd)
507             LogPrint("addrman", "Added %i addresses from %s: %i tried, %i new\n", nAdd, source.ToString(), nTried, nNew);
508         return nAdd > 0;
509     }
510
511     //! Mark an entry as accessible.
512     void Good(const CService &addr, int64_t nTime = GetAdjustedTime())
513     {
514         {
515             LOCK(cs);
516             Check();
517             Good_(addr, nTime);
518             Check();
519         }
520     }
521
522     //! Mark an entry as connection attempted to.
523     void Attempt(const CService &addr, int64_t nTime = GetAdjustedTime())
524     {
525         {
526             LOCK(cs);
527             Check();
528             Attempt_(addr, nTime);
529             Check();
530         }
531     }
532
533     /**
534      * Choose an address to connect to.
535      * nUnkBias determines how much "new" entries are favored over "tried" ones (0-100).
536      */
537     CAddrInfo Select()
538     {
539         CAddrInfo addrRet;
540         {
541             LOCK(cs);
542             Check();
543             addrRet = Select_();
544             Check();
545         }
546         return addrRet;
547     }
548
549     //! Return a bunch of addresses, selected at random.
550     std::vector<CAddress> GetAddr()
551     {
552         Check();
553         std::vector<CAddress> vAddr;
554         {
555             LOCK(cs);
556             GetAddr_(vAddr);
557         }
558         Check();
559         return vAddr;
560     }
561
562     //! Mark an entry as currently-connected-to.
563     void Connected(const CService &addr, int64_t nTime = GetAdjustedTime())
564     {
565         {
566             LOCK(cs);
567             Check();
568             Connected_(addr, nTime);
569             Check();
570         }
571     }
572 };
573
574 #endif // BITCOIN_ADDRMAN_H
This page took 0.057742 seconds and 4 git commands to generate.