Skip to content

Commit e9af4e6

Browse files
committed
Merge pull request #5947
36cba8f Alert if it is very likely we are getting a bad chain (Gavin Andresen)
2 parents e1412d3 + 36cba8f commit e9af4e6

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>
@@ -1392,6 +1394,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
13921394

13931395
StartNode(threadGroup, scheduler);
13941396

1397+
// Monitor the chain, and alert if we get blocks much quicker or slower than expected
1398+
int64_t nPowTargetSpacing = Params().GetConsensus().nPowTargetSpacing;
1399+
CScheduler::Function f = boost::bind(&PartitionCheck, &IsInitialBlockDownload,
1400+
boost::ref(cs_main), boost::cref(chainActive), nPowTargetSpacing);
1401+
scheduler.scheduleEvery(f, nPowTargetSpacing);
1402+
13951403
#ifdef ENABLE_WALLET
13961404
// Generate coins in the background
13971405
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;
@@ -1685,6 +1686,64 @@ void ThreadScriptCheck() {
16851686
scriptcheckqueue.Thread();
16861687
}
16871688

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

src/main.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ bool ProcessMessages(CNode* pfrom);
194194
bool SendMessages(CNode* pto, bool fSendTrickle);
195195
/** Run an instance of the script checking thread */
196196
void ThreadScriptCheck();
197+
/** Try to detect Partition (network isolation) attacks against us */
198+
void PartitionCheck(bool (*initialDownloadCheck)(), CCriticalSection& cs, const CChain& chain, int64_t nPowTargetSpacing);
197199
/** Check whether we are doing an initial block download (synchronizing from disk or network) */
198200
bool IsInitialBlockDownload();
199201
/** 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)