Skip to content

Commit

Permalink
rpc: getrawaddrman 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.

As response JSON object the following FORMAT1 is choosen:
{
  "table": {
    "<bucket>/<position>": { "address": "..", "port": .., ... },
    "<bucket>/<position>": { "address": "..", "port": .., ... },
    "<bucket>/<position>": { "address": "..", "port": .., ... },
    ...
  }
}

An alternative would be FORMAT2
{
  "table": {
    "bucket": {
      "position": { "address": "..", "port": .., ... },
      "position": { "address": "..", "port": .., ... },
      ..
    },
    "bucket": {
      "position": { "address": "..", "port": .., ... },
      ..
    },
  }
}

FORMAT1 and FORMAT2 have different encodings for the location of the
address in the address manager. While FORMAT2 might be easier to process
for downstream tools, it also mimics internal addrman mappings, which
might change at some point. Users not interested in the address location
can ignore the location key. They don't have to adapt to a new RPC
response format, when the internal addrman layout changes. Additionally,
FORMAT1 is also slightly easier to to iterate in downstream tools. The
RPC response-building implemenation complexcity is lower with FORMAT1
as we can more easily build a "<bucket>/<position>" key than a multiple
"bucket" objects with multiple "position" objects (FORMAT2).
  • Loading branch information
0xB10C committed Oct 2, 2023
1 parent 8909667 commit da384a2
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 1 deletion.
38 changes: 38 additions & 0 deletions src/addrman.cpp
Expand Up @@ -838,6 +838,30 @@ std::vector<CAddress> AddrManImpl::GetAddr_(size_t max_addresses, size_t max_pct
return addresses;
}

std::vector<std::pair<AddrInfo, AddressPosition>> 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::pair<AddrInfo, AddressPosition>> 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);
AddressPosition location = AddressPosition(
from_tried,
/*multiplicity_in=*/from_tried ? 1 : info.nRefCount,
bucket,
position);
infos.push_back(std::make_pair(info, location));
}
}
}

return infos;
}

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

std::vector<std::pair<AddrInfo, AddressPosition>> 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 +1322,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::pair<AddrInfo, AddressPosition>> 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
14 changes: 13 additions & 1 deletion src/addrman.h
Expand Up @@ -25,11 +25,12 @@ class InvalidAddrManVersionError : public std::ios_base::failure
};

class AddrManImpl;
class AddrInfo;

/** Default for -checkaddrman */
static constexpr int32_t DEFAULT_ADDRMAN_CONSISTENCY_CHECKS{0};

/** Test-only struct, capturing info about an address in AddrMan */
/** Location information for an address in AddrMan */
struct AddressPosition {
// Whether the address is in the new or tried table
const bool tried;
Expand Down Expand Up @@ -168,6 +169,17 @@ class AddrMan
*/
std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const;

/**
* Returns an information-location pair for all addresses in the selected addrman table.
* If an address appears multiple times in the new table, an information-location pair
* is returned for each occurence. Addresses only ever appear once in the tried table.
*
* @param[in] from_tried Selects which table to return entries from.
*
* @return A vector consisting of pairs of AddrInfo and AddressPosition.
*/
std::vector<std::pair<AddrInfo, AddressPosition>> 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::pair<AddrInfo, AddressPosition>> 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::pair<AddrInfo, AddressPosition>> GetEntries_(bool from_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
70 changes: 70 additions & 0 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 @@ -1063,6 +1064,74 @@ static RPCHelpMan getaddrmaninfo()
};
}

UniValue AddrmanEntryToJSON(const AddrInfo& info)
{
UniValue ret(UniValue::VOBJ);
ret.pushKV("address", info.ToStringAddr());
ret.pushKV("port", info.GetPort());
ret.pushKV("services", (uint64_t)info.nServices);
ret.pushKV("time", int64_t{TicksSinceEpoch<std::chrono::seconds>(info.nTime)});
ret.pushKV("network", GetNetworkName(info.GetNetClass()));
ret.pushKV("source", info.source.ToStringAddr());
ret.pushKV("source_network", GetNetworkName(info.source.GetNetClass()));
return ret;
}

UniValue AddrmanTableToJSON(const std::vector<std::pair<AddrInfo, AddressPosition>>& tableInfos)
{
UniValue table(UniValue::VOBJ);
for (const auto& e : tableInfos) {
AddrInfo info = e.first;
AddressPosition location = e.second;
std::ostringstream key;
key << location.bucket << "/" << location.position;
// Address manager tables have unique entries so there is no advantage
// in using UniValue::pushKV, which checks if the key already exists
// in O(N). UniValue::pushKVEnd is used instead which currently is O(1).
table.pushKVEnd(key.str(), AddrmanEntryToJSON(info));
}
return table;
}

static RPCHelpMan getrawaddrman()
{
return RPCHelpMan{"getrawaddrman",
"EXPERIMENTAL warning: this call may be changed in future releases.\n"
"\nReturns information on all address manager entries for the new and tried tables.\n",
{},
RPCResult{
RPCResult::Type::OBJ_DYN, "", "", {
{RPCResult::Type::OBJ_DYN, "table", "buckets with addresses in the address manager table ( new, tried )", {
{RPCResult::Type::OBJ, "bucket/position", "the location in the address manager table (<bucket>/<position>)", {
{RPCResult::Type::STR, "address", "The address of the node"},
{RPCResult::Type::NUM, "port", "The port number of the node"},
{RPCResult::Type::STR, "network", "The network (" + Join(GetNetworkNames(), ", ") + ") of the address"},
{RPCResult::Type::NUM, "services", "The services offered by the node"},
{RPCResult::Type::NUM_TIME, "time", "The " + UNIX_EPOCH_TIME + " when the node was last seen"},
{RPCResult::Type::STR, "source", "The address that relayed the address to us"},
{RPCResult::Type::STR, "source_network", "The network (" + Join(GetNetworkNames(), ", ") + ") of the source address"},
}}
}}
}
},
RPCExamples{
HelpExampleCli("getrawaddrman", "")
+ HelpExampleRpc("getrawaddrman", "")
},
[&](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);
ret.pushKV("new", AddrmanTableToJSON(node.addrman->GetEntries(false)));
ret.pushKV("tried", AddrmanTableToJSON(node.addrman->GetEntries(true)));
return ret;
},
};
}

void RegisterNetRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
Expand All @@ -1083,6 +1152,7 @@ void RegisterNetRPCCommands(CRPCTable& t)
{"hidden", &addpeeraddress},
{"hidden", &sendmsgtopeer},
{"hidden", &getaddrmaninfo},
{"hidden", &getrawaddrman},
};
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
Expand Down
1 change: 1 addition & 0 deletions src/test/fuzz/rpc.cpp
Expand Up @@ -140,6 +140,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"getnodeaddresses",
"getpeerinfo",
"getprioritisedtransactions",
"getrawaddrman",
"getrawmempool",
"getrawtransaction",
"getrpcinfo",
Expand Down

0 comments on commit da384a2

Please sign in to comment.