}
-
-
-
-
-
-bool IsStandardTx(const CTransaction& tx, string& reason)
+bool IsStandardTx(const CTransaction& tx, string& reason, const int nHeight)
{
- if (tx.nVersion > CTransaction::MAX_CURRENT_VERSION || tx.nVersion < CTransaction::MIN_CURRENT_VERSION) {
- reason = "version";
- return false;
+ bool isOverwinter = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
+
+ if (isOverwinter) {
+ // Overwinter standard rules apply
+ if (tx.nVersion > CTransaction::OVERWINTER_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::OVERWINTER_MIN_CURRENT_VERSION) {
+ reason = "overwinter-version";
+ return false;
+ }
+ } else {
+ // Sprout standard rules apply
+ if (tx.nVersion > CTransaction::SPROUT_MAX_CURRENT_VERSION || tx.nVersion < CTransaction::SPROUT_MIN_CURRENT_VERSION) {
+ reason = "version";
+ return false;
+ }
}
BOOST_FOREACH(const CTxIn& txin, tx.vin)
// IsStandardTx() will have already returned false
// and this method isn't called.
vector<vector<unsigned char> > stack;
- if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker()))
+ if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SIGVERSION_BASE))
return false;
if (whichType == TX_SCRIPTHASH)
return nSigOps;
}
+/**
+ * Check a transaction contextually against a set of consensus rules valid at a given block height.
+ *
+ * Notes:
+ * 1. AcceptToMemoryPool calls CheckTransaction and this function.
+ * 2. ProcessNewBlock calls AcceptBlock, which calls CheckBlock (which calls CheckTransaction)
+ * and ContextualCheckBlock (which calls this function).
+ */
+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;
+
+ // If Sprout rules apply, reject transactions which are intended for Overwinter and beyond
+ if (isSprout && tx.fOverwintered) {
+ return state.DoS(dosLevel, error("ContextualCheckTransaction(): overwinter is not active yet"),
+ REJECT_INVALID, "tx-overwinter-not-active");
+ }
+
+ // If Overwinter rules apply:
+ if (isOverwinter) {
+ // 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 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");
+ }
+
+ // Reject transactions intended for Sprout
+ if (!tx.fOverwintered) {
+ return state.DoS(dosLevel, error("ContextualCheckTransaction: overwinter is active"),
+ REJECT_INVALID, "tx-overwinter-active");
+ }
+ }
+
+ return true;
+}
+
+
bool CheckTransaction(const CTransaction& tx, CValidationState &state,
libzcash::ProofVerifier& verifier)
{
{
// Basic checks that don't depend on any context
- // Check transaction version
- if (tx.nVersion < MIN_TX_VERSION) {
+ /**
+ * Previously:
+ * 1. The consensus rule below was:
+ * if (tx.nVersion < SPROUT_MIN_TX_VERSION) { ... }
+ * which checked if tx.nVersion fell within the range:
+ * INT32_MIN <= tx.nVersion < SPROUT_MIN_TX_VERSION
+ * 2. The parser allowed tx.nVersion to be negative
+ *
+ * Now:
+ * 1. The consensus rule checks to see if tx.Version falls within the range:
+ * 0 <= tx.nVersion < SPROUT_MIN_TX_VERSION
+ * 2. The previous consensus rule checked for negative values within the range:
+ * INT32_MIN <= tx.nVersion < 0
+ * This is unnecessary for Overwinter transactions since the parser now
+ * interprets the sign bit as fOverwintered, so tx.nVersion is always >=0,
+ * and when Overwinter is not active ContextualCheckTransaction rejects
+ * transactions with fOverwintered set. When fOverwintered is set,
+ * this function and ContextualCheckTransaction will together check to
+ * ensure tx.nVersion avoids the following ranges:
+ * 0 <= tx.nVersion < OVERWINTER_MIN_TX_VERSION
+ * OVERWINTER_MAX_TX_VERSION < tx.nVersion <= INT32_MAX
+ */
+ if (!tx.fOverwintered && tx.nVersion < SPROUT_MIN_TX_VERSION) {
return state.DoS(100, error("CheckTransaction(): version too low"),
REJECT_INVALID, "bad-txns-version-too-low");
}
+ else if (tx.fOverwintered) {
+ if (tx.nVersion < OVERWINTER_MIN_TX_VERSION) {
+ 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) {
+ return state.DoS(100, error("CheckTransaction(): unknown tx version group id"),
+ REJECT_INVALID, "bad-tx-version-group-id");
+ }
+ if (tx.nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD) {
+ return state.DoS(100, error("CheckTransaction(): expiry height is too high"),
+ REJECT_INVALID, "bad-tx-expiry-height-too-high");
+ }
+ }
// Transactions can contain empty `vin` and `vout` so long as
// `vjoinsplit` is non-empty.
CScript scriptCode;
uint256 dataToBeSigned;
try {
- dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL);
+ dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, SIGVERSION_BASE);
} catch (std::logic_error ex) {
return state.DoS(100, error("CheckTransaction(): error computing signature hash"),
REJECT_INVALID, "error-computing-signature-hash");
if (!CheckTransaction(tx, state, verifier))
return error("AcceptToMemoryPool: CheckTransaction failed");
+ // DoS level set to 10 to be more forgiving.
+ // Check transaction contextually against the set of consensus rules which apply in the next block to be mined.
+ int nextBlockHeight = chainActive.Height() + 1;
+ if (!ContextualCheckTransaction(tx, state, nextBlockHeight, 10)) {
+ return error("AcceptToMemoryPool: ContextualCheckTransaction failed");
+ }
+
// Coinbase is only valid in a block, not as a loose transaction
if (tx.IsCoinBase())
return state.DoS(100, error("AcceptToMemoryPool: coinbase as individual tx"),
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
string reason;
- if (Params().RequireStandard() && !IsStandardTx(tx, reason))
+ if (Params().RequireStandard() && !IsStandardTx(tx, reason, nextBlockHeight))
return state.DoS(0,
error("AcceptToMemoryPool: nonstandard transaction: %s", reason),
REJECT_NONSTANDARD, reason);
}
}
- CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), mempool.HasNoInputsOf(tx), fSpendsCoinbase);
+ // Grab the branch ID we expect this transaction to commit to. We don't
+ // yet know if it does, but if the entry gets added to the mempool, then
+ // it has passed ContextualCheckInputs and therefore this is correct.
+ auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
+
+ CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), mempool.HasNoInputsOf(tx), fSpendsCoinbase, consensusBranchId);
unsigned int nSize = entry.GetTxSize();
// Accept a tx if it contains joinsplits and has at least the default fee specified by z_sendmany.
// Check against previous transactions
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
- if (!ContextualCheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true, Params().GetConsensus()))
+ PrecomputedTransactionData txdata(tx);
+ if (!ContextualCheckInputs(tx, state, view, true, STANDARD_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus()))
{
return error("AcceptToMemoryPool: ConnectInputs failed %s", hash.ToString());
}
// There is a similar check in CreateNewBlock() to prevent creating
// invalid blocks, however allowing such transactions into the mempool
// can be exploited as a DoS attack.
- if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, Params().GetConsensus()))
+ if (!ContextualCheckInputs(tx, state, view, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus()))
{
return error("AcceptToMemoryPool: BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
}
}
}
-void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, CTxUndo &txundo, int nHeight)
+void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight)
{
// mark inputs spent
if (!tx.IsCoinBase()) {
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
}
-void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCache &inputs, int nHeight)
+void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
{
CTxUndo txundo;
- UpdateCoins(tx, state, inputs, txundo, nHeight);
+ UpdateCoins(tx, inputs, txundo, nHeight);
}
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
- if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, cacheStore), &error)) {
+ if (!VerifyScript(scriptSig, scriptPubKey, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, amount, cacheStore, *txdata), &error)) {
return ::error("CScriptCheck(): %s:%d VerifySignature failed: %s", ptxTo->GetHash().ToString(), nIn, ScriptErrorString(error));
}
return true;
}
}// namespace Consensus
-bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, const Consensus::Params& consensusParams, std::vector<CScriptCheck> *pvChecks)
+bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, const Consensus::Params& consensusParams, std::vector<CScriptCheck> *pvChecks)
{
if (!tx.IsCoinBase())
{
assert(coins);
// Verify signature
- CScriptCheck check(*coins, tx, i, flags, cacheStore);
+ CScriptCheck check(*coins, tx, i, flags, cacheStore, &txdata);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
// arguments; if so, don't trigger DoS protection to
// avoid splitting the network between upgraded and
// non-upgraded nodes.
- CScriptCheck check(*coins, tx, i,
- flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore);
- if (check())
+ CScriptCheck check2(*coins, tx, i,
+ flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheStore, &txdata);
+ if (check2())
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
}
// Failures of other flags indicate a transaction that is
assert(tree.root() == old_tree_root);
}
+ std::vector<PrecomputedTransactionData> txdata;
+ txdata.reserve(block.vtx.size()); // Required so that pointers to individual PrecomputedTransactionData don't get invalidated
for (unsigned int i = 0; i < block.vtx.size(); i++)
{
const CTransaction &tx = block.vtx[i];
if (nSigOps > MAX_BLOCK_SIGOPS)
return state.DoS(100, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops");
+ }
+ txdata.emplace_back(tx);
+
+ if (!tx.IsCoinBase())
+ {
nFees += view.GetValueIn(tx)-tx.GetValueOut();
std::vector<CScriptCheck> vChecks;
- if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, false, chainparams.GetConsensus(), nScriptCheckThreads ? &vChecks : NULL))
+ if (!ContextualCheckInputs(tx, state, view, fExpensiveChecks, flags, false, txdata[i], chainparams.GetConsensus(), nScriptCheckThreads ? &vChecks : NULL))
return false;
control.Add(vChecks);
}
if (i > 0) {
blockundo.vtxundo.push_back(CTxUndo());
}
- UpdateCoins(tx, state, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
+ UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 ¬e_commitment, joinsplit.commitments) {
}
}
-/** Disconnect chainActive's tip. You probably want to call mempool.removeForReorg after this, with cs_main held. */
+/**
+ * Disconnect chainActive's tip. You probably want to call mempool.removeForReorg and
+ * mempool.removeWithoutBranchId after this, with cs_main held.
+ */
bool static DisconnectTip(CValidationState &state, bool fBare = false) {
CBlockIndex *pindexDelete = chainActive.Tip();
assert(pindexDelete);
/**
* Connect a new block to chainActive. pblock is either NULL or a pointer to a CBlock
* corresponding to pindexNew, to bypass loading it again from disk.
+ * You probably want to call mempool.removeWithoutBranchId after this, with cs_main held.
*/
bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *pblock) {
assert(pindexNew->pprev == chainActive.Tip());
// Disconnect active blocks which are no longer in the best chain.
bool fBlocksDisconnected = false;
while (chainActive.Tip() && chainActive.Tip() != pindexFork) {
- if (!DisconnectTip(state)) {
- // Probably an AbortNode() error, but try to keep mempool consistent anyway
- mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1);
+ if (!DisconnectTip(state))
return false;
- }
fBlocksDisconnected = true;
}
break;
} else {
// A system error occurred (disk space, database error, ...).
- // Probably gonna shut down ASAP, but try to keep mempool consistent anyway
- if (fBlocksDisconnected)
- mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1);
return false;
}
} else {
}
if (fBlocksDisconnected) {
- mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1);
+ mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
}
+ mempool.removeWithoutBranchId(
+ CurrentEpochBranchId(chainActive.Tip()->nHeight + 1, Params().GetConsensus()));
mempool.check(pcoinsTip);
// Callbacks/notifications for a new best chain.
// ActivateBestChain considers blocks already in chainActive
// unconditionally valid already, so force disconnect away from it.
if (!DisconnectTip(state)) {
- mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1);
+ mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
+ mempool.removeWithoutBranchId(
+ CurrentEpochBranchId(chainActive.Tip()->nHeight + 1, Params().GetConsensus()));
return false;
}
}
}
InvalidChainFound(pindex);
- mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1);
+ mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
+ mempool.removeWithoutBranchId(
+ CurrentEpochBranchId(chainActive.Tip()->nHeight + 1, Params().GetConsensus()));
return true;
}
// Check that all transactions are finalized
BOOST_FOREACH(const CTransaction& tx, block.vtx) {
+
+ // Check transaction contextually against consensus rules at block height
+ if (!ContextualCheckTransaction(tx, state, nHeight, 100)) {
+ return false; // Failure reason has been set in validation state object
+ }
+
int nLockTimeFlags = 0;
int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST)
? pindexPrev->GetMedianTimePast()
mapOrphanTransactionsByPrev.clear();
}
} instance_of_cmaincleanup;
+
+
+// Set default values of new CMutableTransaction based on consensus rules at given height.
+CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Params& consensusParams, int nHeight)
+{
+ CMutableTransaction mtx;
+
+ bool isOverwintered = NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_OVERWINTER);
+ if (isOverwintered) {
+ mtx.fOverwintered = true;
+ mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID;
+ mtx.nVersion = 3;
+ // Expiry height is not set. Only fields required for a parser to treat as a valid Overwinter V3 tx.
+
+ // TODO: In future, when moving from Overwinter to Sapling, it will be useful
+ // to set the expiry height to: min(activation_height - 1, default_expiry_height)
+ }
+ return mtx;
+}