b4a3da2642
These are technically static libs, but not in any way shared libs. They are used solely only by this repo and really only by the hub. Most important, no header files are installed and basically none of the normal rules for reusable libraries are applied to these files.
474 lines
16 KiB
C++
474 lines
16 KiB
C++
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (c) 2012 Pieter Wuille
|
|
* Copyright (c) 2012-2015 The Bitcoin Core developers
|
|
* Copyright (c) 2017 Tom Zander <tom@flowee.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef FLOWEE_ADDRMAN_H
|
|
#define FLOWEE_ADDRMAN_H
|
|
|
|
#include "netbase.h"
|
|
#include "protocol.h"
|
|
#include "random.h"
|
|
#include "sync.h"
|
|
#include "timedata.h"
|
|
|
|
#include <set>
|
|
#include <mutex>
|
|
|
|
/**
|
|
* Extended statistics about a CAddress
|
|
*/
|
|
class CAddrInfo : public CAddress
|
|
{
|
|
|
|
|
|
public:
|
|
//! last try whatsoever by us (memory only)
|
|
int64_t nLastTry;
|
|
|
|
private:
|
|
//! where knowledge about this address first came from
|
|
CNetAddr source;
|
|
|
|
//! last successful connection by us
|
|
int64_t nLastSuccess;
|
|
|
|
//! connection attempts since last successful attempt
|
|
int nAttempts;
|
|
|
|
//! reference count in new sets (memory only)
|
|
int nRefCount;
|
|
|
|
//! position in vRandom
|
|
int nRandomPos;
|
|
|
|
//! in tried set? (memory only)
|
|
bool fInTried;
|
|
//! remote node knew xthin last time we connected
|
|
bool fKnowsXThin;
|
|
|
|
int uselessness; //< Higher scrores means we should try to avoid connecting to it. Goes up for banned nodes.
|
|
|
|
friend class CAddrMan;
|
|
|
|
public:
|
|
|
|
ADD_SERIALIZE_METHODS
|
|
|
|
template <typename Stream, typename Operation>
|
|
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
|
READWRITE(*(CAddress*)this);
|
|
READWRITE(source);
|
|
READWRITE(nLastSuccess);
|
|
READWRITE(nAttempts);
|
|
if (nVersion >= 3) {
|
|
READWRITE(fKnowsXThin);
|
|
READWRITE(uselessness);
|
|
}
|
|
}
|
|
|
|
void Init();
|
|
|
|
std::string toString() const {
|
|
return source.ToString();
|
|
}
|
|
|
|
CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
CAddrInfo() : CAddress(), source()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
//! Calculate in which "tried" bucket this entry belongs
|
|
int GetTriedBucket(const uint256 &nKey) const;
|
|
|
|
//! Calculate in which "new" bucket this entry belongs, given a certain source
|
|
int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const;
|
|
|
|
//! Calculate in which "new" bucket this entry belongs, using its default source
|
|
int GetNewBucket(const uint256 &nKey) const
|
|
{
|
|
return GetNewBucket(nKey, source);
|
|
}
|
|
|
|
//! Calculate in which position of a bucket to store this entry.
|
|
int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const;
|
|
|
|
//! Determine whether the statistics about this entry are bad enough so that it can just be deleted
|
|
bool IsTerrible(int64_t nNow = GetAdjustedTime()) const;
|
|
|
|
//! Calculate the relative chance this entry should be given when selecting nodes to connect to
|
|
double GetChance(int64_t nNow = GetAdjustedTime()) const;
|
|
|
|
bool getKnowsXThin() const;
|
|
void setKnowsXThin(bool value);
|
|
int getUselessness() const;
|
|
void setUselessness(int value);
|
|
|
|
int64_t getLastSuccess() const;
|
|
};
|
|
|
|
/** Stochastic address manager
|
|
*
|
|
* Design goals:
|
|
* * Keep the address tables in-memory, and asynchronously dump the entire table to peers.dat.
|
|
* * Make sure no (localized) attacker can fill the entire table with his nodes/addresses.
|
|
*
|
|
* To that end:
|
|
* * Addresses are organized into buckets.
|
|
* * Addresses that have not yet been tried go into 1024 "new" buckets.
|
|
* * Based on the address range (/16 for IPv4) of the source of information, 64 buckets are selected at random.
|
|
* * The actual bucket is chosen from one of these, based on the range in which the address itself is located.
|
|
* * One single address can occur in up to 8 different buckets to increase selection chances for addresses that
|
|
* are seen frequently. The chance for increasing this multiplicity decreases exponentially.
|
|
* * When adding a new address to a full bucket, a randomly chosen entry (with a bias favoring less recently seen
|
|
* ones) is removed from it first.
|
|
* * Addresses of nodes that are known to be accessible go into 256 "tried" buckets.
|
|
* * Each address range selects at random 8 of these buckets.
|
|
* * The actual bucket is chosen from one of these, based on the full address.
|
|
* * When adding a new good address to a full bucket, a randomly chosen entry (with a bias favoring less recently
|
|
* tried ones) is evicted from it, back to the "new" buckets.
|
|
* * Bucket selection is based on cryptographic hashing, using a randomly-generated 256-bit key, which should not
|
|
* be observable by adversaries.
|
|
* * Several indexes are kept for high performance. Defining DEBUG_ADDRMAN will introduce frequent (and expensive)
|
|
* consistency checks for the entire data structure.
|
|
*/
|
|
|
|
//! total number of buckets for tried addresses
|
|
#define ADDRMAN_TRIED_BUCKET_COUNT 256
|
|
|
|
//! total number of buckets for new addresses
|
|
#define ADDRMAN_NEW_BUCKET_COUNT 1024
|
|
|
|
//! maximum allowed number of entries in buckets for new and tried addresses
|
|
#define ADDRMAN_BUCKET_SIZE 64
|
|
|
|
//! over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread
|
|
#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8
|
|
|
|
//! over how many buckets entries with new addresses originating from a single group are spread
|
|
#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64
|
|
|
|
//! in how many buckets for entries with new addresses a single address may occur
|
|
#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8
|
|
|
|
//! how old addresses can maximally be
|
|
#define ADDRMAN_HORIZON_DAYS 30
|
|
|
|
//! after how many failed attempts we give up on a new node
|
|
#define ADDRMAN_RETRIES 3
|
|
|
|
//! how many successive failures are allowed ...
|
|
#define ADDRMAN_MAX_FAILURES 10
|
|
|
|
//! ... in at least this many days
|
|
#define ADDRMAN_MIN_FAIL_DAYS 7
|
|
|
|
//! the maximum percentage of nodes to return in a getaddr call
|
|
#define ADDRMAN_GETADDR_MAX_PCT 23
|
|
|
|
//! the maximum number of nodes to return in a getaddr call
|
|
#define ADDRMAN_GETADDR_MAX 2500
|
|
|
|
/**
|
|
* Stochastical (IP) address manager
|
|
*/
|
|
class CAddrMan
|
|
{
|
|
private:
|
|
mutable std::mutex lock;
|
|
|
|
//! last used nId
|
|
int nIdCount;
|
|
|
|
//! table with information about all nIds
|
|
std::map<int, CAddrInfo> mapInfo;
|
|
|
|
//! find an nId based on its network address
|
|
std::map<CNetAddr, int> mapAddr;
|
|
|
|
//! randomly-ordered vector of all nIds
|
|
std::vector<int> vRandom;
|
|
|
|
// number of "tried" entries
|
|
int nTried;
|
|
|
|
//! list of "tried" buckets
|
|
int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE];
|
|
|
|
//! number of (unique) "new" entries
|
|
int nNew;
|
|
|
|
//! list of "new" buckets
|
|
int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE];
|
|
|
|
protected:
|
|
|
|
//! secret key to randomize bucket select with
|
|
uint256 nKey;
|
|
|
|
//! Find an entry.
|
|
CAddrInfo* Find_(const CNetAddr& addr, int *pnId = NULL);
|
|
|
|
//! find an entry, creating it if necessary.
|
|
//! nTime and nServices of the found node are updated, if necessary.
|
|
CAddrInfo* Create(const CAddress &addr, const CNetAddr &addrSource, int *pnId = NULL);
|
|
|
|
//! Swap two elements in vRandom.
|
|
void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2);
|
|
|
|
//! Move an entry from the "new" table(s) to the "tried" table
|
|
void MarkTried(CAddrInfo& info, int nId);
|
|
|
|
//! Delete an entry. It must not be in tried, and have refcount 0.
|
|
void Delete(int nId);
|
|
|
|
//! Clear a position in a "new" table. This is the only place where entries are actually deleted.
|
|
void ClearNew(int nUBucket, int nUBucketPos);
|
|
|
|
//! Add an entry to the "new" table.
|
|
bool Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty);
|
|
|
|
public:
|
|
/**
|
|
* serialized format:
|
|
* * version byte (currently 1)
|
|
* * 0x20 + nKey (serialized as if it were a vector, for backward compatibility)
|
|
* * nNew
|
|
* * nTried
|
|
* * number of "new" buckets XOR 2**30
|
|
* * all nNew addrinfos in vvNew
|
|
* * all nTried addrinfos in vvTried
|
|
* * for each bucket:
|
|
* * number of elements
|
|
* * for each element: index
|
|
*
|
|
* 2**30 is xorred with the number of buckets to make addrman deserializer v0 detect it
|
|
* as incompatible. This is necessary because it did not check the version number on
|
|
* deserialization.
|
|
*
|
|
* Notice that vvTried, mapAddr and vVector are never encoded explicitly;
|
|
* they are instead reconstructed from the other information.
|
|
*
|
|
* vvNew is serialized, but only used if ADDRMAN_UNKNOWN_BUCKET_COUNT didn't change,
|
|
* otherwise it is reconstructed as well.
|
|
*
|
|
* This format is more complex, but significantly smaller (at most 1.5 MiB), and supports
|
|
* changes to the ADDRMAN_ parameters without breaking the on-disk structure.
|
|
*
|
|
* We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has
|
|
* very little in common.
|
|
*/
|
|
template<typename Stream>
|
|
void Serialize(Stream &s, int nType, int nVersionDummy) const
|
|
{
|
|
std::lock_guard<std::mutex> guard(lock);
|
|
|
|
unsigned char nVersion = 3;
|
|
s << nVersion;
|
|
s << ((unsigned char)32);
|
|
s << nKey;
|
|
s << nNew;
|
|
s << nTried;
|
|
|
|
int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
|
|
s << nUBuckets;
|
|
std::map<int, int> mapUnkIds;
|
|
int nIds = 0;
|
|
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ++it) {
|
|
mapUnkIds[(*it).first] = nIds;
|
|
const CAddrInfo &info = (*it).second;
|
|
if (info.nRefCount) {
|
|
assert(nIds != nNew); // this means nNew was wrong, oh ow
|
|
s << info;
|
|
nIds++;
|
|
}
|
|
}
|
|
nIds = 0;
|
|
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ++it) {
|
|
const CAddrInfo &info = (*it).second;
|
|
if (info.fInTried) {
|
|
assert(nIds != nTried); // this means nTried was wrong, oh ow
|
|
s << info;
|
|
nIds++;
|
|
}
|
|
}
|
|
for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
|
|
int nSize = 0;
|
|
for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
|
|
if (vvNew[bucket][i] != -1)
|
|
nSize++;
|
|
}
|
|
s << nSize;
|
|
for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
|
|
if (vvNew[bucket][i] != -1) {
|
|
int nIndex = mapUnkIds[vvNew[bucket][i]];
|
|
s << nIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename Stream>
|
|
void Unserialize(Stream& s, int nType, int nVersionDummy)
|
|
{
|
|
std::lock_guard<std::mutex> guard(lock);
|
|
|
|
Clear();
|
|
|
|
unsigned char nVersion;
|
|
s >> nVersion;
|
|
unsigned char nKeySize;
|
|
s >> nKeySize;
|
|
if (nKeySize != 32) throw std::ios_base::failure("Incorrect keysize in addrman deserialization");
|
|
s >> nKey;
|
|
s >> nNew;
|
|
s >> nTried;
|
|
int nUBuckets = 0;
|
|
s >> nUBuckets;
|
|
nUBuckets ^= (1 << 30);
|
|
|
|
// Deserialize entries from the new table.
|
|
for (int n = 0; n < nNew; n++) {
|
|
CAddrInfo &info = mapInfo[n];
|
|
info.Unserialize(s, nType, nVersion);
|
|
mapAddr[info] = n;
|
|
info.nRandomPos = static_cast<int>(vRandom.size());
|
|
vRandom.push_back(n);
|
|
if (nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) {
|
|
// In case the new table data cannot be used (bucket count wrong),
|
|
// immediately try to give them a reference based on their primary source address.
|
|
int nUBucket = info.GetNewBucket(nKey);
|
|
int nUBucketPos = info.GetBucketPosition(nKey, true, nUBucket);
|
|
if (vvNew[nUBucket][nUBucketPos] == -1) {
|
|
vvNew[nUBucket][nUBucketPos] = n;
|
|
info.nRefCount++;
|
|
}
|
|
}
|
|
}
|
|
nIdCount = nNew;
|
|
|
|
// Deserialize entries from the tried table.
|
|
int nLost = 0;
|
|
for (int n = 0; n < nTried; n++) {
|
|
CAddrInfo info;
|
|
info.Unserialize(s, nType, nVersion);
|
|
int nKBucket = info.GetTriedBucket(nKey);
|
|
int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
|
|
if (vvTried[nKBucket][nKBucketPos] == -1) {
|
|
info.nRandomPos = static_cast<int>(vRandom.size());
|
|
info.fInTried = true;
|
|
vRandom.push_back(nIdCount);
|
|
mapInfo[nIdCount] = info;
|
|
mapAddr[info] = nIdCount;
|
|
vvTried[nKBucket][nKBucketPos] = nIdCount;
|
|
nIdCount++;
|
|
} else {
|
|
nLost++;
|
|
}
|
|
}
|
|
nTried -= nLost;
|
|
|
|
// Deserialize positions in the new table (if possible).
|
|
for (int bucket = 0; bucket < nUBuckets; bucket++) {
|
|
int nSize = 0;
|
|
s >> nSize;
|
|
for (int n = 0; n < nSize; n++) {
|
|
int nIndex = 0;
|
|
s >> nIndex;
|
|
if (nIndex >= 0 && nIndex < nNew) {
|
|
CAddrInfo &info = mapInfo[nIndex];
|
|
int nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
|
|
if (nVersion >= 1 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) {
|
|
info.nRefCount++;
|
|
vvNew[bucket][nUBucketPos] = nIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prune new entries with refcount 0 (as a result of collisions).
|
|
int nLostUnk = 0;
|
|
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) {
|
|
if (it->second.fInTried == false && it->second.nRefCount == 0) {
|
|
std::map<int, CAddrInfo>::const_iterator itCopy = it++;
|
|
Delete(itCopy->first);
|
|
nLostUnk++;
|
|
} else {
|
|
it++;
|
|
}
|
|
}
|
|
if (nLost + nLostUnk > 0)
|
|
logDebug(Log::Addrman) << "addrman lost" << nLostUnk << "new and" << nLost << "tried addresses due to collisions";
|
|
|
|
validateInteral();
|
|
}
|
|
|
|
unsigned int GetSerializeSize(int nType, int nVersion) const
|
|
{
|
|
return static_cast<unsigned int>((CSizeComputer(nType, nVersion) << *this).size());
|
|
}
|
|
|
|
void Clear();
|
|
|
|
CAddrMan();
|
|
|
|
~CAddrMan();
|
|
|
|
//! \internal Consistency check
|
|
int validateInteral();
|
|
|
|
//! Return the number of (unique) addresses in all tables.
|
|
inline size_t size() const
|
|
{
|
|
return vRandom.size();
|
|
}
|
|
|
|
//! Add a single address.
|
|
bool Add(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty = 0);
|
|
|
|
//! Add multiple addresses.
|
|
bool Add(const std::vector<CAddress> &vAddr, const CNetAddr& source, int64_t nTimePenalty = 0);
|
|
|
|
//! Mark an entry "good", possibly moving it from "new" to "tried".
|
|
void Good(const CService &addr, int64_t nTime = GetAdjustedTime());
|
|
|
|
//! Mark an entry as attempted to connect.
|
|
void Attempt(const CService &addr, int64_t nTime = GetAdjustedTime());
|
|
|
|
//! Select an address to connect to, if newOnly is set to true, only the new table is selected from.
|
|
CAddrInfo Select(bool newOnly = false);
|
|
|
|
//! Return a bunch of addresses, selected at random.
|
|
std::vector<CAddress> GetAddr();
|
|
|
|
//! Mark an entry as currently-connected-to.
|
|
void Connected(const CService &addr, int64_t nTime = GetAdjustedTime());
|
|
|
|
//! Mark an address as useless. For instance if its misbehaving
|
|
void increaseUselessness(const CNetAddr &addr, int count = 1);
|
|
|
|
CAddrInfo* Find(const CNetAddr& addr);
|
|
};
|
|
|
|
#endif
|