net, test: Virtualise CConnman and add initial PeerManager unit tests #25515

pull dergoegge wants to merge 4 commits into bitcoin:master from dergoegge:2022-06-virt-connman changing 5 files +397 −67
  1. dergoegge commented at 6:04 pm on June 30, 2022: member
    This PR creates an abstract ConnectionsInterface class that is used as an interface for interacting with the connection manager. The PeerManager is made to hold a reference to a ConnectionsInterface instead of CConnman, which makes it possible for us to mock the connection manager in the newly introduced PeerManager unit tests. Two initial unit tests are added for the version handshake and ping/pong logic.
  2. fanquake added the label P2P on Jun 30, 2022
  3. DrahtBot commented at 7:50 pm on June 30, 2022: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Reviews

    See the guideline for information on the review process.

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #26837 (I2P network optimizations by vasild)
    • #26441 (rpc, p2p: add addpermissionflags RPC and allow whitelisting outbound by brunoerg)
    • #25572 (refactor: Introduce EvictionManager and use it for the inbound eviction logic by dergoegge)
    • #25268 (refactor: Introduce EvictionManager by dergoegge)
    • #24748 (test/BIP324: functional tests for v2 P2P encryption by stratospher)
    • #24545 (BIP324: Enable v2 P2P encrypted transport by dhruv)
    • #24170 (p2p, rpc: Manual block-relay-only connections with addnode by mzumsande)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. dergoegge force-pushed on Jun 30, 2022
  5. dergoegge force-pushed on Jul 1, 2022
  6. dergoegge force-pushed on Jul 1, 2022
  7. jnewbery commented at 7:51 am on July 4, 2022: member
    Obvious concept ACK from me. This was one of the big motivators for #19398.
  8. DrahtBot added the label Needs rebase on Jul 4, 2022
  9. dergoegge force-pushed on Jul 5, 2022
  10. dergoegge force-pushed on Jul 5, 2022
  11. DrahtBot removed the label Needs rebase on Jul 5, 2022
  12. jamesob commented at 4:03 pm on July 5, 2022: member
    Nice, concept ACK
  13. DrahtBot added the label Needs rebase on Jul 12, 2022
  14. MarcoFalke referenced this in commit 2bdce7f7ad on Jul 19, 2022
  15. dergoegge force-pushed on Jul 19, 2022
  16. dergoegge renamed this:
    [draft] PeerManager unit tests
    net, test: Virtualise CConnman and add initial PeerManager unit tests
    on Jul 19, 2022
  17. dergoegge marked this as ready for review on Jul 19, 2022
  18. dergoegge commented at 12:26 pm on July 19, 2022: member
    This PR is ready for review!
  19. DrahtBot removed the label Needs rebase on Jul 19, 2022
  20. naumenkogs commented at 8:10 am on August 25, 2022: member
    Concept ACK, light code review ACK 87fd3609ba87a74fa6ca74c56ffbdb39fd941ebf
  21. dergoegge force-pushed on Sep 14, 2022
  22. dergoegge commented at 1:25 pm on September 14, 2022: member
    Rebased.
  23. [net] Define ConnectionsInterface 4f2533c5fd
  24. [net processing] PeerManager holds a ConnectionsInterface&, not a CConnman& 9efcd9668d
  25. dergoegge force-pushed on Sep 27, 2022
  26. fanquake requested review from theuni on Oct 4, 2022
  27. fanquake commented at 12:40 pm on October 4, 2022: member
    Concept ACK
  28. theStack commented at 2:05 pm on October 14, 2022: contributor
    Concept ACK
  29. in src/test/peerman_tests.cpp:87 in f98a4e8d89 outdated
    82+
    83+};
    84+
    85+void ConnectionsInterfaceMock::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
    86+{
    87+    BOOST_TEST_MESSAGE(strprintf("received message %s from peer %d", msg.m_type, pnode->GetId()));
    


    rajarshimaitra commented at 12:03 pm on October 17, 2022:
    Maybe I am missing something, but shouldn’t this be sending message %s to peer %d??

    dergoegge commented at 1:18 pm on October 21, 2022:
    Right, PushMessage is called to send a message to a peer. Done.
  30. in src/test/peerman_tests.cpp:282 in f98a4e8d89 outdated
    277+
    278+    BOOST_TEST_MESSAGE("Reply with wrong nonce does not cancel ping");
    279+    SendPong(node, *connman, *peerman, connman->m_ping_nonce + 1);
    280+    AdvanceMockTime(1);
    281+
    282+    // Ping metrics have not been updated on the CNode object, and there is a ping outstandgin
    


    rajarshimaitra commented at 12:17 pm on October 17, 2022:

    nit:

    0    // Ping metrics have not been updated on the CNode object, and there is a ping outstanding
    
  31. rajarshimaitra commented at 12:30 pm on October 17, 2022: contributor

    Concept ACK.. I made a first pass and changes look good to me.. I believe its helpful to separate out the interface, and possibly add more thorough unit testing later on?

    I will go through the test logic more thoroughly, for now just two observations.

  32. in src/test/peerman_tests.cpp:102 in f98a4e8d89 outdated
     97+    BOOST_TEST_MESSAGE(strprintf("received ping. Nonce:%d", m_ping_nonce));
     98+}
     99+
    100+void ConnectionsInterfaceMock::NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const
    101+{
    102+    assert(node.ReceiveMsgBytes(msg_bytes, complete));
    


    rajarshimaitra commented at 2:09 pm on October 19, 2022:
    Do we need to pass in the complete flag here?? Seems like we are overriding it anyway into node.ReceiveMsgBytes.. So can’t we just initiate it locally??

    dergoegge commented at 1:16 pm on October 21, 2022:

    Could you suggest a change? I don’t understand what you mean.

    complete is passed by reference and modified by ReceiveMsgBytes.


    brunoerg commented at 2:32 pm on October 21, 2022:
    I think we could simplify ReceiveMsgBytes by not passing complete as reference and using the bool that this function returns. Does it make sense?

    rajarshimaitra commented at 4:06 pm on October 23, 2022:

    I think I would retrace back my comment.. These signatures are part of the interface itself.. So there might be valid reasons for them..

    Though it seems like we are ultimately dropping the bool here in test case in .. The only place its used is inside NodeReceiveMsgBytes. So at first look passing in a value that we are internally processing and then dropping in final return (in the line (void)connman.ReceiveMsgFrom(node, msg);) was confusing me.. I am probably still confused but my suggestion doesn’t make any sense as it will modify the interface..

  33. hernanmarino approved
  34. hernanmarino commented at 6:10 pm on October 19, 2022: contributor
    tested ACK 👍
  35. rajarshimaitra commented at 6:36 pm on October 19, 2022: contributor
    One more question..
  36. [test] Add unit test for version handshake bdfe134ed6
  37. [test] Add unit test for ping/pong logic 9c04c32101
  38. dergoegge force-pushed on Oct 21, 2022
  39. rajarshimaitra approved
  40. rajarshimaitra commented at 4:33 pm on October 23, 2022: contributor
    tACK 9c04c32101cd458c336de1d66ad296dc7f1cf35d
  41. in src/test/peerman_tests.cpp:98 in bdfe134ed6 outdated
    93+            node.nProcessQueueSize += nSizeAdded;
    94+        }
    95+    }
    96+}
    97+
    98+bool ConnectionsInterfaceMock::ReceiveMsgFrom(CNode& node, CSerializedNetMsg& ser_msg) const
    


    MarcoFalke commented at 8:17 am on October 24, 2022:
    Any reason to duplicate this from the test util? Are there plans to de-duplicate this?
  42. in src/test/peerman_tests.cpp:98 in 9c04c32101
    93+
    94+void ConnectionsInterfaceMock::OnPing(CDataStream& data)
    95+{
    96+    data >> m_ping_nonce;
    97+    BOOST_TEST_MESSAGE(strprintf("received ping. Nonce:%d", m_ping_nonce));
    98+}
    


    vasild commented at 9:54 am on November 3, 2022:
    OnPing() is called when sending a ping message, but the log says “received ping”. Also, the comment of m_ping_nonce is “Most recent ping nonce received”.
  43. in src/test/peerman_tests.cpp:252 in 9c04c32101
    247+
    248+    Handshake(*connman, *peerman, node, /*their_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS));
    249+
    250+    BOOST_TEST_MESSAGE("Advanced mocktime and check that ping is sent after connection is established");
    251+    AdvanceMockTime(1);
    252+    BOOST_CHECK_EQUAL(connman->m_message_types_sent[NetMsgType::PING], 1);
    


    vasild commented at 10:00 am on November 3, 2022:
    This sequence is misleading. The ping message is sent immediately after the handshake. We do not need to “wait” 1 second for that to happen. So AdvanceMockTime(1) may be removed.
  44. in src/test/peerman_tests.cpp:100 in 9c04c32101
     95+{
     96+    data >> m_ping_nonce;
     97+    BOOST_TEST_MESSAGE(strprintf("received ping. Nonce:%d", m_ping_nonce));
     98+}
     99+
    100+void ConnectionsInterfaceMock::NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const
    


    vasild commented at 10:30 am on November 3, 2022:
    This is duplicated from ConnmanTestMsg::NodeReceiveMsgBytes() + a subtle change.
  45. in src/test/peerman_tests.cpp:258 in 9c04c32101
    253+
    254+    // The ping nonce should be non-zero
    255+    BOOST_CHECK(connman->m_ping_nonce != 0);
    256+
    257+    // No pong response has been received yet, and current ping is outstanding
    258+    CheckPingTimes(node, *peerman, std::chrono::microseconds::max(), 0us, 1s);
    


    vasild commented at 10:47 am on November 3, 2022:
    The AdvanceMockTime(1) from above belongs to just before this call. If the “1 second” is put in a variable that will make it obvious that it is the same as the 4th argument to CheckPingTimes().
  46. in src/test/peerman_tests.cpp:135 in 9c04c32101
    130+BOOST_FIXTURE_TEST_SUITE(peerman_tests, TestingSetup)
    131+
    132+static void SendMessage(ConnectionsInterfaceMock& connman, PeerManager& peerman, CNode& node, CSerializedNetMsg& msg)
    133+{
    134+    std::atomic<bool> interrupt{false};
    135+    (void)connman.ReceiveMsgFrom(node, msg);
    


    vasild commented at 10:54 am on November 3, 2022:
    This function simulates receiving a message from the peer. I find the name SendMessage() confusing. Naming elsewhere in the code is from our point of view.
  47. in src/test/peerman_tests.cpp:222 in 9c04c32101
    217+    CNodeStateStats stats;
    218+    peerman.GetNodeStateStats(node.GetId(), stats);
    219+    BOOST_CHECK_EQUAL(stats.m_ping_wait.count(), ping_wait.count());
    220+}
    221+
    222+static void SendPong(CNode& node, ConnectionsInterfaceMock& connman,
    


    vasild commented at 10:56 am on November 3, 2022:
    Same as SendMessage(): this is simulating the reception of a pong message from the peer and the name SendPong() is confusing.
  48. in src/test/peerman_tests.cpp:270 in 9c04c32101
    265+
    266+    BOOST_TEST_MESSAGE("Reply without ping");
    267+    SendPong(node, *connman, *peerman, 0);
    268+
    269+    // Ping metrics have not been updated on the CNode object, and there is no ping outstanding
    270+    CheckPingTimes(node, *peerman, std::chrono::microseconds::max(), 0us, 0us);
    


    vasild commented at 11:15 am on November 3, 2022:
    Maybe use ASSERT_DEBUG_LOG("Unsolicited pong without ping") here.
  49. in src/test/peerman_tests.cpp:146 in 9c04c32101
    141+
    142+using HandshakeHookFn = std::function<void(ConnectionsInterfaceMock& connman, PeerManager& peerman, CNode& node)>;
    143+static void Handshake(
    144+    ConnectionsInterfaceMock& connman, PeerManager& peerman,
    145+    CNode& node, ServiceFlags their_services,
    146+    HandshakeHookFn pre_verack_hook = [](ConnectionsInterfaceMock& connman, PeerManager& peerman, CNode& node) {}) noexcept
    


    vasild commented at 12:00 pm on November 3, 2022:
    This is very similar to ConnmanTestMsg::Handshake(). To avoid code duplication, can it be used instead?
  50. in src/test/peerman_tests.cpp:81 in bdfe134ed6 outdated
    76+    m_message_types_sent[msg.m_type]++;
    77+}
    78+
    79+void ConnectionsInterfaceMock::NodeReceiveMsgBytes(CNode& node, Span<const uint8_t> msg_bytes, bool& complete) const
    80+{
    81+    assert(node.ReceiveMsgBytes(msg_bytes, complete));
    


    theStack commented at 4:30 pm on November 3, 2022:

    According to our coding guidelines, assertions shouldn’t have side-effects.

    0    bool success = node.ReceiveMsgBytes(msg_bytes, complete);
    1    assert(success);
    
  51. in src/test/peerman_tests.cpp:14 in bdfe134ed6 outdated
     9+#include <net_processing.h>
    10+#include <netmessagemaker.h>
    11+#include <pubkey.h>
    12+#include <script/sign.h>
    13+#include <script/signingprovider.h>
    14+#include <script/standard.h>
    


    theStack commented at 4:35 pm on November 3, 2022:

    nit: Seems like a lot of these includes are unnecessary and can be removed; e.g. I don’t see any arith_uint256-, pubkey- or script-related stuff used in the unit test. With the following diff it still compiles:

     0diff --git a/src/test/peerman_tests.cpp b/src/test/peerman_tests.cpp
     1index 8ab87be6b..9d0c02fd0 100644
     2--- a/src/test/peerman_tests.cpp
     3+++ b/src/test/peerman_tests.cpp
     4@@ -2,16 +2,11 @@
     5 // Distributed under the MIT software license, see the accompanying
     6 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
     7
     8-#include <arith_uint256.h>
     9 #include <banman.h>
    10 #include <chainparams.h>
    11 #include <net.h>
    12 #include <net_processing.h>
    13 #include <netmessagemaker.h>
    14-#include <pubkey.h>
    15-#include <script/sign.h>
    16-#include <script/signingprovider.h>
    17-#include <script/standard.h>
    18 #include <serialize.h>
    19 #include <span.h>
    20 #include <test/util/net.h>
    21@@ -22,7 +17,6 @@
    22 #include <timedata.h>
    23 #include <validation.h>
    24
    25-#include <array>
    26 #include <optional>
    27 #include <stdint.h>
    

    jonatack commented at 4:06 pm on April 19, 2023:

    Seems like a lot of these includes are unnecessary and can be removed… With the following diff it still compiles

    A way to verify is to add the file to ci/test/06_script_b.sh and look at the lint CI output after repushing.

  52. theStack commented at 4:44 pm on November 3, 2022: contributor

    Reviewed up to 9efcd9668d143afa2e8213a7bacf94da7f645e4c, still have to look a bit deeper how the unit tests exactly work. To get the ball rolling, maybe it’s worth it to split up the PR into two, one for virtualising CConnman (which is I think is way easier to review and could be merged pretty fast), and one for the unit tests?

    For the latter, as Marco pointed out, it seems like there is potential for deduplication with the routines in test/util/net.cpp. Left also two minor review comments below.

  53. in src/net.h:668 in 9c04c32101
    663@@ -663,7 +664,64 @@ class NetEventsInterface
    664     ~NetEventsInterface() = default;
    665 };
    666 
    667-class CConnman
    668+/** Interface class for interacting with CConnman */
    669+class ConnectionsInterface
    


    vasild commented at 2:57 pm on November 4, 2022:
    I think that the tests added in this PR can be done without virtualizing CConnman and without touching the source code at all, by capturing the sent and received messages (see net_tests/initial_advertise_from_version_message for an example).

    dergoegge commented at 3:24 pm on November 4, 2022:

    Huh I have not seen that before, thanks for bringing it up! While we could do that, i think that virtualizing CConnman is just the right thing to do for several reasons.

    • Defining an interface for it gets us a step closer to hiding more implementation details of our net module (similar to what we did with net processing. See net_processing.h, it (almost) only has the interface definition for PeerManager).
    • Mocking CConnman is much easier if such an interface definition exists. That’s nice for unit tests but also nice for fuzz testing as we could have a FuzzedConnman (similar to your work on FuzzedSock).
    • Future tests might need more functionality besides capturing messages, so having the ability to mock the entire CConnman would be nice.

    vasild commented at 11:57 am on November 7, 2022:

    … i think that virtualizing CConnman is just the right thing to do for several reasons

    Maybe put those in the OP to make the case for virtualizing stronger.

    Defining an interface for it gets us a step closer to hiding more implementation details …

    In general, I wonder if we are going a step too far with “hiding implementation details”. Are we aiming to remove private: sections from classes defined in header files?

    PImpl and virtualization have their use cases but IMO shouldn’t be used as if they are universally good (as in “lets pimpl and virtualize everything because the code will become better just by doing that”). They could have adverse effects if abused.

    • Mocking CConnman is much easier if such an interface definition exists …
    • Future tests might need more functionality besides capturing messages …

    I agree.


    vasild commented at 8:19 am on November 15, 2022:

    the tests added in this PR can be done without … touching the source code at all

    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()
    

    jonatack commented at 4:10 pm on April 19, 2023:

    the tests added in this PR can be done without … touching the source code at all

    Yes, that works. Here it is:

    Nice!

    Virtualization/dynamic dispatch has performance and overhead costs in addition to increased class/code size and complexity, and there are places in this codebase where it seems to have been avoided, and still is, for that reason. For an example, see the discussion in #25349.

    If virtualization is still considered to be worth the overhead here, on its own merits above and beyond facilitating unit testing that can be done without it, it may be good to open a PR doing only that, as also suggested by someone above (and maybe consider a non-virtual public interface that calls private virtual implementation methods, i.e. the Template pattern via the Non-Virtual Interface (NVI) idiom. There is a simple example at https://github.com/jonatack/bitcoin/commit/b05383211239d564e98b19c96c80ff778e054146).

  54. hernanmarino commented at 4:40 pm on November 22, 2022: contributor
    cr 9c04c32101cd458c336de1d66ad296dc7f1cf35d . Mostly ACK in my opinion, but i agree with some of the others suggestions ( most of @vasild ’s and some other’s I marked with a thumbs up)
  55. pablomartin4btc commented at 5:05 pm on November 22, 2022: contributor

    Tested ACK.

    I’m not very comfortable using friend class as it breaks encapsulation, perhaps it’s something we could avoid at some point looking forward.

  56. dergoegge marked this as a draft on Dec 1, 2022
  57. DrahtBot added the label Needs rebase on Feb 22, 2023
  58. DrahtBot commented at 6:47 pm on February 22, 2023: contributor

    🐙 This pull request conflicts with the target branch and needs rebase.

  59. in src/test/peerman_tests.cpp:95 in bdfe134ed6 outdated
    90+        {
    91+            LOCK(node.cs_vProcessMsg);
    92+            node.vProcessMsg.splice(node.vProcessMsg.end(), node.vRecvMsg, node.vRecvMsg.begin(), it);
    93+            node.nProcessQueueSize += nSizeAdded;
    94+        }
    95+    }
    


    dergoegge commented at 5:49 pm on March 14, 2023:
    #27257 dedupes this code across the entire code base, reviewing that would help move this forward.
  60. jonatack commented at 4:12 pm on April 19, 2023: contributor
    Concept ACK on adding testing modulo the comments above and #25515 (review). Built and ran a green make check on each commit. Needs rebase.
  61. DrahtBot commented at 2:05 am on July 18, 2023: contributor

    There hasn’t been much activity lately and the patch still needs rebase. What is the status here?

    • Is it still relevant? ➡️ Please solve the conflicts to make it ready for review and to ensure the CI passes.
    • Is it no longer relevant? ➡️ Please close.
    • Did the author lose interest or time to work on this? ➡️ Please close it and mark it ‘Up for grabs’ with the label, so that it can be picked up in the future.
  62. dergoegge commented at 2:23 pm on August 10, 2023: member
    Closing for now, I want to do some interface work before adding the tests and will open a large meta PR soon that includes all the refactoring + tests at the end.
  63. dergoegge closed this on Aug 10, 2023


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-03 10:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me