Skip to content

Commit 53776b4

Browse files
laanwjvijaydasmp
authored andcommitted
Merge bitcoin#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 bitcoin#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 bitcoin#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 (f3883a3), and dropping the temporary `assert(level == GetDoS())` that was in 35ee77f (now c8b0d22) Tree-SHA512: e915a411100876398af5463d0a885920e44d473467bb6af991ef2e8f2681db6c1209bb60f848bd154be72d460f039b5653df20a6840352c5f7ea5486d9f777a3
1 parent d7252f5 commit 53776b4

File tree

9 files changed

+307
-176
lines changed

9 files changed

+307
-176
lines changed

src/blockencodings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
204204
// but that is expensive, and CheckBlock caches a block's
205205
// "checked-status" (in the CBlock?). CBlock should be able to
206206
// check its own merkle root and cache that check.
207-
if (state.CorruptionPossible())
207+
if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED)
208208
return READ_STATUS_FAILED; // Possible Short ID collision
209209
return READ_STATUS_CHECKBLOCK_FAILED;
210210
}

src/consensus/tx_check.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,32 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state)
1919

2020
// Basic checks that don't depend on any context
2121
if (!allowEmptyTxInOut && tx.vin.empty())
22-
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty");
22+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty");
2323
if (!allowEmptyTxInOut && tx.vout.empty())
24-
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty");
24+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-empty");
2525
// Size limits
2626
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_LEGACY_BLOCK_SIZE)
27-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
27+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize");
2828
if (tx.vExtraPayload.size() > MAX_TX_EXTRA_PAYLOAD)
29-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-payload-oversize");
29+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-payload-oversize");
3030

3131
// Check for negative or overflow output values (see CVE-2010-5139)
3232
CAmount nValueOut = 0;
3333
for (const auto& txout : tx.vout) {
3434
if (txout.nValue < 0)
35-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative");
35+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-negative");
3636
if (txout.nValue > MAX_MONEY)
37-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge");
37+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-toolarge");
3838
nValueOut += txout.nValue;
3939
if (!MoneyRange(nValueOut))
40-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
40+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
4141
}
4242

4343
// Check for duplicate inputs
4444
std::set<COutPoint> vInOutPoints;
4545
for (const auto& txin : tx.vin) {
4646
if (!vInOutPoints.insert(txin.prevout).second)
47-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
47+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
4848
}
4949

5050
if (tx.IsCoinBase()) {
@@ -54,11 +54,11 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state)
5454
minCbSize = 1;
5555
}
5656
if (tx.vin[0].scriptSig.size() < minCbSize || tx.vin[0].scriptSig.size() > 100)
57-
return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
57+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length");
5858
} else {
5959
for (const auto& txin : tx.vin)
6060
if (txin.prevout.IsNull())
61-
return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null");
61+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null");
6262
}
6363

6464
return true;

src/consensus/tx_verify.cpp

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

@@ -174,28 +174,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
174174

175175
// If prev is coinbase, check that it's matured
176176
if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) {
177-
return state.Invalid(false,
178-
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
177+
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
179178
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
180179
}
181180

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

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

195194
// Tally transaction fees
196195
const CAmount txfee_aux = nValueIn - value_out;
197196
if (!MoneyRange(txfee_aux)) {
198-
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
197+
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-fee-outofrange");
199198
}
200199

201200
txfee = txfee_aux;

src/consensus/validation.h

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,78 @@ static const unsigned char REJECT_NONSTANDARD = 0x40;
1818
static const unsigned char REJECT_INSUFFICIENTFEE = 0x42;
1919
static const unsigned char REJECT_CHECKPOINT = 0x43;
2020

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

0 commit comments

Comments
 (0)