From 780f526bc9466c4dc51dd290f9fa5f87c5321387 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 17 Jan 2018 11:25:28 +0000 Subject: [PATCH 01/10] Network upgrade activation mechanism --- src/Makefile.am | 2 + src/chainparams.cpp | 8 ++++ src/consensus/params.h | 42 +++++++++++++++++ src/consensus/upgrades.cpp | 94 ++++++++++++++++++++++++++++++++++++++ src/consensus/upgrades.h | 77 +++++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+) create mode 100644 src/consensus/upgrades.cpp create mode 100644 src/consensus/upgrades.h diff --git a/src/Makefile.am b/src/Makefile.am index 44b4d8281e2..d8ab34ea0f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -138,6 +138,7 @@ BITCOIN_CORE_H = \ compressor.h \ consensus/consensus.h \ consensus/params.h \ + consensus/upgrades.h \ consensus/validation.h \ core_io.h \ core_memusage.h \ @@ -354,6 +355,7 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ compressor.cpp \ + consensus/upgrades.cpp \ core_read.cpp \ core_write.cpp \ hash.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 36b71b5c704..fd928318d16 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -90,6 +90,9 @@ class CMainParams : public CChainParams { consensus.nPowMaxAdjustDown = 32; // 32% adjustment down consensus.nPowMaxAdjustUp = 16; // 16% adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = + Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + /** * The message start string should be awesome! ⓩ❤ */ @@ -241,6 +244,9 @@ class CTestNetParams : public CChainParams { consensus.nPowMaxAdjustDown = 32; // 32% adjustment down consensus.nPowMaxAdjustUp = 16; // 16% adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = + Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + pchMessageStart[0] = 0xfa; pchMessageStart[1] = 0x1a; pchMessageStart[2] = 0xf9; @@ -341,6 +347,8 @@ class CRegTestParams : public CChainParams { consensus.nPowMaxAdjustDown = 0; // Turn off adjustment down consensus.nPowMaxAdjustUp = 0; // Turn off adjustment up consensus.nPowTargetSpacing = 2.5 * 60; + consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = + Consensus::NetworkUpgrade::ALWAYS_ACTIVE; pchMessageStart[0] = 0xaa; pchMessageStart[1] = 0xe8; diff --git a/src/consensus/params.h b/src/consensus/params.h index c74e66d5fa4..59d725cbac0 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -9,6 +9,47 @@ #include "uint256.h" namespace Consensus { + +/** + * Index into Params.vUpgrades and NetworkUpgradeInfo + * + * Being array indices, these MUST be numbered consecutively. + * + * The order of these indices MUST match the order of the upgrades on-chain, as + * several functions depends on the enum being sorted. + */ +enum UpgradeIndex { + // Sprout must be first + BASE_SPROUT, + // NOTE: Also add new upgrades to NetworkUpgradeInfo in upgrades.cpp + MAX_NETWORK_UPGRADES +}; + +struct NetworkUpgrade { + /** + * Height of the first block for which the new consensus rules will be active + */ + int nActivationHeight; + + /** + * Special value for nActivationHeight indicating that the upgrade is always active. + * This is useful for testing, as it means tests don't need to deal with the activation + * process (namely, faking a chain of somewhat-arbitrary length). + * + * New blockchains that want to enable upgrade rules from the beginning can also use + * this value. However, additional care must be taken to ensure the genesis block + * satisfies the enabled rules. + */ + static constexpr int ALWAYS_ACTIVE = 0; + + /** + * Special value for nActivationHeight indicating that the upgrade will never activate. + * This is useful when adding upgrade code that has a testnet activation height, but + * should remain disabled on mainnet. + */ + static constexpr int NO_ACTIVATION_HEIGHT = -1; +}; + /** * Parameters that influence chain consensus. */ @@ -39,6 +80,7 @@ struct Params { int nMajorityEnforceBlockUpgrade; int nMajorityRejectBlockOutdated; int nMajorityWindow; + NetworkUpgrade vUpgrades[MAX_NETWORK_UPGRADES]; /** Proof of work parameters */ uint256 powLimit; int64_t nPowAveragingWindow; diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp new file mode 100644 index 00000000000..1191e02ff01 --- /dev/null +++ b/src/consensus/upgrades.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2018 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "consensus/upgrades.h" + +/** + * General information about each network upgrade. + * Ordered by Consensus::UpgradeIndex. + */ +const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = { + { + /*.nBranchId =*/ 0, + /*.strName =*/ "Sprout", + /*.strInfo =*/ "The Zcash network at launch", + } +}; + +UpgradeState NetworkUpgradeState( + int nHeight, + const Consensus::Params& params, + Consensus::UpgradeIndex idx) +{ + assert(nHeight >= 0); + assert(idx >= Consensus::BASE_SPROUT && idx < Consensus::MAX_NETWORK_UPGRADES); + auto nActivationHeight = params.vUpgrades[idx].nActivationHeight; + + if (nActivationHeight == Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT) { + return UPGRADE_DISABLED; + } else if (nHeight >= nActivationHeight) { + // From ZIP ???: + // + // ACTIVATION_HEIGHT + // The block height at which the network upgrade rules will come into effect. + // + // For removal of ambiguity, the block at height ACTIVATION_HEIGHT - 1 is + // subject to the pre-upgrade consensus rules. + return UPGRADE_ACTIVE; + } else { + return UPGRADE_PENDING; + } +} + +bool NetworkUpgradeActive( + int nHeight, + const Consensus::Params& params, + Consensus::UpgradeIndex idx) +{ + return NetworkUpgradeState(nHeight, params, idx) == UPGRADE_ACTIVE; +} + +int CurrentEpoch(int nHeight, const Consensus::Params& params) { + for (auto idxInt = Consensus::MAX_NETWORK_UPGRADES - 1; idxInt >= Consensus::BASE_SPROUT; idxInt--) { + if (NetworkUpgradeActive(nHeight, params, Consensus::UpgradeIndex(idxInt))) { + return idxInt; + } + } +} + +uint32_t CurrentEpochBranchId(int nHeight, const Consensus::Params& params) { + return NetworkUpgradeInfo[CurrentEpoch(nHeight, params)].nBranchId; +} + +bool IsActivationHeight( + int nHeight, + const Consensus::Params& params, + Consensus::UpgradeIndex idx) +{ + assert(idx >= Consensus::BASE_SPROUT && idx < Consensus::MAX_NETWORK_UPGRADES); + + // Don't count Sprout as an activation height + if (idx == Consensus::BASE_SPROUT) { + return false; + } + + return nHeight >= 0 && nHeight == params.vUpgrades[idx].nActivationHeight; +} + +bool IsActivationHeightForAnyUpgrade( + int nHeight, + const Consensus::Params& params) +{ + if (nHeight < 0) { + return false; + } + + // Don't count Sprout as an activation height + for (int idx = Consensus::BASE_SPROUT + 1; idx < Consensus::MAX_NETWORK_UPGRADES; idx++) { + if (nHeight == params.vUpgrades[idx].nActivationHeight) + return true; + } + + return false; +} diff --git a/src/consensus/upgrades.h b/src/consensus/upgrades.h new file mode 100644 index 00000000000..c6265553082 --- /dev/null +++ b/src/consensus/upgrades.h @@ -0,0 +1,77 @@ +// Copyright (c) 2018 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef ZCASH_CONSENSUS_UPGRADES_H +#define ZCASH_CONSENSUS_UPGRADES_H + +#include "consensus/params.h" + +enum UpgradeState { + UPGRADE_DISABLED, + UPGRADE_PENDING, + UPGRADE_ACTIVE +}; + +struct NUInfo { + /** Branch ID (a random non-zero 32-bit value) */ + uint32_t nBranchId; + /** User-facing name for the upgrade */ + std::string strName; + /** User-facing information string about the upgrade */ + std::string strInfo; +}; + +extern const struct NUInfo NetworkUpgradeInfo[]; + +/** + * Checks the state of a given network upgrade based on block height. + * Caller must check that the height is >= 0 (and handle unknown heights). + */ +UpgradeState NetworkUpgradeState( + int nHeight, + const Consensus::Params& params, + Consensus::UpgradeIndex idx); + +/** + * Returns true if the given network upgrade is active as of the given block + * height. Caller must check that the height is >= 0 (and handle unknown + * heights). + */ +bool NetworkUpgradeActive( + int nHeight, + const Consensus::Params& params, + Consensus::UpgradeIndex idx); + +/** + * Returns the index of the most recent upgrade as of the given block height + * (corresponding to the current "epoch"). Consensus::BASE_SPROUT is the + * default value if no upgrades are active. Caller must check that the height + * is >= 0 (and handle unknown heights). + */ +int CurrentEpoch(int nHeight, const Consensus::Params& params); + +/** + * Returns the branch ID of the most recent upgrade as of the given block height + * (corresponding to the current "epoch"), or 0 if no upgrades are active. + * Caller must check that the height is >= 0 (and handle unknown heights). + */ +uint32_t CurrentEpochBranchId(int nHeight, const Consensus::Params& params); + +/** + * Returns true if the given block height is the activation height for the given + * upgrade. + */ +bool IsActivationHeight( + int nHeight, + const Consensus::Params& params, + Consensus::UpgradeIndex upgrade); + +/** + * Returns true if the given block height is the activation height for any upgrade. + */ +bool IsActivationHeightForAnyUpgrade( + int nHeight, + const Consensus::Params& params); + +#endif // ZCASH_CONSENSUS_UPGRADES_H From b174b7e3304116967a0af21b65109ffcf41ba76a Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 31 Jan 2018 20:11:18 +0000 Subject: [PATCH 02/10] Allow changing network upgrade parameters on regtest Derived from upstream commit 56c87e92110f05d7452f1e85bf755246ffc77206: Allow changing BIP9 parameters on regtest --- src/chainparams.cpp | 11 +++++++++++ src/chainparams.h | 5 +++++ src/init.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/utilstrencodings.cpp | 8 ++++++++ src/utilstrencodings.h | 1 + 5 files changed, 61 insertions(+) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index fd928318d16..65bc5577c59 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -402,6 +402,12 @@ class CRegTestParams : public CChainParams { vFoundersRewardAddress = { "t2FwcEhFdNXuFMv1tcYwaBJtYVtMj8b1uTg" }; assert(vFoundersRewardAddress.size() <= consensus.GetLastFoundersRewardBlockHeight()); } + + void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivationHeight) + { + assert(idx > Consensus::BASE_SPROUT && idx < Consensus::MAX_NETWORK_UPGRADES); + consensus.vUpgrades[idx].nActivationHeight = nActivationHeight; + } }; static CRegTestParams regTestParams; @@ -475,3 +481,8 @@ std::string CChainParams::GetFoundersRewardAddressAtIndex(int i) const { assert(i >= 0 && i < vFoundersRewardAddress.size()); return vFoundersRewardAddress[i]; } + +void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivationHeight) +{ + regTestParams.UpdateNetworkUpgradeParameters(idx, nActivationHeight); +} diff --git a/src/chainparams.h b/src/chainparams.h index 10e2e6d15d6..f1d9b43c302 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -134,4 +134,9 @@ void SelectParams(CBaseChainParams::Network network); */ bool SelectParamsFromCommandLine(); +/** + * Allows modifying the network upgrade regtest parameters. + */ +void UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex idx, int nActivationHeight); + #endif // BITCOIN_CHAINPARAMS_H diff --git a/src/init.cpp b/src/init.cpp index 7df31ab1bbe..52b12083004 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -16,6 +16,7 @@ #endif #include "checkpoints.h" #include "compat/sanity.h" +#include "consensus/upgrades.h" #include "consensus/validation.h" #include "httpserver.h" #include "httprpc.h" @@ -44,8 +45,10 @@ #include #endif +#include #include #include +#include #include #include #include @@ -448,6 +451,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-fuzzmessagestest=", "Randomly fuzz 1 of every network messages"); strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", 1)); strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0)); + strUsage += HelpMessageOpt("-nuparams=hexBranchId:activationHeight", "Use given activation height for specified network upgrade (regtest-only)"); } string debugCategories = "addrman, alert, bench, coindb, db, estimatefee, http, libevent, lock, mempool, net, partitioncheck, pow, proxy, prune, " "rand, reindex, rpc, selectcoins, tor, zmq, zrpc, zrpcunsafe (implies zrpc)"; // Don't translate these @@ -1035,6 +1039,38 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } + if (!mapMultiArgs["-nuparams"].empty()) { + // Allow overriding network upgrade parameters for testing + if (Params().NetworkIDString() != "regtest") { + return InitError("Network upgrade parameters may only be overridden on regtest."); + } + const vector& deployments = mapMultiArgs["-nuparams"]; + for (auto i : deployments) { + std::vector vDeploymentParams; + boost::split(vDeploymentParams, i, boost::is_any_of(":")); + if (vDeploymentParams.size() != 2) { + return InitError("Network upgrade parameters malformed, expecting hexBranchId:activationHeight"); + } + int nActivationHeight; + if (!ParseInt32(vDeploymentParams[1], &nActivationHeight)) { + return InitError(strprintf("Invalid nActivationHeight (%s)", vDeploymentParams[1])); + } + bool found = false; + // Exclude Sprout from upgrades + for (auto i = Consensus::BASE_SPROUT + 1; i < Consensus::MAX_NETWORK_UPGRADES; ++i) + { + if (vDeploymentParams[0].compare(HexInt(NetworkUpgradeInfo[i].nBranchId)) == 0) { + UpdateNetworkUpgradeParameters(Consensus::UpgradeIndex(i), nActivationHeight); + found = true; + LogPrintf("Setting network upgrade activation parameters for %s to height=%d\n", vDeploymentParams[0], nActivationHeight); + break; + } + } + if (!found) { + return InitError(strprintf("Invalid network upgrade (%s)", vDeploymentParams[0])); + } + } + } // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log diff --git a/src/utilstrencodings.cpp b/src/utilstrencodings.cpp index 054992cfb66..4648eb0bbc6 100644 --- a/src/utilstrencodings.cpp +++ b/src/utilstrencodings.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include using namespace std; @@ -46,6 +47,13 @@ string SanitizeFilename(const string& str) return strResult; } +std::string HexInt(uint32_t val) +{ + std::stringstream ss; + ss << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex << val; + return ss.str(); +} + const signed char p_util_hexdigit[256] = { -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index d6973a1305f..173bbefd086 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -24,6 +24,7 @@ std::string SanitizeFilename(const std::string& str); std::string SanitizeString(const std::string& str); +std::string HexInt(uint32_t val); std::vector ParseHex(const char* psz); std::vector ParseHex(const std::string& str); signed char HexDigit(char c); From f52da9113997ba9496232c54542a9a7b6f253f6e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 17 Jan 2018 11:42:56 +0000 Subject: [PATCH 03/10] Test network upgrade logic Also demonstrates how to specify a network upgrade. --- src/Makefile.gtest.include | 1 + src/chainparams.cpp | 6 ++ src/consensus/params.h | 1 + src/consensus/upgrades.cpp | 5 ++ src/gtest/test_upgrades.cpp | 142 ++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+) create mode 100644 src/gtest/test_upgrades.cpp diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index 522bfa84aee..eaad8a79f10 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -33,6 +33,7 @@ zcash_gtest_SOURCES += \ gtest/test_random.cpp \ gtest/test_rpc.cpp \ gtest/test_transaction.cpp \ + gtest/test_upgrades.cpp \ gtest/test_validation.cpp \ gtest/test_circuit.cpp \ gtest/test_txid.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 65bc5577c59..6ce3c28f773 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -92,6 +92,8 @@ class CMainParams : public CChainParams { consensus.nPowTargetSpacing = 2.5 * 60; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; /** * The message start string should be awesome! ⓩ❤ @@ -246,6 +248,8 @@ class CTestNetParams : public CChainParams { consensus.nPowTargetSpacing = 2.5 * 60; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; pchMessageStart[0] = 0xfa; pchMessageStart[1] = 0x1a; @@ -349,6 +353,8 @@ class CRegTestParams : public CChainParams { consensus.nPowTargetSpacing = 2.5 * 60; consensus.vUpgrades[Consensus::BASE_SPROUT].nActivationHeight = Consensus::NetworkUpgrade::ALWAYS_ACTIVE; + consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; pchMessageStart[0] = 0xaa; pchMessageStart[1] = 0xe8; diff --git a/src/consensus/params.h b/src/consensus/params.h index 59d725cbac0..c427d26a75c 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -21,6 +21,7 @@ namespace Consensus { enum UpgradeIndex { // Sprout must be first BASE_SPROUT, + UPGRADE_TESTDUMMY, // NOTE: Also add new upgrades to NetworkUpgradeInfo in upgrades.cpp MAX_NETWORK_UPGRADES }; diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index 1191e02ff01..8e4b2f024e9 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -13,6 +13,11 @@ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = { /*.nBranchId =*/ 0, /*.strName =*/ "Sprout", /*.strInfo =*/ "The Zcash network at launch", + }, + { + /*.nBranchId =*/ 0x74736554, + /*.strName =*/ "Test dummy", + /*.strInfo =*/ "Test dummy info", } }; diff --git a/src/gtest/test_upgrades.cpp b/src/gtest/test_upgrades.cpp new file mode 100644 index 00000000000..3312e05693e --- /dev/null +++ b/src/gtest/test_upgrades.cpp @@ -0,0 +1,142 @@ +#include + +#include "chainparams.h" +#include "consensus/upgrades.h" + +class UpgradesTest : public ::testing::Test { +protected: + virtual void SetUp() { + } + + virtual void TearDown() { + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + } +}; + +TEST_F(UpgradesTest, NetworkUpgradeState) { + SelectParams(CBaseChainParams::REGTEST); + const Consensus::Params& params = Params().GetConsensus(); + + // Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT + EXPECT_EQ( + NetworkUpgradeState(0, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_DISABLED); + EXPECT_EQ( + NetworkUpgradeState(1000000, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_DISABLED); + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + + EXPECT_EQ( + NetworkUpgradeState(0, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_ACTIVE); + EXPECT_EQ( + NetworkUpgradeState(1000000, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_ACTIVE); + + int nActivationHeight = 100; + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, nActivationHeight); + + EXPECT_EQ( + NetworkUpgradeState(0, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_PENDING); + EXPECT_EQ( + NetworkUpgradeState(nActivationHeight - 1, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_PENDING); + EXPECT_EQ( + NetworkUpgradeState(nActivationHeight, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_ACTIVE); + EXPECT_EQ( + NetworkUpgradeState(1000000, params, Consensus::UPGRADE_TESTDUMMY), + UPGRADE_ACTIVE); +} + +TEST_F(UpgradesTest, CurrentEpoch) { + SelectParams(CBaseChainParams::REGTEST); + const Consensus::Params& params = Params().GetConsensus(); + auto nBranchId = NetworkUpgradeInfo[Consensus::UPGRADE_TESTDUMMY].nBranchId; + + // Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT + EXPECT_EQ(CurrentEpoch(0, params), Consensus::BASE_SPROUT); + EXPECT_EQ(CurrentEpochBranchId(0, params), 0); + EXPECT_EQ(CurrentEpoch(1000000, params), Consensus::BASE_SPROUT); + EXPECT_EQ(CurrentEpochBranchId(1000000, params), 0); + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + + EXPECT_EQ(CurrentEpoch(0, params), Consensus::UPGRADE_TESTDUMMY); + EXPECT_EQ(CurrentEpochBranchId(0, params), nBranchId); + EXPECT_EQ(CurrentEpoch(1000000, params), Consensus::UPGRADE_TESTDUMMY); + EXPECT_EQ(CurrentEpochBranchId(1000000, params), nBranchId); + + int nActivationHeight = 100; + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, nActivationHeight); + + EXPECT_EQ(CurrentEpoch(0, params), Consensus::BASE_SPROUT); + EXPECT_EQ(CurrentEpochBranchId(0, params), 0); + EXPECT_EQ(CurrentEpoch(nActivationHeight - 1, params), Consensus::BASE_SPROUT); + EXPECT_EQ(CurrentEpochBranchId(nActivationHeight - 1, params), 0); + EXPECT_EQ(CurrentEpoch(nActivationHeight, params), Consensus::UPGRADE_TESTDUMMY); + EXPECT_EQ(CurrentEpochBranchId(nActivationHeight, params), nBranchId); + EXPECT_EQ(CurrentEpoch(1000000, params), Consensus::UPGRADE_TESTDUMMY); + EXPECT_EQ(CurrentEpochBranchId(1000000, params), nBranchId); +} + +TEST_F(UpgradesTest, IsActivationHeight) { + SelectParams(CBaseChainParams::REGTEST); + const Consensus::Params& params = Params().GetConsensus(); + + // Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT + EXPECT_FALSE(IsActivationHeight(-1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(0, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(1000000, params, Consensus::UPGRADE_TESTDUMMY)); + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + + EXPECT_FALSE(IsActivationHeight(-1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_TRUE(IsActivationHeight(0, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(1000000, params, Consensus::UPGRADE_TESTDUMMY)); + + int nActivationHeight = 100; + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, nActivationHeight); + + EXPECT_FALSE(IsActivationHeight(-1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(0, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(nActivationHeight - 1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_TRUE(IsActivationHeight(nActivationHeight, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(nActivationHeight + 1, params, Consensus::UPGRADE_TESTDUMMY)); + EXPECT_FALSE(IsActivationHeight(1000000, params, Consensus::UPGRADE_TESTDUMMY)); +} + +TEST_F(UpgradesTest, IsActivationHeightForAnyUpgrade) { + SelectParams(CBaseChainParams::REGTEST); + const Consensus::Params& params = Params().GetConsensus(); + + // Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(-1, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(0, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1000000, params)); + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(-1, params)); + EXPECT_TRUE(IsActivationHeightForAnyUpgrade(0, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1000000, params)); + + int nActivationHeight = 100; + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, nActivationHeight); + + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(-1, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(0, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(nActivationHeight - 1, params)); + EXPECT_TRUE(IsActivationHeightForAnyUpgrade(nActivationHeight, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(nActivationHeight + 1, params)); + EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1000000, params)); +} From 89f20450c2adda8b99c3b83ca9b0774fbf4c94e3 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 18 Mar 2016 17:20:12 +0100 Subject: [PATCH 04/10] Add rewind logic to deal with post-fork software updates Includes logic for dealing with pruning by Suhas Daftuar. --- src/chain.h | 2 + src/init.cpp | 8 ++++ src/main.cpp | 126 ++++++++++++++++++++++++++++++++++++++++++++------- src/main.h | 3 ++ 4 files changed, 123 insertions(+), 16 deletions(-) diff --git a/src/chain.h b/src/chain.h index a3b1b7ae992..28f77e006b7 100644 --- a/src/chain.h +++ b/src/chain.h @@ -94,6 +94,8 @@ enum BlockStatus: uint32_t { BLOCK_FAILED_VALID = 32, //! stage after last reached validness failed BLOCK_FAILED_CHILD = 64, //! descends from failed block BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD, + + BLOCK_OPT_WITNESS = 128, //! block data in blk*.data was received with a witness-enforcing client }; /** The block chain is a tree shaped structure starting with the diff --git a/src/init.cpp b/src/init.cpp index 52b12083004..e45fe1659b4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1422,6 +1422,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) break; } + if (!fReindex) { + uiInterface.InitMessage(_("Rewinding blocks...")); + if (!RewindBlockIndex(chainparams)) { + strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain"); + break; + } + } + uiInterface.InitMessage(_("Verifying blocks...")); if (fHavePruned && GetArg("-checkblocks", 288) > MIN_BLOCKS_TO_KEEP) { LogPrintf("Prune: pruned datadir may not have more than %d blocks; -checkblocks=%d may fail\n", diff --git a/src/main.cpp b/src/main.cpp index d8e47caac2a..8464c92475c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -543,6 +543,9 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc CBlockIndex* pindex = (*mi).second; if (chain.Contains(pindex)) return pindex; + if (pindex->GetAncestor(chain.Height()) == chain.Tip()) { + return chain.Tip(); + } } } return chain.Genesis(); @@ -2405,7 +2408,7 @@ void static UpdateTip(CBlockIndex *pindexNew) { } /** Disconnect chainActive's tip. */ -bool static DisconnectTip(CValidationState &state) { +bool static DisconnectTip(CValidationState &state, bool fBare = false) { CBlockIndex *pindexDelete = chainActive.Tip(); assert(pindexDelete); mempool.check(pcoinsTip); @@ -2427,21 +2430,25 @@ bool static DisconnectTip(CValidationState &state) { // 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 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 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); + } + // Update chainActive and related variables. UpdateTip(pindexDelete->pprev); // Get the current commitment tree @@ -2848,6 +2855,9 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl pindexNew->nDataPos = pos.nPos; pindexNew->nUndoPos = 0; pindexNew->nStatus |= BLOCK_HAVE_DATA; + if (IsWitnessEnabled(pindexNew->pprev, Params().GetConsensus())) { + pindexNew->nStatus |= BLOCK_OPT_WITNESS; + } pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); setDirtyBlockIndex.insert(pindexNew); @@ -3738,6 +3748,90 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth return true; } +bool RewindBlockIndex(const CChainParams& params) +{ + LOCK(cs_main); + + int nHeight = 1; + while (nHeight <= chainActive.Height()) { + if (IsWitnessEnabled(chainActive[nHeight - 1], params.GetConsensus()) && !(chainActive[nHeight]->nStatus & BLOCK_OPT_WITNESS)) { + break; + } + nHeight++; + } + + // nHeight is now the height of the first insufficiently-validated block, or tipheight + 1 + 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 (IsWitnessEnabled(pindexIter->pprev, params.GetConsensus()) && !(pindexIter->nStatus & BLOCK_OPT_WITNESS) && !chainActive.Contains(pindexIter)) { + // Reduce validity + pindexIter->nStatus = std::min(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 storage location. + pindexIter->nFile = 0; + pindexIter->nDataPos = 0; + pindexIter->nUndoPos = 0; + // Remove various other things + pindexIter->nTx = 0; + pindexIter->nChainTx = 0; + pindexIter->nSequenceId = 0; + // Make sure it gets written. + setDirtyBlockIndex.insert(pindexIter); + // Update indexes + setBlockIndexCandidates.erase(pindexIter); + std::pair::iterator, std::multimap::iterator> 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); diff --git a/src/main.h b/src/main.h index 886542db7e6..c8378e4e90e 100644 --- a/src/main.h +++ b/src/main.h @@ -436,6 +436,9 @@ bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBloc +/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */ +bool RewindBlockIndex(const CChainParams& params); + class CBlockFileInfo { public: From 9e851450abc5495db2938eadbcde869f1d981689 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 27 Jan 2018 23:37:43 +0000 Subject: [PATCH 05/10] Adjust rewind logic to use the network upgrade mechanism --- src/chain.h | 14 +++++++++++- src/init.cpp | 2 +- src/main.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++---------- src/main.h | 6 +++++- src/txdb.cpp | 1 + 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/chain.h b/src/chain.h index 28f77e006b7..c0239d5d282 100644 --- a/src/chain.h +++ b/src/chain.h @@ -95,9 +95,13 @@ enum BlockStatus: uint32_t { BLOCK_FAILED_CHILD = 64, //! descends from failed block BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD, - BLOCK_OPT_WITNESS = 128, //! block data in blk*.data was received with a witness-enforcing client + BLOCK_ACTIVATES_UPGRADE = 128, //! block activates a network upgrade }; +//! Short-hand for the highest consensus validity we implement. +//! Blocks with this validity are assumed to satisfy all consensus rules. +static const BlockStatus BLOCK_VALID_CONSENSUS = BLOCK_VALID_SCRIPTS; + /** The block chain is a tree shaped structure starting with the * genesis block at the root, with each block potentially having multiple * candidates to be the next block. A blockindex may have multiple pprev pointing @@ -142,6 +146,11 @@ class CBlockIndex //! Verification status of this block. See enum BlockStatus unsigned int nStatus; + //! Branch ID corresponding to the consensus rules used to validate this block. + //! Only accurate if block validity is BLOCK_VALID_CONSENSUS. + //! Persisted at each activation height, memory-only for intervening blocks. + uint32_t nConsensusBranchId; + //! The anchor for the tree state up to the start of this block uint256 hashAnchor; @@ -182,6 +191,7 @@ class CBlockIndex nTx = 0; nChainTx = 0; nStatus = 0; + nConsensusBranchId = 0; hashAnchor = uint256(); hashAnchorEnd = uint256(); nSequenceId = 0; @@ -343,6 +353,8 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(VARINT(nDataPos)); if (nStatus & BLOCK_HAVE_UNDO) READWRITE(VARINT(nUndoPos)); + if (nStatus & BLOCK_ACTIVATES_UPGRADE) + READWRITE(nConsensusBranchId); READWRITE(hashAnchor); // block header diff --git a/src/init.cpp b/src/init.cpp index e45fe1659b4..b8143d1783c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1425,7 +1425,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (!fReindex) { uiInterface.InitMessage(_("Rewinding blocks...")); if (!RewindBlockIndex(chainparams)) { - strLoadError = _("Unable to rewind the database to a pre-fork state. You will need to redownload the blockchain"); + strLoadError = _("Unable to rewind the database to a pre-upgrade state. You will need to redownload the blockchain"); break; } } diff --git a/src/main.cpp b/src/main.cpp index 8464c92475c..9aed20d6457 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,6 +13,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "checkqueue.h" +#include "consensus/upgrades.h" #include "consensus/validation.h" #include "deprecation.h" #include "init.h" @@ -2222,6 +2223,17 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin pindex->nStatus |= BLOCK_HAVE_UNDO; } + // Now that all consensus rules have been validated, set nConsensusBranchId. + // Move this if BLOCK_VALID_CONSENSUS is ever altered. + static_assert(BLOCK_VALID_CONSENSUS == BLOCK_VALID_SCRIPTS, + "nConsensusBranchId must be set after all consensus rules have been validated."); + if (IsActivationHeightForAnyUpgrade(pindex->nHeight, Params().GetConsensus())) { + pindex->nStatus |= BLOCK_ACTIVATES_UPGRADE; + pindex->nConsensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus()); + } else if (pindex->pprev) { + pindex->nConsensusBranchId = pindex->pprev->nConsensusBranchId; + } + pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); setDirtyBlockIndex.insert(pindex); } @@ -2855,9 +2867,6 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl pindexNew->nDataPos = pos.nPos; pindexNew->nUndoPos = 0; pindexNew->nStatus |= BLOCK_HAVE_DATA; - if (IsWitnessEnabled(pindexNew->pprev, Params().GetConsensus())) { - pindexNew->nStatus |= BLOCK_OPT_WITNESS; - } pindexNew->RaiseValidity(BLOCK_VALID_TRANSACTIONS); setDirtyBlockIndex.insert(pindexNew); @@ -3565,6 +3574,13 @@ bool static LoadBlockIndexDB() 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. + // Activation blocks will have non-zero branch IDs (read from disk). + if (pindex->IsValid(BLOCK_VALID_CONSENSUS) && pindex->nConsensusBranchId == 0 && pindex->pprev) { + pindex->nConsensusBranchId = pindex->pprev->nConsensusBranchId; + } if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == NULL)) setBlockIndexCandidates.insert(pindex); if (pindex->nStatus & BLOCK_FAILED_MASK && (!pindexBestInvalid || pindex->nChainWork > pindexBestInvalid->nChainWork)) @@ -3752,9 +3768,25 @@ bool RewindBlockIndex(const CChainParams& params) { LOCK(cs_main); + // RewindBlockIndex is called after LoadBlockIndex, so at this point every block + // index will have nConsensusBranchId set based on the values previously persisted + // to disk. By definition, a non-zero nConsensusBranchId 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. + // - nConsensusBranchId 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->nConsensusBranchId == CurrentEpochBranchId(pindex->nHeight, consensus); + }; + int nHeight = 1; while (nHeight <= chainActive.Height()) { - if (IsWitnessEnabled(chainActive[nHeight - 1], params.GetConsensus()) && !(chainActive[nHeight]->nStatus & BLOCK_OPT_WITNESS)) { + if (!sufficientlyValidated(chainActive[nHeight])) { break; } nHeight++; @@ -3791,24 +3823,31 @@ bool RewindBlockIndex(const CChainParams& params) // 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 (IsWitnessEnabled(pindexIter->pprev, params.GetConsensus()) && !(pindexIter->nStatus & BLOCK_OPT_WITNESS) && !chainActive.Contains(pindexIter)) { + if (!sufficientlyValidated(pindexIter) && !chainActive.Contains(pindexIter)) { // Reduce validity - pindexIter->nStatus = std::min(pindexIter->nStatus & BLOCK_VALID_MASK, BLOCK_VALID_TREE) | (pindexIter->nStatus & ~BLOCK_VALID_MASK); - // Remove have-data flags. + pindexIter->nStatus = + std::min(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 storage location. + // Remove branch ID + pindexIter->nStatus &= ~BLOCK_ACTIVATES_UPGRADE; + pindexIter->nConsensusBranchId = 0; + // 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. + // Make sure it gets written setDirtyBlockIndex.insert(pindexIter); - // Update indexes + // Update indices setBlockIndexCandidates.erase(pindexIter); - std::pair::iterator, std::multimap::iterator> ret = mapBlocksUnlinked.equal_range(pindexIter->pprev); + auto ret = mapBlocksUnlinked.equal_range(pindexIter->pprev); while (ret.first != ret.second) { if (ret.first->second == pindexIter) { mapBlocksUnlinked.erase(ret.first++); diff --git a/src/main.h b/src/main.h index c8378e4e90e..03b0464eb7d 100644 --- a/src/main.h +++ b/src/main.h @@ -436,7 +436,11 @@ bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, CBloc -/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */ +/** + * When there are blocks in the active chain with missing data (e.g. if the + * activation height and branch ID of a particular upgrade have been altered), + * rewind the chainstate and remove them from the block index. + */ bool RewindBlockIndex(const CChainParams& params); class CBlockFileInfo diff --git a/src/txdb.cpp b/src/txdb.cpp index e1e29d9aca8..ec0234ba221 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -309,6 +309,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nNonce = diskindex.nNonce; pindexNew->nSolution = diskindex.nSolution; pindexNew->nStatus = diskindex.nStatus; + pindexNew->nConsensusBranchId = diskindex.nConsensusBranchId; pindexNew->nTx = diskindex.nTx; pindexNew->nSproutValue = diskindex.nSproutValue; From 548683767c269124519617c4b747528d24e844cc Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 1 Feb 2018 00:48:26 +0000 Subject: [PATCH 06/10] Add Overwinter to upgrade list --- src/chainparams.cpp | 6 ++++++ src/consensus/params.h | 1 + src/consensus/upgrades.cpp | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 6ce3c28f773..22d425991db 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -94,6 +94,8 @@ class CMainParams : public CChainParams { Consensus::NetworkUpgrade::ALWAYS_ACTIVE; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; /** * The message start string should be awesome! ⓩ❤ @@ -250,6 +252,8 @@ class CTestNetParams : public CChainParams { Consensus::NetworkUpgrade::ALWAYS_ACTIVE; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; pchMessageStart[0] = 0xfa; pchMessageStart[1] = 0x1a; @@ -355,6 +359,8 @@ class CRegTestParams : public CChainParams { Consensus::NetworkUpgrade::ALWAYS_ACTIVE; consensus.vUpgrades[Consensus::UPGRADE_TESTDUMMY].nActivationHeight = Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; + consensus.vUpgrades[Consensus::UPGRADE_OVERWINTER].nActivationHeight = + Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT; pchMessageStart[0] = 0xaa; pchMessageStart[1] = 0xe8; diff --git a/src/consensus/params.h b/src/consensus/params.h index c427d26a75c..53f8609d5f1 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -22,6 +22,7 @@ enum UpgradeIndex { // Sprout must be first BASE_SPROUT, UPGRADE_TESTDUMMY, + UPGRADE_OVERWINTER, // NOTE: Also add new upgrades to NetworkUpgradeInfo in upgrades.cpp MAX_NETWORK_UPGRADES }; diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index 8e4b2f024e9..f06e7063cc8 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -18,6 +18,11 @@ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = { /*.nBranchId =*/ 0x74736554, /*.strName =*/ "Test dummy", /*.strInfo =*/ "Test dummy info", + }, + { + /*.nBranchId =*/ 0x5ba81b19, + /*.strName =*/ "Overwinter", + /*.strInfo =*/ "TBD", } }; From 149d69e36b525619d9ad089d501f7937f5fc064d Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 2 Feb 2018 15:13:48 +0000 Subject: [PATCH 07/10] Add method for fetching the next activation height after a given block height --- src/consensus/upgrades.cpp | 18 ++++++++++++++++++ src/consensus/upgrades.h | 10 ++++++++++ src/gtest/test_upgrades.cpp | 31 +++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index f06e7063cc8..42089bbb16f 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -102,3 +102,21 @@ bool IsActivationHeightForAnyUpgrade( return false; } + +boost::optional NextActivationHeight( + int nHeight, + const Consensus::Params& params) +{ + if (nHeight < 0) { + return boost::none; + } + + // Don't count Sprout as an activation height + for (auto idx = Consensus::BASE_SPROUT + 1; idx < Consensus::MAX_NETWORK_UPGRADES; idx++) { + if (NetworkUpgradeState(nHeight, params, Consensus::UpgradeIndex(idx)) == UPGRADE_PENDING) { + return params.vUpgrades[idx].nActivationHeight; + } + } + + return boost::none; +} diff --git a/src/consensus/upgrades.h b/src/consensus/upgrades.h index c6265553082..0c5462c2e41 100644 --- a/src/consensus/upgrades.h +++ b/src/consensus/upgrades.h @@ -7,6 +7,8 @@ #include "consensus/params.h" +#include + enum UpgradeState { UPGRADE_DISABLED, UPGRADE_PENDING, @@ -74,4 +76,12 @@ bool IsActivationHeightForAnyUpgrade( int nHeight, const Consensus::Params& params); +/** + * Returns the activation height for the next upgrade after the given block height, + * or boost::none if there are no more known upgrades. + */ +boost::optional NextActivationHeight( + int nHeight, + const Consensus::Params& params); + #endif // ZCASH_CONSENSUS_UPGRADES_H diff --git a/src/gtest/test_upgrades.cpp b/src/gtest/test_upgrades.cpp index 3312e05693e..1066a28d015 100644 --- a/src/gtest/test_upgrades.cpp +++ b/src/gtest/test_upgrades.cpp @@ -3,6 +3,8 @@ #include "chainparams.h" #include "consensus/upgrades.h" +#include + class UpgradesTest : public ::testing::Test { protected: virtual void SetUp() { @@ -140,3 +142,32 @@ TEST_F(UpgradesTest, IsActivationHeightForAnyUpgrade) { EXPECT_FALSE(IsActivationHeightForAnyUpgrade(nActivationHeight + 1, params)); EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1000000, params)); } + +TEST_F(UpgradesTest, NextActivationHeight) { + SelectParams(CBaseChainParams::REGTEST); + const Consensus::Params& params = Params().GetConsensus(); + + // Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT + EXPECT_EQ(NextActivationHeight(-1, params), boost::none); + EXPECT_EQ(NextActivationHeight(0, params), boost::none); + EXPECT_EQ(NextActivationHeight(1, params), boost::none); + EXPECT_EQ(NextActivationHeight(1000000, params), boost::none); + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + + EXPECT_EQ(NextActivationHeight(-1, params), boost::none); + EXPECT_EQ(NextActivationHeight(0, params), boost::none); + EXPECT_EQ(NextActivationHeight(1, params), boost::none); + EXPECT_EQ(NextActivationHeight(1000000, params), boost::none); + + int nActivationHeight = 100; + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, nActivationHeight); + + EXPECT_EQ(NextActivationHeight(-1, params), boost::none); + EXPECT_EQ(NextActivationHeight(0, params), nActivationHeight); + EXPECT_EQ(NextActivationHeight(1, params), nActivationHeight); + EXPECT_EQ(NextActivationHeight(nActivationHeight - 1, params), nActivationHeight); + EXPECT_EQ(NextActivationHeight(nActivationHeight, params), boost::none); + EXPECT_EQ(NextActivationHeight(nActivationHeight + 1, params), boost::none); + EXPECT_EQ(NextActivationHeight(1000000, params), boost::none); +} From 828940b1634f80dcfed5491dc370324f209f2aaa Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 6 Feb 2018 12:39:20 +0000 Subject: [PATCH 08/10] Use a boost::optional for nCachedBranchId This enables us to distinguish between it being unset vs. being set to zero. --- src/chain.h | 20 +++++++++++++++----- src/main.cpp | 32 ++++++++++++++++++++------------ src/txdb.cpp | 2 +- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/chain.h b/src/chain.h index c0239d5d282..937e20bda00 100644 --- a/src/chain.h +++ b/src/chain.h @@ -147,9 +147,9 @@ class CBlockIndex unsigned int nStatus; //! Branch ID corresponding to the consensus rules used to validate this block. - //! Only accurate if block validity is BLOCK_VALID_CONSENSUS. + //! Only cached if block validity is BLOCK_VALID_CONSENSUS. //! Persisted at each activation height, memory-only for intervening blocks. - uint32_t nConsensusBranchId; + boost::optional nCachedBranchId; //! The anchor for the tree state up to the start of this block uint256 hashAnchor; @@ -191,7 +191,7 @@ class CBlockIndex nTx = 0; nChainTx = 0; nStatus = 0; - nConsensusBranchId = 0; + nCachedBranchId = boost::none; hashAnchor = uint256(); hashAnchorEnd = uint256(); nSequenceId = 0; @@ -353,8 +353,18 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(VARINT(nDataPos)); if (nStatus & BLOCK_HAVE_UNDO) READWRITE(VARINT(nUndoPos)); - if (nStatus & BLOCK_ACTIVATES_UPGRADE) - READWRITE(nConsensusBranchId); + if (nStatus & BLOCK_ACTIVATES_UPGRADE) { + if (ser_action.ForRead()) { + uint32_t branchId; + READWRITE(branchId); + nCachedBranchId = branchId; + } else { + // nCachedBranchId must always be set if BLOCK_ACTIVATES_UPGRADE is set. + assert(nCachedBranchId); + uint32_t branchId = *nCachedBranchId; + READWRITE(branchId); + } + } READWRITE(hashAnchor); // block header diff --git a/src/main.cpp b/src/main.cpp index 9aed20d6457..062507d0e6b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2223,15 +2223,15 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin pindex->nStatus |= BLOCK_HAVE_UNDO; } - // Now that all consensus rules have been validated, set nConsensusBranchId. + // 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, - "nConsensusBranchId must be set after all consensus rules have been validated."); + "nCachedBranchId must be set after all consensus rules have been validated."); if (IsActivationHeightForAnyUpgrade(pindex->nHeight, Params().GetConsensus())) { pindex->nStatus |= BLOCK_ACTIVATES_UPGRADE; - pindex->nConsensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus()); + pindex->nCachedBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus()); } else if (pindex->pprev) { - pindex->nConsensusBranchId = pindex->pprev->nConsensusBranchId; + pindex->nCachedBranchId = pindex->pprev->nCachedBranchId; } pindex->RaiseValidity(BLOCK_VALID_SCRIPTS); @@ -3577,9 +3577,15 @@ bool static LoadBlockIndexDB() // 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. - // Activation blocks will have non-zero branch IDs (read from disk). - if (pindex->IsValid(BLOCK_VALID_CONSENSUS) && pindex->nConsensusBranchId == 0 && pindex->pprev) { - pindex->nConsensusBranchId = pindex->pprev->nConsensusBranchId; + // 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 = NetworkUpgradeInfo[Consensus::BASE_SPROUT].nBranchId; } if (pindex->IsValid(BLOCK_VALID_TRANSACTIONS) && (pindex->nChainTx || pindex->pprev == NULL)) setBlockIndexCandidates.insert(pindex); @@ -3769,19 +3775,21 @@ bool RewindBlockIndex(const CChainParams& params) LOCK(cs_main); // RewindBlockIndex is called after LoadBlockIndex, so at this point every block - // index will have nConsensusBranchId set based on the values previously persisted - // to disk. By definition, a non-zero nConsensusBranchId means that the block was + // 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. - // - nConsensusBranchId for each block matches what we expect. + // - 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->nConsensusBranchId == CurrentEpochBranchId(pindex->nHeight, consensus); + return fFlagSet == fFlagExpected && + pindex->nCachedBranchId && + *pindex->nCachedBranchId == CurrentEpochBranchId(pindex->nHeight, consensus); }; int nHeight = 1; @@ -3832,7 +3840,7 @@ bool RewindBlockIndex(const CChainParams& params) pindexIter->nStatus &= ~(BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO); // Remove branch ID pindexIter->nStatus &= ~BLOCK_ACTIVATES_UPGRADE; - pindexIter->nConsensusBranchId = 0; + pindexIter->nCachedBranchId = boost::none; // Remove storage location pindexIter->nFile = 0; pindexIter->nDataPos = 0; diff --git a/src/txdb.cpp b/src/txdb.cpp index ec0234ba221..1b66d70b440 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -309,7 +309,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nNonce = diskindex.nNonce; pindexNew->nSolution = diskindex.nSolution; pindexNew->nStatus = diskindex.nStatus; - pindexNew->nConsensusBranchId = diskindex.nConsensusBranchId; + pindexNew->nCachedBranchId = diskindex.nCachedBranchId; pindexNew->nTx = diskindex.nTx; pindexNew->nSproutValue = diskindex.nSproutValue; From 5009136dc3ac32691d30c9c28758f9ebf8dd70e2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 6 Feb 2018 22:49:08 +0000 Subject: [PATCH 09/10] Change UI/log status message for block rewinding --- src/init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.cpp b/src/init.cpp index b8143d1783c..bd6e2e79ecd 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1423,7 +1423,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } if (!fReindex) { - uiInterface.InitMessage(_("Rewinding blocks...")); + uiInterface.InitMessage(_("Rewinding blocks if needed...")); if (!RewindBlockIndex(chainparams)) { strLoadError = _("Unable to rewind the database to a pre-upgrade state. You will need to redownload the blockchain"); break; From cad27eb77fe9e0ba8caa617c96f184423628d90c Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 6 Feb 2018 23:18:56 +0000 Subject: [PATCH 10/10] Update quote from ZIP 200 --- src/consensus/upgrades.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index 42089bbb16f..17606bc63d6 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -38,13 +38,15 @@ UpgradeState NetworkUpgradeState( if (nActivationHeight == Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT) { return UPGRADE_DISABLED; } else if (nHeight >= nActivationHeight) { - // From ZIP ???: + // From ZIP 200: // // ACTIVATION_HEIGHT - // The block height at which the network upgrade rules will come into effect. + // The non-zero block height at which the network upgrade rules will come + // into effect, and be enforced as part of the blockchain consensus. // // For removal of ambiguity, the block at height ACTIVATION_HEIGHT - 1 is - // subject to the pre-upgrade consensus rules. + // subject to the pre-upgrade consensus rules, and would be the last common + // block in the event of a persistent pre-upgrade branch. return UPGRADE_ACTIVE; } else { return UPGRADE_PENDING;