diff --git a/src/Makefile.am b/src/Makefile.am index 6c4a62a8d..87bed1375 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -160,6 +160,7 @@ BITCOIN_CORE_H = \ core_io.h \ core_memusage.h \ deprecation.h \ + fs.h \ hash.h \ httprpc.h \ httpserver.h \ @@ -215,6 +216,7 @@ BITCOIN_CORE_H = \ txdb.h \ txmempool.h \ ui_interface.h \ + util/asmap.h \ uint256.h \ uint252.h \ undo.h \ @@ -285,6 +287,7 @@ libbitcoin_server_a_SOURCES = \ cc/betprotocol.cpp \ chain.cpp \ checkpoints.cpp \ + fs.cpp \ crosschain.cpp \ crosschain_authority.cpp \ deprecation.cpp \ @@ -383,7 +386,6 @@ if EXPERIMENTAL_ASM crypto_libbitcoin_crypto_a_SOURCES += crypto/sha256_sse4.cpp endif - if ENABLE_MINING EQUIHASH_TROMP_SOURCES = \ pow/tromp/equi_miner.h \ @@ -455,6 +457,7 @@ libbitcoin_util_a_SOURCES = \ utilmoneystr.cpp \ utilstrencodings.cpp \ utiltime.cpp \ + util/asmap.cpp \ $(BITCOIN_CORE_H) \ $(LIBZCASH_H) diff --git a/src/Makefile.ktest.include b/src/Makefile.ktest.include index 640c154e4..18b808eef 100644 --- a/src/Makefile.ktest.include +++ b/src/Makefile.ktest.include @@ -10,7 +10,9 @@ komodo_test_SOURCES = \ test-komodo/test_coinimport.cpp \ test-komodo/test_eval_bet.cpp \ test-komodo/test_eval_notarisation.cpp \ - test-komodo/test_parse_notarisation.cpp + test-komodo/test_parse_notarisation.cpp \ + test-komodo/test_addrman.cpp \ + test-komodo/test_netbase_tests.cpp komodo_test_CPPFLAGS = $(komodod_CPPFLAGS) diff --git a/src/addrman.cpp b/src/addrman.cpp index 2f02f3b17..d468a8437 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -24,19 +24,25 @@ #include "serialize.h" #include "streams.h" -int CAddrInfo::GetTriedBucket(const uint256& nKey) const +int CAddrInfo::GetTriedBucket(const uint256& nKey, const std::vector &asmap) const { uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetHash().GetCheapHash(); - uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetCheapHash(); - return hash2 % ADDRMAN_TRIED_BUCKET_COUNT; + uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetCheapHash(); + int tried_bucket = hash2 % ADDRMAN_TRIED_BUCKET_COUNT; + uint32_t mapped_as = GetMappedAS(asmap); + LogPrint("net", "IP %s mapped to AS%i belongs to tried bucket %i\n", ToStringIP(), mapped_as, tried_bucket); + return tried_bucket; } -int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src) const +int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const std::vector &asmap) const { - std::vector vchSourceGroupKey = src.GetGroup(); - uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetHash().GetCheapHash(); + std::vector vchSourceGroupKey = src.GetGroup(asmap); + uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup(asmap) << vchSourceGroupKey).GetHash().GetCheapHash(); uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetHash().GetCheapHash(); - return hash2 % ADDRMAN_NEW_BUCKET_COUNT; + int new_bucket = hash2 % ADDRMAN_NEW_BUCKET_COUNT; + uint32_t mapped_as = GetMappedAS(asmap); + LogPrint("net", "IP %s mapped to AS%i belongs to new bucket %i\n", ToStringIP(), mapped_as, new_bucket); + return new_bucket; } int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const @@ -176,7 +182,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId) assert(info.nRefCount == 0); // which tried bucket to move the entry to - int nKBucket = info.GetTriedBucket(nKey); + int nKBucket = info.GetTriedBucket(nKey, m_asmap); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); // first make space to add it (the existing tried entry there is moved to new, deleting whatever is there). @@ -192,7 +198,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId) nTried--; // find which new bucket it belongs to - int nUBucket = infoOld.GetNewBucket(nKey); + int nUBucket = infoOld.GetNewBucket(nKey, m_asmap); int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket); ClearNew(nUBucket, nUBucketPos); assert(vvNew[nUBucket][nUBucketPos] == -1); @@ -302,7 +308,7 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP fNew = true; } - int nUBucket = pinfo->GetNewBucket(nKey, source); + int nUBucket = pinfo->GetNewBucket(nKey, source, m_asmap); int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket); if (vvNew[nUBucket][nUBucketPos] != nId) { bool fInsert = vvNew[nUBucket][nUBucketPos] == -1; @@ -454,7 +460,7 @@ int CAddrMan::Check_() if (vvTried[n][i] != -1) { if (!setTried.count(vvTried[n][i])) return -11; - if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey) != n) + if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey, m_asmap) != n) return -17; if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i) return -18; @@ -531,3 +537,30 @@ void CAddrMan::Connected_(const CService& addr, int64_t nTime) int CAddrMan::RandomInt(int nMax){ return GetRandInt(nMax); } + +std::vector CAddrMan::DecodeAsmap(fs::path path) +{ + std::vector bits; + FILE *filestr = fsbridge::fopen(path, "rb"); + CAutoFile file(filestr, SER_DISK, CLIENT_VERSION); + if (file.IsNull()) { + LogPrintf("Failed to open asmap file from disk\n"); + return bits; + } + fseek(filestr, 0, SEEK_END); + int length = ftell(filestr); + LogPrintf("Opened asmap file %s (%d bytes) from disk\n", path, length); + fseek(filestr, 0, SEEK_SET); + char cur_byte; + for (int i = 0; i < length; ++i) { + file >> cur_byte; + for (int bit = 0; bit < 8; ++bit) { + bits.push_back((cur_byte >> bit) & 1); + } + } + if (!SanityCheckASMap(bits)) { + LogPrintf("Sanity check of asmap file %s failed\n", path); + return {}; + } + return bits; +} diff --git a/src/addrman.h b/src/addrman.h index b23ab5aa3..0d4d879a0 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -27,6 +27,10 @@ #include "sync.h" #include "timedata.h" #include "util.h" +#include "fs.h" +#include "clientversion.h" +#include "hash.h" +#include "netbase.h" #include #include @@ -98,15 +102,15 @@ public: } //! Calculate in which "tried" bucket this entry belongs - int GetTriedBucket(const uint256 &nKey) const; + int GetTriedBucket(const uint256 &nKey, const std::vector &asmap) const; //! Calculate in which "new" bucket this entry belongs, given a certain source - int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const; + int GetNewBucket(const uint256 &nKey, const CNetAddr& src, const std::vector &asmap) const; //! Calculate in which "new" bucket this entry belongs, using its default source - int GetNewBucket(const uint256 &nKey) const + int GetNewBucket(const uint256 &nKey, const std::vector &asmap) const { - return GetNewBucket(nKey, source); + return GetNewBucket(nKey, source, asmap); } //! Calculate in which position of a bucket to store this entry. @@ -187,6 +191,7 @@ public: */ class CAddrMan { +friend class CAddrManTest; private: //! critical section to protect the inner data structures mutable CCriticalSection cs; @@ -265,9 +270,29 @@ protected: void Connected_(const CService &addr, int64_t nTime); public: + // Compressed IP->ASN mapping, loaded from a file when a node starts. + // Should be always empty if no file was provided. + // This mapping is then used for bucketing nodes in Addrman. + // + // If asmap is provided, nodes will be bucketed by + // AS they belong to, in order to make impossible for a node + // to connect to several nodes hosted in a single AS. + // This is done in response to Erebus attack, but also to generally + // diversify the connections every node creates, + // especially useful when a large fraction of nodes + // operate under a couple of cloud providers. + // + // If a new asmap was provided, the existing records + // would be re-bucketed accordingly. + std::vector m_asmap; + + // Read asmap from provided binary file + static std::vector DecodeAsmap(fs::path path); + + /** * serialized format: - * * version byte (currently 1) + * * version byte (1 for pre-asmap files, 2 for files including asmap version) * * 0x20 + nKey (serialized as if it were a vector, for backward compatibility) * * nNew * * nTried @@ -294,12 +319,12 @@ public: * We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has * very little in common. */ - template - void Serialize(Stream &s) const + template + void Serialize(Stream &s) const { LOCK(cs); - unsigned char nVersion = 1; + unsigned char nVersion = 2; s << nVersion; s << ((unsigned char)32); s << nKey; @@ -310,9 +335,9 @@ public: s << nUBuckets; std::map mapUnkIds; int nIds = 0; - for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { - mapUnkIds[(*it).first] = nIds; - const CAddrInfo &info = (*it).second; + for (const auto& entry : mapInfo) { + mapUnkIds[entry.first] = nIds; + const CAddrInfo &info = entry.second; if (info.nRefCount) { assert(nIds != nNew); // this means nNew was wrong, oh ow s << info; @@ -320,8 +345,8 @@ public: } } nIds = 0; - for (std::map::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) { - const CAddrInfo &info = (*it).second; + for (const auto& entry : mapInfo) { + const CAddrInfo &info = entry.second; if (info.fInTried) { assert(nIds != nTried); // this means nTried was wrong, oh ow s << info; @@ -342,6 +367,13 @@ public: } } } + // Store asmap version after bucket entries so that it + // can be ignored by older clients for backward compatibility. + uint256 asmap_version; + if (m_asmap.size() != 0) { + asmap_version = SerializeHash(m_asmap); + } + s << asmap_version; } template @@ -350,7 +382,6 @@ public: LOCK(cs); Clear(); - unsigned char nVersion; s >> nVersion; unsigned char nKeySize; @@ -380,16 +411,6 @@ public: mapAddr[info] = n; info.nRandomPos = vRandom.size(); vRandom.push_back(n); - if (nVersion != 1 || nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) { - // In case the new table data cannot be used (nVersion unknown, or 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; @@ -398,7 +419,7 @@ public: for (int n = 0; n < nTried; n++) { CAddrInfo info; s >> info; - int nKBucket = info.GetTriedBucket(nKey); + int nKBucket = info.GetTriedBucket(nKey, m_asmap); int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket); if (vvTried[nKBucket][nKBucketPos] == -1) { info.nRandomPos = vRandom.size(); @@ -414,7 +435,9 @@ public: } nTried -= nLost; - // Deserialize positions in the new table (if possible). + // Store positions in the new table buckets to apply later (if possible). + std::map entryToBucket; // Represents which entry belonged to which bucket when serializing + for (int bucket = 0; bucket < nUBuckets; bucket++) { int nSize = 0; s >> nSize; @@ -422,12 +445,38 @@ public: int nIndex = 0; s >> nIndex; if (nIndex >= 0 && nIndex < nNew) { - CAddrInfo &info = mapInfo[nIndex]; + entryToBucket[nIndex] = bucket; + } + } + } + + uint256 supplied_asmap_version; + if (m_asmap.size() != 0) { + supplied_asmap_version = SerializeHash(m_asmap); + } + uint256 serialized_asmap_version; + if (nVersion > 1) { + s >> serialized_asmap_version; + } + + for (int n = 0; n < nNew; n++) { + CAddrInfo &info = mapInfo[n]; + int bucket = entryToBucket[n]; 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) { + if (nVersion == 2 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && + info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS && serialized_asmap_version == supplied_asmap_version) { + // Bucketing has not changed, using existing bucket positions for the new table + vvNew[bucket][nUBucketPos] = n; info.nRefCount++; - vvNew[bucket][nUBucketPos] = nIndex; - } + } else { + // In case the new table data cannot be used (nVersion unknown, bucket count wrong or new asmap), + // try to give them a reference based on their primary source address. + LogPrint("addrman", "Bucketing method was updated, re-bucketing addrman entries from disk\n"); + bucket = info.GetNewBucket(nKey, m_asmap); + nUBucketPos = info.GetBucketPosition(nKey, true, bucket); + if (vvNew[bucket][nUBucketPos] == -1) { + vvNew[bucket][nUBucketPos] = n; + info.nRefCount++; } } } @@ -452,6 +501,7 @@ public: void Clear() { + LOCK(cs); std::vector().swap(vRandom); nKey = GetRandHash(); for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) { @@ -468,6 +518,8 @@ public: nIdCount = 0; nTried = 0; nNew = 0; + mapInfo.clear(); + mapAddr.clear(); } CAddrMan() diff --git a/src/crypto/common.h b/src/crypto/common.h index 130f78fa9..87b565100 100644 --- a/src/crypto/common.h +++ b/src/crypto/common.h @@ -87,6 +87,27 @@ void static inline WriteBE64(unsigned char* ptr, uint64_t x) memcpy(ptr, (char*)&v, 8); } +/** Return the smallest number n such that (x >> n) == 0 (or 64 if the highest bit in x is set. */ +uint64_t static inline CountBits(uint64_t x) +{ +#if HAVE_DECL___BUILTIN_CLZL + if (sizeof(unsigned long) >= sizeof(uint64_t)) { + return x ? 8 * sizeof(unsigned long) - __builtin_clzl(x) : 0; + } +#endif +#if HAVE_DECL___BUILTIN_CLZLL + if (sizeof(unsigned long long) >= sizeof(uint64_t)) { + return x ? 8 * sizeof(unsigned long long) - __builtin_clzll(x) : 0; + } +#endif + int ret = 0; + while (x) { + x >>= 1; + ++ret; + } + return ret; +} + int inline init_and_check_sodium() { if (sodium_init() == -1) { @@ -124,5 +145,4 @@ int inline init_and_check_sodium() return 0; } - #endif // BITCOIN_CRYPTO_COMMON_H diff --git a/src/fs.cpp b/src/fs.cpp new file mode 100644 index 000000000..a5e12f1cf --- /dev/null +++ b/src/fs.cpp @@ -0,0 +1,15 @@ +#include "fs.h" + +namespace fsbridge { + +FILE *fopen(const fs::path& p, const char *mode) +{ + return ::fopen(p.string().c_str(), mode); +} + +FILE *freopen(const fs::path& p, const char *mode, FILE *stream) +{ + return ::freopen(p.string().c_str(), mode, stream); +} + +} // fsbridge diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 000000000..db7d26a65 --- /dev/null +++ b/src/fs.h @@ -0,0 +1,24 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef KOMODO_FS_H +#define KOMODO_FS_H + +#include +#include + +#include +#include +#include + +/** Filesystem operations and types */ +namespace fs = boost::filesystem; + +/** Bridge operations to C stdio */ +namespace fsbridge { + FILE *fopen(const fs::path& p, const char *mode); + FILE *freopen(const fs::path& p, const char *mode, FILE *stream); +}; + +#endif // KOMODO_FS_H diff --git a/src/init.cpp b/src/init.cpp index 6510424af..042228807 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -126,6 +126,9 @@ enum BindFlags { }; static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat"; + +static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map"; + CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h ////////////////////////////////////////////////////////////////////////////// @@ -399,6 +402,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-zindex", strprintf(_("Maintain extra statistics about shielded transactions and payments (default: %u)"), 0)); strUsage += HelpMessageGroup(_("Connection options:")); strUsage += HelpMessageOpt("-addnode=", _("Add a node to connect to and attempt to keep the connection open")); + strUsage += HelpMessageOpt("-asmap=", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME)); strUsage += HelpMessageOpt("-banscore=", strprintf(_("Threshold for disconnecting misbehaving peers (default: %u)"), 100)); strUsage += HelpMessageOpt("-bantime=", strprintf(_("Number of seconds to keep misbehaving peers from reconnecting (default: %u)"), 86400)); strUsage += HelpMessageOpt("-bind=", _("Bind to given address and always listen on it. Use [host]:port notation for IPv6")); @@ -741,7 +745,7 @@ bool InitSanityCheck(void) if (!glibc_sanity_test() || !glibcxx_sanity_test()) { fprintf(stderr,"%s: glibc insanity!\n", __FUNCTION__); return false; - } +} return true; } @@ -806,8 +810,8 @@ static void ZC_LoadParams( if (files_exist(sapling_spend, sapling_output)) { LogPrintf("Found sapling params in /usr/share/hush\n"); found=true; - } - } + } + } if (!found) { // Try .. @@ -816,8 +820,8 @@ static void ZC_LoadParams( if (files_exist(sapling_spend, sapling_output)) { LogPrintf("Found sapling params in ..\n"); found = true; + } } - } if (!found) { // This will catch the case of any external software (i.e. GUI wallets) needing params and installed in same dir as hush3.git @@ -826,7 +830,7 @@ static void ZC_LoadParams( if (files_exist(sapling_spend, sapling_output)) { LogPrintf("Found sapling params in ../hush3\n"); found = true; - } + } } if (!found) { @@ -836,7 +840,7 @@ static void ZC_LoadParams( if (files_exist(sapling_spend, sapling_output)) { LogPrintf("Found sapling params in /Applications/Contents/MacOS\n"); found = true; - } + } } if (!found) { @@ -846,7 +850,7 @@ static void ZC_LoadParams( if (files_exist(sapling_spend, sapling_output)) { LogPrintf("Found sapling params in /Applications/Contents/MacOS\n"); found = true; - } +} } if (!found) { @@ -858,7 +862,7 @@ static void ZC_LoadParams( LogPrintf("Found sapling params in ~/.zcash\n"); found = true; } - } +} if (!found) { // No Sapling params, at least we tried @@ -896,7 +900,7 @@ static void ZC_LoadParams( static_assert( sizeof(boost::filesystem::path::value_type) == sizeof(codeunit), "librustzcash not configured correctly"); - auto sapling_spend_str = sapling_spend.native(); + auto sapling_spend_str = sapling_spend.native(); auto sapling_output_str = sapling_output.native(); LogPrintf("Loading Sapling (Spend) parameters from %s\n", sapling_spend.string().c_str()); @@ -1029,13 +1033,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) fprintf(stderr,"%s zmerge error\n", __FUNCTION__); return InitError(_("RPC method z_mergetoaddress requires -experimentalfeatures.")); } - } + } //fprintf(stderr,"%s tik2\n", __FUNCTION__); // Set this early so that parameter interactions go to console fPrintToConsole = GetBoolArg("-printtoconsole", false); - fLogTimestamps = GetBoolArg("-logtimestamps", true); - fLogIPs = GetBoolArg("-logips", false); + fLogTimestamps = GetBoolArg("-logtimestamps", true); + fLogIPs = GetBoolArg("-logips", false); LogPrintf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); @@ -1084,6 +1088,31 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("%s: parameter interaction: -externalip set -> setting -discover=0\n", __func__); } + // Read asmap file if configured + if (mapArgs.count("-asmap")) { + fs::path asmap_path = fs::path(GetArg("-asmap", "")); + if (asmap_path.empty()) { + asmap_path = DEFAULT_ASMAP_FILENAME; + } + if (!asmap_path.is_absolute()) { + asmap_path = GetDataDir() / asmap_path; + } + if (!fs::exists(asmap_path)) { + InitError(strprintf(_("Could not find asmap file %s"), asmap_path)); + return false; + } + std::vector asmap = CAddrMan::DecodeAsmap(asmap_path); + if (asmap.size() == 0) { + InitError(strprintf(_("Could not parse asmap file %s"), asmap_path)); + return false; + } + const uint256 asmap_version = SerializeHash(asmap); + addrman.m_asmap = std::move(asmap); // //node.connman->SetAsmap(std::move(asmap)); + LogPrintf("Using asmap version %s for IP bucketing\n", asmap_version.ToString()); + } else { + LogPrintf("Using /16 prefix for IP bucketing\n"); + } + if (GetBoolArg("-salvagewallet", false)) { // Rewrite just private keys: rescan to find transactions if (SoftSetBoolArg("-rescan", true)) @@ -1530,7 +1559,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // -proxy sets a proxy for all outgoing network traffic // -noproxy (or -proxy=0) as well as the empty string can be used to not set a proxy, this is the default std::string proxyArg = GetArg("-proxy", ""); - SetLimited(NET_TOR); + SetLimited(NET_ONION); if (proxyArg != "" && proxyArg != "0") { proxyType addrProxy = proxyType(CService(proxyArg, 9050), proxyRandomize); if (!addrProxy.IsValid()) @@ -1538,9 +1567,9 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) SetProxy(NET_IPV4, addrProxy); SetProxy(NET_IPV6, addrProxy); - SetProxy(NET_TOR, addrProxy); + SetProxy(NET_ONION, addrProxy); SetNameProxy(addrProxy); - SetLimited(NET_TOR, false); // by default, -proxy sets onion as reachable, unless -noonion later + SetLimited(NET_ONION, false); // by default, -proxy sets onion as reachable, unless -noonion later } //fprintf(stderr,"%s tik20\n", __FUNCTION__); @@ -1550,19 +1579,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) std::string onionArg = GetArg("-onion", ""); if (onionArg != "") { if (onionArg == "0") { // Handle -noonion/-onion=0 - SetLimited(NET_TOR); // set onions as unreachable + SetLimited(NET_ONION); // set onions as unreachable } else { proxyType addrOnion = proxyType(CService(onionArg, 9050), proxyRandomize); if (!addrOnion.IsValid()) return InitError(strprintf(_("Invalid -onion address: '%s'"), onionArg)); - SetProxy(NET_TOR, addrOnion); - SetLimited(NET_TOR, false); + SetProxy(NET_ONION, addrOnion); + SetLimited(NET_ONION, false); } } // see Step 2: parameter interactions for more information about these - fListen = GetBoolArg("-listen", DEFAULT_LISTEN); - fDiscover = GetBoolArg("-discover", true); + fListen = GetBoolArg("-listen", DEFAULT_LISTEN); + fDiscover = GetBoolArg("-discover", true); fNameLookup = GetBoolArg("-dns", true); //fprintf(stderr,"%s tik22\n", __FUNCTION__); @@ -1624,7 +1653,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) boost::filesystem::path pathTLSTrustredDir(GetArg("-tlstrustdir", "")); if (!boost::filesystem::exists(pathTLSTrustredDir)) return InitError(strprintf(_("Cannot find trusted certificates directory: '%s'"), pathTLSTrustredDir.string())); - } + } #if ENABLE_ZMQ pzmqNotificationInterface = CZMQNotificationInterface::CreateWithArguments(mapArgs); @@ -2142,7 +2171,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LOCK(cs_main); fHaveGenesis = (chainActive.Tip() != NULL); MilliSleep(10); - } + } if (!fHaveGenesis) { MilliSleep(10); diff --git a/src/net.cpp b/src/net.cpp index d48220499..cfe538b5e 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -400,8 +400,6 @@ CNode* FindNode(const CService& addr) return NULL; } - - CNode* ConnectNode(CAddress addrConnect, const char *pszDest) { if (pszDest == NULL) { @@ -515,11 +513,11 @@ void CNode::CloseSocketDisconnect() { LOCK(cs_hSocket); - if (hSocket != INVALID_SOCKET) - { + if (hSocket != INVALID_SOCKET) + { try { - LogPrint("net", "disconnecting peer=%d\n", id); + LogPrint("net", "disconnecting peer=%d\n", id); } catch(std::bad_alloc&) { @@ -535,8 +533,8 @@ void CNode::CloseSocketDisconnect() SSL_free(ssl); ssl = NULL; } - CloseSocket(hSocket); - } + CloseSocket(hSocket); + } } // in case this fails, we'll empty the recv buffer when the CNode is deleted @@ -668,10 +666,13 @@ void CNode::AddWhitelistedRange(const CSubNet &subnet) { vWhitelistedRange.push_back(subnet); } -void CNode::copyStats(CNodeStats &stats) +void CNode::copyStats(CNodeStats &stats, const std::vector &m_asmap) { stats.nodeid = this->GetId(); stats.nServices = nServices; + stats.addr = addr; + // stats.addrBind = addrBind; + stats.m_mapped_as = addr.GetMappedAS(m_asmap); stats.nLastSend = nLastSend; stats.nLastRecv = nLastRecv; stats.nTimeConnected = nTimeConnected; @@ -855,15 +856,15 @@ void SocketSendData(CNode *pnode) if (nRet != SSL_ERROR_WANT_READ && nRet != SSL_ERROR_WANT_WRITE) { LogPrintf("ERROR: SSL_write %s; closing connection\n", ERR_error_string(nRet, NULL)); - pnode->CloseSocketDisconnect(); - } + pnode->CloseSocketDisconnect(); + } else { // preventive measure from exhausting CPU usage // MilliSleep(1); // 1 msec } - } + } else { if (nRet != WSAEWOULDBLOCK && nRet != WSAEMSGSIZE && nRet != WSAEINTR && nRet != WSAEINPROGRESS) @@ -951,8 +952,8 @@ public: CSHA256 hashA, hashB; std::vector vchA(32), vchB(32); - vchGroupA = a->addr.GetGroup(); - vchGroupB = b->addr.GetGroup(); + vchGroupA = a->addr.GetGroup(addrman.m_asmap); + vchGroupB = b->addr.GetGroup(addrman.m_asmap); hashA.Write(begin_ptr(vchGroupA), vchGroupA.size()); hashB.Write(begin_ptr(vchGroupB), vchGroupB.size()); @@ -1048,14 +1049,14 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) { int64_t nMostConnectionsTime = 0; std::map, std::vector > mapAddrCounts; BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) { - mapAddrCounts[node->addr.GetGroup()].push_back(node); - int64_t grouptime = mapAddrCounts[node->addr.GetGroup()][0]->nTimeConnected; - size_t groupsize = mapAddrCounts[node->addr.GetGroup()].size(); + mapAddrCounts[node->addr.GetGroup(addrman.m_asmap)].push_back(node); + int64_t grouptime = mapAddrCounts[node->addr.GetGroup(addrman.m_asmap)][0]->nTimeConnected; + size_t groupsize = mapAddrCounts[node->addr.GetGroup(addrman.m_asmap)].size(); if (groupsize > nMostConnections || (groupsize == nMostConnections && grouptime > nMostConnectionsTime)) { nMostConnections = groupsize; nMostConnectionsTime = grouptime; - naMostConnections = node->addr.GetGroup(); + naMostConnections = node->addr.GetGroup(addrman.m_asmap); } } @@ -1074,7 +1075,6 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) { return true; } - static void AcceptConnection(const ListenSocket& hListenSocket) { struct sockaddr_storage sockaddr; socklen_t len = sizeof(sockaddr); @@ -1347,7 +1347,6 @@ void ThreadSocketHandler() // * We send some data. // * We wait for data to be received (and disconnect after timeout). // * We process a message in the buffer (message handler thread). - { TRY_LOCK(pnode->cs_vSend, lockSend); if (lockSend && !pnode->vSendMsg.empty()) { @@ -1588,7 +1587,7 @@ void ThreadOpenConnections() LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) { if (!pnode->fInbound) { - setConnected.insert(pnode->addr.GetGroup()); + setConnected.insert(pnode->addr.GetGroup(addrman.m_asmap)); nOutbound++; } } @@ -1602,7 +1601,7 @@ void ThreadOpenConnections() CAddrInfo addr = addrman.Select(); // if we selected an invalid address, restart - if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) + if (!addr.IsValid() || setConnected.count(addr.GetGroup(addrman.m_asmap)) || IsLocal(addr)) break; // If we didn't find an appropriate destination after trying 100 addresses fetched from addrman, @@ -2388,7 +2387,7 @@ CNode::~CNode() ssl = NULL; } - CloseSocket(hSocket); + CloseSocket(hSocket); } if (pfilter) @@ -2490,3 +2489,16 @@ void CNode::EndMessage() UNLOCK_FUNCTION(cs_vSend) LEAVE_CRITICAL_SECTION(cs_vSend); } + +void CopyNodeStats(std::vector& vstats) +{ + vstats.clear(); + + LOCK(cs_vNodes); + vstats.reserve(vNodes.size()); + BOOST_FOREACH(CNode* pnode, vNodes) { + CNodeStats stats; + pnode->copyStats(stats, addrman.m_asmap); + vstats.push_back(stats); + } +} diff --git a/src/net.h b/src/net.h index a8afbaff1..bdedcbf81 100644 --- a/src/net.h +++ b/src/net.h @@ -114,6 +114,9 @@ static std::string validationdescription; typedef int NodeId; +class CNodeStats; +void CopyNodeStats(std::vector& vstats); + struct CombinerAll { typedef bool result_type; @@ -202,7 +205,6 @@ struct LocalServiceInfo { int nPort; }; - extern CCriticalSection cs_mapLocalHost; extern std::map mapLocalHost; @@ -227,6 +229,11 @@ public: double dPingTime; double dPingWait; std::string addrLocal; + // Address of this peer + CAddress addr; + // Bind address of our side of the connection + // CAddress addrBind; // https://github.com/bitcoin/bitcoin/commit/a7e3c2814c8e49197889a4679461be42254e5c51 + uint32_t m_mapped_as; }; @@ -303,7 +310,10 @@ public: int64_t nTimeConnected; int64_t nTimeOffset; uint32_t prevtimes[16]; + // Address of this peer CAddress addr; + // Bind address of our side of the connection + // const CAddress addrBind; // https://github.com/bitcoin/bitcoin/commit/a7e3c2814c8e49197889a4679461be42254e5c51 std::string addrName; CService addrLocal; int nVersion; @@ -449,7 +459,7 @@ public: if (addr.IsValid() && !addrKnown.contains(addr.GetKey())) { if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { vAddrToSend[insecure_rand() % vAddrToSend.size()] = addr; - } else { + } else { vAddrToSend.push_back(addr); } } @@ -672,7 +682,7 @@ public: static bool Unban(const CSubNet &ip); static void GetBanned(std::map &banmap); - void copyStats(CNodeStats &stats); + void copyStats(CNodeStats &stats, const std::vector &m_asmap); static bool IsWhitelistedRange(const CNetAddr &ip); static void AddWhitelistedRange(const CSubNet &subnet); diff --git a/src/netbase.cpp b/src/netbase.cpp index 24d5125b5..880e43b8c 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -30,6 +30,7 @@ #include "random.h" #include "util.h" #include "utilstrencodings.h" +#include "crypto/common.h" // for ReadBE32 #ifdef __APPLE__ #ifdef HAVE_GETADDRINFO_A @@ -65,6 +66,9 @@ bool fNameLookup = false; static const unsigned char pchIPv4[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff }; +// 0xFD + sha256("bitcoin")[0:5] +static const unsigned char g_internal_prefix[] = { 0xFD, 0x6B, 0x88, 0xC0, 0x87, 0x24 }; + // Need ample time for negotiation for very slow proxies such as Tor (milliseconds) static const int SOCKS5_RECV_TIMEOUT = 20 * 1000; @@ -72,7 +76,7 @@ enum Network ParseNetwork(std::string net) { boost::to_lower(net); if (net == "ipv4") return NET_IPV4; if (net == "ipv6") return NET_IPV6; - if (net == "tor" || net == "onion") return NET_TOR; + if (net == "tor" || net == "onion") return NET_ONION; return NET_UNROUTABLE; } @@ -81,7 +85,7 @@ std::string GetNetworkName(enum Network net) { { case NET_IPV4: return "ipv4"; case NET_IPV6: return "ipv6"; - case NET_TOR: return "onion"; + case NET_ONION: return "onion"; default: return ""; } } @@ -879,6 +883,11 @@ bool CNetAddr::IsRoutable() const return IsValid() && !(IsRFC1918() || IsRFC2544() || IsRFC3927() || IsRFC4862() || IsRFC6598() || IsRFC5737() || (IsRFC4193() && !IsTor()) || IsRFC4843() || IsLocal()); } +bool CNetAddr::IsInternal() const +{ + return memcmp(ip, g_internal_prefix, sizeof(g_internal_prefix)) == 0; +} + enum Network CNetAddr::GetNetwork() const { if (!IsRoutable()) @@ -888,7 +897,7 @@ enum Network CNetAddr::GetNetwork() const return NET_IPV4; if (IsTor()) - return NET_TOR; + return NET_ONION; return NET_IPV6; } @@ -949,11 +958,88 @@ bool CNetAddr::GetIn6Addr(struct in6_addr* pipv6Addr) const return true; } +bool CNetAddr::HasLinkedIPv4() const +{ + return IsRoutable() && (IsIPv4() || IsRFC6145() || IsRFC6052() || IsRFC3964() || IsRFC4380()); +} + +uint32_t CNetAddr::GetLinkedIPv4() const +{ + if (IsIPv4() || IsRFC6145() || IsRFC6052()) { + // IPv4, mapped IPv4, SIIT translated IPv4: the IPv4 address is the last 4 bytes of the address + return ReadBE32(ip + 12); + } else if (IsRFC3964()) { + // 6to4 tunneled IPv4: the IPv4 address is in bytes 2-6 + return ReadBE32(ip + 2); + } else if (IsRFC4380()) { + // Teredo tunneled IPv4: the IPv4 address is in the last 4 bytes of the address, but bitflipped + return ~ReadBE32(ip + 12); + } + assert(false); +} + +uint32_t CNetAddr::GetNetClass() const { + uint32_t net_class = NET_IPV6; + if (IsLocal()) { + net_class = 255; + } + if (IsInternal()) { + net_class = NET_INTERNAL; + } else if (!IsRoutable()) { + net_class = NET_UNROUTABLE; + } else if (HasLinkedIPv4()) { + net_class = NET_IPV4; + } else if (IsTor()) { + net_class = NET_ONION; + } + return net_class; +} + +uint32_t CNetAddr::GetMappedAS(const std::vector &asmap) const { + uint32_t net_class = GetNetClass(); + if (asmap.size() == 0 || (net_class != NET_IPV4 && net_class != NET_IPV6)) { + return 0; // Indicates not found, safe because AS0 is reserved per RFC7607. + } + std::vector ip_bits(128); + if (HasLinkedIPv4()) { + // For lookup, treat as if it was just an IPv4 address (pchIPv4 prefix + IPv4 bits) + for (int8_t byte_i = 0; byte_i < 12; ++byte_i) { + for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) { + ip_bits[byte_i * 8 + bit_i] = (pchIPv4[byte_i] >> (7 - bit_i)) & 1; + } + } + uint32_t ipv4 = GetLinkedIPv4(); + for (int i = 0; i < 32; ++i) { + ip_bits[96 + i] = (ipv4 >> (31 - i)) & 1; + } + } else { + // Use all 128 bits of the IPv6 address otherwise + for (int8_t byte_i = 0; byte_i < 16; ++byte_i) { + uint8_t cur_byte = GetByte(15 - byte_i); + for (uint8_t bit_i = 0; bit_i < 8; ++bit_i) { + ip_bits[byte_i * 8 + bit_i] = (cur_byte >> (7 - bit_i)) & 1; + } + } + } + uint32_t mapped_as = Interpret(asmap, ip_bits); + return mapped_as; +} + // get canonical identifier of an address' group // no two connections will be attempted to addresses with the same group -std::vector CNetAddr::GetGroup() const +std::vector CNetAddr::GetGroup(const std::vector &asmap) const { std::vector vchRet; + // If non-empty asmap is supplied and the address is IPv4/IPv6, + // return ASN to be used for bucketing. + uint32_t asn = GetMappedAS(asmap); + if (asn != 0) { // Either asmap was empty, or address has non-asmappable net class (e.g. TOR). + vchRet.push_back(NET_IPV6); // IPv4 and IPv6 with same ASN should be in the same bucket + for (int i = 0; i < 4; i++) { + vchRet.push_back((asn >> (8 * i)) & 0xFF); + } + return vchRet; + } int nClass = NET_IPV6; int nStartByte = 0; int nBits = 16; @@ -994,7 +1080,7 @@ std::vector CNetAddr::GetGroup() const } else if (IsTor()) { - nClass = NET_TOR; + nClass = NET_ONION; nStartByte = 6; nBits = 4; } @@ -1072,11 +1158,11 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_IPV4: return REACH_IPV4; case NET_IPV6: return fTunnel ? REACH_IPV6_WEAK : REACH_IPV6_STRONG; // only prefer giving our IPv6 address if it's not tunnelled } - case NET_TOR: + case NET_ONION: switch(ourNet) { default: return REACH_DEFAULT; case NET_IPV4: return REACH_IPV4; // Tor users can connect to IPv4 as well - case NET_TOR: return REACH_PRIVATE; + case NET_ONION: return REACH_PRIVATE; } case NET_TEREDO: switch(ourNet) { @@ -1093,7 +1179,7 @@ int CNetAddr::GetReachabilityFrom(const CNetAddr *paddrPartner) const case NET_TEREDO: return REACH_TEREDO; case NET_IPV6: return REACH_IPV6_WEAK; case NET_IPV4: return REACH_IPV4; - case NET_TOR: return REACH_PRIVATE; // either from Tor, or don't care about our address + case NET_ONION: return REACH_PRIVATE; // either from Tor, or don't care about our address } } } @@ -1443,3 +1529,8 @@ bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking) return true; } + +bool SanityCheckASMap(const std::vector& asmap) +{ + return SanityCheckASMap(asmap, 128); // For IP address lookups, the input is 128 bits +} diff --git a/src/netbase.h b/src/netbase.h index bc5475bfe..e660c9d2e 100644 --- a/src/netbase.h +++ b/src/netbase.h @@ -26,6 +26,7 @@ #include "compat.h" #include "serialize.h" +#include "util/asmap.h" #include #include @@ -47,7 +48,8 @@ enum Network NET_UNROUTABLE = 0, NET_IPV4, NET_IPV6, - NET_TOR, + NET_ONION, + NET_INTERNAL, NET_MAX, }; @@ -91,6 +93,7 @@ class CNetAddr bool IsTor() const; bool IsLocal() const; bool IsRoutable() const; + bool IsInternal() const; bool IsValid() const; bool IsMulticast() const; enum Network GetNetwork() const; @@ -99,7 +102,19 @@ class CNetAddr unsigned int GetByte(int n) const; uint64_t GetHash() const; bool GetInAddr(struct in_addr* pipv4Addr) const; - std::vector GetGroup() const; + uint32_t GetNetClass() const; + + //! For IPv4, mapped IPv4, SIIT translated IPv4, Teredo, 6to4 tunneled addresses, return the relevant IPv4 address as a uint32. + uint32_t GetLinkedIPv4() const; + //! Whether this address has a linked IPv4 address (see GetLinkedIPv4()). + bool HasLinkedIPv4() const; + + // The AS on the BGP path to the node we use to diversify + // peers in AddrMan bucketing based on the AS infrastructure. + // The ip->AS mapping depends on how asmap is constructed. + uint32_t GetMappedAS(const std::vector &asmap) const; + + std::vector GetGroup(const std::vector &asmap) const; int GetReachabilityFrom(const CNetAddr *paddrPartner = NULL) const; CNetAddr(const struct in6_addr& pipv6Addr); @@ -223,4 +238,6 @@ bool SetSocketNonBlocking(SOCKET& hSocket, bool fNonBlocking); */ struct timeval MillisToTimeval(int64_t nTimeout); +bool SanityCheckASMap(const std::vector& asmap); + #endif // BITCOIN_NETBASE_H diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f5f2df9d4..9cc44a014 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -80,19 +80,6 @@ UniValue ping(const UniValue& params, bool fHelp, const CPubKey& mypk) return NullUniValue; } -static void CopyNodeStats(std::vector& vstats) -{ - vstats.clear(); - - LOCK(cs_vNodes); - vstats.reserve(vNodes.size()); - BOOST_FOREACH(CNode* pnode, vNodes) { - CNodeStats stats; - pnode->copyStats(stats); - vstats.push_back(stats); - } -} - UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) { if (fHelp || params.size() != 0) @@ -149,6 +136,11 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) obj.push_back(Pair("addr", stats.addrName)); if (!(stats.addrLocal.empty())) obj.push_back(Pair("addrlocal", stats.addrLocal)); + // if (stats.addrBind.IsValid()) + // obj.push_back(Pair("addrbind", stats.addrBind.ToString())); + if (stats.m_mapped_as != 0) { + obj.push_back(Pair("mapped_as", uint64_t(stats.m_mapped_as))); + } obj.push_back(Pair("services", strprintf("%016x", stats.nServices))); obj.push_back(Pair("tls_established", stats.fTLSEstablished)); obj.push_back(Pair("lastsend", stats.nLastSend)); @@ -156,7 +148,7 @@ UniValue getpeerinfo(const UniValue& params, bool fHelp, const CPubKey& mypk) obj.push_back(Pair("bytessent", stats.nSendBytes)); obj.push_back(Pair("bytesrecv", stats.nRecvBytes)); obj.push_back(Pair("conntime", stats.nTimeConnected)); - obj.push_back(Pair("timeoffset", 0)); + obj.push_back(Pair("timeoffset", 0)); obj.push_back(Pair("pingtime", stats.dPingTime)); if (stats.dPingWait > 0.0) obj.push_back(Pair("pingwait", stats.dPingWait)); @@ -338,6 +330,7 @@ UniValue getaddednodeinfo(const UniValue& params, bool fHelp, const CPubKey& myp + HelpExampleCli("getaddednodeinfo", "true \"192.168.0.201\"") + HelpExampleRpc("getaddednodeinfo", "true, \"192.168.0.201\"") ); + bool fDns = params[0].get_bool(); list laddedNodes(0); diff --git a/src/test-komodo/test_addrman.cpp b/src/test-komodo/test_addrman.cpp new file mode 100644 index 000000000..f283b9710 --- /dev/null +++ b/src/test-komodo/test_addrman.cpp @@ -0,0 +1,862 @@ +#include + +#include "addrman.h" +#include +#include +#include + +#include "hash.h" +#include "random.h" +#include "util/asmap.h" + +#include "netbase.h" +#include "chainparams.h" +#include "tinyformat.h" +#include "utilstrencodings.h" + +#define NODE_NONE 0 + +// https://stackoverflow.com/questions/16491675/how-to-send-custom-message-in-google-c-testing-framework/29155677 +#define GTEST_COUT_NOCOLOR std::cerr << "[ ] [ INFO ] " +namespace testing +{ + namespace internal + { + enum GTestColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW + }; + + extern void ColoredPrintf(GTestColor color, const char* fmt, ...); + } +} +#define PRINTF(...) do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, "[ ] "); testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } while(0) + +// C++ stream interface +class TestCout : public std::stringstream +{ + public: + ~TestCout() + { + PRINTF("%s",str().c_str()); + } +}; + +#define GTEST_COUT_COLOR TestCout() + +using namespace std; + +/* xxd -i est-komodo/data/asmap.raw | sed 's/unsigned char/static unsigned const char/g' */ +static unsigned const char asmap_raw[] = { + 0xfb, 0x03, 0xec, 0x0f, 0xb0, 0x3f, 0xc0, 0xfe, 0x00, 0xfb, 0x03, 0xec, + 0x0f, 0xb0, 0x3f, 0xc0, 0xfe, 0x00, 0xfb, 0x03, 0xec, 0x0f, 0xb0, 0xff, + 0xff, 0xfe, 0xff, 0xed, 0xb0, 0xff, 0xd4, 0x86, 0xe6, 0x28, 0x29, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x40, 0x99, 0x01, 0x00, 0x80, 0x01, + 0x80, 0x04, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x1c, 0xf0, 0x39 +}; +unsigned int asmap_raw_len = 59; + +class CAddrManTest : public CAddrMan +{ + private: + uint64_t state; + bool deterministic; + public: + + explicit CAddrManTest(bool makeDeterministic = true, + std::vector asmap = std::vector()) + { + if (makeDeterministic) { + // Set addrman addr placement to be deterministic. + MakeDeterministic(); + } + deterministic = makeDeterministic; + m_asmap = asmap; + state = 1; + } + + void PrintInternals() + { + GTEST_COUT_NOCOLOR << "mapInfo.size() = " << mapInfo.size() << std::endl; + GTEST_COUT_NOCOLOR << "nNew = " << nNew << std::endl; + } + + //! Ensure that bucket placement is always the same for testing purposes. + void MakeDeterministic() + { + nKey.SetNull(); + seed_insecure_rand(true); + } + + int RandomInt(int nMax) + { + state = (CHashWriter(SER_GETHASH, 0) << state).GetHash().GetCheapHash(); + return (unsigned int)(state % nMax); + } + + CAddrInfo* Find(const CNetAddr& addr, int* pnId = NULL) + { + return CAddrMan::Find(addr, pnId); + } + + CAddrInfo* Create(const CAddress& addr, const CNetAddr& addrSource, int* pnId = NULL) + { + return CAddrMan::Create(addr, addrSource, pnId); + } + + void Delete(int nId) + { + CAddrMan::Delete(nId); + } + + // Used to test deserialization + std::pair GetBucketAndEntry(const CAddress& addr) + { + // LOCK(cs); + int nId = mapAddr[addr]; + for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) { + for (int entry = 0; entry < ADDRMAN_BUCKET_SIZE; ++entry) { + if (nId == vvNew[bucket][entry]) { + return std::pair(bucket, entry); + } + } + } + return std::pair(-1, -1); + } + + void Clear() + { + CAddrMan::Clear(); + if (deterministic) { + nKey.SetNull(); + seed_insecure_rand(true); + } + } +}; + +static CNetAddr ResolveIP(const std::string& ip) +{ + vector vIPs; + CNetAddr addr; + if (LookupHost(ip.c_str(), vIPs)) { + addr = vIPs[0]; + } else + { + // it was BOOST_CHECK_MESSAGE, but we can't use ASSERT or EXPECT outside a test + GTEST_COUT_COLOR << strprintf("failed to resolve: %s", ip) << std::endl; + } + return addr; +} + +static CService ResolveService(const std::string& ip, const int port = 0) +{ + CService serv; + if (!Lookup(ip.c_str(), serv, port, false)) + GTEST_COUT_COLOR << strprintf("failed to resolve: %s:%i", ip, port) << std::endl; + return serv; +} + +static std::vector FromBytes(const unsigned char* source, int vector_size) { + std::vector result(vector_size); + for (int byte_i = 0; byte_i < vector_size / 8; ++byte_i) { + unsigned char cur_byte = source[byte_i]; + for (int bit_i = 0; bit_i < 8; ++bit_i) { + result[byte_i * 8 + bit_i] = (cur_byte >> bit_i) & 1; + } + } + return result; +} + +namespace TestAddrmanTests { + + TEST(TestAddrmanTests, display_constants) { + + // Not actually the test, just used to display constants + GTEST_COUT_COLOR << "ADDRMAN_NEW_BUCKET_COUNT = " << ADDRMAN_NEW_BUCKET_COUNT << std::endl; + GTEST_COUT_COLOR << "ADDRMAN_TRIED_BUCKET_COUNT = " << ADDRMAN_TRIED_BUCKET_COUNT << std::endl; + GTEST_COUT_COLOR << "ADDRMAN_BUCKET_SIZE = " << ADDRMAN_BUCKET_SIZE << std::endl; + + } + + TEST(TestAddrmanTests, addrman_simple) { + + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + CNetAddr source = CNetAddr("252.2.2.2"); + + // Test 1: Does Addrman respond correctly when empty. + ASSERT_TRUE(addrman.size() == 0); + CAddrInfo addr_null = addrman.Select(); + ASSERT_TRUE(addr_null.ToString() == "[::]:0"); + + // Test 2: Does Addrman::Add work as expected. + CService addr1 = CService("250.1.1.1", 8333); + addrman.Add(CAddress(addr1, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 1); + CAddrInfo addr_ret1 = addrman.Select(); + ASSERT_TRUE(addr_ret1.ToString() == "250.1.1.1:8333"); + + // Test 3: Does IP address deduplication work correctly. + // Expected dup IP should not be added. + CService addr1_dup = CService("250.1.1.1", 8333); + addrman.Add(CAddress(addr1_dup, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 1); + + // Test 5: New table has one addr and we add a diff addr we should + // have two addrs. + CService addr2 = CService("250.1.1.2", 8333); + addrman.Add(CAddress(addr2, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 2); + + // Test 6: AddrMan::Clear() should empty the new table. + addrman.Clear(); + ASSERT_TRUE(addrman.size() == 0); + CAddrInfo addr_null2 = addrman.Select(); + ASSERT_TRUE(addr_null2.ToString() == "[::]:0"); + + } + + TEST(TestAddrmanTests, addrman_ports) { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + CNetAddr source = CNetAddr("252.2.2.2"); + + ASSERT_TRUE(addrman.size() == 0); + + // Test 7; Addr with same IP but diff port does not replace existing addr. + CService addr1 = CService("250.1.1.1", 8333); + addrman.Add(CAddress(addr1, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 1); + + CService addr1_port = CService("250.1.1.1", 8334); + addrman.Add(CAddress(addr1_port, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 1); + CAddrInfo addr_ret2 = addrman.Select(); + ASSERT_TRUE(addr_ret2.ToString() == "250.1.1.1:8333"); + + // Test 8: Add same IP but diff port to tried table, it doesn't get added. + // Perhaps this is not ideal behavior but it is the current behavior. + addrman.Good(CAddress(addr1_port, NODE_NONE)); + ASSERT_TRUE(addrman.size() == 1); + bool newOnly = true; + CAddrInfo addr_ret3 = addrman.Select(newOnly); + ASSERT_TRUE(addr_ret3.ToString() == "250.1.1.1:8333"); + + } + + TEST(TestAddrmanTests, addrman_select) { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + CNetAddr source = CNetAddr("252.2.2.2"); + + // Test 9: Select from new with 1 addr in new. + CService addr1 = CService("250.1.1.1", 8333); + addrman.Add(CAddress(addr1, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 1); + + bool newOnly = true; + CAddrInfo addr_ret1 = addrman.Select(newOnly); + ASSERT_TRUE(addr_ret1.ToString() == "250.1.1.1:8333"); + + // Test 10: move addr to tried, select from new expected nothing returned. + addrman.Good(CAddress(addr1, NODE_NONE)); + ASSERT_TRUE(addrman.size() == 1); + CAddrInfo addr_ret2 = addrman.Select(newOnly); + ASSERT_TRUE(addr_ret2.ToString() == "[::]:0"); + + CAddrInfo addr_ret3 = addrman.Select(); + ASSERT_TRUE(addr_ret3.ToString() == "250.1.1.1:8333"); + + ASSERT_TRUE(addrman.size() == 1); + + + // Add three addresses to new table. + CService addr2 = CService("250.3.1.1", 8333); + CService addr3 = CService("250.3.2.2", 9999); + CService addr4 = CService("250.3.3.3", 9999); + + addrman.Add(CAddress(addr2, NODE_NONE), CService("250.3.1.1", 8333)); + addrman.Add(CAddress(addr3, NODE_NONE), CService("250.3.1.1", 8333)); + addrman.Add(CAddress(addr4, NODE_NONE), CService("250.4.1.1", 8333)); + + // Add three addresses to tried table. + CService addr5 = CService("250.4.4.4", 8333); + CService addr6 = CService("250.4.5.5", 7777); + CService addr7 = CService("250.4.6.6", 8333); + + addrman.Add(CAddress(addr5, NODE_NONE), CService("250.3.1.1", 8333)); + addrman.Good(CAddress(addr5, NODE_NONE)); + addrman.Add(CAddress(addr6, NODE_NONE), CService("250.3.1.1", 8333)); + addrman.Good(CAddress(addr6, NODE_NONE)); + addrman.Add(CAddress(addr7, NODE_NONE), CService("250.1.1.3", 8333)); + addrman.Good(CAddress(addr7, NODE_NONE)); + + // Test 11: 6 addrs + 1 addr from last test = 7. + ASSERT_TRUE(addrman.size() == 7); + + // Test 12: Select pulls from new and tried regardless of port number. + ASSERT_TRUE(addrman.Select().ToString() == "250.4.6.6:8333"); + ASSERT_TRUE(addrman.Select().ToString() == "250.3.2.2:9999"); + ASSERT_TRUE(addrman.Select().ToString() == "250.3.3.3:9999"); + ASSERT_TRUE(addrman.Select().ToString() == "250.4.4.4:8333"); + + } + + TEST(TestAddrmanTests, addrman_new_collisions) + { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + CNetAddr source = CNetAddr("252.2.2.2"); + + ASSERT_TRUE(addrman.size() == 0); + + for (unsigned int i = 1; i < 18; i++) { + CService addr = CService("250.1.1." + boost::to_string(i)); + addrman.Add(CAddress(addr, NODE_NONE), source); + //Test 13: No collision in new table yet. + ASSERT_TRUE(addrman.size() == i); + } + + //Test 14: new table collision! + CService addr1 = CService("250.1.1.18"); + addrman.Add(CAddress(addr1, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 17); + + CService addr2 = CService("250.1.1.19"); + addrman.Add(CAddress(addr2, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 18); + } + + TEST(TestAddrmanTests, addrman_tried_collisions) + { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + CNetAddr source = CNetAddr("252.2.2.2"); + + ASSERT_TRUE(addrman.size() == 0); + + for (unsigned int i = 1; i < 80; i++) { + CService addr = CService("250.1.1." + boost::to_string(i)); + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(CAddress(addr, NODE_NONE)); + + //Test 15: No collision in tried table yet. + // GTEST_COUT << addrman.size() << std::endl; + ASSERT_TRUE(addrman.size() == i); + } + + //Test 16: tried table collision! + CService addr1 = CService("250.1.1.80"); + addrman.Add(CAddress(addr1, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 79); + + CService addr2 = CService("250.1.1.81"); + addrman.Add(CAddress(addr2, NODE_NONE), source); + ASSERT_TRUE(addrman.size() == 80); + } + + TEST(TestAddrmanTests, addrman_find) + { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + ASSERT_TRUE(addrman.size() == 0); + + CAddress addr1 = CAddress(CService("250.1.2.1", 8333), NODE_NONE); + CAddress addr2 = CAddress(CService("250.1.2.1", 9999), NODE_NONE); + CAddress addr3 = CAddress(CService("251.255.2.1", 8333), NODE_NONE); + + CNetAddr source1 = CNetAddr("250.1.2.1"); + CNetAddr source2 = CNetAddr("250.1.2.2"); + + addrman.Add(addr1, source1); + addrman.Add(addr2, source2); + addrman.Add(addr3, source1); + + // Test 17: ensure Find returns an IP matching what we searched on. + CAddrInfo* info1 = addrman.Find(addr1); + ASSERT_TRUE(info1); + if (info1) + ASSERT_TRUE(info1->ToString() == "250.1.2.1:8333"); + + // Test 18; Find does not discriminate by port number. + CAddrInfo* info2 = addrman.Find(addr2); + ASSERT_TRUE(info2); + if (info2) + ASSERT_TRUE(info2->ToString() == info1->ToString()); + + // Test 19: Find returns another IP matching what we searched on. + CAddrInfo* info3 = addrman.Find(addr3); + ASSERT_TRUE(info3); + if (info3) + ASSERT_TRUE(info3->ToString() == "251.255.2.1:8333"); + } + + TEST(TestAddrmanTests, addrman_create) + { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + ASSERT_TRUE(addrman.size() == 0); + + CAddress addr1 = CAddress(CService("250.1.2.1", 8333), NODE_NONE); + CNetAddr source1 = CNetAddr("250.1.2.1"); + + int nId; + CAddrInfo* pinfo = addrman.Create(addr1, source1, &nId); + + // Test 20: The result should be the same as the input addr. + ASSERT_TRUE(pinfo->ToString() == "250.1.2.1:8333"); + + CAddrInfo* info2 = addrman.Find(addr1); + ASSERT_TRUE(info2->ToString() == "250.1.2.1:8333"); + } + + + TEST(TestAddrmanTests, addrman_delete) + { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + ASSERT_TRUE(addrman.size() == 0); + + CAddress addr1 = CAddress(CService("250.1.2.1", 8333), NODE_NONE); + CNetAddr source1 = CNetAddr("250.1.2.1"); + + int nId; + addrman.Create(addr1, source1, &nId); + + // Test 21: Delete should actually delete the addr. + ASSERT_TRUE(addrman.size() == 1); + addrman.Delete(nId); + ASSERT_TRUE(addrman.size() == 0); + CAddrInfo* info2 = addrman.Find(addr1); + ASSERT_TRUE(info2 == NULL); + } + + TEST(TestAddrmanTests, addrman_getaddr) + { + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + // Test 22: Sanity check, GetAddr should never return anything if addrman + // is empty. + ASSERT_TRUE(addrman.size() == 0); + vector vAddr1 = addrman.GetAddr(); + ASSERT_TRUE(vAddr1.size() == 0); + + CAddress addr1 = CAddress(CService("250.250.2.1", 8333), NODE_NONE); + addr1.nTime = GetTime(); // Set time so isTerrible = false + CAddress addr2 = CAddress(CService("250.251.2.2", 9999), NODE_NONE); + addr2.nTime = GetTime(); + CAddress addr3 = CAddress(CService("251.252.2.3", 8333), NODE_NONE); + addr3.nTime = GetTime(); + CAddress addr4 = CAddress(CService("252.253.3.4", 8333), NODE_NONE); + addr4.nTime = GetTime(); + CAddress addr5 = CAddress(CService("252.254.4.5", 8333), NODE_NONE); + addr5.nTime = GetTime(); + CNetAddr source1 = CNetAddr("250.1.2.1"); + CNetAddr source2 = CNetAddr("250.2.3.3"); + + // Test 23: Ensure GetAddr works with new addresses. + addrman.Add(addr1, source1); + addrman.Add(addr2, source2); + addrman.Add(addr3, source1); + addrman.Add(addr4, source2); + addrman.Add(addr5, source1); + + // GetAddr returns 23% of addresses, 23% of 5 is 1 rounded down. + ASSERT_TRUE(addrman.GetAddr().size() == 1); + + // Test 24: Ensure GetAddr works with new and tried addresses. + addrman.Good(CAddress(addr1, NODE_NONE)); + addrman.Good(CAddress(addr2, NODE_NONE)); + ASSERT_TRUE(addrman.GetAddr().size() == 1); + + // Test 25: Ensure GetAddr still returns 23% when addrman has many addrs. + for (unsigned int i = 1; i < (8 * 256); i++) { + int octet1 = i % 256; + int octet2 = (i / 256) % 256; + int octet3 = (i / (256 * 2)) % 256; + string strAddr = boost::to_string(octet1) + "." + boost::to_string(octet2) + "." + boost::to_string(octet3) + ".23"; + CAddress addr = CAddress(CService(strAddr), NODE_NONE); + + // Ensure that for all addrs in addrman, isTerrible == false. + addr.nTime = GetTime(); + addrman.Add(addr, CNetAddr(strAddr)); + if (i % 8 == 0) + addrman.Good(addr); + } + vector vAddr = addrman.GetAddr(); + + size_t percent23 = (addrman.size() * 23) / 100; + ASSERT_TRUE(vAddr.size() == percent23); + ASSERT_TRUE(vAddr.size() == 461); + // (Addrman.size() < number of addresses added) due to address collisons. + ASSERT_TRUE(addrman.size() == 2007); + } + + TEST(TestAddrmanTests, caddrinfo_get_tried_bucket_legacy) + { + CAddrManTest addrman; + + CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); + CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); + + CNetAddr source1 = ResolveIP("250.1.1.1"); + + CAddrInfo info1 = CAddrInfo(addr1, source1); + + uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); + uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); + + std::vector asmap; // use /16 + + ASSERT_EQ(info1.GetTriedBucket(nKey1, asmap), 40); + + // Test: Make sure key actually randomizes bucket placement. A fail on + // this test could be a security issue. + ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap)); + + // Test: Two addresses with same IP but different ports can map to + // different buckets because they have different keys. + CAddrInfo info2 = CAddrInfo(addr2, source1); + + ASSERT_TRUE(info1.GetKey() != info2.GetKey()); + ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap)); + + std::set buckets; + for (int i = 0; i < 255; i++) { + CAddrInfo infoi = CAddrInfo( + CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE), + ResolveIP("250.1.1." + boost::to_string(i))); + int bucket = infoi.GetTriedBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the same /16 prefix should + // never get more than 8 buckets with legacy grouping + ASSERT_EQ(buckets.size(), 8U); + + buckets.clear(); + for (int j = 0; j < 255; j++) { + CAddrInfo infoj = CAddrInfo( + CAddress(ResolveService("250." + boost::to_string(j) + ".1.1"), NODE_NONE), + ResolveIP("250." + boost::to_string(j) + ".1.1")); + int bucket = infoj.GetTriedBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the different /16 prefix should map to more than + // 8 buckets with legacy grouping + ASSERT_EQ(buckets.size(), 160U); + } + + TEST(TestAddrmanTests, caddrinfo_get_new_bucket_legacy) + { + CAddrManTest addrman; + + CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); + CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); + + CNetAddr source1 = ResolveIP("250.1.2.1"); + + CAddrInfo info1 = CAddrInfo(addr1, source1); + + uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); + uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); + + std::vector asmap; // use /16 + + // Test: Make sure the buckets are what we expect + ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), 786); + ASSERT_EQ(info1.GetNewBucket(nKey1, source1, asmap), 786); + + // Test: Make sure key actually randomizes bucket placement. A fail on + // this test could be a security issue. + ASSERT_TRUE(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap)); + + // Test: Ports should not affect bucket placement in the addr + CAddrInfo info2 = CAddrInfo(addr2, source1); + ASSERT_TRUE(info1.GetKey() != info2.GetKey()); + ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap)); + + std::set buckets; + for (int i = 0; i < 255; i++) { + CAddrInfo infoi = CAddrInfo( + CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE), + ResolveIP("250.1.1." + boost::to_string(i))); + int bucket = infoi.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the same group (\16 prefix for IPv4) should + // always map to the same bucket. + ASSERT_EQ(buckets.size(), 1U); + + buckets.clear(); + for (int j = 0; j < 4 * 255; j++) { + CAddrInfo infoj = CAddrInfo(CAddress( + ResolveService( + boost::to_string(250 + (j / 255)) + "." + boost::to_string(j % 256) + ".1.1"), NODE_NONE), + ResolveIP("251.4.1.1")); + int bucket = infoj.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the same source groups should map to NO MORE + // than 64 buckets. + ASSERT_TRUE(buckets.size() <= 64); + + buckets.clear(); + for (int p = 0; p < 255; p++) { + CAddrInfo infoj = CAddrInfo( + CAddress(ResolveService("250.1.1.1"), NODE_NONE), + ResolveIP("250." + boost::to_string(p) + ".1.1")); + int bucket = infoj.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the different source groups should map to MORE + // than 64 buckets. + ASSERT_TRUE(buckets.size() > 64); + + } + + // The following three test cases use asmap_raw[] from asmap.raw file + // We use an artificial minimal mock mapping + // 250.0.0.0/8 AS1000 + // 101.1.0.0/16 AS1 + // 101.2.0.0/16 AS2 + // 101.3.0.0/16 AS3 + // 101.4.0.0/16 AS4 + // 101.5.0.0/16 AS5 + // 101.6.0.0/16 AS6 + // 101.7.0.0/16 AS7 + // 101.8.0.0/16 AS8 + + TEST(TestAddrmanTests, caddrinfo_get_tried_bucket) + { + CAddrManTest addrman; + + CAddress addr1 = CAddress(ResolveService("250.1.1.1", 8333), NODE_NONE); + CAddress addr2 = CAddress(ResolveService("250.1.1.1", 9999), NODE_NONE); + + CNetAddr source1 = ResolveIP("250.1.1.1"); + + + CAddrInfo info1 = CAddrInfo(addr1, source1); + + uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); + uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); + + std::vector asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); + + ASSERT_EQ(info1.GetTriedBucket(nKey1, asmap), 236); + + // Test: Make sure key actually randomizes bucket placement. A fail on + // this test could be a security issue. + ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info1.GetTriedBucket(nKey2, asmap)); + + // Test: Two addresses with same IP but different ports can map to + // different buckets because they have different keys. + CAddrInfo info2 = CAddrInfo(addr2, source1); + + ASSERT_TRUE(info1.GetKey() != info2.GetKey()); + ASSERT_TRUE(info1.GetTriedBucket(nKey1, asmap) != info2.GetTriedBucket(nKey1, asmap)); + + std::set buckets; + for (int j = 0; j < 255; j++) { + CAddrInfo infoj = CAddrInfo( + CAddress(ResolveService("101." + boost::to_string(j) + ".1.1"), NODE_NONE), + ResolveIP("101." + boost::to_string(j) + ".1.1")); + int bucket = infoj.GetTriedBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the different /16 prefix MAY map to more than + // 8 buckets. + ASSERT_TRUE(buckets.size() > 8); + + buckets.clear(); + for (int j = 0; j < 255; j++) { + CAddrInfo infoj = CAddrInfo( + CAddress(ResolveService("250." + boost::to_string(j) + ".1.1"), NODE_NONE), + ResolveIP("250." + boost::to_string(j) + ".1.1")); + int bucket = infoj.GetTriedBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the different /16 prefix MAY NOT map to more than + // 8 buckets. + ASSERT_TRUE(buckets.size() == 8); + } + + TEST(TestAddrmanTests, caddrinfo_get_new_bucket) + { + CAddrManTest addrman; + + CAddress addr1 = CAddress(ResolveService("250.1.2.1", 8333), NODE_NONE); + CAddress addr2 = CAddress(ResolveService("250.1.2.1", 9999), NODE_NONE); + + CNetAddr source1 = ResolveIP("250.1.2.1"); + + CAddrInfo info1 = CAddrInfo(addr1, source1); + + uint256 nKey1 = (uint256)(CHashWriter(SER_GETHASH, 0) << 1).GetHash(); + uint256 nKey2 = (uint256)(CHashWriter(SER_GETHASH, 0) << 2).GetHash(); + + std::vector asmap = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); + + // Test: Make sure the buckets are what we expect + ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), 795); + ASSERT_EQ(info1.GetNewBucket(nKey1, source1, asmap), 795); + + // Test: Make sure key actually randomizes bucket placement. A fail on + // this test could be a security issue. + ASSERT_TRUE(info1.GetNewBucket(nKey1, asmap) != info1.GetNewBucket(nKey2, asmap)); + + // Test: Ports should not affect bucket placement in the addr + CAddrInfo info2 = CAddrInfo(addr2, source1); + ASSERT_TRUE(info1.GetKey() != info2.GetKey()); + ASSERT_EQ(info1.GetNewBucket(nKey1, asmap), info2.GetNewBucket(nKey1, asmap)); + + std::set buckets; + for (int i = 0; i < 255; i++) { + CAddrInfo infoi = CAddrInfo( + CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE), + ResolveIP("250.1.1." + boost::to_string(i))); + int bucket = infoi.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the same /16 prefix + // usually map to the same bucket. + ASSERT_EQ(buckets.size(), 1U); + + buckets.clear(); + for (int j = 0; j < 4 * 255; j++) { + CAddrInfo infoj = CAddrInfo(CAddress( + ResolveService( + boost::to_string(250 + (j / 255)) + "." + boost::to_string(j % 256) + ".1.1"), NODE_NONE), + ResolveIP("251.4.1.1")); + int bucket = infoj.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the same source /16 prefix should not map to more + // than 64 buckets. + ASSERT_TRUE(buckets.size() <= 64); + + buckets.clear(); + for (int p = 0; p < 255; p++) { + CAddrInfo infoj = CAddrInfo( + CAddress(ResolveService("250.1.1.1"), NODE_NONE), + ResolveIP("101." + boost::to_string(p) + ".1.1")); + int bucket = infoj.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the different source /16 prefixes usually map to MORE + // than 1 bucket. + ASSERT_TRUE(buckets.size() > 1); + + buckets.clear(); + for (int p = 0; p < 255; p++) { + CAddrInfo infoj = CAddrInfo( + CAddress(ResolveService("250.1.1.1"), NODE_NONE), + ResolveIP("250." + boost::to_string(p) + ".1.1")); + int bucket = infoj.GetNewBucket(nKey1, asmap); + buckets.insert(bucket); + } + // Test: IP addresses in the different source /16 prefixes sometimes map to NO MORE + // than 1 bucket. + ASSERT_TRUE(buckets.size() == 1); + } + + TEST(TestAddrmanTests, addrman_serialization) + { + std::vector asmap1 = FromBytes(asmap_raw, sizeof(asmap_raw) * 8); + + CAddrManTest addrman_asmap1(true, asmap1); + CAddrManTest addrman_asmap1_dup(true, asmap1); + CAddrManTest addrman_noasmap; + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + CAddress addr = CAddress(ResolveService("250.1.1.1"), NODE_NONE); + CNetAddr default_source; + + addrman_asmap1.Add(addr, default_source); + + stream << addrman_asmap1; + // serizalizing/deserializing addrman with the same asmap + stream >> addrman_asmap1_dup; + + std::pair bucketAndEntry_asmap1 = addrman_asmap1.GetBucketAndEntry(addr); + std::pair bucketAndEntry_asmap1_dup = addrman_asmap1_dup.GetBucketAndEntry(addr); + ASSERT_TRUE(bucketAndEntry_asmap1.second != -1); + ASSERT_TRUE(bucketAndEntry_asmap1_dup.second != -1); + + ASSERT_TRUE(bucketAndEntry_asmap1.first == bucketAndEntry_asmap1_dup.first); + ASSERT_TRUE(bucketAndEntry_asmap1.second == bucketAndEntry_asmap1_dup.second); + + // deserializing asmaped peers.dat to non-asmaped addrman + stream << addrman_asmap1; + stream >> addrman_noasmap; + std::pair bucketAndEntry_noasmap = addrman_noasmap.GetBucketAndEntry(addr); + ASSERT_TRUE(bucketAndEntry_noasmap.second != -1); + ASSERT_TRUE(bucketAndEntry_asmap1.first != bucketAndEntry_noasmap.first); + ASSERT_TRUE(bucketAndEntry_asmap1.second != bucketAndEntry_noasmap.second); + + // deserializing non-asmaped peers.dat to asmaped addrman + addrman_asmap1.Clear(); + addrman_noasmap.Clear(); + addrman_noasmap.Add(addr, default_source); + // GTEST_COUT_COLOR << addr.ToString() << " - " << default_source.ToString() << " - " << addrman_noasmap.size() << std::endl; + // addrman_noasmap.PrintInternals(); + stream << addrman_noasmap; + // std::string strHex = HexStr(stream.begin(), stream.end()); + // GTEST_COUT_COLOR << strHex << std::endl; + + stream >> addrman_asmap1; + std::pair bucketAndEntry_asmap1_deser = addrman_asmap1.GetBucketAndEntry(addr); + ASSERT_TRUE(bucketAndEntry_asmap1_deser.second != -1); + ASSERT_TRUE(bucketAndEntry_asmap1_deser.first != bucketAndEntry_noasmap.first); + ASSERT_TRUE(bucketAndEntry_asmap1_deser.first == bucketAndEntry_asmap1_dup.first); + ASSERT_TRUE(bucketAndEntry_asmap1_deser.second == bucketAndEntry_asmap1_dup.second); + + // used to map to different buckets, now maps to the same bucket. + addrman_asmap1.Clear(); + addrman_noasmap.Clear(); + CAddress addr1 = CAddress(ResolveService("250.1.1.1"), NODE_NONE); + CAddress addr2 = CAddress(ResolveService("250.2.1.1"), NODE_NONE); + addrman_noasmap.Add(addr, default_source); + addrman_noasmap.Add(addr2, default_source); + std::pair bucketAndEntry_noasmap_addr1 = addrman_noasmap.GetBucketAndEntry(addr1); + std::pair bucketAndEntry_noasmap_addr2 = addrman_noasmap.GetBucketAndEntry(addr2); + ASSERT_TRUE(bucketAndEntry_noasmap_addr1.first != bucketAndEntry_noasmap_addr2.first); + ASSERT_TRUE(bucketAndEntry_noasmap_addr1.second != bucketAndEntry_noasmap_addr2.second); + stream << addrman_noasmap; + stream >> addrman_asmap1; + std::pair bucketAndEntry_asmap1_deser_addr1 = addrman_asmap1.GetBucketAndEntry(addr1); + std::pair bucketAndEntry_asmap1_deser_addr2 = addrman_asmap1.GetBucketAndEntry(addr2); + ASSERT_TRUE(bucketAndEntry_asmap1_deser_addr1.first == bucketAndEntry_asmap1_deser_addr2.first); + ASSERT_TRUE(bucketAndEntry_asmap1_deser_addr1.second != bucketAndEntry_asmap1_deser_addr2.second); + } + +} diff --git a/src/test-komodo/test_netbase_tests.cpp b/src/test-komodo/test_netbase_tests.cpp new file mode 100644 index 000000000..563b3a495 --- /dev/null +++ b/src/test-komodo/test_netbase_tests.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +#include "addrman.h" +#include +#include "netbase.h" + +#define GTEST_COUT_NOCOLOR std::cerr << "[ ] [ INFO ] " +namespace testing +{ + namespace internal + { + enum GTestColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW + }; + + extern void ColoredPrintf(GTestColor color, const char* fmt, ...); + } +} +#define PRINTF(...) do { testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, "[ ] "); testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, __VA_ARGS__); } while(0) + +// C++ stream interface +class TestCout : public std::stringstream +{ + public: + ~TestCout() + { + PRINTF("%s",str().c_str()); + } +}; + +#define GTEST_COUT_COLOR TestCout() + +using namespace std; + +static CNetAddr ResolveIP(const std::string& ip) +{ + vector vIPs; + CNetAddr addr; + if (LookupHost(ip.c_str(), vIPs)) { + addr = vIPs[0]; + } else + { + // it was BOOST_CHECK_MESSAGE, but we can't use ASSERT outside a test + GTEST_COUT_COLOR << strprintf("failed to resolve: %s", ip) << std::endl; + } + return addr; +} + +namespace TestNetBaseTests { + + TEST(TestAddrmanTests, netbase_getgroup) { + + std::vector asmap; // use /16 + ASSERT_TRUE(ResolveIP("127.0.0.1").GetGroup(asmap) == std::vector({0})); // Local -> !Routable() + ASSERT_TRUE(ResolveIP("257.0.0.1").GetGroup(asmap) == std::vector({0})); // !Valid -> !Routable() + ASSERT_TRUE(ResolveIP("10.0.0.1").GetGroup(asmap) == std::vector({0})); // RFC1918 -> !Routable() + ASSERT_TRUE(ResolveIP("169.254.1.1").GetGroup(asmap) == std::vector({0})); // RFC3927 -> !Routable() + ASSERT_TRUE(ResolveIP("1.2.3.4").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV4, 1, 2})); // IPv4 + + // std::vector vch = ResolveIP("4.3.2.1").GetGroup(asmap); + // GTEST_COUT_COLOR << boost::to_string((int)vch[0]) << boost::to_string((int)vch[1]) << boost::to_string((int)vch[2]) << std::endl; + + ASSERT_TRUE(ResolveIP("::FFFF:0:102:304").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV4, 1, 2})); // RFC6145 + ASSERT_TRUE(ResolveIP("64:FF9B::102:304").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV4, 1, 2})); // RFC6052 + ASSERT_TRUE(ResolveIP("2002:102:304:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV4, 1, 2})); // RFC3964 + ASSERT_TRUE(ResolveIP("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV4, 1, 2})); // RFC4380 + ASSERT_TRUE(ResolveIP("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup(asmap) == std::vector({(unsigned char)NET_ONION, 239})); // Tor + ASSERT_TRUE(ResolveIP("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV6, 32, 1, 4, 112, 175})); //he.net + ASSERT_TRUE(ResolveIP("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup(asmap) == std::vector({(unsigned char)NET_IPV6, 32, 1, 32, 1})); //IPv6 + + } +} diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index 65a079c31..9a477ef29 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(netbase_networks) BOOST_CHECK(CNetAddr("::1").GetNetwork() == NET_UNROUTABLE); BOOST_CHECK(CNetAddr("8.8.8.8").GetNetwork() == NET_IPV4); BOOST_CHECK(CNetAddr("2001::8888").GetNetwork() == NET_IPV6); - BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_TOR); + BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetNetwork() == NET_ONION); } BOOST_AUTO_TEST_CASE(netbase_properties) @@ -157,7 +157,7 @@ BOOST_AUTO_TEST_CASE(netbase_getgroup) BOOST_CHECK(CNetAddr("64:FF9B::102:304").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC6052 BOOST_CHECK(CNetAddr("2002:102:304:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC3964 BOOST_CHECK(CNetAddr("2001:0:9999:9999:9999:9999:FEFD:FCFB").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV4)(1)(2)); // RFC4380 - BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == boost::assign::list_of((unsigned char)NET_TOR)(239)); // Tor + BOOST_CHECK(CNetAddr("FD87:D87E:EB43:edb1:8e4:3588:e546:35ca").GetGroup() == boost::assign::list_of((unsigned char)NET_ONION)(239)); // Tor BOOST_CHECK(CNetAddr("2001:470:abcd:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(4)(112)(175)); //he.net BOOST_CHECK(CNetAddr("2001:2001:9999:9999:9999:9999:9999:9999").GetGroup() == boost::assign::list_of((unsigned char)NET_IPV6)(32)(1)(32)(1)); //IPv6 } diff --git a/src/torcontrol.cpp b/src/torcontrol.cpp index a2efb9fa1..c7000a07a 100644 --- a/src/torcontrol.cpp +++ b/src/torcontrol.cpp @@ -530,8 +530,8 @@ void TorController::auth_cb(TorControlConnection& conn, const TorControlReply& r // if -onion isn't set to something else. if (GetArg("-onion", "") == "") { proxyType addrOnion = proxyType(CService("127.0.0.1", 9050), true); - SetProxy(NET_TOR, addrOnion); - SetLimited(NET_TOR, false); + SetProxy(NET_ONION, addrOnion); + SetLimited(NET_ONION, false); } // Finally - now create the service diff --git a/src/util/asmap.cpp b/src/util/asmap.cpp new file mode 100644 index 000000000..bd77d7421 --- /dev/null +++ b/src/util/asmap.cpp @@ -0,0 +1,187 @@ +// Copyright (c) 2019-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include + +namespace { + +constexpr uint32_t INVALID = 0xFFFFFFFF; + +uint32_t DecodeBits(std::vector::const_iterator& bitpos, const std::vector::const_iterator& endpos, uint8_t minval, const std::vector &bit_sizes) +{ + uint32_t val = minval; + bool bit; + for (std::vector::const_iterator bit_sizes_it = bit_sizes.begin(); + bit_sizes_it != bit_sizes.end(); ++bit_sizes_it) { + if (bit_sizes_it + 1 != bit_sizes.end()) { + if (bitpos == endpos) break; + bit = *bitpos; + bitpos++; + } else { + bit = 0; + } + if (bit) { + val += (1 << *bit_sizes_it); + } else { + for (int b = 0; b < *bit_sizes_it; b++) { + if (bitpos == endpos) return INVALID; // Reached EOF in mantissa + bit = *bitpos; + bitpos++; + val += bit << (*bit_sizes_it - 1 - b); + } + return val; + } + } + return INVALID; // Reached EOF in exponent +} + +enum class Instruction : uint32_t +{ + RETURN = 0, + JUMP = 1, + MATCH = 2, + DEFAULT = 3, +}; + +const std::vector TYPE_BIT_SIZES{0, 0, 1}; +Instruction DecodeType(std::vector::const_iterator& bitpos, const std::vector::const_iterator& endpos) +{ + return Instruction(DecodeBits(bitpos, endpos, 0, TYPE_BIT_SIZES)); +} + +const std::vector ASN_BIT_SIZES{15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; +uint32_t DecodeASN(std::vector::const_iterator& bitpos, const std::vector::const_iterator& endpos) +{ + return DecodeBits(bitpos, endpos, 1, ASN_BIT_SIZES); +} + + +const std::vector MATCH_BIT_SIZES{1, 2, 3, 4, 5, 6, 7, 8}; +uint32_t DecodeMatch(std::vector::const_iterator& bitpos, const std::vector::const_iterator& endpos) +{ + return DecodeBits(bitpos, endpos, 2, MATCH_BIT_SIZES); +} + + +const std::vector JUMP_BIT_SIZES{5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}; +uint32_t DecodeJump(std::vector::const_iterator& bitpos, const std::vector::const_iterator& endpos) +{ + return DecodeBits(bitpos, endpos, 17, JUMP_BIT_SIZES); +} + +} + +uint32_t Interpret(const std::vector &asmap, const std::vector &ip) +{ + std::vector::const_iterator pos = asmap.begin(); + const std::vector::const_iterator endpos = asmap.end(); + uint8_t bits = ip.size(); + uint32_t default_asn = 0; + uint32_t jump, match, matchlen; + Instruction opcode; + while (pos != endpos) { + opcode = DecodeType(pos, endpos); + if (opcode == Instruction::RETURN) { + default_asn = DecodeASN(pos, endpos); + if (default_asn == INVALID) break; // ASN straddles EOF + return default_asn; + } else if (opcode == Instruction::JUMP) { + jump = DecodeJump(pos, endpos); + if (jump == INVALID) break; // Jump offset straddles EOF + if (bits == 0) break; // No input bits left + if (pos + jump < pos) break; // overflow + if (pos + jump >= endpos) break; // Jumping past EOF + if (ip[ip.size() - bits]) { + pos += jump; + } + bits--; + } else if (opcode == Instruction::MATCH) { + match = DecodeMatch(pos, endpos); + if (match == INVALID) break; // Match bits straddle EOF + matchlen = CountBits(match) - 1; + if (bits < matchlen) break; // Not enough input bits + for (uint32_t bit = 0; bit < matchlen; bit++) { + if ((ip[ip.size() - bits]) != ((match >> (matchlen - 1 - bit)) & 1)) { + return default_asn; + } + bits--; + } + } else if (opcode == Instruction::DEFAULT) { + default_asn = DecodeASN(pos, endpos); + if (default_asn == INVALID) break; // ASN straddles EOF + } else { + break; // Instruction straddles EOF + } + } + assert(false); // Reached EOF without RETURN, or aborted (see any of the breaks above) - should have been caught by SanityCheckASMap below + return 0; // 0 is not a valid ASN +} + +bool SanityCheckASMap(const std::vector& asmap, int bits) +{ + const std::vector::const_iterator begin = asmap.begin(), endpos = asmap.end(); + std::vector::const_iterator pos = begin; + std::vector> jumps; // All future positions we may jump to (bit offset in asmap -> bits to consume left) + jumps.reserve(bits); + Instruction prevopcode = Instruction::JUMP; + bool had_incomplete_match = false; + while (pos != endpos) { + uint32_t offset = pos - begin; + if (!jumps.empty() && offset >= jumps.back().first) return false; // There was a jump into the middle of the previous instruction + Instruction opcode = DecodeType(pos, endpos); + if (opcode == Instruction::RETURN) { + if (prevopcode == Instruction::DEFAULT) return false; // There should not be any RETURN immediately after a DEFAULT (could be combined into just RETURN) + uint32_t asn = DecodeASN(pos, endpos); + if (asn == INVALID) return false; // ASN straddles EOF + if (jumps.empty()) { + // Nothing to execute anymore + if (endpos - pos > 7) return false; // Excessive padding + while (pos != endpos) { + if (*pos) return false; // Nonzero padding bit + ++pos; + } + return true; // Sanely reached EOF + } else { + // Continue by pretending we jumped to the next instruction + offset = pos - begin; + if (offset != jumps.back().first) return false; // Unreachable code + bits = jumps.back().second; // Restore the number of bits we would have had left after this jump + jumps.pop_back(); + prevopcode = Instruction::JUMP; + } + } else if (opcode == Instruction::JUMP) { + uint32_t jump = DecodeJump(pos, endpos); + if (jump == INVALID) return false; // Jump offset straddles EOF + if (pos + jump < pos) return false; // overflow + if (pos + jump > endpos) return false; // Jump out of range + if (bits == 0) return false; // Consuming bits past the end of the input + --bits; + uint32_t jump_offset = pos - begin + jump; + if (!jumps.empty() && jump_offset >= jumps.back().first) return false; // Intersecting jumps + jumps.emplace_back(jump_offset, bits); + prevopcode = Instruction::JUMP; + } else if (opcode == Instruction::MATCH) { + uint32_t match = DecodeMatch(pos, endpos); + if (match == INVALID) return false; // Match bits straddle EOF + int matchlen = CountBits(match) - 1; + if (prevopcode != Instruction::MATCH) had_incomplete_match = false; + if (matchlen < 8 && had_incomplete_match) return false; // Within a sequence of matches only at most one should be incomplete + had_incomplete_match = (matchlen < 8); + if (bits < matchlen) return false; // Consuming bits past the end of the input + bits -= matchlen; + prevopcode = Instruction::MATCH; + } else if (opcode == Instruction::DEFAULT) { + if (prevopcode == Instruction::DEFAULT) return false; // There should not be two successive DEFAULTs (they could be combined into one) + uint32_t asn = DecodeASN(pos, endpos); + if (asn == INVALID) return false; // ASN straddles EOF + prevopcode = Instruction::DEFAULT; + } else { + return false; // Instruction straddles EOF + } + } + return false; // Reached EOF without RETURN instruction +} diff --git a/src/util/asmap.h b/src/util/asmap.h new file mode 100644 index 000000000..b31e639bb --- /dev/null +++ b/src/util/asmap.h @@ -0,0 +1,15 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_ASMAP_H +#define BITCOIN_UTIL_ASMAP_H + +#include +#include + +uint32_t Interpret(const std::vector &asmap, const std::vector &ip); + +bool SanityCheckASMap(const std::vector& asmap, int bits); + +#endif // BITCOIN_UTIL_ASMAP_H