diff options
Diffstat (limited to 'src/consensus')
-rw-r--r-- | src/consensus/tx_check.cpp | 20 | ||||
-rw-r--r-- | src/consensus/tx_check.h | 4 | ||||
-rw-r--r-- | src/consensus/tx_verify.cpp | 12 | ||||
-rw-r--r-- | src/consensus/tx_verify.h | 4 | ||||
-rw-r--r-- | src/consensus/validation.h | 167 |
5 files changed, 111 insertions, 96 deletions
diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp index 6793f871cf..88bb12c713 100644 --- a/src/consensus/tx_check.cpp +++ b/src/consensus/tx_check.cpp @@ -7,28 +7,28 @@ #include <primitives/transaction.h> #include <consensus/validation.h> -bool CheckTransaction(const CTransaction& tx, CValidationState& state) +bool CheckTransaction(const CTransaction& tx, TxValidationState& state) { // Basic checks that don't depend on any context if (tx.vin.empty()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vin-empty"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vin-empty"); if (tx.vout.empty()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-empty"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-empty"); // Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-oversize"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-oversize"); // Check for negative or overflow output values (see CVE-2010-5139) CAmount nValueOut = 0; for (const auto& txout : tx.vout) { if (txout.nValue < 0) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-negative"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-negative"); if (txout.nValue > MAX_MONEY) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-toolarge"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-toolarge"); nValueOut += txout.nValue; if (!MoneyRange(nValueOut)) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-txouttotal-toolarge"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-txouttotal-toolarge"); } // Check for duplicate inputs (see CVE-2018-17144) @@ -39,19 +39,19 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state) std::set<COutPoint> vInOutPoints; for (const auto& txin : tx.vin) { if (!vInOutPoints.insert(txin.prevout).second) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-inputs-duplicate"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate"); } if (tx.IsCoinBase()) { if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-cb-length"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cb-length"); } else { for (const auto& txin : tx.vin) if (txin.prevout.IsNull()) - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-prevout-null"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-prevout-null"); } return true; diff --git a/src/consensus/tx_check.h b/src/consensus/tx_check.h index 6f3f8fe969..b818a284f1 100644 --- a/src/consensus/tx_check.h +++ b/src/consensus/tx_check.h @@ -13,8 +13,8 @@ */ class CTransaction; -class CValidationState; +class TxValidationState; -bool CheckTransaction(const CTransaction& tx, CValidationState& state); +bool CheckTransaction(const CTransaction& tx, TxValidationState& state); #endif // BITCOIN_CONSENSUS_TX_CHECK_H diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index ceeddc3f6d..31bdabea28 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -156,11 +156,11 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i return nSigOps; } -bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) +bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee) { // are the actual inputs available? if (!inputs.HaveInputs(tx)) { - return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, "bad-txns-inputs-missingorspent", + return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent", strprintf("%s: inputs missing/spent", __func__)); } @@ -172,27 +172,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c // If prev is coinbase, check that it's matured if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { - return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, "bad-txns-premature-spend-of-coinbase", + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-premature-spend-of-coinbase", strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); } // Check for negative or overflow input values nValueIn += coin.out.nValue; if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-inputvalues-outofrange"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputvalues-outofrange"); } } const CAmount value_out = tx.GetValueOut(); if (nValueIn < value_out) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-in-belowout", + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-in-belowout", strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out))); } // Tally transaction fees const CAmount txfee_aux = nValueIn - value_out; if (!MoneyRange(txfee_aux)) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-fee-outofrange"); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-fee-outofrange"); } txfee = txfee_aux; diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 3519fc555d..b6599f2878 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -13,7 +13,7 @@ class CBlockIndex; class CCoinsViewCache; class CTransaction; -class CValidationState; +class TxValidationState; /** Transaction validation functions */ @@ -24,7 +24,7 @@ namespace Consensus { * @param[out] txfee Set to the transaction fee if successful. * Preconditions: tx.IsCoinBase() is false. */ -bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); +bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee); } // namespace Consensus /** Auxiliary functions for transaction validation (ideally should not be exposed) */ diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 4920cdf881..3401eb64ca 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -12,13 +12,12 @@ #include <primitives/transaction.h> #include <primitives/block.h> -/** A "reason" why something was invalid, suitable for determining whether the - * provider of the object should be banned/ignored/disconnected/etc. +/** A "reason" why a transaction was invalid, suitable for determining whether the + * provider of the transaction should be banned/ignored/disconnected/etc. */ -enum class ValidationInvalidReason { - // txn and blocks: - NONE, //!< not actually invalid - CONSENSUS, //!< invalid by consensus rules (excluding any below reasons) +enum class TxValidationResult { + TX_RESULT_UNSET, //!< initial value. Tx has not yet been rejected + TX_CONSENSUS, //!< invalid by consensus rules /** * Invalid by a change to consensus rules more recent than SegWit. * Currently unused as there are no such consensus rule changes, and any download @@ -26,18 +25,9 @@ enum class ValidationInvalidReason { * so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork * is uninteresting. */ - RECENT_CONSENSUS_CHANGE, - // Only blocks (or headers): - CACHED_INVALID, //!< this object was cached as being invalid, but we don't know why - BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old - BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW - BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on - BLOCK_INVALID_PREV, //!< A block this one builds on is invalid - BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) - BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints - // Only loose txn: + TX_RECENT_CONSENSUS_CHANGE, TX_NOT_STANDARD, //!< didn't meet our local policy rules - TX_MISSING_INPUTS, //!< a transaction was missing some of its inputs + TX_MISSING_INPUTS, //!< transaction was missing some of its inputs TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks /** * Transaction might be missing a witness, have a witness prior to SegWit @@ -48,82 +38,107 @@ enum class ValidationInvalidReason { /** * Tx already in mempool or conflicts with a tx in the chain * (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold) - * TODO: Currently this is only used if the transaction already exists in the mempool or on chain, - * TODO: ATMP's fMissingInputs and a valid CValidationState being used to indicate missing inputs + * Currently this is only used if the transaction already exists in the mempool or on chain. */ TX_CONFLICT, TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits }; -inline bool IsTransactionReason(ValidationInvalidReason r) -{ - return r == ValidationInvalidReason::NONE || - r == ValidationInvalidReason::CONSENSUS || - r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || - r == ValidationInvalidReason::TX_NOT_STANDARD || - r == ValidationInvalidReason::TX_PREMATURE_SPEND || - r == ValidationInvalidReason::TX_MISSING_INPUTS || - r == ValidationInvalidReason::TX_WITNESS_MUTATED || - r == ValidationInvalidReason::TX_CONFLICT || - r == ValidationInvalidReason::TX_MEMPOOL_POLICY; -} +/** A "reason" why a block was invalid, suitable for determining whether the + * provider of the block should be banned/ignored/disconnected/etc. + * These are much more granular than the rejection codes, which may be more + * useful for some other use-cases. + */ +enum class BlockValidationResult { + BLOCK_RESULT_UNSET, //!< initial value. Block has not yet been rejected + BLOCK_CONSENSUS, //!< invalid by consensus rules (excluding any below reasons) + /** + * Invalid by a change to consensus rules more recent than SegWit. + * Currently unused as there are no such consensus rule changes, and any download + * sources realistically need to support SegWit in order to provide useful data, + * so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork + * is uninteresting. + */ + BLOCK_RECENT_CONSENSUS_CHANGE, + BLOCK_CACHED_INVALID, //!< this block was cached as being invalid and we didn't store the reason why + BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old + BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW + BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on + BLOCK_INVALID_PREV, //!< A block this one builds on is invalid + BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad) + BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints +}; -inline bool IsBlockReason(ValidationInvalidReason r) -{ - return r == ValidationInvalidReason::NONE || - r == ValidationInvalidReason::CONSENSUS || - r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE || - r == ValidationInvalidReason::CACHED_INVALID || - r == ValidationInvalidReason::BLOCK_INVALID_HEADER || - r == ValidationInvalidReason::BLOCK_MUTATED || - r == ValidationInvalidReason::BLOCK_MISSING_PREV || - r == ValidationInvalidReason::BLOCK_INVALID_PREV || - r == ValidationInvalidReason::BLOCK_TIME_FUTURE || - r == ValidationInvalidReason::BLOCK_CHECKPOINT; -} -/** Capture information about block/transaction validation */ -class CValidationState { + +/** Base class for capturing information about block/transaction validation. This is subclassed + * by TxValidationState and BlockValidationState for validation information on transactions + * and blocks respectively. */ +class ValidationState { private: enum mode_state { MODE_VALID, //!< everything ok MODE_INVALID, //!< network rule violation (DoS value may be set) MODE_ERROR, //!< run-time error - } mode; - ValidationInvalidReason m_reason; - std::string strRejectReason; - std::string strDebugMessage; -public: - CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE) {} - bool Invalid(ValidationInvalidReason reasonIn, bool ret = false, - const std::string &strRejectReasonIn="", - const std::string &strDebugMessageIn="") { - m_reason = reasonIn; - strRejectReason = strRejectReasonIn; - strDebugMessage = strDebugMessageIn; - if (mode == MODE_ERROR) - return ret; - mode = MODE_INVALID; - return ret; + } m_mode; + std::string m_reject_reason; + std::string m_debug_message; +protected: + void Invalid(const std::string &reject_reason="", + const std::string &debug_message="") + { + m_reject_reason = reject_reason; + m_debug_message = debug_message; + if (m_mode != MODE_ERROR) m_mode = MODE_INVALID; } - bool Error(const std::string& strRejectReasonIn) { - if (mode == MODE_VALID) - strRejectReason = strRejectReasonIn; - mode = MODE_ERROR; +public: + // ValidationState is abstract. Have a pure virtual destructor. + virtual ~ValidationState() = 0; + + ValidationState() : m_mode(MODE_VALID) {} + bool Error(const std::string& reject_reason) + { + if (m_mode == MODE_VALID) + m_reject_reason = reject_reason; + m_mode = MODE_ERROR; return false; } - bool IsValid() const { - return mode == MODE_VALID; - } - bool IsInvalid() const { - return mode == MODE_INVALID; + bool IsValid() const { return m_mode == MODE_VALID; } + bool IsInvalid() const { return m_mode == MODE_INVALID; } + bool IsError() const { return m_mode == MODE_ERROR; } + std::string GetRejectReason() const { return m_reject_reason; } + std::string GetDebugMessage() const { return m_debug_message; } +}; + +inline ValidationState::~ValidationState() {}; + +class TxValidationState : public ValidationState { +private: + TxValidationResult m_result = TxValidationResult::TX_RESULT_UNSET; +public: + bool Invalid(TxValidationResult result, + const std::string &reject_reason="", + const std::string &debug_message="") + { + m_result = result; + ValidationState::Invalid(reject_reason, debug_message); + return false; } - bool IsError() const { - return mode == MODE_ERROR; + TxValidationResult GetResult() const { return m_result; } +}; + +class BlockValidationState : public ValidationState { +private: + BlockValidationResult m_result = BlockValidationResult::BLOCK_RESULT_UNSET; +public: + bool Invalid(BlockValidationResult result, + const std::string &reject_reason="", + const std::string &debug_message="") { + m_result = result; + ValidationState::Invalid(reject_reason, debug_message); + return false; } - ValidationInvalidReason GetReason() const { return m_reason; } - std::string GetRejectReason() const { return strRejectReason; } - std::string GetDebugMessage() const { return strDebugMessage; } + BlockValidationResult GetResult() const { return m_result; } }; // These implement the weight = (stripped_size * 4) + witness_size formula, |