2022-05-06 06:04:50 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2017-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 that the mempool ensures transaction delivery by periodically sending
|
|
|
|
to peers until a GETDATA is received."""
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
2024-01-15 20:35:29 +01:00
|
|
|
from test_framework.p2p import P2PTxInvStore
|
2022-05-06 06:04:50 +02:00
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
|
|
from test_framework.util import (
|
|
|
|
assert_equal,
|
|
|
|
create_confirmed_utxos,
|
|
|
|
)
|
|
|
|
|
2020-05-30 18:22:03 +02:00
|
|
|
MAX_INITIAL_BROADCAST_DELAY = 15 * 60 # 15 minutes in seconds
|
2022-05-06 06:04:50 +02:00
|
|
|
|
|
|
|
class MempoolUnbroadcastTest(BitcoinTestFramework):
|
|
|
|
def set_test_params(self):
|
|
|
|
self.num_nodes = 2
|
|
|
|
|
|
|
|
def skip_test_if_missing_module(self):
|
|
|
|
self.skip_if_no_wallet()
|
|
|
|
|
|
|
|
def run_test(self):
|
|
|
|
self.test_broadcast()
|
|
|
|
self.test_txn_removal()
|
|
|
|
|
|
|
|
def test_broadcast(self):
|
|
|
|
self.log.info("Test that mempool reattempts delivery of locally submitted transaction")
|
|
|
|
node = self.nodes[0]
|
|
|
|
|
|
|
|
min_relay_fee = node.getnetworkinfo()["relayfee"]
|
2024-09-26 21:13:22 +02:00
|
|
|
utxos = create_confirmed_utxos(self, min_relay_fee, node, 10)
|
2022-05-06 06:04:50 +02:00
|
|
|
|
2022-09-24 14:36:35 +02:00
|
|
|
self.disconnect_nodes(0, 1)
|
2022-05-06 06:04:50 +02:00
|
|
|
|
|
|
|
self.log.info("Generate transactions that only node 0 knows about")
|
|
|
|
|
|
|
|
# generate a wallet txn
|
|
|
|
addr = node.getnewaddress()
|
|
|
|
wallet_tx_hsh = node.sendtoaddress(addr, 0.0001)
|
|
|
|
|
|
|
|
# generate a txn using sendrawtransaction
|
|
|
|
us0 = utxos.pop()
|
|
|
|
inputs = [{"txid": us0["txid"], "vout": us0["vout"]}]
|
|
|
|
outputs = {addr: 0.0001}
|
|
|
|
tx = node.createrawtransaction(inputs, outputs)
|
|
|
|
node.settxfee(min_relay_fee)
|
|
|
|
txF = node.fundrawtransaction(tx)
|
|
|
|
txFS = node.signrawtransactionwithwallet(txF["hex"])
|
|
|
|
rpc_tx_hsh = node.sendrawtransaction(txFS["hex"])
|
|
|
|
|
2020-05-22 01:27:28 +02:00
|
|
|
# check transactions are in unbroadcast using rpc
|
|
|
|
mempoolinfo = self.nodes[0].getmempoolinfo()
|
|
|
|
assert_equal(mempoolinfo['unbroadcastcount'], 2)
|
|
|
|
mempool = self.nodes[0].getrawmempool(True)
|
|
|
|
for tx in mempool:
|
|
|
|
assert_equal(mempool[tx]['unbroadcast'], True)
|
|
|
|
|
2022-05-06 06:04:50 +02:00
|
|
|
# check that second node doesn't have these two txns
|
|
|
|
mempool = self.nodes[1].getrawmempool()
|
|
|
|
assert rpc_tx_hsh not in mempool
|
|
|
|
assert wallet_tx_hsh not in mempool
|
|
|
|
|
|
|
|
# ensure that unbroadcast txs are persisted to mempool.dat
|
|
|
|
self.restart_node(0)
|
|
|
|
|
|
|
|
self.log.info("Reconnect nodes & check if they are sent to node 1")
|
2022-09-24 14:36:35 +02:00
|
|
|
self.connect_nodes(0, 1)
|
2022-05-06 06:04:50 +02:00
|
|
|
|
|
|
|
# fast forward into the future & ensure that the second node has the txns
|
2020-05-30 18:22:03 +02:00
|
|
|
node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
|
2022-05-06 06:04:50 +02:00
|
|
|
self.sync_mempools(timeout=30)
|
|
|
|
mempool = self.nodes[1].getrawmempool()
|
|
|
|
assert rpc_tx_hsh in mempool
|
|
|
|
assert wallet_tx_hsh in mempool
|
|
|
|
|
2020-05-22 01:27:28 +02:00
|
|
|
# check that transactions are no longer in first node's unbroadcast set
|
|
|
|
mempool = self.nodes[0].getrawmempool(True)
|
|
|
|
for tx in mempool:
|
|
|
|
assert_equal(mempool[tx]['unbroadcast'], False)
|
|
|
|
|
2022-05-06 06:04:50 +02:00
|
|
|
self.log.info("Add another connection & ensure transactions aren't broadcast again")
|
|
|
|
|
|
|
|
conn = node.add_p2p_connection(P2PTxInvStore())
|
2020-05-30 18:22:03 +02:00
|
|
|
node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY)
|
|
|
|
time.sleep(2) # allow sufficient time for possibility of broadcast
|
2022-05-06 06:04:50 +02:00
|
|
|
assert_equal(len(conn.get_invs()), 0)
|
|
|
|
|
2020-05-30 18:22:03 +02:00
|
|
|
self.disconnect_nodes(0, 1)
|
|
|
|
node.disconnect_p2ps()
|
|
|
|
|
Merge bitcoin/bitcoin#22261: [p2p/mempool] Two small fixes to node broadcast logic
5a77abd4e657458852875a07692898982f4b1db5 [style] Clean up BroadcastTransaction() (John Newbery)
7282d4c0363ab5152baa34af626cb49afbfddc32 [test] Allow rebroadcast for same-txid-different-wtxid transactions (glozow)
cd48372b67d961fe661990a2c6d3cc3d91478924 [mempool] Allow rebroadcast for same-txid-different-wtxid transactions (John Newbery)
847b6ed48d7bacec9024618922e9b339d2d97676 [test] Test transactions are not re-added to unbroadcast set (Duncan Dean)
2837a9f1eaa2c6bf402d1d9891d9aa84c4a56033 [mempool] Only add a transaction to the unbroadcast set when it's added to the mempool (John Newbery)
Pull request description:
1. Only add a transaction to the unbroadcast set when it's added to the mempool
Currently, if BroadcastTransaction() is called to rebroadcast a
transaction (e.g. by ResendWalletTransactions()), then we add the
transaction to the unbroadcast set. That transaction has already been
broadcast in the past, so peers are unlikely to request it again,
meaning RemoveUnbroadcastTx() won't be called and it won't be removed
from m_unbroadcast_txids.
Net processing will therefore continue to attempt rebroadcast for the
transaction every 10-15 minutes. This will most likely continue until
the node connects to a new peer which hasn't yet seen the transaction
(or perhaps indefinitely).
Fix by only adding the transaction to the broadcast set when it's added to the mempool.
2. Allow rebroadcast for same-txid-different-wtxid transactions
There is some slightly unexpected behaviour when:
- there is already transaction in the mempool (the "mempool tx")
- BroadcastTransaction() is called for a transaction with the same txid
as the mempool transaction but a different witness (the "new tx")
Prior to this commit, if BroadcastTransaction() is called with
relay=true, then it'll call RelayTransaction() using the txid/wtxid of
the new tx, not the txid/wtxid of the mempool tx. For wtxid relay peers,
in SendMessages(), the wtxid of the new tx will be taken from
setInventoryTxToSend, but will then be filtered out from the vector of
wtxids to announce, since m_mempool.info() won't find the transaction
(the mempool contains the mempool tx, which has a different wtxid from
the new tx).
Fix this by calling RelayTransaction() with the wtxid of the mempool
transaction in this case.
The third commit is a comment/whitespace only change to tidy up the BroadcastTransaction() function.
ACKs for top commit:
duncandean:
reACK 5a77abd
naumenkogs:
ACK 5a77abd4e657458852875a07692898982f4b1db5
theStack:
re-ACK 5a77abd4e657458852875a07692898982f4b1db5
lsilva01:
re-ACK https://github.com/bitcoin/bitcoin/pull/22261/commits/5a77abd4e657458852875a07692898982f4b1db5
Tree-SHA512: d1a46d32a9f975220e5b432ff6633fac9be01ea41925b4958395b8d641680500dc44476b12d18852e5b674d2d87e4d0160b4483e45d3d149176bdff9f4dc8516
2021-07-20 14:39:57 +02:00
|
|
|
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']
|
|
|
|
|
2022-05-06 06:04:50 +02:00
|
|
|
def test_txn_removal(self):
|
|
|
|
self.log.info("Test that transactions removed from mempool are removed from unbroadcast set")
|
|
|
|
node = self.nodes[0]
|
|
|
|
|
|
|
|
# since the node doesn't have any connections, it will not receive
|
|
|
|
# any GETDATAs & thus the transaction will remain in the unbroadcast set.
|
|
|
|
addr = node.getnewaddress()
|
|
|
|
txhsh = node.sendtoaddress(addr, 0.0001)
|
|
|
|
|
|
|
|
# check transaction was removed from unbroadcast set due to presence in
|
|
|
|
# a block
|
|
|
|
removal_reason = "Removed {} from set of unbroadcast txns before confirmation that txn was sent out".format(txhsh)
|
|
|
|
with node.assert_debug_log([removal_reason]):
|
2024-09-26 21:17:04 +02:00
|
|
|
self.generate(node, 1, sync_fun=self.no_op)
|
|
|
|
|
2022-05-06 06:04:50 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
MempoolUnbroadcastTest().main()
|