mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 10:38:42 +01:00
fafddafc2c
Currently there are some confusions in net_processing: * There is confusion between `-blocksonly mode` and `block-relay-only`, so adjust all comments to use the same nomenclature. * Whether to disconnect peers for providing invs/txs is implemented differently. For example, it seems a bit confusing to disconnect `block-relay-only` peers with `relay` permission when they send a tx message, but not when they send an inv message. Also, keeping track of their inv announcements seems both wasteful and confusing, as it does nothing. This isn't possible in practice, as outbound connections do not have permissions assigned, but sees fragile to rely on. Especially in light of proposed changes to make that possible: https://github.com/bitcoin/bitcoin/pull/17167
130 lines
6.6 KiB
Python
Executable File
130 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2019-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 blocksonly mode & block-relay-only connections."""
|
|
|
|
import time
|
|
|
|
from test_framework.messages import msg_tx, msg_inv, CInv, MSG_WTX
|
|
from test_framework.p2p import P2PInterface, P2PTxInvStore
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import assert_equal
|
|
from test_framework.wallet import MiniWallet
|
|
|
|
|
|
class P2PBlocksOnly(BitcoinTestFramework):
|
|
def set_test_params(self):
|
|
self.num_nodes = 1
|
|
self.extra_args = [["-blocksonly"]]
|
|
|
|
def run_test(self):
|
|
self.miniwallet = MiniWallet(self.nodes[0])
|
|
# Add enough mature utxos to the wallet, so that all txs spend confirmed coins
|
|
self.miniwallet.rescan_utxos()
|
|
|
|
self.blocksonly_mode_tests()
|
|
self.blocks_relay_conn_tests()
|
|
|
|
def blocksonly_mode_tests(self):
|
|
self.log.info("Tests with node running in -blocksonly mode")
|
|
assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False)
|
|
|
|
self.nodes[0].add_p2p_connection(P2PInterface())
|
|
tx, txid, wtxid, tx_hex = self.check_p2p_tx_violation()
|
|
|
|
self.log.info('Check that tx invs also violate the protocol')
|
|
self.nodes[0].add_p2p_connection(P2PInterface())
|
|
with self.nodes[0].assert_debug_log(['transaction (0000000000000000000000000000000000000000000000000000000000001234) inv sent in violation of protocol, disconnecting peer']):
|
|
self.nodes[0].p2ps[0].send_message(msg_inv([CInv(t=MSG_WTX, h=0x1234)]))
|
|
self.nodes[0].p2ps[0].wait_for_disconnect()
|
|
del self.nodes[0].p2ps[0]
|
|
|
|
self.log.info('Check that txs from rpc are not rejected and relayed to other peers')
|
|
tx_relay_peer = self.nodes[0].add_p2p_connection(P2PInterface())
|
|
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True)
|
|
|
|
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
|
|
with self.nodes[0].assert_debug_log(['received getdata for: wtx {} peer'.format(wtxid)]):
|
|
self.nodes[0].sendrawtransaction(tx_hex)
|
|
tx_relay_peer.wait_for_tx(txid)
|
|
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
|
|
|
|
self.log.info("Restarting node 0 with relay permission and blocksonly")
|
|
self.restart_node(0, ["-persistmempool=0", "-whitelist=relay@127.0.0.1", "-blocksonly"])
|
|
assert_equal(self.nodes[0].getrawmempool(), [])
|
|
first_peer = self.nodes[0].add_p2p_connection(P2PInterface())
|
|
second_peer = self.nodes[0].add_p2p_connection(P2PInterface())
|
|
peer_1_info = self.nodes[0].getpeerinfo()[0]
|
|
assert_equal(peer_1_info['permissions'], ['relay'])
|
|
peer_2_info = self.nodes[0].getpeerinfo()[1]
|
|
assert_equal(peer_2_info['permissions'], ['relay'])
|
|
assert_equal(self.nodes[0].testmempoolaccept([tx_hex])[0]['allowed'], True)
|
|
|
|
self.log.info('Check that the tx from first_peer with relay-permission is relayed to others (ie.second_peer)')
|
|
with self.nodes[0].assert_debug_log(["received getdata"]):
|
|
# Note that normally, first_peer would never send us transactions since we're a blocksonly node.
|
|
# By activating blocksonly, we explicitly tell our peers that they should not send us transactions,
|
|
# and Bitcoin Core respects that choice and will not send transactions.
|
|
# But if, for some reason, first_peer decides to relay transactions to us anyway, we should relay them to
|
|
# second_peer since we gave relay permission to first_peer.
|
|
# See https://github.com/bitcoin/bitcoin/issues/19943 for details.
|
|
first_peer.send_message(msg_tx(tx))
|
|
self.log.info('Check that the peer with relay-permission is still connected after sending the transaction')
|
|
assert_equal(first_peer.is_connected, True)
|
|
second_peer.wait_for_tx(txid)
|
|
assert_equal(self.nodes[0].getmempoolinfo()['size'], 1)
|
|
self.log.info("Relay-permission peer's transaction is accepted and relayed")
|
|
|
|
self.nodes[0].disconnect_p2ps()
|
|
self.generate(self.nodes[0], 1)
|
|
|
|
def blocks_relay_conn_tests(self):
|
|
self.log.info('Tests with node in normal mode with block-relay-only connections')
|
|
self.restart_node(0, ["-noblocksonly"]) # disables blocks only mode
|
|
assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], True)
|
|
|
|
# Ensure we disconnect if a block-relay-only connection sends us a transaction
|
|
self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only")
|
|
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
|
|
_, txid, _, tx_hex = self.check_p2p_tx_violation()
|
|
|
|
self.log.info("Tests with node in normal mode with block-relay-only connection, sending an inv")
|
|
conn = self.nodes[0].add_outbound_p2p_connection(P2PInterface(), p2p_idx=0, connection_type="block-relay-only")
|
|
assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], False)
|
|
self.check_p2p_inv_violation(conn)
|
|
|
|
self.log.info("Check that txs from RPC are not sent to blockrelay connection")
|
|
conn = self.nodes[0].add_outbound_p2p_connection(P2PTxInvStore(), p2p_idx=1, connection_type="block-relay-only")
|
|
|
|
self.nodes[0].sendrawtransaction(tx_hex)
|
|
|
|
# Bump time forward to ensure m_next_inv_send_time timer pops
|
|
self.nodes[0].setmocktime(int(time.time()) + 60)
|
|
|
|
conn.sync_send_with_ping()
|
|
assert(int(txid, 16) not in conn.get_invs())
|
|
|
|
def check_p2p_inv_violation(self, peer):
|
|
self.log.info("Check that tx-invs from P2P are rejected and result in disconnect")
|
|
with self.nodes[0].assert_debug_log(["inv sent in violation of protocol, disconnecting peer"]):
|
|
peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0x12345)]))
|
|
peer.wait_for_disconnect()
|
|
self.nodes[0].disconnect_p2ps()
|
|
|
|
def check_p2p_tx_violation(self):
|
|
self.log.info('Check that txs from P2P are rejected and result in disconnect')
|
|
spendtx = self.miniwallet.create_self_transfer()
|
|
|
|
with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']):
|
|
self.nodes[0].p2ps[0].send_message(msg_tx(spendtx['tx']))
|
|
self.nodes[0].p2ps[0].wait_for_disconnect()
|
|
assert_equal(self.nodes[0].getmempoolinfo()['size'], 0)
|
|
self.nodes[0].disconnect_p2ps()
|
|
|
|
return spendtx['tx'], spendtx['txid'], spendtx['wtxid'], spendtx['hex']
|
|
|
|
|
|
if __name__ == '__main__':
|
|
P2PBlocksOnly().main()
|