diff --git a/src/init.cpp b/src/init.cpp index b44233d90..dc9080fec 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -39,7 +39,9 @@ #include #include +#include #include +#include #include #include #include @@ -1392,6 +1394,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) StartNode(threadGroup, scheduler); + // Monitor the chain, and alert if we get blocks much quicker or slower than expected + int64_t nPowTargetSpacing = Params().GetConsensus().nPowTargetSpacing; + CScheduler::Function f = boost::bind(&PartitionCheck, &IsInitialBlockDownload, + boost::ref(cs_main), boost::cref(chainActive), nPowTargetSpacing); + scheduler.scheduleEvery(f, nPowTargetSpacing); + #ifdef ENABLE_WALLET // Generate coins in the background if (pwalletMain) diff --git a/src/main.cpp b/src/main.cpp index d3956fafd..ac7d50ce6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include using namespace std; @@ -1685,6 +1686,64 @@ void ThreadScriptCheck() { scriptcheckqueue.Thread(); } +// +// Called periodically asynchronously; alerts if it smells like +// we're being fed a bad chain (blocks being generated much +// too slowly or too quickly). +// +void PartitionCheck(bool (*initialDownloadCheck)(), CCriticalSection& cs, const CChain& chain, int64_t nPowTargetSpacing) +{ + if (initialDownloadCheck()) return; + + static int64_t lastAlertTime = 0; + int64_t now = GetAdjustedTime(); + if (lastAlertTime > now-60*60*24) return; // Alert at most once per day + + const int SPAN_HOURS=4; + const int SPAN_SECONDS=SPAN_HOURS*60*60; + int BLOCKS_EXPECTED = SPAN_SECONDS / nPowTargetSpacing; + + boost::math::poisson_distribution poisson(BLOCKS_EXPECTED); + + std::string strWarning; + int64_t startTime = GetAdjustedTime()-SPAN_SECONDS; + + LOCK(cs); + int h = chain.Height(); + while (h > 0 && chain[h]->GetBlockTime() >= startTime) + --h; + int nBlocks = chain.Height()-h; + + // How likely is it to find that many by chance? + double p = boost::math::pdf(poisson, nBlocks); + + LogPrint("partitioncheck", "%s : Found %d blocks in the last %d hours\n", __func__, nBlocks, SPAN_HOURS); + LogPrint("partitioncheck", "%s : likelihood: %g\n", __func__, p); + + // Aim for one false-positive about every fifty years of normal running: + const int FIFTY_YEARS = 50*365*24*60*60; + double alertThreshold = 1.0 / (FIFTY_YEARS / SPAN_SECONDS); + + if (p <= alertThreshold && nBlocks < BLOCKS_EXPECTED) + { + // Many fewer blocks than expected: alert! + strWarning = strprintf(_("WARNING: check your network connection, %d blocks received in the last %d hours (%d expected)"), + nBlocks, SPAN_HOURS, BLOCKS_EXPECTED); + } + else if (p <= alertThreshold && nBlocks > BLOCKS_EXPECTED) + { + // Many more blocks than expected: alert! + strWarning = strprintf(_("WARNING: abnormally high number of blocks generated, %d blocks received in the last %d hours (%d expected)"), + nBlocks, SPAN_HOURS, BLOCKS_EXPECTED); + } + if (!strWarning.empty()) + { + strMiscWarning = strWarning; + CAlert::Notify(strWarning, true); + lastAlertTime = now; + } +} + static int64_t nTimeVerify = 0; static int64_t nTimeConnect = 0; static int64_t nTimeIndex = 0; diff --git a/src/main.h b/src/main.h index 07a709b75..6c380b104 100644 --- a/src/main.h +++ b/src/main.h @@ -194,6 +194,8 @@ bool ProcessMessages(CNode* pfrom); bool SendMessages(CNode* pto, bool fSendTrickle); /** Run an instance of the script checking thread */ void ThreadScriptCheck(); +/** Try to detect Partition (network isolation) attacks against us */ +void PartitionCheck(bool (*initialDownloadCheck)(), CCriticalSection& cs, const CChain& chain, int64_t nPowTargetSpacing); /** Check whether we are doing an initial block download (synchronizing from disk or network) */ bool IsInitialBlockDownload(); /** Format a string that describes several potential problems detected by the core */ diff --git a/src/test/alert_tests.cpp b/src/test/alert_tests.cpp index 6b6df5199..cd5b416bd 100644 --- a/src/test/alert_tests.cpp +++ b/src/test/alert_tests.cpp @@ -7,10 +7,13 @@ // #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" @@ -193,4 +196,65 @@ BOOST_AUTO_TEST_CASE(AlertNotify) 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()