Skip to content

Commit ba3d327

Browse files
committed
Merge bitcoin/bitcoin#26847: p2p: track AddrMan totals by network and table, improve precision of adding fixed seeds
80f39c9 addrman, refactor: combine two size functions (Amiti Uttarwar) 4885d6f addrman, refactor: move count increment into Create() (Martin Zumsande) c77c877 net: Load fixed seeds from reachable networks for which we don't have addresses (Martin Zumsande) d35595a addrman: add function to return size by network and table (Martin Zumsande) Pull request description: AddrMan currently doesn't track the number of its entries by network, it only knows the total number of addresses. This PR makes AddrMan keep track of these numbers, which would be helpful for multiple things: 1. Allow to specifically add fixed seeds to AddrMan of networks where we don't have any addresses yet - even if AddrMan as a whole is not empty (partly fixing #26035). This is in particular helpful if the user abruptly changes `-onlynet` settings (such that addrs that used to be reachable are no longer and vice versa), in which case they currently could get stuck and not find any outbound peers. The second commit of this PR implements this. 1. (Future work): Add logic for automatic connection management with respect to networks - such as making attempts to have at least one connection to each reachable network as suggested [here](bitcoin/bitcoin#26035 (comment)). This would involve requesting an address from a particular network from AddrMan, and expanding its corresponding function `AddrMan::Select()` to do this requires internal knowledge of the current number of addresses for each network and table to avoid getting stuck in endless loops. 1. (Future work): Perhaps display the totals to users. At least I would find this helpful to debug, the existing option (`./bitcoin-cli -addrinfo`) is rather indirect by doing the aggregation itself in each call, doesn't distinguish between new and tried, and being based on `AddrMan::GetAddr()` it's also subject to a quality filter which we probably don't want in this spot. ACKs for top commit: naumenkogs: utACK 80f39c9 stratospher: ACK 80f39c9 achow101: ACK 80f39c9 vasild: ACK 80f39c9 Tree-SHA512: 6359f2e3f4db7c120c0789d92d74cb7d87a2ceedb7d6a34b5eff20c7f55c5c81092d10ed94efe29afc1c66947820a0d9c14876ee0c8d1f8e068a6df4e1131927
2 parents b5868f4 + 80f39c9 commit ba3d327

File tree

8 files changed

+182
-62
lines changed

8 files changed

+182
-62
lines changed

src/addrdb.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ std::optional<bilingual_str> LoadAddrman(const NetGroupManager& netgroupman, con
190190
const auto path_addr{args.GetDataDirNet() / "peers.dat"};
191191
try {
192192
DeserializeFileDB(path_addr, *addrman, CLIENT_VERSION);
193-
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
193+
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->Size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
194194
} catch (const DbNotFoundError&) {
195195
// Addrman can be in an inconsistent state after failure, reset it
196196
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);

src/addrman.cpp

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ void AddrManImpl::Unserialize(Stream& s_)
291291
mapAddr[info] = n;
292292
info.nRandomPos = vRandom.size();
293293
vRandom.push_back(n);
294+
m_network_counts[info.GetNetwork()].n_new++;
294295
}
295296
nIdCount = nNew;
296297

@@ -310,6 +311,7 @@ void AddrManImpl::Unserialize(Stream& s_)
310311
mapAddr[info] = nIdCount;
311312
vvTried[nKBucket][nKBucketPos] = nIdCount;
312313
nIdCount++;
314+
m_network_counts[info.GetNetwork()].n_tried++;
313315
} else {
314316
nLost++;
315317
}
@@ -425,6 +427,8 @@ AddrInfo* AddrManImpl::Create(const CAddress& addr, const CNetAddr& addrSource,
425427
mapAddr[addr] = nId;
426428
mapInfo[nId].nRandomPos = vRandom.size();
427429
vRandom.push_back(nId);
430+
nNew++;
431+
m_network_counts[addr.GetNetwork()].n_new++;
428432
if (pnId)
429433
*pnId = nId;
430434
return &mapInfo[nId];
@@ -464,6 +468,7 @@ void AddrManImpl::Delete(int nId)
464468
assert(info.nRefCount == 0);
465469

466470
SwapRandom(info.nRandomPos, vRandom.size() - 1);
471+
m_network_counts[info.GetNetwork()].n_new--;
467472
vRandom.pop_back();
468473
mapAddr.erase(info);
469474
mapInfo.erase(nId);
@@ -504,6 +509,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId)
504509
}
505510
}
506511
nNew--;
512+
m_network_counts[info.GetNetwork()].n_new--;
507513

508514
assert(info.nRefCount == 0);
509515

@@ -522,6 +528,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId)
522528
infoOld.fInTried = false;
523529
vvTried[nKBucket][nKBucketPos] = -1;
524530
nTried--;
531+
m_network_counts[infoOld.GetNetwork()].n_tried--;
525532

526533
// find which new bucket it belongs to
527534
int nUBucket = infoOld.GetNewBucket(nKey, m_netgroupman);
@@ -533,6 +540,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId)
533540
infoOld.nRefCount = 1;
534541
vvNew[nUBucket][nUBucketPos] = nIdEvict;
535542
nNew++;
543+
m_network_counts[infoOld.GetNetwork()].n_new++;
536544
LogPrint(BCLog::ADDRMAN, "Moved %s from tried[%i][%i] to new[%i][%i] to make space\n",
537545
infoOld.ToString(), nKBucket, nKBucketPos, nUBucket, nUBucketPos);
538546
}
@@ -541,6 +549,7 @@ void AddrManImpl::MakeTried(AddrInfo& info, int nId)
541549
vvTried[nKBucket][nKBucketPos] = nId;
542550
nTried++;
543551
info.fInTried = true;
552+
m_network_counts[info.GetNetwork()].n_tried++;
544553
}
545554

546555
bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, std::chrono::seconds time_penalty)
@@ -591,7 +600,6 @@ bool AddrManImpl::AddSingle(const CAddress& addr, const CNetAddr& source, std::c
591600
} else {
592601
pinfo = Create(addr, source, &nId);
593602
pinfo->nTime = std::max(NodeSeconds{0s}, pinfo->nTime - time_penalty);
594-
nNew++;
595603
}
596604

597605
int nUBucket = pinfo->GetNewBucket(nKey, source, m_netgroupman);
@@ -962,6 +970,28 @@ std::optional<AddressPosition> AddrManImpl::FindAddressEntry_(const CAddress& ad
962970
}
963971
}
964972

973+
size_t AddrManImpl::Size_(std::optional<Network> net, std::optional<bool> in_new) const
974+
{
975+
AssertLockHeld(cs);
976+
977+
if (!net.has_value()) {
978+
if (in_new.has_value()) {
979+
return *in_new ? nNew : nTried;
980+
} else {
981+
return vRandom.size();
982+
}
983+
}
984+
if (auto it = m_network_counts.find(*net); it != m_network_counts.end()) {
985+
auto net_count = it->second;
986+
if (in_new.has_value()) {
987+
return *in_new ? net_count.n_new : net_count.n_tried;
988+
} else {
989+
return net_count.n_new + net_count.n_tried;
990+
}
991+
}
992+
return 0;
993+
}
994+
965995
void AddrManImpl::Check() const
966996
{
967997
AssertLockHeld(cs);
@@ -986,6 +1016,7 @@ int AddrManImpl::CheckAddrman() const
9861016

9871017
std::unordered_set<int> setTried;
9881018
std::unordered_map<int, int> mapNew;
1019+
std::unordered_map<Network, NewTriedCount> local_counts;
9891020

9901021
if (vRandom.size() != (size_t)(nTried + nNew))
9911022
return -7;
@@ -1000,12 +1031,14 @@ int AddrManImpl::CheckAddrman() const
10001031
if (info.nRefCount)
10011032
return -2;
10021033
setTried.insert(n);
1034+
local_counts[info.GetNetwork()].n_tried++;
10031035
} else {
10041036
if (info.nRefCount < 0 || info.nRefCount > ADDRMAN_NEW_BUCKETS_PER_ADDRESS)
10051037
return -3;
10061038
if (!info.nRefCount)
10071039
return -4;
10081040
mapNew[n] = info.nRefCount;
1041+
local_counts[info.GetNetwork()].n_new++;
10091042
}
10101043
const auto it{mapAddr.find(info)};
10111044
if (it == mapAddr.end() || it->second != n) {
@@ -1065,13 +1098,27 @@ int AddrManImpl::CheckAddrman() const
10651098
if (nKey.IsNull())
10661099
return -16;
10671100

1101+
// It's possible that m_network_counts may have all-zero entries that local_counts
1102+
// doesn't have if addrs from a network were being added and then removed again in the past.
1103+
if (m_network_counts.size() < local_counts.size()) {
1104+
return -20;
1105+
}
1106+
for (const auto& [net, count] : m_network_counts) {
1107+
if (local_counts[net].n_new != count.n_new || local_counts[net].n_tried != count.n_tried) {
1108+
return -21;
1109+
}
1110+
}
1111+
10681112
return 0;
10691113
}
10701114

1071-
size_t AddrManImpl::size() const
1115+
size_t AddrManImpl::Size(std::optional<Network> net, std::optional<bool> in_new) const
10721116
{
1073-
LOCK(cs); // TODO: Cache this in an atomic to avoid this overhead
1074-
return vRandom.size();
1117+
LOCK(cs);
1118+
Check();
1119+
auto ret = Size_(net, in_new);
1120+
Check();
1121+
return ret;
10751122
}
10761123

10771124
bool AddrManImpl::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
@@ -1185,9 +1232,9 @@ template void AddrMan::Unserialize(CHashVerifier<CAutoFile>& s);
11851232
template void AddrMan::Unserialize(CDataStream& s);
11861233
template void AddrMan::Unserialize(CHashVerifier<CDataStream>& s);
11871234

1188-
size_t AddrMan::size() const
1235+
size_t AddrMan::Size(std::optional<Network> net, std::optional<bool> in_new) const
11891236
{
1190-
return m_impl->size();
1237+
return m_impl->Size(net, in_new);
11911238
}
11921239

11931240
bool AddrMan::Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)

src/addrman.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,14 @@ class AddrMan
9999
template <typename Stream>
100100
void Unserialize(Stream& s_);
101101

102-
//! Return the number of (unique) addresses in all tables.
103-
size_t size() const;
102+
/**
103+
* Return size information about addrman.
104+
*
105+
* @param[in] net Select addresses only from specified network (nullopt = all)
106+
* @param[in] in_new Select addresses only from one table (true = new, false = tried, nullopt = both)
107+
* @return Number of unique addresses that match specified options.
108+
*/
109+
size_t Size(std::optional<Network> net = {}, std::optional<bool> in_new = {}) const;
104110

105111
/**
106112
* Attempt to add one or more addresses to addrman's new table.

src/addrman_impl.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class AddrManImpl
112112
template <typename Stream>
113113
void Unserialize(Stream& s_) EXCLUSIVE_LOCKS_REQUIRED(!cs);
114114

115-
size_t size() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
115+
size_t Size(std::optional<Network> net, std::optional<bool> in_new) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
116116

117117
bool Add(const std::vector<CAddress>& vAddr, const CNetAddr& source, std::chrono::seconds time_penalty)
118118
EXCLUSIVE_LOCKS_REQUIRED(!cs);
@@ -215,6 +215,14 @@ class AddrManImpl
215215
/** Reference to the netgroup manager. netgroupman must be constructed before addrman and destructed after. */
216216
const NetGroupManager& m_netgroupman;
217217

218+
struct NewTriedCount {
219+
size_t n_new;
220+
size_t n_tried;
221+
};
222+
223+
/** Number of entries in addrman per network and new/tried table. */
224+
std::unordered_map<Network, NewTriedCount> m_network_counts GUARDED_BY(cs);
225+
218226
//! Find an entry.
219227
AddrInfo* Find(const CService& addr, int* pnId = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs);
220228

@@ -257,6 +265,8 @@ class AddrManImpl
257265

258266
std::optional<AddressPosition> FindAddressEntry_(const CAddress& addr) EXCLUSIVE_LOCKS_REQUIRED(cs);
259267

268+
size_t Size_(std::optional<Network> net, std::optional<bool> in_new) const EXCLUSIVE_LOCKS_REQUIRED(cs);
269+
260270
//! Consistency check, taking into account m_consistency_check_ratio.
261271
//! Will std::abort if an inconsistency is detected.
262272
void Check() const EXCLUSIVE_LOCKS_REQUIRED(cs);

src/net.cpp

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,7 @@ void CConnman::ThreadDNSAddressSeed()
13991399
if (gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED)) {
14001400
// When -forcednsseed is provided, query all.
14011401
seeds_right_now = seeds.size();
1402-
} else if (addrman.size() == 0) {
1402+
} else if (addrman.Size() == 0) {
14031403
// If we have no known peers, query all.
14041404
// This will occur on the first run, or if peers.dat has been
14051405
// deleted.
@@ -1418,13 +1418,13 @@ void CConnman::ThreadDNSAddressSeed()
14181418
// * If we continue having problems, eventually query all the
14191419
// DNS seeds, and if that fails too, also try the fixed seeds.
14201420
// (done in ThreadOpenConnections)
1421-
const std::chrono::seconds seeds_wait_time = (addrman.size() >= DNSSEEDS_DELAY_PEER_THRESHOLD ? DNSSEEDS_DELAY_MANY_PEERS : DNSSEEDS_DELAY_FEW_PEERS);
1421+
const std::chrono::seconds seeds_wait_time = (addrman.Size() >= DNSSEEDS_DELAY_PEER_THRESHOLD ? DNSSEEDS_DELAY_MANY_PEERS : DNSSEEDS_DELAY_FEW_PEERS);
14221422

14231423
for (const std::string& seed : seeds) {
14241424
if (seeds_right_now == 0) {
14251425
seeds_right_now += DNSSEEDS_TO_QUERY_AT_ONCE;
14261426

1427-
if (addrman.size() > 0) {
1427+
if (addrman.Size() > 0) {
14281428
LogPrintf("Waiting %d seconds before querying DNS seeds.\n", seeds_wait_time.count());
14291429
std::chrono::seconds to_wait = seeds_wait_time;
14301430
while (to_wait.count() > 0) {
@@ -1504,7 +1504,7 @@ void CConnman::DumpAddresses()
15041504
DumpPeerAddresses(::gArgs, addrman);
15051505

15061506
LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n",
1507-
addrman.size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
1507+
addrman.Size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
15081508
}
15091509

15101510
void CConnman::ProcessAddrFetch()
@@ -1575,6 +1575,19 @@ int CConnman::GetExtraBlockRelayCount() const
15751575
return std::max(block_relay_peers - m_max_outbound_block_relay, 0);
15761576
}
15771577

1578+
std::unordered_set<Network> CConnman::GetReachableEmptyNetworks() const
1579+
{
1580+
std::unordered_set<Network> networks{};
1581+
for (int n = 0; n < NET_MAX; n++) {
1582+
enum Network net = (enum Network)n;
1583+
if (net == NET_UNROUTABLE || net == NET_INTERNAL) continue;
1584+
if (IsReachable(net) && addrman.Size(net, std::nullopt) == 0) {
1585+
networks.insert(net);
1586+
}
1587+
}
1588+
return networks;
1589+
}
1590+
15781591
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
15791592
{
15801593
SetSyscallSandboxPolicy(SyscallSandboxPolicy::NET_OPEN_CONNECTION);
@@ -1624,7 +1637,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
16241637
if (interruptNet)
16251638
return;
16261639

1627-
if (add_fixed_seeds && addrman.size() == 0) {
1640+
const std::unordered_set<Network> fixed_seed_networks{GetReachableEmptyNetworks()};
1641+
if (add_fixed_seeds && !fixed_seed_networks.empty()) {
16281642
// When the node starts with an empty peers.dat, there are a few other sources of peers before
16291643
// we fallback on to fixed seeds: -dnsseed, -seednode, -addnode
16301644
// If none of those are available, we fallback on to fixed seeds immediately, else we allow
@@ -1633,7 +1647,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
16331647
// It is cheapest to check if enough time has passed first.
16341648
if (GetTime<std::chrono::seconds>() > start + std::chrono::minutes{1}) {
16351649
add_fixed_seeds_now = true;
1636-
LogPrintf("Adding fixed seeds as 60 seconds have passed and addrman is empty\n");
1650+
LogPrintf("Adding fixed seeds as 60 seconds have passed and addrman is empty for at least one reachable network\n");
16371651
}
16381652

16391653
// Checking !dnsseed is cheaper before locking 2 mutexes.
@@ -1650,14 +1664,12 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
16501664
// We will not make outgoing connections to peers that are unreachable
16511665
// (e.g. because of -onlynet configuration).
16521666
// Therefore, we do not add them to addrman in the first place.
1653-
// Note that if you change -onlynet setting from one network to another,
1654-
// peers.dat will contain only peers of unreachable networks and
1655-
// manual intervention will be needed (either delete peers.dat after
1656-
// configuration change or manually add some reachable peer using addnode),
1657-
// see <https://github.com/bitcoin/bitcoin/issues/26035> for details.
1667+
// In case previously unreachable networks become reachable
1668+
// (e.g. in case of -onlynet changes by the user), fixed seeds will
1669+
// be loaded only for networks for which we have no addressses.
16581670
seed_addrs.erase(std::remove_if(seed_addrs.begin(), seed_addrs.end(),
1659-
[](const CAddress& addr) { return !IsReachable(addr); }),
1660-
seed_addrs.end());
1671+
[&fixed_seed_networks](const CAddress& addr) { return fixed_seed_networks.count(addr.GetNetwork()) == 0; }),
1672+
seed_addrs.end());
16611673
CNetAddr local;
16621674
local.SetInternal("fixedseeds");
16631675
addrman.Add(seed_addrs, local);

src/net.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <memory>
4040
#include <optional>
4141
#include <thread>
42+
#include <unordered_set>
4243
#include <vector>
4344

4445
class AddrMan;
@@ -969,6 +970,12 @@ class CConnman
969970
void RecordBytesRecv(uint64_t bytes);
970971
void RecordBytesSent(uint64_t bytes) EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex);
971972

973+
/**
974+
Return reachable networks for which we have no addresses in addrman and therefore
975+
may require loading fixed seeds.
976+
*/
977+
std::unordered_set<Network> GetReachableEmptyNetworks() const;
978+
972979
/**
973980
* Return vector of current BLOCK_RELAY peers.
974981
*/

0 commit comments

Comments
 (0)