diff --git a/qa/rpc-tests/p2p-tx-download.py b/qa/rpc-tests/p2p-tx-download.py index 5768634e7..8c8a951f8 100644 --- a/qa/rpc-tests/p2p-tx-download.py +++ b/qa/rpc-tests/p2p-tx-download.py @@ -18,6 +18,7 @@ TX_EXPIRY_INTERVAL = 10 * GETDATA_TX_INTERVAL # 5 minutes INBOUND_PEER_TX_DELAY = 2 # seconds TXID_RELAY_DELAY = 2 # seconds MAX_GETDATA_IN_FLIGHT = 100 +MAX_PEER_TX_ANNOUNCEMENTS = 5000 class TxDownloadTestNode(SingleNodeConnCB): def __init__(self): @@ -51,6 +52,11 @@ class TxDownloadTestNode(SingleNodeConnCB): return self.connection.state == "closed" return wait_until(is_closed, timeout=30) + def wait_until_numgetdata(self, num): + def has_num(): + return len(self.tx_getdata_received) == num + return wait_until(has_num, timeout=60) + def disconnect(self): self.connection.disconnect_node() return self.wait_for_disconnect() @@ -80,6 +86,7 @@ class TxDownloadTest(BitcoinTestFramework): self.test_invblock_resolution() self.test_disconnect_fallback() self.test_notfound_fallback() + self.test_max_announcements() def setup_network(self): # set up full nodes @@ -261,5 +268,23 @@ class TxDownloadTest(BitcoinTestFramework): # the losing peer is now the fallback and received a getdata message assert self.wait_for_getdata([txid], [loser]) + def test_max_announcements(self): + # create a test node + peer = self.create_testnode() + peer.wait_for_verack() + + hashes = [] + for _ in range(MAX_PEER_TX_ANNOUNCEMENTS): + hashes.append(self.next_fake_txid()) + + peer.send_tx_inv(hashes) + peer.wait_until_numgetdata(MAX_PEER_TX_ANNOUNCEMENTS) + peer.sync_with_ping() + + # send one more - this should never come back. + extratx = self.next_fake_txid() + peer.send_tx_inv([extratx]) + assert not self.any_received_getdata(extratx, [peer]) + if __name__ == '__main__': TxDownloadTest().main() diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 717c98eee..3825e6a0e 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -52,8 +52,15 @@ struct IteratorComparator /** Maximum number of in-flight transactions from a peer */ static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100; -/** Maximum number of announced transactions from a peer */ -static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ; +/** + * Maximum number of transactions to consider for requesting, per peer. + * + * It provides a reasonable DoS limit to per-peer memory usage spent on + * announcements, while covering peers continuously sending INVs at the maximum + * rate for several minutes (see INVENTORY_BROADCAST_MAX), while not receiving + * the actual transaction (from any peer) in response to requests for them. + */ +static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 5000; /** How many microseconds to delay requesting transactions from inbound peers */ static constexpr int64_t NONPREF_PEER_TX_DELAY = 2 * 1000000; // 2 seconds /** How many microseconds to delay requesting transactions from overloaded peers */