Changeset View
Changeset View
Standalone View
Standalone View
test/functional/data/invalid_txs.py
Show All 30 Lines | from test_framework.script import ( | ||||
OP_INVERT, | OP_INVERT, | ||||
OP_LSHIFT, | OP_LSHIFT, | ||||
OP_MUL, | OP_MUL, | ||||
OP_RSHIFT, | OP_RSHIFT, | ||||
CScript, | CScript, | ||||
) | ) | ||||
from test_framework.txtools import pad_tx | from test_framework.txtools import pad_tx | ||||
basic_p2sh = sc.CScript( | basic_p2sh = sc.CScript([sc.OP_HASH160, sc.hash160(sc.CScript([sc.OP_0])), sc.OP_EQUAL]) | ||||
[sc.OP_HASH160, sc.hash160(sc.CScript([sc.OP_0])), sc.OP_EQUAL]) | |||||
class BadTxTemplate: | class BadTxTemplate: | ||||
"""Allows simple construction of a certain kind of invalid tx. Base class to be subclassed.""" | """Allows simple construction of a certain kind of invalid tx. Base class to be subclassed.""" | ||||
__metaclass__ = abc.ABCMeta | __metaclass__ = abc.ABCMeta | ||||
# The expected error code given by bitcoind upon submission of the tx. | # The expected error code given by bitcoind upon submission of the tx. | ||||
reject_reason: Optional[str] = "" | reject_reason: Optional[str] = "" | ||||
# Only specified if it differs from mempool acceptance error. | # Only specified if it differs from mempool acceptance error. | ||||
block_reject_reason = "" | block_reject_reason = "" | ||||
# Do we expect to be disconnected after submitting this tx? | # Do we expect to be disconnected after submitting this tx? | ||||
expect_disconnect = False | expect_disconnect = False | ||||
# Is this tx considered valid when included in a block, but not for acceptance into | # Is this tx considered valid when included in a block, but not for acceptance into | ||||
# the mempool (i.e. does it violate policy but not consensus)? | # the mempool (i.e. does it violate policy but not consensus)? | ||||
valid_in_block = False | valid_in_block = False | ||||
def __init__(self, *, spend_tx=None, spend_block=None): | def __init__(self, *, spend_tx=None, spend_block=None): | ||||
self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx | self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx | ||||
self.spend_avail = sum(o.nValue for o in self.spend_tx.vout) | self.spend_avail = sum(o.nValue for o in self.spend_tx.vout) | ||||
self.valid_txin = CTxIn( | self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", 0xFFFFFFFF) | ||||
COutPoint( | |||||
self.spend_tx.sha256, | |||||
0), | |||||
b"", | |||||
0xffffffff) | |||||
@abc.abstractmethod | @abc.abstractmethod | ||||
def get_tx(self, *args, **kwargs): | def get_tx(self, *args, **kwargs): | ||||
"""Return a CTransaction that is invalid per the subclass.""" | """Return a CTransaction that is invalid per the subclass.""" | ||||
pass | pass | ||||
class OutputMissing(BadTxTemplate): | class OutputMissing(BadTxTemplate): | ||||
Show All 38 Lines | class BadInputOutpointIndex(BadTxTemplate): | ||||
reject_reason = None | reject_reason = None | ||||
expect_disconnect = False | expect_disconnect = False | ||||
def get_tx(self): | def get_tx(self): | ||||
num_indices = len(self.spend_tx.vin) | num_indices = len(self.spend_tx.vin) | ||||
bad_idx = num_indices + 100 | bad_idx = num_indices + 100 | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append( | tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", 0xFFFFFFFF)) | ||||
CTxIn( | |||||
COutPoint( | |||||
self.spend_tx.sha256, | |||||
bad_idx), | |||||
b"", | |||||
0xffffffff)) | |||||
tx.vout.append(CTxOut(0, basic_p2sh)) | tx.vout.append(CTxOut(0, basic_p2sh)) | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
return tx | return tx | ||||
class DuplicateInput(BadTxTemplate): | class DuplicateInput(BadTxTemplate): | ||||
reject_reason = 'bad-txns-inputs-duplicate' | reject_reason = "bad-txns-inputs-duplicate" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
def get_tx(self): | def get_tx(self): | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append(self.valid_txin) | tx.vin.append(self.valid_txin) | ||||
tx.vin.append(self.valid_txin) | tx.vin.append(self.valid_txin) | ||||
tx.vout.append(CTxOut(1, basic_p2sh)) | tx.vout.append(CTxOut(1, basic_p2sh)) | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
return tx | return tx | ||||
class PrevoutNullInput(BadTxTemplate): | class PrevoutNullInput(BadTxTemplate): | ||||
reject_reason = 'bad-txns-prevout-null' | reject_reason = "bad-txns-prevout-null" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
def get_tx(self): | def get_tx(self): | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append(self.valid_txin) | tx.vin.append(self.valid_txin) | ||||
tx.vin.append(CTxIn(COutPoint(txid=0, n=0xffffffff))) | tx.vin.append(CTxIn(COutPoint(txid=0, n=0xFFFFFFFF))) | ||||
tx.vout.append(CTxOut(1, basic_p2sh)) | tx.vout.append(CTxOut(1, basic_p2sh)) | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
return tx | return tx | ||||
class NonexistentInput(BadTxTemplate): | class NonexistentInput(BadTxTemplate): | ||||
# Added as an orphan tx. | # Added as an orphan tx. | ||||
reject_reason = None | reject_reason = None | ||||
expect_disconnect = False | expect_disconnect = False | ||||
def get_tx(self): | def get_tx(self): | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append( | tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", 0xFFFFFFFF)) | ||||
CTxIn( | |||||
COutPoint( | |||||
self.spend_tx.sha256 + | |||||
1, | |||||
0), | |||||
b"", | |||||
0xffffffff)) | |||||
tx.vin.append(self.valid_txin) | tx.vin.append(self.valid_txin) | ||||
tx.vout.append(CTxOut(1, basic_p2sh)) | tx.vout.append(CTxOut(1, basic_p2sh)) | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
return tx | return tx | ||||
class SpendTooMuch(BadTxTemplate): | class SpendTooMuch(BadTxTemplate): | ||||
reject_reason = 'bad-txns-in-belowout' | reject_reason = "bad-txns-in-belowout" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
def get_tx(self): | def get_tx(self): | ||||
return create_tx_with_script( | return create_tx_with_script( | ||||
self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1)) | self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1) | ||||
) | |||||
class CreateNegative(BadTxTemplate): | class CreateNegative(BadTxTemplate): | ||||
reject_reason = 'bad-txns-vout-negative' | reject_reason = "bad-txns-vout-negative" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
def get_tx(self): | def get_tx(self): | ||||
return create_tx_with_script(self.spend_tx, 0, amount=-1) | return create_tx_with_script(self.spend_tx, 0, amount=-1) | ||||
class CreateTooLarge(BadTxTemplate): | class CreateTooLarge(BadTxTemplate): | ||||
reject_reason = 'bad-txns-vout-toolarge' | reject_reason = "bad-txns-vout-toolarge" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
def get_tx(self): | def get_tx(self): | ||||
return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1) | return create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY + 1) | ||||
class CreateSumTooLarge(BadTxTemplate): | class CreateSumTooLarge(BadTxTemplate): | ||||
reject_reason = 'bad-txns-txouttotal-toolarge' | reject_reason = "bad-txns-txouttotal-toolarge" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
def get_tx(self): | def get_tx(self): | ||||
tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY) | tx = create_tx_with_script(self.spend_tx, 0, amount=MAX_MONEY) | ||||
tx.vout = [tx.vout[0]] * 2 | tx.vout = [tx.vout[0]] * 2 | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
return tx | return tx | ||||
class InvalidOPIFConstruction(BadTxTemplate): | class InvalidOPIFConstruction(BadTxTemplate): | ||||
reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)" | reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)" | ||||
expect_disconnect = True | expect_disconnect = True | ||||
valid_in_block = True | valid_in_block = True | ||||
def get_tx(self): | def get_tx(self): | ||||
return create_tx_with_script( | return create_tx_with_script( | ||||
self.spend_tx, 0, script_sig=b'\x64' * 35, | self.spend_tx, 0, script_sig=b"\x64" * 35, amount=(self.spend_avail // 2) | ||||
amount=(self.spend_avail // 2)) | ) | ||||
def getDisabledOpcodeTemplate(opcode): | def getDisabledOpcodeTemplate(opcode): | ||||
""" Creates disabled opcode tx template class""" | """Creates disabled opcode tx template class""" | ||||
def get_tx(self): | def get_tx(self): | ||||
tx = CTransaction() | tx = CTransaction() | ||||
vin = self.valid_txin | vin = self.valid_txin | ||||
vin.scriptSig = CScript([opcode]) | vin.scriptSig = CScript([opcode]) | ||||
tx.vin.append(vin) | tx.vin.append(vin) | ||||
tx.vout.append(CTxOut(1, basic_p2sh)) | tx.vout.append(CTxOut(1, basic_p2sh)) | ||||
pad_tx(tx) | pad_tx(tx) | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
return tx | return tx | ||||
return type(f"DisabledOpcode_{str(opcode)}", (BadTxTemplate,), { | return type( | ||||
'reject_reason': "disabled opcode", | f"DisabledOpcode_{str(opcode)}", | ||||
'expect_disconnect': True, | (BadTxTemplate,), | ||||
'get_tx': get_tx, | { | ||||
'valid_in_block': True | "reject_reason": "disabled opcode", | ||||
}) | "expect_disconnect": True, | ||||
"get_tx": get_tx, | |||||
"valid_in_block": True, | |||||
}, | |||||
) | |||||
# Disabled opcode tx templates (CVE-2010-5137) | # Disabled opcode tx templates (CVE-2010-5137) | ||||
DisabledOpcodeTemplates = [getDisabledOpcodeTemplate(opcode) for opcode in [ | DisabledOpcodeTemplates = [ | ||||
OP_INVERT, | getDisabledOpcodeTemplate(opcode) | ||||
OP_2MUL, | for opcode in [OP_INVERT, OP_2MUL, OP_2DIV, OP_MUL, OP_LSHIFT, OP_RSHIFT] | ||||
OP_2DIV, | ] | ||||
OP_MUL, | |||||
OP_LSHIFT, | |||||
OP_RSHIFT]] | |||||
def iter_all_templates(): | def iter_all_templates(): | ||||
"""Iterate through all bad transaction template types.""" | """Iterate through all bad transaction template types.""" | ||||
return BadTxTemplate.__subclasses__() | return BadTxTemplate.__subclasses__() |