Skip to content

Commit

Permalink
rpc: getaddrmaninfo verbose for addrman entries
Browse files Browse the repository at this point in the history
Exposing address manager table entries in a hidden RPC allows to introspect
addrman tables in tests and during development.

Also ran clang-format-diff.py on my diff to reduce the extra indentation
from the getaddrmaninfo RPC implementation.

Can be reviewed with --ignore-all-space

Includes a small follow-up from bitcoin#27511 too which adds `all_networks` to
the list of possible "network" key's in the JSON response.
See bitcoin#27511 (comment)
  • Loading branch information
0xB10C committed Sep 27, 2023
1 parent 719cb30 commit fbc29f2
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 43 deletions.
33 changes: 33 additions & 0 deletions src/addrman.cpp
Expand Up @@ -838,6 +838,25 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct
return addresses;
}

std::vector<std::tuple<int, int, AddrInfo>> AddrManImpl::GetEntries_(bool from_tried) const
{
AssertLockHeld(cs);

const int bucket_count = from_tried ? ADDRMAN_TRIED_BUCKET_COUNT : ADDRMAN_NEW_BUCKET_COUNT;
std::vector<std::tuple<int, int, AddrInfo>> infos;
for (int bucket = 0; bucket < bucket_count; ++bucket) {
for (int position = 0; position < ADDRMAN_BUCKET_SIZE; position++) {
int id = GetEntry(from_tried, bucket, position);
if (id >= 0) {
AddrInfo info = mapInfo.at(id);
infos.push_back({bucket, position, info});
}
}
}

return infos;
}

void AddrManImpl::Connected_(const CService& addr, NodeSeconds time)
{
AssertLockHeld(cs);
Expand Down Expand Up @@ -1199,6 +1218,15 @@ std::vector<CAddress> AddrManImpl::GetAddr(size_t max_addresses, size_t max_pct,
return addresses;
}

std::vector<std::tuple<int, int, AddrInfo>> AddrManImpl::GetEntries(bool from_tried) const
{
LOCK(cs);
Check();
auto addrInfos = GetEntries_(from_tried);
Check();
return addrInfos;
}

void AddrManImpl::Connected(const CService& addr, NodeSeconds time)
{
LOCK(cs);
Expand Down Expand Up @@ -1289,6 +1317,11 @@ std::vector<CAddress> AddrMan::GetAddr(size_t max_addresses, size_t max_pct, std
return m_impl->GetAddr(max_addresses, max_pct, network);
}

std::vector<std::tuple<int, int, AddrInfo>> AddrMan::GetEntries(bool use_tried) const
{
return m_impl->GetEntries(use_tried);
}

void AddrMan::Connected(const CService& addr, NodeSeconds time)
{
m_impl->Connected(addr, time);
Expand Down
10 changes: 10 additions & 0 deletions src/addrman.h
Expand Up @@ -25,6 +25,7 @@ class InvalidAddrManVersionError : public std::ios_base::failure
};

class AddrManImpl;
class AddrInfo;

/** Default for -checkaddrman */
static constexpr int32_t DEFAULT_ADDRMAN_CONSISTENCY_CHECKS{0};
Expand Down Expand Up @@ -168,6 +169,15 @@ class AddrMan
*/
std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const;

/**
* Return information, including bucket and position, about all entries of a addrman table.
*
* @param[in] from_tried If tried table entries should be returned. Otherwise, new table entries are returned.
*
* @return A vector of tuples consisting of the bucket, position and AddrInfo.
*/
std::vector<std::tuple<int, int, AddrInfo>> GetEntries(bool from_tried) const;

/** We have successfully connected to this peer. Calling this function
* updates the CAddress's nTime, which is used in our IsTerrible()
* decisions and gossiped to peers. Callers should be careful that updating
Expand Down
5 changes: 5 additions & 0 deletions src/addrman_impl.h
Expand Up @@ -132,6 +132,9 @@ class AddrManImpl
std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const
EXCLUSIVE_LOCKS_REQUIRED(!cs);

std::vector<std::tuple<int, int, AddrInfo>> GetEntries(bool from_tried) const
EXCLUSIVE_LOCKS_REQUIRED(!cs);

void Connected(const CService& addr, NodeSeconds time)
EXCLUSIVE_LOCKS_REQUIRED(!cs);

Expand Down Expand Up @@ -260,6 +263,8 @@ class AddrManImpl

std::vector<CAddress> GetAddr_(size_t max_addresses, size_t max_pct, std::optional<Network> network) const EXCLUSIVE_LOCKS_REQUIRED(cs);

std::vector<std::tuple<int, int, AddrInfo>> GetEntries_(bool use_tried) const EXCLUSIVE_LOCKS_REQUIRED(cs);

void Connected_(const CService& addr, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);

void SetServices_(const CService& addr, ServiceFlags nServices) EXCLUSIVE_LOCKS_REQUIRED(cs);
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Expand Up @@ -300,6 +300,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "addpeeraddress", 1, "port"},
{ "addpeeraddress", 2, "tried"},
{ "sendmsgtopeer", 0, "peer_id" },
{ "getaddrmaninfo", 0, "verbose" },
{ "stop", 0, "wait" },
};
// clang-format on
Expand Down
141 changes: 98 additions & 43 deletions src/rpc/net.cpp
Expand Up @@ -5,6 +5,7 @@
#include <rpc/server.h>

#include <addrman.h>
#include <addrman_impl.h>
#include <banman.h>
#include <chainparams.h>
#include <clientversion.h>
Expand Down Expand Up @@ -1014,52 +1015,106 @@ static RPCHelpMan sendmsgtopeer()
};
}

UniValue AddrmanEntryToJSON(unsigned int bucket, unsigned int position, const AddrInfo& info)
{
UniValue ret(UniValue::VOBJ);
ret.pushKV("address", info.ToStringAddrPort());
ret.pushKV("services", (uint64_t)info.nServices);
ret.pushKV("bucket", bucket);
ret.pushKV("position", position);
ret.pushKV("source", info.source.ToStringAddr());
return ret;
}

static RPCHelpMan getaddrmaninfo()
{
return RPCHelpMan{"getaddrmaninfo",
"\nProvides information about the node's address manager by returning the number of "
"addresses in the `new` and `tried` tables and their sum for all networks.\n"
"This RPC is for testing only.\n",
{},
RPCResult{
RPCResult::Type::OBJ_DYN, "", "json object with network type as keys",
{
{RPCResult::Type::OBJ, "network", "the network (" + Join(GetNetworkNames(), ", ") + ")",
{
{RPCResult::Type::NUM, "new", "number of addresses in the new table, which represent potential peers the node has discovered but hasn't yet successfully connected to."},
{RPCResult::Type::NUM, "tried", "number of addresses in the tried table, which represent peers the node has successfully connected to in the past."},
{RPCResult::Type::NUM, "total", "total number of addresses in both new/tried tables"},
}},
}
},
RPCExamples{
HelpExampleCli("getaddrmaninfo", "")
+ HelpExampleRpc("getaddrmaninfo", "")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
NodeContext& node = EnsureAnyNodeContext(request.context);
if (!node.addrman) {
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Address manager functionality missing or disabled");
}

UniValue ret(UniValue::VOBJ);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
UniValue obj(UniValue::VOBJ);
obj.pushKV("new", node.addrman->Size(network, true));
obj.pushKV("tried", node.addrman->Size(network, false));
obj.pushKV("total", node.addrman->Size(network));
ret.pushKV(GetNetworkName(network), obj);
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("new", node.addrman->Size(std::nullopt, true));
obj.pushKV("tried", node.addrman->Size(std::nullopt, false));
obj.pushKV("total", node.addrman->Size());
ret.pushKV("all_networks", obj);
return ret;
},
"\nProvides information about the node's address manager by returning the number of "
"addresses in the `new` and `tried` tables and their sum for all networks.\n"
"When setting the verbose flag, the address manager entries for the new "
"and tried table are also returned.\n"
"This RPC is for testing only.\n",
{
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "True for a list of address manager entries by table, false for address counts only"},
},
{
RPCResult{"for verbose = false", RPCResult::Type::OBJ_DYN, "", "json object with network type as keys",
{
{RPCResult::Type::OBJ, "network", "the network (" + Join(GetNetworkNames(), ", ") + ", all_networks )", {
{RPCResult::Type::NUM, "new", "number of addresses in the new table, which represent potential peers the node has discovered but hasn't yet successfully connected to."},
{RPCResult::Type::NUM, "tried", "number of addresses in the tried table, which represent peers the node has successfully connected to in the past."},
{RPCResult::Type::NUM, "total", "total number of addresses in both new/tried tables"},
}},
},
},
RPCResult{"for verbose = true", RPCResult::Type::OBJ_DYN, "", "json object with network type, new_table and tried_table as keys",
{
{RPCResult::Type::ELISION, "", "The same output as verbose = false"},
{
RPCResult::Type::ARR, "table", "list of addresses in the address manager table ( new_table, tried_table )",
{
{RPCResult::Type::OBJ, "", "an address manager table entry", {
{RPCResult::Type::STR, "address", "the address"},
{RPCResult::Type::NUM, "services", "the services the node might support"},
{RPCResult::Type::NUM, "bucket", "the address manager bucket the address is placed in"},
{RPCResult::Type::NUM, "position", "the bucket position the address is placed in"},
{RPCResult::Type::STR, "source", "the address that relayed the address to us"},
}},
},
},
},
},
},
RPCExamples{
HelpExampleCli("getaddrmaninfo", "")
+ HelpExampleCli("getaddrmaninfo", "true")
+ HelpExampleRpc("getaddrmaninfo", "")},
+ HelpExampleRpc("getaddrmaninfo", "true")
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
NodeContext& node = EnsureAnyNodeContext(request.context);
if (!node.addrman) {
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Address manager functionality missing or disabled");
}

bool verbose = false;
if (!request.params[0].isNull()) {
verbose = request.params[0].get_bool();
}

UniValue ret(UniValue::VOBJ);
for (int n = 0; n < NET_MAX; ++n) {
enum Network network = static_cast<enum Network>(n);
if (network == NET_UNROUTABLE || network == NET_INTERNAL) continue;
UniValue obj(UniValue::VOBJ);
obj.pushKV("new", node.addrman->Size(network, true));
obj.pushKV("tried", node.addrman->Size(network, false));
obj.pushKV("total", node.addrman->Size(network));
ret.pushKV(GetNetworkName(network), obj);
}
UniValue obj(UniValue::VOBJ);
obj.pushKV("new", node.addrman->Size(std::nullopt, true));
obj.pushKV("tried", node.addrman->Size(std::nullopt, false));
obj.pushKV("total", node.addrman->Size());
ret.pushKV("all_networks", obj);

if (verbose) {
std::vector<std::tuple<int, int, AddrInfo>> newTableAddrInfos = node.addrman->GetEntries(false);
UniValue newTable(UniValue::VARR);
for (const auto& e : newTableAddrInfos) {
newTable.push_back(AddrmanEntryToJSON(std::get<0>(e), std::get<1>(e), std::get<2>(e)));
}
ret.pushKV("new_table", newTable);

UniValue triedTable(UniValue::VARR);
std::vector<std::tuple<int, int, AddrInfo>> triedTableAddrInfos = node.addrman->GetEntries(true);
for (const auto& e : triedTableAddrInfos) {
triedTable.push_back(AddrmanEntryToJSON(std::get<0>(e), std::get<1>(e), std::get<2>(e)));
}
ret.pushKV("tried_table", triedTable);
}

return ret;
},
};
}

Expand Down

0 comments on commit fbc29f2

Please sign in to comment.