would be good to explicitly test that the parent-giver isn't punished in this scenario, and test if parent is consensus-bad it results in something expected.
diff --git a/test/functional/p2p_opportunistic_1p1c.py b/test/functional/p2p_opportunistic_1p1c.py
index 603cbf08a9..1c887289dc 100755
--- a/test/functional/p2p_opportunistic_1p1c.py
+++ b/test/functional/p2p_opportunistic_1p1c.py
@@ -164,78 +164,129 @@ class PackageRelayTest(BitcoinTestFramework):
# itself and with a child. This is necessary, otherwise high_fee_child can be censored.
parent_txid_int = int(low_fee_parent["txid"], 16)
peer_sender.wait_for_getdata([parent_txid_int])
# 7. The low feerate parent + high feerate child are submitted as a package.
peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
# 8. Both transactions should now be in mempool
node_mempool = node.getrawmempool()
assert low_fee_parent["txid"] in node_mempool
assert high_fee_child["txid"] in node_mempool
node.disconnect_p2ps()
def test_orphan_consensus_failure(self):
node = self.nodes[0]
low_fee_parent = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, confirmed_only=True)
coin = low_fee_parent["new_utxo"]
address = node.get_deterministic_priv_key().address
# Create raw transaction spending the parent, but with no signature (a consensus error).
hex_orphan_no_sig = node.createrawtransaction([{"txid": coin["txid"], "vout": coin["vout"]}], {address : coin["value"] - Decimal("0.0001")})
tx_orphan_bad_wit = tx_from_hex(hex_orphan_no_sig)
tx_orphan_bad_wit.wit.vtxinwit.append(CTxInWitness())
tx_orphan_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
- peer_sender = node.add_p2p_connection(P2PInterface())
+ child_peer_sender = node.add_p2p_connection(P2PInterface())
+ parent_peer_sender = node.add_p2p_connection(P2PInterface())
# 1. Child is received first. It is missing an input.
child_wtxid_int = int(tx_orphan_bad_wit.getwtxid(), 16)
- peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
- peer_sender.wait_for_getdata([child_wtxid_int])
- peer_sender.send_and_ping(msg_tx(tx_orphan_bad_wit))
+ child_peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
+ child_peer_sender.wait_for_getdata([child_wtxid_int])
+ child_peer_sender.send_and_ping(msg_tx(tx_orphan_bad_wit))
# 2. Node requests the missing parent by txid.
parent_txid_int = int(low_fee_parent["txid"], 16)
- peer_sender.wait_for_getdata([parent_txid_int])
+ child_peer_sender.wait_for_getdata([parent_txid_int])
- # 3. Sender relays the parent. Parent+Child are evaluated as a package and accepted.
- peer_sender.send_message(msg_tx(low_fee_parent["tx"]))
+ # 3. "Honest" sender relays the parent. Parent+Child are evaluated as a package and accepted.
+ parent_peer_sender.send_message(msg_tx(low_fee_parent["tx"]))
# 4. Transactions should not be in mempool.
node_mempool = node.getrawmempool()
assert low_fee_parent["txid"] not in node_mempool
assert tx_orphan_bad_wit.rehash() not in node_mempool
# 5. Peer sent a consensus-invalid transaction.
- peer_sender.wait_for_disconnect()
+ child_peer_sender.wait_for_disconnect()
+
+ # 6. "Honest" peer unpunished
+ parent_peer_sender.sync_with_ping()
+
+ def test_parent_consensus_failure(self):
+ node = self.nodes[0]
+ low_fee_parent = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, confirmed_only=True)
+
+ # Add bad sigature to parent
+ tx_parent_bad_wit = tx_from_hex(low_fee_parent["hex"])
+ tx_parent_bad_wit.wit.vtxinwit.append(CTxInWitness())
+ tx_parent_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
+
+ coin = low_fee_parent["new_utxo"]
+ address = node.get_deterministic_priv_key().address
+ # Create raw transaction spending the parent, but with no signature (a consensus error).
+ hex_orphan_no_sig = node.createrawtransaction([{"txid": coin["txid"], "vout": coin["vout"]}], {address : coin["value"] - Decimal("0.0001")})
+ tx_orphan_bad_wit = tx_from_hex(hex_orphan_no_sig)
+ tx_orphan_bad_wit.wit.vtxinwit.append(CTxInWitness())
+ tx_orphan_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
+
+ child_peer_sender = node.add_p2p_connection(P2PInterface())
+ parent_peer_sender = node.add_p2p_connection(P2PInterface())
+
+ # 1. Child is received first. It is missing an input.
+ child_wtxid_int = int(tx_orphan_bad_wit.getwtxid(), 16)
+ child_peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
+ child_peer_sender.wait_for_getdata([child_wtxid_int])
+ child_peer_sender.send_and_ping(msg_tx(tx_orphan_bad_wit))
+
+ # 2. Node requests the missing parent by txid.
+ parent_txid_int = int(tx_parent_bad_wit.rehash(), 16)
+ child_peer_sender.wait_for_getdata([parent_txid_int])
+
+ # 3. Parent sender relays the parent. Only parent should be evaluated.
+ parent_peer_sender.send_message(msg_tx(tx_parent_bad_wit))
+
+ # 4. Transactions should not be in mempool.
+ node_mempool = node.getrawmempool()
+ assert tx_parent_bad_wit.rehash() not in node_mempool
+ assert tx_orphan_bad_wit.rehash() not in node_mempool
+
+ # 5. Peer sent a consensus-invalid transaction.
+ parent_peer_sender.wait_for_disconnect()
+
+ # 6. Child-sending peer unpunished for now!
+ child_peer_sender.sync_with_ping()
def run_test(self):
node = self.nodes[0]
self.wallet = MiniWallet(node)
self.wallet_nonsegwit = MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)
self.generate(self.wallet_nonsegwit, 10)
self.generate(self.wallet, 20)
fill_mempool(self, node, self.wallet)
self.log.info("Check opportunistic 1p1c logic when parent (txid != wtxid) is received before child")
self.test_basic_parent_then_child(self.wallet)
self.log.info("Check opportunistic 1p1c logic when parent (txid == wtxid) is received before child")
self.test_basic_parent_then_child(self.wallet_nonsegwit)
self.log.info("Check opportunistic 1p1c logic when child is received before parent")
self.test_basic_child_then_parent()
self.log.info("Check opportunistic 1p1c logic when 2 candidate children exist (parent txid != wtxid)")
self.test_low_and_high_child(self.wallet)
self.log.info("Check opportunistic 1p1c logic when 2 candidate children exist (parent txid == wtxid)")
self.test_low_and_high_child(self.wallet_nonsegwit)
self.log.info("Check opportunistic 1p1c logic with consensus-invalid orphan causes disconnect")
self.test_orphan_consensus_failure()
+ self.log.info("Check opportunistic 1p1c logic doesn't evaluate package with consensus-invalid parent")
+ self.test_parent_consensus_failure()
+
if __name__ == '__main__':
PackageRelayTest().main()