Merge #15141: Rewrite DoS interface between validation and net_processing

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:
    utACK 0ff1c2a838
  ryanofsky:
    utACK 0ff1c2a838. Only change is dropping the first commit (f3883a321bf4ab289edcd9754b12cae3a648b175), and dropping the temporary `assert(level == GetDoS())` that was in 35ee77f2832eaffce30042e00785c310c5540cdc (now c8b0d22698)

Tree-SHA512: e915a411100876398af5463d0a885920e44d473467bb6af991ef2e8f2681db6c1209bb60f848bd154be72d460f039b5653df20a6840352c5f7ea5486d9f777a3
This commit is contained in:
Wladimir J. van der Laan 2019-05-04 11:58:48 +02:00
commit d7d7d31506
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
9 changed files with 326 additions and 223 deletions

View file

@ -203,7 +203,7 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
// but that is expensive, and CheckBlock caches a block's // but that is expensive, and CheckBlock caches a block's
// "checked-status" (in the CBlock?). CBlock should be able to // "checked-status" (in the CBlock?). CBlock should be able to
// check its own merkle root and cache that check. // check its own merkle root and cache that check.
if (state.CorruptionPossible()) if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED)
return READ_STATUS_FAILED; // Possible Short ID collision return READ_STATUS_FAILED; // Possible Short ID collision
return READ_STATUS_CHECKBLOCK_FAILED; return READ_STATUS_CHECKBLOCK_FAILED;
} }

View file

@ -11,24 +11,24 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
{ {
// Basic checks that don't depend on any context // Basic checks that don't depend on any context
if (tx.vin.empty()) if (tx.vin.empty())
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vin-empty"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vin-empty");
if (tx.vout.empty()) if (tx.vout.empty())
return state.DoS(10, false, REJECT_INVALID, "bad-txns-vout-empty"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-empty");
// Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability) // 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) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values // Check for negative or overflow output values
CAmount nValueOut = 0; CAmount nValueOut = 0;
for (const auto& txout : tx.vout) for (const auto& txout : tx.vout)
{ {
if (txout.nValue < 0) if (txout.nValue < 0)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY) if (txout.nValue > MAX_MONEY)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue; nValueOut += txout.nValue;
if (!MoneyRange(nValueOut)) if (!MoneyRange(nValueOut))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
} }
// Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock // 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
for (const auto& txin : tx.vin) for (const auto& txin : tx.vin)
{ {
if (!vInOutPoints.insert(txin.prevout).second) if (!vInOutPoints.insert(txin.prevout).second)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-duplicate"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputs-duplicate");
} }
} }
if (tx.IsCoinBase()) if (tx.IsCoinBase())
{ {
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100) if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-length");
} }
else else
{ {
for (const auto& txin : tx.vin) for (const auto& txin : tx.vin)
if (txin.prevout.IsNull()) if (txin.prevout.IsNull())
return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-prevout-null");
} }
return true; return true;

View file

@ -160,7 +160,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
{ {
// are the actual inputs available? // are the actual inputs available?
if (!inputs.HaveInputs(tx)) { if (!inputs.HaveInputs(tx)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent", false, return state.Invalid(ValidationInvalidReason::TX_MISSING_INPUTS, false, REJECT_INVALID, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__)); strprintf("%s: inputs missing/spent", __func__));
} }
@ -172,28 +172,27 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, CValidationState& state, c
// If prev is coinbase, check that it's matured // If prev is coinbase, check that it's matured
if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) { if (coin.IsCoinBase() && nSpendHeight - coin.nHeight < COINBASE_MATURITY) {
return state.Invalid(false, return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight)); strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
} }
// Check for negative or overflow input values // Check for negative or overflow input values
nValueIn += coin.out.nValue; nValueIn += coin.out.nValue;
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) { if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
} }
} }
const CAmount value_out = tx.GetValueOut(); const CAmount value_out = tx.GetValueOut();
if (nValueIn < value_out) { if (nValueIn < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-in-belowout",
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out))); strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
} }
// Tally transaction fees // Tally transaction fees
const CAmount txfee_aux = nValueIn - value_out; const CAmount txfee_aux = nValueIn - value_out;
if (!MoneyRange(txfee_aux)) { if (!MoneyRange(txfee_aux)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-fee-outofrange");
} }
txfee = txfee_aux; txfee = txfee_aux;

View file

@ -22,6 +22,78 @@ static const unsigned char REJECT_NONSTANDARD = 0x40;
static const unsigned char REJECT_INSUFFICIENTFEE = 0x42; static const unsigned char REJECT_INSUFFICIENTFEE = 0x42;
static const unsigned char REJECT_CHECKPOINT = 0x43; static const unsigned char REJECT_CHECKPOINT = 0x43;
/** A "reason" why something was invalid, suitable for determining whether the
* provider of the object 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 ValidationInvalidReason {
// txn and blocks:
NONE, //!< not actually invalid
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.
*/
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_NOT_STANDARD, //!< didn't meet our local policy rules
TX_MISSING_INPUTS, //!< a 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
* activation, or witness may have been malleated (which includes
* non-standard witnesses).
*/
TX_WITNESS_MUTATED,
/**
* 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
*/
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;
}
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 */ /** Capture information about block/transaction validation */
class CValidationState { class CValidationState {
private: private:
@ -30,32 +102,24 @@ private:
MODE_INVALID, //!< network rule violation (DoS value may be set) MODE_INVALID, //!< network rule violation (DoS value may be set)
MODE_ERROR, //!< run-time error MODE_ERROR, //!< run-time error
} mode; } mode;
int nDoS; ValidationInvalidReason m_reason;
std::string strRejectReason; std::string strRejectReason;
unsigned int chRejectCode; unsigned int chRejectCode;
bool corruptionPossible;
std::string strDebugMessage; std::string strDebugMessage;
public: public:
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {} CValidationState() : mode(MODE_VALID), m_reason(ValidationInvalidReason::NONE), chRejectCode(0) {}
bool DoS(int level, bool ret = false, bool Invalid(ValidationInvalidReason reasonIn, bool ret = false,
unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="", unsigned int chRejectCodeIn=0, const std::string &strRejectReasonIn="",
bool corruptionIn=false, const std::string &strDebugMessageIn="") {
const std::string &strDebugMessageIn="") { m_reason = reasonIn;
chRejectCode = chRejectCodeIn; chRejectCode = chRejectCodeIn;
strRejectReason = strRejectReasonIn; strRejectReason = strRejectReasonIn;
corruptionPossible = corruptionIn;
strDebugMessage = strDebugMessageIn; strDebugMessage = strDebugMessageIn;
if (mode == MODE_ERROR) if (mode == MODE_ERROR)
return ret; return ret;
nDoS += level;
mode = MODE_INVALID; mode = MODE_INVALID;
return ret; return ret;
} }
bool Invalid(bool ret = false,
unsigned int _chRejectCode=0, const std::string &_strRejectReason="",
const std::string &_strDebugMessage="") {
return DoS(0, ret, _chRejectCode, _strRejectReason, false, _strDebugMessage);
}
bool Error(const std::string& strRejectReasonIn) { bool Error(const std::string& strRejectReasonIn) {
if (mode == MODE_VALID) if (mode == MODE_VALID)
strRejectReason = strRejectReasonIn; strRejectReason = strRejectReasonIn;
@ -71,19 +135,7 @@ public:
bool IsError() const { bool IsError() const {
return mode == MODE_ERROR; return mode == MODE_ERROR;
} }
bool IsInvalid(int &nDoSOut) const { ValidationInvalidReason GetReason() const { return m_reason; }
if (IsInvalid()) {
nDoSOut = nDoS;
return true;
}
return false;
}
bool CorruptionPossible() const {
return corruptionPossible;
}
void SetCorruptionPossible() {
corruptionPossible = true;
}
unsigned int GetRejectCode() const { return chRejectCode; } unsigned int GetRejectCode() const { return chRejectCode; }
std::string GetRejectReason() const { return strRejectReason; } std::string GetRejectReason() const { return strRejectReason; }
std::string GetDebugMessage() const { return strDebugMessage; } std::string GetDebugMessage() const { return strDebugMessage; }

View file

@ -351,7 +351,16 @@ struct CNodeState {
TxDownloadState m_tx_download; TxDownloadState m_tx_download;
CNodeState(CAddress addrIn, std::string addrNameIn) : address(addrIn), name(addrNameIn) { //! Whether this peer is an inbound connection
bool m_is_inbound;
//! Whether this peer is a manual connection
bool m_is_manual_connection;
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound, bool is_manual) :
address(addrIn), name(std::move(addrNameIn)), m_is_inbound(is_inbound),
m_is_manual_connection (is_manual)
{
fCurrentlyConnected = false; fCurrentlyConnected = false;
nMisbehavior = 0; nMisbehavior = 0;
fShouldBan = false; fShouldBan = false;
@ -747,7 +756,7 @@ void PeerLogicValidation::InitializeNode(CNode *pnode) {
NodeId nodeid = pnode->GetId(); NodeId nodeid = pnode->GetId();
{ {
LOCK(cs_main); LOCK(cs_main);
mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName))); mapNodeState.emplace_hint(mapNodeState.end(), std::piecewise_construct, std::forward_as_tuple(nodeid), std::forward_as_tuple(addr, std::move(addrName), pnode->fInbound, pnode->m_manual_connection));
} }
if(!pnode->fInbound) if(!pnode->fInbound)
PushNodeVersion(pnode, connman, GetTime()); PushNodeVersion(pnode, connman, GetTime());
@ -959,6 +968,90 @@ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIV
LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d)%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed); LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d)%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed);
} }
/**
* Returns true if the given validation state result may result in a peer
* banning/disconnecting us. We use this to determine which unaccepted
* transactions from a whitelisted peer that we can safely relay.
*/
static bool TxRelayMayResultInDisconnect(const CValidationState& state)
{
assert(IsTransactionReason(state.GetReason()));
return state.GetReason() == ValidationInvalidReason::CONSENSUS;
}
/**
* Potentially ban a node based on the contents of a CValidationState object
*
* @param[in] via_compact_block: this bool is passed in because net_processing should
* punish peers differently depending on whether the data was provided in a compact
* block message or not. If the compact block had a valid header, but contained invalid
* txs, the peer should not be punished. See BIP 152.
*
* @return Returns true if the peer was punished (probably disconnected)
*
* Changes here may need to be reflected in TxRelayMayResultInDisconnect().
*/
static bool MaybePunishNode(NodeId nodeid, const CValidationState& state, bool via_compact_block, const std::string& message = "") {
switch (state.GetReason()) {
case ValidationInvalidReason::NONE:
break;
// The node is providing invalid data:
case ValidationInvalidReason::CONSENSUS:
case ValidationInvalidReason::BLOCK_MUTATED:
if (!via_compact_block) {
LOCK(cs_main);
Misbehaving(nodeid, 100, message);
return true;
}
break;
case ValidationInvalidReason::CACHED_INVALID:
{
LOCK(cs_main);
CNodeState *node_state = State(nodeid);
if (node_state == nullptr) {
break;
}
// Ban outbound (but not inbound) peers if on an invalid chain.
// Exempt HB compact block peers and manual connections.
if (!via_compact_block && !node_state->m_is_inbound && !node_state->m_is_manual_connection) {
Misbehaving(nodeid, 100, message);
return true;
}
break;
}
case ValidationInvalidReason::BLOCK_INVALID_HEADER:
case ValidationInvalidReason::BLOCK_CHECKPOINT:
case ValidationInvalidReason::BLOCK_INVALID_PREV:
{
LOCK(cs_main);
Misbehaving(nodeid, 100, message);
}
return true;
// Conflicting (but not necessarily invalid) data or different policy:
case ValidationInvalidReason::BLOCK_MISSING_PREV:
{
// TODO: Handle this much more gracefully (10 DoS points is super arbitrary)
LOCK(cs_main);
Misbehaving(nodeid, 10, message);
}
return true;
case ValidationInvalidReason::RECENT_CONSENSUS_CHANGE:
case ValidationInvalidReason::BLOCK_TIME_FUTURE:
case ValidationInvalidReason::TX_NOT_STANDARD:
case ValidationInvalidReason::TX_MISSING_INPUTS:
case ValidationInvalidReason::TX_PREMATURE_SPEND:
case ValidationInvalidReason::TX_WITNESS_MUTATED:
case ValidationInvalidReason::TX_CONFLICT:
case ValidationInvalidReason::TX_MEMPOOL_POLICY:
break;
}
if (message != "") {
LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message);
}
return false;
}
@ -1132,14 +1225,12 @@ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationSta
const uint256 hash(block.GetHash()); const uint256 hash(block.GetHash());
std::map<uint256, std::pair<NodeId, bool>>::iterator it = mapBlockSource.find(hash); std::map<uint256, std::pair<NodeId, bool>>::iterator it = mapBlockSource.find(hash);
int nDoS = 0; if (state.IsInvalid()) {
if (state.IsInvalid(nDoS)) {
// Don't send reject message with code 0 or an internal reject code. // Don't send reject message with code 0 or an internal reject code.
if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) { if (it != mapBlockSource.end() && State(it->second.first) && state.GetRejectCode() > 0 && state.GetRejectCode() < REJECT_INTERNAL) {
CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), hash}; CBlockReject reject = {(unsigned char)state.GetRejectCode(), state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), hash};
State(it->second.first)->rejects.push_back(reject); State(it->second.first)->rejects.push_back(reject);
if (nDoS > 0 && it->second.second) MaybePunishNode(/*nodeid=*/ it->second.first, state, /*via_compact_block=*/ !it->second.second);
Misbehaving(it->second.first, nDoS);
} }
} }
// Check that: // Check that:
@ -1489,7 +1580,7 @@ inline void static SendBlockTransactions(const CBlock& block, const BlockTransac
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
} }
bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool punish_duplicate_invalid) bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::vector<CBlockHeader>& headers, const CChainParams& chainparams, bool via_compact_block)
{ {
const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
size_t nCount = headers.size(); size_t nCount = headers.size();
@ -1551,48 +1642,8 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
CValidationState state; CValidationState state;
CBlockHeader first_invalid_header; CBlockHeader first_invalid_header;
if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) { if (!ProcessNewBlockHeaders(headers, state, chainparams, &pindexLast, &first_invalid_header)) {
int nDoS; if (state.IsInvalid()) {
if (state.IsInvalid(nDoS)) { MaybePunishNode(pfrom->GetId(), state, via_compact_block, "invalid header received");
LOCK(cs_main);
if (nDoS > 0) {
Misbehaving(pfrom->GetId(), nDoS, "invalid header received");
} else {
LogPrint(BCLog::NET, "peer=%d: invalid header received\n", pfrom->GetId());
}
if (punish_duplicate_invalid && LookupBlockIndex(first_invalid_header.GetHash())) {
// Goal: don't allow outbound peers to use up our outbound
// connection slots if they are on incompatible chains.
//
// We ask the caller to set punish_invalid appropriately based
// on the peer and the method of header delivery (compact
// blocks are allowed to be invalid in some circumstances,
// under BIP 152).
// Here, we try to detect the narrow situation that we have a
// valid block header (ie it was valid at the time the header
// was received, and hence stored in mapBlockIndex) but know the
// block is invalid, and that a peer has announced that same
// block as being on its active chain.
// Disconnect the peer in such a situation.
//
// Note: if the header that is invalid was not accepted to our
// mapBlockIndex at all, that may also be grounds for
// disconnecting the peer, as the chain they are on is likely
// to be incompatible. However, there is a circumstance where
// that does not hold: if the header's timestamp is more than
// 2 hours ahead of our current time. In that case, the header
// may become valid in the future, and we don't want to
// disconnect a peer merely for serving us one too-far-ahead
// block header, to prevent an attacker from splitting the
// network by mining a block right at the 2 hour boundary.
//
// TODO: update the DoS logic (or, rather, rewrite the
// DoS-interface between validation and net_processing) so that
// the interface is cleaner, and so that we disconnect on all the
// reasons that a peer's headers chain is incompatible
// with ours (eg block->nVersion softforks, MTP violations,
// etc), and not just the duplicate-invalid case.
pfrom->fDisconnect = true;
}
return false; return false;
} }
} }
@ -1727,13 +1778,13 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se
const CTransaction& orphanTx = *porphanTx; const CTransaction& orphanTx = *porphanTx;
NodeId fromPeer = orphan_it->second.fromPeer; NodeId fromPeer = orphan_it->second.fromPeer;
bool fMissingInputs2 = false; bool fMissingInputs2 = false;
// Use a dummy CValidationState so someone can't setup nodes to counter-DoS based on orphan // Use a new CValidationState because orphans come from different peers (and we call
// resolution (that is, feeding people an invalid transaction based on LegitTxX in order to get // MaybePunishNode based on the source peer from the orphan map, not based on the peer
// anyone relaying LegitTxX banned) // that relayed the previous transaction).
CValidationState stateDummy; CValidationState orphan_state;
if (setMisbehaving.count(fromPeer)) continue; if (setMisbehaving.count(fromPeer)) continue;
if (AcceptToMemoryPool(mempool, stateDummy, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) { if (AcceptToMemoryPool(mempool, orphan_state, porphanTx, &fMissingInputs2, &removed_txn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString()); LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
RelayTransaction(orphanTx, connman); RelayTransaction(orphanTx, connman);
for (unsigned int i = 0; i < orphanTx.vout.size(); i++) { for (unsigned int i = 0; i < orphanTx.vout.size(); i++) {
@ -1747,17 +1798,18 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se
EraseOrphanTx(orphanHash); EraseOrphanTx(orphanHash);
done = true; done = true;
} else if (!fMissingInputs2) { } else if (!fMissingInputs2) {
int nDos = 0; if (orphan_state.IsInvalid()) {
if (stateDummy.IsInvalid(nDos) && nDos > 0) {
// Punish peer that gave us an invalid orphan tx // Punish peer that gave us an invalid orphan tx
Misbehaving(fromPeer, nDos); if (MaybePunishNode(fromPeer, orphan_state, /*via_compact_block*/ false)) {
setMisbehaving.insert(fromPeer); setMisbehaving.insert(fromPeer);
}
LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString()); LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n", orphanHash.ToString());
} }
// Has inputs but not accepted to mempool // Has inputs but not accepted to mempool
// Probably non-standard or insufficient fee // Probably non-standard or insufficient fee
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString()); LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString());
if (!orphanTx.HasWitness() && !stateDummy.CorruptionPossible()) { assert(IsTransactionReason(orphan_state.GetReason()));
if (!orphanTx.HasWitness() && orphan_state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) {
// Do not use rejection cache for witness transactions or // Do not use rejection cache for witness transactions or
// witness-stripped transactions, as they can have been malleated. // witness-stripped transactions, as they can have been malleated.
// See https://github.com/bitcoin/bitcoin/issues/8279 for details. // See https://github.com/bitcoin/bitcoin/issues/8279 for details.
@ -2474,7 +2526,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
recentRejects->insert(tx.GetHash()); recentRejects->insert(tx.GetHash());
} }
} else { } else {
if (!tx.HasWitness() && !state.CorruptionPossible()) { assert(IsTransactionReason(state.GetReason()));
if (!tx.HasWitness() && state.GetReason() != ValidationInvalidReason::TX_WITNESS_MUTATED) {
// Do not use rejection cache for witness transactions or // Do not use rejection cache for witness transactions or
// witness-stripped transactions, as they can have been malleated. // witness-stripped transactions, as they can have been malleated.
// See https://github.com/bitcoin/bitcoin/issues/8279 for details. // See https://github.com/bitcoin/bitcoin/issues/8279 for details.
@ -2493,15 +2546,13 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// to policy, allowing the node to function as a gateway for // to policy, allowing the node to function as a gateway for
// nodes hidden behind it. // nodes hidden behind it.
// //
// Never relay transactions that we would assign a non-zero DoS // Never relay transactions that might result in being
// score for, as we expect peers to do the same with us in that // disconnected (or banned).
// case. if (state.IsInvalid() && TxRelayMayResultInDisconnect(state)) {
int nDoS = 0; LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state));
if (!state.IsInvalid(nDoS) || nDoS == 0) { } else {
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId()); LogPrintf("Force relaying tx %s from whitelisted peer=%d\n", tx.GetHash().ToString(), pfrom->GetId());
RelayTransaction(tx, connman); RelayTransaction(tx, connman);
} else {
LogPrintf("Not relaying invalid transaction %s from whitelisted peer=%d (%s)\n", tx.GetHash().ToString(), pfrom->GetId(), FormatStateMessage(state));
} }
} }
} }
@ -2526,8 +2577,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// peer simply for relaying a tx that our recentRejects has caught, // peer simply for relaying a tx that our recentRejects has caught,
// regardless of false positives. // regardless of false positives.
int nDoS = 0; if (state.IsInvalid())
if (state.IsInvalid(nDoS))
{ {
LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(), LogPrint(BCLog::MEMPOOLREJ, "%s from peer=%d was not accepted: %s\n", tx.GetHash().ToString(),
pfrom->GetId(), pfrom->GetId(),
@ -2536,9 +2586,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(), connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::REJECT, strCommand, (unsigned char)state.GetRejectCode(),
state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash)); state.GetRejectReason().substr(0, MAX_REJECT_MESSAGE_LENGTH), inv.hash));
} }
if (nDoS > 0) { MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ false);
Misbehaving(pfrom->GetId(), nDoS);
}
} }
return true; return true;
} }
@ -2574,14 +2622,8 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
const CBlockIndex *pindex = nullptr; const CBlockIndex *pindex = nullptr;
CValidationState state; CValidationState state;
if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) { if (!ProcessNewBlockHeaders({cmpctblock.header}, state, chainparams, &pindex)) {
int nDoS; if (state.IsInvalid()) {
if (state.IsInvalid(nDoS)) { MaybePunishNode(pfrom->GetId(), state, /*via_compact_block*/ true, "invalid header via cmpctblock");
if (nDoS > 0) {
LOCK(cs_main);
Misbehaving(pfrom->GetId(), nDoS, strprintf("Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId()));
} else {
LogPrint(BCLog::NET, "Peer %d sent us invalid header via cmpctblock\n", pfrom->GetId());
}
return true; return true;
} }
} }
@ -2731,7 +2773,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// the peer if the header turns out to be for an invalid block. // the peer if the header turns out to be for an invalid block.
// Note that if a peer tries to build on an invalid chain, that // Note that if a peer tries to build on an invalid chain, that
// will be detected and the peer will be banned. // will be detected and the peer will be banned.
return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*punish_duplicate_invalid=*/false); return ProcessHeadersMessage(pfrom, connman, {cmpctblock.header}, chainparams, /*via_compact_block=*/true);
} }
if (fBlockReconstructed) { if (fBlockReconstructed) {
@ -2874,12 +2916,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
ReadCompactSize(vRecv); // ignore tx count; assume it is 0. ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
} }
// Headers received via a HEADERS message should be valid, and reflect return ProcessHeadersMessage(pfrom, connman, headers, chainparams, /*via_compact_block=*/false);
// the chain the peer is on. If we receive a known-invalid header,
// disconnect the peer if it is using one of our outbound connection
// slots.
bool should_punish = !pfrom->fInbound && !pfrom->m_manual_connection;
return ProcessHeadersMessage(pfrom, connman, headers, chainparams, should_punish);
} }
if (strCommand == NetMsgType::BLOCK) if (strCommand == NetMsgType::BLOCK)

View file

@ -52,10 +52,7 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
// Check that the validation state reflects the unsuccessful attempt. // Check that the validation state reflects the unsuccessful attempt.
BOOST_CHECK(state.IsInvalid()); BOOST_CHECK(state.IsInvalid());
BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase"); BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase");
BOOST_CHECK(state.GetReason() == ValidationInvalidReason::CONSENSUS);
int nDoS;
BOOST_CHECK_EQUAL(state.IsInvalid(nDoS), true);
BOOST_CHECK_EQUAL(nDoS, 100);
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

View file

@ -585,28 +585,28 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// Coinbase is only valid in a block, not as a loose transaction // Coinbase is only valid in a block, not as a loose transaction
if (tx.IsCoinBase()) if (tx.IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "coinbase"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "coinbase");
// Rather not work on nonstandard transactions (unless -testnet/-regtest) // Rather not work on nonstandard transactions (unless -testnet/-regtest)
std::string reason; std::string reason;
if (fRequireStandard && !IsStandardTx(tx, reason)) if (fRequireStandard && !IsStandardTx(tx, reason))
return state.DoS(0, false, REJECT_NONSTANDARD, reason); return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, reason);
// Do not work on transactions that are too small. // Do not work on transactions that are too small.
// A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes. // A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes.
// Transactions smaller than this are not relayed to reduce unnecessary malloc overhead. // Transactions smaller than this are not relayed to reduce unnecessary malloc overhead.
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE) if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE)
return state.DoS(0, false, REJECT_NONSTANDARD, "tx-size-small"); return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "tx-size-small");
// Only accept nLockTime-using transactions that can be mined in the next // Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't // block; we don't want our mempool filled up with transactions that can't
// be mined yet. // be mined yet.
if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS)) if (!CheckFinalTx(tx, STANDARD_LOCKTIME_VERIFY_FLAGS))
return state.DoS(0, false, REJECT_NONSTANDARD, "non-final"); return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-final");
// is it already in the memory pool? // is it already in the memory pool?
if (pool.exists(hash)) { if (pool.exists(hash)) {
return state.Invalid(false, REJECT_DUPLICATE, "txn-already-in-mempool"); return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-in-mempool");
} }
// Check for conflicts with in-memory transactions // Check for conflicts with in-memory transactions
@ -642,7 +642,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
} }
} }
if (fReplacementOptOut) { if (fReplacementOptOut) {
return state.Invalid(false, REJECT_DUPLICATE, "txn-mempool-conflict"); return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_DUPLICATE, "txn-mempool-conflict");
} }
setConflicts.insert(ptxConflicting->GetHash()); setConflicts.insert(ptxConflicting->GetHash());
@ -672,7 +672,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
for (size_t out = 0; out < tx.vout.size(); out++) { for (size_t out = 0; out < tx.vout.size(); out++) {
// Optimistically just do efficient check of cache for outputs // Optimistically just do efficient check of cache for outputs
if (pcoinsTip->HaveCoinInCache(COutPoint(hash, out))) { if (pcoinsTip->HaveCoinInCache(COutPoint(hash, out))) {
return state.Invalid(false, REJECT_DUPLICATE, "txn-already-known"); return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known");
} }
} }
// Otherwise assume this might be an orphan tx for which we just haven't seen parents yet // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
@ -695,7 +695,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// Must keep pool.cs for this unless we change CheckSequenceLocks to take a // Must keep pool.cs for this unless we change CheckSequenceLocks to take a
// CoinsViewCache instead of create its own // CoinsViewCache instead of create its own
if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final"); return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final");
CAmount nFees = 0; CAmount nFees = 0;
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) { if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) {
@ -704,11 +704,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// Check for non-standard pay-to-script-hash in inputs // Check for non-standard pay-to-script-hash in inputs
if (fRequireStandard && !AreInputsStandard(tx, view)) if (fRequireStandard && !AreInputsStandard(tx, view))
return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
// Check for non-standard witness in P2WSH // Check for non-standard witness in P2WSH
if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view)) if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view))
return state.DoS(0, false, REJECT_NONSTANDARD, "bad-witness-nonstandard", true); return state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, REJECT_NONSTANDARD, "bad-witness-nonstandard");
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);
@ -731,27 +731,22 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
fSpendsCoinbase, nSigOpsCost, lp); fSpendsCoinbase, nSigOpsCost, lp);
unsigned int nSize = entry.GetTxSize(); unsigned int nSize = entry.GetTxSize();
// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
// itself can contain sigops MAX_STANDARD_TX_SIGOPS is less than
// MAX_BLOCK_SIGOPS; we still consider this an invalid rather than
// merely non-standard transaction.
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
return state.DoS(0, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops", false, return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops",
strprintf("%d", nSigOpsCost)); strprintf("%d", nSigOpsCost));
CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize); CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize);
if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) { if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", false, strprintf("%d < %d", nModifiedFees, mempoolRejectFee)); return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", nModifiedFees, mempoolRejectFee));
} }
// No transactions are allowed below minRelayTxFee except from disconnected blocks // No transactions are allowed below minRelayTxFee except from disconnected blocks
if (!bypass_limits && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) { if (!bypass_limits && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", false, strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize))); return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize)));
} }
if (nAbsurdFee && nFees > nAbsurdFee) if (nAbsurdFee && nFees > nAbsurdFee)
return state.Invalid(false, return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false,
REJECT_HIGHFEE, "absurdly-high-fee", REJECT_HIGHFEE, "absurdly-high-fee",
strprintf("%d > %d", nFees, nAbsurdFee)); strprintf("%d > %d", nFees, nAbsurdFee));
@ -763,7 +758,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
std::string errString; std::string errString;
if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
return state.DoS(0, false, REJECT_NONSTANDARD, "too-long-mempool-chain", false, errString); return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString);
} }
// A transaction that spends outputs that would be replaced by it is invalid. Now // A transaction that spends outputs that would be replaced by it is invalid. Now
@ -775,8 +770,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
const uint256 &hashAncestor = ancestorIt->GetTx().GetHash(); const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
if (setConflicts.count(hashAncestor)) if (setConflicts.count(hashAncestor))
{ {
return state.DoS(10, false, return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-spends-conflicting-tx",
REJECT_INVALID, "bad-txns-spends-conflicting-tx", false,
strprintf("%s spends conflicting transaction %s", strprintf("%s spends conflicting transaction %s",
hash.ToString(), hash.ToString(),
hashAncestor.ToString())); hashAncestor.ToString()));
@ -818,8 +812,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
if (newFeeRate <= oldFeeRate) if (newFeeRate <= oldFeeRate)
{ {
return state.DoS(0, false, return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
strprintf("rejecting replacement %s; new feerate %s <= old feerate %s", strprintf("rejecting replacement %s; new feerate %s <= old feerate %s",
hash.ToString(), hash.ToString(),
newFeeRate.ToString(), newFeeRate.ToString(),
@ -847,8 +840,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
nConflictingSize += it->GetTxSize(); nConflictingSize += it->GetTxSize();
} }
} else { } else {
return state.DoS(0, false, return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too many potential replacements",
REJECT_NONSTANDARD, "too many potential replacements", false,
strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n", strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
hash.ToString(), hash.ToString(),
nConflictingCount, nConflictingCount,
@ -867,8 +859,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// it's cheaper to just check if the new input refers to a // it's cheaper to just check if the new input refers to a
// tx that's in the mempool. // tx that's in the mempool.
if (pool.exists(tx.vin[j].prevout.hash)) { if (pool.exists(tx.vin[j].prevout.hash)) {
return state.DoS(0, false, return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "replacement-adds-unconfirmed",
REJECT_NONSTANDARD, "replacement-adds-unconfirmed", false,
strprintf("replacement %s adds unconfirmed input, idx %d", strprintf("replacement %s adds unconfirmed input, idx %d",
hash.ToString(), j)); hash.ToString(), j));
} }
@ -880,8 +871,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// transactions would not be paid for. // transactions would not be paid for.
if (nModifiedFees < nConflictingFees) if (nModifiedFees < nConflictingFees)
{ {
return state.DoS(0, false, return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s", strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees))); hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)));
} }
@ -891,8 +881,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
CAmount nDeltaFees = nModifiedFees - nConflictingFees; CAmount nDeltaFees = nModifiedFees - nConflictingFees;
if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize)) if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
{ {
return state.DoS(0, false, return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
REJECT_INSUFFICIENTFEE, "insufficient fee", false,
strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s", strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
hash.ToString(), hash.ToString(),
FormatMoney(nDeltaFees), FormatMoney(nDeltaFees),
@ -913,8 +902,10 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) && if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
!CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) { !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
// Only the witness is missing, so the transaction itself may be fine. // Only the witness is missing, so the transaction itself may be fine.
state.SetCorruptionPossible(); state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false,
state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage());
} }
assert(IsTransactionReason(state.GetReason()));
return false; // state filled in by CheckInputs return false; // state filled in by CheckInputs
} }
@ -971,7 +962,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
if (!bypass_limits) { if (!bypass_limits) {
LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60); LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
if (!pool.exists(hash)) if (!pool.exists(hash))
return state.DoS(0, false, REJECT_INSUFFICIENTFEE, "mempool full"); return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full");
} }
} }
@ -1309,7 +1300,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(c
} }
void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) { void CChainState::InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state) {
if (!state.CorruptionPossible()) { if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
m_failed_blocks.insert(pindex); m_failed_blocks.insert(pindex);
setDirtyBlockIndex.insert(pindex); setDirtyBlockIndex.insert(pindex);
@ -1377,6 +1368,9 @@ void InitScriptExecutionCache() {
* which are matched. This is useful for checking blocks where we will likely never need the cache * which are matched. This is useful for checking blocks where we will likely never need the cache
* entry again. * entry again.
* *
* Note that we may set state.reason to NOT_STANDARD for extra soft-fork flags in flags, block-checking
* callers should probably reset it to CONSENSUS in such cases.
*
* Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp * Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp
*/ */
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main) bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
@ -1432,22 +1426,26 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// Check whether the failure was caused by a // Check whether the failure was caused by a
// non-mandatory script verification check, such as // non-mandatory script verification check, such as
// non-standard DER encodings or non-null dummy // non-standard DER encodings or non-null dummy
// arguments; if so, don't trigger DoS protection to // arguments; if so, ensure we return NOT_STANDARD
// avoid splitting the network between upgraded and // instead of CONSENSUS to avoid downstream users
// non-upgraded nodes. // splitting the network between upgraded and
// non-upgraded nodes by banning CONSENSUS-failing
// data providers.
CScriptCheck check2(coin.out, tx, i, CScriptCheck check2(coin.out, tx, i,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata); flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
if (check2()) if (check2())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError()))); return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
} }
// Failures of other flags indicate a transaction that is // MANDATORY flag failures correspond to
// invalid in new blocks, e.g. an invalid P2SH. We DoS ban // ValidationInvalidReason::CONSENSUS. Because CONSENSUS
// such nodes as they are not following the protocol. That // failures are the most serious case of validation
// said during an upgrade careful thought should be taken // failures, we may need to consider using
// as to the correct behavior - we may want to continue // RECENT_CONSENSUS_CHANGE for any script failure that
// peering with non-upgraded nodes even after soft-fork // could be due to non-upgraded nodes which we may want to
// super-majority signaling has occurred. // support, to avoid splitting the network (but this
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError()))); // depends on the details of how net_processing handles
// such errors).
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
} }
} }
@ -1807,7 +1805,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
// re-enforce that rule here (at least until we make it impossible for // re-enforce that rule here (at least until we make it impossible for
// GetAdjustedTime() to go backward). // GetAdjustedTime() to go backward).
if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) { if (!CheckBlock(block, state, chainparams.GetConsensus(), !fJustCheck, !fJustCheck)) {
if (state.CorruptionPossible()) { if (state.GetReason() == ValidationInvalidReason::BLOCK_MUTATED) {
// We don't write down blocks to disk if they may have been // We don't write down blocks to disk if they may have been
// corrupted, so this should be impossible unless we're having hardware // corrupted, so this should be impossible unless we're having hardware
// problems. // problems.
@ -1942,7 +1940,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
for (const auto& tx : block.vtx) { for (const auto& tx : block.vtx) {
for (size_t o = 0; o < tx->vout.size(); o++) { for (size_t o = 0; o < tx->vout.size(); o++) {
if (view.HaveCoin(COutPoint(tx->GetHash(), o))) { if (view.HaveCoin(COutPoint(tx->GetHash(), o))) {
return state.DoS(100, error("ConnectBlock(): tried to overwrite transaction"), return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): tried to overwrite transaction"),
REJECT_INVALID, "bad-txns-BIP30"); REJECT_INVALID, "bad-txns-BIP30");
} }
} }
@ -1982,11 +1980,19 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
{ {
CAmount txfee = 0; CAmount txfee = 0;
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) { if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee)) {
if (!IsBlockReason(state.GetReason())) {
// CheckTxInputs may return MISSING_INPUTS or
// PREMATURE_SPEND but we can't return that, as it's not
// defined for a block, so we reset the reason flag to
// CONSENSUS here.
state.Invalid(ValidationInvalidReason::CONSENSUS, false,
state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage());
}
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state)); return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
} }
nFees += txfee; nFees += txfee;
if (!MoneyRange(nFees)) { if (!MoneyRange(nFees)) {
return state.DoS(100, error("%s: accumulated fee in the block out of range.", __func__), return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: accumulated fee in the block out of range.", __func__),
REJECT_INVALID, "bad-txns-accumulated-fee-outofrange"); REJECT_INVALID, "bad-txns-accumulated-fee-outofrange");
} }
@ -1999,7 +2005,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
} }
if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) { if (!SequenceLocks(tx, nLockTimeFlags, &prevheights, *pindex)) {
return state.DoS(100, error("%s: contains a non-BIP68-final transaction", __func__), return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: contains a non-BIP68-final transaction", __func__),
REJECT_INVALID, "bad-txns-nonfinal"); REJECT_INVALID, "bad-txns-nonfinal");
} }
} }
@ -2010,7 +2016,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
// * witness (when witness enabled in flags and excludes coinbase) // * witness (when witness enabled in flags and excludes coinbase)
nSigOpsCost += GetTransactionSigOpCost(tx, view, flags); nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);
if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST)
return state.DoS(100, error("ConnectBlock(): too many sigops"), return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops"); REJECT_INVALID, "bad-blk-sigops");
txdata.emplace_back(tx); txdata.emplace_back(tx);
@ -2018,9 +2024,20 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
{ {
std::vector<CScriptCheck> vChecks; std::vector<CScriptCheck> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) {
if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) {
// CheckInputs may return NOT_STANDARD for extra flags we passed,
// but we can't return that, as it's not defined for a block, so
// we reset the reason flag to CONSENSUS here.
// In the event of a future soft-fork, we may need to
// consider whether rewriting to CONSENSUS or
// RECENT_CONSENSUS_CHANGE would be more appropriate.
state.Invalid(ValidationInvalidReason::CONSENSUS, false,
state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage());
}
return error("ConnectBlock(): CheckInputs on %s failed with %s", return error("ConnectBlock(): CheckInputs on %s failed with %s",
tx.GetHash().ToString(), FormatStateMessage(state)); tx.GetHash().ToString(), FormatStateMessage(state));
}
control.Add(vChecks); control.Add(vChecks);
} }
@ -2035,13 +2052,13 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()); CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus());
if (block.vtx[0]->GetValueOut() > blockReward) if (block.vtx[0]->GetValueOut() > blockReward)
return state.DoS(100, return state.Invalid(ValidationInvalidReason::CONSENSUS,
error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)", error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)",
block.vtx[0]->GetValueOut(), blockReward), block.vtx[0]->GetValueOut(), blockReward),
REJECT_INVALID, "bad-cb-amount"); REJECT_INVALID, "bad-cb-amount");
if (!control.Wait()) if (!control.Wait())
return state.DoS(100, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed"); return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed");
int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2; int64_t nTime4 = GetTimeMicros(); nTimeVerify += nTime4 - nTime2;
LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal); LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1, MILLI * (nTime4 - nTime2), nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs-1), nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal);
@ -2569,7 +2586,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar
if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) { if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) {
if (state.IsInvalid()) { if (state.IsInvalid()) {
// The block violates a consensus rule. // The block violates a consensus rule.
if (!state.CorruptionPossible()) { if (state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) {
InvalidChainFound(vpindexToConnect.front()); InvalidChainFound(vpindexToConnect.front());
} }
state = CValidationState(); state = CValidationState();
@ -3067,7 +3084,7 @@ static bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state,
{ {
// Check proof of work matches claimed amount // Check proof of work matches claimed amount
if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) if (fCheckPOW && !CheckProofOfWork(block.GetHash(), block.nBits, consensusParams))
return state.DoS(50, false, REJECT_INVALID, "high-hash", false, "proof of work failed"); return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "high-hash", "proof of work failed");
return true; return true;
} }
@ -3089,13 +3106,13 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P
bool mutated; bool mutated;
uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated);
if (block.hashMerkleRoot != hashMerkleRoot2) if (block.hashMerkleRoot != hashMerkleRoot2)
return state.DoS(100, false, REJECT_INVALID, "bad-txnmrklroot", true, "hashMerkleRoot mismatch"); return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txnmrklroot", "hashMerkleRoot mismatch");
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences // Check for merkle tree malleability (CVE-2012-2459): repeating sequences
// of transactions in a block without affecting the merkle root of a block, // of transactions in a block without affecting the merkle root of a block,
// while still invalidating it. // while still invalidating it.
if (mutated) if (mutated)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-duplicate", true, "duplicate transaction"); return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-txns-duplicate", "duplicate transaction");
} }
// All potential-corruption validation must be done before we do any // All potential-corruption validation must be done before we do any
@ -3106,19 +3123,19 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P
// Size limits // Size limits
if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT) if (block.vtx.empty() || block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT || ::GetSerializeSize(block, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false, "size limits failed"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-length", "size limits failed");
// First transaction must be coinbase, the rest must not be // First transaction must be coinbase, the rest must not be
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) if (block.vtx.empty() || !block.vtx[0]->IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "bad-cb-missing", false, "first tx is not coinbase"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-missing", "first tx is not coinbase");
for (unsigned int i = 1; i < block.vtx.size(); i++) for (unsigned int i = 1; i < block.vtx.size(); i++)
if (block.vtx[i]->IsCoinBase()) if (block.vtx[i]->IsCoinBase())
return state.DoS(100, false, REJECT_INVALID, "bad-cb-multiple", false, "more than one coinbase"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase");
// Check transactions // Check transactions
for (const auto& tx : block.vtx) for (const auto& tx : block.vtx)
if (!CheckTransaction(*tx, state, true)) if (!CheckTransaction(*tx, state, true))
return state.Invalid(false, state.GetRejectCode(), state.GetRejectReason(), return state.Invalid(state.GetReason(), false, state.GetRejectCode(), state.GetRejectReason(),
strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage())); strprintf("Transaction check failed (tx hash %s) %s", tx->GetHash().ToString(), state.GetDebugMessage()));
unsigned int nSigOps = 0; unsigned int nSigOps = 0;
@ -3127,7 +3144,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P
nSigOps += GetLegacySigOpCount(*tx); nSigOps += GetLegacySigOpCount(*tx);
} }
if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST) if (nSigOps * WITNESS_SCALE_FACTOR > MAX_BLOCK_SIGOPS_COST)
return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops", false, "out-of-bounds SigOpCount"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-sigops", "out-of-bounds SigOpCount");
if (fCheckPOW && fCheckMerkleRoot) if (fCheckPOW && fCheckMerkleRoot)
block.fChecked = true; block.fChecked = true;
@ -3236,7 +3253,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta
// Check proof of work // Check proof of work
const Consensus::Params& consensusParams = params.GetConsensus(); const Consensus::Params& consensusParams = params.GetConsensus();
if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams))
return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false, "incorrect proof of work"); return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "bad-diffbits", "incorrect proof of work");
// Check against checkpoints // Check against checkpoints
if (fCheckpointsEnabled) { if (fCheckpointsEnabled) {
@ -3245,23 +3262,23 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta
// MapBlockIndex. // MapBlockIndex.
CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints()); CBlockIndex* pcheckpoint = GetLastCheckpoint(params.Checkpoints());
if (pcheckpoint && nHeight < pcheckpoint->nHeight) if (pcheckpoint && nHeight < pcheckpoint->nHeight)
return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint"); return state.Invalid(ValidationInvalidReason::BLOCK_CHECKPOINT, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight), REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint");
} }
// Check timestamp against prev // Check timestamp against prev
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast())
return state.Invalid(false, REJECT_INVALID, "time-too-old", "block's timestamp is too early"); return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_INVALID, "time-too-old", "block's timestamp is too early");
// Check timestamp // Check timestamp
if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME)
return state.Invalid(false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future"); return state.Invalid(ValidationInvalidReason::BLOCK_TIME_FUTURE, false, REJECT_INVALID, "time-too-new", "block timestamp too far in the future");
// Reject outdated version blocks when 95% (75% on testnet) of the network has upgraded: // Reject outdated version blocks when 95% (75% on testnet) of the network has upgraded:
// check for version 2, 3 and 4 upgrades // check for version 2, 3 and 4 upgrades
if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) || if((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) ||
(block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) || (block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) ||
(block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) (block.nVersion < 4 && nHeight >= consensusParams.BIP65Height))
return state.Invalid(false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion), return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_HEADER, false, REJECT_OBSOLETE, strprintf("bad-version(0x%08x)", block.nVersion),
strprintf("rejected nVersion=0x%08x block", block.nVersion)); strprintf("rejected nVersion=0x%08x block", block.nVersion));
return true; return true;
@ -3291,7 +3308,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c
// Check that all transactions are finalized // Check that all transactions are finalized
for (const auto& tx : block.vtx) { for (const auto& tx : block.vtx) {
if (!IsFinalTx(*tx, nHeight, nLockTimeCutoff)) { if (!IsFinalTx(*tx, nHeight, nLockTimeCutoff)) {
return state.DoS(10, false, REJECT_INVALID, "bad-txns-nonfinal", false, "non-final transaction"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-nonfinal", "non-final transaction");
} }
} }
@ -3301,7 +3318,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c
CScript expect = CScript() << nHeight; CScript expect = CScript() << nHeight;
if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() || if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() ||
!std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) { !std::equal(expect.begin(), expect.end(), block.vtx[0]->vin[0].scriptSig.begin())) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-height", false, "block height mismatch in coinbase"); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-height", "block height mismatch in coinbase");
} }
} }
@ -3323,11 +3340,11 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c
// already does not permit it, it is impossible to trigger in the // already does not permit it, it is impossible to trigger in the
// witness tree. // witness tree.
if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) { if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) {
return state.DoS(100, false, REJECT_INVALID, "bad-witness-nonce-size", true, strprintf("%s : invalid witness reserved value size", __func__)); return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__));
} }
CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin()); CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->vin[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin());
if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) {
return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__)); return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__));
} }
fHaveWitness = true; fHaveWitness = true;
} }
@ -3337,7 +3354,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c
if (!fHaveWitness) { if (!fHaveWitness) {
for (const auto& tx : block.vtx) { for (const auto& tx : block.vtx) {
if (tx->HasWitness()) { if (tx->HasWitness()) {
return state.DoS(100, false, REJECT_INVALID, "unexpected-witness", true, strprintf("%s : unexpected witness data found", __func__)); return state.Invalid(ValidationInvalidReason::BLOCK_MUTATED, false, REJECT_INVALID, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__));
} }
} }
} }
@ -3349,7 +3366,7 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c
// the block hash, so we couldn't mark the block as permanently // the block hash, so we couldn't mark the block as permanently
// failed). // failed).
if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) { if (GetBlockWeight(block) > MAX_BLOCK_WEIGHT) {
return state.DoS(100, false, REJECT_INVALID, "bad-blk-weight", false, strprintf("%s : weight limit failed", __func__)); return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-blk-weight", strprintf("%s : weight limit failed", __func__));
} }
return true; return true;
@ -3369,7 +3386,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState&
if (ppindex) if (ppindex)
*ppindex = pindex; *ppindex = pindex;
if (pindex->nStatus & BLOCK_FAILED_MASK) if (pindex->nStatus & BLOCK_FAILED_MASK)
return state.Invalid(error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate"); return state.Invalid(ValidationInvalidReason::CACHED_INVALID, error("%s: block %s is marked invalid", __func__, hash.ToString()), 0, "duplicate");
return true; return true;
} }
@ -3380,10 +3397,10 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState&
CBlockIndex* pindexPrev = nullptr; CBlockIndex* pindexPrev = nullptr;
BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
if (mi == mapBlockIndex.end()) if (mi == mapBlockIndex.end())
return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found"); return state.Invalid(ValidationInvalidReason::BLOCK_MISSING_PREV, error("%s: prev block not found", __func__), 0, "prev-blk-not-found");
pindexPrev = (*mi).second; pindexPrev = (*mi).second;
if (pindexPrev->nStatus & BLOCK_FAILED_MASK) if (pindexPrev->nStatus & BLOCK_FAILED_MASK)
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime()))
return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state));
@ -3420,7 +3437,7 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState&
setDirtyBlockIndex.insert(invalid_walk); setDirtyBlockIndex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev; invalid_walk = invalid_walk->pprev;
} }
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk"); return state.Invalid(ValidationInvalidReason::BLOCK_INVALID_PREV, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
} }
} }
} }
@ -3524,7 +3541,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CVali
if (!CheckBlock(block, state, chainparams.GetConsensus()) || if (!CheckBlock(block, state, chainparams.GetConsensus()) ||
!ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) { !ContextualCheckBlock(block, state, chainparams.GetConsensus(), pindex->pprev)) {
if (state.IsInvalid() && !state.CorruptionPossible()) { assert(IsBlockReason(state.GetReason()));
if (state.IsInvalid() && state.GetReason() != ValidationInvalidReason::BLOCK_MUTATED) {
pindex->nStatus |= BLOCK_FAILED_VALID; pindex->nStatus |= BLOCK_FAILED_VALID;
setDirtyBlockIndex.insert(pindex); setDirtyBlockIndex.insert(pindex);
} }

View file

@ -58,7 +58,7 @@ class BadTxTemplate:
class OutputMissing(BadTxTemplate): class OutputMissing(BadTxTemplate):
reject_reason = "bad-txns-vout-empty" reject_reason = "bad-txns-vout-empty"
expect_disconnect = False expect_disconnect = True
def get_tx(self): def get_tx(self):
tx = CTransaction() tx = CTransaction()
@ -69,7 +69,7 @@ class OutputMissing(BadTxTemplate):
class InputMissing(BadTxTemplate): class InputMissing(BadTxTemplate):
reject_reason = "bad-txns-vin-empty" reject_reason = "bad-txns-vin-empty"
expect_disconnect = False expect_disconnect = True
# We use a blank transaction here to make sure # We use a blank transaction here to make sure
# it is interpreted as a non-witness transaction. # it is interpreted as a non-witness transaction.

View file

@ -281,7 +281,7 @@ class FullBlockTest(BitcoinTestFramework):
self.log.info("Reject a block spending an immature coinbase.") self.log.info("Reject a block spending an immature coinbase.")
self.move_tip(15) self.move_tip(15)
b20 = self.next_block(20, spend=out[7]) b20 = self.next_block(20, spend=out[7])
self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase', reconnect=True)
# Attempt to spend a coinbase at depth too low (on a fork this time) # Attempt to spend a coinbase at depth too low (on a fork this time)
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
@ -294,7 +294,7 @@ class FullBlockTest(BitcoinTestFramework):
self.send_blocks([b21], False) self.send_blocks([b21], False)
b22 = self.next_block(22, spend=out[5]) b22 = self.next_block(22, spend=out[5])
self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase', reconnect=True)
# Create a block on either side of MAX_BLOCK_BASE_SIZE and make sure its accepted/rejected # Create a block on either side of MAX_BLOCK_BASE_SIZE and make sure its accepted/rejected
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
@ -616,7 +616,7 @@ class FullBlockTest(BitcoinTestFramework):
while b47.sha256 < target: while b47.sha256 < target:
b47.nNonce += 1 b47.nNonce += 1
b47.rehash() b47.rehash()
self.send_blocks([b47], False, force_send=True, reject_reason='high-hash') self.send_blocks([b47], False, force_send=True, reject_reason='high-hash', reconnect=True)
self.log.info("Reject a block with a timestamp >2 hours in the future") self.log.info("Reject a block with a timestamp >2 hours in the future")
self.move_tip(44) self.move_tip(44)
@ -667,7 +667,7 @@ class FullBlockTest(BitcoinTestFramework):
b54 = self.next_block(54, spend=out[15]) b54 = self.next_block(54, spend=out[15])
b54.nTime = b35.nTime - 1 b54.nTime = b35.nTime - 1
b54.solve() b54.solve()
self.send_blocks([b54], False, force_send=True, reject_reason='time-too-old') self.send_blocks([b54], False, force_send=True, reject_reason='time-too-old', reconnect=True)
# valid timestamp # valid timestamp
self.move_tip(53) self.move_tip(53)
@ -813,7 +813,7 @@ class FullBlockTest(BitcoinTestFramework):
assert tx.vin[0].nSequence < 0xffffffff assert tx.vin[0].nSequence < 0xffffffff
tx.calc_sha256() tx.calc_sha256()
b62 = self.update_block(62, [tx]) b62 = self.update_block(62, [tx])
self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal') self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal', reconnect=True)
# Test a non-final coinbase is also rejected # Test a non-final coinbase is also rejected
# #
@ -827,7 +827,7 @@ class FullBlockTest(BitcoinTestFramework):
b63.vtx[0].vin[0].nSequence = 0xDEADBEEF b63.vtx[0].vin[0].nSequence = 0xDEADBEEF
b63.vtx[0].rehash() b63.vtx[0].rehash()
b63 = self.update_block(63, []) b63 = self.update_block(63, [])
self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal') self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal', reconnect=True)
# This checks that a block with a bloated VARINT between the block_header and the array of tx such that # This checks that a block with a bloated VARINT between the block_header and the array of tx such that
# the block is > MAX_BLOCK_BASE_SIZE with the bloated varint, but <= MAX_BLOCK_BASE_SIZE without the bloated varint, # the block is > MAX_BLOCK_BASE_SIZE with the bloated varint, but <= MAX_BLOCK_BASE_SIZE without the bloated varint,
@ -1241,7 +1241,7 @@ class FullBlockTest(BitcoinTestFramework):
self.log.info("Reject a block with an invalid block header version") self.log.info("Reject a block with an invalid block header version")
b_v1 = self.next_block('b_v1', version=1) b_v1 = self.next_block('b_v1', version=1)
self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)') self.send_blocks([b_v1], success=False, force_send=True, reject_reason='bad-version(0x00000001)', reconnect=True)
self.move_tip(chain1_tip + 2) self.move_tip(chain1_tip + 2)
b_cb34 = self.next_block('b_cb34', version=4) b_cb34 = self.next_block('b_cb34', version=4)