Skip to content

Commit 36cba8f

Browse files
committed
Alert if it is very likely we are getting a bad chain
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
1 parent b4c219b commit 36cba8f

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

src/init.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939

4040
#include <boost/algorithm/string/predicate.hpp>
4141
#include <boost/algorithm/string/replace.hpp>
42+
#include <boost/bind.hpp>
4243
#include <boost/filesystem.hpp>
44+
#include <boost/function.hpp>
4345
#include <boost/interprocess/sync/file_lock.hpp>
4446
#include <boost/thread.hpp>
4547
#include <openssl/crypto.h>
@@ -1380,6 +1382,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
13801382

13811383
StartNode(threadGroup, scheduler);
13821384

1385+
// Monitor the chain, and alert if we get blocks much quicker or slower than expected
1386+
int64_t nPowTargetSpacing = Params().GetConsensus().nPowTargetSpacing;
1387+
CScheduler::Function f = boost::bind(&PartitionCheck, &IsInitialBlockDownload,
1388+
boost::ref(cs_main), boost::cref(chainActive), nPowTargetSpacing);
1389+
scheduler.scheduleEvery(f, nPowTargetSpacing);
1390+
13831391
#ifdef ENABLE_WALLET
13841392
// Generate coins in the background
13851393
if (pwalletMain)

src/main.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <boost/algorithm/string/replace.hpp>
2929
#include <boost/filesystem.hpp>
3030
#include <boost/filesystem/fstream.hpp>
31+
#include <boost/math/distributions/poisson.hpp>
3132
#include <boost/thread.hpp>
3233

3334
using namespace std;
@@ -1688,6 +1689,64 @@ void ThreadScriptCheck() {
16881689
scriptcheckqueue.Thread();
16891690
}
16901691

1692+
//
1693+
// Called periodically asynchronously; alerts if it smells like
1694+
// we're being fed a bad chain (blocks being generated much
1695+
// too slowly or too quickly).
1696+
//
1697+
void PartitionCheck(bool (*initialDownloadCheck)(), CCriticalSection& cs, const CChain& chain, int64_t nPowTargetSpacing)
1698+
{
1699+
if (initialDownloadCheck()) return;
1700+
1701+
static int64_t lastAlertTime = 0;
1702+
int64_t now = GetAdjustedTime();
1703+
if (lastAlertTime > now-60*60*24) return; // Alert at most once per day
1704+
1705+
const int SPAN_HOURS=4;
1706+
const int SPAN_SECONDS=SPAN_HOURS*60*60;
1707+
int BLOCKS_EXPECTED = SPAN_SECONDS / nPowTargetSpacing;
1708+
1709+
boost::math::poisson_distribution<double> poisson(BLOCKS_EXPECTED);
1710+
1711+
std::string strWarning;
1712+
int64_t startTime = GetAdjustedTime()-SPAN_SECONDS;
1713+
1714+
LOCK(cs);
1715+
int h = chain.Height();
1716+
while (h > 0 && chain[h]->GetBlockTime() >= startTime)
1717+
--h;
1718+
int nBlocks = chain.Height()-h;
1719+
1720+
// How likely is it to find that many by chance?
1721+
double p = boost::math::pdf(poisson, nBlocks);
1722+
1723+
LogPrint("partitioncheck", "%s : Found %d blocks in the last %d hours\n", __func__, nBlocks, SPAN_HOURS);
1724+
LogPrint("partitioncheck", "%s : likelihood: %g\n", __func__, p);
1725+
1726+
// Aim for one false-positive about every fifty years of normal running:
1727+
const int FIFTY_YEARS = 50*365*24*60*60;
1728+
double alertThreshold = 1.0 / (FIFTY_YEARS / SPAN_SECONDS);
1729+
1730+
if (p <= alertThreshold && nBlocks < BLOCKS_EXPECTED)
1731+
{
1732+
// Many fewer blocks than expected: alert!
1733+
strWarning = strprintf(_("WARNING: check your network connection, %d blocks received in the last %d hours (%d expected)"),
1734+
nBlocks, SPAN_HOURS, BLOCKS_EXPECTED);
1735+
}
1736+
else if (p <= alertThreshold && nBlocks > BLOCKS_EXPECTED)
1737+
{
1738+
// Many more blocks than expected: alert!
1739+
strWarning = strprintf(_("WARNING: abnormally high number of blocks generated, %d blocks received in the last %d hours (%d expected)"),
1740+
nBlocks, SPAN_HOURS, BLOCKS_EXPECTED);
1741+
}
1742+
if (!strWarning.empty())
1743+
{
1744+
strMiscWarning = strWarning;
1745+
CAlert::Notify(strWarning, true);
1746+
lastAlertTime = now;
1747+
}
1748+
}
1749+
16911750
static int64_t nTimeVerify = 0;
16921751
static int64_t nTimeConnect = 0;
16931752
static int64_t nTimeIndex = 0;

src/main.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ bool ProcessMessages(CNode* pfrom);
192192
bool SendMessages(CNode* pto, bool fSendTrickle);
193193
/** Run an instance of the script checking thread */
194194
void ThreadScriptCheck();
195+
/** Try to detect Partition (network isolation) attacks against us */
196+
void PartitionCheck(bool (*initialDownloadCheck)(), CCriticalSection& cs, const CChain& chain, int64_t nPowTargetSpacing);
195197
/** Check whether we are doing an initial block download (synchronizing from disk or network) */
196198
bool IsInitialBlockDownload();
197199
/** Format a string that describes several potential problems detected by the core */

src/test/alert_tests.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
//
88

99
#include "alert.h"
10+
#include "chain.h"
11+
#include "chainparams.h"
1012
#include "clientversion.h"
1113
#include "data/alertTests.raw.h"
1214

1315
#include "chainparams.h"
16+
#include "main.h"
1417
#include "serialize.h"
1518
#include "streams.h"
1619
#include "util.h"
@@ -193,4 +196,65 @@ BOOST_AUTO_TEST_CASE(AlertNotify)
193196
SetMockTime(0);
194197
}
195198

199+
static bool falseFunc() { return false; }
200+
201+
BOOST_AUTO_TEST_CASE(PartitionAlert)
202+
{
203+
// Test PartitionCheck
204+
CCriticalSection csDummy;
205+
CChain chainDummy;
206+
CBlockIndex indexDummy[100];
207+
CChainParams& params = Params(CBaseChainParams::MAIN);
208+
int64_t nPowTargetSpacing = params.GetConsensus().nPowTargetSpacing;
209+
210+
// Generate fake blockchain timestamps relative to
211+
// an arbitrary time:
212+
int64_t now = 1427379054;
213+
SetMockTime(now);
214+
for (int i = 0; i < 100; i++)
215+
{
216+
indexDummy[i].phashBlock = NULL;
217+
if (i == 0) indexDummy[i].pprev = NULL;
218+
else indexDummy[i].pprev = &indexDummy[i-1];
219+
indexDummy[i].nHeight = i;
220+
indexDummy[i].nTime = now - (100-i)*nPowTargetSpacing;
221+
// Other members don't matter, the partition check code doesn't
222+
// use them
223+
}
224+
chainDummy.SetTip(&indexDummy[99]);
225+
226+
// Test 1: chain with blocks every nPowTargetSpacing seconds,
227+
// as normal, no worries:
228+
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
229+
BOOST_CHECK(strMiscWarning.empty());
230+
231+
// Test 2: go 3.5 hours without a block, expect a warning:
232+
now += 3*60*60+30*60;
233+
SetMockTime(now);
234+
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
235+
BOOST_CHECK(!strMiscWarning.empty());
236+
BOOST_TEST_MESSAGE(std::string("Got alert text: ")+strMiscWarning);
237+
strMiscWarning = "";
238+
239+
// Test 3: test the "partition alerts only go off once per day"
240+
// code:
241+
now += 60*10;
242+
SetMockTime(now);
243+
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
244+
BOOST_CHECK(strMiscWarning.empty());
245+
246+
// Test 4: get 2.5 times as many blocks as expected:
247+
now += 60*60*24; // Pretend it is a day later
248+
SetMockTime(now);
249+
int64_t quickSpacing = nPowTargetSpacing*2/5;
250+
for (int i = 0; i < 100; i++) // Tweak chain timestamps:
251+
indexDummy[i].nTime = now - (100-i)*quickSpacing;
252+
PartitionCheck(falseFunc, csDummy, chainDummy, nPowTargetSpacing);
253+
BOOST_CHECK(!strMiscWarning.empty());
254+
BOOST_TEST_MESSAGE(std::string("Got alert text: ")+strMiscWarning);
255+
strMiscWarning = "";
256+
257+
SetMockTime(0);
258+
}
259+
196260
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)