Skip to content

Commit 223ad2f

Browse files
author
merge-script
committed
Merge bitcoin/bitcoin#22831: test: add addpeeraddress "tried", test addrman checks on restart with asmap
cdaab90 Add test for addrman consistency check on restart with asmap (Jon Atack) 869f136 Add test for rpc addpeeraddress with "tried" argument (Jon Atack) ef242f5 Allow passing "tried" to rpc addpeeraddress to call CAddrMan::Good() (Jon Atack) Pull request description: This pull adds a `tried` argument to RPC addpeeraddress and a regression test for the recent addrman/asmap changes and issue. PR #22697 introduced a reproducible bug in commit 181a120 that fails addrman consistency checks and causes it to significantly lose peer entries when the `-asmap` configuration option is used. The issue occurs upon bitcoind restart due to an initialization order change in `src/init.cpp` in that commit, whereby CAddrman asmap is set after deserializing `peers.dat`, rather than before. Issue reported on the `#bitcoin-core-dev` IRC channel starting at https://www.erisian.com.au/bitcoin-core-dev/log-2021-08-23.html#l-263. ``` addrman lost 22813 new and 2 tried addresses due to collisions or invalid addresses ADDRMAN CONSISTENCY CHECK FAILED!!! err=-17 bitcoind: ./addrman.h:707: void CAddrMan::Check() const: Assertion `false' failed. Aborted ``` How to reproduce: - `git checkout 181a120`, build, and launch bitcoind with the `-asmap` and `-checkaddrman=1` configuration options enabled - restart bitcoind - bitcoind aborts on the second call to the addrman consistency checks in `CAddrMan::Check()` How to test this pull: - `git checkout 181a120`, cherry pick the first commit of this branch, build, git checkout this branch, run `test/functional/rpc_net.py`, which should pass, and then run `test/functional/feature_asmap.py`, which should fail with the following output: ``` AssertionError: Unexpected stderr bitcoind: ./addrman.h:739: void CAddrMan::Check() const: Assertion `false' failed. ``` ACKs for top commit: jnewbery: utACK cdaab90 mzumsande: re-ACK cdaab90 (based on code review of diff to d586817) vasild: ACK cdaab90 Tree-SHA512: 0251a18fea629b62486fc907d7ab0e96c6df6fadb9e4d62cff018bc681afb6ac31e0e7258809c0a88f91e4a36c4fb0b16ed294ce47ef30585217de89c3342399
2 parents 0c1a393 + cdaab90 commit 223ad2f

File tree

4 files changed

+65
-14
lines changed

4 files changed

+65
-14
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
192192
{ "unloadwallet", 1, "load_on_startup"},
193193
{ "getnodeaddresses", 0, "count"},
194194
{ "addpeeraddress", 1, "port"},
195+
{ "addpeeraddress", 2, "tried"},
195196
{ "stop", 0, "wait" },
196197
};
197198
// clang-format on

src/rpc/net.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ static RPCHelpMan addpeeraddress()
921921
{
922922
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The IP address of the peer"},
923923
{"port", RPCArg::Type::NUM, RPCArg::Optional::NO, "The port of the peer"},
924+
{"tried", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, attempt to add the peer to the tried addresses table"},
924925
},
925926
RPCResult{
926927
RPCResult::Type::OBJ, "", "",
@@ -929,8 +930,8 @@ static RPCHelpMan addpeeraddress()
929930
},
930931
},
931932
RPCExamples{
932-
HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333")
933-
+ HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333")
933+
HelpExampleCli("addpeeraddress", "\"1.2.3.4\" 8333 true")
934+
+ HelpExampleRpc("addpeeraddress", "\"1.2.3.4\", 8333, true")
934935
},
935936
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
936937
{
@@ -941,6 +942,7 @@ static RPCHelpMan addpeeraddress()
941942

942943
const std::string& addr_string{request.params[0].get_str()};
943944
const uint16_t port{static_cast<uint16_t>(request.params[1].get_int())};
945+
const bool tried{request.params[2].isTrue()};
944946

945947
UniValue obj(UniValue::VOBJ);
946948
CNetAddr net_addr;
@@ -951,7 +953,13 @@ static RPCHelpMan addpeeraddress()
951953
address.nTime = GetAdjustedTime();
952954
// The source address is set equal to the address. This is equivalent to the peer
953955
// announcing itself.
954-
if (node.addrman->Add({address}, address)) success = true;
956+
if (node.addrman->Add({address}, address)) {
957+
success = true;
958+
if (tried) {
959+
// Attempt to move the address to the tried addresses table.
960+
node.addrman->Good(address);
961+
}
962+
}
955963
}
956964

957965
obj.pushKV("success", success);

test/functional/feature_asmap.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
1515
4. `bitcoind -asmap/-asmap=` with no file specified, using the default asmap
1616
17-
5. `bitcoind -asmap` with no file specified and a missing default asmap file
17+
5. `bitcoind -asmap` restart with an addrman containing new and tried entries
1818
19-
6. `bitcoind -asmap` with an empty (unparsable) default asmap file
19+
6. `bitcoind -asmap` with no file specified and a missing default asmap file
20+
21+
7. `bitcoind -asmap` with an empty (unparsable) default asmap file
2022
2123
The tests are order-independent.
2224
@@ -37,6 +39,12 @@ def expected_messages(filename):
3739
class AsmapTest(BitcoinTestFramework):
3840
def set_test_params(self):
3941
self.num_nodes = 1
42+
self.extra_args = [["-checkaddrman=1"]] # Do addrman checks on all operations.
43+
44+
def fill_addrman(self, node_id):
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]]:
47+
self.nodes[node_id].addpeeraddress(address=f"101.{addr}.0.0", tried=tried, port=8333)
4048

4149
def test_without_asmap_arg(self):
4250
self.log.info('Test bitcoind with no -asmap arg passed')
@@ -72,6 +80,22 @@ def test_default_asmap(self):
7280
self.start_node(0, [arg])
7381
os.remove(self.default_asmap)
7482

83+
def test_asmap_interaction_with_addrman_containing_entries(self):
84+
self.log.info("Test bitcoind -asmap restart with addrman containing new and tried entries")
85+
self.stop_node(0)
86+
shutil.copyfile(self.asmap_raw, self.default_asmap)
87+
self.start_node(0, ["-asmap", "-checkaddrman=1"])
88+
self.fill_addrman(node_id=0)
89+
self.restart_node(0, ["-asmap", "-checkaddrman=1"])
90+
with self.node.assert_debug_log(
91+
expected_msgs=[
92+
"Addrman checks started: new 2, tried 2, total 4",
93+
"Addrman checks completed successfully",
94+
]
95+
):
96+
self.node.getnodeaddresses() # getnodeaddresses re-runs the addrman checks
97+
os.remove(self.default_asmap)
98+
7599
def test_default_asmap_with_missing_file(self):
76100
self.log.info('Test bitcoind -asmap with missing default map file')
77101
self.stop_node(0)
@@ -97,6 +121,7 @@ def run_test(self):
97121
self.test_asmap_with_absolute_path()
98122
self.test_asmap_with_relative_path()
99123
self.test_default_asmap()
124+
self.test_asmap_interaction_with_addrman_containing_entries()
100125
self.test_default_asmap_with_missing_file()
101126
self.test_empty_asmap()
102127

test/functional/rpc_net.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,16 @@ def test_getnodeaddresses(self):
239239
assert_raises_rpc_error(-8, "Network not recognized: Foo", self.nodes[0].getnodeaddresses, 1, "Foo")
240240

241241
def test_addpeeraddress(self):
242+
"""RPC addpeeraddress sets the source address equal to the destination address.
243+
If an address with the same /16 as an existing new entry is passed, it will be
244+
placed in the same new bucket and have a 1/64 chance of the bucket positions
245+
colliding (depending on the value of nKey in the addrman), in which case the
246+
new address won't be added. The probability of collision can be reduced to
247+
1/2^16 = 1/65536 by using an address from a different /16. We avoid this here
248+
by first testing adding a tried table entry before testing adding a new table one.
249+
"""
242250
self.log.info("Test addpeeraddress")
251+
self.restart_node(1, ["-checkaddrman=1"])
243252
node = self.nodes[1]
244253

245254
self.log.debug("Test that addpeerinfo is a hidden RPC")
@@ -251,17 +260,25 @@ def test_addpeeraddress(self):
251260
assert_equal(node.addpeeraddress(address="", port=8333), {"success": False})
252261
assert_equal(node.getnodeaddresses(count=0), [])
253262

254-
self.log.debug("Test that adding a valid address succeeds")
255-
assert_equal(node.addpeeraddress(address="1.2.3.4", port=8333), {"success": True})
256-
addrs = node.getnodeaddresses(count=0)
257-
assert_equal(len(addrs), 1)
258-
assert_equal(addrs[0]["address"], "1.2.3.4")
259-
assert_equal(addrs[0]["port"], 8333)
260-
261-
self.log.debug("Test that adding the same address again when already present fails")
262-
assert_equal(node.addpeeraddress(address="1.2.3.4", port=8333), {"success": False})
263+
self.log.debug("Test that adding a valid address to the tried table succeeds")
264+
assert_equal(node.addpeeraddress(address="1.2.3.4", tried=True, port=8333), {"success": True})
265+
with node.assert_debug_log(expected_msgs=["Addrman checks started: new 0, tried 1, total 1"]):
266+
addrs = node.getnodeaddresses(count=0) # getnodeaddresses re-runs the addrman checks
267+
assert_equal(len(addrs), 1)
268+
assert_equal(addrs[0]["address"], "1.2.3.4")
269+
assert_equal(addrs[0]["port"], 8333)
270+
271+
self.log.debug("Test that adding an already-present tried address to the new and tried tables fails")
272+
for value in [True, False]:
273+
assert_equal(node.addpeeraddress(address="1.2.3.4", tried=value, port=8333), {"success": False})
263274
assert_equal(len(node.getnodeaddresses(count=0)), 1)
264275

276+
self.log.debug("Test that adding a second address, this time to the new table, succeeds")
277+
assert_equal(node.addpeeraddress(address="2.0.0.0", port=8333), {"success": True})
278+
with node.assert_debug_log(expected_msgs=["Addrman checks started: new 1, tried 1, total 2"]):
279+
addrs = node.getnodeaddresses(count=0) # getnodeaddresses re-runs the addrman checks
280+
assert_equal(len(addrs), 2)
281+
265282

266283
if __name__ == '__main__':
267284
NetTest().main()

0 commit comments

Comments
 (0)