diff --git a/doc/release-notes.md b/doc/release-notes.md index 0a8b7c794cc7..bef2af2b2b0f 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -369,6 +369,30 @@ Low-level RPC changes now the empty string `""` instead of `"wallet.dat"`. If PIVX is started with any `-wallet=` options, there is no change in behavior, and the name of any wallet is just its `` string. + +### New RPC Commands + +* `getnodeaddresses` + ``` + getnodeaddresses ( count "network" ) + + Return known addresses which can potentially be used to find new nodes in the network + + Arguments: + 1. count (numeric, optional) The maximum number of addresses to return. Specify 0 to return all known addresses. + 2. "network" (string, optional) Return only addresses of the specified network. Can be one of: ipv4, ipv6, onion. + Result: + [ + { + "time": ttt, (numeric) Timestamp in seconds since epoch (Jan 1 1970 GMT) when the node was last seen + "services": n, (numeric) The services offered by the node + "address": "host", (string) The address of the node + "port": n, (numeric) The port number of the node + "network": "xxxx" (string) The network (ipv4, ipv6, onion) the node connected through + } + ,... + ] + ``` Database cache memory increased -------------------------------- diff --git a/src/addrman.cpp b/src/addrman.cpp index de1d81b8e0b0..f233f14b2295 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -7,8 +7,10 @@ #include "addrman.h" #include "hash.h" -#include "streams.h" #include "logging.h" +#include "netaddress.h" +#include "optional.h" +#include "streams.h" #include "serialize.h" @@ -481,13 +483,18 @@ int CAddrMan::Check_() } #endif -void CAddrMan::GetAddr_(std::vector& vAddr) +void CAddrMan::GetAddr_(std::vector& vAddr, size_t max_addresses, size_t max_pct, Optional network) { - unsigned int nNodes = ADDRMAN_GETADDR_MAX_PCT * vRandom.size() / 100; - if (nNodes > ADDRMAN_GETADDR_MAX) - nNodes = ADDRMAN_GETADDR_MAX; + size_t nNodes = vRandom.size(); + if (max_pct != 0) { + nNodes = max_pct * nNodes / 100; + } + if (max_addresses != 0) { + nNodes = std::min(nNodes, max_addresses); + } // gather a list of random nodes, skipping those of low quality + const int64_t now{GetAdjustedTime()}; for (unsigned int n = 0; n < vRandom.size(); n++) { if (vAddr.size() >= nNodes) break; @@ -497,8 +504,14 @@ void CAddrMan::GetAddr_(std::vector& vAddr) assert(mapInfo.count(vRandom[n]) == 1); const CAddrInfo& ai = mapInfo[vRandom[n]]; - if (!ai.IsTerrible()) - vAddr.push_back(ai); + + // Filter by network (optional) + if (network != nullopt && ai.GetNetClass() != network) continue; + + // Filter for quality + if (ai.IsTerrible(now)) continue; + + vAddr.push_back(ai); } } diff --git a/src/addrman.h b/src/addrman.h index 88b04bce6477..251260e248bc 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -13,6 +13,7 @@ #include "clientversion.h" #include "netaddress.h" +#include "optional.h" #include "protocol.h" #include "random.h" #include "sync.h" @@ -159,12 +160,6 @@ class CAddrInfo : public CAddress //! how recent a successful connection should be before we allow an address to be evicted from tried #define ADDRMAN_REPLACEMENT_HOURS 4 -//! 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 - //! Convenience #define ADDRMAN_TRIED_BUCKET_COUNT (1 << ADDRMAN_TRIED_BUCKET_COUNT_LOG2) #define ADDRMAN_NEW_BUCKET_COUNT (1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2) @@ -288,8 +283,15 @@ friend class CAddrManTest; int Check_() EXCLUSIVE_LOCKS_REQUIRED(cs); #endif - //! Select several addresses at once. - void GetAddr_(std::vector& vAddr) EXCLUSIVE_LOCKS_REQUIRED(cs); + /** + * Return all or many randomly selected addresses, optionally by network. + * + * @param[out] vAddr Vector of randomly selected addresses from vRandom. + * @param[in] max_addresses Maximum number of addresses to return (0 = all). + * @param[in] max_pct Maximum percentage of addresses to return (0 = all). + * @param[in] network Select only addresses of this network (nullopt = all). + */ + void GetAddr_(std::vector& vAddr, size_t max_addresses, size_t max_pct, Optional network) EXCLUSIVE_LOCKS_REQUIRED(cs); //! Mark an entry as currently-connected-to. void Connected_(const CService& addr, int64_t nTime) EXCLUSIVE_LOCKS_REQUIRED(cs); @@ -702,14 +704,20 @@ friend class CAddrManTest; return addrRet; } - //! Return a bunch of addresses, selected at random. - std::vector GetAddr() + /** + * Return all or many randomly selected addresses, optionally by network. + * + * @param[in] max_addresses Maximum number of addresses to return (0 = all). + * @param[in] max_pct Maximum percentage of addresses to return (0 = all). + * @param[in] network Select only addresses of this network (nullopt = all). + */ + std::vector GetAddr(size_t max_addresses, size_t max_pct, Optional network) { Check(); std::vector vAddr; { LOCK(cs); - GetAddr_(vAddr); + GetAddr_(vAddr, max_addresses, max_pct, network); } Check(); return vAddr; diff --git a/src/net.cpp b/src/net.cpp index b94233af0fe5..1395e3160c9c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -16,8 +16,10 @@ #include "crypto/common.h" #include "crypto/sha256.h" #include "guiinterface.h" +#include "netaddress.h" #include "netbase.h" #include "netmessagemaker.h" +#include "optional.h" #include "primitives/transaction.h" #include "scheduler.h" #include "validation.h" @@ -2137,14 +2139,14 @@ void CConnman::AddNewAddress(const CAddress& addr, const CAddress& addrFrom, int addrman.Add(addr, addrFrom, nTimePenalty); } -void CConnman::AddNewAddresses(const std::vector& vAddr, const CAddress& addrFrom, int64_t nTimePenalty) +bool CConnman::AddNewAddresses(const std::vector& vAddr, const CAddress& addrFrom, int64_t nTimePenalty) { - addrman.Add(vAddr, addrFrom, nTimePenalty); + return addrman.Add(vAddr, addrFrom, nTimePenalty); } -std::vector CConnman::GetAddresses() +std::vector CConnman::GetAddresses(size_t max_addresses, size_t max_pct, Optional network) { - return addrman.GetAddr(); + return addrman.GetAddr(max_addresses, max_pct, network); } bool CConnman::AddNode(const std::string& strNode) diff --git a/src/net.h b/src/net.h index 549ef3aba159..255024d974bd 100644 --- a/src/net.h +++ b/src/net.h @@ -49,8 +49,8 @@ static const int FEELER_INTERVAL = 120; static const unsigned int MAX_INV_SZ = 50000; /** The maximum number of entries in a locator */ static const unsigned int MAX_LOCATOR_SZ = 101; -/** The maximum number of new addresses to accumulate before announcing. */ -static const unsigned int MAX_ADDR_TO_SEND = 1000; +/** The maximum number of addresses from our addrman to return in response to a getaddr message. */ +static constexpr size_t MAX_ADDR_TO_SEND = 1000; /** Maximum length of incoming protocol messages (no message over 2 MiB is currently acceptable). */ static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 2 * 1024 * 1024; /** Maximum length of strSubVer in `version` message */ @@ -216,8 +216,15 @@ class CConnman void SetServices(const CService &addr, ServiceFlags nServices); void MarkAddressGood(const CAddress& addr); void AddNewAddress(const CAddress& addr, const CAddress& addrFrom, int64_t nTimePenalty = 0); - void AddNewAddresses(const std::vector& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); - std::vector GetAddresses(); + bool AddNewAddresses(const std::vector& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0); + /** + * Return all or many randomly selected addresses, optionally by network. + * + * @param[in] max_addresses Maximum number of addresses to return (0 = all). + * @param[in] max_pct Maximum percentage of addresses to return (0 = all). + * @param[in] network Select only addresses of this network (nullopt = all). + */ + std::vector GetAddresses(size_t max_addresses, size_t max_pct, Optional network); // Denial-of-service detection/prevention // The idea is to detect peers that are behaving diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 73566e18e28d..b7df8b66431e 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -24,6 +24,9 @@ int64_t nTimeBestReceived = 0; // Used only to inform the wallet of when we las static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; // SHA256("main address relay")[0:8] +/** the maximum percentage of addresses from our addrman to return in response to a getaddr message. */ +static constexpr size_t MAX_PCT_ADDR_TO_SEND = 23; + struct IteratorComparator { template @@ -1746,7 +1749,7 @@ bool static ProcessMessage(CNode* pfrom, std::string strCommand, CDataStream& vR // getaddr message mitigates the attack. else if ((strCommand == NetMsgType::GETADDR) && (pfrom->fInbound)) { pfrom->vAddrToSend.clear(); - std::vector vAddr = connman->GetAddresses(); + std::vector vAddr = connman->GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND, /* network */ nullopt); FastRandomContext insecure_rand; for (const CAddress& addr : vAddr) pfrom->PushAddress(addr, insecure_rand); diff --git a/src/netaddress.cpp b/src/netaddress.cpp index a930f5d2318f..6e61557c6dcd 100644 --- a/src/netaddress.cpp +++ b/src/netaddress.cpp @@ -636,7 +636,7 @@ uint32_t CNetAddr::GetLinkedIPv4() const assert(false); } -uint32_t CNetAddr::GetNetClass() const +Network CNetAddr::GetNetClass() const { // Make sure that if we return NET_IPV6, then IsIPv6() is true. The callers expect that. diff --git a/src/netaddress.h b/src/netaddress.h index 7c5fbd26166e..373a99d9a018 100644 --- a/src/netaddress.h +++ b/src/netaddress.h @@ -188,7 +188,7 @@ class CNetAddr std::string ToStringIP() const; uint64_t GetHash() const; bool GetInAddr(struct in_addr* pipv4Addr) const; - uint32_t GetNetClass() const; + Network 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; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 83bb4f5c593c..a989a4a1d87d 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -28,6 +28,7 @@ class CRPCConvertParam static const CRPCConvertParam vRPCConvertParams[] = { { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, + { "addpeeraddress", 1, "port" }, { "autocombinerewards", 0, "enable" }, { "autocombinerewards", 1, "threshold" }, { "createmultisig", 0, "nrequired" }, @@ -62,6 +63,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { { "getshieldbalance", 2, "include_watchonly" }, { "getnetworkhashps", 0, "nblocks" }, { "getnetworkhashps", 1, "height" }, + { "getnodeaddresses", 0, "count" }, { "getrawmempool", 0, "verbose" }, { "getrawtransaction", 1, "verbose" }, { "getreceivedbyaddress", 1, "minconf" }, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f392bf3c3ccc..36eb4a4f7302 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -10,6 +10,7 @@ #include "net.h" #include "netbase.h" #include "net_processing.h" +#include "optional.h" #include "protocol.h" #include "sync.h" #include "timedata.h" @@ -559,6 +560,111 @@ UniValue clearbanned(const JSONRPCRequest& request) return NullUniValue; } +static UniValue getnodeaddresses(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() > 2) { + throw std::runtime_error( + "getnodeaddresses ( count \"network\" )\n" + "\nReturn known addresses which can potentially be used to find new nodes in the network\n" + + "\nArguments:\n" + "1. count (numeric, optional) The maximum number of addresses to return. Specify 0 to return all known addresses.\n" + "2. \"network\" (string, optional) Return only addresses of the specified network. Can be one of: ipv4, ipv6, onion." + + "\nResult:\n" + "[\n" + " {\n" + " \"time\": ttt, (numeric) Timestamp in seconds since epoch (Jan 1 1970 GMT) when the node was last seen\n" + " \"services\": n, (numeric) The services offered by the node\n" + " \"address\": \"host\", (string) The address of the node\n" + " \"port\": n, (numeric) The port number of the node\n" + " \"network\": \"xxxx\" (string) The network (ipv4, ipv6, onion) the node connected through\n" + " }\n" + " ,...\n" + "]\n" + + "\nExamples:\n" + + HelpExampleCli("getnodeaddresses", "8") + + HelpExampleCli("getnodeaddresses", "4 \"ipv4\"") + + HelpExampleRpc("getnodeaddresses", "8") + + HelpExampleRpc("getnodeaddresses", "4 \"ipv4\"") + ); + } + if (!g_connman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + + const int count{request.params[0].isNull() ? 1 : request.params[0].get_int()}; + if (count < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Address count out of range"); + + const Optional network{request.params[1].isNull() ? nullopt : Optional{ParseNetwork(request.params[1].get_str())}}; + if (network == NET_UNROUTABLE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Network not recognized: %s", request.params[1].get_str())); + } + + // returns a shuffled list of CAddress + const std::vector vAddr{g_connman->GetAddresses(count, /* max_pct */ 0, network)}; + UniValue ret(UniValue::VARR); + + for (const CAddress& addr : vAddr) { + UniValue obj(UniValue::VOBJ); + obj.pushKV("time", (int)addr.nTime); + obj.pushKV("services", (uint64_t)addr.nServices); + obj.pushKV("address", addr.ToStringIP()); + obj.pushKV("port", addr.GetPort()); + obj.pushKV("network", GetNetworkName(addr.GetNetClass())); + ret.push_back(obj); + } + return ret; +} + +static UniValue addpeeraddress(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 2) { + throw std::runtime_error( + "addpeeraddress \"address\" port\n" + "\nAdd the address of a potential peer to the address manager. This RPC is for testing only.\n" + + "\nArguments\n" + "1. \"address\" (string, required) The IP address of the peer\n" + "2. port (numeric, required) The port of the peer\n" + + "\nResult:\n" + "{\n" + " \"success\": true|false (boolean) Whether the peer address was successfully added to the address manager\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 51472") + + HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 51472")); + } + if (!g_connman) { + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } + + UniValue obj(UniValue::VOBJ); + + std::string addr_string = request.params[0].get_str(); + uint16_t port = request.params[1].get_int(); + + CNetAddr net_addr; + if (!LookupHost(addr_string, net_addr, false)) { + obj.pushKV("success", false); + return obj; + } + CAddress address = CAddress({net_addr, port}, ServiceFlags(NODE_NETWORK)); + address.nTime = GetAdjustedTime(); + // The source address is set equal to the address. This is equivalent to the peer + // announcing itself. + if (!g_connman->AddNewAddresses({address}, address)) { + obj.pushKV("success", false); + return obj; + } + + obj.pushKV("success", true); + return obj; +} + static const CRPCCommand commands[] = { // category name actor (function) okSafe argNames // --------------------- ------------------------ ----------------------- ------ -------- @@ -569,10 +675,14 @@ static const CRPCCommand commands[] = { "network", "getconnectioncount", &getconnectioncount, true, {} }, { "network", "getnettotals", &getnettotals, true, {} }, { "network", "getnetworkinfo", &getnetworkinfo, true, {} }, + { "network", "getnodeaddresses", &getnodeaddresses, true, {"count"} }, { "network", "getpeerinfo", &getpeerinfo, true, {} }, { "network", "listbanned", &listbanned, true, {} }, { "network", "ping", &ping, true, {} }, { "network", "setban", &setban, true, {"subnet", "command", "bantime", "absolute"} }, + + // Hidden, for testing only + { "hidden", "addpeeraddress", &addpeeraddress, true, {"address", "port"} }, }; void RegisterNetRPCCommands(CRPCTable &tableRPC) diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index ba3eb77b513e..aa520473ed35 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -2,18 +2,22 @@ // Copyright (c) 2019 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "addrman.h" + #include "test/test_pivx.h" -#include -#include -#include // for ReadLE64 -#include "util/asmap.h" #include "test/data/asmap.raw.h" +#include "addrman.h" +#include "crypto/common.h" // for ReadLE64 #include "hash.h" #include "netbase.h" +#include "optional.h" +#include "util/asmap.h" #include "random.h" +#include + +#include + class CAddrManTest : public CAddrMan { private: @@ -385,7 +389,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) // Test: Sanity check, GetAddr should never return anything if addrman // is empty. BOOST_CHECK(addrman.size() == 0); - std::vector vAddr1 = addrman.GetAddr(); + std::vector vAddr1 = addrman.GetAddr(/* max_addresses */ 0, /* max_pct */0, /* network */ nullopt); BOOST_CHECK(vAddr1.size() == 0); CAddress addr1 = CAddress(ResolveService("250.250.2.1", 8333), NODE_NONE); @@ -408,13 +412,15 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) addrman.Add(addr4, source2); addrman.Add(addr5, source1); - // GetAddr returns 23% of addresses, 23% of 5 is 1 rounded down. - BOOST_CHECK(addrman.GetAddr().size() == 1); + BOOST_CHECK_EQUAL(addrman.GetAddr(/* max_addresses */ 0, /* max_pct */ 0, /* network */ nullopt).size(), 5U); + // Net processing asks for 23% of addresses. 23% of 5 is 1 rounded down. + BOOST_CHECK_EQUAL(addrman.GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ nullopt).size(), 1U); // Test 24: Ensure GetAddr works with new and tried addresses. addrman.Good(CAddress(addr1, NODE_NONE)); addrman.Good(CAddress(addr2, NODE_NONE)); - BOOST_CHECK(addrman.GetAddr().size() == 1); + BOOST_CHECK_EQUAL(addrman.GetAddr(/* max_addresses */ 0, /* max_pct */ 0, /* network */ nullopt).size(), 5U); + BOOST_CHECK_EQUAL(addrman.GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ nullopt).size(), 1U); // Test 25: Ensure GetAddr still returns 23% when addrman has many addrs. for (unsigned int i = 1; i < (8 * 256); i++) { @@ -430,7 +436,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) if (i % 8 == 0) addrman.Good(addr); } - std::vector vAddr = addrman.GetAddr(); + std::vector vAddr = addrman.GetAddr(/* max_addresses */ 2500, /* max_pct */ 23, /* network */ nullopt); size_t percent23 = (addrman.size() * 23) / 100; BOOST_CHECK(vAddr.size() == percent23); diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index 49d041dca5f0..7d715bee9413 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -7,10 +7,13 @@ Tests correspond to code in rpc/net.cpp. """ +from test_framework.messages import NODE_NETWORK +from test_framework.mininode import P2PInterface from test_framework.test_framework import PivxTestFramework from test_framework.util import ( assert_equal, assert_greater_than_or_equal, + assert_greater_than, assert_raises_rpc_error, connect_nodes, disconnect_nodes, @@ -32,7 +35,8 @@ def run_test(self): self._test_getnettotals() self._test_getnetworkinginfo() self._test_getaddednodeinfo() - #self._test_getpeerinfo() + # self._test_getpeerinfo() + self._test_getnodeaddresses() def _test_connection_count(self): assert_equal(self.nodes[0].getconnectioncount(), 2) @@ -93,5 +97,56 @@ def _test_getpeerinfo(self): assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr']) assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr']) + def _test_getnodeaddresses(self): + self.nodes[0].add_p2p_connection(P2PInterface()) + services = NODE_NETWORK + + # Add an IPv6 address to the address manager. + ipv6_addr = "1233:3432:2434:2343:3234:2345:6546:4534" + self.nodes[0].addpeeraddress(ipv6_addr, 51472) + + # Add 10,000 IPv4 addresses to the address manager. Due to the way bucket + # and bucket positions are calculated, some of these addresses will collide. + imported_addrs = [] + for i in range(10000): + first_octet = i >> 8 + second_octet = i % 256 + a = f"{first_octet}.{second_octet}.1.1" + imported_addrs.append(a) + self.nodes[0].addpeeraddress(a, 51472) + + # Fetch the addresses via the RPC and test the results. + assert_equal(len(self.nodes[0].getnodeaddresses()), 1) # default count is 1 + assert_equal(len(self.nodes[0].getnodeaddresses(2)), 2) + assert_equal(len(self.nodes[0].getnodeaddresses(8, "ipv4")), 8) + + # Maximum possible addresses in AddrMan is 10000. The actual number will + # usually be less due to bucket and bucket position collisions. + node_addresses = self.nodes[0].getnodeaddresses(0, "ipv4") + assert_greater_than(len(node_addresses), 5000) + assert_greater_than(10000, len(node_addresses)) + for a in node_addresses: + assert_greater_than(a["time"], 1527811200) # 1st June 2018 + assert_equal(a["services"], services) + assert a["address"] in imported_addrs + assert_equal(a["port"], 51472) + assert_equal(a["network"], "ipv4") + + # Test the IPv6 address. + res = self.nodes[0].getnodeaddresses(0, "ipv6") + assert_equal(len(res), 1) + assert_equal(res[0]["address"], ipv6_addr) + assert_equal(res[0]["network"], "ipv6") + assert_equal(res[0]["port"], 51472) + assert_equal(res[0]["services"], services) + + # Test for the absence of onion addresses. + for network in ["onion"]: + assert_equal(self.nodes[0].getnodeaddresses(0, network), []) + + # Test invalid arguments. + assert_raises_rpc_error(-8, "Address count out of range", self.nodes[0].getnodeaddresses, -1) + assert_raises_rpc_error(-8, "Network not recognized: Foo", self.nodes[0].getnodeaddresses, 1, "Foo") + if __name__ == '__main__': NetTest().main()