diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 8d6489d5c6..99f793881c 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4053,6 +4053,11 @@ void PeerManagerImpl::ProcessMessage( } if (msg_type == NetMsgType::TX || msg_type == NetMsgType::DSTX) { + // Stop processing the transaction early if we are still in IBD since we don't + // have enough information to validate it yet. Sending unsolicited transactions + // is not considered a protocol violation, so don't punish the peer. + if (m_chainman.ActiveChainstate().IsInitialBlockDownload()) return; + CTransactionRef ptx; CCoinJoinBroadcastTx dstx; int nInvType = MSG_TX; diff --git a/test/functional/p2p_ibd_txrelay.py b/test/functional/p2p_ibd_txrelay.py new file mode 100755 index 0000000000..1af89d6a2f --- /dev/null +++ b/test/functional/p2p_ibd_txrelay.py @@ -0,0 +1,78 @@ +#!/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 transaction relay behavior during IBD: +- Don't request transactions +- Ignore all transaction messages +""" + +from decimal import Decimal +import time + +from test_framework.messages import ( + CInv, + COIN, + CTransaction, + from_hex, + msg_inv, + msg_tx, + MSG_TX, +) +from test_framework.p2p import ( + NONPREF_PEER_TX_DELAY, + P2PDataStore, + P2PInterface, + p2p_lock +) +from test_framework.test_framework import BitcoinTestFramework + +NORMAL_FEE_FILTER = Decimal(100) / COIN + +class P2PIBDTxRelayTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.disable_mocktime = True + self.num_nodes = 2 + self.extra_args = [ + ["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)], + ["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)], + ] + + def run_test(self): + self.log.info("Check that nodes don't send getdatas for transactions while still in IBD") + peer_inver = self.nodes[0].add_p2p_connection(P2PDataStore()) + txid = 0xdeadbeef + peer_inver.send_and_ping(msg_inv([CInv(t=MSG_TX, h=txid)])) + # The node should not send a getdata, but if it did, it would first delay 2 seconds + self.nodes[0].setmocktime(int(time.time() + NONPREF_PEER_TX_DELAY)) + peer_inver.sync_send_with_ping() + with p2p_lock: + assert txid not in peer_inver.getdata_requests + self.nodes[0].disconnect_p2ps() + + self.log.info("Check that nodes don't process unsolicited transactions while still in IBD") + # A transaction hex pulled from tx_valid.json. There are no valid transactions since no UTXOs + # exist yet, but it should be a well-formed transaction. + rawhex = "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba260000000004a01ff473" + \ + "04402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e168" + \ + "1a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696a" + \ + "d990364e555c271ad504b88ac00000000" + assert self.nodes[1].decoderawtransaction(rawhex) # returns a dict, should not throw + tx = from_hex(CTransaction(), rawhex) + peer_txer = self.nodes[0].add_p2p_connection(P2PInterface()) + with self.nodes[0].assert_debug_log(expected_msgs=["received: tx"], unexpected_msgs=["was not accepted"]): + peer_txer.send_and_ping(msg_tx(tx)) + self.nodes[0].disconnect_p2ps() + + # Come out of IBD by generating a block + self.nodes[0].generate(1) + self.sync_all() + + self.log.info("Check that nodes process the same transaction, even when unsolicited, when no longer in IBD") + peer_txer = self.nodes[0].add_p2p_connection(P2PInterface()) + with self.nodes[0].assert_debug_log(expected_msgs=["was not accepted"]): + peer_txer.send_and_ping(msg_tx(tx)) + +if __name__ == '__main__': + P2PIBDTxRelayTest().main() diff --git a/test/functional/test_framework/p2p.py b/test/functional/test_framework/p2p.py index 4e88e5268a..1c9e9a2958 100755 --- a/test/functional/test_framework/p2p.py +++ b/test/functional/test_framework/p2p.py @@ -99,6 +99,8 @@ P2P_SERVICES = NODE_NETWORK | NODE_HEADERS_COMPRESSED P2P_SUBVERSION = "/python-p2p-tester:0.0.3%s/" # Value for relay that this test framework sends in its `version` message P2P_VERSION_RELAY = 1 +# Delay after receiving a tx inv before requesting transactions from non-preferred peers, in seconds +NONPREF_PEER_TX_DELAY = 2 MESSAGEMAP = { b"addr": msg_addr, diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 80bd7abfb7..4ecef85f37 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -306,6 +306,7 @@ BASE_SCRIPTS = [ 'rpc_estimatefee.py', 'p2p_unrequested_blocks.py', # NOTE: needs dash_hash to pass 'feature_shutdown.py', + 'p2p_ibd_txrelay.py', 'rpc_coinjoin.py', 'rpc_masternode.py', 'rpc_mnauth.py',