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.
This commit is contained in:
parent
3bc71e1572
commit
2d1d6581ec
2 changed files with 33 additions and 31 deletions
60
src/main.cpp
60
src/main.cpp
|
@ -195,15 +195,10 @@ namespace {
|
||||||
struct QueuedBlock {
|
struct QueuedBlock {
|
||||||
uint256 hash;
|
uint256 hash;
|
||||||
CBlockIndex* pindex; //!< Optional.
|
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.
|
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)
|
|
||||||
};
|
};
|
||||||
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> > mapBlocksInFlight;
|
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. */
|
/** Number of preferable block download peers. */
|
||||||
int nPreferredDownload = 0;
|
int nPreferredDownload = 0;
|
||||||
|
|
||||||
|
@ -212,6 +207,9 @@ namespace {
|
||||||
|
|
||||||
/** Dirty block file entries. */
|
/** Dirty block file entries. */
|
||||||
set<int> setDirtyFileInfo;
|
set<int> setDirtyFileInfo;
|
||||||
|
|
||||||
|
/** Number of peers from which we're downloading blocks. */
|
||||||
|
int nPeersWithValidatedDownloads = 0;
|
||||||
} // anon namespace
|
} // anon namespace
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -259,6 +257,8 @@ struct CNodeState {
|
||||||
//! Since when we're stalling block download progress (in microseconds), or 0.
|
//! Since when we're stalling block download progress (in microseconds), or 0.
|
||||||
int64_t nStallingSince;
|
int64_t nStallingSince;
|
||||||
list<QueuedBlock> vBlocksInFlight;
|
list<QueuedBlock> vBlocksInFlight;
|
||||||
|
//! When the first entry in vBlocksInFlight started downloading. Don't care when vBlocksInFlight is empty.
|
||||||
|
int64_t nDownloadingSince;
|
||||||
int nBlocksInFlight;
|
int nBlocksInFlight;
|
||||||
int nBlocksInFlightValidHeaders;
|
int nBlocksInFlightValidHeaders;
|
||||||
//! Whether we consider this a preferred download peer.
|
//! Whether we consider this a preferred download peer.
|
||||||
|
@ -276,6 +276,7 @@ struct CNodeState {
|
||||||
pindexBestHeaderSent = NULL;
|
pindexBestHeaderSent = NULL;
|
||||||
fSyncStarted = false;
|
fSyncStarted = false;
|
||||||
nStallingSince = 0;
|
nStallingSince = 0;
|
||||||
|
nDownloadingSince = 0;
|
||||||
nBlocksInFlight = 0;
|
nBlocksInFlight = 0;
|
||||||
nBlocksInFlightValidHeaders = 0;
|
nBlocksInFlightValidHeaders = 0;
|
||||||
fPreferredDownload = false;
|
fPreferredDownload = false;
|
||||||
|
@ -310,12 +311,6 @@ void UpdatePreferredDownload(CNode* node, CNodeState* state)
|
||||||
nPreferredDownload += state->fPreferredDownload;
|
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) {
|
void InitializeNode(NodeId nodeid, const CNode *pnode) {
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
CNodeState &state = mapNodeState.insert(std::make_pair(nodeid, CNodeState())).first->second;
|
CNodeState &state = mapNodeState.insert(std::make_pair(nodeid, CNodeState())).first->second;
|
||||||
|
@ -335,11 +330,12 @@ void FinalizeNode(NodeId nodeid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_FOREACH(const QueuedBlock& entry, state->vBlocksInFlight) {
|
BOOST_FOREACH(const QueuedBlock& entry, state->vBlocksInFlight) {
|
||||||
nQueuedValidatedHeaders -= entry.fValidatedHeaders;
|
|
||||||
mapBlocksInFlight.erase(entry.hash);
|
mapBlocksInFlight.erase(entry.hash);
|
||||||
}
|
}
|
||||||
EraseOrphansFor(nodeid);
|
EraseOrphansFor(nodeid);
|
||||||
nPreferredDownload -= state->fPreferredDownload;
|
nPreferredDownload -= state->fPreferredDownload;
|
||||||
|
nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0);
|
||||||
|
assert(nPeersWithValidatedDownloads >= 0);
|
||||||
|
|
||||||
mapNodeState.erase(nodeid);
|
mapNodeState.erase(nodeid);
|
||||||
}
|
}
|
||||||
|
@ -350,8 +346,15 @@ bool MarkBlockAsReceived(const uint256& hash) {
|
||||||
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash);
|
map<uint256, pair<NodeId, list<QueuedBlock>::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash);
|
||||||
if (itInFlight != mapBlocksInFlight.end()) {
|
if (itInFlight != mapBlocksInFlight.end()) {
|
||||||
CNodeState *state = State(itInFlight->second.first);
|
CNodeState *state = State(itInFlight->second.first);
|
||||||
nQueuedValidatedHeaders -= itInFlight->second.second->fValidatedHeaders;
|
|
||||||
state->nBlocksInFlightValidHeaders -= 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->vBlocksInFlight.erase(itInFlight->second.second);
|
||||||
state->nBlocksInFlight--;
|
state->nBlocksInFlight--;
|
||||||
state->nStallingSince = 0;
|
state->nStallingSince = 0;
|
||||||
|
@ -369,12 +372,17 @@ void MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const Consensus::Pa
|
||||||
// Make sure it's not listed somewhere already.
|
// Make sure it's not listed somewhere already.
|
||||||
MarkBlockAsReceived(hash);
|
MarkBlockAsReceived(hash);
|
||||||
|
|
||||||
int64_t nNow = GetTimeMicros();
|
QueuedBlock newentry = {hash, pindex, pindex != NULL};
|
||||||
QueuedBlock newentry = {hash, pindex, nNow, pindex != NULL, GetBlockTimeout(nNow, nQueuedValidatedHeaders, consensusParams)};
|
|
||||||
nQueuedValidatedHeaders += newentry.fValidatedHeaders;
|
|
||||||
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
|
list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), newentry);
|
||||||
state->nBlocksInFlight++;
|
state->nBlocksInFlight++;
|
||||||
state->nBlocksInFlightValidHeaders += newentry.fValidatedHeaders;
|
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);
|
mapBlocksInFlight[hash] = std::make_pair(nodeid, it);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3894,7 +3902,6 @@ void UnloadBlockIndex()
|
||||||
nBlockSequenceId = 1;
|
nBlockSequenceId = 1;
|
||||||
mapBlockSource.clear();
|
mapBlockSource.clear();
|
||||||
mapBlocksInFlight.clear();
|
mapBlocksInFlight.clear();
|
||||||
nQueuedValidatedHeaders = 0;
|
|
||||||
nPreferredDownload = 0;
|
nPreferredDownload = 0;
|
||||||
setDirtyBlockIndex.clear();
|
setDirtyBlockIndex.clear();
|
||||||
setDirtyFileInfo.clear();
|
setDirtyFileInfo.clear();
|
||||||
|
@ -5811,24 +5818,15 @@ bool SendMessages(CNode* pto)
|
||||||
LogPrintf("Peer=%d is stalling block download, disconnecting\n", pto->id);
|
LogPrintf("Peer=%d is stalling block download, disconnecting\n", pto->id);
|
||||||
pto->fDisconnect = true;
|
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
|
// 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
|
// (with N the number of peers from which we're downloading validated blocks), disconnect due to timeout.
|
||||||
// timeout. We compensate for in-flight blocks to prevent killing off peers due to our own downstream link
|
// 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
|
// being saturated. We only count validated in-flight blocks so peers can't advertise non-existing block hashes
|
||||||
// to unreasonably increase our timeout.
|
// 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) {
|
if (!pto->fDisconnect && state.vBlocksInFlight.size() > 0) {
|
||||||
QueuedBlock &queuedBlock = state.vBlocksInFlight.front();
|
QueuedBlock &queuedBlock = state.vBlocksInFlight.front();
|
||||||
int64_t nTimeoutIfRequestedNow = GetBlockTimeout(nNow, nQueuedValidatedHeaders - state.nBlocksInFlightValidHeaders, consensusParams);
|
int nOtherPeersWithValidatedDownloads = nPeersWithValidatedDownloads - (state.nBlocksInFlightValidHeaders > 0);
|
||||||
if (queuedBlock.nTimeDisconnect > nTimeoutIfRequestedNow) {
|
if (nNow > state.nDownloadingSince + consensusParams.nPowTargetSpacing * (BLOCK_DOWNLOAD_TIMEOUT_BASE + BLOCK_DOWNLOAD_TIMEOUT_PER_PEER * nOtherPeersWithValidatedDownloads)) {
|
||||||
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) {
|
|
||||||
LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->id);
|
LogPrintf("Timeout downloading block %s from peer=%d, disconnecting\n", queuedBlock.hash.ToString(), pto->id);
|
||||||
pto->fDisconnect = true;
|
pto->fDisconnect = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,10 @@ static const unsigned int AVG_INVENTORY_BROADCAST_INTERVAL = 5;
|
||||||
static const unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
|
static const unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
|
||||||
/** Maximum feefilter broadcast delay after significant change. */
|
/** Maximum feefilter broadcast delay after significant change. */
|
||||||
static const unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
|
static const unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
|
||||||
|
/** 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 unsigned int DEFAULT_LIMITFREERELAY = 15;
|
||||||
static const bool DEFAULT_RELAYPRIORITY = true;
|
static const bool DEFAULT_RELAYPRIORITY = true;
|
||||||
|
|
Loading…
Reference in a new issue