Skip to content

Commit 3a93957

Browse files
committed
Merge bitcoin/bitcoin#27214: addrman: Enable selecting addresses by network
17e7054 doc: clarify new_only param for Select function (Amiti Uttarwar) b0010c8 bench: test select for a new table with only one address (Amiti Uttarwar) 9b91aae bench: add coverage for addrman select with network parameter (Amiti Uttarwar) 22a4d14 test: increase coverage of addrman select (without network) (Amiti Uttarwar) a98e542 test: add addrman test for special case (Amiti Uttarwar) 5c8b4ba tests: add addrman_select_by_network test (Amiti Uttarwar) 6b22928 addrman: add functionality to select by network (Amiti Uttarwar) 26c3bf1 scripted-diff: rename local variables to match modern conventions (Amiti Uttarwar) 4880641 refactor: consolidate select logic for new and tried tables (Amiti Uttarwar) ca2a9c5 refactor: generalize select logic (Amiti Uttarwar) 052fbcd addrman: Introduce helper to generalize looking up an addrman entry (Amiti Uttarwar) 9bf078f refactor: update Select_ function (Amiti Uttarwar) Pull request description: For the full context & motivation of this patch, see #27213 This is joint work with mzumsande. This PR adds functionality to `AddrMan::Select` to enable callers to specify a network they are interested in. Along the way, it refactors the function to deduplicate the logic, updates the local variables to match modern conventions, adds test coverage for both the new and existing `Select` logic, and adds bench tests for the worst case performance of both the new and existing `Select` logic. This functionality is used in the parent PR. ACKs for top commit: vasild: ACK 17e7054 brunoerg: re-ACK 17e7054 ajtowns: ACK 17e7054 mzumsande: Code Review ACK 17e7054 Tree-SHA512: e99d1ce0c44a15601a3daa37deeadfc9d26208a92969ecffbea358d57ca951102d759734ccf77eacd38db368da0bf5b6fede3cd900d8a77b3061f4adc54e52d8
2 parents bbbf89a + 17e7054 commit 3a93957

File tree

5 files changed

+259
-91
lines changed

5 files changed

+259
-91
lines changed

src/addrman.cpp

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ int AddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src, const NetGr
5858
return hash2 % ADDRMAN_NEW_BUCKET_COUNT;
5959
}
6060

61-
int AddrInfo::GetBucketPosition(const uint256& nKey, bool fNew, int nBucket) const
61+
int AddrInfo::GetBucketPosition(const uint256& nKey, bool fNew, int bucket) const
6262
{
63-
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? uint8_t{'N'} : uint8_t{'K'}) << nBucket << GetKey()).GetCheapHash();
63+
uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? uint8_t{'N'} : uint8_t{'K'}) << bucket << GetKey()).GetCheapHash();
6464
return hash1 % ADDRMAN_BUCKET_SIZE;
6565
}
6666

@@ -714,72 +714,98 @@ void AddrManImpl::Attempt_(const CService& addr, bool fCountFailure, NodeSeconds
714714
}
715715
}
716716

717-
std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool newOnly) const
717+
std::pair<CAddress, NodeSeconds> AddrManImpl::Select_(bool new_only, std::optional<Network> network) const
718718
{
719719
AssertLockHeld(cs);
720720

721721
if (vRandom.empty()) return {};
722722

723-
if (newOnly && nNew == 0) return {};
724-
725-
// Use a 50% chance for choosing between tried and new table entries.
726-
if (!newOnly &&
727-
(nTried > 0 && (nNew == 0 || insecure_rand.randbool() == 0))) {
728-
// use a tried node
729-
double fChanceFactor = 1.0;
730-
while (1) {
731-
// Pick a tried bucket, and an initial position in that bucket.
732-
int nKBucket = insecure_rand.randrange(ADDRMAN_TRIED_BUCKET_COUNT);
733-
int nKBucketPos = insecure_rand.randrange(ADDRMAN_BUCKET_SIZE);
734-
// Iterate over the positions of that bucket, starting at the initial one,
735-
// and looping around.
736-
int i;
737-
for (i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
738-
if (vvTried[nKBucket][(nKBucketPos + i) % ADDRMAN_BUCKET_SIZE] != -1) break;
739-
}
740-
// If the bucket is entirely empty, start over with a (likely) different one.
741-
if (i == ADDRMAN_BUCKET_SIZE) continue;
742-
// Find the entry to return.
743-
int nId = vvTried[nKBucket][(nKBucketPos + i) % ADDRMAN_BUCKET_SIZE];
744-
const auto it_found{mapInfo.find(nId)};
745-
assert(it_found != mapInfo.end());
746-
const AddrInfo& info{it_found->second};
747-
// With probability GetChance() * fChanceFactor, return the entry.
748-
if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) {
749-
LogPrint(BCLog::ADDRMAN, "Selected %s from tried\n", info.ToStringAddrPort());
750-
return {info, info.m_last_try};
751-
}
752-
// Otherwise start over with a (likely) different bucket, and increased chance factor.
753-
fChanceFactor *= 1.2;
754-
}
723+
size_t new_count = nNew;
724+
size_t tried_count = nTried;
725+
726+
if (network.has_value()) {
727+
auto it = m_network_counts.find(*network);
728+
if (it == m_network_counts.end()) return {};
729+
730+
auto counts = it->second;
731+
new_count = counts.n_new;
732+
tried_count = counts.n_tried;
733+
}
734+
735+
if (new_only && new_count == 0) return {};
736+
if (new_count + tried_count == 0) return {};
737+
738+
// Decide if we are going to search the new or tried table
739+
// If either option is viable, use a 50% chance to choose
740+
bool search_tried;
741+
if (new_only || tried_count == 0) {
742+
search_tried = false;
743+
} else if (new_count == 0) {
744+
search_tried = true;
755745
} else {
756-
// use a new node
757-
double fChanceFactor = 1.0;
758-
while (1) {
759-
// Pick a new bucket, and an initial position in that bucket.
760-
int nUBucket = insecure_rand.randrange(ADDRMAN_NEW_BUCKET_COUNT);
761-
int nUBucketPos = insecure_rand.randrange(ADDRMAN_BUCKET_SIZE);
762-
// Iterate over the positions of that bucket, starting at the initial one,
763-
// and looping around.
764-
int i;
765-
for (i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
766-
if (vvNew[nUBucket][(nUBucketPos + i) % ADDRMAN_BUCKET_SIZE] != -1) break;
767-
}
768-
// If the bucket is entirely empty, start over with a (likely) different one.
769-
if (i == ADDRMAN_BUCKET_SIZE) continue;
770-
// Find the entry to return.
771-
int nId = vvNew[nUBucket][(nUBucketPos + i) % ADDRMAN_BUCKET_SIZE];
772-
const auto it_found{mapInfo.find(nId)};
773-
assert(it_found != mapInfo.end());
774-
const AddrInfo& info{it_found->second};
775-
// With probability GetChance() * fChanceFactor, return the entry.
776-
if (insecure_rand.randbits(30) < fChanceFactor * info.GetChance() * (1 << 30)) {
777-
LogPrint(BCLog::ADDRMAN, "Selected %s from new\n", info.ToStringAddrPort());
778-
return {info, info.m_last_try};
746+
search_tried = insecure_rand.randbool();
747+
}
748+
749+
const int bucket_count{search_tried ? ADDRMAN_TRIED_BUCKET_COUNT : ADDRMAN_NEW_BUCKET_COUNT};
750+
751+
// Loop through the addrman table until we find an appropriate entry
752+
double chance_factor = 1.0;
753+
while (1) {
754+
// Pick a bucket, and an initial position in that bucket.
755+
int bucket = insecure_rand.randrange(bucket_count);
756+
int initial_position = insecure_rand.randrange(ADDRMAN_BUCKET_SIZE);
757+
758+
// Iterate over the positions of that bucket, starting at the initial one,
759+
// and looping around.
760+
int i;
761+
for (i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
762+
int position = (initial_position + i) % ADDRMAN_BUCKET_SIZE;
763+
int node_id = GetEntry(search_tried, bucket, position);
764+
if (node_id != -1) {
765+
if (network.has_value()) {
766+
const auto it{mapInfo.find(node_id)};
767+
assert(it != mapInfo.end());
768+
const auto info{it->second};
769+
if (info.GetNetwork() == *network) break;
770+
} else {
771+
break;
772+
}
779773
}
780-
// Otherwise start over with a (likely) different bucket, and increased chance factor.
781-
fChanceFactor *= 1.2;
782774
}
775+
776+
// If the bucket is entirely empty, start over with a (likely) different one.
777+
if (i == ADDRMAN_BUCKET_SIZE) continue;
778+
779+
// Find the entry to return.
780+
int position = (initial_position + i) % ADDRMAN_BUCKET_SIZE;
781+
int nId = GetEntry(search_tried, bucket, position);
782+
const auto it_found{mapInfo.find(nId)};
783+
assert(it_found != mapInfo.end());
784+
const AddrInfo& info{it_found->second};
785+
786+
// With probability GetChance() * chance_factor, return the entry.
787+
if (insecure_rand.randbits(30) < chance_factor * info.GetChance() * (1 << 30)) {
788+
LogPrint(BCLog::ADDRMAN, "Selected %s from %s\n", info.ToStringAddrPort(), search_tried ? "tried" : "new");
789+
return {info, info.m_last_try};
790+
}
791+
792+
// Otherwise start over with a (likely) different bucket, and increased chance factor.
793+
chance_factor *= 1.2;
794+
}
795+
}
796+
797+
int AddrManImpl::GetEntry(bool use_tried, size_t bucket, size_t position) const
798+
{
799+
AssertLockHeld(cs);
800+
801+
assert(position < ADDRMAN_BUCKET_SIZE);
802+
803+
if (use_tried) {
804+
assert(bucket < ADDRMAN_TRIED_BUCKET_COUNT);
805+
return vvTried[bucket][position];
806+
} else {
807+
assert(bucket < ADDRMAN_NEW_BUCKET_COUNT);
808+
return vvNew[bucket][position];
783809
}
784810
}
785811

@@ -1164,11 +1190,11 @@ std::pair<CAddress, NodeSeconds> AddrManImpl::SelectTriedCollision()
11641190
return ret;
11651191
}
11661192

1167-
std::pair<CAddress, NodeSeconds> AddrManImpl::Select(bool newOnly) const
1193+
std::pair<CAddress, NodeSeconds> AddrManImpl::Select(bool new_only, std::optional<Network> network) const
11681194
{
11691195
LOCK(cs);
11701196
Check();
1171-
auto addrRet = Select_(newOnly);
1197+
auto addrRet = Select_(new_only, network);
11721198
Check();
11731199
return addrRet;
11741200
}
@@ -1262,9 +1288,9 @@ std::pair<CAddress, NodeSeconds> AddrMan::SelectTriedCollision()
12621288
return m_impl->SelectTriedCollision();
12631289
}
12641290

1265-
std::pair<CAddress, NodeSeconds> AddrMan::Select(bool newOnly) const
1291+
std::pair<CAddress, NodeSeconds> AddrMan::Select(bool new_only, std::optional<Network> network) const
12661292
{
1267-
return m_impl->Select(newOnly);
1293+
return m_impl->Select(new_only, network);
12681294
}
12691295

12701296
std::vector<CAddress> AddrMan::GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const

src/addrman.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,14 @@ class AddrMan
146146
/**
147147
* Choose an address to connect to.
148148
*
149-
* @param[in] newOnly Whether to only select addresses from the new table.
149+
* @param[in] new_only Whether to only select addresses from the new table. Passing `true` returns
150+
* an address from the new table or an empty pair. Passing `false` will return an
151+
* address from either the new or tried table (it does not guarantee a tried entry).
152+
* @param[in] network Select only addresses of this network (nullopt = all)
150153
* @return CAddress The record for the selected peer.
151154
* seconds The last time we attempted to connect to that peer.
152155
*/
153-
std::pair<CAddress, NodeSeconds> Select(bool newOnly = false) const;
156+
std::pair<CAddress, NodeSeconds> Select(bool new_only = false, std::optional<Network> network = std::nullopt) const;
154157

155158
/**
156159
* Return all or many randomly selected addresses, optionally by network.

src/addrman_impl.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class AddrInfo : public CAddress
9090
}
9191

9292
//! Calculate in which position of a bucket to store this entry.
93-
int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const;
93+
int GetBucketPosition(const uint256 &nKey, bool fNew, int bucket) const;
9494

9595
//! Determine whether the statistics about this entry are bad enough so that it can just be deleted
9696
bool IsTerrible(NodeSeconds now = Now<NodeSeconds>()) const;
@@ -127,7 +127,7 @@ class AddrManImpl
127127

128128
std::pair<CAddress, NodeSeconds> SelectTriedCollision() EXCLUSIVE_LOCKS_REQUIRED(!cs);
129129

130-
std::pair<CAddress, NodeSeconds> Select(bool newOnly) const
130+
std::pair<CAddress, NodeSeconds> Select(bool new_only, std::optional<Network> network) const
131131
EXCLUSIVE_LOCKS_REQUIRED(!cs);
132132

133133
std::vector<CAddress> GetAddr(size_t max_addresses, size_t max_pct, std::optional<Network> network) const
@@ -251,7 +251,13 @@ class AddrManImpl
251251

252252
void Attempt_(const CService& addr, bool fCountFailure, NodeSeconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
253253

254-
std::pair<CAddress, NodeSeconds> Select_(bool newOnly) const EXCLUSIVE_LOCKS_REQUIRED(cs);
254+
std::pair<CAddress, NodeSeconds> Select_(bool new_only, std::optional<Network> network) const EXCLUSIVE_LOCKS_REQUIRED(cs);
255+
256+
/** Helper to generalize looking up an addrman entry from either table.
257+
*
258+
* @return int The nid of the entry or -1 if the addrman position is empty.
259+
* */
260+
int GetEntry(bool use_tried, size_t bucket, size_t position) const EXCLUSIVE_LOCKS_REQUIRED(cs);
255261

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

src/bench/addrman.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <addrman.h>
66
#include <bench/bench.h>
7+
#include <netbase.h>
78
#include <netgroup.h>
89
#include <random.h>
910
#include <util/check.h>
@@ -71,6 +72,20 @@ static void FillAddrMan(AddrMan& addrman)
7172
AddAddressesToAddrMan(addrman);
7273
}
7374

75+
static CNetAddr ResolveIP(const std::string& ip)
76+
{
77+
CNetAddr addr;
78+
LookupHost(ip, addr, false);
79+
return addr;
80+
}
81+
82+
static CService ResolveService(const std::string& ip, uint16_t port = 0)
83+
{
84+
CService serv;
85+
Lookup(ip, serv, port, false);
86+
return serv;
87+
}
88+
7489
/* Benchmarks */
7590

7691
static void AddrManAdd(benchmark::Bench& bench)
@@ -95,6 +110,41 @@ static void AddrManSelect(benchmark::Bench& bench)
95110
});
96111
}
97112

113+
// The worst case performance of the Select() function is when there is only
114+
// one address on the table, because it linearly searches every position of
115+
// several buckets before identifying the correct bucket
116+
static void AddrManSelectFromAlmostEmpty(benchmark::Bench& bench)
117+
{
118+
AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO};
119+
120+
// Add one address to the new table
121+
CService addr = ResolveService("250.3.1.1", 8333);
122+
addrman.Add({CAddress(addr, NODE_NONE)}, ResolveService("250.3.1.1", 8333));
123+
124+
bench.run([&] {
125+
(void)addrman.Select();
126+
});
127+
}
128+
129+
static void AddrManSelectByNetwork(benchmark::Bench& bench)
130+
{
131+
AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO};
132+
133+
// add single I2P address to new table
134+
CService i2p_service;
135+
i2p_service.SetSpecial("udhdrtrcetjm5sxzskjyr5ztpeszydbh4dpl3pl4utgqqw2v4jna.b32.i2p");
136+
CAddress i2p_address(i2p_service, NODE_NONE);
137+
i2p_address.nTime = Now<NodeSeconds>();
138+
CNetAddr source = ResolveIP("252.2.2.2");
139+
addrman.Add({i2p_address}, source);
140+
141+
FillAddrMan(addrman);
142+
143+
bench.run([&] {
144+
(void)addrman.Select(/*new_only=*/false, NET_I2P);
145+
});
146+
}
147+
98148
static void AddrManGetAddr(benchmark::Bench& bench)
99149
{
100150
AddrMan addrman{EMPTY_NETGROUPMAN, /*deterministic=*/false, ADDRMAN_CONSISTENCY_CHECK_RATIO};
@@ -135,5 +185,7 @@ static void AddrManAddThenGood(benchmark::Bench& bench)
135185

136186
BENCHMARK(AddrManAdd, benchmark::PriorityLevel::HIGH);
137187
BENCHMARK(AddrManSelect, benchmark::PriorityLevel::HIGH);
188+
BENCHMARK(AddrManSelectFromAlmostEmpty, benchmark::PriorityLevel::HIGH);
189+
BENCHMARK(AddrManSelectByNetwork, benchmark::PriorityLevel::HIGH);
138190
BENCHMARK(AddrManGetAddr, benchmark::PriorityLevel::HIGH);
139191
BENCHMARK(AddrManAddThenGood, benchmark::PriorityLevel::HIGH);

0 commit comments

Comments
 (0)