/ test / functional / data / invalid_txs.py
invalid_txs.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2015-2022 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  Templates for constructing various sorts of invalid transactions.
  7  
  8  These templates (or an iterator over all of them) can be reused in different
  9  contexts to test using a number of invalid transaction types.
 10  
 11  Hopefully this makes it easier to get coverage of a full variety of tx
 12  validation checks through different interfaces (AcceptBlock, AcceptToMemPool,
 13  etc.) without repeating ourselves.
 14  
 15  Invalid tx cases not covered here can be found by running:
 16  
 17      $ diff \
 18        <(grep -IREho "bad-txns[a-zA-Z-]+" src | sort -u) \
 19        <(grep -IEho "bad-txns[a-zA-Z-]+" test/functional/data/invalid_txs.py | sort -u)
 20  
 21  """
 22  import abc
 23  
 24  from typing import Optional
 25  from test_framework.messages import (
 26      COutPoint,
 27      CTransaction,
 28      CTxIn,
 29      CTxOut,
 30      MAX_MONEY,
 31      SEQUENCE_FINAL,
 32  )
 33  from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS
 34  from test_framework.script import (
 35      CScript,
 36      OP_0,
 37      OP_2DIV,
 38      OP_2MUL,
 39      OP_AND,
 40      OP_CAT,
 41      OP_CHECKSIG,
 42      OP_DIV,
 43      OP_INVERT,
 44      OP_LEFT,
 45      OP_LSHIFT,
 46      OP_MOD,
 47      OP_MUL,
 48      OP_OR,
 49      OP_RETURN,
 50      OP_RIGHT,
 51      OP_RSHIFT,
 52      OP_SUBSTR,
 53      OP_XOR,
 54  )
 55  from test_framework.script_util import (
 56      MIN_PADDING,
 57      MIN_STANDARD_TX_NONWITNESS_SIZE,
 58      script_to_p2sh_script,
 59  )
 60  basic_p2sh = script_to_p2sh_script(CScript([OP_0]))
 61  
 62  class BadTxTemplate:
 63      """Allows simple construction of a certain kind of invalid tx. Base class to be subclassed."""
 64      __metaclass__ = abc.ABCMeta
 65  
 66      # The expected error code given by bitcoind upon submission of the tx.
 67      reject_reason: Optional[str] = ""
 68  
 69      # Only specified if it differs from mempool acceptance error.
 70      block_reject_reason = ""
 71  
 72      # Do we expect to be disconnected after submitting this tx?
 73      expect_disconnect = False
 74  
 75      # Is this tx considered valid when included in a block, but not for acceptance into
 76      # the mempool (i.e. does it violate policy but not consensus)?
 77      valid_in_block = False
 78  
 79      def __init__(self, *, spend_tx=None, spend_block=None):
 80          self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx
 81          self.spend_avail = sum(o.nValue for o in self.spend_tx.vout)
 82          self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", SEQUENCE_FINAL)
 83  
 84      @abc.abstractmethod
 85      def get_tx(self, *args, **kwargs):
 86          """Return a CTransaction that is invalid per the subclass."""
 87          pass
 88  
 89  
 90  class OutputMissing(BadTxTemplate):
 91      reject_reason = "bad-txns-vout-empty"
 92      expect_disconnect = True
 93  
 94      def get_tx(self):
 95          tx = CTransaction()
 96          tx.vin.append(self.valid_txin)
 97          tx.calc_sha256()
 98          return tx
 99  
100  
101  class InputMissing(BadTxTemplate):
102      reject_reason = "bad-txns-vin-empty"
103      expect_disconnect = True
104  
105      # We use a blank transaction here to make sure
106      # it is interpreted as a non-witness transaction.
107      # Otherwise the transaction will fail the
108      # "surpufluous witness" check during deserialization
109      # rather than the input count check.
110      def get_tx(self):
111          tx = CTransaction()
112          tx.calc_sha256()
113          return tx
114  
115  
116  # The following check prevents exploit of lack of merkle
117  # tree depth commitment (CVE-2017-12842)
118  class SizeTooSmall(BadTxTemplate):
119      reject_reason = "tx-size-small"
120      expect_disconnect = False
121      valid_in_block = True
122  
123      def get_tx(self):
124          tx = CTransaction()
125          tx.vin.append(self.valid_txin)
126          tx.vout.append(CTxOut(0, CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 2)))))
127          assert len(tx.serialize_without_witness()) == 64
128          assert MIN_STANDARD_TX_NONWITNESS_SIZE - 1 == 64
129          tx.calc_sha256()
130          return tx
131  
132  
133  class BadInputOutpointIndex(BadTxTemplate):
134      # Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins
135      # database can't distinguish between spent outpoints and outpoints which never existed.
136      reject_reason = None
137      expect_disconnect = False
138  
139      def get_tx(self):
140          num_indices = len(self.spend_tx.vin)
141          bad_idx = num_indices + 100
142  
143          tx = CTransaction()
144          tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", SEQUENCE_FINAL))
145          tx.vout.append(CTxOut(0, basic_p2sh))
146          tx.calc_sha256()
147          return tx
148  
149  
150  class DuplicateInput(BadTxTemplate):
151      reject_reason = 'bad-txns-inputs-duplicate'
152      expect_disconnect = True
153  
154      def get_tx(self):
155          tx = CTransaction()
156          tx.vin.append(self.valid_txin)
157          tx.vin.append(self.valid_txin)
158          tx.vout.append(CTxOut(1, basic_p2sh))
159          tx.calc_sha256()
160          return tx
161  
162  
163  class PrevoutNullInput(BadTxTemplate):
164      reject_reason = 'bad-txns-prevout-null'
165      expect_disconnect = True
166  
167      def get_tx(self):
168          tx = CTransaction()
169          tx.vin.append(self.valid_txin)
170          tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff)))
171          tx.vout.append(CTxOut(1, basic_p2sh))
172          tx.calc_sha256()
173          return tx
174  
175  
176  class NonexistentInput(BadTxTemplate):
177      reject_reason = None  # Added as an orphan tx.
178      expect_disconnect = False
179  
180      def get_tx(self):
181          tx = CTransaction()
182          tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", SEQUENCE_FINAL))
183          tx.vin.append(self.valid_txin)
184          tx.vout.append(CTxOut(1, basic_p2sh))
185          tx.calc_sha256()
186          return tx
187  
188  
189  class SpendTooMuch(BadTxTemplate):
190      reject_reason = 'bad-txns-in-belowout'
191      expect_disconnect = True
192  
193      def get_tx(self):
194          return create_tx_with_script(
195              self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1))
196  
197  
198  class CreateNegative(BadTxTemplate):
199      reject_reason = 'bad-txns-vout-negative'
200      expect_disconnect = True
201  
202      def get_tx(self):
203          return create_tx_with_script(self.spend_tx, 0, amount=-1)
204  
205  
206  class CreateTooLarge(BadTxTemplate):
207      reject_reason = 'bad-txns-vout-toolarge'
208      expect_disconnect = True
209  
210      def get_tx(self):
211          return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1)
212  
213  
214  class CreateSumTooLarge(BadTxTemplate):
215      reject_reason = 'bad-txns-txouttotal-toolarge'
216      expect_disconnect = True
217  
218      def get_tx(self):
219          tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY)
220          tx.vout = [tx.vout[0]] * 2
221          tx.calc_sha256()
222          return tx
223  
224  
225  class InvalidOPIFConstruction(BadTxTemplate):
226      reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)"
227      expect_disconnect = True
228      valid_in_block = True
229  
230      def get_tx(self):
231          return create_tx_with_script(
232              self.spend_tx, 0, script_sig=b'\x64' * 35,
233              amount=(self.spend_avail // 2))
234  
235  
236  class TooManySigops(BadTxTemplate):
237      reject_reason = "bad-txns-too-many-sigops"
238      block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount"
239      expect_disconnect = False
240  
241      def get_tx(self):
242          lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS))
243          return create_tx_with_script(
244              self.spend_tx, 0,
245              script_pub_key=lotsa_checksigs,
246              amount=1)
247  
248  def getDisabledOpcodeTemplate(opcode):
249      """ Creates disabled opcode tx template class"""
250      def get_tx(self):
251          tx = CTransaction()
252          vin = self.valid_txin
253          vin.scriptSig = CScript([opcode])
254          tx.vin.append(vin)
255          tx.vout.append(CTxOut(1, basic_p2sh))
256          tx.calc_sha256()
257          return tx
258  
259      return type('DisabledOpcode_' + str(opcode), (BadTxTemplate,), {
260          'reject_reason': "disabled opcode",
261          'expect_disconnect': True,
262          'get_tx': get_tx,
263          'valid_in_block' : True
264          })
265  
266  # Disabled opcode tx templates (CVE-2010-5137)
267  DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [
268      OP_CAT,
269      OP_SUBSTR,
270      OP_LEFT,
271      OP_RIGHT,
272      OP_INVERT,
273      OP_AND,
274      OP_OR,
275      OP_XOR,
276      OP_2MUL,
277      OP_2DIV,
278      OP_MUL,
279      OP_DIV,
280      OP_MOD,
281      OP_LSHIFT,
282      OP_RSHIFT]]
283  
284  
285  def iter_all_templates():
286      """Iterate through all bad transaction template types."""
287      return BadTxTemplate.__subclasses__()