Track block download times per individual block
Currently, we're keeping a timeout for each requested block, starting from when it is requested, with a correction factor for the number of blocks in the queue. That's unnecessarily complicated and inaccurate. As peers process block requests in order, we can make the timeout for each block start counting only when all previous ones have been received, and have a correction based on the number of peers, rather than the total number of blocks. Conflicts: src/main.cpp src/main.h Self check after the last peer is removed Github-Pull: #7804 Rebased-From: 2d1d6581eca4508838cd339cc19c72efc42d6ea0 0e24bbf679c95784ed5514a6a1f2fbf99dd97725
This commit is contained in:
parent
4226aacdba
commit
90f1d246d3
71
src/main.cpp
71
src/main.cpp
@ -192,16 +192,11 @@ namespace {
|
||||
/** Blocks that are in flight, and that are in the queue to be downloaded. Protected by cs_main. */
|
||||
struct QueuedBlock {
|
||||
uint256 hash;
|
||||
CBlockIndex *pindex; //! Optional.
|
||||
int64_t nTime; //! Time of "getdata" request in microseconds.
|
||||
bool fValidatedHeaders; //! Whether this block has validated headers at the time of request.
|
||||
int64_t nTimeDisconnect; //! The timeout for this block request (for disconnecting a slow peer)
|
||||
CBlockIndex* pindex; //!< Optional.
|
||||
bool fValidatedHeaders; //!< Whether this block has validated headers at the time of request.
|
||||
};
|
||||
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> > mapBlocksInFlight;
|
||||
|
||||
/** Number of blocks in flight with validated headers. */
|
||||
int nQueuedValidatedHeaders = 0;
|
||||
|
||||
/** Number of preferable block download peers. */
|
||||
int nPreferredDownload = 0;
|
||||
|
||||
@ -210,6 +205,9 @@ namespace {
|
||||
|
||||
/** Dirty block file entries. */
|
||||
set<int> setDirtyFileInfo;
|
||||
|
||||
/** Number of peers from which we're downloading blocks. */
|
||||
int nPeersWithValidatedDownloads = 0;
|
||||
} // anon namespace
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@ -257,6 +255,8 @@ struct CNodeState {
|
||||
//! Since when we're stalling block download progress (in microseconds), or 0.
|
||||
int64_t nStallingSince;
|
||||
list<QueuedBlock> vBlocksInFlight;
|
||||
//! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty.
|
||||
int64_t nDownloadingSince;
|
||||
int nBlocksInFlight;
|
||||
int nBlocksInFlightValidHeaders;
|
||||
//! Whether we consider this a preferred download peer.
|
||||
@ -274,6 +274,7 @@ struct CNodeState {
|
||||
pindexBestHeaderSent = NULL;
|
||||
fSyncStarted = false;
|
||||
nStallingSince = 0;
|
||||
nDownloadingSince = 0;
|
||||
nBlocksInFlight = 0;
|
||||
nBlocksInFlightValidHeaders = 0;
|
||||
fPreferredDownload = false;
|
||||
@ -308,12 +309,6 @@ void UpdatePreferredDownload(CNode* node, CNodeState* state)
|
||||
nPreferredDownload += state->fPreferredDownload;
|
||||
}
|
||||
|
||||
// Returns time at which to timeout block request (nTime in microseconds)
|
||||
int64_t GetBlockTimeout(int64_t nTime, int nValidatedQueuedBefore, const Consensus::Params &consensusParams)
|
||||
{
|
||||
return nTime + 500000 * consensusParams.nPowTargetSpacing * (4 + nValidatedQueuedBefore);
|
||||
}
|
||||
|
||||
void InitializeNode(NodeId nodeid, const CNode *pnode) {
|
||||
LOCK(cs_main);
|
||||
CNodeState &state = mapNodeState.insert(std::make_pair(nodeid, CNodeState())).first->second;
|
||||
@ -333,13 +328,21 @@ void FinalizeNode(NodeId nodeid) {
|
||||
}
|
||||
|
||||
BOOST_FOREACH(const QueuedBlock& entry, state->vBlocksInFlight) {
|
||||
nQueuedValidatedHeaders -= entry.fValidatedHeaders;
|
||||
mapBlocksInFlight.erase(entry.hash);
|
||||
}
|
||||
EraseOrphansFor(nodeid);
|
||||
nPreferredDownload -= state->fPreferredDownload;
|
||||
nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0);
|
||||
assert(nPeersWithValidatedDownloads >= 0);
|
||||
|
||||
mapNodeState.erase(nodeid);
|
||||
|
||||
if (mapNodeState.empty()) {
|
||||
// Do a consistency check after the last peer is removed.
|
||||
assert(mapBlocksInFlight.empty());
|
||||
assert(nPreferredDownload == 0);
|
||||
assert(nPeersWithValidatedDownloads == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Requires cs_main.
|
||||
@ -348,8 +351,15 @@ bool MarkBlockAsReceived(const uint256& hash) {
|
||||
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash);
|
||||
if (itInFlight != mapBlocksInFlight.end()) {
|
||||
CNodeState *state = State(itInFlight->second.first);
|
||||
nQueuedValidatedHeaders -= itInFlight->second.second->fValidatedHeaders;
|
||||
state->nBlocksInFlightValidHeaders -= itInFlight->second.second->fValidatedHeaders;
|
||||
if (state->nBlocksInFlightValidHeaders == 0 && itInFlight->second.second->fValidatedHeaders) {
|
||||
// Last validated block on the queue was received.
|
||||
nPeersWithValidatedDownloads--;
|
||||
}
|
||||
if (state->vBlocksInFlight.begin() == itInFlight->second.second) {
|
||||
// First block on the queue was received, update the start download time for the next one
|
||||
state->nDownloadingSince = std::max(state->nDownloadingSince, GetTimeMicros());
|
||||
}
|
||||
state->vBlocksInFlight.erase(itInFlight->second.second);
|
||||
state->nBlocksInFlight--;
|
||||
state->nStallingSince = 0;
|
||||
@ -367,12 +377,17 @@ void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Pa
|
||||
// Make sure it's not listed somewhere already.
|
||||
MarkBlockAsReceived(hash);
|
||||
|
||||
int64_t nNow = GetTimeMicros();
|
||||
QueuedBlock newentry = {hash, pindex, nNow, pindex != NULL, GetBlockTimeout(nNow, nQueuedValidatedHeaders, consensusParams)};
|
||||
nQueuedValidatedHeaders += newentry.fValidatedHeaders;
|
||||
QueuedBlock newentry = {hash, pindex, pindex != NULL};
|
||||
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
|
||||
state->nBlocksInFlight++;
|
||||
state->nBlocksInFlightValidHeaders += newentry.fValidatedHeaders;
|
||||
if (state->nBlocksInFlight == 1) {
|
||||
// We're starting a block download (batch) from this peer.
|
||||
state->nDownloadingSince = GetTimeMicros();
|
||||
}
|
||||
if (state->nBlocksInFlightValidHeaders == 1 && pindex != NULL) {
|
||||
nPeersWithValidatedDownloads++;
|
||||
}
|
||||
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
|
||||
}
|
||||
|
||||
@ -3911,7 +3926,6 @@ void UnloadBlockIndex()
|
||||
nBlockSequenceId = 1;
|
||||
mapBlockSource.clear();
|
||||
mapBlocksInFlight.clear();
|
||||
nQueuedValidatedHeaders = 0;
|
||||
nPreferredDownload = 0;
|
||||
setDirtyBlockIndex.clear();
|
||||
setDirtyFileInfo.clear();
|
||||
@ -5866,24 +5880,15 @@ bool SendMessages(CNode* pto)
|
||||
LogPrintf("Peer=%d is stalling block download, disconnecting\n", pto->id);
|
||||
pto->fDisconnect = true;
|
||||
}
|
||||
// In case there is a block that has been in flight from this peer for (2 + 0.5 * N) times the block interval
|
||||
// (with N the number of validated blocks that were in flight at the time it was requested), disconnect due to
|
||||
// timeout. We compensate for in-flight blocks to prevent killing off peers due to our own downstream link
|
||||
// In case there is a block that has been in flight from this peer for 2 + 0.5 * N times the block interval
|
||||
// (with N the number of peers from which we're downloading validated blocks), disconnect due to timeout.
|
||||
// We compensate for other peers to prevent killing off peers due to our own downstream link
|
||||
// being saturated. We only count validated in-flight blocks so peers can't advertise non-existing block hashes
|
||||
// to unreasonably increase our timeout.
|
||||
// We also compare the block download timeout originally calculated against the time at which we'd disconnect
|
||||
// if we assumed the block were being requested now (ignoring blocks we've requested from this peer, since we're
|
||||
// only looking at this peer's oldest request). This way a large queue in the past doesn't result in a
|
||||
// permanently large window for this block to be delivered (ie if the number of blocks in flight is decreasing
|
||||
// more quickly than once every 5 minutes, then we'll shorten the download window for this block).
|
||||
if (!pto->fDisconnect && state.vBlocksInFlight.size() > 0) {
|
||||
QueuedBlock &queuedBlock = state.vBlocksInFlight.front();
|
||||
int64_t nTimeoutIfRequestedNow = GetBlockTimeout(nNow, nQueuedValidatedHeaders - state.nBlocksInFlightValidHeaders, consensusParams);
|
||||
if (queuedBlock.nTimeDisconnect > nTimeoutIfRequestedNow) {
|
||||
LogPrint("net", "Reducing block download timeout for peer=%d block=%s, orig=%d new=%d\n", pto->id, queuedBlock.hash.ToString(), queuedBlock.nTimeDisconnect, nTimeoutIfRequestedNow);
|
||||
queuedBlock.nTimeDisconnect = nTimeoutIfRequestedNow;
|
||||
}
|
||||
if (queuedBlock.nTimeDisconnect < nNow) {
|
||||
int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0);
|
||||
if (nNow > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) {
|
||||
LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->id);
|
||||
pto->fDisconnect = true;
|
||||
}
|
||||
|
@ -98,6 +98,10 @@ static const unsigned int AVG_ADDRESS_BROADCAST_INTERVAL = 30;
|
||||
/** Average delay between trickled inventory broadcasts in seconds.
|
||||
* Blocks, whitelisted receivers, and a random 25% of transactions bypass this. */
|
||||
static const unsigned int AVG_INVENTORY_BROADCAST_INTERVAL = 5;
|
||||
/** Block download timeout base, expressed in millionths of the block interval (i.e. 20 min) */
|
||||
static const int64_t BLOCK_DOWNLOAD_TIMEOUT_BASE = 2000000;
|
||||
/** Additional block download timeout per parallel downloading peer (i.e. 5 min) */
|
||||
static const int64_t BLOCK_DOWNLOAD_TIMEOUT_PER_PEER = 500000;
|
||||
|
||||
static const unsigned int DEFAULT_LIMITFREERELAY = 15;
|
||||
static const bool DEFAULT_RELAYPRIORITY = true;
|
||||
|
Loading…
Reference in New Issue
Block a user