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