diff options
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/mempool_accept_wtxid.py | 17 | ||||
-rwxr-xr-x | test/functional/mempool_unbroadcast.py | 6 | ||||
-rwxr-xr-x | test/functional/p2p_addr_relay.py | 99 | ||||
-rwxr-xr-x | test/functional/p2p_addrfetch.py | 62 | ||||
-rwxr-xr-x | test/functional/p2p_addrv2_relay.py | 5 | ||||
-rwxr-xr-x | test/functional/p2p_invalid_messages.py | 1 | ||||
-rwxr-xr-x | test/functional/test_framework/test_node.py | 5 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rwxr-xr-x | test/lint/lint-spelling.sh | 2 |
9 files changed, 187 insertions, 11 deletions
diff --git a/test/functional/mempool_accept_wtxid.py b/test/functional/mempool_accept_wtxid.py index dd1f8997ad..63ecc8ee2a 100755 --- a/test/functional/mempool_accept_wtxid.py +++ b/test/functional/mempool_accept_wtxid.py @@ -16,6 +16,7 @@ from test_framework.messages import ( CTxOut, sha256, ) +from test_framework.p2p import P2PTxInvStore from test_framework.script import ( CScript, OP_0, @@ -62,6 +63,8 @@ class MempoolWtxidTest(BitcoinTestFramework): parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0) node.generate(1) + peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore()) + # Create a new transaction with witness solving first branch child_witness_script = CScript([OP_TRUE]) child_witness_program = sha256(child_witness_script) @@ -87,10 +90,13 @@ class MempoolWtxidTest(BitcoinTestFramework): assert_equal(child_one_txid, child_two_txid) assert child_one_wtxid != child_two_wtxid - self.log.info("Submit one child to the mempool") + self.log.info("Submit child_one to the mempool") txid_submitted = node.sendrawtransaction(child_one.serialize().hex()) assert_equal(node.getrawmempool(True)[txid_submitted]['wtxid'], child_one_wtxid) + peer_wtxid_relay.wait_for_broadcast([child_one_wtxid]) + assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0) + # testmempoolaccept reports the "already in mempool" error assert_equal(node.testmempoolaccept([child_one.serialize().hex()]), [{ "txid": child_one_txid, @@ -108,9 +114,18 @@ class MempoolWtxidTest(BitcoinTestFramework): # sendrawtransaction will not throw but quits early when the exact same transaction is already in mempool node.sendrawtransaction(child_one.serialize().hex()) + + self.log.info("Connect another peer that hasn't seen child_one before") + peer_wtxid_relay_2 = node.add_p2p_connection(P2PTxInvStore()) + + self.log.info("Submit child_two to the mempool") # sendrawtransaction will not throw but quits early when a transaction with the same non-witness data is already in mempool node.sendrawtransaction(child_two.serialize().hex()) + # The node should rebroadcast the transaction using the wtxid of the correct transaction + # (child_one, which is in its mempool). + peer_wtxid_relay_2.wait_for_broadcast([child_one_wtxid]) + assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0) if __name__ == '__main__': MempoolWtxidTest().main() diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py index b475b65e68..7d9e6c306d 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -92,6 +92,12 @@ class MempoolUnbroadcastTest(BitcoinTestFramework): self.disconnect_nodes(0, 1) node.disconnect_p2ps() + self.log.info("Rebroadcast transaction and ensure it is not added to unbroadcast set when already in mempool") + rpc_tx_hsh = node.sendrawtransaction(txFS["hex"]) + mempool = node.getrawmempool(True) + assert rpc_tx_hsh in mempool + assert not mempool[rpc_tx_hsh]['unbroadcast'] + def test_txn_removal(self): self.log.info("Test that transactions removed from mempool are removed from unbroadcast set") node = self.nodes[0] diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py index 1a414959b9..ff1d85a9be 100755 --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -13,17 +13,20 @@ from test_framework.messages import ( msg_addr, msg_getaddr ) -from test_framework.p2p import P2PInterface -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, +from test_framework.p2p import ( + P2PInterface, + p2p_lock, ) +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal +import random import time class AddrReceiver(P2PInterface): num_ipv4_received = 0 test_addr_contents = False + _tokens = 1 def __init__(self, test_addr_contents=False): super().__init__() @@ -40,6 +43,20 @@ class AddrReceiver(P2PInterface): raise AssertionError("Invalid addr.port of {} (8333-8342 expected)".format(addr.port)) assert addr.ip.startswith('123.123.123.') + def on_getaddr(self, message): + # When the node sends us a getaddr, it increments the addr relay tokens for the connection by 1000 + self._tokens += 1000 + + @property + def tokens(self): + with p2p_lock: + return self._tokens + + def increment_tokens(self, n): + # When we move mocktime forward, the node increments the addr relay tokens for its peers + with p2p_lock: + self._tokens += n + def addr_received(self): return self.num_ipv4_received != 0 @@ -53,12 +70,14 @@ class AddrTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [["-whitelist=addr@127.0.0.1"]] def run_test(self): self.oversized_addr_test() self.relay_tests() self.getaddr_tests() self.blocksonly_mode_tests() + self.rate_limit_tests() def setup_addr_msg(self, num): addrs = [] @@ -75,6 +94,19 @@ class AddrTest(BitcoinTestFramework): msg.addrs = addrs return msg + def setup_rand_addr_msg(self, num): + addrs = [] + for i in range(num): + addr = CAddress() + addr.time = self.mocktime + i + addr.nServices = NODE_NETWORK | NODE_WITNESS + addr.ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}" + addr.port = 8333 + addrs.append(addr) + msg = msg_addr() + msg.addrs = addrs + return msg + def send_addr_msg(self, source, msg, receivers): source.send_and_ping(msg) # pop m_next_addr_send timer @@ -191,7 +223,7 @@ class AddrTest(BitcoinTestFramework): def blocksonly_mode_tests(self): self.log.info('Test addr relay in -blocksonly mode') - self.restart_node(0, ["-blocksonly"]) + self.restart_node(0, ["-blocksonly", "-whitelist=addr@127.0.0.1"]) self.mocktime = int(time.time()) self.log.info('Check that we send getaddr messages') @@ -207,6 +239,63 @@ class AddrTest(BitcoinTestFramework): self.nodes[0].disconnect_p2ps() + def send_addrs_and_test_rate_limiting(self, peer, no_relay, new_addrs, total_addrs): + """Send an addr message and check that the number of addresses processed and rate-limited is as expected""" + + peer.send_and_ping(self.setup_rand_addr_msg(new_addrs)) + + peerinfo = self.nodes[0].getpeerinfo()[0] + addrs_processed = peerinfo['addr_processed'] + addrs_rate_limited = peerinfo['addr_rate_limited'] + self.log.debug(f"addrs_processed = {addrs_processed}, addrs_rate_limited = {addrs_rate_limited}") + + if no_relay: + assert_equal(addrs_processed, 0) + assert_equal(addrs_rate_limited, 0) + else: + assert_equal(addrs_processed, min(total_addrs, peer.tokens)) + assert_equal(addrs_rate_limited, max(0, total_addrs - peer.tokens)) + + def rate_limit_tests(self): + + self.mocktime = int(time.time()) + self.restart_node(0, []) + self.nodes[0].setmocktime(self.mocktime) + + for contype, no_relay in [("outbound-full-relay", False), ("block-relay-only", True), ("inbound", False)]: + self.log.info(f'Test rate limiting of addr processing for {contype} peers') + if contype == "inbound": + peer = self.nodes[0].add_p2p_connection(AddrReceiver()) + else: + peer = self.nodes[0].add_outbound_p2p_connection(AddrReceiver(), p2p_idx=0, connection_type=contype) + + # Send 600 addresses. For all but the block-relay-only peer this should result in addresses being processed. + self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 600) + + # Send 600 more addresses. For the outbound-full-relay peer (which we send a GETADDR, and thus will + # process up to 1001 incoming addresses), this means more addresses will be processed. + self.send_addrs_and_test_rate_limiting(peer, no_relay, 600, 1200) + + # Send 10 more. As we reached the processing limit for all nodes, no more addresses should be procesesd. + self.send_addrs_and_test_rate_limiting(peer, no_relay, 10, 1210) + + # Advance the time by 100 seconds, permitting the processing of 10 more addresses. + # Send 200 and verify that 10 are processed. + self.mocktime += 100 + self.nodes[0].setmocktime(self.mocktime) + peer.increment_tokens(10) + + self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1410) + + # Advance the time by 1000 seconds, permitting the processing of 100 more addresses. + # Send 200 and verify that 100 are processed. + self.mocktime += 1000 + self.nodes[0].setmocktime(self.mocktime) + peer.increment_tokens(100) + + self.send_addrs_and_test_rate_limiting(peer, no_relay, 200, 1610) + + self.nodes[0].disconnect_p2ps() if __name__ == '__main__': AddrTest().main() diff --git a/test/functional/p2p_addrfetch.py b/test/functional/p2p_addrfetch.py new file mode 100755 index 0000000000..66ee1544a9 --- /dev/null +++ b/test/functional/p2p_addrfetch.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 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 p2p addr-fetch connections +""" + +import time + +from test_framework.messages import msg_addr, CAddress, NODE_NETWORK, NODE_WITNESS +from test_framework.p2p import P2PInterface, p2p_lock +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + +ADDR = CAddress() +ADDR.time = int(time.time()) +ADDR.nServices = NODE_NETWORK | NODE_WITNESS +ADDR.ip = "192.0.0.8" +ADDR.port = 18444 + + +class P2PAddrFetch(BitcoinTestFramework): + + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def run_test(self): + node = self.nodes[0] + self.log.info("Connect to an addr-fetch peer") + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="addr-fetch") + info = node.getpeerinfo() + assert_equal(len(info), 1) + assert_equal(info[0]['connection_type'], 'addr-fetch') + + self.log.info("Check that we send getaddr but don't try to sync headers with the addr-fetch peer") + peer.sync_send_with_ping() + with p2p_lock: + assert peer.message_count['getaddr'] == 1 + assert peer.message_count['getheaders'] == 0 + + self.log.info("Check that answering the getaddr with a single address does not lead to disconnect") + # This prevents disconnecting on self-announcements + msg = msg_addr() + msg.addrs = [ADDR] + peer.send_and_ping(msg) + assert_equal(len(node.getpeerinfo()), 1) + + self.log.info("Check that answering with larger addr messages leads to disconnect") + msg.addrs = [ADDR] * 2 + peer.send_message(msg) + peer.wait_for_disconnect(timeout=5) + + self.log.info("Check timeout for addr-fetch peer that does not send addrs") + peer = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="addr-fetch") + node.setmocktime(int(time.time()) + 301) # Timeout: 5 minutes + peer.wait_for_disconnect(timeout=5) + + +if __name__ == '__main__': + P2PAddrFetch().main() diff --git a/test/functional/p2p_addrv2_relay.py b/test/functional/p2p_addrv2_relay.py index c006a6c24f..32c1d42b1c 100755 --- a/test/functional/p2p_addrv2_relay.py +++ b/test/functional/p2p_addrv2_relay.py @@ -42,7 +42,9 @@ class AddrReceiver(P2PInterface): super().__init__(support_addrv2 = True) def on_addrv2(self, message): - if ADDRS == message.addrs: + expected_set = set((addr.ip, addr.port) for addr in ADDRS) + received_set = set((addr.ip, addr.port) for addr in message.addrs) + if expected_set == received_set: self.addrv2_received_and_checked = True def wait_for_addrv2(self): @@ -53,6 +55,7 @@ class AddrTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + self.extra_args = [["-whitelist=addr@127.0.0.1"]] def run_test(self): self.log.info('Create connection that sends addrv2 messages') diff --git a/test/functional/p2p_invalid_messages.py b/test/functional/p2p_invalid_messages.py index 788a81d4af..9c34506320 100755 --- a/test/functional/p2p_invalid_messages.py +++ b/test/functional/p2p_invalid_messages.py @@ -58,6 +58,7 @@ class InvalidMessagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True + self.extra_args = [["-whitelist=addr@127.0.0.1"]] def run_test(self): self.test_buffer() diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index ba52abc7dd..afa904c8d7 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -557,9 +557,8 @@ class TestNode(): return p2p_conn def add_outbound_p2p_connection(self, p2p_conn, *, p2p_idx, connection_type="outbound-full-relay", **kwargs): - """Add an outbound p2p connection from node. Either - full-relay("outbound-full-relay") or - block-relay-only("block-relay-only") connection. + """Add an outbound p2p connection from node. Must be an + "outbound-full-relay", "block-relay-only" or "addr-fetch" connection. This method adds the p2p connection to the self.p2ps list and returns the connection to the caller. diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 8afd8b3bc1..725706947f 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -186,6 +186,7 @@ BASE_SCRIPTS = [ 'p2p_addr_relay.py', 'p2p_getaddr_caching.py', 'p2p_getdata.py', + 'p2p_addrfetch.py', 'rpc_net.py', 'wallet_keypool.py --legacy-wallet', 'wallet_keypool.py --descriptors', diff --git a/test/lint/lint-spelling.sh b/test/lint/lint-spelling.sh index 238fa63c45..111091b7f8 100755 --- a/test/lint/lint-spelling.sh +++ b/test/lint/lint-spelling.sh @@ -15,6 +15,6 @@ if ! command -v codespell > /dev/null; then fi IGNORE_WORDS_FILE=test/lint/lint-spelling.ignore-words.txt -if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/qt/locale/" ":(exclude)src/qt/*.qrc" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)contrib/gitian-keys/keys.txt" ":(exclude)contrib/guix/patches"); then +if ! codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=${IGNORE_WORDS_FILE} $(git ls-files -- ":(exclude)build-aux/m4/" ":(exclude)contrib/seeds/*.txt" ":(exclude)depends/" ":(exclude)doc/release-notes/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/qt/locale/" ":(exclude)src/qt/*.qrc" ":(exclude)src/secp256k1/" ":(exclude)src/univalue/" ":(exclude)contrib/builder-keys/keys.txt" ":(exclude)contrib/guix/patches"); then echo "^ Warning: codespell identified likely spelling errors. Any false positives? Add them to the list of ignored words in ${IGNORE_WORDS_FILE}" fi |