diff --git a/src/consensus/consensus.h b/src/consensus/consensus.h index 8650c453a..1efaf99ea 100644 --- a/src/consensus/consensus.h +++ b/src/consensus/consensus.h @@ -10,10 +10,14 @@ static const int32_t MIN_BLOCK_VERSION = 4; /** The minimum allowed transaction version (network rule) */ static const int32_t SPROUT_MIN_TX_VERSION = 1; -/** The minimum allowed transaction version (network rule) */ +/** The minimum allowed Overwinter transaction version (network rule) */ static const int32_t OVERWINTER_MIN_TX_VERSION = 3; -/** The maximum allowed transaction version (network rule) */ +/** The maximum allowed Overwinter transaction version (network rule) */ static const int32_t OVERWINTER_MAX_TX_VERSION = 3; +/** The minimum allowed Sapling transaction version (network rule) */ +static const int32_t SAPLING_MIN_TX_VERSION = 4; +/** The maximum allowed Sapling transaction version (network rule) */ +static const int32_t SAPLING_MAX_TX_VERSION = 4; /** The maximum allowed size for a serialized block, in bytes (network rule) */ static const unsigned int MAX_BLOCK_SIZE = 2000000; /** The maximum allowed number of signature check operations in a block (network rule) */ diff --git a/src/gtest/test_checkblock.cpp b/src/gtest/test_checkblock.cpp index 53c0efcc7..225f8ef3b 100644 --- a/src/gtest/test_checkblock.cpp +++ b/src/gtest/test_checkblock.cpp @@ -110,6 +110,36 @@ TEST(ContextualCheckBlock, BadCoinbaseHeight) { EXPECT_TRUE(ContextualCheckBlock(block, state, &indexPrev)); } + +// Test that a block evaluated under Sprout rules cannot contain Sapling transactions. +// This test assumes that mainnet Overwinter activation is at least height 2. +TEST(ContextualCheckBlock, BlockSproutRulesRejectSaplingTx) { + SelectParams(CBaseChainParams::MAIN); + + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.SetNull(); + mtx.vin[0].scriptSig = CScript() << 1 << OP_0; + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey = CScript() << OP_TRUE; + mtx.vout[0].nValue = 0; + + mtx.fOverwintered = true; + mtx.nVersion = SAPLING_TX_VERSION; + mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; + + CTransaction tx {mtx}; + CBlock block; + block.vtx.push_back(tx); + + MockCValidationState state; + CBlockIndex indexPrev {Params().GenesisBlock()}; + + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1); + EXPECT_FALSE(ContextualCheckBlock(block, state, &indexPrev)); +} + + // Test that a block evaluated under Sprout rules cannot contain Overwinter transactions. // This test assumes that mainnet Overwinter activation is at least height 2. TEST(ContextualCheckBlock, BlockSproutRulesRejectOverwinterTx) { @@ -170,6 +200,41 @@ TEST(ContextualCheckBlock, BlockSproutRulesAcceptSproutTx) { } +// Test that a block evaluated under Overwinter rules cannot contain Sapling transactions. +TEST(ContextualCheckBlock, BlockOverwinterRulesRejectSaplingTx) { + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, 1); + + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.SetNull(); + mtx.vin[0].scriptSig = CScript() << 1 << OP_0; + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey = CScript() << OP_TRUE; + mtx.vout[0].nValue = 0; + mtx.vout.push_back(CTxOut( + GetBlockSubsidy(1, Params().GetConsensus())/5, + Params().GetFoundersRewardScriptAtHeight(1))); + + mtx.fOverwintered = true; + mtx.nVersion = SAPLING_TX_VERSION; + mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; + + CTransaction tx {mtx}; + CBlock block; + block.vtx.push_back(tx); + + MockCValidationState state; + CBlockIndex indexPrev {Params().GenesisBlock()}; + + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-overwinter-tx-version-group-id", false)).Times(1); + EXPECT_FALSE(ContextualCheckBlock(block, state, &indexPrev)); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} + + // Test block evaluated under Overwinter rules will accept Overwinter transactions TEST(ContextualCheckBlock, BlockOverwinterRulesAcceptOverwinterTx) { SelectParams(CBaseChainParams::REGTEST); @@ -202,7 +267,6 @@ TEST(ContextualCheckBlock, BlockOverwinterRulesAcceptOverwinterTx) { } - // Test block evaluated under Overwinter rules will reject Sprout transactions TEST(ContextualCheckBlock, BlockOverwinterRulesRejectSproutTx) { SelectParams(CBaseChainParams::REGTEST); @@ -230,4 +294,108 @@ TEST(ContextualCheckBlock, BlockOverwinterRulesRejectSproutTx) { // Revert to default UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); -} \ No newline at end of file +} + + +// Test that a block evaluated under Sapling rules can contain Sapling transactions. +TEST(ContextualCheckBlock, BlockSaplingRulesAcceptSaplingTx) { + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, 1); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, 1); + + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.SetNull(); + mtx.vin[0].scriptSig = CScript() << 1 << OP_0; + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey = CScript() << OP_TRUE; + mtx.vout[0].nValue = 0; + mtx.vout.push_back(CTxOut( + GetBlockSubsidy(1, Params().GetConsensus())/5, + Params().GetFoundersRewardScriptAtHeight(1))); + + mtx.fOverwintered = true; + mtx.nVersion = SAPLING_TX_VERSION; + mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; + + CTransaction tx {mtx}; + CBlock block; + block.vtx.push_back(tx); + + MockCValidationState state; + CBlockIndex indexPrev {Params().GenesisBlock()}; + + EXPECT_TRUE(ContextualCheckBlock(block, state, &indexPrev)); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} + + +// Test block evaluated under Sapling rules cannot contain Overwinter transactions +TEST(ContextualCheckBlock, BlockSaplingRulesRejectOverwinterTx) { + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, 1); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, 1); + + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.SetNull(); + mtx.vin[0].scriptSig = CScript() << 1 << OP_0; + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey = CScript() << OP_TRUE; + mtx.vout[0].nValue = 0; + mtx.vout.push_back(CTxOut( + GetBlockSubsidy(1, Params().GetConsensus())/5, + Params().GetFoundersRewardScriptAtHeight(1))); + mtx.fOverwintered = true; + mtx.nVersion = 3; + mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; + + CTransaction tx {mtx}; + CBlock block; + block.vtx.push_back(tx); + MockCValidationState state; + CBlockIndex indexPrev {Params().GenesisBlock()}; + + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-sapling-tx-version-group-id", false)).Times(1); + EXPECT_FALSE(ContextualCheckBlock(block, state, &indexPrev)); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} + + + +// Test block evaluated under Sapling rules cannot contain Sprout transactions +TEST(ContextualCheckBlock, BlockSaplingRulesRejectSproutTx) { + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, 1); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, 1); + + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].prevout.SetNull(); + mtx.vin[0].scriptSig = CScript() << 1 << OP_0; + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey = CScript() << OP_TRUE; + mtx.vout[0].nValue = 0; + + mtx.nVersion = 2; + + CTransaction tx {mtx}; + CBlock block; + block.vtx.push_back(tx); + + MockCValidationState state; + CBlockIndex indexPrev {Params().GenesisBlock()}; + + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-active", false)).Times(1); + EXPECT_FALSE(ContextualCheckBlock(block, state, &indexPrev)); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} diff --git a/src/main.cpp b/src/main.cpp index f6d3f3992..9db267be6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -866,8 +866,9 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in */ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, const int nHeight, const int dosLevel) { - bool isOverwinter = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); - bool isSprout = !isOverwinter; + bool overwinterActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); + bool saplingActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING); + bool isSprout = !overwinterActive; // If Sprout rules apply, reject transactions which are intended for Overwinter and beyond if (isSprout && tx.fOverwintered) { @@ -875,20 +876,52 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, REJECT_INVALID, "tx-overwinter-not-active"); } - // If Overwinter rules apply: - if (isOverwinter) { + if (saplingActive) { + // Reject transactions with valid version but missing overwintered flag + if (tx.nVersion >= SAPLING_MIN_TX_VERSION && !tx.fOverwintered) { + return state.DoS(dosLevel, error("ContextualCheckTransaction(): overwintered flag must be set"), + REJECT_INVALID, "tx-overwintered-flag-not-set"); + } + + // Reject transactions with non-Sapling version group ID + if (tx.fOverwintered && tx.nVersionGroupId != SAPLING_VERSION_GROUP_ID) { + return state.DoS(dosLevel, error("CheckTransaction(): invalid Sapling tx version"), + REJECT_INVALID, "bad-sapling-tx-version-group-id"); + } + + // Reject transactions with invalid version + if (tx.fOverwintered && tx.nVersion < SAPLING_MIN_TX_VERSION ) { + return state.DoS(100, error("CheckTransaction(): Sapling version too low"), + REJECT_INVALID, "bad-tx-sapling-version-too-low"); + } + + // Reject transactions with invalid version + if (tx.fOverwintered && tx.nVersion > SAPLING_MAX_TX_VERSION ) { + return state.DoS(100, error("CheckTransaction(): Sapling version too high"), + REJECT_INVALID, "bad-tx-sapling-version-too-high"); + } + } else if (overwinterActive) { // Reject transactions with valid version but missing overwinter flag if (tx.nVersion >= OVERWINTER_MIN_TX_VERSION && !tx.fOverwintered) { return state.DoS(dosLevel, error("ContextualCheckTransaction(): overwinter flag must be set"), REJECT_INVALID, "tx-overwinter-flag-not-set"); } + // Reject transactions with non-Overwinter version group ID + if (tx.fOverwintered && tx.nVersionGroupId != OVERWINTER_VERSION_GROUP_ID) { + return state.DoS(dosLevel, 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(100, 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(dosLevel, error("ContextualCheckTransaction: overwinter is active"), @@ -988,7 +1021,8 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio return state.DoS(100, error("CheckTransaction(): overwinter version too low"), REJECT_INVALID, "bad-tx-overwinter-version-too-low"); } - if (tx.nVersionGroupId != OVERWINTER_VERSION_GROUP_ID) { + if (tx.nVersionGroupId != OVERWINTER_VERSION_GROUP_ID && + tx.nVersionGroupId != SAPLING_VERSION_GROUP_ID) { return state.DoS(100, error("CheckTransaction(): unknown tx version group id"), REJECT_INVALID, "bad-tx-version-group-id"); } diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 683b13543..1ec961e1c 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -308,6 +308,10 @@ public: static constexpr uint32_t OVERWINTER_VERSION_GROUP_ID = 0x03C48270; static_assert(OVERWINTER_VERSION_GROUP_ID != 0, "version group id must be non-zero as specified in ZIP 202"); +// Sapling version group id +static constexpr uint32_t SAPLING_VERSION_GROUP_ID = 0x892F2085; +static_assert(SAPLING_VERSION_GROUP_ID != 0, "version group id must be non-zero as specified in ZIP 202"); + struct CMutableTransaction; /** The basic transaction that is broadcasted on the network and contained in