addrLocal
with the address they gave us when self-advertising to an inbound peer. But if we don’t also change nTime
again from the default TIME_INIT
, our peer will not relay our advertised address any further.
addrLocal
with the address they gave us when self-advertising to an inbound peer. But if we don’t also change nTime
again from the default TIME_INIT
, our peer will not relay our advertised address any further.
good catch. could be a good functional test?
I think that would be hard, because the local addresses in the functional tests are unroutable, so nodes don’t self-advertise there. There is even the workaround of the -addrmantest
parameter used in p2p_node_network_limited.py
for this, but that just returns a fixed addr so the code changed here isn’t executed.
However, the unit test net_tests/get_local_addr_for_peer_port
covers this (and would fail without the update in this PR). Before, it would just check that the expected and actual CAddress have a default-initialized nTime.
250@@ -251,6 +251,7 @@ std::optional<CAddress> GetLocalAddrForPeer(CNode *pnode)
251 // For inbound connections, assume both the address and the port
252 // as seen from the peer.
253 addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices};
254+ addrLocal.nTime = GetAdjustedTime();
Here we want to preserve the nTime
as it was before the addrLocal =
assignment. So it is better to use addrLocal.nTime
instead of GetAdjustedTime()
because using the latter seeps knowledge that GetLocalAddress()
has set nTime
to GetAdjustedTime()
and so if that is changed to set it to e.g. GetFooTime()
, this line would have to be changed too.
Luckily CAddress
has a constructor that takes nTime
which is handy:
0addrLocal = CAddress{pnode->GetAddrLocal(), addrLocal.nServices, addrLocal.nTime};
677@@ -678,7 +678,10 @@ BOOST_AUTO_TEST_CASE(get_local_addr_for_peer_port)
678 // Our address:port as seen from the peer, completely different from the above.
679 in_addr peer_us_addr;
680 peer_us_addr.s_addr = htonl(0x02030405);
681- const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK};
682+ CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK};
683+ int64_t current_time{GetAdjustedTime()};
684+ SetMockTime(current_time);
685+ peer_us.nTime = current_time;
These changes would not be needed if the test below is fixed to just compare the address and port - that is/was the intention, but in the last check:
0BOOST_CHECK(*chosen_local_addr == peer_us);
it ends up comparing to CAddress
objects which also compares time and services. That is not intended. Better change it to:
0BOOST_CHECK(static_cast<CService>(*chosen_local_addr) == peer_us);
or even more obvious:
0BOOST_CHECK(static_cast<CService>(*chosen_local_addr) == static_cast<CService>(peer_us));
CAddress
, especially with the difficulties of writing functional test for self-advertisements?
I know it is not the main intention for this test, but I think it is nice to have this tested on the side, especially with the footgunny CAddress(CService, ServiceFlags)
ctor that is used so frequently, probably to avoid int64_t
/ uint32_t
compiler warnings when inserting a int64_t
time into the full ctor with nTime
.
So maybe it would be better to instead also raise the earlier check to the Level of CAddress
?
Alright, it is good to test more, even though this test is targeted at testing the ports stuff. IMO it is better for one test to test one thing, so maybe create a new test that just calls GetLocalAddrForPeer()
without the ports complications from the get_local_addr_for_peer_port
test. Anyway, up to you. If the existent test is to be modified, then I guess something like this should suffice:
0 m_node.args->ForceSetArg("-bind", strprintf("3.4.5.6:%u", bind_port));
1
2+ const uint32_t current_time = static_cast<uint32_t>(GetAdjustedTime());
3+
4+ SetMockTime(current_time);
5+
6 // Our address:port as seen from the peer, completely different from the above.
7 in_addr peer_us_addr;
8 peer_us_addr.s_addr = htonl(0x02030405);
9- const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK};
10+ const CAddress peer_us{CService{peer_us_addr, 20002}, NODE_NETWORK, current_time};
11
12 // Create a peer with a routable IPv4 address (outbound).
expected
to the level of CAddress
. I think it’s natural for a unit test of a function that returns an object to check the entire object is as expected, and not just parts of it, especially if it’s just two more fields and a (thorough) separate test that would have caught this issue would have required to replicate some of the port stuff in order to also get to the branch where nTime was left default-initialized.
Concept ACK
The proficiency of the engineers is just astonishing in this community (@mzumsande)! How the hell did you catch this?
The proficiency of the engineers is just astonishing in this community (@mzumsande)! How the hell did you catch this?
Thank you! Pretty much by chance - I was reviewing #25303 and while looking at this commit msg I was wondering if it was possible to somehow have addresses with default-initialised nTime in addrman or addr relay - that’s how I stumbled upon this.
If we self-advertised to an inbound peer with the address they gave us,
nTime was left default-initialized, so that our peer wouldn't relay it
any further along.
ACK 99b9e5f3a9fa29bbc1e45fc958470fbcc207ef23
Thank you!
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
No conflicts as of last run.