Skip to content

Commit a27a295

Browse files
committed
[validation] Add CValidationState subclasses
Split CValidationState into TxValidationState and BlockValidationState to store validation results for transactions and blocks respectively.
1 parent 48cb468 commit a27a295

33 files changed

+363
-323
lines changed

src/bench/block_assemble.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ static void AssembleBlock(benchmark::State& state)
3838
LOCK(::cs_main); // Required for ::AcceptToMemoryPool.
3939

4040
for (const auto& txr : txs) {
41-
CValidationState state;
41+
TxValidationState state;
4242
bool ret{::AcceptToMemoryPool(::mempool, state, txr, nullptr /* pfMissingInputs */, nullptr /* plTxnReplaced */, false /* bypass_limits */, /* nAbsurdFee */ 0)};
4343
assert(ret);
4444
}

src/bench/checkblock.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ static void DeserializeAndCheckBlockTest(benchmark::State& state)
4242
bool rewound = stream.Rewind(benchmark::data::block413567.size());
4343
assert(rewound);
4444

45-
CValidationState validationState;
45+
BlockValidationState validationState;
4646
bool checked = CheckBlock(block, validationState, chainParams->GetConsensus());
4747
assert(checked);
4848
}

src/bench/duplicate_inputs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ static void DuplicateInputs(benchmark::State& state)
5454
block.hashMerkleRoot = BlockMerkleRoot(block);
5555

5656
while (state.KeepRunning()) {
57-
CValidationState cvstate{};
57+
BlockValidationState cvstate{};
5858
assert(!CheckBlock(block, cvstate, chainparams.GetConsensus(), false, false));
5959
assert(cvstate.GetRejectReason() == "bad-txns-inputs-duplicate");
6060
}

src/blockencodings.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,13 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
197197
if (vtx_missing.size() != tx_missing_offset)
198198
return READ_STATUS_INVALID;
199199

200-
CValidationState state;
200+
BlockValidationState state;
201201
if (!CheckBlock(block, state, Params().GetConsensus())) {
202202
// TODO: We really want to just check merkle tree manually here,
203203
// but that is expensive, and CheckBlock caches a block's
204204
// "checked-status" (in the CBlock?). CBlock should be able to
205205
// check its own merkle root and cache that check.
206-
if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED)
206+
if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED)
207207
return READ_STATUS_FAILED; // Possible Short ID collision
208208
return READ_STATUS_CHECKBLOCK_FAILED;
209209
}

src/consensus/tx_check.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,28 @@
77
#include <primitives/transaction.h>
88
#include <consensus/validation.h>
99

10-
bool CheckTransaction(const CTransaction& tx, CValidationState& state)
10+
bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
1111
{
1212
// Basic checks that don't depend on any context
1313
if (tx.vin.empty())
14-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vin-empty");
14+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-vin-empty");
1515
if (tx.vout.empty())
16-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-empty");
16+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-vout-empty");
1717
// Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
1818
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
19-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-oversize");
19+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-oversize");
2020

2121
// Check for negative or overflow output values (see CVE-2010-5139)
2222
CAmount nValueOut = 0;
2323
for (const auto& txout : tx.vout)
2424
{
2525
if (txout.nValue < 0)
26-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-negative");
26+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-vout-negative");
2727
if (txout.nValue > MAX_MONEY)
28-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-vout-toolarge");
28+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-vout-toolarge");
2929
nValueOut += txout.nValue;
3030
if (!MoneyRange(nValueOut))
31-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-txouttotal-toolarge");
31+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-txouttotal-toolarge");
3232
}
3333

3434
// Check for duplicate inputs (see CVE-2018-17144)
@@ -39,19 +39,19 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state)
3939
std::set<COutPoint> vInOutPoints;
4040
for (const auto& txin : tx.vin) {
4141
if (!vInOutPoints.insert(txin.prevout).second)
42-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-inputs-duplicate");
42+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-inputs-duplicate");
4343
}
4444

4545
if (tx.IsCoinBase())
4646
{
4747
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
48-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-cb-length");
48+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-cb-length");
4949
}
5050
else
5151
{
5252
for (const auto& txin : tx.vin)
5353
if (txin.prevout.IsNull())
54-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-prevout-null");
54+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-prevout-null");
5555
}
5656

5757
return true;

src/consensus/tx_check.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
*/
1414

1515
class CTransaction;
16-
class CValidationState;
16+
class TxValidationState;
1717

18-
bool CheckTransaction(const CTransaction& tx, CValidationState& state);
18+
bool CheckTransaction(const CTransaction& tx, TxValidationState& state);
1919

2020
#endif // BITCOIN_CONSENSUS_TX_CHECK_H

src/consensus/tx_verify.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,11 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
156156
return nSigOps;
157157
}
158158

159-
bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
159+
bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
160160
{
161161
// are the actual inputs available?
162162
if (!inputs.HaveInputs(tx)) {
163-
return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, "bad-txns-inputs-missingorspent",
163+
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, false, "bad-txns-inputs-missingorspent",
164164
strprintf("%s: inputs missing/spent", __func__));
165165
}
166166

@@ -172,27 +172,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
172172

173173
// If prev is coinbase, check that it's matured
174174
if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) {
175-
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, "bad-txns-premature-spend-of-coinbase",
175+
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, false, "bad-txns-premature-spend-of-coinbase",
176176
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
177177
}
178178

179179
// Check for negative or overflow input values
180180
nValueIn += coin.out.nValue;
181181
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
182-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-inputvalues-outofrange");
182+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-inputvalues-outofrange");
183183
}
184184
}
185185

186186
const CAmount value_out = tx.GetValueOut();
187187
if (nValueIn < value_out) {
188-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-in-belowout",
188+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-in-belowout",
189189
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
190190
}
191191

192192
// Tally transaction fees
193193
const CAmount txfee_aux = nValueIn - value_out;
194194
if (!MoneyRange(txfee_aux)) {
195-
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, "bad-txns-fee-outofrange");
195+
return state.Invalid(TxValidationResult::TX_CONSENSUS, false, "bad-txns-fee-outofrange");
196196
}
197197

198198
txfee = txfee_aux;

src/consensus/tx_verify.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class CBlockIndex;
1414
class CCoinsViewCache;
1515
class CTransaction;
16-
class CValidationState;
16+
class TxValidationState;
1717

1818
/** Transaction validation functions */
1919

@@ -24,7 +24,7 @@ namespace Consensus {
2424
* @param[out] txfee Set to the transaction fee if successful.
2525
* Preconditions: tx.IsCoinBase() is false.
2626
*/
27-
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee);
27+
bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee);
2828
} // namespace Consensus
2929

3030
/** Auxiliary functions for transaction validation (ideally should not be exposed) */

src/consensus/validation.h

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,27 @@
1212
#include <primitives/transaction.h>
1313
#include <primitives/block.h>
1414

15-
/** A "reason" why something was invalid, suitable for determining whether the
16-
* provider of the object should be banned/ignored/disconnected/etc.
15+
/** A "reason" why a transaction was invalid, suitable for determining whether the
16+
* provider of the transaction should be banned/ignored/disconnected/etc.
1717
*/
18-
enum class ValidationInvalidReason {
19-
// txn and blocks:
20-
NONE, //!< not actually invalid
21-
CONSENSUS, //!< invalid by consensus rules (excluding any below reasons)
18+
enum class TxValidationResult {
19+
TX_RESULT_UNSET, //!< initial value. Tx has not yet been rejected
20+
TX_CONSENSUS, //!< invalid by consensus rules
2221
/**
2322
* Invalid by a change to consensus rules more recent than SegWit.
2423
* Currently unused as there are no such consensus rule changes, and any download
2524
* sources realistically need to support SegWit in order to provide useful data,
2625
* so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork
2726
* is uninteresting.
2827
*/
29-
RECENT_CONSENSUS_CHANGE,
30-
// Only blocks (or headers):
31-
CACHED_INVALID, //!< this object was cached as being invalid, but we don't know why
32-
BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old
33-
BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW
34-
BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on
35-
BLOCK_INVALID_PREV, //!< A block this one builds on is invalid
36-
BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad)
37-
BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints
38-
// Only loose txn:
28+
TX_RECENT_CONSENSUS_CHANGE,
3929
TX_NOT_STANDARD, //!< didn't meet our local policy rules
40-
TX_MISSING_INPUTS, //!< a transaction was missing some of its inputs
30+
/**
31+
* transaction was missing some of its inputs
32+
* TODO: ATMP uses fMissingInputs and a valid ValidationState to indicate missing inputs.
33+
* Change ATMP to use TX_MISSING_INPUTS.
34+
*/
35+
TX_MISSING_INPUTS,
4136
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
4237
/**
4338
* Transaction might be missing a witness, have a witness prior to SegWit
@@ -48,64 +43,67 @@ enum class ValidationInvalidReason {
4843
/**
4944
* Tx already in mempool or conflicts with a tx in the chain
5045
* (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold)
51-
* TODO: Currently this is only used if the transaction already exists in the mempool or on chain,
52-
* TODO: ATMP's fMissingInputs and a valid CValidationState being used to indicate missing inputs
46+
* Currently this is only used if the transaction already exists in the mempool or on chain.
5347
*/
5448
TX_CONFLICT,
5549
TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits
5650
};
5751

58-
inline bool IsTransactionReason(ValidationInvalidReason r)
59-
{
60-
return r == ValidationInvalidReason::NONE ||
61-
r == ValidationInvalidReason::CONSENSUS ||
62-
r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE ||
63-
r == ValidationInvalidReason::TX_NOT_STANDARD ||
64-
r == ValidationInvalidReason::TX_PREMATURE_SPEND ||
65-
r == ValidationInvalidReason::TX_MISSING_INPUTS ||
66-
r == ValidationInvalidReason::TX_WITNESS_MUTATED ||
67-
r == ValidationInvalidReason::TX_CONFLICT ||
68-
r == ValidationInvalidReason::TX_MEMPOOL_POLICY;
69-
}
52+
/** A "reason" why a block was invalid, suitable for determining whether the
53+
* provider of the block should be banned/ignored/disconnected/etc.
54+
* These are much more granular than the rejection codes, which may be more
55+
* useful for some other use-cases.
56+
*/
57+
enum class BlockValidationResult {
58+
BLOCK_RESULT_UNSET, //!< initial value. Block has not yet been rejected
59+
BLOCK_CONSENSUS, //!< invalid by consensus rules (excluding any below reasons)
60+
/**
61+
* Invalid by a change to consensus rules more recent than SegWit.
62+
* Currently unused as there are no such consensus rule changes, and any download
63+
* sources realistically need to support SegWit in order to provide useful data,
64+
* so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork
65+
* is uninteresting.
66+
*/
67+
BLOCK_RECENT_CONSENSUS_CHANGE,
68+
BLOCK_CACHED_INVALID, //!< this block was cached as being invalid and we didn't store the reason why
69+
BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old
70+
BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW
71+
BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on
72+
BLOCK_INVALID_PREV, //!< A block this one builds on is invalid
73+
BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad)
74+
BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints
75+
};
7076

71-
inline bool IsBlockReason(ValidationInvalidReason r)
72-
{
73-
return r == ValidationInvalidReason::NONE ||
74-
r == ValidationInvalidReason::CONSENSUS ||
75-
r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE ||
76-
r == ValidationInvalidReason::CACHED_INVALID ||
77-
r == ValidationInvalidReason::BLOCK_INVALID_HEADER ||
78-
r == ValidationInvalidReason::BLOCK_MUTATED ||
79-
r == ValidationInvalidReason::BLOCK_MISSING_PREV ||
80-
r == ValidationInvalidReason::BLOCK_INVALID_PREV ||
81-
r == ValidationInvalidReason::BLOCK_TIME_FUTURE ||
82-
r == ValidationInvalidReason::BLOCK_CHECKPOINT;
83-
}
8477

85-
/** Capture information about block/transaction validation */
86-
class CValidationState {
78+
79+
/** Base class for capturing information about block/transaction validation. This is subclassed
80+
* by TxValidationState and BlockValidationState for validation information on transactions
81+
* and blocks respectively. */
82+
class ValidationState {
8783
private:
8884
enum mode_state {
8985
MODE_VALID, //!< everything ok
9086
MODE_INVALID, //!< network rule violation (DoS value may be set)
9187
MODE_ERROR, //!< run-time error
9288
} mode;
93-
ValidationInvalidReason m_reason;
9489
std::string strRejectReason;
9590
std::string strDebugMessage;
96-
public:
97-
CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE) {}
98-
bool Invalid(ValidationInvalidReason reasonIn, bool ret = false,
91+
protected:
92+
bool Invalid(bool ret = false,
9993
const std::string &strRejectReasonIn="",
10094
const std::string &strDebugMessageIn="") {
101-
m_reason = reasonIn;
10295
strRejectReason = strRejectReasonIn;
10396
strDebugMessage = strDebugMessageIn;
10497
if (mode == MODE_ERROR)
10598
return ret;
10699
mode = MODE_INVALID;
107100
return ret;
108101
}
102+
public:
103+
// ValidationState is abstract. Have a pure virtual destructor.
104+
virtual ~ValidationState() = 0;
105+
106+
ValidationState() : mode(MODE_VALID) {}
109107
bool Error(const std::string& strRejectReasonIn) {
110108
if (mode == MODE_VALID)
111109
strRejectReason = strRejectReasonIn;
@@ -121,11 +119,38 @@ class CValidationState {
121119
bool IsError() const {
122120
return mode == MODE_ERROR;
123121
}
124-
ValidationInvalidReason GetReason() const { return m_reason; }
125122
std::string GetRejectReason() const { return strRejectReason; }
126123
std::string GetDebugMessage() const { return strDebugMessage; }
127124
};
128125

126+
inline ValidationState::~ValidationState() {};
127+
128+
class TxValidationState : public ValidationState {
129+
private:
130+
TxValidationResult m_result;
131+
public:
132+
bool Invalid(TxValidationResult result, bool ret = false,
133+
const std::string &_strRejectReason="",
134+
const std::string &_strDebugMessage="") {
135+
m_result = result;
136+
return ValidationState::Invalid(ret, _strRejectReason, _strDebugMessage);
137+
}
138+
TxValidationResult GetResult() const { return m_result; }
139+
};
140+
141+
class BlockValidationState : public ValidationState {
142+
private:
143+
BlockValidationResult m_result;
144+
public:
145+
bool Invalid(BlockValidationResult result, bool ret = false,
146+
const std::string &_strRejectReason="",
147+
const std::string &_strDebugMessage="") {
148+
m_result = result;
149+
return ValidationState::Invalid(ret, _strRejectReason, _strDebugMessage);
150+
}
151+
BlockValidationResult GetResult() const { return m_result; }
152+
};
153+
129154
// These implement the weight = (stripped_size * 4) + witness_size formula,
130155
// using only serialization with and without witness data. As witness_size
131156
// is equal to total_size - stripped_size, this formula is identical to:

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ static void ThreadImport(std::vector<fs::path> vImportFiles)
712712
}
713713

714714
// scan for better chains in the block chain database, that are not yet connected in the active best chain
715-
CValidationState state;
715+
BlockValidationState state;
716716
if (!ActivateBestChain(state, chainparams)) {
717717
LogPrintf("Failed to connect best block (%s)\n", FormatStateMessage(state));
718718
StartShutdown();

0 commit comments

Comments
 (0)