Create a monitoring task that counts how many blocks have been found in the last four hours.
If very few or too many have been found, an alert is triggered.
"Very few" and "too many" are set based on a false positive rate of once every fifty years of constant running with constant hashing power, which works out to getting 5 or fewer or 48 or more blocks in four hours (instead of the average of 24).
Only one alert per day is triggered, so if you get disconnected from the network (or are being Sybil'ed) -alertnotify will be triggered after 3.5 hours but you won't get another -alertnotify for 24 hours.
Tested with a new unit test and by running on the main network with -debug=partitioncheck
Run test/test_bitcoin --log_level=message to see the alert messages:
WARNING: check your network connection, 3 blocks received in the last 4 hours (24 expected)
WARNING: abnormally high number of blocks generated, 60 blocks received in the last 4 hours (24 expected)
The -debug=partitioncheck debug.log messages look like:
ThreadPartitionCheck : Found 22 blocks in the last 4 hours
ThreadPartitionCheck : likelihood: 0.0777702
261 lines
7.8 KiB
C++
261 lines
7.8 KiB
C++
// Copyright (c) 2013 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
//
|
|
// Unit tests for alert system
|
|
//
|
|
|
|
#include "alert.h"
|
|
#include "chain.h"
|
|
#include "chainparams.h"
|
|
#include "clientversion.h"
|
|
#include "data/alertTests.raw.h"
|
|
|
|
#include "chainparams.h"
|
|
#include "main.h"
|
|
#include "serialize.h"
|
|
#include "streams.h"
|
|
#include "util.h"
|
|
#include "utilstrencodings.h"
|
|
|
|
#include "test/test_bitcoin.h"
|
|
|
|
#include <fstream>
|
|
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#if 0
|
|
//
|
|
// alertTests contains 7 alerts, generated with this code:
|
|
// (SignAndSave code not shown, alert signing key is secret)
|
|
//
|
|
{
|
|
CAlert alert;
|
|
alert.nRelayUntil = 60;
|
|
alert.nExpiration = 24 * 60 * 60;
|
|
alert.nID = 1;
|
|
alert.nCancel = 0; // cancels previous messages up to this ID number
|
|
alert.nMinVer = 0; // These versions are protocol versions
|
|
alert.nMaxVer = 999001;
|
|
alert.nPriority = 1;
|
|
alert.strComment = "Alert comment";
|
|
alert.strStatusBar = "Alert 1";
|
|
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
alert.setSubVer.insert(std::string("/Satoshi:0.1.0/"));
|
|
alert.strStatusBar = "Alert 1 for Satoshi 0.1.0";
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
alert.setSubVer.insert(std::string("/Satoshi:0.2.0/"));
|
|
alert.strStatusBar = "Alert 1 for Satoshi 0.1.0, 0.2.0";
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
alert.setSubVer.clear();
|
|
++alert.nID;
|
|
alert.nCancel = 1;
|
|
alert.nPriority = 100;
|
|
alert.strStatusBar = "Alert 2, cancels 1";
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
alert.nExpiration += 60;
|
|
++alert.nID;
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
++alert.nID;
|
|
alert.nMinVer = 11;
|
|
alert.nMaxVer = 22;
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
++alert.nID;
|
|
alert.strStatusBar = "Alert 2 for Satoshi 0.1.0";
|
|
alert.setSubVer.insert(std::string("/Satoshi:0.1.0/"));
|
|
SignAndSave(alert, "test/alertTests");
|
|
|
|
++alert.nID;
|
|
alert.nMinVer = 0;
|
|
alert.nMaxVer = 999999;
|
|
alert.strStatusBar = "Evil Alert'; /bin/ls; echo '";
|
|
alert.setSubVer.clear();
|
|
SignAndSave(alert, "test/alertTests");
|
|
}
|
|
#endif
|
|
|
|
struct ReadAlerts : public TestingSetup
|
|
{
|
|
ReadAlerts()
|
|
{
|
|
std::vector<unsigned char> vch(alert_tests::alertTests, alert_tests::alertTests + sizeof(alert_tests::alertTests));
|
|
CDataStream stream(vch, SER_DISK, CLIENT_VERSION);
|
|
try {
|
|
while (!stream.eof())
|
|
{
|
|
CAlert alert;
|
|
stream >> alert;
|
|
alerts.push_back(alert);
|
|
}
|
|
}
|
|
catch (const std::exception&) { }
|
|
}
|
|
~ReadAlerts() { }
|
|
|
|
static std::vector<std::string> read_lines(boost::filesystem::path filepath)
|
|
{
|
|
std::vector<std::string> result;
|
|
|
|
std::ifstream f(filepath.string().c_str());
|
|
std::string line;
|
|
while (std::getline(f,line))
|
|
result.push_back(line);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<CAlert> alerts;
|
|
};
|
|
|
|
BOOST_FIXTURE_TEST_SUITE(Alert_tests, ReadAlerts)
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(AlertApplies)
|
|
{
|
|
SetMockTime(11);
|
|
const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey();
|
|
|
|
BOOST_FOREACH(const CAlert& alert, alerts)
|
|
{
|
|
BOOST_CHECK(alert.CheckSignature(alertKey));
|
|
}
|
|
|
|
BOOST_CHECK(alerts.size() >= 3);
|
|
|
|
// Matches:
|
|
BOOST_CHECK(alerts[0].AppliesTo(1, ""));
|
|
BOOST_CHECK(alerts[0].AppliesTo(999001, ""));
|
|
BOOST_CHECK(alerts[0].AppliesTo(1, "/Satoshi:11.11.11/"));
|
|
|
|
BOOST_CHECK(alerts[1].AppliesTo(1, "/Satoshi:0.1.0/"));
|
|
BOOST_CHECK(alerts[1].AppliesTo(999001, "/Satoshi:0.1.0/"));
|
|
|
|
BOOST_CHECK(alerts[2].AppliesTo(1, "/Satoshi:0.1.0/"));
|
|
BOOST_CHECK(alerts[2].AppliesTo(1, "/Satoshi:0.2.0/"));
|
|
|
|
// Don't match:
|
|
BOOST_CHECK(!alerts[0].AppliesTo(-1, ""));
|
|
BOOST_CHECK(!alerts[0].AppliesTo(999002, ""));
|
|
|
|
BOOST_CHECK(!alerts[1].AppliesTo(1, ""));
|
|
BOOST_CHECK(!alerts[1].AppliesTo(1, "Satoshi:0.1.0"));
|
|
BOOST_CHECK(!alerts[1].AppliesTo(1, "/Satoshi:0.1.0"));
|
|
BOOST_CHECK(!alerts[1].AppliesTo(1, "Satoshi:0.1.0/"));
|
|
BOOST_CHECK(!alerts[1].AppliesTo(-1, "/Satoshi:0.1.0/"));
|
|
BOOST_CHECK(!alerts[1].AppliesTo(999002, "/Satoshi:0.1.0/"));
|
|
BOOST_CHECK(!alerts[1].AppliesTo(1, "/Satoshi:0.2.0/"));
|
|
|
|
BOOST_CHECK(!alerts[2].AppliesTo(1, "/Satoshi:0.3.0/"));
|
|
|
|
SetMockTime(0);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(AlertNotify)
|
|
{
|
|
SetMockTime(11);
|
|
const std::vector<unsigned char>& alertKey = Params(CBaseChainParams::MAIN).AlertKey();
|
|
|
|
boost::filesystem::path temp = GetTempPath() / "alertnotify.txt";
|
|
boost::filesystem::remove(temp);
|
|
|
|
mapArgs["-alertnotify"] = std::string("echo %s >> ") + temp.string();
|
|
|
|
BOOST_FOREACH(CAlert alert, alerts)
|
|
alert.ProcessAlert(alertKey, false);
|
|
|
|
std::vector<std::string> r = read_lines(temp);
|
|
BOOST_CHECK_EQUAL(r.size(), 4u);
|
|
|
|
// Windows built-in echo semantics are different than posixy shells. Quotes and
|
|
// whitespace are printed literally.
|
|
|
|
#ifndef WIN32
|
|
BOOST_CHECK_EQUAL(r[0], "Alert 1");
|
|
BOOST_CHECK_EQUAL(r[1], "Alert 2, cancels 1");
|
|
BOOST_CHECK_EQUAL(r[2], "Alert 2, cancels 1");
|
|
BOOST_CHECK_EQUAL(r[3], "Evil Alert; /bin/ls; echo "); // single-quotes should be removed
|
|
#else
|
|
BOOST_CHECK_EQUAL(r[0], "'Alert 1' ");
|
|
BOOST_CHECK_EQUAL(r[1], "'Alert 2, cancels 1' ");
|
|
BOOST_CHECK_EQUAL(r[2], "'Alert 2, cancels 1' ");
|
|
BOOST_CHECK_EQUAL(r[3], "'Evil Alert; /bin/ls; echo ' ");
|
|
#endif
|
|
boost::filesystem::remove(temp);
|
|
|
|
SetMockTime(0);
|
|
}
|
|
|
|
static bool falseFunc() { return false; }
|
|
|
|
BOOST_AUTO_TEST_CASE(PartitionAlert)
|
|
{
|
|
// Test PartitionCheck
|
|
CCriticalSection csDummy;
|
|
CChain chainDummy;
|
|
CBlockIndex indexDummy[100];
|
|
CChainParams& params = Params(CBaseChainParams::MAIN);
|
|
int64_t nPowTargetSpacing = params.GetConsensus().nPowTargetSpacing;
|
|
|
|
// Generate fake blockchain timestamps relative to
|
|
// an arbitrary time:
|
|
int64_t now = 1427379054;
|
|
SetMockTime(now);
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
indexDummy[i].phashBlock = NULL;
|
|
if (i == 0) indexDummy[i].pprev = NULL;
|
|
else indexDummy[i].pprev = &indexDummy[i-1];
|
|
indexDummy[i].nHeight = i;
|
|
indexDummy[i].nTime = now - (100-i)*nPowTargetSpacing;
|
|
// Other members don't matter, the partition check code doesn't
|
|
// use them
|
|
}
|
|
chainDummy.SetTip(&indexDummy[99]);
|
|
|
|
// Test 1: chain with blocks every nPowTargetSpacing seconds,
|
|
// as normal, no worries:
|
|
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
|
|
BOOST_CHECK(strMiscWarning.empty());
|
|
|
|
// Test 2: go 3.5 hours without a block, expect a warning:
|
|
now += 3*60*60+30*60;
|
|
SetMockTime(now);
|
|
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
|
|
BOOST_CHECK(!strMiscWarning.empty());
|
|
BOOST_TEST_MESSAGE(std::string("Got alert text: ")+strMiscWarning);
|
|
strMiscWarning = "";
|
|
|
|
// Test 3: test the "partition alerts only go off once per day"
|
|
// code:
|
|
now += 60*10;
|
|
SetMockTime(now);
|
|
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
|
|
BOOST_CHECK(strMiscWarning.empty());
|
|
|
|
// Test 4: get 2.5 times as many blocks as expected:
|
|
now += 60*60*24; // Pretend it is a day later
|
|
SetMockTime(now);
|
|
int64_t quickSpacing = nPowTargetSpacing*2/5;
|
|
for (int i = 0; i < 100; i++) // Tweak chain timestamps:
|
|
indexDummy[i].nTime = now - (100-i)*quickSpacing;
|
|
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
|
|
BOOST_CHECK(!strMiscWarning.empty());
|
|
BOOST_TEST_MESSAGE(std::string("Got alert text: ")+strMiscWarning);
|
|
strMiscWarning = "";
|
|
|
|
SetMockTime(0);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|