Auto merge of #2297 - str4d:2274-apoptosis, r=nathan-at-least

Implement automatic shutdown of deprecated Zcash versions

Closes #2274.
This commit is contained in:
Homu 2017-05-14 18:39:14 -07:00
commit 3a98e3b4c2
11 changed files with 209 additions and 8 deletions

View File

@ -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`.

View File

@ -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 \

View File

@ -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 \

View File

@ -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);

View File

@ -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<std::string>& comments);

55
src/deprecation.cpp Normal file
View File

@ -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);
}
}

23
src/deprecation.h Normal file
View File

@ -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

View File

@ -0,0 +1,105 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#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<bool> 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<MockUIInterface> 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());
}

View File

@ -344,6 +344,8 @@ std::string HelpMessage(HelpMessageMode mode)
#endif
}
strUsage += HelpMessageOpt("-datadir=<dir>", _("Specify data directory"));
strUsage += HelpMessageOpt("-disabledeprecation=<version>", strprintf(_("Disable block-height node deprecation and automatic shutdown (example: -disabledeprecation=%s)"),
FormatVersion(CLIENT_VERSION)));
strUsage += HelpMessageOpt("-exportdir=<dir>", _("Specify directory to be used when exporting data"));
strUsage += HelpMessageOpt("-dbcache=<n>", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache));
strUsage += HelpMessageOpt("-loadblock=<file>", _("Imports blocks from external blk000??.dat file") + " " + _("on startup"));

View File

@ -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;
}

View File

@ -10,6 +10,7 @@
#include "util.h"
#include "utiltime.h"
#include "utilmoneystr.h"
#include "utilstrencodings.h"
#include <boost/thread.hpp>
#include <boost/thread/synchronized_value.hpp>
@ -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;
}
}