diff options
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/p2p_filter.py | 110 | ||||
-rwxr-xr-x | test/functional/test_framework/messages.py | 62 | ||||
-rwxr-xr-x | test/functional/test_framework/mininode.py | 20 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rwxr-xr-x | test/functional/wallet_listsinceblock.py | 20 |
5 files changed, 198 insertions, 15 deletions
diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py new file mode 100755 index 0000000000..ad7a9dcf6e --- /dev/null +++ b/test/functional/p2p_filter.py @@ -0,0 +1,110 @@ +#!/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 ( + MSG_BLOCK, + MSG_FILTERED_BLOCK, + msg_getdata, + msg_filterload, + msg_filterclear, +) +from test_framework.mininode import ( + P2PInterface, + mininode_lock, +) +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: + i.type = MSG_FILTERED_BLOCK + 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): + self.log.info('Add filtered P2P connection to the node') + filter_node = self.nodes[0].add_p2p_connection(FilterNode()) + filter_node.send_message(filter_node.watch_filter_init) + filter_node.sync_with_ping() + 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') + filter_node.merkleblock_received = False + block_hash = self.nodes[0].generatetoaddress(1, filter_address)[0] + txid = self.nodes[0].getblock(block_hash)['tx'][0] + filter_node.wait_for_tx(txid) + assert filter_node.merkleblock_received + + self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block') + with mininode_lock: + filter_node.last_message.pop("merkleblock", None) + filter_node.tx_received = False + self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress()) + filter_node.wait_for_merkleblock() + 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_message(msg_filterclear()) + filter_node.sync_with_ping() + for _ in range(5): + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7) + filter_node.wait_for_tx(txid) + + +if __name__ == '__main__': + FilterTest().main() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 285a3fbbf4..ff0c763b72 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -51,6 +51,7 @@ NODE_NETWORK_LIMITED = (1 << 10) MSG_TX = 1 MSG_BLOCK = 2 +MSG_FILTERED_BLOCK = 3 MSG_WITNESS_FLAG = 1 << 30 MSG_TYPE_MASK = 0xffffffff >> 2 @@ -225,10 +226,11 @@ class CInv: typemap = { 0: "Error", - 1: "TX", - 2: "Block", - 1|MSG_WITNESS_FLAG: "WitnessTx", - 2|MSG_WITNESS_FLAG : "WitnessBlock", + MSG_TX: "TX", + MSG_BLOCK: "Block", + MSG_TX | MSG_WITNESS_FLAG: "WitnessTx", + MSG_BLOCK | MSG_WITNESS_FLAG: "WitnessBlock", + MSG_FILTERED_BLOCK: "filtered Block", 4: "CompactBlock" } @@ -1318,6 +1320,58 @@ class msg_headers: return "msg_headers(headers=%s)" % repr(self.headers) +class msg_merkleblock: + command = b"merkleblock" + + def deserialize(self, f): + pass # Placeholder for now + + +class msg_filterload: + __slots__ = ("data", "nHashFuncs", "nTweak", "nFlags") + command = b"filterload" + + def __init__(self, data=b'00', nHashFuncs=0, nTweak=0, nFlags=0): + self.data = data + self.nHashFuncs = nHashFuncs + self.nTweak = nTweak + self.nFlags = nFlags + + def deserialize(self, f): + self.data = deser_string(f) + self.nHashFuncs = struct.unpack("<I", f.read(4))[0] + self.nTweak = struct.unpack("<I", f.read(4))[0] + self.nFlags = struct.unpack("<B", f.read(1))[0] + + def serialize(self): + r = b"" + r += ser_string(self.data) + r += struct.pack("<I", self.nHashFuncs) + r += struct.pack("<I", self.nTweak) + r += struct.pack("<B", self.nFlags) + return r + + def __repr__(self): + return "msg_filterload(data={}, nHashFuncs={}, nTweak={}, nFlags={})".format( + self.data, self.nHashFuncs, self.nTweak, self.nFlags) + + +class msg_filterclear: + __slots__ = () + command = b"filterclear" + + def __init__(self): + pass + + def deserialize(self, f): + pass + + def serialize(self): + return b"" + + def __repr__(self): + return "msg_filterclear()" + class msg_feefilter: __slots__ = ("feerate",) diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index a9e669fea9..ce51513ce9 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -30,6 +30,8 @@ from test_framework.messages import ( msg_blocktxn, msg_cmpctblock, msg_feefilter, + msg_filterclear, + msg_filterload, msg_getaddr, msg_getblocks, msg_getblocktxn, @@ -38,6 +40,7 @@ from test_framework.messages import ( msg_headers, msg_inv, msg_mempool, + msg_merkleblock, msg_notfound, msg_ping, msg_pong, @@ -62,6 +65,8 @@ MESSAGEMAP = { b"blocktxn": msg_blocktxn, b"cmpctblock": msg_cmpctblock, b"feefilter": msg_feefilter, + b"filterclear": msg_filterclear, + b"filterload": msg_filterload, b"getaddr": msg_getaddr, b"getblocks": msg_getblocks, b"getblocktxn": msg_getblocktxn, @@ -70,6 +75,7 @@ MESSAGEMAP = { b"headers": msg_headers, b"inv": msg_inv, b"mempool": msg_mempool, + b"merkleblock": msg_merkleblock, b"notfound": msg_notfound, b"ping": msg_ping, b"pong": msg_pong, @@ -318,6 +324,8 @@ class P2PInterface(P2PConnection): def on_blocktxn(self, message): pass def on_cmpctblock(self, message): pass def on_feefilter(self, message): pass + def on_filterclear(self, message): pass + def on_filterload(self, message): pass def on_getaddr(self, message): pass def on_getblocks(self, message): pass def on_getblocktxn(self, message): pass @@ -325,6 +333,7 @@ class P2PInterface(P2PConnection): def on_getheaders(self, message): pass def on_headers(self, message): pass def on_mempool(self, message): pass + def on_merkleblock(self, message): pass def on_notfound(self, message): pass def on_pong(self, message): pass def on_reject(self, message): pass @@ -385,6 +394,17 @@ class P2PInterface(P2PConnection): wait_until(test_function, timeout=timeout, lock=mininode_lock) + def wait_for_merkleblock(self, timeout=60): + def test_function(): + assert self.is_connected + last_filtered_block = self.last_message.get('merkleblock') + if not last_filtered_block: + return False + # TODO change this method to take a hash value and only return true if the correct block has been received + return True + + wait_until(test_function, timeout=timeout, lock=mininode_lock) + def wait_for_getdata(self, timeout=60): """Waits for a getdata message. diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ce9b37edfb..2f307750a9 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -147,6 +147,7 @@ BASE_SCRIPTS = [ 'rpc_net.py', 'wallet_keypool.py', 'p2p_mempool.py', + 'p2p_filter.py', 'rpc_setban.py', 'p2p_blocksonly.py', 'mining_prioritisetransaction.py', diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 229eda9806..6d51ca6c93 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -111,23 +111,21 @@ class ListSinceBlockTest(BitcoinTestFramework): senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1) # generate on both sides - lastblockhash = self.nodes[1].generate(6)[5] - self.nodes[2].generate(7) - self.log.debug('lastblockhash={}'.format(lastblockhash)) + nodes1_last_blockhash = self.nodes[1].generate(6)[-1] + nodes2_first_blockhash = self.nodes[2].generate(7)[0] + self.log.debug("nodes[1] last blockhash = {}".format(nodes1_last_blockhash)) + self.log.debug("nodes[2] first blockhash = {}".format(nodes2_first_blockhash)) self.sync_all(self.nodes[:2]) self.sync_all(self.nodes[2:]) self.join_network() - # listsinceblock(lastblockhash) should now include tx, as seen from nodes[0] - lsbres = self.nodes[0].listsinceblock(lastblockhash) - found = False - for tx in lsbres['transactions']: - if tx['txid'] == senttx: - found = True - break - assert found + # listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0] + # and return the block height which listsinceblock now exposes since a5e7795. + transactions = self.nodes[0].listsinceblock(nodes1_last_blockhash)['transactions'] + found = next(tx for tx in transactions if tx['txid'] == senttx) + assert_equal(found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height']) def test_double_spend(self): ''' |