#include "chainparams.h"
#include "checkpoints.h"
#include "checkqueue.h"
+#include "consensus/upgrades.h"
#include "consensus/validation.h"
#include "deprecation.h"
#include "init.h"
uint64_t nPruneTarget = 0;
bool fAlerts = DEFAULT_ALERTS;
+unsigned int expiryDelta = DEFAULT_TX_EXPIRY_DELTA;
+
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
CBlockIndex* pindex = (*mi).second;
if (chain.Contains(pindex))
return pindex;
+ if (pindex->GetAncestor(chain.Height()) == chain.Tip()) {
+ return chain.Tip();
+ }
}
}
return chain.Genesis();
}
-
-
-
-
-
-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)
return true;
}
+bool IsExpiredTx(const CTransaction &tx, int nBlockHeight)
+{
+ if (tx.nExpiryHeight == 0 || tx.IsCoinBase()) {
+ return false;
+ }
+ return static_cast<uint32_t>(nBlockHeight) > tx.nExpiryHeight;
+}
+
bool CheckFinalTx(const CTransaction &tx, int flags)
{
AssertLockHeld(cs_main);
* 2. P2SH scripts with a crazy number of expensive
* CHECKSIG/CHECKMULTISIG operations
*/
-bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
+bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, uint32_t consensusBranchId)
{
if (tx.IsCoinBase())
return true; // Coinbases don't use vin normally
// 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(), consensusBranchId))
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");
+ }
+
+ // Check that all transactions are unexpired
+ if (IsExpiredTx(tx, nHeight)) {
+ return state.DoS(dosLevel, error("ContextualCheckTransaction(): transaction is expired"), REJECT_INVALID, "tx-overwinter-expired");
+ }
+ }
+
+ if (!(tx.IsCoinBase() || tx.vjoinsplit.empty())) {
+ auto consensusBranchId = CurrentEpochBranchId(nHeight, Params().GetConsensus());
+ // Empty output script.
+ CScript scriptCode;
+ uint256 dataToBeSigned;
+ try {
+ dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
+ } catch (std::logic_error ex) {
+ return state.DoS(100, error("CheckTransaction(): error computing signature hash"),
+ REJECT_INVALID, "error-computing-signature-hash");
+ }
+
+ BOOST_STATIC_ASSERT(crypto_sign_PUBLICKEYBYTES == 32);
+
+ // We rely on libsodium to check that the signature is canonical.
+ // https://github.com/jedisct1/libsodium/commit/62911edb7ff2275cccd74bf1c8aefcc4d76924e0
+ if (crypto_sign_verify_detached(&tx.joinSplitSig[0],
+ dataToBeSigned.begin(), 32,
+ tx.joinSplitPubKey.begin()
+ ) != 0) {
+ return state.DoS(100, error("CheckTransaction(): invalid joinsplit signature"),
+ REJECT_INVALID, "bad-txns-invalid-joinsplit-signature");
+ }
+ }
+ 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.
if (txin.prevout.IsNull())
return state.DoS(10, error("CheckTransaction(): prevout is null"),
REJECT_INVALID, "bad-txns-prevout-null");
-
- if (tx.vjoinsplit.size() > 0) {
- // Empty output script.
- CScript scriptCode;
- uint256 dataToBeSigned;
- try {
- dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL);
- } catch (std::logic_error ex) {
- return state.DoS(100, error("CheckTransaction(): error computing signature hash"),
- REJECT_INVALID, "error-computing-signature-hash");
- }
-
- BOOST_STATIC_ASSERT(crypto_sign_PUBLICKEYBYTES == 32);
-
- // We rely on libsodium to check that the signature is canonical.
- // https://github.com/jedisct1/libsodium/commit/62911edb7ff2275cccd74bf1c8aefcc4d76924e0
- if (crypto_sign_verify_detached(&tx.joinSplitSig[0],
- dataToBeSigned.begin(), 32,
- tx.joinSplitPubKey.begin()
- ) != 0) {
- return state.DoS(100, error("CheckTransaction(): invalid joinsplit signature"),
- REJECT_INVALID, "bad-txns-invalid-joinsplit-signature");
- }
- }
}
return true;
if (pfMissingInputs)
*pfMissingInputs = false;
+ int nextBlockHeight = chainActive.Height() + 1;
+ auto consensusBranchId = CurrentEpochBranchId(nextBlockHeight, Params().GetConsensus());
+
// Node operator can choose to reject tx by number of transparent inputs
static_assert(std::numeric_limits<size_t>::max() >= std::numeric_limits<int64_t>::max(), "size_t too small");
size_t limit = (size_t) GetArg("-mempooltxinputlimit", 0);
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.
+ 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);
}
// Check for non-standard pay-to-script-hash in inputs
- if (Params().RequireStandard() && !AreInputsStandard(tx, view))
+ if (Params().RequireStandard() && !AreInputsStandard(tx, view, consensusBranchId))
return error("AcceptToMemoryPool: nonstandard transaction input");
// Check that the transaction doesn't have an excessive number of
CAmount nFees = nValueIn-nValueOut;
double dPriority = view.GetPriority(tx, chainActive.Height());
- CTxMemPoolEntry entry(tx, nFees, GetTime(), dPriority, chainActive.Height(), mempool.HasNoInputsOf(tx));
+ // Keep track of transactions that spend a coinbase, which we re-scan
+ // during reorgs to ensure COINBASE_MATURITY is still met.
+ bool fSpendsCoinbase = false;
+ BOOST_FOREACH(const CTxIn &txin, tx.vin) {
+ const CCoins *coins = view.AccessCoins(txin.prevout.hash);
+ if (coins->IsCoinBase()) {
+ fSpendsCoinbase = true;
+ break;
+ }
+ }
+
+ // 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(), consensusBranchId))
{
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(), consensusBranchId))
{
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), consensusBranchId, &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,
+ uint32_t consensusBranchId,
+ 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, consensusBranchId, &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, consensusBranchId, &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);
}
+ // Grab the consensus branch ID for the block's height
+ auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, Params().GetConsensus());
+
+ 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(), consensusBranchId, 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) {
pindex->nStatus |= BLOCK_HAVE_UNDO;
}
+ // Now that all consensus rules have been validated, set nCachedBranchId.
+ // Move this if BLOCK_VALID_CONSENSUS is ever altered.
+ static_assert(BLOCK_VALID_CONSENSUS == BLOCK_VALID_SCRIPTS,
+ "nCachedBranchId must be set after all consensus rules have been validated.");
+ if (IsActivationHeightForAnyUpgrade(pindex->nHeight, Params().GetConsensus())) {
+ pindex->nStatus |= BLOCK_ACTIVATES_UPGRADE;
+ pindex->nCachedBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus());
+ } else if (pindex->pprev) {
+ pindex->nCachedBranchId = pindex->pprev->nCachedBranchId;
+ }
+
pindex->RaiseValidity(BLOCK_VALID_SCRIPTS);
setDirtyBlockIndex.insert(pindex);
}
}
}
-/** Disconnect chainActive's tip. */
-bool static DisconnectTip(CValidationState &state) {
+/**
+ * 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);
- mempool.check(pcoinsTip);
// Read block from disk.
CBlock block;
if (!ReadBlockFromDisk(block, pindexDelete))
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
return false;
- // Resurrect mempool transactions from the disconnected block.
- BOOST_FOREACH(const CTransaction &tx, block.vtx) {
- // ignore validation errors in resurrected transactions
- list<CTransaction> removed;
- CValidationState stateDummy;
- if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
- mempool.remove(tx, removed, true);
- }
- if (anchorBeforeDisconnect != anchorAfterDisconnect) {
- // The anchor may not change between block disconnects,
- // in which case we don't want to evict from the mempool yet!
- mempool.removeWithAnchor(anchorBeforeDisconnect);
- }
- mempool.removeCoinbaseSpends(pcoinsTip, pindexDelete->nHeight);
- mempool.check(pcoinsTip);
+
+ if (!fBare) {
+ // Resurrect mempool transactions from the disconnected block.
+ BOOST_FOREACH(const CTransaction &tx, block.vtx) {
+ // ignore validation errors in resurrected transactions
+ list<CTransaction> removed;
+ CValidationState stateDummy;
+ if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
+ mempool.remove(tx, removed, true);
+ }
+ if (anchorBeforeDisconnect != anchorAfterDisconnect) {
+ // The anchor may not change between block disconnects,
+ // in which case we don't want to evict from the mempool yet!
+ mempool.removeWithAnchor(anchorBeforeDisconnect);
+ }
+ }
+
// Update chainActive and related variables.
UpdateTip(pindexDelete->pprev);
// Get the current commitment tree
/**
* 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());
- mempool.check(pcoinsTip);
// Read block from disk.
int64_t nTime1 = GetTimeMicros();
CBlock block;
// Remove conflicting transactions from the mempool.
list<CTransaction> txConflicted;
mempool.removeForBlock(pblock->vtx, pindexNew->nHeight, txConflicted, !IsInitialBlockDownload());
- mempool.check(pcoinsTip);
+
+ // Remove transactions that expire at new block height from mempool
+ mempool.removeExpired(pindexNew->nHeight);
+
// Update chainActive & related variables.
UpdateTip(pindexNew);
// Tell wallet about transactions that went from mempool
const CBlockIndex *pindexOldTip = chainActive.Tip();
const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork);
+ // - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks.
+ // - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain,
+ // then pindexFork will be null, and we would need to remove the entire chain including
+ // our genesis block. In practice this (probably) won't happen because of checks elsewhere.
+ auto reorgLength = pindexOldTip ? pindexOldTip->nHeight - (pindexFork ? pindexFork->nHeight : -1) : 0;
+ static_assert(MAX_REORG_LENGTH > 0, "We must be able to reorg some distance");
+ if (reorgLength > MAX_REORG_LENGTH) {
+ auto msg = strprintf(_(
+ "A block chain reorganization has been detected that would roll back %d blocks! "
+ "This is larger than the maximum of %d blocks, and so the node is shutting down for your safety."
+ ), reorgLength, MAX_REORG_LENGTH) + "\n\n" +
+ _("Reorganization details") + ":\n" +
+ "- " + strprintf(_("Current tip: %s, height %d, work %s"),
+ pindexOldTip->phashBlock->GetHex(), pindexOldTip->nHeight, pindexOldTip->nChainWork.GetHex()) + "\n" +
+ "- " + strprintf(_("New tip: %s, height %d, work %s"),
+ pindexMostWork->phashBlock->GetHex(), pindexMostWork->nHeight, pindexMostWork->nChainWork.GetHex()) + "\n" +
+ "- " + strprintf(_("Fork point: %s, height %d"),
+ pindexFork->phashBlock->GetHex(), pindexFork->nHeight) + "\n\n" +
+ _("Please help, human!");
+ LogPrintf("*** %s\n", msg);
+ uiInterface.ThreadSafeMessageBox(msg, "", CClientUIInterface::MSG_ERROR);
+ StartShutdown();
+ return false;
+ }
+
// Disconnect active blocks which are no longer in the best chain.
+ bool fBlocksDisconnected = false;
while (chainActive.Tip() && chainActive.Tip() != pindexFork) {
if (!DisconnectTip(state))
return false;
+ fBlocksDisconnected = true;
}
// Build list of new blocks to connect.
bool fContinue = true;
int nHeight = pindexFork ? pindexFork->nHeight : -1;
while (fContinue && nHeight != pindexMostWork->nHeight) {
- // Don't iterate the entire list of potential improvements toward the best tip, as we likely only need
- // a few blocks along the way.
- int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
- vpindexToConnect.clear();
- vpindexToConnect.reserve(nTargetHeight - nHeight);
- CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
- while (pindexIter && pindexIter->nHeight != nHeight) {
- vpindexToConnect.push_back(pindexIter);
- pindexIter = pindexIter->pprev;
- }
- nHeight = nTargetHeight;
-
- // Connect new blocks.
- BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
- if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
- if (state.IsInvalid()) {
- // The block violates a consensus rule.
- if (!state.CorruptionPossible())
- InvalidChainFound(vpindexToConnect.back());
- state = CValidationState();
- fInvalidFound = true;
- fContinue = false;
- break;
+ // Don't iterate the entire list of potential improvements toward the best tip, as we likely only need
+ // a few blocks along the way.
+ int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
+ vpindexToConnect.clear();
+ vpindexToConnect.reserve(nTargetHeight - nHeight);
+ CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
+ while (pindexIter && pindexIter->nHeight != nHeight) {
+ vpindexToConnect.push_back(pindexIter);
+ pindexIter = pindexIter->pprev;
+ }
+ nHeight = nTargetHeight;
+
+ // Connect new blocks.
+ BOOST_REVERSE_FOREACH(CBlockIndex *pindexConnect, vpindexToConnect) {
+ if (!ConnectTip(state, pindexConnect, pindexConnect == pindexMostWork ? pblock : NULL)) {
+ if (state.IsInvalid()) {
+ // The block violates a consensus rule.
+ if (!state.CorruptionPossible())
+ InvalidChainFound(vpindexToConnect.back());
+ state = CValidationState();
+ fInvalidFound = true;
+ fContinue = false;
+ break;
+ } else {
+ // A system error occurred (disk space, database error, ...).
+ return false;
+ }
} else {
- // A system error occurred (disk space, database error, ...).
- return false;
- }
- } else {
- PruneBlockIndexCandidates();
- if (!pindexOldTip || chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) {
- // We're in a better position than we were. Return temporarily to release the lock.
- fContinue = false;
- break;
+ PruneBlockIndexCandidates();
+ if (!pindexOldTip || chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) {
+ // We're in a better position than we were. Return temporarily to release the lock.
+ fContinue = false;
+ break;
+ }
}
}
}
+
+ if (fBlocksDisconnected) {
+ 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.
if (fInvalidFound)
// 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, STANDARD_LOCKTIME_VERIFY_FLAGS);
+ mempool.removeWithoutBranchId(
+ CurrentEpochBranchId(chainActive.Tip()->nHeight + 1, Params().GetConsensus()));
return false;
}
}
}
InvalidChainFound(pindex);
+ mempool.removeForReorg(pcoinsTip, chainActive.Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
+ mempool.removeWithoutBranchId(
+ CurrentEpochBranchId(chainActive.Tip()->nHeight + 1, Params().GetConsensus()));
return true;
}
{
pindexNew->nTx = block.vtx.size();
pindexNew->nChainTx = 0;
+ CAmount sproutValue = 0;
+ for (auto tx : block.vtx) {
+ for (auto js : tx.vjoinsplit) {
+ sproutValue += js.vpub_old;
+ sproutValue -= js.vpub_new;
+ }
+ }
+ pindexNew->nSproutValue = sproutValue;
+ pindexNew->nChainSproutValue = boost::none;
pindexNew->nFile = pos.nFile;
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
+ if (pindex->pprev) {
+ if (pindex->pprev->nChainSproutValue && pindex->nSproutValue) {
+ pindex->nChainSproutValue = *pindex->pprev->nChainSproutValue + *pindex->nSproutValue;
+ } else {
+ pindex->nChainSproutValue = boost::none;
+ }
+ } else {
+ pindex->nChainSproutValue = pindex->nSproutValue;
+ }
{
LOCK(cs_nBlockSequenceId);
pindex->nSequenceId = nBlockSequenceId++;
// 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()
if (pindex->pprev) {
if (pindex->pprev->nChainTx) {
pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
+ if (pindex->pprev->nChainSproutValue && pindex->nSproutValue) {
+ pindex->nChainSproutValue = *pindex->pprev->nChainSproutValue + *pindex->nSproutValue;
+ } else {
+ pindex->nChainSproutValue = boost::none;
+ }
} else {
pindex->nChainTx = 0;
+ pindex->nChainSproutValue = boost::none;
mapBlocksUnlinked.insert(std::make_pair(pindex->pprev, pindex));
}
} else {
pindex->nChainTx = pindex->nTx;
+ pindex->nChainSproutValue = pindex->nSproutValue;
+ }
+ }
+ // Construct in-memory chain of branch IDs.
+ // Relies on invariant: a block that does not activate a network upgrade
+ // will always be valid under the same consensus rules as its parent.
+ // Genesis block has a branch ID of zero by definition, but has no
+ // validity status because it is side-loaded into a fresh chain.
+ // Activation blocks will have branch IDs set (read from disk).
+ if (pindex->pprev) {
+ if (pindex->IsValid(BLOCK_VALID_CONSENSUS) && !pindex->nCachedBranchId) {
+ pindex->nCachedBranchId = pindex->pprev->nCachedBranchId;
}
+ } else {
+ pindex->nCachedBranchId = SPROUT_BRANCH_ID;
}
if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == NULL))
setBlockIndexCandidates.insert(pindex);
return true;
}
+bool RewindBlockIndex(const CChainParams& params)
+{
+ LOCK(cs_main);
+
+ // RewindBlockIndex is called after LoadBlockIndex, so at this point every block
+ // index will have nCachedBranchId set based on the values previously persisted
+ // to disk. By definition, a set nCachedBranchId means that the block was
+ // fully-validated under the corresponding consensus rules. Thus we can quickly
+ // identify whether the current active chain matches our expected sequence of
+ // consensus rule changes, with two checks:
+ //
+ // - BLOCK_ACTIVATES_UPGRADE is set only on blocks that activate upgrades.
+ // - nCachedBranchId for each block matches what we expect.
+ auto sufficientlyValidated = [¶ms](const CBlockIndex* pindex) {
+ auto consensus = params.GetConsensus();
+ bool fFlagSet = pindex->nStatus & BLOCK_ACTIVATES_UPGRADE;
+ bool fFlagExpected = IsActivationHeightForAnyUpgrade(pindex->nHeight, consensus);
+ return fFlagSet == fFlagExpected &&
+ pindex->nCachedBranchId &&
+ *pindex->nCachedBranchId == CurrentEpochBranchId(pindex->nHeight, consensus);
+ };
+
+ int nHeight = 1;
+ while (nHeight <= chainActive.Height()) {
+ if (!sufficientlyValidated(chainActive[nHeight])) {
+ break;
+ }
+ nHeight++;
+ }
+
+ // nHeight is now the height of the first insufficiently-validated block, or tipheight + 1
+ auto rewindLength = chainActive.Height() - nHeight;
+ if (rewindLength > 0 && rewindLength > MAX_REORG_LENGTH) {
+ auto pindexOldTip = chainActive.Tip();
+ auto pindexRewind = chainActive[nHeight - 1];
+ auto msg = strprintf(_(
+ "A block chain rewind has been detected that would roll back %d blocks! "
+ "This is larger than the maximum of %d blocks, and so the node is shutting down for your safety."
+ ), rewindLength, MAX_REORG_LENGTH) + "\n\n" +
+ _("Rewind details") + ":\n" +
+ "- " + strprintf(_("Current tip: %s, height %d"),
+ pindexOldTip->phashBlock->GetHex(), pindexOldTip->nHeight) + "\n" +
+ "- " + strprintf(_("Rewinding to: %s, height %d"),
+ pindexRewind->phashBlock->GetHex(), pindexRewind->nHeight) + "\n\n" +
+ _("Please help, human!");
+ LogPrintf("*** %s\n", msg);
+ uiInterface.ThreadSafeMessageBox(msg, "", CClientUIInterface::MSG_ERROR);
+ StartShutdown();
+ return false;
+ }
+
+ CValidationState state;
+ CBlockIndex* pindex = chainActive.Tip();
+ while (chainActive.Height() >= nHeight) {
+ if (fPruneMode && !(chainActive.Tip()->nStatus & BLOCK_HAVE_DATA)) {
+ // If pruning, don't try rewinding past the HAVE_DATA point;
+ // since older blocks can't be served anyway, there's
+ // no need to walk further, and trying to DisconnectTip()
+ // will fail (and require a needless reindex/redownload
+ // of the blockchain).
+ break;
+ }
+ if (!DisconnectTip(state, true)) {
+ return error("RewindBlockIndex: unable to disconnect block at height %i", pindex->nHeight);
+ }
+ // Occasionally flush state to disk.
+ if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC))
+ return false;
+ }
+
+ // Reduce validity flag and have-data flags.
+ // We do this after actual disconnecting, otherwise we'll end up writing the lack of data
+ // to disk before writing the chainstate, resulting in a failure to continue if interrupted.
+ for (BlockMap::iterator it = mapBlockIndex.begin(); it != mapBlockIndex.end(); it++) {
+ CBlockIndex* pindexIter = it->second;
+
+ // Note: If we encounter an insufficiently validated block that
+ // is on chainActive, it must be because we are a pruning node, and
+ // this block or some successor doesn't HAVE_DATA, so we were unable to
+ // rewind all the way. Blocks remaining on chainActive at this point
+ // must not have their validity reduced.
+ if (!sufficientlyValidated(pindexIter) && !chainActive.Contains(pindexIter)) {
+ // Reduce validity
+ pindexIter->nStatus =
+ std::min<unsigned int>(pindexIter->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) |
+ (pindexIter->nStatus & ~BLOCK_VALID_MASK);
+ // Remove have-data flags
+ pindexIter->nStatus &= ~(BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO);
+ // Remove branch ID
+ pindexIter->nStatus &= ~BLOCK_ACTIVATES_UPGRADE;
+ pindexIter->nCachedBranchId = boost::none;
+ // Remove storage location
+ pindexIter->nFile = 0;
+ pindexIter->nDataPos = 0;
+ pindexIter->nUndoPos = 0;
+ // Remove various other things
+ pindexIter->nTx = 0;
+ pindexIter->nChainTx = 0;
+ pindexIter->nSproutValue = boost::none;
+ pindexIter->nChainSproutValue = boost::none;
+ pindexIter->nSequenceId = 0;
+ // Make sure it gets written
+ setDirtyBlockIndex.insert(pindexIter);
+ // Update indices
+ setBlockIndexCandidates.erase(pindexIter);
+ auto ret = mapBlocksUnlinked.equal_range(pindexIter->pprev);
+ while (ret.first != ret.second) {
+ if (ret.first->second == pindexIter) {
+ mapBlocksUnlinked.erase(ret.first++);
+ } else {
+ ++ret.first;
+ }
+ }
+ } else if (pindexIter->IsValid(BLOCK_VALID_TRANSACTIONS) && pindexIter->nChainTx) {
+ setBlockIndexCandidates.insert(pindexIter);
+ }
+ }
+
+ PruneBlockIndexCandidates();
+
+ CheckBlockIndex();
+
+ if (!FlushStateToDisk(state, FLUSH_STATE_ALWAYS)) {
+ return false;
+ }
+
+ return true;
+}
+
void UnloadBlockIndex()
{
LOCK(cs_main);
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;
+}