invalid_txs.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2015-2022 The Limenka 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 limenkad 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      # Is this tx considered valid when included in a block, but not for acceptance into
  73      # the mempool (i.e. does it violate policy but not consensus)?
  74      valid_in_block = False
  75  
  76      def __init__(self, *, spend_tx=None, spend_block=None):
  77          self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx
  78          self.spend_avail = sum(o.nValue for o in self.spend_tx.vout)
  79          self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", SEQUENCE_FINAL)
  80  
  81      @abc.abstractmethod
  82      def get_tx(self, *args, **kwargs):
  83          """Return a CTransaction that is invalid per the subclass."""
  84          pass
  85  
  86  
  87  class OutputMissing(BadTxTemplate):
  88      reject_reason = "bad-txns-vout-empty"
  89  
  90      def get_tx(self):
  91          tx = CTransaction()
  92          tx.vin.append(self.valid_txin)
  93          tx.calc_sha256()
  94          return tx
  95  
  96  
  97  class InputMissing(BadTxTemplate):
  98      reject_reason = "bad-txns-vin-empty"
  99  
 100      # We use a blank transaction here to make sure
 101      # it is interpreted as a non-witness transaction.
 102      # Otherwise the transaction will fail the
 103      # "surpufluous witness" check during deserialization
 104      # rather than the input count check.
 105      def get_tx(self):
 106          tx = CTransaction()
 107          tx.calc_sha256()
 108          return tx
 109  
 110  
 111  # The following check prevents exploit of lack of merkle
 112  # tree depth commitment (CVE-2017-12842)
 113  class SizeTooSmall(BadTxTemplate):
 114      reject_reason = "tx-size-small"
 115      valid_in_block = True
 116  
 117      def get_tx(self):
 118          tx = CTransaction()
 119          tx.vin.append(self.valid_txin)
 120          tx.vout.append(CTxOut(0, CScript([OP_RETURN] + ([OP_0] * (MIN_PADDING - 2)))))
 121          assert len(tx.serialize_without_witness()) == 64
 122          assert MIN_STANDARD_TX_NONWITNESS_SIZE - 1 == 64
 123          tx.calc_sha256()
 124          return tx
 125  
 126  
 127  class BadInputOutpointIndex(BadTxTemplate):
 128      # Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins
 129      # database can't distinguish between spent outpoints and outpoints which never existed.
 130      reject_reason = None
 131  
 132      def get_tx(self):
 133          num_indices = len(self.spend_tx.vin)
 134          bad_idx = num_indices + 100
 135  
 136          tx = CTransaction()
 137          tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", SEQUENCE_FINAL))
 138          tx.vout.append(CTxOut(0, basic_p2sh))
 139          tx.calc_sha256()
 140          return tx
 141  
 142  
 143  class DuplicateInput(BadTxTemplate):
 144      reject_reason = 'bad-txns-inputs-duplicate'
 145  
 146      def get_tx(self):
 147          tx = CTransaction()
 148          tx.vin.append(self.valid_txin)
 149          tx.vin.append(self.valid_txin)
 150          tx.vout.append(CTxOut(1, basic_p2sh))
 151          tx.calc_sha256()
 152          return tx
 153  
 154  
 155  class PrevoutNullInput(BadTxTemplate):
 156      reject_reason = 'bad-txns-prevout-null'
 157  
 158      def get_tx(self):
 159          tx = CTransaction()
 160          tx.vin.append(self.valid_txin)
 161          tx.vin.append(CTxIn(COutPoint(hash=0, n=0xffffffff)))
 162          tx.vout.append(CTxOut(1, basic_p2sh))
 163          tx.calc_sha256()
 164          return tx
 165  
 166  
 167  class NonexistentInput(BadTxTemplate):
 168      reject_reason = None  # Added as an orphan tx.
 169  
 170      def get_tx(self):
 171          tx = CTransaction()
 172          tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", SEQUENCE_FINAL))
 173          tx.vin.append(self.valid_txin)
 174          tx.vout.append(CTxOut(1, basic_p2sh))
 175          tx.calc_sha256()
 176          return tx
 177  
 178  
 179  class SpendTooMuch(BadTxTemplate):
 180      reject_reason = 'bad-txns-in-belowout'
 181  
 182      def get_tx(self):
 183          return create_tx_with_script(
 184              self.spend_tx, 0, output_script=basic_p2sh, amount=(self.spend_avail + 1))
 185  
 186  
 187  class CreateNegative(BadTxTemplate):
 188      reject_reason = 'bad-txns-vout-negative'
 189  
 190      def get_tx(self):
 191          return create_tx_with_script(self.spend_tx, 0, amount=-1)
 192  
 193  
 194  class CreateTooLarge(BadTxTemplate):
 195      reject_reason = 'bad-txns-vout-toolarge'
 196  
 197      def get_tx(self):
 198          return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1)
 199  
 200  
 201  class CreateSumTooLarge(BadTxTemplate):
 202      reject_reason = 'bad-txns-txouttotal-toolarge'
 203  
 204      def get_tx(self):
 205          tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY)
 206          tx.vout = [tx.vout[0]] * 2
 207          tx.calc_sha256()
 208          return tx
 209  
 210  
 211  class InvalidOPIFConstruction(BadTxTemplate):
 212      reject_reason = "mempool-script-verify-flag-failed (Invalid OP_IF construction)"
 213      valid_in_block = True
 214  
 215      def get_tx(self):
 216          return create_tx_with_script(
 217              self.spend_tx, 0, script_sig=b'\x64' * 35,
 218              amount=(self.spend_avail // 2))
 219  
 220  
 221  class TooManySigops(BadTxTemplate):
 222      reject_reason = "bad-txns-too-many-sigops"
 223      block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount"
 224  
 225      def get_tx(self):
 226          # Put OP_CHECKSIGs in scriptSig (input) instead of scriptPubKey (output)
 227          # to avoid violating MAX_OUTPUT_SCRIPT_SIZE=34 consensus limit.
 228          # Sigops are counted from both input and output scripts.
 229          lotsa_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS))
 230          return create_tx_with_script(
 231              self.spend_tx, 0,
 232              script_sig=lotsa_checksigs,
 233              output_script=basic_p2sh,  # 23-byte P2SH, well under 34-byte limit
 234              amount=1)
 235  
 236  def getDisabledOpcodeTemplate(opcode):
 237      """ Creates disabled opcode tx template class"""
 238      def get_tx(self):
 239          tx = CTransaction()
 240          vin = self.valid_txin
 241          vin.scriptSig = CScript([opcode])
 242          tx.vin.append(vin)
 243          tx.vout.append(CTxOut(1, basic_p2sh))
 244          tx.calc_sha256()
 245          return tx
 246  
 247      return type('DisabledOpcode_' + str(opcode), (BadTxTemplate,), {
 248          'reject_reason': "disabled opcode",
 249          'get_tx': get_tx,
 250          'valid_in_block' : True
 251          })
 252  
 253  class NonStandardAndInvalid(BadTxTemplate):
 254      """A non-standard transaction which is also consensus-invalid should return the first error."""
 255      reject_reason = "mempool-script-verify-flag-failed (Using OP_CODESEPARATOR in non-witness script)"
 256      block_reject_reason = "mandatory-script-verify-flag-failed (OP_RETURN was encountered)"
 257      valid_in_block = False
 258  
 259      def get_tx(self):
 260          return create_tx_with_script(
 261              self.spend_tx, 0, script_sig=b'\x00' * 3 + b'\xab\x6a',
 262              amount=(self.spend_avail // 2))
 263  
 264  # Disabled opcode tx templates (CVE-2010-5137)
 265  DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [
 266      OP_CAT,
 267      OP_SUBSTR,
 268      OP_LEFT,
 269      OP_RIGHT,
 270      OP_INVERT,
 271      OP_AND,
 272      OP_OR,
 273      OP_XOR,
 274      OP_2MUL,
 275      OP_2DIV,
 276      OP_MUL,
 277      OP_DIV,
 278      OP_MOD,
 279      OP_LSHIFT,
 280      OP_RSHIFT]]
 281  
 282  
 283  def iter_all_templates():
 284      """Iterate through all bad transaction template types."""
 285      return BadTxTemplate.__subclasses__()
 286