From df6b4639d2b4090ebcade1bc45d4843aa9e7922c Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 31 Jan 2020 20:57:06 +0000 Subject: [PATCH 01/18] Move check for block times that are too far ahead of adjusted time, to ContextualCheckBlock. Signed-off-by: Daira Hopwood --- src/main.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index bcf652693..1f63e6043 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3692,11 +3692,6 @@ bool CheckBlockHeader( return state.DoS(50, error("CheckBlockHeader(): proof of work failed"), REJECT_INVALID, "high-hash"); - // Check timestamp - if (block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) - return state.Invalid(error("CheckBlockHeader(): block timestamp too far in the future"), - REJECT_INVALID, "time-too-new"); - return true; } @@ -3786,6 +3781,11 @@ bool ContextualCheckBlockHeader( return state.Invalid(error("%s: block's timestamp is too early", __func__), REJECT_INVALID, "time-too-old"); + // Check timestamp + if (block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) + return state.Invalid(error("%s: block timestamp too far in the future", __func__), + REJECT_INVALID, "time-too-new"); + if (fCheckpointsEnabled) { // Don't accept any forks from the main chain prior to last checkpoint From 3a001196b64eaaca92176145928d72c500986248 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 4 Feb 2020 19:15:41 +0000 Subject: [PATCH 02/18] Improve messages for timestamp rules. Signed-off-by: Daira Hopwood --- src/main.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 1f63e6043..ac47a9daf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3777,14 +3777,20 @@ bool ContextualCheckBlockHeader( REJECT_INVALID, "bad-diffbits"); // Check timestamp against prev - if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) - return state.Invalid(error("%s: block's timestamp is too early", __func__), + auto medianTimePast = pindexPrev->GetMedianTimePast(); + if (block.GetBlockTime() <= medianTimePast) { + return state.Invalid(error("%s: block at height %d, timestamp %d is not later than median-time-past %d", + __func__, nHeight, block.GetBlockTime(), medianTimePast), REJECT_INVALID, "time-too-old"); + } // Check timestamp - if (block.GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60) - return state.Invalid(error("%s: block timestamp too far in the future", __func__), + auto nTimeLimit = GetAdjustedTime() + 2 * 60 * 60; + if (block.GetBlockTime() > nTimeLimit) { + return state.Invalid(error("%s: block at height %d. timestamp %d is too far ahead of adjusted time, limit is %d", + __func__, nHeight, block.GetBlockTime(), nTimeLimit), REJECT_INVALID, "time-too-new"); + } if (fCheckpointsEnabled) { From a3bb1966eb80acaa213c9ef5ec75665a0b2f6c62 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 4 Feb 2020 19:46:48 +0000 Subject: [PATCH 03/18] Add constant for how far a block timestamp can be ahead of adjusted time. Loosely based on https://github.com/bitcoin/bitcoin/commit/e57a1fd8999800b3fc744d45bb96354cae294032 Signed-off-by: Daira Hopwood --- src/chain.h | 16 ++++++++++++++++ src/main.cpp | 2 +- src/wallet/rpcdump.cpp | 3 ++- src/wallet/wallet.cpp | 8 +++++--- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/chain.h b/src/chain.h index 7512b06b4..fa5e3e29d 100644 --- a/src/chain.h +++ b/src/chain.h @@ -17,6 +17,22 @@ static const int SPROUT_VALUE_VERSION = 1001400; static const int SAPLING_VALUE_VERSION = 1010100; +/** + * Maximum amount of time that a block timestamp is allowed to be ahead of the + * current network-adjusted time. + */ +static const int64_t MAX_FUTURE_BLOCK_TIME_ADJUSTED = 2 * 60 * 60; + +/** + * Timestamp window used as a grace period by code that compares external + * timestamps (such as timestamps passed to RPCs, or wallet key creation times) + * to block timestamps. + */ +static const int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME_ADJUSTED + 60; + +static_assert(TIMESTAMP_WINDOW > MAX_FUTURE_BLOCK_TIME_ADJUSTED); + + class CBlockFileInfo { public: diff --git a/src/main.cpp b/src/main.cpp index ac47a9daf..a44a4fa0d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3785,7 +3785,7 @@ bool ContextualCheckBlockHeader( } // Check timestamp - auto nTimeLimit = GetAdjustedTime() + 2 * 60 * 60; + auto nTimeLimit = GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME_ADJUSTED; if (block.GetBlockTime() > nTimeLimit) { return state.Invalid(error("%s: block at height %d. timestamp %d is too far ahead of adjusted time, limit is %d", __func__, nHeight, block.GetBlockTime(), nTimeLimit), diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index cad0cb0f5..d59e8aff0 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -448,8 +448,9 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI CBlockIndex *pindex = chainActive.Tip(); - while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200) + while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - TIMESTAMP_WINDOW) { pindex = pindex->pprev; + } if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey) pwalletMain->nTimeFirstKey = nTimeBegin; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fc74f0677..1608c8066 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2523,8 +2523,9 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) // no need to read and scan block, if block was created before // our wallet birthday (as adjusted for block time variability) - while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - 7200))) + while (pindex && nTimeFirstKey && pindex->GetBlockTime() < nTimeFirstKey - TIMESTAMP_WINDOW) { pindex = chainActive.Next(pindex); + } ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false); @@ -4347,8 +4348,9 @@ void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { } // Extract block timestamps for those keys - for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) - mapKeyBirth[it->first] = it->second->GetBlockTime() - 7200; // block times can be 2h off + for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) { + mapKeyBirth[it->first] = it->second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be off + } } bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) From 7704ab48461ea54278dd7b914179a5823b434a6e Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 4 Feb 2020 19:26:08 +0000 Subject: [PATCH 04/18] Soft fork: restrict block timestamps to be no more than 90 minutes after the MTP of the previous block. Note that the MTP of a block is the median timestamp of the preceding 11 blocks, i.e. it is typically (with no or only moderate timestamp manipulation) expected to be 6 block intervals behind that block's timestamp, which *on average* is 450 seconds behind (after Blossom activation). So the effective limit on future dating of timestamps is ~82.5 minutes. This makes it exceptionally unlikely --even taking into account feasible timestamp manipulation of this and previous blocks-- that the chain will stall because no block is found before the limit. (This may rely on assumptions that do not hold for testnet.) If an adversary were to have a sufficient fraction of mining power to engineer this situation then there would be something seriously wrong, and arguably the chain should stall in that case, pending manual intervention. Co-authored-by: Jack Grigg Signed-off-by: Daira Hopwood --- src/chain.h | 7 +++++++ src/main.cpp | 30 +++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/chain.h b/src/chain.h index fa5e3e29d..eabf0ec3c 100644 --- a/src/chain.h +++ b/src/chain.h @@ -17,6 +17,12 @@ static const int SPROUT_VALUE_VERSION = 1001400; static const int SAPLING_VALUE_VERSION = 1010100; +/** + * Maximum amount of time that a block timestamp is allowed to be ahead of the + * median-time-past of the previous block. + */ +static const int64_t MAX_FUTURE_BLOCK_TIME_MTP = 90 * 60; + /** * Maximum amount of time that a block timestamp is allowed to be ahead of the * current network-adjusted time. @@ -30,6 +36,7 @@ static const int64_t MAX_FUTURE_BLOCK_TIME_ADJUSTED = 2 * 60 * 60; */ static const int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME_ADJUSTED + 60; +static_assert(MAX_FUTURE_BLOCK_TIME_ADJUSTED > MAX_FUTURE_BLOCK_TIME_MTP); static_assert(TIMESTAMP_WINDOW > MAX_FUTURE_BLOCK_TIME_ADJUSTED); diff --git a/src/main.cpp b/src/main.cpp index a44a4fa0d..0dc29f73a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3784,10 +3784,38 @@ bool ContextualCheckBlockHeader( REJECT_INVALID, "time-too-old"); } + // Genesis blocks are hard-coded into the binary, and both testnet and + // regtest have now-ancient timestamps, so we need to handle the case + // where we might use the genesis block's timestamp as the median-time-past. + // + // GetMedianTimePast() is implemented such that the chosen block is the + // median of however many blocks we are able to select up to + // nMedianTimeSpan = 11. For example, if nHeight == 6: + // + // ,-= 2 && block.GetBlockTime() > medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP) { + return state.Invalid(error("%s: block at height %d, timestamp %d is too far ahead of median-time-past, limit is %d", + __func__, nHeight, block.GetBlockTime(), medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP), + REJECT_INVALID, "time-too-far-ahead-of-mtp"); + } + // Check timestamp auto nTimeLimit = GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME_ADJUSTED; if (block.GetBlockTime() > nTimeLimit) { - return state.Invalid(error("%s: block at height %d. timestamp %d is too far ahead of adjusted time, limit is %d", + return state.Invalid(error("%s: block at height %d, timestamp %d is too far ahead of adjusted time, limit is %d", __func__, nHeight, block.GetBlockTime(), nTimeLimit), REJECT_INVALID, "time-too-new"); } From bdb985a62791249efba75e56310223f0adba2f26 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Fri, 31 Jan 2020 22:39:49 +0000 Subject: [PATCH 05/18] Adjust the miner to satisfy consensus regarding future timestamps relative to median-time-past. Co-authored-by: Jack Grigg Signed-off-by: Daira Hopwood --- src/miner.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/miner.cpp b/src/miner.cpp index 505195ab3..423330749 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -99,7 +99,13 @@ public: void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) { - pblock->nTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); + auto medianTimePast = pindexPrev->GetMedianTimePast(); + auto nTime = std::max(medianTimePast + 1, GetAdjustedTime()); + // See the comment in ContextualCheckBlockHeader() for background. + if (pindexPrev->nHeight >= 1) { + nTime = std::min(nTime, medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP); + } + pblock->nTime = nTime; // Updating time can change work required on testnet: if (consensusParams.nPowAllowMinDifficultyBlocksAfterHeight != boost::none) { From 5199ecdf41faade25d996c86a48151487d74ee36 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 3 Feb 2020 16:40:30 +0000 Subject: [PATCH 06/18] test: Update RPC test cache generation to handle new consensus rule Co-authored-by: Daira Hopwood --- qa/rpc-tests/test_framework/util.py | 35 +++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py index 11bfc08e1..a2095cd65 100644 --- a/qa/rpc-tests/test_framework/util.py +++ b/qa/rpc-tests/test_framework/util.py @@ -23,6 +23,9 @@ import re from authproxy import AuthServiceProxy +PRE_BLOSSOM_BLOCK_TARGET_SPACING = 150 +POST_BLOSSOM_BLOCK_TARGET_SPACING = 75 + def p2p_port(n): return 11000 + n + os.getpid()%999 def rpc_port(n): @@ -109,6 +112,27 @@ def initialize_chain(test_dir): bitcoind and bitcoin-cli must be in search path. """ + # Due to the consensus change fix for the timejacking attack, we need to + # ensure that the cache is pretty fresh. Specifically, we need the median + # time past of the chain tip of the cache to be no more than 90 minutes + # behind the current local time, or else mined blocks will be rejected by + # all nodes, halting the test. With Sapling active by default, this requires + # the chain tip itself to be no more than 75 minutes behind the current + # local time. + # + # We address this here, by regenerating the cache if it is more than 60 + # minutes old. This gives 15 minutes of slack initially that an RPC test has + # to complete in, if it is started right at the oldest cache time. Within an + # individual test, the first five calls to `generate` will each advance the + # median time past of the chain tip by 2.5 minutes (with Sapling active by + # default). Therefore, if the logic between the completion of any two + # adjacent calls to `generate` within a test takes longer than 2.5 minutes, + # the excess will subtract from the slack. + if os.path.isdir(os.path.join("cache", "node0")): + if os.stat("cache").st_mtime + (60 * 60) < time.time(): + print("initialize_chain(): Removing stale cache") + shutil.rmtree("cache") + if not os.path.isdir(os.path.join("cache", "node0")): devnull = open("/dev/null", "w+") # Create cache directories, run bitcoinds: @@ -140,17 +164,20 @@ def initialize_chain(test_dir): # Create a 200-block-long chain; each of the 4 nodes # gets 25 mature blocks and 25 immature. - # blocks are created with timestamps 10 minutes apart, starting - # at 1 Jan 2014 - block_time = 1388534400 + # Blocks are created with timestamps 2.5 minutes apart (matching the + # chain defaulting above to Sapling active), starting 200 * 2.5 minutes + # before the current time. + block_time = int(time.time()) - (200 * PRE_BLOSSOM_BLOCK_TARGET_SPACING) for i in range(2): for peer in range(4): for j in range(25): set_node_times(rpcs, block_time) rpcs[peer].generate(1) - block_time += 10*60 + block_time += PRE_BLOSSOM_BLOCK_TARGET_SPACING # Must sync before next peer starts generating blocks sync_blocks(rpcs) + # Check that local time isn't going backwards + assert_greater_than(time.time() + 1, block_time) # Shut them down, and clean up cache directories: stop_nodes(rpcs) From 4ab896c69d15bb7dfb9ab7926299a5f48d50ab1c Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 4 Feb 2020 19:56:53 +0000 Subject: [PATCH 07/18] Enable future timestamp soft fork at varying heights according to network. Signed-off-by: Daira Hopwood --- src/chainparams.cpp | 16 +++++++++++++++ src/consensus/params.cpp | 4 ++++ src/consensus/params.h | 43 ++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 29 +++++++-------------------- src/miner.cpp | 2 +- 5 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 2322d8151..301ece19c 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -323,6 +323,22 @@ public: consensus.vUpgrades[Consensus::UPGRADE_HEARTWOOD].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + // On testnet we activate this rule 6 blocks after Blossom activation. From block 299188 and + // prior to Blossom activation, the testnet minimum-difficulty threshold was 15 minutes (i.e. + // a minimum difficulty block can be mined if no block is mined normally within 15 minutes): + // + // However the median-time-past is 6 blocks behind, and the worst-case time for 7 blocks at a + // 15-minute spacing is ~105 minutes, which is exceeds the limit imposed by the soft fork of + // 90 minutes. + // + // After Blossom, the minimum difficulty threshold time is changed to 6 times the block target + // spacing, which is 7.5 minutes: + // + // 7 times that is 52.5 minutes which is well within the limit imposed by the soft fork. + + static_assert(6 * Consensus::POST_BLOSSOM_POW_TARGET_SPACING * 7 < MAX_FUTURE_BLOCK_TIME_MTP - 60); + consensus.nFutureTimestampSoftForkHeight = consensus.vUpgrades[Consensus::UPGRADE_BLOSSOM].nActivationHeight + 6; + // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x0000000000000000000000000000000000000000000000000000001dbb4c4224"); diff --git a/src/consensus/params.cpp b/src/consensus/params.cpp index 1062df349..076866add 100644 --- a/src/consensus/params.cpp +++ b/src/consensus/params.cpp @@ -11,6 +11,10 @@ namespace Consensus { return NetworkUpgradeState(nHeight, *this, idx) == UPGRADE_ACTIVE; } + bool Params::FutureTimestampSoftForkActive(int nHeight) const { + return nHeight >= nFutureTimestampSoftForkHeight; + } + int Params::Halving(int nHeight) const { // zip208 // Halving(height) := diff --git a/src/consensus/params.h b/src/consensus/params.h index ea08b532e..78e5e5c5a 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -96,6 +96,8 @@ struct Params { */ bool NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const; + bool FutureTimestampSoftForkActive(int nHeight) const; + uint256 hashGenesisBlock; bool fCoinbaseMustBeShielded; @@ -126,6 +128,47 @@ struct Params { int nMajorityRejectBlockOutdated; int nMajorityWindow; NetworkUpgrade vUpgrades[MAX_NETWORK_UPGRADES]; + + /** + * Default block height at which the future timestamp soft fork rule activates. + * + * Genesis blocks are hard-coded into the binary for all networks + * (mainnet, testnet, regtest), and have now-ancient timestamps. So we need to + * handle the case where we might use the genesis block's timestamp as the + * median-time-past. + * + * GetMedianTimePast() is implemented such that the chosen block is the + * median of however many blocks we are able to select up to + * nMedianTimeSpan = 11. For example, if nHeight == 6: + * + * ,-= 2 && block.GetBlockTime() > medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP) { + if (consensusParams.FutureTimestampSoftForkActive(nHeight) && + block.GetBlockTime() > medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP) { return state.Invalid(error("%s: block at height %d, timestamp %d is too far ahead of median-time-past, limit is %d", __func__, nHeight, block.GetBlockTime(), medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP), REJECT_INVALID, "time-too-far-ahead-of-mtp"); diff --git a/src/miner.cpp b/src/miner.cpp index 423330749..20918ee68 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -102,7 +102,7 @@ void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, auto medianTimePast = pindexPrev->GetMedianTimePast(); auto nTime = std::max(medianTimePast + 1, GetAdjustedTime()); // See the comment in ContextualCheckBlockHeader() for background. - if (pindexPrev->nHeight >= 1) { + if (consensusParams.FutureTimestampSoftForkActive(pindexPrev->nHeight + 1)) { nTime = std::min(nTime, medianTimePast + MAX_FUTURE_BLOCK_TIME_MTP); } pblock->nTime = nTime; From 3010c9277c2c118cd34ec5c5bd81bd857d9c5765 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 4 Feb 2020 19:33:56 +0000 Subject: [PATCH 08/18] Cosmetic: brace style in ContextualCheckBlockHeader. Signed-off-by: Daira Hopwood --- src/main.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0d7d14548..e398a1b41 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3764,17 +3764,19 @@ bool ContextualCheckBlockHeader( { const Consensus::Params& consensusParams = chainParams.GetConsensus(); uint256 hash = block.GetHash(); - if (hash == consensusParams.hashGenesisBlock) + if (hash == consensusParams.hashGenesisBlock) { return true; + } assert(pindexPrev); int nHeight = pindexPrev->nHeight+1; // Check proof of work - if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) + if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) { return state.DoS(100, error("%s: incorrect proof of work", __func__), REJECT_INVALID, "bad-diffbits"); + } // Check timestamp against prev auto medianTimePast = pindexPrev->GetMedianTimePast(); @@ -3805,18 +3807,19 @@ bool ContextualCheckBlockHeader( REJECT_INVALID, "time-too-new"); } - if (fCheckpointsEnabled) - { + if (fCheckpointsEnabled) { // Don't accept any forks from the main chain prior to last checkpoint CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(chainParams.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 block.nVersion < 4 blocks - if (block.nVersion < 4) + if (block.nVersion < 4) { return state.Invalid(error("%s : rejected nVersion<4 block", __func__), REJECT_OBSOLETE, "bad-version"); + } return true; } From eb5e328073ced7c867de99d6385a9d5f142e1ce0 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Tue, 4 Feb 2020 22:46:57 +0000 Subject: [PATCH 09/18] Add -maxtimeadjustment with default of 0 instead of the 4200 seconds used in Bitcoin Core. Based on https://github.com/bitcoinxt/bitcoinxt/pull/140/commits/40061b05dc96c4fea6c2a89cc9160f4df22a3bf8 Co-authored-by: mruddy Signed-off-by: Daira Hopwood --- src/init.cpp | 7 +++++++ src/timedata.cpp | 2 +- src/timedata.h | 8 ++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index 2c4727a56..46d4d570e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -32,6 +32,7 @@ #include "script/standard.h" #include "script/sigcache.h" #include "scheduler.h" +#include "timedata.h" #include "txdb.h" #include "torcontrol.h" #include "ui_interface.h" @@ -356,6 +357,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-dbcache=", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache)); strUsage += HelpMessageOpt("-loadblock=", _("Imports blocks from external blk000??.dat file on startup")); strUsage += HelpMessageOpt("-maxorphantx=", strprintf(_("Keep at most unconnectable transactions in memory (default: %u)"), DEFAULT_MAX_ORPHAN_TRANSACTIONS)); + strUsage += HelpMessageOpt("-maxtimeadjustment=", strprintf(_("Maximum allowed median peer time offset adjustment, in seconds. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds, maximum: %u seconds)"), DEFAULT_MAX_TIME_ADJUSTMENT, LIMIT_MAX_TIME_ADJUSTMENT)); strUsage += HelpMessageOpt("-par=", strprintf(_("Set the number of script verification threads (%u to %d, 0 = auto, <0 = leave that many cores free, default: %d)"), -GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS)); #ifndef WIN32 @@ -982,6 +984,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) fPruneMode = true; } + int64_t nMaxTimeAdjustment = GetArg("-maxtimeadjustment", DEFAULT_MAX_TIME_ADJUSTMENT); + if (nMaxTimeAdjustment < 0 || nMaxTimeAdjustment > LIMIT_MAX_TIME_ADJUSTMENT) { + return InitError(strprintf(_("-maxtimeadjustment must be in the range 0 to %u seconds"), LIMIT_MAX_TIME_ADJUSTMENT)); + } + RegisterAllCoreRPCCommands(tableRPC); #ifdef ENABLE_WALLET bool fDisableWallet = GetBoolArg("-disablewallet", false); diff --git a/src/timedata.cpp b/src/timedata.cpp index 40eb4d33e..26665730b 100644 --- a/src/timedata.cpp +++ b/src/timedata.cpp @@ -79,7 +79,7 @@ void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) int64_t nMedian = vTimeOffsets.median(); std::vector vSorted = vTimeOffsets.sorted(); // Only let other nodes change our time by so much - if (abs64(nMedian) < 70 * 60) + if (abs64(nMedian) <= GetArg("-maxtimeadjustment", DEFAULT_MAX_TIME_ADJUSTMENT)) { nTimeOffset = nMedian; } diff --git a/src/timedata.h b/src/timedata.h index 69a1eb0f6..e918c00de 100644 --- a/src/timedata.h +++ b/src/timedata.h @@ -10,6 +10,14 @@ #include #include +#include "chain.h" + +static const int64_t DEFAULT_MAX_TIME_ADJUSTMENT = 0; +static const int64_t LIMIT_MAX_TIME_ADJUSTMENT = 25 * 60; + +static_assert(LIMIT_MAX_TIME_ADJUSTMENT * 2 < MAX_FUTURE_BLOCK_TIME_MTP); +static_assert(MAX_FUTURE_BLOCK_TIME_MTP + LIMIT_MAX_TIME_ADJUSTMENT < MAX_FUTURE_BLOCK_TIME_ADJUSTED); + class CNetAddr; /** From 3d8af86c2c9589f9d118225d9e9a66a38120c0ca Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 4 Feb 2020 14:45:22 -0700 Subject: [PATCH 10/18] Release notes for vulnerability and -maxtimeadjustment option. Co-authored-by: Daira Hopwood Co-authored-by: Sean Bowe Signed-off-by: Daira Hopwood --- doc/release-notes.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/release-notes.md b/doc/release-notes.md index a29094b51..4b5e4a17c 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -4,3 +4,12 @@ release-notes at release time) Notable changes =============== +This release fixes a security issue described at +https://z.cash/support/security/announcements/security-announcement-2020-02-05/ . + +This release also adds a `-maxtimeadjustment` option to set the maximum time, in +seconds, by which the node's clock can be adjusted based on the clocks of its +peer nodes. This option defaults to 0, meaning that no such adjustment is performed. +This is a change from the previous behaviour, which was to adjust the clock by up +to 70 minutes forward or backward. The maximum setting for this option is now +25 minutes (1500 seconds). From 7bbd846f0f5c0d333f5c582692a7beb9e6aa8178 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 6 Feb 2020 14:30:37 +0000 Subject: [PATCH 11/18] Apply a consistent ban policy within ContextualCheckTransaction --- src/gtest/test_checktransaction.cpp | 49 ++++++++---- src/gtest/test_transaction_builder.cpp | 8 +- src/main.cpp | 106 +++++++++++++++++-------- src/main.h | 2 +- src/test/transaction_tests.cpp | 4 +- 5 files changed, 115 insertions(+), 54 deletions(-) diff --git a/src/gtest/test_checktransaction.cpp b/src/gtest/test_checktransaction.cpp index 04d69ab19..dd1c93cfc 100644 --- a/src/gtest/test_checktransaction.cpp +++ b/src/gtest/test_checktransaction.cpp @@ -167,7 +167,7 @@ TEST(checktransaction_tests, BadTxnsOversize) { // ... but fails contextual ones! EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-oversize", false)).Times(1); - EXPECT_FALSE(ContextualCheckTransaction(tx, state, Params(), 1, 100)); + EXPECT_FALSE(ContextualCheckTransaction(tx, state, Params(), 1, true)); } { @@ -188,7 +188,7 @@ TEST(checktransaction_tests, BadTxnsOversize) { MockCValidationState state; EXPECT_TRUE(CheckTransactionWithoutProofVerification(tx, state)); - EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 1, 100)); + EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 1, true)); // Revert to default RegtestDeactivateSapling(); @@ -503,11 +503,18 @@ TEST(checktransaction_tests, bad_txns_invalid_joinsplit_signature) { CTransaction tx(mtx); MockCValidationState state; - // during initial block download, DoS ban score should be zero, else 100 + // during initial block download, for transactions being accepted into the + // mempool (and thus not mined), DoS ban score should be zero, else 10 EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); - ContextualCheckTransaction(tx, state, chainparams, 0, 100, [](const CChainParams&) { return true; }); + ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const CChainParams&) { return true; }); + EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); + ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const CChainParams&) { return false; }); + // for transactions that have been mined in a block, DoS ban score should + // always be 100. EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); - ContextualCheckTransaction(tx, state, chainparams, 0, 100, [](const CChainParams&) { return false; }); + ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return true; }); + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); + ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return false; }); } TEST(checktransaction_tests, non_canonical_ed25519_signature) { @@ -520,7 +527,7 @@ TEST(checktransaction_tests, non_canonical_ed25519_signature) { { CTransaction tx(mtx); MockCValidationState state; - EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 0, 100)); + EXPECT_TRUE(ContextualCheckTransaction(tx, state, chainparams, 0, true)); } // Copied from libsodium/crypto_sign/ed25519/ref10/open.c @@ -540,11 +547,18 @@ TEST(checktransaction_tests, non_canonical_ed25519_signature) { CTransaction tx(mtx); MockCValidationState state; - // during initial block download, DoS ban score should be zero, else 100 + // during initial block download, for transactions being accepted into the + // mempool (and thus not mined), DoS ban score should be zero, else 10 EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); - ContextualCheckTransaction(tx, state, chainparams, 0, 100, [](const CChainParams&) { return true; }); + ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const CChainParams&) { return true; }); + EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); + ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const CChainParams&) { return false; }); + // for transactions that have been mined in a block, DoS ban score should + // always be 100. EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); - ContextualCheckTransaction(tx, state, chainparams, 0, 100, [](const CChainParams&) { return false; }); + ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return true; }); + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1); + ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return false; }); } TEST(checktransaction_tests, OverwinterConstructors) { @@ -806,7 +820,7 @@ TEST(checktransaction_tests, OverwinterVersionNumberHigh) { UNSAFE_CTransaction tx(mtx); MockCValidationState state; EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-tx-overwinter-version-too-high", false)).Times(1); - ContextualCheckTransaction(tx, state, Params(), 1, 100); + ContextualCheckTransaction(tx, state, Params(), 1, true); // Revert to default UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); @@ -841,11 +855,18 @@ TEST(checktransaction_tests, OverwinterNotActive) { CTransaction tx(mtx); MockCValidationState state; - // during initial block download, DoS ban score should be zero, else 100 + // during initial block download, for transactions being accepted into the + // mempool (and thus not mined), DoS ban score should be zero, else 10 EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1); - ContextualCheckTransaction(tx, state, chainparams, 1, 100, [](const CChainParams&) { return true; }); + ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const CChainParams&) { return true; }); + EXPECT_CALL(state, DoS(10, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1); + ContextualCheckTransaction(tx, state, chainparams, 0, false, [](const CChainParams&) { return false; }); + // for transactions that have been mined in a block, DoS ban score should + // always be 100. EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1); - ContextualCheckTransaction(tx, state, chainparams, 1, 100, [](const CChainParams&) { return false; }); + ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return true; }); + EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1); + ContextualCheckTransaction(tx, state, chainparams, 0, true, [](const CChainParams&) { return false; }); } // This tests a transaction without the fOverwintered flag set, against the Overwinter consensus rule set. @@ -862,7 +883,7 @@ TEST(checktransaction_tests, OverwinterFlagNotSet) { CTransaction tx(mtx); MockCValidationState state; EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-flag-not-set", false)).Times(1); - ContextualCheckTransaction(tx, state, Params(), 1, 100); + ContextualCheckTransaction(tx, state, Params(), 1, true); // Revert to default UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 332c194f8..cd0680d9e 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -106,7 +106,7 @@ TEST(TransactionBuilder, TransparentToSapling) EXPECT_EQ(tx.valueBalance, -40000); CValidationState state; - EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 2, 0)); + EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 2, true)); EXPECT_EQ(state.GetRejectReason(), ""); // Revert to default @@ -143,7 +143,7 @@ TEST(TransactionBuilder, SaplingToSapling) { EXPECT_EQ(tx.valueBalance, 10000); CValidationState state; - EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 3, 0)); + EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 3, true)); EXPECT_EQ(state.GetRejectReason(), ""); // Revert to default @@ -181,7 +181,7 @@ TEST(TransactionBuilder, SaplingToSprout) { EXPECT_EQ(tx.valueBalance, 35000); CValidationState state; - EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 3, 0)); + EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 3, true)); EXPECT_EQ(state.GetRejectReason(), ""); // Revert to default @@ -242,7 +242,7 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) { EXPECT_EQ(tx.valueBalance, -5000); CValidationState state; - EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 4, 0)); + EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 4, true)); EXPECT_EQ(state.GetRejectReason(), ""); // Revert to default diff --git a/src/main.cpp b/src/main.cpp index bcf652693..be5d9ef91 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -763,69 +763,96 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in * 1. AcceptToMemoryPool calls CheckTransaction and this function. * 2. ProcessNewBlock calls AcceptBlock, which calls CheckBlock (which calls CheckTransaction) * and ContextualCheckBlock (which calls this function). - * 3. The isInitBlockDownload argument is only to assist with testing. + * 3. For consensus rules that relax restrictions (where a transaction that is invalid at + * nHeight can become valid at a later height), we make the bans conditional on not + * being in Initial Block Download mode. + * 4. The isInitBlockDownload argument is a function parameter to assist with testing. */ bool ContextualCheckTransaction( const CTransaction& tx, CValidationState &state, const CChainParams& chainparams, const int nHeight, - const int dosLevel, + const bool isMined, bool (*isInitBlockDownload)(const CChainParams&)) { + const int DOS_LEVEL_BLOCK = 100; + // DoS level set to 10 to be more forgiving. + const int DOS_LEVEL_MEMPOOL = 10; + + // For constricting rules, we don't need to account for IBD mode. + auto dosLevelConstricting = isMined ? DOS_LEVEL_BLOCK : DOS_LEVEL_MEMPOOL; + // For rules that are relaxing (or might become relaxing when a future + // network upgrade is implemented), we need to account for IBD mode. + auto dosLevelPotentiallyRelaxing = isMined ? DOS_LEVEL_BLOCK : ( + isInitBlockDownload(chainparams) ? 0 : DOS_LEVEL_MEMPOOL); + bool overwinterActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_OVERWINTER); bool saplingActive = chainparams.GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_SAPLING); bool isSprout = !overwinterActive; // If Sprout rules apply, reject transactions which are intended for Overwinter and beyond if (isSprout && tx.fOverwintered) { - return state.DoS(isInitBlockDownload(chainparams) ? 0 : dosLevel, - error("ContextualCheckTransaction(): overwinter is not active yet"), - REJECT_INVALID, "tx-overwinter-not-active"); + return state.DoS( + dosLevelPotentiallyRelaxing, + error("ContextualCheckTransaction(): overwinter is not active yet"), + REJECT_INVALID, "tx-overwinter-not-active"); } 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"); + return state.DoS( + dosLevelConstricting, + 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(isInitBlockDownload(chainparams) ? 0 : dosLevel, - error("CheckTransaction(): invalid Sapling tx version"), - REJECT_INVALID, "bad-sapling-tx-version-group-id"); + return state.DoS( + dosLevelPotentiallyRelaxing, + 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"), + return state.DoS( + dosLevelConstricting, + 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"), + return state.DoS( + dosLevelPotentiallyRelaxing, + 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"); + return state.DoS( + dosLevelConstricting, + 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(isInitBlockDownload(chainparams) ? 0 : dosLevel, - error("CheckTransaction(): invalid Overwinter tx version"), - REJECT_INVALID, "bad-overwinter-tx-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(100, error("CheckTransaction(): overwinter version too high"), + return state.DoS( + dosLevelPotentiallyRelaxing, + error("CheckTransaction(): overwinter version too high"), REJECT_INVALID, "bad-tx-overwinter-version-too-high"); } } @@ -834,14 +861,16 @@ bool ContextualCheckTransaction( if (overwinterActive) { // Reject transactions intended for Sprout if (!tx.fOverwintered) { - return state.DoS(dosLevel, error("ContextualCheckTransaction: overwinter is active"), - REJECT_INVALID, "tx-overwinter-active"); + 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) ? dosLevel : 0; + int expiredDosLevel = IsExpiredTx(tx, nHeight - 1) ? dosLevelConstricting : 0; return state.DoS(expiredDosLevel, error("ContextualCheckTransaction(): transaction is expired"), REJECT_INVALID, "tx-overwinter-expired"); } } @@ -851,8 +880,10 @@ bool ContextualCheckTransaction( // Size limits BOOST_STATIC_ASSERT(MAX_BLOCK_SIZE > MAX_TX_SIZE_BEFORE_SAPLING); // sanity if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_TX_SIZE_BEFORE_SAPLING) - return state.DoS(100, error("ContextualCheckTransaction(): size limits failed"), - REJECT_INVALID, "bad-txns-oversize"); + return state.DoS( + dosLevelPotentiallyRelaxing, + error("ContextualCheckTransaction(): size limits failed"), + REJECT_INVALID, "bad-txns-oversize"); } uint256 dataToBeSigned; @@ -867,6 +898,8 @@ bool ContextualCheckTransaction( try { dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId); } catch (std::logic_error ex) { + // A logic error should never occur because we pass NOT_AN_INPUT and + // SIGHASH_ALL to SignatureHash(). return state.DoS(100, error("CheckTransaction(): error computing signature hash"), REJECT_INVALID, "error-computing-signature-hash"); } @@ -882,9 +915,10 @@ bool ContextualCheckTransaction( dataToBeSigned.begin(), 32, tx.joinSplitPubKey.begin() ) != 0) { - return state.DoS(isInitBlockDownload(chainparams) ? 0 : 100, - error("CheckTransaction(): invalid joinsplit signature"), - REJECT_INVALID, "bad-txns-invalid-joinsplit-signature"); + return state.DoS( + dosLevelPotentiallyRelaxing, + error("CheckTransaction(): invalid joinsplit signature"), + REJECT_INVALID, "bad-txns-invalid-joinsplit-signature"); } } @@ -906,8 +940,10 @@ bool ContextualCheckTransaction( )) { librustzcash_sapling_verification_ctx_free(ctx); - return state.DoS(100, error("ContextualCheckTransaction(): Sapling spend description invalid"), - REJECT_INVALID, "bad-txns-sapling-spend-description-invalid"); + return state.DoS( + dosLevelPotentiallyRelaxing, + error("ContextualCheckTransaction(): Sapling spend description invalid"), + REJECT_INVALID, "bad-txns-sapling-spend-description-invalid"); } } @@ -921,6 +957,9 @@ bool ContextualCheckTransaction( )) { librustzcash_sapling_verification_ctx_free(ctx); + // This should be a non-contextual check, but we check it here + // as we need to pass over the outputs anyway in order to then + // call librustzcash_sapling_final_check(). return state.DoS(100, error("ContextualCheckTransaction(): Sapling output description invalid"), REJECT_INVALID, "bad-txns-sapling-output-description-invalid"); } @@ -934,8 +973,10 @@ bool ContextualCheckTransaction( )) { librustzcash_sapling_verification_ctx_free(ctx); - return state.DoS(100, error("ContextualCheckTransaction(): Sapling binding signature invalid"), - REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid"); + return state.DoS( + dosLevelPotentiallyRelaxing, + error("ContextualCheckTransaction(): Sapling binding signature invalid"), + REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid"); } librustzcash_sapling_verification_ctx_free(ctx); @@ -1249,9 +1290,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa 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, Params(), nextBlockHeight, 10)) { + if (!ContextualCheckTransaction(tx, state, Params(), nextBlockHeight, false)) { return error("AcceptToMemoryPool: ContextualCheckTransaction failed"); } @@ -3813,7 +3853,7 @@ bool ContextualCheckBlock( BOOST_FOREACH(const CTransaction& tx, block.vtx) { // Check transaction contextually against consensus rules at block height - if (!ContextualCheckTransaction(tx, state, chainparams, nHeight, 100)) { + if (!ContextualCheckTransaction(tx, state, chainparams, nHeight, true)) { return false; // Failure reason has been set in validation state object } diff --git a/src/main.h b/src/main.h index a7992c69b..8427fdf07 100644 --- a/src/main.h +++ b/src/main.h @@ -341,7 +341,7 @@ bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, cons /** Check a transaction contextually against a set of consensus rules */ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, - const CChainParams& chainparams, int nHeight, int dosLevel, + const CChainParams& chainparams, int nHeight, bool isMined, bool (*isInitBlockDownload)(const CChainParams&) = IsInitialBlockDownload); /** Apply the effects of this transaction on the UTXO set represented by view */ diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 1f1d3e069..fadbc6c29 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -491,7 +491,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa jsdesc->nullifiers[1] = GetRandHash(); BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state)); - BOOST_CHECK(!ContextualCheckTransaction(newTx, state, Params(), 0, 100)); + BOOST_CHECK(!ContextualCheckTransaction(newTx, state, Params(), 0, true)); BOOST_CHECK(state.GetRejectReason() == "bad-txns-invalid-joinsplit-signature"); // Empty output script. @@ -505,7 +505,7 @@ void test_simple_joinsplit_invalidity(uint32_t consensusBranchId, CMutableTransa ) == 0); BOOST_CHECK(CheckTransactionWithoutProofVerification(newTx, state)); - BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, 100)); + BOOST_CHECK(ContextualCheckTransaction(newTx, state, Params(), 0, true)); } { // Ensure that values within the joinsplit are well-formed. From e9e9bda3295a4a076600670e66c158b9cda54c4a Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 6 Feb 2020 21:09:29 +0000 Subject: [PATCH 12/18] Fix ContextualCheckBlock test (the ban score should be 100 since these are mined transactions). Signed-off-by: Daira Hopwood --- src/gtest/test_checkblock.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gtest/test_checkblock.cpp b/src/gtest/test_checkblock.cpp index 4195647e8..f8ca90362 100644 --- a/src/gtest/test_checkblock.cpp +++ b/src/gtest/test_checkblock.cpp @@ -252,7 +252,7 @@ TEST_F(ContextualCheckBlockTest, BlockSproutRulesRejectOtherTx) { { SCOPED_TRACE("BlockSproutRulesRejectOverwinterTx"); - ExpectInvalidBlockFromTx(CTransaction(mtx), 0, "tx-overwinter-not-active"); + ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwinter-not-active"); } // Make it a Sapling transaction @@ -262,7 +262,7 @@ TEST_F(ContextualCheckBlockTest, BlockSproutRulesRejectOtherTx) { { SCOPED_TRACE("BlockSproutRulesRejectSaplingTx"); - ExpectInvalidBlockFromTx(CTransaction(mtx), 0, "tx-overwinter-not-active"); + ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "tx-overwinter-not-active"); } }; @@ -290,7 +290,7 @@ TEST_F(ContextualCheckBlockTest, BlockOverwinterRulesRejectOtherTx) { { SCOPED_TRACE("BlockOverwinterRulesRejectSaplingTx"); - ExpectInvalidBlockFromTx(CTransaction(mtx), 0, "bad-overwinter-tx-version-group-id"); + ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "bad-overwinter-tx-version-group-id"); } } @@ -318,6 +318,6 @@ TEST_F(ContextualCheckBlockTest, BlockSaplingRulesRejectOtherTx) { { SCOPED_TRACE("BlockSaplingRulesRejectOverwinterTx"); - ExpectInvalidBlockFromTx(CTransaction(mtx), 0, "bad-sapling-tx-version-group-id"); + ExpectInvalidBlockFromTx(CTransaction(mtx), 100, "bad-sapling-tx-version-group-id"); } } From ca8d32070a52f64fd97465ba01d51fdfb5d87cc5 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 6 Feb 2020 23:35:56 +0000 Subject: [PATCH 13/18] Add string argument to static_asserts to satisfy C++11. Signed-off-by: Daira Hopwood --- src/chain.h | 6 ++++-- src/chainparams.cpp | 3 ++- src/timedata.h | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/chain.h b/src/chain.h index eabf0ec3c..5bb580386 100644 --- a/src/chain.h +++ b/src/chain.h @@ -36,8 +36,10 @@ static const int64_t MAX_FUTURE_BLOCK_TIME_ADJUSTED = 2 * 60 * 60; */ static const int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME_ADJUSTED + 60; -static_assert(MAX_FUTURE_BLOCK_TIME_ADJUSTED > MAX_FUTURE_BLOCK_TIME_MTP); -static_assert(TIMESTAMP_WINDOW > MAX_FUTURE_BLOCK_TIME_ADJUSTED); +static_assert(MAX_FUTURE_BLOCK_TIME_ADJUSTED > MAX_FUTURE_BLOCK_TIME_MTP, + "MAX_FUTURE_BLOCK_TIME_ADJUSTED must be greater than MAX_FUTURE_BLOCK_TIME_MTP"); +static_assert(TIMESTAMP_WINDOW > MAX_FUTURE_BLOCK_TIME_ADJUSTED, + "TIMESTAMP_WINDOW must be greater than MAX_FUTURE_BLOCK_TIME_ADJUSTED"); class CBlockFileInfo diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 301ece19c..acfe62dce 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -336,7 +336,8 @@ public: // // 7 times that is 52.5 minutes which is well within the limit imposed by the soft fork. - static_assert(6 * Consensus::POST_BLOSSOM_POW_TARGET_SPACING * 7 < MAX_FUTURE_BLOCK_TIME_MTP - 60); + static_assert(6 * Consensus::POST_BLOSSOM_POW_TARGET_SPACING * 7 < MAX_FUTURE_BLOCK_TIME_MTP - 60, + "MAX_FUTURE_BLOCK_TIME_MTP is too low given block target spacing"); consensus.nFutureTimestampSoftForkHeight = consensus.vUpgrades[Consensus::UPGRADE_BLOSSOM].nActivationHeight + 6; // The best chain should have at least this much work. diff --git a/src/timedata.h b/src/timedata.h index e918c00de..3e660d7e5 100644 --- a/src/timedata.h +++ b/src/timedata.h @@ -15,8 +15,10 @@ static const int64_t DEFAULT_MAX_TIME_ADJUSTMENT = 0; static const int64_t LIMIT_MAX_TIME_ADJUSTMENT = 25 * 60; -static_assert(LIMIT_MAX_TIME_ADJUSTMENT * 2 < MAX_FUTURE_BLOCK_TIME_MTP); -static_assert(MAX_FUTURE_BLOCK_TIME_MTP + LIMIT_MAX_TIME_ADJUSTMENT < MAX_FUTURE_BLOCK_TIME_ADJUSTED); +static_assert(LIMIT_MAX_TIME_ADJUSTMENT * 2 < MAX_FUTURE_BLOCK_TIME_MTP, + "LIMIT_MAX_TIME_ADJUSTMENT is too high given MAX_FUTURE_BLOCK_TIME_MTP"); +static_assert(MAX_FUTURE_BLOCK_TIME_MTP + LIMIT_MAX_TIME_ADJUSTMENT < MAX_FUTURE_BLOCK_TIME_ADJUSTED, + "LIMIT_MAX_TIME_ADJUSTMENT is too high given MAX_FUTURE_BLOCK_TIME_MTP and MAX_FUTURE_BLOCK_TIME_ADJUSTED"); class CNetAddr; From afc553c448688cbb22986775bd8888818f3cff5f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 6 Feb 2020 16:51:15 -0700 Subject: [PATCH 14/18] make-release.py: Versioning changes for 2.1.1-1. --- README.md | 2 +- configure.ac | 2 +- contrib/gitian-descriptors/gitian-linux.yml | 2 +- src/clientversion.h | 2 +- src/deprecation.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 25a2602cd..11d2f3e5f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Zcash 2.1.1 +Zcash 2.1.1-1 =========== diff --git a/configure.ac b/configure.ac index 8c15d16c4..d8c7ebf7f 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 2) define(_CLIENT_VERSION_MINOR, 1) define(_CLIENT_VERSION_REVISION, 1) -define(_CLIENT_VERSION_BUILD, 50) +define(_CLIENT_VERSION_BUILD, 51) define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50))) define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1))) define(_CLIENT_VERSION_IS_RELEASE, true) diff --git a/contrib/gitian-descriptors/gitian-linux.yml b/contrib/gitian-descriptors/gitian-linux.yml index 2c273e65f..a650d7bfb 100644 --- a/contrib/gitian-descriptors/gitian-linux.yml +++ b/contrib/gitian-descriptors/gitian-linux.yml @@ -1,5 +1,5 @@ --- -name: "zcash-2.1.1" +name: "zcash-2.1.1-1" enable_cache: true distro: "debian" suites: diff --git a/src/clientversion.h b/src/clientversion.h index 02d73160e..ea5fa22b3 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -18,7 +18,7 @@ #define CLIENT_VERSION_MAJOR 2 #define CLIENT_VERSION_MINOR 1 #define CLIENT_VERSION_REVISION 1 -#define CLIENT_VERSION_BUILD 50 +#define CLIENT_VERSION_BUILD 51 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true diff --git a/src/deprecation.h b/src/deprecation.h index 7fea8de8d..f649f4680 100644 --- a/src/deprecation.h +++ b/src/deprecation.h @@ -8,7 +8,7 @@ // Deprecation policy: // * Shut down 16 weeks' worth of blocks after the estimated release block height. // * A warning is shown during the 2 weeks' worth of blocks prior to shut down. -static const int APPROX_RELEASE_HEIGHT = 698112; +static const int APPROX_RELEASE_HEIGHT = 719034; static const int WEEKS_UNTIL_DEPRECATION = 16; static const int DEPRECATION_HEIGHT = APPROX_RELEASE_HEIGHT + (WEEKS_UNTIL_DEPRECATION * 7 * 24 * 48); From b10c90de345f220b6c500ae5f52227f21f5cd1c2 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 6 Feb 2020 16:53:18 -0700 Subject: [PATCH 15/18] make-release.py: Updated manpages for 2.1.1-1. --- doc/man/zcash-cli.1 | 6 +++--- doc/man/zcash-tx.1 | 6 +++--- doc/man/zcashd.1 | 12 +++++++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/doc/man/zcash-cli.1 b/doc/man/zcash-cli.1 index fecef06d0..df61119e6 100644 --- a/doc/man/zcash-cli.1 +++ b/doc/man/zcash-cli.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11. -.TH ZCASH-CLI "1" "January 2020" "zcash-cli v2.1.1" "User Commands" +.TH ZCASH-CLI "1" "February 2020" "zcash-cli v2.1.1-1" "User Commands" .SH NAME -zcash-cli \- manual page for zcash-cli v2.1.1 +zcash-cli \- manual page for zcash-cli v2.1.1-1 .SH DESCRIPTION -Zcash RPC client version v2.1.1 +Zcash RPC client version v2.1.1\-1 .PP In order to ensure you are adequately protecting your privacy when using Zcash, please see . diff --git a/doc/man/zcash-tx.1 b/doc/man/zcash-tx.1 index 5f8b9d638..db69019c1 100644 --- a/doc/man/zcash-tx.1 +++ b/doc/man/zcash-tx.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11. -.TH ZCASH-TX "1" "January 2020" "zcash-tx v2.1.1" "User Commands" +.TH ZCASH-TX "1" "February 2020" "zcash-tx v2.1.1-1" "User Commands" .SH NAME -zcash-tx \- manual page for zcash-tx v2.1.1 +zcash-tx \- manual page for zcash-tx v2.1.1-1 .SH DESCRIPTION -Zcash zcash\-tx utility version v2.1.1 +Zcash zcash\-tx utility version v2.1.1\-1 .SS "Usage:" .TP zcash\-tx [options] [commands] diff --git a/doc/man/zcashd.1 b/doc/man/zcashd.1 index b194e2d18..4d73bb2c3 100644 --- a/doc/man/zcashd.1 +++ b/doc/man/zcashd.1 @@ -1,9 +1,9 @@ .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.11. -.TH ZCASHD "1" "January 2020" "zcashd v2.1.1" "User Commands" +.TH ZCASHD "1" "February 2020" "zcashd v2.1.1-1" "User Commands" .SH NAME -zcashd \- manual page for zcashd v2.1.1 +zcashd \- manual page for zcashd v2.1.1-1 .SH DESCRIPTION -Zcash Daemon version v2.1.1 +Zcash Daemon version v2.1.1\-1 .PP In order to ensure you are adequately protecting your privacy when using Zcash, please see . @@ -67,6 +67,12 @@ Imports blocks from external blk000??.dat file on startup .IP Keep at most unconnectable transactions in memory (default: 100) .HP +\fB\-maxtimeadjustment=\fR +.IP +Maximum allowed median peer time offset adjustment, in seconds. Local +perspective of time may be influenced by peers forward or backward by +this amount. (default: 0 seconds, maximum: 1500 seconds) +.HP \fB\-par=\fR .IP Set the number of script verification threads (\fB\-16\fR to 16, 0 = auto, <0 = From 7d21fdb5820fcd9afebc6fd5f651573b0c805391 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 6 Feb 2020 16:53:19 -0700 Subject: [PATCH 16/18] make-release.py: Updated release notes and changelog for 2.1.1-1. --- contrib/debian/changelog | 6 ++++ doc/authors.md | 6 ++-- doc/release-notes.md | 9 ------ doc/release-notes/release-notes-2.1.1-1.md | 37 ++++++++++++++++++++++ 4 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 doc/release-notes/release-notes-2.1.1-1.md diff --git a/contrib/debian/changelog b/contrib/debian/changelog index a33a8178c..be1b2755e 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,3 +1,9 @@ +zcash (2.1.1+1) stable; urgency=medium + + * 2.1.1-1 release. + + -- Electric Coin Company Thu, 06 Feb 2020 16:53:19 -0700 + zcash (2.1.1) stable; urgency=medium * 2.1.1 release. diff --git a/doc/authors.md b/doc/authors.md index 0f310fedb..0221cf1e3 100644 --- a/doc/authors.md +++ b/doc/authors.md @@ -1,11 +1,11 @@ Zcash Contributors ================== -Jack Grigg (923) +Jack Grigg (925) Simon Liu (460) -Sean Bowe (288) +Sean Bowe (291) Eirik Ogilvie-Wigley (212) -Daira Hopwood (145) +Daira Hopwood (155) Wladimir J. van der Laan (89) Jay Graber (89) Taylor Hornby (84) diff --git a/doc/release-notes.md b/doc/release-notes.md index 4b5e4a17c..a29094b51 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -4,12 +4,3 @@ release-notes at release time) Notable changes =============== -This release fixes a security issue described at -https://z.cash/support/security/announcements/security-announcement-2020-02-05/ . - -This release also adds a `-maxtimeadjustment` option to set the maximum time, in -seconds, by which the node's clock can be adjusted based on the clocks of its -peer nodes. This option defaults to 0, meaning that no such adjustment is performed. -This is a change from the previous behaviour, which was to adjust the clock by up -to 70 minutes forward or backward. The maximum setting for this option is now -25 minutes (1500 seconds). diff --git a/doc/release-notes/release-notes-2.1.1-1.md b/doc/release-notes/release-notes-2.1.1-1.md new file mode 100644 index 000000000..618a08b94 --- /dev/null +++ b/doc/release-notes/release-notes-2.1.1-1.md @@ -0,0 +1,37 @@ +Notable changes +=============== + +This release fixes a security issue described at +https://z.cash/support/security/announcements/security-announcement-2020-02-05/ . + +This release also adds a `-maxtimeadjustment` option to set the maximum time, in +seconds, by which the node's clock can be adjusted based on the clocks of its +peer nodes. This option defaults to 0, meaning that no such adjustment is performed. +This is a change from the previous behaviour, which was to adjust the clock by up +to 70 minutes forward or backward. The maximum setting for this option is now +25 minutes (1500 seconds). + +Changelog +========= + +Daira Hopwood (10): + Move check for block times that are too far ahead of adjusted time, to ContextualCheckBlock. + Improve messages for timestamp rules. + Add constant for how far a block timestamp can be ahead of adjusted time. Loosely based on https://github.com/bitcoin/bitcoin/commit/e57a1fd8999800b3fc744d45bb96354cae294032 + Soft fork: restrict block timestamps to be no more than 90 minutes after the MTP of the previous block. + Adjust the miner to satisfy consensus regarding future timestamps relative to median-time-past. + Enable future timestamp soft fork at varying heights according to network. + Cosmetic: brace style in ContextualCheckBlockHeader. + Add -maxtimeadjustment with default of 0 instead of the 4200 seconds used in Bitcoin Core. + Fix ContextualCheckBlock test (the ban score should be 100 since these are mined transactions). + Add string argument to static_asserts to satisfy C++11. + +Jack Grigg (2): + test: Update RPC test cache generation to handle new consensus rule + Apply a consistent ban policy within ContextualCheckTransaction + +Sean Bowe (3): + Release notes for vulnerability and -maxtimeadjustment option. + make-release.py: Versioning changes for 2.1.1-1. + make-release.py: Updated manpages for 2.1.1-1. + From 7095fa930702a15af398865f94b8176b90ab0cb6 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 6 Feb 2020 14:17:01 -0700 Subject: [PATCH 17/18] Changes to release notes. --- doc/release-notes/release-notes-2.1.1-1.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/release-notes/release-notes-2.1.1-1.md b/doc/release-notes/release-notes-2.1.1-1.md index 618a08b94..5c764f24c 100644 --- a/doc/release-notes/release-notes-2.1.1-1.md +++ b/doc/release-notes/release-notes-2.1.1-1.md @@ -2,7 +2,7 @@ Notable changes =============== This release fixes a security issue described at -https://z.cash/support/security/announcements/security-announcement-2020-02-05/ . +https://z.cash/support/security/announcements/security-announcement-2020-02-06/ . This release also adds a `-maxtimeadjustment` option to set the maximum time, in seconds, by which the node's clock can be adjusted based on the clocks of its @@ -11,6 +11,12 @@ This is a change from the previous behaviour, which was to adjust the clock by u to 70 minutes forward or backward. The maximum setting for this option is now 25 minutes (1500 seconds). +Fix for incorrect banning of nodes during syncing +------------------------------------------------- +After activation of the Blossom network upgrade, a node that is syncing the +block chain from before Blossom would incorrectly ban peers that send it a +Blossom transaction. This resulted in slower and less reliable syncing (#4283). + Changelog ========= From 5361ce1e8c2a6876f9266360b14ae5198d5eb421 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 6 Feb 2020 14:19:37 -0700 Subject: [PATCH 18/18] Mark release v2.1.1-1 as critical. --- contrib/debian/changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/debian/changelog b/contrib/debian/changelog index be1b2755e..cfe6e329c 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,4 +1,4 @@ -zcash (2.1.1+1) stable; urgency=medium +zcash (2.1.1+1) stable; urgency=critical * 2.1.1-1 release.