56 const std::vector<bool> &asmap)
const {
65 const std::vector<bool> &asmap)
const {
66 std::vector<uint8_t> vchSourceGroupKey = src.
GetGroup(asmap);
80 (
HashWriter{} << nKey << (fNew ? uint8_t{
'N'} : uint8_t{
'K'}) << nBucket
93 if (
nTime > now + 10min) {
118 double fChance = 1.0;
127 fChance *= std::pow(0.66, std::min(
nAttempts, 8));
133 int32_t consistency_check_ratio)
134 : m_consistency_check_ratio{consistency_check_ratio},
135 m_asmap{
std::move(asmap)} {}
192 static constexpr uint8_t lowest_compatible = Format::V4_MULTIPORT;
201 std::unordered_map<int, int> mapUnkIds;
203 for (
const auto &entry : mapInfo) {
204 mapUnkIds[entry.first] = nIds;
205 const AddrInfo &info = entry.second;
214 for (
const auto &entry : mapInfo) {
215 const AddrInfo &info = entry.second;
226 if (vvNew[bucket][i] != -1) {
232 if (vvNew[bucket][i] != -1) {
233 int nIndex = mapUnkIds[vvNew[bucket][i]];
253 s_ >> Using<CustomUintFormatter<1>>(
format);
255 int stream_version = s_.GetVersion();
256 if (
format >= Format::V3_BIP155) {
268 throw std::ios_base::failure(
269 strprintf(
"Corrupted addrman database: The compat value (%u) "
270 "is lower than the expected minimum value %u.",
276 "Unsupported format of addrman database: %u. It is compatible with "
277 "formats >=%u, but the maximum supported by this version of %s is "
279 uint8_t{
format}, lowest_compatible, PACKAGE_NAME,
288 if (
format >= Format::V1_DETERMINISTIC) {
289 nUBuckets ^= (1 << 30);
294 "Corrupt AddrMan serialization: nNew=%d, should be in [0, %d]",
301 "Corrupt AddrMan serialization: nTried=%d, should be in [0, "
307 for (
int n = 0; n < nNew; n++) {
312 vRandom.push_back(n);
318 for (
int n = 0; n < nTried; n++) {
323 if (vvTried[nKBucket][nKBucketPos] == -1) {
326 vRandom.push_back(nIdCount);
327 mapInfo[nIdCount] = info;
328 mapAddr[info] = nIdCount;
329 vvTried[nKBucket][nKBucketPos] = nIdCount;
341 std::vector<std::pair<int, int>> bucket_entries;
343 for (
int bucket = 0; bucket < nUBuckets; ++bucket) {
346 for (
int n = 0; n < num_entries; ++n) {
349 if (entry_index >= 0 && entry_index < nNew) {
350 bucket_entries.emplace_back(bucket, entry_index);
358 uint256 supplied_asmap_checksum;
362 uint256 serialized_asmap_checksum;
363 if (
format >= Format::V2_ASMAP) {
364 s >> serialized_asmap_checksum;
367 serialized_asmap_checksum ==
368 supplied_asmap_checksum};
370 if (!restore_bucketing) {
372 "Bucketing method was updated, re-bucketing addrman "
373 "entries from disk\n");
376 for (
auto bucket_entry : bucket_entries) {
377 int bucket{bucket_entry.first};
378 const int entry_index{bucket_entry.second};
379 AddrInfo &info = mapInfo[entry_index];
389 if (restore_bucketing && vvNew[bucket][bucket_position] == -1) {
392 vvNew[bucket][bucket_position] = entry_index;
400 if (vvNew[bucket][bucket_position] == -1) {
401 vvNew[bucket][bucket_position] = entry_index;
409 for (
auto it = mapInfo.cbegin(); it != mapInfo.cend();) {
410 if (it->second.fInTried ==
false && it->second.nRefCount == 0) {
411 const auto itCopy = it++;
418 if (nLost + nLostUnk > 0) {
420 "addrman lost %i new and %i tried addresses due to "
426 if (check_code != 0) {
428 "Corrupt data. Consistency check failed with code %s", check_code));
435 const auto it = mapAddr.find(addr);
436 if (it == mapAddr.end()) {
440 *pnId = (*it).second;
442 const auto it2 = mapInfo.find((*it).second);
443 if (it2 != mapInfo.end()) {
444 return &(*it2).second;
453 int nId = nIdCount++;
454 mapInfo[nId] =
AddrInfo(addr, addrSource);
456 mapInfo[nId].nRandomPos = vRandom.size();
457 vRandom.push_back(nId);
461 return &mapInfo[nId];
465 unsigned int nRndPos2)
const {
468 if (nRndPos1 == nRndPos2) {
472 assert(nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size());
474 int nId1 = vRandom[nRndPos1];
475 int nId2 = vRandom[nRndPos2];
477 const auto it_1{mapInfo.find(nId1)};
478 const auto it_2{mapInfo.find(nId2)};
479 assert(it_1 != mapInfo.end());
480 assert(it_2 != mapInfo.end());
482 it_1->second.nRandomPos = nRndPos2;
483 it_2->second.nRandomPos = nRndPos1;
485 vRandom[nRndPos1] = nId2;
486 vRandom[nRndPos2] = nId1;
492 assert(mapInfo.count(nId) != 0);
508 if (vvNew[nUBucket][nUBucketPos] != -1) {
509 int nIdDelete = vvNew[nUBucket][nUBucketPos];
510 AddrInfo &infoDelete = mapInfo[nIdDelete];
513 vvNew[nUBucket][nUBucketPos] = -1;
515 infoDelete.
ToString(), nUBucket, nUBucketPos);
530 if (vvNew[bucket][pos] == nId) {
531 vvNew[bucket][pos] = -1;
548 if (vvTried[nKBucket][nKBucketPos] != -1) {
550 int nIdEvict = vvTried[nKBucket][nKBucketPos];
551 assert(mapInfo.count(nIdEvict) == 1);
552 AddrInfo &infoOld = mapInfo[nIdEvict];
556 vvTried[nKBucket][nKBucketPos] = -1;
563 assert(vvNew[nUBucket][nUBucketPos] == -1);
567 vvNew[nUBucket][nUBucketPos] = nIdEvict;
570 "Moved %s from tried[%i][%i] to new[%i][%i] to make space\n",
571 infoOld.
ToString(), nKBucket, nKBucketPos, nUBucket,
574 assert(vvTried[nKBucket][nKBucketPos] == -1);
576 vvTried[nKBucket][nKBucketPos] = nId;
582 std::chrono::seconds time_penalty) {
600 const auto update_interval{currently_online ? 1h : 24h};
601 if (pinfo->
nTime < addr.
nTime - update_interval - time_penalty) {
626 for (
int n = 0; n < pinfo->
nRefCount; n++) {
641 bool fInsert = vvNew[nUBucket][nUBucketPos] == -1;
642 if (vvNew[nUBucket][nUBucketPos] != nId) {
644 AddrInfo &infoExisting = mapInfo[vvNew[nUBucket][nUBucketPos]];
654 vvNew[nUBucket][nUBucketPos] = nId;
704 if (test_before_evict && (vvTried[tried_bucket][tried_bucket_pos] != -1)) {
709 auto colliding_entry =
710 mapInfo.find(vvTried[tried_bucket][tried_bucket_pos]);
712 "Collision with %s while attempting to move %s to tried "
713 "table. Collisions=%d\n",
714 colliding_entry != mapInfo.end()
715 ? colliding_entry->second.ToString()
729 std::chrono::seconds time_penalty) {
731 for (std::vector<CAddress>::const_iterator it = vAddr.begin();
732 it != vAddr.end(); it++) {
737 "Added %i addresses (of %i) from %s: %i tried, %i new\n",
738 added, vAddr.size(),
source.ToString(), nTried, nNew);
767 if (vRandom.empty()) {
771 if (newOnly && nNew == 0) {
779 double fChanceFactor = 1.0;
788 if (vvTried[nKBucket]
801 const auto it_found{mapInfo.find(nId)};
802 assert(it_found != mapInfo.end());
803 const AddrInfo &info{it_found->second};
806 fChanceFactor * info.GetChance() * (1 << 30)) {
809 return {info, info.m_last_try};
813 fChanceFactor *= 1.2;
817 double fChanceFactor = 1.0;
838 const auto it_found{mapInfo.find(nId)};
839 assert(it_found != mapInfo.end());
840 const AddrInfo &info{it_found->second};
843 fChanceFactor * info.GetChance() * (1 << 30)) {
846 return {info, info.m_last_try};
850 fChanceFactor *= 1.2;
857 std::optional<Network> network)
const {
860 size_t nNodes = vRandom.size();
862 nNodes = max_pct * nNodes / 100;
864 if (max_addresses != 0) {
865 nNodes = std::min(nNodes, max_addresses);
869 const auto now{Now<NodeSeconds>()};
870 std::vector<CAddress> addresses;
871 for (
unsigned int n = 0; n < vRandom.size(); n++) {
872 if (addresses.size() >= nNodes) {
878 const auto it{mapInfo.find(vRandom[n])};
879 assert(it != mapInfo.end());
884 if (network != std::nullopt && ai.GetNetClass() != network) {
889 if (ai.IsTerrible(now)) {
893 addresses.push_back(ai);
913 const auto update_interval{20min};
914 if (time - info.
nTime > update_interval) {
938 const auto current_time{Now<NodeSeconds>()};
944 bool erase_collision =
false;
948 auto id_new_it = mapInfo.find(id_new);
949 if (id_new_it == mapInfo.end()) {
950 erase_collision =
true;
952 AddrInfo &info_new = mapInfo[id_new];
956 int tried_bucket_pos =
960 erase_collision =
true;
961 }
else if (vvTried[tried_bucket][tried_bucket_pos] != -1) {
965 int id_old = vvTried[tried_bucket][tried_bucket_pos];
966 AddrInfo &info_old = mapInfo[id_old];
971 erase_collision =
true;
972 }
else if (current_time - info_old.
m_last_try <
978 if (current_time - info_old.
m_last_try > 60s) {
980 "Replacing %s with %s in tried table\n",
985 Good_(info_new,
false, current_time);
986 erase_collision =
true;
995 "Unable to test; replacing %s with %s in tried "
998 Good_(info_new,
false, current_time);
999 erase_collision =
true;
1003 Good_(info_new,
false, current_time);
1004 erase_collision =
true;
1008 if (erase_collision) {
1030 auto id_new_it = mapInfo.find(id_new);
1031 if (id_new_it == mapInfo.end()) {
1036 const AddrInfo &newInfo = id_new_it->second;
1042 const AddrInfo &info_old = mapInfo[vvTried[tried_bucket][tried_bucket_pos]];
1059 LogPrintf(
"ADDRMAN CONSISTENCY CHECK FAILED!!! err=%i\n", err);
1068 strprintf(
"new %i, tried %i, total %u", nNew, nTried, vRandom.size()),
1071 std::unordered_set<int> setTried;
1072 std::unordered_map<int, int> mapNew;
1074 if (vRandom.size() !=
size_t(nTried + nNew)) {
1078 for (
const auto &entry : mapInfo) {
1079 int n = entry.first;
1080 const AddrInfo &info = entry.second;
1082 if (!TicksSinceEpoch<std::chrono::seconds>(info.
m_last_success)) {
1099 const auto it{mapAddr.find(info)};
1100 if (it == mapAddr.end() || it->second != n) {
1115 if (setTried.size() !=
size_t(nTried)) {
1118 if (mapNew.size() !=
size_t(nNew)) {
1124 if (vvTried[n][i] != -1) {
1125 if (!setTried.count(vvTried[n][i])) {
1128 const auto it{mapInfo.find(vvTried[n][i])};
1129 if (it == mapInfo.end() ||
1133 if (it->second.GetBucketPosition(
nKey,
false, n) != i) {
1136 setTried.erase(vvTried[n][i]);
1143 if (vvNew[n][i] != -1) {
1144 if (!mapNew.count(vvNew[n][i])) {
1147 const auto it{mapInfo.find(vvNew[n][i])};
1148 if (it == mapInfo.end() ||
1149 it->second.GetBucketPosition(
nKey,
true, n) != i) {
1152 if (--mapNew[vvNew[n][i]] == 0) {
1153 mapNew.erase(vvNew[n][i]);
1159 if (setTried.size()) {
1162 if (mapNew.size()) {
1175 return vRandom.size();
1180 std::chrono::seconds time_penalty) {
1183 auto ret =
Add_(vAddr,
source, time_penalty);
1192 Good_(addr, test_before_evict, time);
1200 Attempt_(addr, fCountFailure, time);
1222 const auto addrRet =
Select_(newOnly);
1227std::vector<CAddress>
1229 std::optional<Network> network)
const {
1232 const auto addresses =
GetAddr_(max_addresses, max_pct, network);
1257 std::vector<int>().swap(vRandom);
1267 vvNew[bucket][entry] = -1;
1272 vvTried[bucket][entry] = -1;
1292 consistency_check_ratio)) {
1299 m_impl->Serialize<Stream>(s_);
1303 m_impl->Unserialize<Stream>(s_);
1320 std::chrono::seconds time_penalty) {
1326 m_impl->Good(addr, test_before_evict, time);
1331 m_impl->Attempt(addr, fCountFailure, time);
1335 m_impl->ResolveCollisions();
1339 return m_impl->SelectTriedCollision();
1343 return m_impl->Select(newOnly);
1347 std::optional<Network> network)
const {
1348 return m_impl->GetAddr(max_addresses, max_pct, network);
1352 m_impl->Connected(addr, time);
1356 m_impl->SetServices(addr, nServices);
1360 return m_impl->GetAsmap();
1368 return m_impl->MakeDeterministic();
static constexpr uint32_t ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP
Over how many buckets entries with new addresses originating from a single group are spread.
static constexpr auto ADDRMAN_HORIZON
How old addresses can maximally be.
static constexpr int32_t ADDRMAN_MAX_FAILURES
How many successive failures are allowed ...
static constexpr auto ADDRMAN_MIN_FAIL
... in at least this duration
static constexpr auto ADDRMAN_TEST_WINDOW
The maximum time we'll spend trying to resolve a tried table collision.
static constexpr auto ADDRMAN_REPLACEMENT
How recent a successful connection should be before we allow an address to be evicted from tried.
static constexpr int32_t ADDRMAN_RETRIES
After how many failed attempts we give up on a new node.
static constexpr size_t ADDRMAN_SET_TRIED_COLLISION_SIZE
The maximum number of tried addr collisions to store.
static constexpr uint32_t ADDRMAN_TRIED_BUCKETS_PER_GROUP
Over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread.
static constexpr int32_t ADDRMAN_NEW_BUCKETS_PER_ADDRESS
Maximum number of times an address can occur in the new table.
static constexpr int ADDRMAN_TRIED_BUCKET_COUNT
static constexpr int ADDRMAN_BUCKET_SIZE
static constexpr int ADDRMAN_NEW_BUCKET_COUNT
#define Assume(val)
Assume is the identity function.
Extended statistics about a CAddress.
int GetTriedBucket(const uint256 &nKey, const std::vector< bool > &asmap) const
Calculate in which "tried" bucket this entry belongs.
int nRandomPos
position in vRandom
bool fInTried
in tried set? (memory only)
NodeSeconds m_last_success
last successful connection by us
int GetNewBucket(const uint256 &nKey, const CNetAddr &src, const std::vector< bool > &asmap) const
Calculate in which "new" bucket this entry belongs, given a certain source.
NodeSeconds m_last_count_attempt
last counted attempt (memory only)
NodeSeconds m_last_try
last try whatsoever by us (memory only)
double GetChance(NodeSeconds now=Now< NodeSeconds >()) const
Calculate the relative chance this entry should be given when selecting nodes to connect to.
bool IsTerrible(NodeSeconds now=Now< NodeSeconds >()) const
Determine whether the statistics about this entry are bad enough so that it can just be deleted.
int nRefCount
reference count in new sets (memory only)
int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const
Calculate in which position of a bucket to store this entry.
int nAttempts
connection attempts since last successful attempt
void Connected(const CService &addr, NodeSeconds time=Now< NodeSeconds >())
We have successfully connected to this peer.
void MakeDeterministic()
Ensure that bucket placement is always the same for testing purposes.
const std::unique_ptr< AddrManImpl > m_impl
std::vector< CAddress > GetAddr(size_t max_addresses, size_t max_pct, std::optional< Network > network) const
Return all or many randomly selected addresses, optionally by network.
const std::vector< bool > & GetAsmap() const
void Attempt(const CService &addr, bool fCountFailure, NodeSeconds time=Now< NodeSeconds >())
Mark an entry as connection attempted to.
std::pair< CAddress, NodeSeconds > Select(bool newOnly=false) const
Choose an address to connect to.
void ResolveCollisions()
See if any to-be-evicted tried table entries have been tested and if so resolve the collisions.
AddrMan(std::vector< bool > asmap, int32_t consistency_check_ratio)
void Serialize(Stream &s_) const
size_t size() const
Return the number of (unique) addresses in all tables.
void Unserialize(Stream &s_)
void Good(const CService &addr, bool test_before_evict=true, NodeSeconds time=Now< NodeSeconds >())
Mark an entry as accessible, possibly moving it from "new" to "tried".
std::pair< CAddress, NodeSeconds > SelectTriedCollision()
Randomly select an address in the tried table that another address is attempting to evict.
bool Add(const std::vector< CAddress > &vAddr, const CNetAddr &source, std::chrono::seconds time_penalty=0s)
Attempt to add one or more addresses to addrman's new table.
void SetServices(const CService &addr, ServiceFlags nServices)
Update an entry's service bits.
void ClearNew(int nUBucket, int nUBucketPos) EXCLUSIVE_LOCKS_REQUIRED(cs)
Clear a position in a "new" table.
std::pair< CAddress, NodeSeconds > Select(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(!cs)
void Connected_(const CService &addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs)
void Attempt_(const CService &addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs)
static constexpr Format FILE_FORMAT
The maximum format this software knows it can unserialize.
void ResolveCollisions_() EXCLUSIVE_LOCKS_REQUIRED(cs)
Format
Serialization versions.
void Serialize(Stream &s_) const EXCLUSIVE_LOCKS_REQUIRED(!cs)
void Connected(const CService &addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(!cs)
std::vector< CAddress > GetAddr(size_t max_addresses, size_t max_pct, std::optional< Network > network) const EXCLUSIVE_LOCKS_REQUIRED(!cs)
size_t size() const EXCLUSIVE_LOCKS_REQUIRED(!cs)
void MakeTried(AddrInfo &info, int nId) EXCLUSIVE_LOCKS_REQUIRED(cs)
Move an entry from the "new" table(s) to the "tried" table.
void SetServices(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(!cs)
AddrManImpl(std::vector< bool > &&asmap, int32_t consistency_check_ratio)
void SetServices_(const CService &addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs)
const int32_t m_consistency_check_ratio
Perform consistency checks every m_consistency_check_ratio operations (if non-zero).
const std::vector< bool > & GetAsmap() const
void MakeDeterministic() EXCLUSIVE_LOCKS_REQUIRED(!cs)
void Check() const EXCLUSIVE_LOCKS_REQUIRED(cs)
Consistency check, taking into account m_consistency_check_ratio.
int CheckAddrman() const EXCLUSIVE_LOCKS_REQUIRED(cs)
Perform consistency check, regardless of m_consistency_check_ratio.
bool Add(const std::vector< CAddress > &vAddr, const CNetAddr &source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(!cs)
AddrInfo * Find(const CService &addr, int *pnId=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs)
Find an entry.
Mutex cs
A mutex to protect the inner data structures.
std::pair< CAddress, NodeSeconds > SelectTriedCollision_() EXCLUSIVE_LOCKS_REQUIRED(cs)
std::pair< CAddress, NodeSeconds > Select_(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(cs)
std::pair< CAddress, NodeSeconds > SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs)
bool deterministic
Use deterministic bucket selection and inner loops randomization.
void Good_(const CService &addr, bool test_before_evict, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs)
static constexpr uint8_t INCOMPATIBILITY_BASE
The initial value of a field that is incremented every time an incompatible format change is made (su...
void Delete(int nId) EXCLUSIVE_LOCKS_REQUIRED(cs)
Delete an entry. It must not be in tried, and have refcount 0.
void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2) const EXCLUSIVE_LOCKS_REQUIRED(cs)
Swap two elements in vRandom.
void Attempt(const CService &addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(!cs)
FastRandomContext insecure_rand
Source of random numbers for randomization in inner loops.
void Good(const CService &addr, bool test_before_evict, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(!cs)
void Clear() EXCLUSIVE_LOCKS_REQUIRED(!cs)
void Unserialize(Stream &s_) EXCLUSIVE_LOCKS_REQUIRED(!cs)
std::set< int > m_tried_collisions
Holds addrs inserted into tried table that collide with existing entries.
const std::vector< bool > m_asmap
uint256 nKey
secret key to randomize bucket select with
void ResolveCollisions() EXCLUSIVE_LOCKS_REQUIRED(!cs)
AddrInfo * Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs)
find an entry, creating it if necessary.
std::vector< CAddress > GetAddr_(size_t max_addresses, size_t max_pct, std::optional< Network > network) const EXCLUSIVE_LOCKS_REQUIRED(cs)
bool Add_(const std::vector< CAddress > &vAddr, const CNetAddr &source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs)
bool AddSingle(const CAddress &addr, const CNetAddr &source, std::chrono::seconds time_penalty) EXCLUSIVE_LOCKS_REQUIRED(cs)
Attempt to add a single address to addrman's new table.
A CService with information about it as peer.
ServiceFlags nServices
Serialized as uint64_t in V1, and as CompactSize in V2.
NodeSeconds nTime
Always included in serialization, except in the network format on INIT_PROTO_VERSION.
Double ended buffer combining vector and stream-like interfaces.
Reads data from an underlying stream, while hashing the read data.
std::vector< uint8_t > GetGroup(const std::vector< bool > &asmap) const
Get the canonical identifier of our network group.
uint32_t GetMappedAS(const std::vector< bool > &asmap) const
A combination of a network address (CNetAddr) and a (TCP) port.
std::string ToString() const
std::vector< uint8_t > GetKey() const
uint256 rand256() noexcept
generate a random uint256.
bool randbool() noexcept
Generate a random boolean.
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
uint64_t randrange(uint64_t range) noexcept
Generate a random integer in the range [0..range).
A writer stream (for serialization) that computes a 256-bit hash.
uint256 SerializeHash(const T &obj, int nType=SER_GETHASH, int nVersion=PROTOCOL_VERSION)
Compute the 256-bit hash of an object's serialization.
#define LogPrint(category,...)
Implement std::hash so RCUPtr can be used as a key for maps or sets.
static constexpr int ADDRV2_FORMAT
A flag that is ORed into the protocol version to designate that addresses should be serialized in (un...
ServiceFlags
nServices flags.
static time_point now() noexcept
Return current system time or mocked time, if set.
std::chrono::time_point< NodeClock, std::chrono::seconds > NodeSeconds
#define LOG_TIME_MILLIS_WITH_CATEGORY_MSG_ONCE(end_msg, log_category)