Skip to content

Commit 169afaf

Browse files
codablockUdjinM6
authored andcommitted
Fix duplicate headers download in initial sync (#1589)
* Fix duplicate headers download in initial sync Now that initial block download is delayed until the headers sync is done, it was noticed that the initial headers sync may happen multiple times in parallel in the case new blocks are announced. This happens because for every block in INV that is received, a getheaders message is immediately sent out resulting in a full download of the headers chain starting from the point of where the initial headers sync is currently at. This happens once for each peer that announces the new block. This slows down the initial headers sync and increases the chance of another block being announced before it is finished, probably leading to the same behavior as already described, slowing down the sync even more...and so on. This commit delays sending of GETHEADERS to later in case the chain is too far behind while a new block gets announced. Header chains will still be downloaded multiple times, but the downloading will start much closer to the tip of the chain, so the damage is not that bad anymore. This ensures that we get all headers from all peers, even if any of them is on another chain. This should avoid what happened in bitcoin#8054 which needed to be reverted later. This fixes the Bitcoin issue bitcoin#6755 * Introduce DelayGetHeadersTime chain param and fix tests The delaying of GETHEADERS in combination with very old block times in test cases resulted in the delaying being triggered when the first newly mined block arrives. This results in a completely stalled sync. This is fixed by avoiding delaying in when running tests. * Disconnect peers which are not catched up Peers which stop sending us headers too early are very likely peers which did not catch up before and stalled for some reason. We should disconnect these peers and chose another one to continue.
1 parent 6496fc9 commit 169afaf

File tree

4 files changed

+69
-23
lines changed

4 files changed

+69
-23
lines changed

src/chainparams.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ class CMainParams : public CChainParams {
125125
vAlertPubKey = ParseHex("048240a8748a80a286b270ba126705ced4f2ce5a7847b3610ea3c06513150dade2a8512ed5ea86320824683fc0818f0ac019214973e677acd1244f6d0571fc5103");
126126
nDefaultPort = 9999;
127127
nMaxTipAge = 6 * 60 * 60; // ~144 blocks behind -> 2 x fork detection time, was 24 * 60 * 60 in bitcoin
128+
nDelayGetHeadersTime = 24 * 60 * 60;
128129
nPruneAfterHeight = 100000;
129130

130131
genesis = CreateGenesisBlock(1390095618, 28917698, 0x1e0ffff0, 1, 50 * COIN);
@@ -250,6 +251,7 @@ class CTestNetParams : public CChainParams {
250251
vAlertPubKey = ParseHex("04517d8a699cb43d3938d7b24faaff7cda448ca4ea267723ba614784de661949bf632d6304316b244646dea079735b9a6fc4af804efb4752075b9fe2245e14e412");
251252
nDefaultPort = 19999;
252253
nMaxTipAge = 0x7fffffff; // allow mining on top of old blocks for testnet
254+
nDelayGetHeadersTime = 24 * 60 * 60;
253255
nPruneAfterHeight = 1000;
254256

255257
genesis = CreateGenesisBlock(1390666206UL, 3861367235UL, 0x1e0ffff0, 1, 50 * COIN);
@@ -359,6 +361,7 @@ class CRegTestParams : public CChainParams {
359361
pchMessageStart[2] = 0xb7;
360362
pchMessageStart[3] = 0xdc;
361363
nMaxTipAge = 6 * 60 * 60; // ~144 blocks behind -> 2 x fork detection time, was 24 * 60 * 60 in bitcoin
364+
nDelayGetHeadersTime = 0; // never delay GETHEADERS in regtests
362365
nDefaultPort = 19994;
363366
nPruneAfterHeight = 1000;
364367

src/chainparams.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class CChainParams
6565
/** Policy: Filter transactions that do not match well-defined patterns */
6666
bool RequireStandard() const { return fRequireStandard; }
6767
int64_t MaxTipAge() const { return nMaxTipAge; }
68+
int64_t DelayGetHeadersTime() const { return nDelayGetHeadersTime; }
6869
uint64_t PruneAfterHeight() const { return nPruneAfterHeight; }
6970
/** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */
7071
bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; }
@@ -89,6 +90,7 @@ class CChainParams
8990
std::vector<unsigned char> vAlertPubKey;
9091
int nDefaultPort;
9192
long nMaxTipAge;
93+
int64_t nDelayGetHeadersTime;
9294
uint64_t nPruneAfterHeight;
9395
std::vector<CDNSSeedData> vSeeds;
9496
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];

src/net.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,9 @@ class CNode
742742
// Used for headers announcements - unfiltered blocks to relay
743743
// Also protected by cs_inventory
744744
std::vector<uint256> vBlockHashesToAnnounce;
745+
// Blocks received by INV while headers chain was too far behind. These are used to delay GETHEADERS messages
746+
// Also protected by cs_inventory
747+
std::vector<uint256> vBlockHashesFromINV;
745748

746749
// Block and TXN accept times
747750
std::atomic<int64_t> nLastBlockTime;
@@ -875,6 +878,12 @@ class CNode
875878
vBlockHashesToAnnounce.push_back(hash);
876879
}
877880

881+
void PushBlockHashFromINV(const uint256 &hash)
882+
{
883+
LOCK(cs_inventory);
884+
vBlockHashesFromINV.push_back(hash);
885+
}
886+
878887
void AskFor(const CInv& inv);
879888

880889
void CloseSocketDisconnect();

src/net_processing.cpp

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,24 +1380,35 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
13801380
if (inv.type == MSG_BLOCK) {
13811381
UpdateBlockAvailability(pfrom->GetId(), inv.hash);
13821382
if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
1383-
// First request the headers preceding the announced block. In the normal fully-synced
1384-
// case where a new block is announced that succeeds the current tip (no reorganization),
1385-
// there are no such headers.
1386-
// Secondly, and only when we are close to being synced, we request the announced block directly,
1387-
// to avoid an extra round-trip. Note that we must *first* ask for the headers, so by the
1388-
// time the block arrives, the header chain leading up to it is already validated. Not
1389-
// doing this will result in the received block being rejected as an orphan in case it is
1390-
// not a direct successor.
1391-
connman.PushMessage(pfrom, NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash);
1392-
CNodeState *nodestate = State(pfrom->GetId());
1393-
if (CanDirectFetch(chainparams.GetConsensus()) &&
1394-
nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
1395-
vToFetch.push_back(inv);
1396-
// Mark block as in flight already, even though the actual "getdata" message only goes out
1397-
// later (within the same cs_main lock, though).
1398-
MarkBlockAsInFlight(pfrom->GetId(), inv.hash, chainparams.GetConsensus());
1383+
if (chainparams.DelayGetHeadersTime() != 0 && pindexBestHeader->GetBlockTime() < GetAdjustedTime() - chainparams.DelayGetHeadersTime()) {
1384+
// We are pretty far from being completely synced at the moment. If we would initiate a new
1385+
// chain of GETHEADERS/HEADERS now, we may end up downnloading the full chain from multiple
1386+
// peers at the same time, slowing down the initial sync. At the same time, we don't know
1387+
// if the peer we got this INV from may have a chain we don't know about yet, so we HAVE TO
1388+
// send a GETHEADERS message at some point in time. This is delayed to later in SendMessages
1389+
// when the headers chain has catched up enough.
1390+
LogPrint("net", "delaying getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->id);
1391+
pfrom->PushBlockHashFromINV(inv.hash);
1392+
} else {
1393+
// First request the headers preceding the announced block. In the normal fully-synced
1394+
// case where a new block is announced that succeeds the current tip (no reorganization),
1395+
// there are no such headers.
1396+
// Secondly, and only when we are close to being synced, we request the announced block directly,
1397+
// to avoid an extra round-trip. Note that we must *first* ask for the headers, so by the
1398+
// time the block arrives, the header chain leading up to it is already validated. Not
1399+
// doing this will result in the received block being rejected as an orphan in case it is
1400+
// not a direct successor.
1401+
connman.PushMessage(pfrom, NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash);
1402+
CNodeState *nodestate = State(pfrom->GetId());
1403+
if (CanDirectFetch(chainparams.GetConsensus()) &&
1404+
nodestate->nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
1405+
vToFetch.push_back(inv);
1406+
// Mark block as in flight already, even though the actual "getdata" message only goes out
1407+
// later (within the same cs_main lock, though).
1408+
MarkBlockAsInFlight(pfrom->GetId(), inv.hash, chainparams.GetConsensus());
1409+
}
1410+
LogPrint("net", "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->id);
13991411
}
1400-
LogPrint("net", "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->id);
14011412
}
14021413
}
14031414
else
@@ -1764,11 +1775,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
17641775
ReadCompactSize(vRecv); // ignore tx count; assume it is 0.
17651776
}
17661777

1767-
if (nCount == 0) {
1768-
// Nothing interesting. Stop asking this peers for more headers.
1769-
return true;
1770-
}
1771-
17721778
CBlockIndex *pindexLast = NULL;
17731779
{
17741780
LOCK(cs_main);
@@ -1805,6 +1811,20 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
18051811
// from there instead.
18061812
LogPrint("net", "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->id, pfrom->nStartingHeight);
18071813
connman.PushMessage(pfrom, NetMsgType::GETHEADERS, chainActive.GetLocator(pindexLast), uint256());
1814+
} else {
1815+
if (chainparams.DelayGetHeadersTime() != 0 && pindexBestHeader->GetBlockTime() < GetAdjustedTime() - chainparams.DelayGetHeadersTime()) {
1816+
// peer has sent us a HEADERS message below maximum size and we are still quite far from being fully
1817+
// synced, this means we probably got a bad peer for initial sync and need to continue with another one.
1818+
// By disconnecting we force to start a new iteration of initial headers sync in SendMessages
1819+
// TODO should we handle whitelisted peers here as we do in headers sync timeout handling?
1820+
pfrom->fDisconnect = true;
1821+
return error("detected bad peer for initial headers sync, disconnecting %d", pfrom->id);
1822+
}
1823+
1824+
if (nCount == 0) {
1825+
// Nothing interesting. Stop asking this peers for more headers.
1826+
return true;
1827+
}
18081828
}
18091829

18101830
bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus());
@@ -2274,7 +2294,8 @@ bool ProcessMessages(CNode* pfrom, CConnman& connman, std::atomic<bool>& interru
22742294

22752295
bool SendMessages(CNode* pto, CConnman& connman, std::atomic<bool>& interruptMsgProc)
22762296
{
2277-
const Consensus::Params& consensusParams = Params().GetConsensus();
2297+
const CChainParams chainParams = Params();
2298+
const Consensus::Params& consensusParams = chainParams.GetConsensus();
22782299
{
22792300
// Don't send anything until the version handshake is complete
22802301
if (!pto->fSuccessfullyConnected || pto->fDisconnect)
@@ -2391,6 +2412,17 @@ bool SendMessages(CNode* pto, CConnman& connman, std::atomic<bool>& interruptMsg
23912412
}
23922413
}
23932414

2415+
if (chainParams.DelayGetHeadersTime() != 0 && pindexBestHeader->GetBlockTime() >= GetAdjustedTime() - chainParams.DelayGetHeadersTime()) {
2416+
// Headers chain has catched up enough so we can send out GETHEADER messages which were initially meant to
2417+
// be sent directly after INV was received
2418+
LOCK(pto->cs_inventory);
2419+
BOOST_FOREACH(const uint256 &hash, pto->vBlockHashesFromINV) {
2420+
LogPrint("net", "process delayed getheaders (%d) to peer=%d\n", pindexBestHeader->nHeight, pto->id);
2421+
connman.PushMessage(pto, NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), hash);
2422+
}
2423+
pto->vBlockHashesFromINV.clear();
2424+
}
2425+
23942426
// Resend wallet transactions that haven't gotten in a block yet
23952427
// Except during reindex, importing and IBD, when old wallet
23962428
// transactions become unconfirmed and spams other nodes.

0 commit comments

Comments
 (0)