p2p: track AddrMan totals by network and table, improve precision of adding fixed seeds #26847 #12
Replies: 1 comment
-
SummaryThis PR adds a new map Previously, before this PR, core only counted the total number of When is a network reachable in Bitcoin Core? (it’s not as self-explanatory as it may seem!)Reachability of a network is determined by the
// Global state variables
//
bool fDiscover = true;
bool fListen = true;
GlobalMutex g_maplocalhost_mutex;
std::map<CNetAddr, LocalServiceInfo> mapLocalHost GUARDED_BY(g_maplocalhost_mutex);
static bool vfLimited[NET_MAX] GUARDED_BY(g_maplocalhost_mutex) = {};
std::string strSubVersion;
This vector is defined by default Two functions
void SetReachable(enum Network net, bool reachable)
{
if (net == NET_UNROUTABLE || net == NET_INTERNAL)
return;
LOCK(g_maplocalhost_mutex);
vfLimited[net] = !reachable;
}
bool IsReachable(enum Network net)
{
LOCK(g_maplocalhost_mutex);
return !vfLimited[net];
} Different networks are set with reachability at various stages in the
if (args.IsArgSet("-onlynet")) {
std::set<enum Network> nets;
for (const std::string& snet : args.GetArgs("-onlynet")) {
enum Network net = ParseNetwork(snet);
if (net == NET_UNROUTABLE)
return InitError(strprintf(_("Unknown network specified in -onlynet: '%s'"), snet));
nets.insert(net);
}
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
assert(IsReachable(net));
if (!nets.count(net))
SetReachable(net, false);
}
} If
if (!args.IsArgSet("-cjdnsreachable")) {
if (args.IsArgSet("-onlynet") && IsReachable(NET_CJDNS)) {
return InitError(
_("Outbound connections restricted to CJDNS (-onlynet=cjdns) but "
"-cjdnsreachable is not provided"));
}
SetReachable(NET_CJDNS, false);
} If cjdns configs aren't set at the start of the node, then it is set as unreachable. Similarly, for Tor and I2P.
if (onlynet_used_with_onion && listenonion_disabled) {
return InitError(
_("Outbound connections restricted to Tor (-onlynet=onion) but the proxy for "
"reaching the Tor network is not provided: none of -proxy, -onion or "
"-listenonion is given"));
}
SetReachable(NET_ONION, false);
...
if (args.IsArgSet("-onlynet") && IsReachable(NET_I2P)) {
return InitError(
_("Outbound connections restricted to i2p (-onlynet=i2p) but "
"-i2psam is not provided"));
}
SetReachable(NET_I2P, false); How are addresses relayed over the p2p network treated depending on whether they are reachable vs. non-reachable - do we store them and/or forward them to peers? (Hint: Look in ProcessMessage)Only reachable addresses are stored in the database.
bool fReachable = IsReachable(addr);
if (addr.nTime > current_a_time - 10min && !peer->m_getaddr_sent && vAddr.size() <= 10 && addr.IsRoutable()) {
// Relay to a limited number of other nodes
RelayAddress(pfrom.GetId(), addr, fReachable);
}
// Do not store addresses outside our network
if (fReachable)
vAddrOk.push_back(addr); Where as bot reachable and unreachable addresses are relayed to peers.
void PeerManagerImpl::RelayAddress(NodeId originator,
const CAddress& addr,
bool fReachable)
{
// We choose the same nodes within a given 24h window (if the list of connected
// nodes does not change) and we don't relay to nodes that already know an
// address. So within 24h we will likely relay a given address once. This is to
// prevent a peer from unjustly giving their address better propagation by sending
// it to us repeatedly.
if (!fReachable && !addr.IsRelayable()) return;
...
// Relay reachable addresses to 2 peers. Unreachable addresses are relayed randomly to 1 or 2 peers.
unsigned int nRelayNodes = (fReachable || (hasher.Finalize() & 1)) ? 2 : 1; How does this PR attempt to make sure that there are no bugs causing the added counts per network to fall out of sync with other AddrMan internals such as nNew and nTried?Every places where the total An extra check is added in the // It's possible that m_network_counts may have all-zero entries that local_counts
// doesn't have if addrs from a network were being added and then removed again in the past.
if (m_network_counts.size() < local_counts.size()) {
return -20;
}
for (const auto& [net, count] : m_network_counts) {
if (local_counts[net].n_new != count.n_new || local_counts[net].n_tried != count.n_tried) {
return -21;
}
} How can a node currently get stuck with only unreachable addresses in AddrMan, finding no outbound peers? How does this PR fix it?If the user suddenly changes This is fixed in this PR, by adding a new
std::unordered_set<Network> CConnman::GetReachableEmptyNetworks() const
{
std::unordered_set<Network> networks{};
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
if (net == NET_UNROUTABLE || net == NET_INTERNAL) continue;
if (IsReachable(net) && addrman.Size(net, std::nullopt) == 0) {
networks.insert(net);
}
}
return networks;
} This information is then used to seed those networks with fixed seeds. const std::unordered_set<Network> fixed_seed_networks{GetReachableEmptyNetworks()};
if (add_fixed_seeds && !fixed_seed_networks.empty()) {
...
if (add_fixed_seeds_now) {
std::vector<CAddress> seed_addrs{ConvertSeeds(Params().FixedSeeds())};
// We will not make outgoing connections to peers that are unreachable
// (e.g. because of -onlynet configuration).
// Therefore, we do not add them to addrman in the first place.
// In case previously unreachable networks become reachable
// (e.g. in case of -onlynet changes by the user), fixed seeds will
// be loaded only for networks for which we have no addressses.
seed_addrs.erase(std::remove_if(seed_addrs.begin(), seed_addrs.end(),
[&fixed_seed_networks](const CAddress& addr) { return fixed_seed_networks.count(addr.GetNetwork()) == 0; }),
seed_addrs.end());
CNetAddr local;
local.SetInternal("fixedseeds");
addrman.Add(seed_addrs, local);
add_fixed_seeds = false;
LogPrintf("Added %d fixed seeds from reachable networks.\n", seed_addrs.size());
}
...
} Why would it be beneficial to have an outbound connection to each reachable network at all times? Why is the current logic in ThreadOpenConnections insufficient to guarantee this?Having outbound to each different network can increase resistance to exlipse attack. What would be the possible next steps towards this goal after this PR?To make fixed number of connections for each sub network. How are different addresses are evaluated and ranked?The address manager assigns health-score to each address by some metrics. And this is used to replace one address in favor of another.
bool AddrInfo::IsTerrible(NodeSeconds now) const
{
if (now - m_last_try <= 1min) { // never remove things tried in the last minute
return false;
}
if (nTime > now + 10min) { // came in a flying DeLorean
return true;
}
if (now - nTime > ADDRMAN_HORIZON) { // not seen in recent history
return true;
}
if (TicksSinceEpoch<std::chrono::seconds>(m_last_success) == 0 && nAttempts >= ADDRMAN_RETRIES) { // tried N times and never a success
return true;
}
if (now - m_last_success > ADDRMAN_MIN_FAIL && nAttempts >= ADDRMAN_MAX_FAILURES) { // N successive failures in the last week
return true;
}
return false;
} |
Beta Was this translation helpful? Give feedback.
-
Session Details
[net][addrs_manager]
[c++]
Summary
This PR adds internal tracking of the totals by network and table to AddrMan and adds this information to its public interface.
Learnings
Beta Was this translation helpful? Give feedback.
All reactions