Yes, that works. Here it is:
0diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp
1index f24509dd97..105f4e4487 100644
2--- a/src/test/net_tests.cpp
3+++ b/src/test/net_tests.cpp
4@@ -11,12 +11,14 @@
5 #include <netaddress.h>
6 #include <netbase.h>
7 #include <netmessagemaker.h>
8 #include <serialize.h>
9 #include <span.h>
10 #include <streams.h>
11+#include <test/util/logging.h>
12+#include <test/util/net.h>
13 #include <test/util/setup_common.h>
14 #include <test/util/validation.h>
15 #include <timedata.h>
16 #include <util/strencodings.h>
17 #include <util/string.h>
18 #include <util/system.h>
19@@ -902,7 +904,213 @@ BOOST_AUTO_TEST_CASE(initial_advertise_from_version_message)
20 // PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state
21 // in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset
22 // that state as it was before our test was run.
23 TestOnlyResetTimeData();
24 }
25
26+BOOST_AUTO_TEST_CASE(initial_messages_sent)
27+{
28+ LOCK(NetEventsInterface::g_msgproc_mutex);
29+
30+ m_node.args->ForceSetArg("-capturemessages", "1");
31+
32+ auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
33+ auto& peerman = *m_node.peerman;
34+
35+ CNode outbound_peer{
36+ /*id=*/NodeId{0},
37+ /*sock=*/nullptr,
38+ /*addrIn=*/CAddress{CService{CNetAddr{in_addr{.s_addr = htonl(0x01020304)}}, 8333}, NODE_NONE},
39+ /*nKeyedNetGroupIn=*/0,
40+ /*nLocalHostNonceIn=*/0,
41+ /*addrBindIn=*/CAddress{},
42+ /*addrNameIn=*/"",
43+ /*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
44+ /*inbound_onion=*/false};
45+
46+ std::unordered_map<std::string, size_t> count_sent_messages;
47+
48+ const auto CaptureMessageOrig = CaptureMessage;
49+ CaptureMessage = [&count_sent_messages](const CAddress& addr,
50+ const std::string& msg_type,
51+ Span<const unsigned char> data,
52+ bool is_incoming) -> void {
53+ if (!is_incoming) {
54+ count_sent_messages[msg_type]++;
55+ }
56+ };
57+
58+ connman.Handshake(
59+ /*node=*/outbound_peer,
60+ /*successfully_connected=*/true,
61+ /*remote_services=*/ServiceFlags{NODE_NETWORK | NODE_WITNESS},
62+ /*local_services=*/ServiceFlags{NODE_NETWORK | NODE_WITNESS},
63+ /*version=*/PROTOCOL_VERSION,
64+ /*relay_txs=*/true);
65+
66+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::VERSION], 1);
67+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::WTXIDRELAY], 1);
68+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::SENDADDRV2], 1);
69+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::VERACK], 1);
70+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::GETADDR], 1);
71+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::SENDCMPCT], 1);
72+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::PING], 1);
73+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::GETHEADERS], 1);
74+ BOOST_CHECK_EQUAL(count_sent_messages[NetMsgType::FEEFILTER], 1);
75+
76+ peerman.FinalizeNode(outbound_peer);
77+
78+ CaptureMessage = CaptureMessageOrig;
79+ m_node.args->ForceSetArg("-capturemessages", "0");
80+ // PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state
81+ // in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset
82+ // that state as it was before our test was run.
83+ TestOnlyResetTimeData();
84+}
85+
86+BOOST_AUTO_TEST_CASE(pingpong)
87+{
88+ // See PING_INTERVAL in net_processing.cpp
89+ static constexpr auto PING_INTERVAL{2min};
90+
91+ m_node.args->ForceSetArg("-capturemessages", "1");
92+
93+ auto& connman = static_cast<ConnmanTestMsg&>(*m_node.connman);
94+ auto& peerman = *m_node.peerman;
95+
96+ CNode outbound_peer{
97+ /*id=*/NodeId{0},
98+ /*sock=*/nullptr,
99+ /*addrIn=*/CAddress{CService{CNetAddr{in_addr{.s_addr = htonl(0x01020304)}}, 8333}, NODE_NONE},
100+ /*nKeyedNetGroupIn=*/0,
101+ /*nLocalHostNonceIn=*/0,
102+ /*addrBindIn=*/CAddress{},
103+ /*addrNameIn=*/"",
104+ /*conn_type_in=*/ConnectionType::OUTBOUND_FULL_RELAY,
105+ /*inbound_onion=*/false};
106+
107+ auto CheckPingTimes = [&peerman, &outbound_peer](std::chrono::microseconds min_ping_time,
108+ std::chrono::microseconds last_ping_time,
109+ std::chrono::microseconds ping_wait) {
110+ // Check min and last ping times
111+ BOOST_CHECK_EQUAL(outbound_peer.m_min_ping_time.load().count(), min_ping_time.count());
112+ BOOST_CHECK_EQUAL(outbound_peer.m_last_ping_time.load().count(), last_ping_time.count());
113+
114+ // Check if and how long current ping has been pending
115+ CNodeStateStats stats;
116+ BOOST_REQUIRE(peerman.GetNodeStateStats(outbound_peer.GetId(), stats));
117+ BOOST_CHECK_EQUAL(stats.m_ping_wait.count(), ping_wait.count());
118+ };
119+
120+ uint64_t ping_nonce_sent;
121+
122+ const auto CaptureMessageOrig = CaptureMessage;
123+ CaptureMessage = [&ping_nonce_sent](const CAddress& addr,
124+ const std::string& msg_type,
125+ Span<const unsigned char> data,
126+ bool is_incoming) {
127+ if (!is_incoming && msg_type == NetMsgType::PING) {
128+ CDataStream stream{data, SER_NETWORK, PROTOCOL_VERSION};
129+ stream >> ping_nonce_sent;
130+ }
131+ };
132+
133+ const auto SendPing = [&peerman, &outbound_peer, &ping_nonce_sent]() EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) {
134+ SetMockTime(GetMockTime() + PING_INTERVAL + 1s);
135+ ping_nonce_sent = 0;
136+ peerman.SendMessages(&outbound_peer);
137+ BOOST_REQUIRE(ping_nonce_sent != 0);
138+ };
139+
140+ const auto ReceiveAndProcessMessage = [&connman, &outbound_peer](CSerializedNetMsg& msg) EXCLUSIVE_LOCKS_REQUIRED(NetEventsInterface::g_msgproc_mutex) {
141+ connman.ReceiveMsgFrom(outbound_peer, msg);
142+ // The send buffer CConnman::nSendBufferMaxSize happens to be 0 during tests which
143+ // trips fPauseSend to be set to true which cancels processing of incoming messages.
144+ outbound_peer.fPauseSend = false;
145+ connman.ProcessMessagesOnce(outbound_peer);
146+ };
147+
148+ // Use mock time to control pings from node
149+ SetMockTime(GetTime());
150+
151+ ping_nonce_sent = 0;
152+
153+ LOCK(NetEventsInterface::g_msgproc_mutex);
154+
155+ connman.Handshake(
156+ /*node=*/outbound_peer,
157+ /*successfully_connected=*/true,
158+ /*remote_services=*/ServiceFlags{NODE_NETWORK | NODE_WITNESS},
159+ /*local_services=*/ServiceFlags{NODE_NETWORK | NODE_WITNESS},
160+ /*version=*/PROTOCOL_VERSION,
161+ /*relay_txs=*/true);
162+ BOOST_REQUIRE(ping_nonce_sent != 0);
163+
164+ auto time_elapsed = 1s;
165+ SetMockTime(GetMockTime() + time_elapsed);
166+ // No pong response has been received and current ping is outstanding.
167+ CheckPingTimes(std::chrono::microseconds::max(), 0us, time_elapsed);
168+
169+ CSerializedNetMsg pong_msg;
170+
171+ BOOST_TEST_MESSAGE("Receiving a PONG without nonce cancels our PING");
172+ {
173+ ASSERT_DEBUG_LOG("Short payload");
174+ pong_msg = CNetMsgMaker{PROTOCOL_VERSION}.Make(NetMsgType::PONG);
175+ ReceiveAndProcessMessage(pong_msg);
176+ }
177+ // Ping metrics have not been updated and there is no ping outstanding.
178+ CheckPingTimes(std::chrono::microseconds::max(), 0us, 0us);
179+
180+ BOOST_TEST_MESSAGE("Receiving an unrequested PONG is logged and ignored");
181+ {
182+ ASSERT_DEBUG_LOG("Unsolicited pong without ping");
183+ pong_msg = CNetMsgMaker{PROTOCOL_VERSION}.Make(NetMsgType::PONG, /*nonce=*/(uint64_t)0);
184+ ReceiveAndProcessMessage(pong_msg);
185+ }
186+ // Ping metrics have not been updated and there is no ping outstanding.
187+ CheckPingTimes(std::chrono::microseconds::max(), 0us, 0us);
188+
189+ SendPing();
190+
191+ BOOST_TEST_MESSAGE("Receiving a PONG with the wrong nonce does not cancel our PING");
192+ {
193+ ASSERT_DEBUG_LOG("Nonce mismatch");
194+ pong_msg = CNetMsgMaker{PROTOCOL_VERSION}.Make(NetMsgType::PONG, /*nonce=*/ping_nonce_sent + 1);
195+ ReceiveAndProcessMessage(pong_msg);
196+ }
197+ // Ping metrics have not been updated and there is an outstanding ping.
198+ time_elapsed = 5s;
199+ SetMockTime(GetMockTime() + time_elapsed);
200+ CheckPingTimes(std::chrono::microseconds::max(), 0us, time_elapsed);
201+
202+ BOOST_TEST_MESSAGE("Receiving a PONG with nonce=0 cancels our PING");
203+ {
204+ ASSERT_DEBUG_LOG("Nonce zero");
205+ pong_msg = CNetMsgMaker{PROTOCOL_VERSION}.Make(NetMsgType::PONG, /*nonce=*/(uint64_t)0);
206+ ReceiveAndProcessMessage(pong_msg);
207+ }
208+ // Ping metrics have not been updated and there is no ping outstanding.
209+ CheckPingTimes(std::chrono::microseconds::max(), 0us, 0us);
210+
211+ SendPing();
212+
213+ time_elapsed = 5s;
214+ SetMockTime(GetMockTime() + time_elapsed);
215+
216+ BOOST_TEST_MESSAGE("Receiving a PONG with the correct nonce cancels our PING");
217+ pong_msg = CNetMsgMaker{PROTOCOL_VERSION}.Make(NetMsgType::PONG, ping_nonce_sent);
218+ ReceiveAndProcessMessage(pong_msg);
219+ // Ping metrics have been updated and there is no ping outstanding.
220+ CheckPingTimes(time_elapsed, time_elapsed, 0us);
221+
222+ peerman.FinalizeNode(outbound_peer);
223+
224+ CaptureMessage = CaptureMessageOrig;
225+ m_node.args->ForceSetArg("-capturemessages", "0");
226+ // PeerManager::ProcessMessage() calls AddTimeData() which changes the internal state
227+ // in timedata.cpp and later confuses the test "timedata_tests/addtimedata". Thus reset
228+ // that state as it was before our test was run.
229+ TestOnlyResetTimeData();
230+}
231+
232 BOOST_AUTO_TEST_SUITE_END()