Auto merge of #4542 - nuttycom:reorder_checks, r=nuttycom

Organize transaction version checks.

Some minor cleanup for ContextualCheckTransaction, in service of making the checks here easier to reason about.
This commit is contained in:
Homu 2020-08-26 02:09:06 +00:00
commit d3acf75745
4 changed files with 98 additions and 98 deletions

View File

@ -311,7 +311,7 @@ TEST_F(ContextualCheckBlockTest, BlockOverwinterRulesRejectOtherTx) {
{ {
SCOPED_TRACE("BlockOverwinterRulesRejectSproutTx"); SCOPED_TRACE("BlockOverwinterRulesRejectSproutTx");
ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwinter-active"); ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwintered-flag-not-set");
} }
// Make it a Sapling transaction // Make it a Sapling transaction
@ -321,7 +321,7 @@ TEST_F(ContextualCheckBlockTest, BlockOverwinterRulesRejectOtherTx) {
{ {
SCOPED_TRACE("BlockOverwinterRulesRejectSaplingTx"); SCOPED_TRACE("BlockOverwinterRulesRejectSaplingTx");
ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "bad-overwinter-tx-version-group-id"); ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "bad-tx-overwinter-version-too-high");
} }
} }
@ -339,7 +339,7 @@ TEST_F(ContextualCheckBlockTest, BlockSaplingRulesRejectOtherTx) {
{ {
SCOPED_TRACE("BlockSaplingRulesRejectSproutTx"); SCOPED_TRACE("BlockSaplingRulesRejectSproutTx");
ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwinter-active"); ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwintered-flag-not-set");
} }
// Make it an Overwinter transaction // Make it an Overwinter transaction
@ -368,7 +368,7 @@ TEST_F(ContextualCheckBlockTest, BlockBlossomRulesRejectOtherTx) {
{ {
SCOPED_TRACE("BlockBlossomRulesRejectSproutTx"); SCOPED_TRACE("BlockBlossomRulesRejectSproutTx");
ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwinter-active"); ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwintered-flag-not-set");
} }
// Make it an Overwinter transaction // Make it an Overwinter transaction

View File

@ -71,7 +71,7 @@ CMutableTransaction GetValidTransaction(uint32_t consensusBranchId=SPROUT_BRANCH
mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002"); mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002");
mtx.vin[1].prevout.n = 0; mtx.vin[1].prevout.n = 0;
mtx.vout.resize(2); mtx.vout.resize(2);
// mtx.vout[0].scriptPubKey = // mtx.vout[0].scriptPubKey =
mtx.vout[0].nValue = 0; mtx.vout[0].nValue = 0;
mtx.vout[1].nValue = 0; mtx.vout[1].nValue = 0;
mtx.vJoinSplit.resize(2); mtx.vJoinSplit.resize(2);
@ -947,7 +947,7 @@ TEST(ChecktransactionTests, OverwinterFlagNotSet) {
CTransaction tx(mtx); CTransaction tx(mtx);
MockCValidationState state; MockCValidationState state;
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-flag-not-set", false)).Times(1); EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwintered-flag-not-set", false)).Times(1);
ContextualCheckTransaction(tx, state, Params(), 1, true); ContextualCheckTransaction(tx, state, Params(), 1, true);
// Revert to default // Revert to default

View File

@ -167,7 +167,7 @@ TEST(Mempool, SproutV3TxWhenOverwinterActive) {
LOCK(cs_main); LOCK(cs_main);
EXPECT_FALSE(AcceptToMemoryPool(pool, state1, tx1, false, &missingInputs)); EXPECT_FALSE(AcceptToMemoryPool(pool, state1, tx1, false, &missingInputs));
EXPECT_EQ(state1.GetRejectReason(), "tx-overwinter-flag-not-set"); EXPECT_EQ(state1.GetRejectReason(), "tx-overwintered-flag-not-set");
// Revert to default // Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);

View File

@ -761,7 +761,7 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
/** /**
* Check a transaction contextually against a set of consensus rules valid at a given block height. * Check a transaction contextually against a set of consensus rules valid at a given block height.
* *
* Notes: * Notes:
* 1. AcceptToMemoryPool calls CheckTransaction and this function. * 1. AcceptToMemoryPool calls CheckTransaction and this function.
* 2. ProcessNewBlock calls AcceptBlock, which calls CheckBlock (which calls CheckTransaction) * 2. ProcessNewBlock calls AcceptBlock, which calls CheckBlock (which calls CheckTransaction)
@ -792,29 +792,73 @@ bool ContextualCheckTransaction(
bool overwinterActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_OVERWINTER); bool overwinterActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_OVERWINTER);
bool saplingActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_SAPLING); bool saplingActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_SAPLING);
bool isSprout = !overwinterActive; bool beforeOverwinter = !overwinterActive;
bool heartwoodActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_HEARTWOOD); bool heartwoodActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_HEARTWOOD);
bool canopyActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_CANOPY); bool canopyActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_CANOPY);
// If Sprout rules apply, reject transactions which are intended for Overwinter and beyond assert(!saplingActive || overwinterActive); // Sapling cannot be active unless Overwinter is
if (isSprout && tx.fOverwintered) { assert(!heartwoodActive || saplingActive); // Heartwood cannot be active unless Sapling is
return state.DoS( assert(!canopyActive || heartwoodActive); // Canopy cannot be active unless Heartwood is
dosLevelPotentiallyRelaxing,
error("ContextualCheckTransaction(): overwinter is not active yet"), // Rules that apply only to Sprout
REJECT_INVALID, "tx-overwinter-not-active"); if (beforeOverwinter) {
// Reject transactions which are intended for Overwinter and beyond
if (tx.fOverwintered) {
return state.DoS(
dosLevelPotentiallyRelaxing,
error("ContextualCheckTransaction(): overwinter is not active yet"),
REJECT_INVALID, "tx-overwinter-not-active");
}
} }
if (saplingActive) { // Rules that apply to Overwinter and later:
// Reject transactions with valid version but missing overwintered flag if (overwinterActive) {
if (tx.nVersion >= SAPLING_MIN_TX_VERSION && !tx.fOverwintered) { // Reject transactions intended for Sprout
if (!tx.fOverwintered) {
return state.DoS( return state.DoS(
dosLevelConstricting, dosLevelConstricting,
error("ContextualCheckTransaction(): overwintered flag must be set"), error("ContextualCheckTransaction: fOverwintered flag must be set when Overwinter is active"),
REJECT_INVALID, "tx-overwintered-flag-not-set"); REJECT_INVALID, "tx-overwintered-flag-not-set");
} }
// The condition `if (tx.nVersion < OVERWINTER_MIN_TX_VERSION)`
// that would otherwise fire here is already performed in the
// noncontextual checks in CheckTransactionWithoutProofVerification
// Check that all transactions are unexpired
if (IsExpiredTx(tx, nHeight)) {
// Don't increase banscore if the transaction only just expired
int expiredDosLevel = IsExpiredTx(tx, nHeight - 1) ? dosLevelConstricting : 0;
return state.DoS(
expiredDosLevel,
error("ContextualCheckTransaction(): transaction is expired. Resending when caught up with the blockchain, or manually setting the zcashd txexpirydelta parameter may help."),
REJECT_INVALID, "tx-overwinter-expired");
}
// Rules that became inactive after Sapling activation.
if (!saplingActive) {
// Reject transactions with invalid version
if (tx.nVersion > OVERWINTER_MAX_TX_VERSION ) {
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): overwinter version too high"),
REJECT_INVALID, "bad-tx-overwinter-version-too-high");
}
// Reject transactions with non-Overwinter version group ID
if (tx.nVersionGroupId != OVERWINTER_VERSION_GROUP_ID) {
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): invalid Overwinter tx version"),
REJECT_INVALID, "bad-overwinter-tx-version-group-id");
}
}
}
// Rules that apply to Sapling and later:
if (saplingActive) {
// Reject transactions with non-Sapling version group ID // Reject transactions with non-Sapling version group ID
if (tx.fOverwintered && tx.nVersionGroupId != SAPLING_VERSION_GROUP_ID) { if (tx.nVersionGroupId != SAPLING_VERSION_GROUP_ID) {
return state.DoS( return state.DoS(
dosLevelPotentiallyRelaxing, dosLevelPotentiallyRelaxing,
error("CheckTransaction(): invalid Sapling tx version"), error("CheckTransaction(): invalid Sapling tx version"),
@ -822,7 +866,7 @@ bool ContextualCheckTransaction(
} }
// Reject transactions with invalid version // Reject transactions with invalid version
if (tx.fOverwintered && tx.nVersion < SAPLING_MIN_TX_VERSION ) { if (tx.nVersion < SAPLING_MIN_TX_VERSION) {
return state.DoS( return state.DoS(
dosLevelConstricting, dosLevelConstricting,
error("CheckTransaction(): Sapling version too low"), error("CheckTransaction(): Sapling version too low"),
@ -830,62 +874,18 @@ bool ContextualCheckTransaction(
} }
// Reject transactions with invalid version // Reject transactions with invalid version
if (tx.fOverwintered && tx.nVersion > SAPLING_MAX_TX_VERSION ) { if (tx.nVersion > SAPLING_MAX_TX_VERSION) {
return state.DoS( return state.DoS(
dosLevelPotentiallyRelaxing, dosLevelPotentiallyRelaxing,
error("CheckTransaction(): Sapling version too high"), error("CheckTransaction(): Sapling version too high"),
REJECT_INVALID, "bad-tx-sapling-version-too-high"); REJECT_INVALID, "bad-tx-sapling-version-too-high");
} }
} else if (overwinterActive) { } else {
// Reject transactions with valid version but missing overwinter flag // Rules that apply generally before Sapling. These were
if (tx.nVersion >= OVERWINTER_MIN_TX_VERSION && !tx.fOverwintered) { // previously noncontextual checks that became contextual
return state.DoS( // after Sapling activation.
dosLevelConstricting,
error("ContextualCheckTransaction(): overwinter flag must be set"),
REJECT_INVALID, "tx-overwinter-flag-not-set");
}
// Reject transactions with non-Overwinter version group ID // Reject transactions that exceed pre-sapling size limits
if (tx.fOverwintered && tx.nVersionGroupId != OVERWINTER_VERSION_GROUP_ID) {
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): invalid Overwinter tx version"),
REJECT_INVALID, "bad-overwinter-tx-version-group-id");
}
// Reject transactions with invalid version
if (tx.fOverwintered && tx.nVersion > OVERWINTER_MAX_TX_VERSION ) {
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): overwinter version too high"),
REJECT_INVALID, "bad-tx-overwinter-version-too-high");
}
}
// Rules that apply to Overwinter or later:
if (overwinterActive) {
// Reject transactions intended for Sprout
if (!tx.fOverwintered) {
return state.DoS(
dosLevelConstricting,
error("ContextualCheckTransaction: overwinter is active"),
REJECT_INVALID, "tx-overwinter-active");
}
// Check that all transactions are unexpired
if (IsExpiredTx(tx, nHeight)) {
// Don't increase banscore if the transaction only just expired
int expiredDosLevel = IsExpiredTx(tx, nHeight - 1) ? dosLevelConstricting : 0;
return state.DoS(
expiredDosLevel,
error("ContextualCheckTransaction(): transaction is expired. Resending when caught up with the blockchain, or manually setting the zcashd txexpirydelta parameter may help."),
REJECT_INVALID, "tx-overwinter-expired");
}
}
// Rules that apply before Sapling:
if (!saplingActive) {
// Size limits
BOOST_STATIC_ASSERT(MAX_BLOCK_SIZE > MAX_TX_SIZE_BEFORE_SAPLING); // sanity BOOST_STATIC_ASSERT(MAX_BLOCK_SIZE > MAX_TX_SIZE_BEFORE_SAPLING); // sanity
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_TX_SIZE_BEFORE_SAPLING) if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_TX_SIZE_BEFORE_SAPLING)
return state.DoS( return state.DoS(
@ -894,31 +894,16 @@ bool ContextualCheckTransaction(
REJECT_INVALID, "bad-txns-oversize"); REJECT_INVALID, "bad-txns-oversize");
} }
// Rules that apply before Heartwood:
if (!heartwoodActive) {
if (tx.IsCoinBase()) {
// A coinbase transaction cannot have output descriptions
if (tx.vShieldedOutput.size() > 0)
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): coinbase has output descriptions"),
REJECT_INVALID, "bad-cb-has-output-description");
}
}
// From Canopy onward, coinbase transaction must include outputs corresponding to the // From Canopy onward, coinbase transaction must include outputs corresponding to the
// ZIP 207 consensus funding streams active at the current block height. To avoid // ZIP 207 consensus funding streams active at the current block height. To avoid
// double-decrypting, we detect any shielded funding streams during the Heartwood // double-decrypting, we detect any shielded funding streams during the Heartwood
// consensus check. If Canopy is not yet active, fundingStreamElements will be empty. // consensus check. If Canopy is not yet active, fundingStreamElements will be empty.
std::set<Consensus::FundingStreamElement> fundingStreamElements; std::set<Consensus::FundingStreamElement> fundingStreamElements = Consensus::GetActiveFundingStreamElements(
if (canopyActive) { nHeight,
fundingStreamElements = Consensus::GetActiveFundingStreamElements( GetBlockSubsidy(nHeight, chainparams.GetConsensus()),
nHeight, chainparams.GetConsensus());
GetBlockSubsidy(nHeight, chainparams.GetConsensus()),
chainparams.GetConsensus());
}
// Rules that apply to Heartwood or later: // Rules that apply to Heartwood and later:
if (heartwoodActive) { if (heartwoodActive) {
if (tx.IsCoinBase()) { if (tx.IsCoinBase()) {
// All Sapling outputs in coinbase transactions MUST have valid note commitments // All Sapling outputs in coinbase transactions MUST have valid note commitments
@ -932,8 +917,7 @@ bool ContextualCheckTransaction(
return state.DoS( return state.DoS(
DOS_LEVEL_BLOCK, DOS_LEVEL_BLOCK,
error("CheckTransaction(): coinbase output description has invalid outCiphertext"), error("CheckTransaction(): coinbase output description has invalid outCiphertext"),
REJECT_INVALID, REJECT_INVALID, "bad-cb-output-desc-invalid-outct");
"bad-cb-output-desc-invalid-outct");
} }
// SaplingNotePlaintext::decrypt() checks note commitment validity. // SaplingNotePlaintext::decrypt() checks note commitment validity.
@ -950,8 +934,7 @@ bool ContextualCheckTransaction(
return state.DoS( return state.DoS(
DOS_LEVEL_BLOCK, DOS_LEVEL_BLOCK,
error("CheckTransaction(): coinbase output description has invalid encCiphertext"), error("CheckTransaction(): coinbase output description has invalid encCiphertext"),
REJECT_INVALID, REJECT_INVALID, "bad-cb-output-desc-invalid-encct");
"bad-cb-output-desc-invalid-encct");
} }
// ZIP 207: detect shielded funding stream elements // ZIP 207: detect shielded funding stream elements
@ -980,6 +963,19 @@ bool ContextualCheckTransaction(
} }
} }
} }
} else {
// Rules that apply generally before Heartwood. These were
// previously noncontextual checks that became contextual
// after Heartwood activation.
if (tx.IsCoinBase()) {
// A coinbase transaction cannot have output descriptions
if (tx.vShieldedOutput.size() > 0)
return state.DoS(
dosLevelPotentiallyRelaxing,
error("CheckTransaction(): coinbase has output descriptions"),
REJECT_INVALID, "bad-cb-has-output-description");
}
} }
// Rules that apply to Canopy or later: // Rules that apply to Canopy or later:
@ -1008,6 +1004,10 @@ bool ContextualCheckTransaction(
REJECT_INVALID, "cb-funding-stream-missing"); REJECT_INVALID, "cb-funding-stream-missing");
} }
} }
} else {
// Rules that apply generally before Canopy. These were
// previously noncontextual checks that became contextual
// after Canopy activation.
} }
auto consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus()); auto consensusBranchId = CurrentEpochBranchId(nHeight, chainparams.GetConsensus());
@ -1663,7 +1663,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
{ {
// We lock to prevent other threads from accessing the mempool between adding and evicting // We lock to prevent other threads from accessing the mempool between adding and evicting
LOCK(pool.cs); LOCK(pool.cs);
// Store transaction in memory // Store transaction in memory
pool.addUnchecked(hash, entry, !IsInitialBlockDownload(Params())); pool.addUnchecked(hash, entry, !IsInitialBlockDownload(Params()));
@ -5048,7 +5048,7 @@ bool LoadBlockIndex()
return true; return true;
} }
bool InitBlockIndex(const CChainParams& chainparams) bool InitBlockIndex(const CChainParams& chainparams)
{ {
LOCK(cs_main); LOCK(cs_main);
@ -6913,7 +6913,7 @@ CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Para
mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
mtx.nVersion = OVERWINTER_TX_VERSION; mtx.nVersion = OVERWINTER_TX_VERSION;
} }
bool blossomActive = consensusParams.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_BLOSSOM); bool blossomActive = consensusParams.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_BLOSSOM);
unsigned int defaultExpiryDelta = blossomActive ? DEFAULT_POST_BLOSSOM_TX_EXPIRY_DELTA : DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA; unsigned int defaultExpiryDelta = blossomActive ? DEFAULT_POST_BLOSSOM_TX_EXPIRY_DELTA : DEFAULT_PRE_BLOSSOM_TX_EXPIRY_DELTA;
mtx.nExpiryHeight = nHeight + (expiryDeltaArg ? expiryDeltaArg.get() : defaultExpiryDelta); mtx.nExpiryHeight = nHeight + (expiryDeltaArg ? expiryDeltaArg.get() : defaultExpiryDelta);