/ test / functional / mempool_accept_wtxid.py
mempool_accept_wtxid.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2021 The Bitcoin Core developers
  3  # Distributed under the MIT software license, see the accompanying
  4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
  5  """
  6  Test mempool acceptance in case of an already known transaction
  7  with identical non-witness data but different witness.
  8  """
  9  
 10  from copy import deepcopy
 11  from test_framework.messages import (
 12      COIN,
 13      COutPoint,
 14      CTransaction,
 15      CTxIn,
 16      CTxInWitness,
 17      CTxOut,
 18      sha256,
 19  )
 20  from test_framework.p2p import P2PTxInvStore
 21  from test_framework.script import (
 22      CScript,
 23      OP_0,
 24      OP_ELSE,
 25      OP_ENDIF,
 26      OP_EQUAL,
 27      OP_HASH160,
 28      OP_IF,
 29      OP_TRUE,
 30      hash160,
 31  )
 32  from test_framework.test_framework import BitcoinTestFramework
 33  from test_framework.util import (
 34      assert_not_equal,
 35      assert_equal,
 36  )
 37  
 38  class MempoolWtxidTest(BitcoinTestFramework):
 39      def set_test_params(self):
 40          self.num_nodes = 1
 41          self.setup_clean_chain = True
 42  
 43      def run_test(self):
 44          node = self.nodes[0]
 45  
 46          self.log.info('Start with empty mempool and 101 blocks')
 47          # The last 100 coinbase transactions are premature
 48          blockhash = self.generate(node, 101)[0]
 49          txid = node.getblock(blockhash=blockhash, verbosity=2)["tx"][0]["txid"]
 50          assert_equal(node.getmempoolinfo()['size'], 0)
 51  
 52          self.log.info("Submit parent with multiple script branches to mempool")
 53          hashlock = hash160(b'Preimage')
 54          witness_script = CScript([OP_IF, OP_HASH160, hashlock, OP_EQUAL, OP_ELSE, OP_TRUE, OP_ENDIF])
 55          witness_program = sha256(witness_script)
 56          script_pubkey = CScript([OP_0, witness_program])
 57  
 58          parent = CTransaction()
 59          parent.vin.append(CTxIn(COutPoint(int(txid, 16), 0), b""))
 60          parent.vout.append(CTxOut(int(9.99998 * COIN), script_pubkey))
 61          parent.rehash()
 62  
 63          privkeys = [node.get_deterministic_priv_key().key]
 64          raw_parent = node.signrawtransactionwithkey(hexstring=parent.serialize().hex(), privkeys=privkeys)['hex']
 65          parent_txid = node.sendrawtransaction(hexstring=raw_parent, maxfeerate=0)
 66          self.generate(node, 1)
 67  
 68          peer_wtxid_relay = node.add_p2p_connection(P2PTxInvStore())
 69  
 70          # Create a new transaction with witness solving first branch
 71          child_witness_script = CScript([OP_TRUE])
 72          child_witness_program = sha256(child_witness_script)
 73          child_script_pubkey = CScript([OP_0, child_witness_program])
 74  
 75          child_one = CTransaction()
 76          child_one.vin.append(CTxIn(COutPoint(int(parent_txid, 16), 0), b""))
 77          child_one.vout.append(CTxOut(int(9.99996 * COIN), child_script_pubkey))
 78          child_one.wit.vtxinwit.append(CTxInWitness())
 79          child_one.wit.vtxinwit[0].scriptWitness.stack = [b'Preimage', b'\x01', witness_script]
 80          child_one_wtxid = child_one.getwtxid()
 81          child_one_txid = child_one.rehash()
 82  
 83          # Create another identical transaction with witness solving second branch
 84          child_two = deepcopy(child_one)
 85          child_two.wit.vtxinwit[0].scriptWitness.stack = [b'', witness_script]
 86          child_two_wtxid = child_two.getwtxid()
 87          child_two_txid = child_two.rehash()
 88  
 89          assert_equal(child_one_txid, child_two_txid)
 90          assert_not_equal(child_one_wtxid, child_two_wtxid)
 91  
 92          self.log.info("Submit child_one to the mempool")
 93          txid_submitted = node.sendrawtransaction(child_one.serialize().hex())
 94          assert_equal(node.getmempoolentry(txid_submitted)['wtxid'], child_one_wtxid)
 95  
 96          peer_wtxid_relay.wait_for_broadcast([child_one_wtxid])
 97          assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0)
 98  
 99          # testmempoolaccept reports the "already in mempool" error
100          assert_equal(node.testmempoolaccept([child_one.serialize().hex()]), [{
101              "txid": child_one_txid,
102              "wtxid": child_one_wtxid,
103              "allowed": False,
104              "reject-reason": "txn-already-in-mempool",
105              "reject-details": "txn-already-in-mempool"
106          }])
107          assert_equal(node.testmempoolaccept([child_two.serialize().hex()])[0], {
108              "txid": child_two_txid,
109              "wtxid": child_two_wtxid,
110              "allowed": False,
111              "reject-reason": "txn-same-nonwitness-data-in-mempool",
112              "reject-details": "txn-same-nonwitness-data-in-mempool"
113          })
114  
115          # sendrawtransaction will not throw but quits early when the exact same transaction is already in mempool
116          node.sendrawtransaction(child_one.serialize().hex())
117  
118          self.log.info("Connect another peer that hasn't seen child_one before")
119          peer_wtxid_relay_2 = node.add_p2p_connection(P2PTxInvStore())
120  
121          self.log.info("Submit child_two to the mempool")
122          # sendrawtransaction will not throw but quits early when a transaction with the same non-witness data is already in mempool
123          node.sendrawtransaction(child_two.serialize().hex())
124  
125          # The node should rebroadcast the transaction using the wtxid of the correct transaction
126          # (child_one, which is in its mempool).
127          peer_wtxid_relay_2.wait_for_broadcast([child_one_wtxid])
128          assert_equal(node.getmempoolinfo()["unbroadcastcount"], 0)
129  
130  if __name__ == '__main__':
131      MempoolWtxidTest(__file__).main()