Skip to content

Commit a945f09

Browse files
committed
Merge bitcoin/bitcoin#29007: test: create deterministic addrman in the functional tests
2cc8ca1 [test] Use deterministic addrman in addrman info tests (stratospher) a897866 [test] Restart a node with empty addrman (stratospher) 71c1991 [test] Use deterministic addrman in addpeeraddress test (stratospher) 7b868e6 Revert "test: avoid non-determinism in asmap-addrman test" (stratospher) 69e091f [init] Create deterministic addrman in tests using -test=addrman (stratospher) be25ac3 [init] Remove -addrmantest command line arg (stratospher) 802e6e1 [init] Add new command line arg for use only in functional tests (stratospher) Pull request description: An address is placed in a `[bucket,position]` in the addrman table (new table or tried table) using the `addpeeraddress` RPC. This `[bucket,position]` is calculated using `nKey`(and other metrics) for the addrman which is chosen randomly during every run. Supposing there are 2 addresses to be placed in an addrman table. During every test run, a different `[bucket,position]` would be calculated for each address.These calculated `[bucket,position]` could even be the same for the 2 addresses in some test runs and result in collisions in the addrman. We wouldn't be able to predict when the collisions are going to happen because we can't predict the `nKey` value which is chosen at random. This can cause flaky tests. Because of these non deterministic collisions, we are limited in what we can do to test addrman functionality. Currently in our tests don't add a second address to prevent these collisions from happening - we only keep 1 address in the new table and 1 address in the tried table. See bitcoin/bitcoin#26988 (comment), bitcoin/bitcoin#23084, [#22831(comment)](https://github.com/bitcoin/bitcoin/pull/22831/files#r708302639). This PR lets us create a deterministic addrman with fixed `nKey` so that we can know the `[bucket,position]` collisions beforehand, safely add more addresses in an addrman table and write more extensive tests. ACKs for top commit: amitiuttarwar: ACK 2cc8ca1 achow101: ACK 2cc8ca1 0xB10C: ACK 2cc8ca1 mzumsande: Code Review ACK 2cc8ca1 Tree-SHA512: 8acd9bdfe7de1eb44d22373bf13533d8ecf602df966fdd5b8b78afcd8cc35a286c95d2712f67a89473a0d68dded7d38f5599f6e4bf95a6589475444545bfb189
2 parents 6dda050 + 2cc8ca1 commit a945f09

File tree

10 files changed

+156
-115
lines changed

10 files changed

+156
-115
lines changed

src/addrdb.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ void ReadFromStream(AddrMan& addr, DataStream& ssPeers)
189189
util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgroupman, const ArgsManager& args)
190190
{
191191
auto check_addrman = std::clamp<int32_t>(args.GetIntArg("-checkaddrman", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), 0, 1000000);
192-
auto addrman{std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman)};
192+
bool deterministic = HasTestOption(args, "addrman"); // use a deterministic addrman only for tests
193+
194+
auto addrman{std::make_unique<AddrMan>(netgroupman, /*deterministic=*/deterministic, /*consistency_check_ratio=*/check_addrman)};
193195

194196
const auto start{SteadyClock::now()};
195197
const auto path_addr{args.GetDataDirNet() / "peers.dat"};
@@ -198,15 +200,15 @@ util::Result<std::unique_ptr<AddrMan>> LoadAddrman(const NetGroupManager& netgro
198200
LogPrintf("Loaded %i addresses from peers.dat %dms\n", addrman->Size(), Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
199201
} catch (const DbNotFoundError&) {
200202
// Addrman can be in an inconsistent state after failure, reset it
201-
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
203+
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/deterministic, /*consistency_check_ratio=*/check_addrman);
202204
LogPrintf("Creating peers.dat because the file was not found (%s)\n", fs::quoted(fs::PathToString(path_addr)));
203205
DumpPeerAddresses(args, *addrman);
204206
} catch (const InvalidAddrManVersionError&) {
205207
if (!RenameOver(path_addr, (fs::path)path_addr + ".bak")) {
206208
return util::Error{strprintf(_("Failed to rename invalid peers.dat file. Please move or delete it and try again."))};
207209
}
208210
// Addrman can be in an inconsistent state after failure, reset it
209-
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/false, /*consistency_check_ratio=*/check_addrman);
211+
addrman = std::make_unique<AddrMan>(netgroupman, /*deterministic=*/deterministic, /*consistency_check_ratio=*/check_addrman);
210212
LogPrintf("Creating new peers.dat because the file version was not compatible (%s). Original backed up to peers.dat.bak\n", fs::quoted(fs::PathToString(path_addr)));
211213
DumpPeerAddresses(args, *addrman);
212214
} catch (const std::exception& e) {

src/common/args.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,18 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message
682682
std::string("\n\n");
683683
}
684684

685+
const std::vector<std::string> TEST_OPTIONS_DOC{
686+
"addrman (use deterministic addrman)",
687+
};
688+
689+
bool HasTestOption(const ArgsManager& args, const std::string& test_option)
690+
{
691+
const auto options = args.GetArgs("-test");
692+
return std::any_of(options.begin(), options.end(), [test_option](const auto& option) {
693+
return option == test_option;
694+
});
695+
}
696+
685697
fs::path GetDefaultDataDir()
686698
{
687699
// Windows: C:\Users\Username\AppData\Roaming\Bitcoin

src/common/args.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,11 @@ bool HelpRequested(const ArgsManager& args);
447447
/** Add help options to the args manager */
448448
void SetupHelpOptions(ArgsManager& args);
449449

450+
extern const std::vector<std::string> TEST_OPTIONS_DOC;
451+
452+
/** Checks if a particular test option is present in -test command-line arg options */
453+
bool HasTestOption(const ArgsManager& args, const std::string& test_option);
454+
450455
/**
451456
* Format a string to be used as group of options in help messages
452457
*

src/init.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ void SetupServerArgs(ArgsManager& argsman)
614614
argsman.AddArg("-limitancestorsize=<n>", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
615615
argsman.AddArg("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
616616
argsman.AddArg("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
617-
argsman.AddArg("-addrmantest", "Allows to test address relay on localhost", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
617+
argsman.AddArg("-test=<option>", "Pass a test-only option. Options include : " + Join(TEST_OPTIONS_DOC, ", ") + ".", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
618618
argsman.AddArg("-capturemessages", "Capture all P2P messages to disk", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
619619
argsman.AddArg("-mocktime=<n>", "Replace actual time with " + UNIX_EPOCH_TIME + " (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
620620
argsman.AddArg("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_BYTES >> 20), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
@@ -1028,6 +1028,22 @@ bool AppInitParameterInteraction(const ArgsManager& args)
10281028
if (args.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS))
10291029
nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
10301030

1031+
if (args.IsArgSet("-test")) {
1032+
if (chainparams.GetChainType() != ChainType::REGTEST) {
1033+
return InitError(Untranslated("-test=<option> should only be used in functional tests"));
1034+
}
1035+
const std::vector<std::string> options = args.GetArgs("-test");
1036+
for (const std::string& option : options) {
1037+
auto it = std::find_if(TEST_OPTIONS_DOC.begin(), TEST_OPTIONS_DOC.end(), [&option](const std::string& doc_option) {
1038+
size_t pos = doc_option.find(" (");
1039+
return (pos != std::string::npos) && (doc_option.substr(0, pos) == option);
1040+
});
1041+
if (it == TEST_OPTIONS_DOC.end()) {
1042+
InitWarning(strprintf(_("Unrecognised option \"%s\" provided in -test=<option>."), option));
1043+
}
1044+
}
1045+
}
1046+
10311047
// Also report errors from parsing before daemonization
10321048
{
10331049
kernel::Notifications notifications{};

src/net.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,6 @@ static int GetnScore(const CService& addr)
238238
std::optional<CService> GetLocalAddrForPeer(CNode& node)
239239
{
240240
CService addrLocal{GetLocalAddress(node)};
241-
if (gArgs.GetBoolArg("-addrmantest", false)) {
242-
// use IPv4 loopback during addrmantest
243-
addrLocal = CService(LookupNumeric("127.0.0.1", GetListenPort()));
244-
}
245241
// If discovery is enabled, sometimes give our peer the address it
246242
// tells us that it sees us as in case it has a better idea of our
247243
// address than we do.
@@ -261,8 +257,7 @@ std::optional<CService> GetLocalAddrForPeer(CNode& node)
261257
addrLocal.SetIP(node.GetAddrLocal());
262258
}
263259
}
264-
if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false))
265-
{
260+
if (addrLocal.IsRoutable()) {
266261
LogPrint(BCLog::NET, "Advertising address %s to peer=%d\n", addrLocal.ToStringAddrPort(), node.GetId());
267262
return addrLocal;
268263
}

test/functional/feature_addrman.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,7 @@ def run_test(self):
156156
)
157157

158158
self.log.info("Check that missing addrman is recreated")
159-
self.stop_node(0)
160-
os.remove(peers_dat)
161-
with self.nodes[0].assert_debug_log([
162-
f'Creating peers.dat because the file was not found ("{peers_dat}")',
163-
]):
164-
self.start_node(0)
159+
self.restart_node(0, clear_addrman=True)
165160
assert_equal(self.nodes[0].getnodeaddresses(), [])
166161

167162

test/functional/feature_asmap.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def set_test_params(self):
4242
self.extra_args = [["-checkaddrman=1"]] # Do addrman checks on all operations.
4343

4444
def fill_addrman(self, node_id):
45-
"""Add 1 tried address to the addrman, followed by 1 new address."""
46-
for addr, tried in [[0, True], [1, False]]:
45+
"""Add 2 tried addresses to the addrman, followed by 2 new addresses."""
46+
for addr, tried in [[0, True], [1, True], [2, False], [3, False]]:
4747
self.nodes[node_id].addpeeraddress(address=f"101.{addr}.0.0", tried=tried, port=8333)
4848

4949
def test_without_asmap_arg(self):
@@ -84,12 +84,12 @@ def test_asmap_interaction_with_addrman_containing_entries(self):
8484
self.log.info("Test bitcoind -asmap restart with addrman containing new and tried entries")
8585
self.stop_node(0)
8686
shutil.copyfile(self.asmap_raw, self.default_asmap)
87-
self.start_node(0, ["-asmap", "-checkaddrman=1"])
87+
self.start_node(0, ["-asmap", "-checkaddrman=1", "-test=addrman"])
8888
self.fill_addrman(node_id=0)
89-
self.restart_node(0, ["-asmap", "-checkaddrman=1"])
89+
self.restart_node(0, ["-asmap", "-checkaddrman=1", "-test=addrman"])
9090
with self.node.assert_debug_log(
9191
expected_msgs=[
92-
"CheckAddrman: new 1, tried 1, total 2 started",
92+
"CheckAddrman: new 2, tried 2, total 4 started",
9393
"CheckAddrman: completed",
9494
]
9595
):
@@ -114,7 +114,7 @@ def test_empty_asmap(self):
114114
def test_asmap_health_check(self):
115115
self.log.info('Test bitcoind -asmap logs ASMap Health Check with basic stats')
116116
shutil.copyfile(self.asmap_raw, self.default_asmap)
117-
msg = "ASMap Health Check: 2 clearnet peers are mapped to 1 ASNs with 0 peers being unmapped"
117+
msg = "ASMap Health Check: 4 clearnet peers are mapped to 3 ASNs with 0 peers being unmapped"
118118
with self.node.assert_debug_log(expected_msgs=[msg]):
119119
self.start_node(0, extra_args=['-asmap'])
120120
os.remove(self.default_asmap)

test/functional/p2p_node_network_limited.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
NODE_P2P_V2,
1616
NODE_WITNESS,
1717
msg_getdata,
18-
msg_verack,
1918
)
2019
from test_framework.p2p import P2PInterface
2120
from test_framework.test_framework import BitcoinTestFramework
@@ -47,7 +46,7 @@ class NodeNetworkLimitedTest(BitcoinTestFramework):
4746
def set_test_params(self):
4847
self.setup_clean_chain = True
4948
self.num_nodes = 3
50-
self.extra_args = [['-prune=550', '-addrmantest'], [], []]
49+
self.extra_args = [['-prune=550'], [], []]
5150

5251
def disconnect_all(self):
5352
self.disconnect_nodes(0, 1)
@@ -139,16 +138,6 @@ def run_test(self):
139138
self.log.info("Requesting block at height 2 (tip-289) must fail (ignored).")
140139
node.send_getdata_for_block(blocks[0]) # first block outside of the 288+2 limit
141140
node.wait_for_disconnect(5)
142-
143-
self.log.info("Check local address relay, do a fresh connection.")
144-
self.nodes[0].disconnect_p2ps()
145-
node1 = self.nodes[0].add_p2p_connection(P2PIgnoreInv())
146-
node1.send_message(msg_verack())
147-
148-
node1.wait_for_addr()
149-
#must relay address with NODE_NETWORK_LIMITED
150-
assert_equal(node1.firstAddrnServices, expected_services)
151-
152141
self.nodes[0].disconnect_p2ps()
153142

154143
# connect unsynced node 2 with pruned NODE_NETWORK_LIMITED peer

0 commit comments

Comments
 (0)