d7d7d31506
0ff1c2a838
Separate reason for premature spends (coinbase/locktime) (Suhas Daftuar)54470e767b
Assert validation reasons are contextually correct (Suhas Daftuar)2120c31521
[refactor] Update some comments in validation.cpp as we arent doing DoS there (Matt Corallo)12dbdd7a41
[refactor] Drop unused state.DoS(), state.GetDoS(), state.CorruptionPossible() (Matt Corallo)aa502b88d1
scripted-diff: Remove DoS calls to CValidationState (Matt Corallo)7721ad64f4
[refactor] Prep for scripted-diff by removing some \ns which annoy sed. (Matt Corallo)5e78c5734b
Allow use of state.Invalid() for all reasons (Matt Corallo)6b34bc6b6f
Fix handling of invalid headers (Suhas Daftuar)ef54b486d5
[refactor] Use Reasons directly instead of DoS codes (Matt Corallo)9ab2a0412e
CorruptionPossible -> BLOCK_MUTATED (Matt Corallo)6e55b292b0
CorruptionPossible -> TX_WITNESS_MUTATED (Matt Corallo)7df16e70e6
LookupBlockIndex -> CACHED_INVALID (Matt Corallo)c8b0d22698
[refactor] Drop redundant nDoS, corruptionPossible, SetCorruptionPossible (Matt Corallo)34477ccd39
[refactor] Add useful-for-dos "reason" field to CValidationState (Matt Corallo)6a7f8777a0
Ban all peers for all block script failures (Suhas Daftuar)7b999103e2
Clean up banning levels (Matt Corallo)b8b4c80146
[refactor] drop IsInvalid(nDoSOut) (Matt Corallo)8818729013
[refactor] Refactor misbehavior ban decisions to MaybePunishNode() (Matt Corallo)00e11e61c0
[refactor] rename stateDummy -> orphan_state (Matt Corallo)f34fa719cf
Drop obsolete sigops comment (Matt Corallo) Pull request description: This is a rebase of #11639 with some fixes for the last few comments which were not yet addressed. The original PR text, with some strikethroughs of text that is no longer correct: > This cleans up an old main-carryover - it made sense that main could decide what DoS scores to assign things because the DoS scores were handled in a different part of main, but now validation is telling net_processing what DoS scores to assign to different things, which is utter nonsense. Instead, we replace CValidationState's nDoS and CorruptionPossible with a general ValidationInvalidReason, which net_processing can handle as it sees fit. I keep the behavior changes here to a minimum, but in the future we can utilize these changes for other smarter behavior, such as disconnecting/preferring to rotate outbound peers based on them providing things which are invalid due to SOFT_FORK because we shouldn't ban for such cases. > > This is somewhat complementary with, though obviously conflicts heavily with #11523, which added enums in place of DoS scores, as well as a few other cleanups (which are still relevant). > > Compared with previous bans, the following changes are made: > > Txn with empty vin/vout or null prevouts move from 10 DoS > points to 100. > Loose transactions with a dependency loop now result in a ban > instead of 10 DoS points. > ~~BIP68-violation no longer results in a ban as it is SOFT_FORK.~~ > ~~Non-SegWit SigOp violation no longer results in a ban as it > considers P2SH sigops and is thus SOFT_FORK.~~ > ~~Any script violation in a block no longer results in a ban as > it may be the result of a SOFT_FORK. This should likely be > fixed in the future by differentiating between them.~~ > Proof of work failure moves from 50 DoS points to a ban. > Blocks with timestamps under MTP now result in a ban, blocks > too far in the future continue to not result in a ban. > Inclusion of non-final transactions in a block now results in a > ban instead of 10 DoS points. Note: The change to ban all peers for consensus violations is actually NOT the change I'd like to make -- I'd prefer to only ban outbound peers in those situations. The current behavior is a bit of a mess, however, and so in the interests of advancing this PR I tried to keep the changes to a minimum. I plan to revisit the behavior in a followup PR. EDIT: One reviewer suggested I add some additional context for this PR: > The goal of this work was to make net_processing aware of the actual reasons for validation failures, rather than just deal with opaque numbers instructing it to do something. > > In the future, I'd like to make it so that we use more context to decide how to punish a peer. One example is to differentiate inbound and outbound peer misbehaviors. Another potential example is if we'd treat RECENT_CONSENSUS_CHANGE failures differently (ie after the next consensus change is implemented), and perhaps again we'd want to treat some peers differently than others. ACKs for commit 0ff1c2: jnewbery: utACK0ff1c2a838
ryanofsky: utACK0ff1c2a838
. Only change is dropping the first commit (f3883a321bf4ab289edcd9754b12cae3a648b175), and dropping the temporary `assert(level == GetDoS())` that was in 35ee77f2832eaffce30042e00785c310c5540cdc (nowc8b0d22698
) Tree-SHA512: e915a411100876398af5463d0a885920e44d473467bb6af991ef2e8f2681db6c1209bb60f848bd154be72d460f039b5653df20a6840352c5f7ea5486d9f777a3
184 lines
5.7 KiB
Python
184 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) 2015-2018 The Bitcoin Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
"""
|
|
Templates for constructing various sorts of invalid transactions.
|
|
|
|
These templates (or an iterator over all of them) can be reused in different
|
|
contexts to test using a number of invalid transaction types.
|
|
|
|
Hopefully this makes it easier to get coverage of a full variety of tx
|
|
validation checks through different interfaces (AcceptBlock, AcceptToMemPool,
|
|
etc.) without repeating ourselves.
|
|
|
|
Invalid tx cases not covered here can be found by running:
|
|
|
|
$ diff \
|
|
<(grep -IREho "bad-txns[a-zA-Z-]+" src | sort -u) \
|
|
<(grep -IEho "bad-txns[a-zA-Z-]+" test/functional/data/invalid_txs.py | sort -u)
|
|
|
|
"""
|
|
import abc
|
|
|
|
from test_framework.messages import CTransaction, CTxIn, CTxOut, COutPoint
|
|
from test_framework import script as sc
|
|
from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS
|
|
|
|
basic_p2sh = sc.CScript([sc.OP_HASH160, sc.hash160(sc.CScript([sc.OP_0])), sc.OP_EQUAL])
|
|
|
|
|
|
class BadTxTemplate:
|
|
"""Allows simple construction of a certain kind of invalid tx. Base class to be subclassed."""
|
|
__metaclass__ = abc.ABCMeta
|
|
|
|
# The expected error code given by bitcoind upon submission of the tx.
|
|
reject_reason = ""
|
|
|
|
# Only specified if it differs from mempool acceptance error.
|
|
block_reject_reason = ""
|
|
|
|
# Do we expect to be disconnected after submitting this tx?
|
|
expect_disconnect = False
|
|
|
|
# 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)?
|
|
valid_in_block = False
|
|
|
|
def __init__(self, *, spend_tx=None, spend_block=None):
|
|
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.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", 0xffffffff)
|
|
|
|
@abc.abstractmethod
|
|
def get_tx(self, *args, **kwargs):
|
|
"""Return a CTransaction that is invalid per the subclass."""
|
|
pass
|
|
|
|
|
|
class OutputMissing(BadTxTemplate):
|
|
reject_reason = "bad-txns-vout-empty"
|
|
expect_disconnect = True
|
|
|
|
def get_tx(self):
|
|
tx = CTransaction()
|
|
tx.vin.append(self.valid_txin)
|
|
tx.calc_sha256()
|
|
return tx
|
|
|
|
|
|
class InputMissing(BadTxTemplate):
|
|
reject_reason = "bad-txns-vin-empty"
|
|
expect_disconnect = True
|
|
|
|
# We use a blank transaction here to make sure
|
|
# it is interpreted as a non-witness transaction.
|
|
# Otherwise the transaction will fail the
|
|
# "surpufluous witness" check during deserialization
|
|
# rather than the input count check.
|
|
def get_tx(self):
|
|
tx = CTransaction()
|
|
tx.calc_sha256()
|
|
return tx
|
|
|
|
|
|
class SizeTooSmall(BadTxTemplate):
|
|
reject_reason = "tx-size-small"
|
|
expect_disconnect = False
|
|
valid_in_block = True
|
|
|
|
def get_tx(self):
|
|
tx = CTransaction()
|
|
tx.vin.append(self.valid_txin)
|
|
tx.vout.append(CTxOut(0, sc.CScript([sc.OP_TRUE])))
|
|
tx.calc_sha256()
|
|
return tx
|
|
|
|
|
|
class BadInputOutpointIndex(BadTxTemplate):
|
|
# Won't be rejected - nonexistent outpoint index is treated as an orphan since the coins
|
|
# database can't distinguish between spent outpoints and outpoints which never existed.
|
|
reject_reason = None
|
|
expect_disconnect = False
|
|
|
|
def get_tx(self):
|
|
num_indices = len(self.spend_tx.vin)
|
|
bad_idx = num_indices + 100
|
|
|
|
tx = CTransaction()
|
|
tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", 0xffffffff))
|
|
tx.vout.append(CTxOut(0, basic_p2sh))
|
|
tx.calc_sha256()
|
|
return tx
|
|
|
|
|
|
class DuplicateInput(BadTxTemplate):
|
|
reject_reason = 'bad-txns-inputs-duplicate'
|
|
expect_disconnect = True
|
|
|
|
def get_tx(self):
|
|
tx = CTransaction()
|
|
tx.vin.append(self.valid_txin)
|
|
tx.vin.append(self.valid_txin)
|
|
tx.vout.append(CTxOut(1, basic_p2sh))
|
|
tx.calc_sha256()
|
|
return tx
|
|
|
|
|
|
class NonexistentInput(BadTxTemplate):
|
|
reject_reason = None # Added as an orphan tx.
|
|
expect_disconnect = False
|
|
|
|
def get_tx(self):
|
|
tx = CTransaction()
|
|
tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", 0xffffffff))
|
|
tx.vin.append(self.valid_txin)
|
|
tx.vout.append(CTxOut(1, basic_p2sh))
|
|
tx.calc_sha256()
|
|
return tx
|
|
|
|
|
|
class SpendTooMuch(BadTxTemplate):
|
|
reject_reason = 'bad-txns-in-belowout'
|
|
expect_disconnect = True
|
|
|
|
def get_tx(self):
|
|
return create_tx_with_script(
|
|
self.spend_tx, 0, script_pub_key=basic_p2sh, amount=(self.spend_avail + 1))
|
|
|
|
|
|
class SpendNegative(BadTxTemplate):
|
|
reject_reason = 'bad-txns-vout-negative'
|
|
expect_disconnect = True
|
|
|
|
def get_tx(self):
|
|
return create_tx_with_script(self.spend_tx, 0, amount=-1)
|
|
|
|
|
|
class InvalidOPIFConstruction(BadTxTemplate):
|
|
reject_reason = "mandatory-script-verify-flag-failed (Invalid OP_IF construction)"
|
|
expect_disconnect = True
|
|
valid_in_block = True
|
|
|
|
def get_tx(self):
|
|
return create_tx_with_script(
|
|
self.spend_tx, 0, script_sig=b'\x64' * 35,
|
|
amount=(self.spend_avail // 2))
|
|
|
|
|
|
class TooManySigops(BadTxTemplate):
|
|
reject_reason = "bad-txns-too-many-sigops"
|
|
block_reject_reason = "bad-blk-sigops, out-of-bounds SigOpCount"
|
|
expect_disconnect = False
|
|
|
|
def get_tx(self):
|
|
lotsa_checksigs = sc.CScript([sc.OP_CHECKSIG] * (MAX_BLOCK_SIGOPS))
|
|
return create_tx_with_script(
|
|
self.spend_tx, 0,
|
|
script_pub_key=lotsa_checksigs,
|
|
amount=1)
|
|
|
|
|
|
def iter_all_templates():
|
|
"""Iterate through all bad transaction template types."""
|
|
return BadTxTemplate.__subclasses__()
|