Skip to content

Commit d7d7d31

Browse files
committed
Merge #15141: Rewrite DoS interface between validation and net_processing
0ff1c2a Separate reason for premature spends (coinbase/locktime) (Suhas Daftuar) 54470e7 Assert validation reasons are contextually correct (Suhas Daftuar) 2120c31 [refactor] Update some comments in validation.cpp as we arent doing DoS there (Matt Corallo) 12dbdd7 [refactor] Drop unused state.DoS(), state.GetDoS(), state.CorruptionPossible() (Matt Corallo) aa502b8 scripted-diff: Remove DoS calls to CValidationState (Matt Corallo) 7721ad6 [refactor] Prep for scripted-diff by removing some \ns which annoy sed. (Matt Corallo) 5e78c57 Allow use of state.Invalid() for all reasons (Matt Corallo) 6b34bc6 Fix handling of invalid headers (Suhas Daftuar) ef54b48 [refactor] Use Reasons directly instead of DoS codes (Matt Corallo) 9ab2a04 CorruptionPossible -> BLOCK_MUTATED (Matt Corallo) 6e55b29 CorruptionPossible -> TX_WITNESS_MUTATED (Matt Corallo) 7df16e7 LookupBlockIndex -> CACHED_INVALID (Matt Corallo) c8b0d22 [refactor] Drop redundant nDoS, corruptionPossible, SetCorruptionPossible (Matt Corallo) 34477cc [refactor] Add useful-for-dos "reason" field to CValidationState (Matt Corallo) 6a7f877 Ban all peers for all block script failures (Suhas Daftuar) 7b99910 Clean up banning levels (Matt Corallo) b8b4c80 [refactor] drop IsInvalid(nDoSOut) (Matt Corallo) 8818729 [refactor] Refactor misbehavior ban decisions to MaybePunishNode() (Matt Corallo) 00e11e6 [refactor] rename stateDummy -> orphan_state (Matt Corallo) f34fa71 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: utACK 0ff1c2a ryanofsky: utACK 0ff1c2a. Only change is dropping the first commit (f3883a321bf4ab289edcd9754b12cae3a648b175), and dropping the temporary `assert(level == GetDoS())` that was in 35ee77f2832eaffce30042e00785c310c5540cdc (now c8b0d22) Tree-SHA512: e915a411100876398af5463d0a885920e44d473467bb6af991ef2e8f2681db6c1209bb60f848bd154be72d460f039b5653df20a6840352c5f7ea5486d9f777a3
2 parents f19a3b2 + 0ff1c2a commit d7d7d31

File tree

9 files changed

+326
-223
lines changed

9 files changed

+326
-223
lines changed

src/blockencodings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
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.CorruptionPossible())
206+
if (state.GetReason() == ValidationInvalidReason::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: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
1111
{
1212
// Basic checks that don't depend on any context
1313
if (tx.vin.empty())
14-
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty");
14+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty");
1515
if (tx.vout.empty())
16-
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty");
16+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "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.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
19+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize");
2020

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

3434
// Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock
@@ -37,20 +37,20 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
3737
for (const auto& txin : tx.vin)
3838
{
3939
if (!vInOutPoints.insert(txin.prevout).second)
40-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
40+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
4141
}
4242
}
4343

4444
if (tx.IsCoinBase())
4545
{
4646
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
47-
return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
47+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length");
4848
}
4949
else
5050
{
5151
for (const auto& txin : tx.vin)
5252
if (txin.prevout.IsNull())
53-
return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null");
53+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null");
5454
}
5555

5656
return true;

src/consensus/tx_verify.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
160160
{
161161
// are the actual inputs available?
162162
if (!inputs.HaveInputs(tx)) {
163-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", false,
163+
return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, REJECT_INVALID, "bad-txns-inputs-missingorspent",
164164
strprintf("%s: inputs missing/spent", __func__));
165165
}
166166

@@ -172,28 +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(false,
176-
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
175+
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
177176
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
178177
}
179178

180179
// Check for negative or overflow input values
181180
nValueIn += coin.out.nValue;
182181
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
183-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
182+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
184183
}
185184
}
186185

187186
const CAmount value_out = tx.GetValueOut();
188187
if (nValueIn < value_out) {
189-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
188+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-in-belowout",
190189
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
191190
}
192191

193192
// Tally transaction fees
194193
const CAmount txfee_aux = nValueIn - value_out;
195194
if (!MoneyRange(txfee_aux)) {
196-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
195+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-fee-outofrange");
197196
}
198197

199198
txfee = txfee_aux;

src/consensus/validation.h

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,78 @@ static const unsigned char REJECT_NONSTANDARD = 0x40;
2222
static const unsigned char REJECT_INSUFFICIENTFEE = 0x42;
2323
static const unsigned char REJECT_CHECKPOINT = 0x43;
2424

25+
/** A "reason" why something was invalid, suitable for determining whether the
26+
* provider of the object should be banned/ignored/disconnected/etc.
27+
* These are much more granular than the rejection codes, which may be more
28+
* useful for some other use-cases.
29+
*/
30+
enum class ValidationInvalidReason {
31+
// txn and blocks:
32+
NONE, //!< not actually invalid
33+
CONSENSUS, //!< invalid by consensus rules (excluding any below reasons)
34+
/**
35+
* Invalid by a change to consensus rules more recent than SegWit.
36+
* Currently unused as there are no such consensus rule changes, and any download
37+
* sources realistically need to support SegWit in order to provide useful data,
38+
* so differentiating between always-invalid and invalid-by-pre-SegWit-soft-fork
39+
* is uninteresting.
40+
*/
41+
RECENT_CONSENSUS_CHANGE,
42+
// Only blocks (or headers):
43+
CACHED_INVALID, //!< this object was cached as being invalid, but we don't know why
44+
BLOCK_INVALID_HEADER, //!< invalid proof of work or time too old
45+
BLOCK_MUTATED, //!< the block's data didn't match the data committed to by the PoW
46+
BLOCK_MISSING_PREV, //!< We don't have the previous block the checked one is built on
47+
BLOCK_INVALID_PREV, //!< A block this one builds on is invalid
48+
BLOCK_TIME_FUTURE, //!< block timestamp was > 2 hours in the future (or our clock is bad)
49+
BLOCK_CHECKPOINT, //!< the block failed to meet one of our checkpoints
50+
// Only loose txn:
51+
TX_NOT_STANDARD, //!< didn't meet our local policy rules
52+
TX_MISSING_INPUTS, //!< a transaction was missing some of its inputs
53+
TX_PREMATURE_SPEND, //!< transaction spends a coinbase too early, or violates locktime/sequence locks
54+
/**
55+
* Transaction might be missing a witness, have a witness prior to SegWit
56+
* activation, or witness may have been malleated (which includes
57+
* non-standard witnesses).
58+
*/
59+
TX_WITNESS_MUTATED,
60+
/**
61+
* Tx already in mempool or conflicts with a tx in the chain
62+
* (if it conflicts with another tx in mempool, we use MEMPOOL_POLICY as it failed to reach the RBF threshold)
63+
* TODO: Currently this is only used if the transaction already exists in the mempool or on chain,
64+
* TODO: ATMP's fMissingInputs and a valid CValidationState being used to indicate missing inputs
65+
*/
66+
TX_CONFLICT,
67+
TX_MEMPOOL_POLICY, //!< violated mempool's fee/size/descendant/RBF/etc limits
68+
};
69+
70+
inline bool IsTransactionReason(ValidationInvalidReason r)
71+
{
72+
return r == ValidationInvalidReason::NONE ||
73+
r == ValidationInvalidReason::CONSENSUS ||
74+
r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE ||
75+
r == ValidationInvalidReason::TX_NOT_STANDARD ||
76+
r == ValidationInvalidReason::TX_PREMATURE_SPEND ||
77+
r == ValidationInvalidReason::TX_MISSING_INPUTS ||
78+
r == ValidationInvalidReason::TX_WITNESS_MUTATED ||
79+
r == ValidationInvalidReason::TX_CONFLICT ||
80+
r == ValidationInvalidReason::TX_MEMPOOL_POLICY;
81+
}
82+
83+
inline bool IsBlockReason(ValidationInvalidReason r)
84+
{
85+
return r == ValidationInvalidReason::NONE ||
86+
r == ValidationInvalidReason::CONSENSUS ||
87+
r == ValidationInvalidReason::RECENT_CONSENSUS_CHANGE ||
88+
r == ValidationInvalidReason::CACHED_INVALID ||
89+
r == ValidationInvalidReason::BLOCK_INVALID_HEADER ||
90+
r == ValidationInvalidReason::BLOCK_MUTATED ||
91+
r == ValidationInvalidReason::BLOCK_MISSING_PREV ||
92+
r == ValidationInvalidReason::BLOCK_INVALID_PREV ||
93+
r == ValidationInvalidReason::BLOCK_TIME_FUTURE ||
94+
r == ValidationInvalidReason::BLOCK_CHECKPOINT;
95+
}
96+
2597
/** Capture information about block/transaction validation */
2698
class CValidationState {
2799
private:
@@ -30,32 +102,24 @@ class CValidationState {
30102
MODE_INVALID, //!< network rule violation (DoS value may be set)
31103
MODE_ERROR, //!< run-time error
32104
} mode;
33-
int nDoS;
105+
ValidationInvalidReason m_reason;
34106
std::string strRejectReason;
35107
unsigned int chRejectCode;
36-
bool corruptionPossible;
37108
std::string strDebugMessage;
38109
public:
39-
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {}
40-
bool DoS(int level, bool ret = false,
41-
unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="",
42-
bool corruptionIn=false,
43-
const std::string &strDebugMessageIn="") {
110+
CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE), chRejectCode(0) {}
111+
bool Invalid(ValidationInvalidReason reasonIn, bool ret = false,
112+
unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="",
113+
const std::string &strDebugMessageIn="") {
114+
m_reason = reasonIn;
44115
chRejectCode = chRejectCodeIn;
45116
strRejectReason = strRejectReasonIn;
46-
corruptionPossible = corruptionIn;
47117
strDebugMessage = strDebugMessageIn;
48118
if (mode == MODE_ERROR)
49119
return ret;
50-
nDoS += level;
51120
mode = MODE_INVALID;
52121
return ret;
53122
}
54-
bool Invalid(bool ret = false,
55-
unsigned int _chRejectCode=0, const std::string &_strRejectReason="",
56-
const std::string &_strDebugMessage="") {
57-
return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage);
58-
}
59123
bool Error(const std::string& strRejectReasonIn) {
60124
if (mode == MODE_VALID)
61125
strRejectReason = strRejectReasonIn;
@@ -71,19 +135,7 @@ class CValidationState {
71135
bool IsError() const {
72136
return mode == MODE_ERROR;
73137
}
74-
bool IsInvalid(int &nDoSOut) const {
75-
if (IsInvalid()) {
76-
nDoSOut = nDoS;
77-
return true;
78-
}
79-
return false;
80-
}
81-
bool CorruptionPossible() const {
82-
return corruptionPossible;
83-
}
84-
void SetCorruptionPossible() {
85-
corruptionPossible = true;
86-
}
138+
ValidationInvalidReason GetReason() const { return m_reason; }
87139
unsigned int GetRejectCode() const { return chRejectCode; }
88140
std::string GetRejectReason() const { return strRejectReason; }
89141
std::string GetDebugMessage() const { return strDebugMessage; }

0 commit comments

Comments
 (0)