aboutsummaryrefslogtreecommitdiff
path: root/test/functional/p2p_filter.py
blob: a8b768c14478e27363b2c0099d84cce8d08d9c59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#!/usr/bin/env python3
# Copyright (c) 2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Test BIP 37
"""

from test_framework.messages import (
    CInv,
    MAX_BLOOM_FILTER_SIZE,
    MAX_BLOOM_HASH_FUNCS,
    MSG_BLOCK,
    MSG_FILTERED_BLOCK,
    msg_filteradd,
    msg_filterclear,
    msg_filterload,
    msg_getdata,
)
from test_framework.mininode import P2PInterface
from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE
from test_framework.test_framework import BitcoinTestFramework


class FilterNode(P2PInterface):
    # This is a P2SH watch-only wallet
    watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87'
    # The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added
    watch_filter_init = msg_filterload(
        data=
        b'@\x00\x08\x00\x80\x00\x00 \x00\xc0\x00 \x04\x00\x08$\x00\x04\x80\x00\x00 \x00\x00\x00\x00\x80\x00\x00@\x00\x02@ \x00',
        nHashFuncs=19,
        nTweak=0,
        nFlags=1,
    )

    def on_inv(self, message):
        want = msg_getdata()
        for i in message.inv:
            # inv messages can only contain TX or BLOCK, so translate BLOCK to FILTERED_BLOCK
            if i.type == MSG_BLOCK:
                want.inv.append(CInv(MSG_FILTERED_BLOCK, i.hash))
            else:
                want.inv.append(i)
        if len(want.inv):
            self.send_message(want)

    def on_merkleblock(self, message):
        self.merkleblock_received = True

    def on_tx(self, message):
        self.tx_received = True


class FilterTest(BitcoinTestFramework):
    def set_test_params(self):
        self.setup_clean_chain = False
        self.num_nodes = 1
        self.extra_args = [[
            '-peerbloomfilters',
            '-whitelist=noban@127.0.0.1',  # immediate tx relay
        ]]

    def skip_test_if_missing_module(self):
        self.skip_if_no_wallet()

    def run_test(self):
        filter_node = self.nodes[0].add_p2p_connection(FilterNode())

        self.log.info('Check that too large filter is rejected')
        with self.nodes[0].assert_debug_log(['Misbehaving']):
            filter_node.send_and_ping(msg_filterload(data=b'\xaa', nHashFuncs=MAX_BLOOM_HASH_FUNCS+1))
        with self.nodes[0].assert_debug_log(['Misbehaving']):
            filter_node.send_and_ping(msg_filterload(data=b'\xbb'*(MAX_BLOOM_FILTER_SIZE+1)))

        self.log.info('Check that too large data element to add to the filter is rejected')
        with self.nodes[0].assert_debug_log(['Misbehaving']):
            filter_node.send_and_ping(msg_filteradd(data=b'\xcc'*(MAX_SCRIPT_ELEMENT_SIZE+1)))

        self.log.info('Add filtered P2P connection to the node')
        filter_node.send_and_ping(filter_node.watch_filter_init)
        filter_address = self.nodes[0].decodescript(filter_node.watch_script_pubkey)['addresses'][0]

        self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block')
        block_hash = self.nodes[0].generatetoaddress(1, filter_address)[0]
        txid = self.nodes[0].getblock(block_hash)['tx'][0]
        filter_node.wait_for_merkleblock(block_hash)
        filter_node.wait_for_tx(txid)

        self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
        filter_node.tx_received = False
        block_hash = self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
        filter_node.wait_for_merkleblock(block_hash)
        assert not filter_node.tx_received

        self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
        filter_node.merkleblock_received = False
        filter_node.tx_received = False
        self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 90)
        filter_node.sync_with_ping()
        filter_node.sync_with_ping()
        assert not filter_node.merkleblock_received
        assert not filter_node.tx_received

        self.log.info('Check that we receive a tx in reply to a mempool msg if the filter matches a mempool tx')
        filter_node.merkleblock_received = False
        txid = self.nodes[0].sendtoaddress(filter_address, 90)
        filter_node.wait_for_tx(txid)
        assert not filter_node.merkleblock_received

        self.log.info('Check that after deleting filter all txs get relayed again')
        filter_node.send_and_ping(msg_filterclear())
        for _ in range(5):
            txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7)
            filter_node.wait_for_tx(txid)

        self.log.info('Check that request for filtered blocks is ignored if no filter is set')
        filter_node.merkleblock_received = False
        filter_node.tx_received = False
        with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']):
            block_hash = self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())[0]
            filter_node.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))])
            filter_node.sync_with_ping()
            assert not filter_node.merkleblock_received
            assert not filter_node.tx_received

        self.log.info('Check that sending "filteradd" if no filter is set is treated as misbehavior')
        with self.nodes[0].assert_debug_log(['Misbehaving']):
            filter_node.send_and_ping(msg_filteradd(data=b'letsmisbehave'))

        self.log.info("Check that division-by-zero remote crash bug [CVE-2013-5700] is fixed")
        filter_node.send_and_ping(msg_filterload(data=b'', nHashFuncs=1))
        filter_node.send_and_ping(msg_filteradd(data=b'letstrytocrashthisnode'))


if __name__ == '__main__':
    FilterTest().main()