From 5b3bc9716ec1095b7a4624cfe6e1b03d739ab90f Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 25 Apr 2017 18:06:29 +1200 Subject: [PATCH 1/2] Implement automatic shutdown of deprecated Zcash versions Closes #2274. --- doc/release-process.md | 9 ++- src/Makefile.am | 2 + src/Makefile.gtest.include | 1 + src/clientversion.cpp | 2 +- src/clientversion.h | 1 + src/deprecation.cpp | 55 +++++++++++++++++ src/deprecation.h | 23 ++++++++ src/gtest/test_deprecation.cpp | 105 +++++++++++++++++++++++++++++++++ src/init.cpp | 2 + src/main.cpp | 5 ++ 10 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 src/deprecation.cpp create mode 100644 src/deprecation.h create mode 100644 src/gtest/test_deprecation.cpp diff --git a/doc/release-process.md b/doc/release-process.md index f86e5993..8b95123e 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -36,7 +36,9 @@ previous release: ### B1. Check that you are up-to-date with current master, then create a release branch. -### B2. Update (commit) version in sources. +### B2. Update (commit) version and deprecation in sources. + +Update the client version in these files: README.md src/clientversion.h @@ -56,6 +58,11 @@ In `configure.ac` and `clientversion.h`: - Change `CLIENT_VERSION_IS_RELEASE` to false while Zcash is in beta-test phase. +Update `APPROX_RELEASE_HEIGHT` and `WEEKS_UNTIL_DEPRECATION` in `src/deprecation.h` +so that `APPROX_RELEASE_HEIGHT` will be reached shortly after release, and +`WEEKS_UNTIL_DEPRECATION` is the number of weeks from release day until the +deprecation target (as defined by the current deprecation policy). + If this release changes the behavior of the protocol or fixes a serious bug, we may also wish to change the `PROTOCOL_VERSION` in `version.h`. diff --git a/src/Makefile.am b/src/Makefile.am index cb31b8a3..8a7b4aec 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -125,6 +125,7 @@ BITCOIN_CORE_H = \ core_io.h \ core_memusage.h \ eccryptoverify.h \ + deprecation.h \ ecwrapper.h \ hash.h \ httprpc.h \ @@ -215,6 +216,7 @@ libbitcoin_server_a_SOURCES = \ bloom.cpp \ chain.cpp \ checkpoints.cpp \ + deprecation.cpp \ httprpc.cpp \ httpserver.cpp \ init.cpp \ diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index daa10ca4..e4e12686 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -19,6 +19,7 @@ zcash_gtest_SOURCES += \ endif zcash_gtest_SOURCES += \ gtest/test_tautology.cpp \ + gtest/test_deprecation.cpp \ gtest/test_equihash.cpp \ gtest/test_joinsplit.cpp \ gtest/test_keystore.cpp \ diff --git a/src/clientversion.cpp b/src/clientversion.cpp index 1e3eccbe..ae67e678 100644 --- a/src/clientversion.cpp +++ b/src/clientversion.cpp @@ -100,7 +100,7 @@ const std::string CLIENT_NAME("MagicBean"); const std::string CLIENT_BUILD(BUILD_DESC CLIENT_VERSION_SUFFIX); const std::string CLIENT_DATE(BUILD_DATE); -static std::string FormatVersion(int nVersion) +std::string FormatVersion(int nVersion) { if (nVersion % 100 < 25) return strprintf("%d.%d.%d-beta%d", nVersion / 1000000, (nVersion / 10000) % 100, (nVersion / 100) % 100, (nVersion % 100)+1); diff --git a/src/clientversion.h b/src/clientversion.h index bba551ee..b739ed73 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -63,6 +63,7 @@ extern const std::string CLIENT_BUILD; extern const std::string CLIENT_DATE; +std::string FormatVersion(int nVersion); std::string FormatFullVersion(); std::string FormatSubVersion(const std::string& name, int nClientVersion, const std::vector& comments); diff --git a/src/deprecation.cpp b/src/deprecation.cpp new file mode 100644 index 00000000..7f574931 --- /dev/null +++ b/src/deprecation.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2017 The Zcash developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "deprecation.h" + +#include "clientversion.h" +#include "init.h" +#include "ui_interface.h" +#include "util.h" + +static const std::string CLIENT_VERSION_STR = FormatVersion(CLIENT_VERSION); + +void EnforceNodeDeprecation(int nHeight, bool forceLogging) { + int blocksToDeprecation = DEPRECATION_HEIGHT - nHeight; + bool disableDeprecation = (GetArg("-disabledeprecation", "") == CLIENT_VERSION_STR); + if (blocksToDeprecation <= 0) { + // In order to ensure we only log once per process when deprecation is + // disabled (to avoid log spam), we only need to log in two cases: + // - The deprecating block just arrived + // - This can be triggered more than once if a block chain reorg + // occurs, but that's an irregular event that won't cause spam. + // - The node is starting + if (blocksToDeprecation == 0 || forceLogging) { + auto msg = strprintf(_("This version has been deprecated as of block height %d."), + DEPRECATION_HEIGHT) + " " + + _("You should upgrade to the latest version of Zcash."); + if (!disableDeprecation) { + msg += " " + strprintf(_("To disable deprecation for this version, set %s%s."), + "-disabledeprecation=", CLIENT_VERSION_STR); + } + LogPrintf("*** %s\n", msg); + uiInterface.ThreadSafeMessageBox(msg, "", CClientUIInterface::MSG_ERROR); + } + if (!disableDeprecation) { + StartShutdown(); + } + } else if (blocksToDeprecation == DEPRECATION_WARN_LIMIT || + (blocksToDeprecation < DEPRECATION_WARN_LIMIT && forceLogging)) { + std::string msg; + if (disableDeprecation) { + msg = strprintf(_("This version will be deprecated at block height %d."), + DEPRECATION_HEIGHT) + " " + + _("You should upgrade to the latest version of Zcash."); + } else { + msg = strprintf(_("This version will be deprecated at block height %d, and will automatically shut down."), + DEPRECATION_HEIGHT) + " " + + _("You should upgrade to the latest version of Zcash.") + " " + + strprintf(_("To disable deprecation for this version, set %s%s."), + "-disabledeprecation=", CLIENT_VERSION_STR); + } + LogPrintf("*** %s\n", msg); + uiInterface.ThreadSafeMessageBox(msg, "", CClientUIInterface::MSG_WARNING); + } +} \ No newline at end of file diff --git a/src/deprecation.h b/src/deprecation.h new file mode 100644 index 00000000..f109384a --- /dev/null +++ b/src/deprecation.h @@ -0,0 +1,23 @@ +// Copyright (c) 2017 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_DEPRECATION_H +#define ZCASH_DEPRECATION_H + +// Deprecation policy is 4th third-Tuesday after a release +static const int APPROX_RELEASE_HEIGHT = 115000; +static const int WEEKS_UNTIL_DEPRECATION = 18; +static const int DEPRECATION_HEIGHT = APPROX_RELEASE_HEIGHT + (WEEKS_UNTIL_DEPRECATION * 7 * 24 * 24); + +// Number of blocks before deprecation to warn users +static const int DEPRECATION_WARN_LIMIT = 14 * 24 * 24; // 2 weeks + +/** + * Checks whether the node is deprecated based on the current block height, and + * shuts down the node with an error if so (and deprecation is not disabled for + * the current client version). + */ +void EnforceNodeDeprecation(int nHeight, bool forceLogging=false); + +#endif // ZCASH_DEPRECATION_H diff --git a/src/gtest/test_deprecation.cpp b/src/gtest/test_deprecation.cpp new file mode 100644 index 00000000..72d98cf9 --- /dev/null +++ b/src/gtest/test_deprecation.cpp @@ -0,0 +1,105 @@ +#include +#include + +#include "clientversion.h" +#include "deprecation.h" +#include "init.h" +#include "ui_interface.h" +#include "util.h" + +using ::testing::StrictMock; + +static const std::string CLIENT_VERSION_STR = FormatVersion(CLIENT_VERSION); +extern std::atomic fRequestShutdown; + +class MockUIInterface { +public: + MOCK_METHOD3(ThreadSafeMessageBox, bool(const std::string& message, + const std::string& caption, + unsigned int style)); +}; + +static bool ThreadSafeMessageBox(MockUIInterface *mock, + const std::string& message, + const std::string& caption, + unsigned int style) +{ + mock->ThreadSafeMessageBox(message, caption, style); +} + +class DeprecationTest : public ::testing::Test { +protected: + virtual void SetUp() { + uiInterface.ThreadSafeMessageBox.disconnect_all_slots(); + uiInterface.ThreadSafeMessageBox.connect(boost::bind(ThreadSafeMessageBox, &mock_, _1, _2, _3)); + } + + virtual void TearDown() { + fRequestShutdown = false; + mapArgs["-disabledeprecation"] = ""; + } + + StrictMock mock_; +}; + +TEST_F(DeprecationTest, NonDeprecatedNodeKeepsRunning) { + EXPECT_FALSE(ShutdownRequested()); + EnforceNodeDeprecation(DEPRECATION_HEIGHT - DEPRECATION_WARN_LIMIT - 1); + EXPECT_FALSE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, NodeNearDeprecationIsWarned) { + EXPECT_FALSE(ShutdownRequested()); + EXPECT_CALL(mock_, ThreadSafeMessageBox(::testing::_, "", CClientUIInterface::MSG_WARNING)); + EnforceNodeDeprecation(DEPRECATION_HEIGHT - DEPRECATION_WARN_LIMIT); + EXPECT_FALSE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, NodeNearDeprecationWarningIsNotDuplicated) { + EXPECT_FALSE(ShutdownRequested()); + EnforceNodeDeprecation(DEPRECATION_HEIGHT - DEPRECATION_WARN_LIMIT + 1); + EXPECT_FALSE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, NodeNearDeprecationWarningIsRepeatedOnStartup) { + EXPECT_FALSE(ShutdownRequested()); + EXPECT_CALL(mock_, ThreadSafeMessageBox(::testing::_, "", CClientUIInterface::MSG_WARNING)); + EnforceNodeDeprecation(DEPRECATION_HEIGHT - DEPRECATION_WARN_LIMIT + 1, true); + EXPECT_FALSE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, DeprecatedNodeShutsDown) { + EXPECT_FALSE(ShutdownRequested()); + EXPECT_CALL(mock_, ThreadSafeMessageBox(::testing::_, "", CClientUIInterface::MSG_ERROR)); + EnforceNodeDeprecation(DEPRECATION_HEIGHT); + EXPECT_TRUE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, DeprecatedNodeErrorIsNotDuplicated) { + EXPECT_FALSE(ShutdownRequested()); + EnforceNodeDeprecation(DEPRECATION_HEIGHT + 1); + EXPECT_TRUE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, DeprecatedNodeErrorIsRepeatedOnStartup) { + EXPECT_FALSE(ShutdownRequested()); + EXPECT_CALL(mock_, ThreadSafeMessageBox(::testing::_, "", CClientUIInterface::MSG_ERROR)); + EnforceNodeDeprecation(DEPRECATION_HEIGHT + 1, true); + EXPECT_TRUE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, DeprecatedNodeShutsDownIfOldVersionDisabled) { + EXPECT_FALSE(ShutdownRequested()); + mapArgs["-disabledeprecation"] = "1.0.0"; + EXPECT_CALL(mock_, ThreadSafeMessageBox(::testing::_, "", CClientUIInterface::MSG_ERROR)); + EnforceNodeDeprecation(DEPRECATION_HEIGHT); + EXPECT_TRUE(ShutdownRequested()); +} + +TEST_F(DeprecationTest, DeprecatedNodeKeepsRunningIfCurrentVersionDisabled) { + EXPECT_FALSE(ShutdownRequested()); + mapArgs["-disabledeprecation"] = CLIENT_VERSION_STR; + EXPECT_CALL(mock_, ThreadSafeMessageBox(::testing::_, "", CClientUIInterface::MSG_ERROR)); + EnforceNodeDeprecation(DEPRECATION_HEIGHT); + EXPECT_FALSE(ShutdownRequested()); +} \ No newline at end of file diff --git a/src/init.cpp b/src/init.cpp index 2c905bd3..34df422f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -344,6 +344,8 @@ std::string HelpMessage(HelpMessageMode mode) #endif } strUsage += HelpMessageOpt("-datadir=", _("Specify data directory")); + strUsage += HelpMessageOpt("-disabledeprecation=", strprintf(_("Disable block-height node deprecation and automatic shutdown (example: -disabledeprecation=%s)"), + FormatVersion(CLIENT_VERSION))); strUsage += HelpMessageOpt("-exportdir=", _("Specify directory to be used when exporting data")); 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")); diff --git a/src/main.cpp b/src/main.cpp index ab91ee41..a3de5e6d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,6 +14,7 @@ #include "checkpoints.h" #include "checkqueue.h" #include "consensus/validation.h" +#include "deprecation.h" #include "init.h" #include "merkleblock.h" #include "metrics.h" @@ -2520,6 +2521,8 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock * // Update cached incremental witnesses GetMainSignals().ChainTip(pindexNew, pblock, oldTree, true); + EnforceNodeDeprecation(pindexNew->nHeight); + int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); LogPrint("bench", "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001); @@ -3613,6 +3616,8 @@ bool static LoadBlockIndexDB() DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), Checkpoints::GuessVerificationProgress(chainparams.Checkpoints(), chainActive.Tip())); + EnforceNodeDeprecation(chainActive.Height(), true); + return true; } From b4f861d1919e962df426fc279ade032936ac90fb Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 11 May 2017 15:35:57 +1200 Subject: [PATCH 2/2] Wrap messages nicely on metrics screen --- src/metrics.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/metrics.cpp b/src/metrics.cpp index fd800054..37424566 100644 --- a/src/metrics.cpp +++ b/src/metrics.cpp @@ -10,6 +10,7 @@ #include "util.h" #include "utiltime.h" #include "utilmoneystr.h" +#include "utilstrencodings.h" #include #include @@ -334,20 +335,19 @@ int printMessageBox(size_t cols) int lines = 2 + u->size(); std::cout << _("Messages:") << std::endl; for (auto it = u->cbegin(); it != u->cend(); ++it) { - std::cout << *it << std::endl; + auto msg = FormatParagraph(*it, cols, 2); + std::cout << "- " << msg << std::endl; // Handle newlines and wrapped lines size_t i = 0; size_t j = 0; - while (j < it->size()) { - i = it->find('\n', j); + while (j < msg.size()) { + i = msg.find('\n', j); if (i == std::string::npos) { - i = it->size(); + i = msg.size(); } else { // Newline lines++; } - // Wrapped lines - lines += ((i-j) / cols); j = i + 1; } }