Repository: bitcoin/bitcoin
File: test/functional/p2p_v2_misbehaving.py (Python)

Code before:
            msg_to_send = random_bitflip(msg_to_send)
        elif self.test_type == TestType.SEND_NO_AAD:
            aad = b''

        # Optionally send decoy packets after garbage terminator.
        for decoy_content_len in [random.randint(1, 100) for _ in range(random.randint(0, 10))]:
            msg_to_send += self.v2_enc_packet(decoy_content_len * b'\x00', aad=aad, ignore=True)
            aad = b''

        # Send version packet.
        if self.test_type == TestType.SEND_NON_EMPTY_VERSION_PACKET:
            msg_to_send += self.v2_enc_packet(random.randbytes(5), aad=aad)
        else:
            msg_to_send += self.v2_enc_packet(TRANSPORT_VERSION, aad=aad)

        return 64 - len(self.received_prefix), msg_to_send


class MisbehavingV2Peer(P2PInterface):
    """Custom implementation of P2PInterface which uses modified v2 P2P protocol functions for testing purposes."""
    def __init__(self, test_type):
        super().__init__()
        self.v2_state = None
        self.connection_opened = False
        self.test_type = test_type

    def connection_made(self, transport):
        """Only first 4 ellswift bytes which match network magic bytes is sent using `initial_v2_handshake()` in
        `connection_made()` when TestType = (EARLY_KEY_RESPONSE)"""
        self.v2_state = TestEncryptedP2PState(self.test_type)
        super().connection_made(transport)

    def data_received(self, t):
        if self.test_type == TestType.EARLY_KEY_RESPONSE:
            # check that data can be received on recvbuf only when mismatch from V1_PREFIX happens (magic_sent = True)
            assert self.v2_state.can_data_be_received and self.v2_state.magic_sent
        else:
            super().data_received(t)

    def on_open(self):
        self.connection_opened = True


class EncryptedP2PMisbehaving(BitcoinTestFramework):
    def set_test_params(self):
        self.num_nodes = 1
        self.extra_args = [["-v2transport=1", "-peertimeout=3"]]

    def run_test(self):
        self.test_earlykeyresponse()
        self.test_v2disconnection()

Review comment (bug):
Are the `test_type`'s in `test_v2disconnection` dependent on `test_earlykeyresponse` to run first?

I tried running the tests individually for each `test_type` but I ran into this error, using this diff a74f4ddcc50eeba04977d8eeb96ffee2a94dbbf9 
```
2024-02-16T02:38:17.055000Z TestFramework (INFO): PRNG seed is: 8074818075718275852
2024-02-16T02:38:17.056000Z TestFramework (INFO): Initializing test directory /var/folders/9g/cvx014yx4dq5lwl_x5zvn_j80000gn/T/bitcoin_func_test_dmwxb4ar
2024-02-16T02:38:22.936000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
  File "/Users/kevkevin/DEVDIR/bitcoin/test/functional/test_framework/test_framework.py", line 131, in main
    self.run_test()
  File "/Users/kevkevin/DEVDIR/bitcoin/./test/functional/p2p_v2_misbehaving.py", line 176, in run_test
    with self.nodes[0].assert_debug_log(expected_debug_message[test_type.value], timeout=5):
  File "/usr/local/Cellar/python@3.11/3.11.6_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 144, in __exit__
    next(self.gen)
  File "/Users/kevkevin/DEVDIR/bitcoin/test/functional/test_framework/test_node.py", line 492, in assert_debug_log
    self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log))
  File "/Users/kevkevin/DEVDIR/bitcoin/test/functional/test_framework/test_node.py", line 190, in _raise_assertion_error
    raise AssertionError(self._node_msg(msg))
AssertionError: [node 0] Expected messages "['version handshake timeout peer=2']" does not partially match log:

 - 2024-02-16T02:38:17.908176Z [net] [net.cpp:3712] [CNode] [net] Added connection peer=0
 - 2024-02-16T02:38:17.908650Z [net] [net.cpp:1820] [CreateNodeFromAcceptedSocket] [net] connection from 127.0.0.1:63849 accepted
 - 2024-02-16T02:38:21.015546Z [net] [net.cpp:1992] [InactivityCheck] [net] version handshake timeout peer=0
 - 2024-02-16T02:38:21.015844Z [net] [net.cpp:555] [CloseSocketDisconnect] [net] disconnecting peer=0
 - 2024-02-16T02:38:21.016463Z [net] [net_processing.cpp:1672] [FinalizeNode] [net] Cleared nodestate for peer=0


2024-02-16T02:38:22.999000Z TestFramework (INFO): Stopping nodes
2024-02-16T02:38:23.270000Z TestFramework (WARNING): Not cleaning up dir /var/folders/9g/cvx014yx4dq5lwl_x5zvn_j80000gn/T/bitcoin_func_test_dmwxb4ar
2024-02-16T02:38:23.270000Z TestFramework (ERROR): Test failed. Test logging available at /var/folders/9g/cvx014yx4dq5lwl_x5zvn_j80000gn/T/bitcoin_func_test_dmwxb4ar/test_framework.log
2024-02-16T02:38:23.271000Z TestFramework (ERROR):
2024-02-16T02:38:23.271000Z TestFramework (ERROR): Hint: Call /Users/kevkevin/DEVDIR/bitcoin/test/functional/combine_logs.py '/var/folders/9g/cvx014yx4dq5lwl_x5zvn_j80000gn/T/bitcoin_func_test_dmwxb4ar' to consolidate all logs
2024-02-16T02:38:23.271000Z TestFramework (ERROR):
2024-02-16T02:38:23.271000Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.
2024-02-16T02:38:23.272000Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues
2024-02-16T02:38:23.272000Z TestFramework (ERROR):
```

Code after:
        elif self.test_type == TestType.WRONG_GARBAGE_TERMINATOR:
            self.v2_state = WrongGarbageTerminatorState(initiating=True, net='regtest')
        elif self.test_type == TestType.WRONG_GARBAGE:
            self.v2_state = WrongGarbageState(initiating=True, net='regtest')
        elif self.test_type == TestType.SEND_NO_AAD:
            self.v2_state = NoAADState(initiating=True, net='regtest')
        elif TestType.SEND_NON_EMPTY_VERSION_PACKET:
            self.v2_state = NonEmptyVersionPacketState(initiating=True, net='regtest')
        super().connection_made(transport)

    def data_received(self, t):
        if self.test_type == TestType.EARLY_KEY_RESPONSE:
            # check that data can be received on recvbuf only when mismatch from V1_PREFIX happens
            assert self.v2_state.can_data_be_received
        else:
            super().data_received(t)


class EncryptedP2PMisbehaving(BitcoinTestFramework):
    def set_test_params(self):
        self.num_nodes = 1
        self.extra_args = [["-v2transport=1", "-peertimeout=3"]]

    def run_test(self):
        self.test_earlykeyresponse()
        self.test_v2disconnection()

    def test_earlykeyresponse(self):
        self.log.info('Sending ellswift bytes in parts to ensure that response from responder is received only when')
        self.log.info('ellswift bytes have a mismatch from the 16 bytes(network magic followed by "version\\x00\\x00\\x00\\x00\\x00")')
        node0 = self.nodes[0]
        self.log.info('Sending first 4 bytes of ellswift which match network magic')
        self.log.info('If a response is received, assertion failure would happen in our custom data_received() function')
        peer1 = node0.add_p2p_connection(MisbehavingV2Peer(TestType.EARLY_KEY_RESPONSE), wait_for_verack=False, send_version=False, supports_v2_p2p=True, wait_for_v2_handshake=False)
        peer1.send_raw_message(MAGIC_BYTES['regtest'])
        self.log.info('Sending remaining ellswift and garbage which are different from V1_PREFIX. Since a response is')
        self.log.info('expected now, our custom data_received() function wouldn\'t result in assertion failure')
        peer1.v2_state.can_data_be_received = True
        peer1.send_raw_message(peer1.v2_state.ellswift_ours[4:] + peer1.v2_state.sent_garbage)
        with node0.assert_debug_log(['V2 handshake timeout peer=0']):
            peer1.wait_for_disconnect(timeout=5)
        self.log.info('successful disconnection since modified ellswift was sent as response')

    def test_v2disconnection(self):
        # test v2 disconnection scenarios
        node0 = self.nodes[0]
        expected_debug_message = [
            [],  # EARLY_KEY_RESPONSE
            ["V2 transport error: missing garbage terminator, peer=1"],  # EXCESS_GARBAGE
            ["V2 handshake timeout peer=2"],  # WRONG_GARBAGE_TERMINATOR
            ["V2 transport error: packet decryption failure"],  # WRONG_GARBAGE