invalid_txs.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2015-present 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      CTxInWitness,
  30      CTxOut,
  31      MAX_MONEY,
  32      SEQUENCE_FINAL,
  33  )
  34  from test_framework.blocktools import (
  35      create_tx_with_script,
  36      MAX_BLOCK_SIGOPS,
  37      MAX_STANDARD_TX_SIGOPS,
  38  )
  39  from test_framework.script import (
  40      OP_TRUE,
  41      CScript,
  42      OP_0,
  43      OP_2DIV,
  44      OP_2MUL,
  45      OP_AND,
  46      OP_CAT,
  47      OP_CHECKSIG,
  48      OP_DIV,
  49      OP_INVERT,
  50      OP_LEFT,
  51      OP_LSHIFT,
  52      OP_MOD,
  53      OP_MUL,
  54      OP_OR,
  55      OP_RETURN,
  56      OP_RIGHT,
  57      OP_RSHIFT,
  58      OP_SUBSTR,
  59      OP_XOR,
  60  )
  61  from test_framework.util import assert_equal
  62  from test_framework.script_util import (
  63      MIN_PADDING,
  64      MIN_STANDARD_TX_NONWITNESS_SIZE,
  65      script_to_p2sh_script,
  66  )
  67  basic_p2sh = script_to_p2sh_script(CScript([OP_0]))
  68  
  69  class BadTxTemplate:
  70      """Allows simple construction of a certain kind of invalid tx. Base class to be subclassed."""
  71      __metaclass__ = abc.ABCMeta
  72  
  73      # The expected error code given by bitcoind upon submission of the tx.
  74      reject_reason: Optional[str] = ""
  75  
  76      # Only specified if it differs from mempool acceptance error.
  77      block_reject_reason = ""
  78  
  79      # Is this tx considered valid when included in a block, but not for acceptance into
  80      # the mempool (i.e. does it violate policy but not consensus)?
  81      valid_in_block = False
  82  
  83      def __init__(self, *, spend_tx=None, spend_block=None):
  84          self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx
  85          self.spend_avail = sum(o.nValue for o in self.spend_tx.vout)
  86          self.valid_txin = CTxIn(COutPoint(self.spend_tx.txid_int, 0), b"", SEQUENCE_FINAL)
  87  
  88      @abc.abstractmethod
  89      def get_tx(self, *args, **kwargs):
  90          """Return a CTransaction that is invalid per the subclass."""
  91          pass
  92  
  93  
  94  class OutputMissing(BadTxTemplate):
  95      reject_reason = "bad-txns-vout-empty"
  96  
  97      def get_tx(self):
  98          tx = CTransaction()
  99          tx.vin.append(self.valid_txin)
 100          return tx
 101  
 102  
 103  class InputMissing(BadTxTemplate):
 104      reject_reason = "bad-txns-vin-empty"
 105  
 106      # We use a blank transaction here to make sure
 107      # it is interpreted as a non-witness transaction.
 108      # Otherwise the transaction will fail the
 109      # "surpufluous witness" check during deserialization
 110      # rather than the input count check.
 111      def get_tx(self):
 112          tx = CTransaction()
 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      valid_in_block = True
 121  
 122      def get_tx(self):
 123          tx = CTransaction()
 124          tx.vin.append(self.valid_txin)
 125          tx.vout.append(CTxOut(0, CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 2)))))
 126          assert_equal(len(tx.serialize_without_witness()), 64)
 127          assert_equal(MIN_STANDARD_TX_NONWITNESS_SIZE - 1, 64)
 128          return tx
 129  
 130  # reject a transaction that contains a witness
 131  # but doesn't spend a segwit output
 132  class ExtraWitness(BadTxTemplate):
 133      reject_reason = "tx-size-small"
 134      block_reject_reason = "block-script-verify-flag-failed (Witness provided for non-witness script)"
 135  
 136      def get_tx(self):
 137          tx = CTransaction()
 138          tx.vin.append(self.valid_txin)
 139          tx.vout.append(CTxOut(0, CScript()))
 140          tx.wit.vtxinwit = [CTxInWitness()]
 141          tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_TRUE])]
 142          return tx
 143  
 144  
 145  class BadInputOutpointIndex(BadTxTemplate):
 146      # Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins
 147      # database can't distinguish between spent outpoints and outpoints which never existed.
 148      reject_reason = None
 149      # But fails in block
 150      block_reject_reason = "bad-txns-inputs-missingorspent"
 151  
 152      def get_tx(self):
 153          num_indices = len(self.spend_tx.vin)
 154          bad_idx = num_indices + 100
 155  
 156          tx = CTransaction()
 157          tx.vin.append(CTxIn(COutPoint(self.spend_tx.txid_int, bad_idx), b"", SEQUENCE_FINAL))
 158          tx.vout.append(CTxOut(0, basic_p2sh))
 159          return tx
 160  
 161  
 162  class DuplicateInput(BadTxTemplate):
 163      reject_reason = 'bad-txns-inputs-duplicate'
 164  
 165      def get_tx(self):
 166          tx = CTransaction()
 167          tx.vin.append(self.valid_txin)
 168          tx.vin.append(self.valid_txin)
 169          tx.vout.append(CTxOut(1, basic_p2sh))
 170          return tx
 171  
 172  
 173  class PrevoutNullInput(BadTxTemplate):
 174      reject_reason = 'bad-txns-prevout-null'
 175  
 176      def get_tx(self):
 177          tx = CTransaction()
 178          tx.vin.append(self.valid_txin)
 179          tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff)))
 180          tx.vout.append(CTxOut(1, basic_p2sh))
 181          return tx
 182  
 183  
 184  class NonexistentInput(BadTxTemplate):
 185      reject_reason = None  # Added as an orphan tx.
 186      # But fails in block
 187      block_reject_reason = "bad-txns-inputs-missingorspent"
 188  
 189      def get_tx(self):
 190          tx = CTransaction()
 191          tx.vin.append(CTxIn(COutPoint(self.spend_tx.txid_int + 1, 0), b"", SEQUENCE_FINAL))
 192          tx.vin.append(self.valid_txin)
 193          tx.vout.append(CTxOut(1, basic_p2sh))
 194          return tx
 195  
 196  
 197  class SpendTooMuch(BadTxTemplate):
 198      reject_reason = 'bad-txns-in-belowout'
 199  
 200      def get_tx(self):
 201          return create_tx_with_script(
 202              self.spend_tx, 0, output_script=basic_p2sh, amount=(self.spend_avail + 1))
 203  
 204  
 205  class CreateNegative(BadTxTemplate):
 206      reject_reason = 'bad-txns-vout-negative'
 207  
 208      def get_tx(self):
 209          return create_tx_with_script(self.spend_tx, 0, amount=-1)
 210  
 211  
 212  class CreateTooLarge(BadTxTemplate):
 213      reject_reason = 'bad-txns-vout-toolarge'
 214  
 215      def get_tx(self):
 216          return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1)
 217  
 218  
 219  class CreateSumTooLarge(BadTxTemplate):
 220      reject_reason = 'bad-txns-txouttotal-toolarge'
 221  
 222      def get_tx(self):
 223          tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY)
 224          tx.vout = [tx.vout[0]] * 2
 225          return tx
 226  
 227  
 228  class InvalidOPIFConstruction(BadTxTemplate):
 229      reject_reason = "mempool-script-verify-flag-failed (Invalid OP_IF construction)"
 230  
 231      def get_tx(self):
 232          return create_tx_with_script(
 233              self.spend_tx, 0, script_sig=b'\x68' * 35,
 234              amount=(self.spend_avail // 2))
 235  
 236  
 237  class TooManySigopsPerBlock(BadTxTemplate):
 238      reject_reason = "bad-txns-too-many-sigops"
 239      block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount"
 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              output_script=lotsa_checksigs,
 246              amount=1)
 247  
 248  
 249  class TooManySigopsPerTransaction(BadTxTemplate):
 250      reject_reason = "bad-txns-too-many-sigops"
 251      valid_in_block = True
 252  
 253      def get_tx(self):
 254          lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_STANDARD_TX_SIGOPS + 1))
 255          return create_tx_with_script(
 256              self.spend_tx, 0,
 257              output_script=lotsa_checksigs,
 258              amount=1)
 259  
 260  
 261  def getDisabledOpcodeTemplate(opcode):
 262      """ Creates disabled opcode tx template class"""
 263      def get_tx(self):
 264          tx = CTransaction()
 265          vin = self.valid_txin
 266          vin.scriptSig = CScript([opcode])
 267          tx.vin.append(vin)
 268          tx.vout.append(CTxOut(1, basic_p2sh))
 269          return tx
 270  
 271      return type('DisabledOpcode_' + str(opcode), (BadTxTemplate,), {
 272          'reject_reason': "disabled opcode",
 273          'get_tx': get_tx,
 274          'valid_in_block' : False
 275          })
 276  
 277  class NonStandardAndInvalid(BadTxTemplate):
 278      """A non-standard transaction which is also consensus-invalid should return the first error."""
 279      reject_reason = "mempool-script-verify-flag-failed (Using OP_CODESEPARATOR in non-witness script)"
 280      block_reject_reason = "block-script-verify-flag-failed (OP_RETURN was encountered)"
 281      valid_in_block = False
 282  
 283      def get_tx(self):
 284          return create_tx_with_script(
 285              self.spend_tx, 0, script_sig=b'\x00' * 3 + b'\xab\x6a',
 286              amount=(self.spend_avail // 2))
 287  
 288  # Disabled opcode tx templates (CVE-2010-5137)
 289  DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [
 290      OP_CAT,
 291      OP_SUBSTR,
 292      OP_LEFT,
 293      OP_RIGHT,
 294      OP_INVERT,
 295      OP_AND,
 296      OP_OR,
 297      OP_XOR,
 298      OP_2MUL,
 299      OP_2DIV,
 300      OP_MUL,
 301      OP_DIV,
 302      OP_MOD,
 303      OP_LSHIFT,
 304      OP_RSHIFT]]
 305  
 306  
 307  def iter_all_templates():
 308      """Iterate through all bad transaction template types."""
 309      return BadTxTemplate.__subclasses__()
 310