mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
partial bitcoin#25454: Avoid multiple getheaders messages in flight to the same peer
excludes: - 99f4785cad94657dcf349d00fdd6f1d44cac9bb0
This commit is contained in:
parent
26d477b6ae
commit
c9923ca36b
@ -98,6 +98,8 @@ static constexpr auto UNCONDITIONAL_RELAY_DELAY = 2min;
|
||||
* Timeout = base + per_header * (expected number of headers) */
|
||||
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
|
||||
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1ms;
|
||||
/** How long to wait for a peer to respond to a getheaders request */
|
||||
static constexpr auto HEADERS_RESPONSE_TIME{2min};
|
||||
/** Protect at least this many outbound peers from disconnection due to slow/
|
||||
* behind headers chain.
|
||||
*/
|
||||
@ -356,6 +358,9 @@ struct Peer {
|
||||
/** Work queue of items requested by this peer **/
|
||||
std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
|
||||
|
||||
/** Time of the last getheaders message to this peer */
|
||||
std::atomic<std::chrono::seconds> m_last_getheaders_timestamp{0s};
|
||||
|
||||
explicit Peer(NodeId id, bool block_relay_only)
|
||||
: m_id(id)
|
||||
, m_tx_relay(std::make_unique<TxRelay>())
|
||||
@ -422,7 +427,7 @@ private:
|
||||
void ProcessPeerMsgRet(const PeerMsgRet& ret, CNode& pfrom) EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||
|
||||
/** Consider evicting an outbound peer based on the amount of time they've been behind our tip */
|
||||
void ConsiderEviction(CNode& pto, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
void ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
/** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */
|
||||
void EvictExtraOutboundPeers(std::chrono::seconds now) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
@ -473,10 +478,27 @@ private:
|
||||
void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||
/** Process a single headers message from a peer. */
|
||||
void ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
||||
void ProcessHeadersMessage(CNode& pfrom, Peer& peer,
|
||||
const std::vector<CBlockHeader>& headers,
|
||||
bool via_compact_block)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||
/** Various helpers for headers processing, invoked by ProcessHeadersMessage() */
|
||||
/** Deal with state tracking and headers sync for peers that send the
|
||||
* occasional non-connecting header (this can happen due to BIP 130 headers
|
||||
* announcements for blocks interacting with the 2hr (MAX_FUTURE_BLOCK_TIME) rule). */
|
||||
void HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer, const std::vector<CBlockHeader>& headers)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||
/** Return true if the headers connect to each other, false otherwise */
|
||||
bool CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const;
|
||||
/** Request further headers from this peer with a given locator.
|
||||
* We don't issue a getheaders message if we have a recent one outstanding.
|
||||
* This returns true if a getheaders is actually sent, and false otherwise.
|
||||
*/
|
||||
bool MaybeSendGetHeaders(CNode& pfrom, const std::string& msg_type, const CBlockLocator& locator, Peer& peer);
|
||||
/** Potentially fetch blocks from this peer upon receipt of a new headers tip */
|
||||
void HeadersDirectFetchBlocks(CNode& pfrom, const CBlockIndex* pindexLast);
|
||||
/** Update peer state based on received headers message */
|
||||
void UpdatePeerStateForReceivedHeaders(CNode& pfrom, const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers);
|
||||
|
||||
void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||
@ -2795,7 +2817,205 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, c
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp));
|
||||
}
|
||||
|
||||
void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
||||
/**
|
||||
* Special handling for unconnecting headers that might be part of a block
|
||||
* announcement.
|
||||
*
|
||||
* We'll send a getheaders message in response to try to connect the chain.
|
||||
*
|
||||
* The peer can send up to MAX_UNCONNECTING_HEADERS in a row that
|
||||
* don't connect before given DoS points.
|
||||
*
|
||||
* Once a headers message is received that is valid and does connect,
|
||||
* nUnconnectingHeaders gets reset back to 0.
|
||||
*/
|
||||
void PeerManagerImpl::HandleFewUnconnectingHeaders(CNode& pfrom, Peer& peer,
|
||||
const std::vector<CBlockHeader>& headers)
|
||||
{
|
||||
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
|
||||
|
||||
LOCK(cs_main);
|
||||
CNodeState *nodestate = State(pfrom.GetId());
|
||||
|
||||
nodestate->nUnconnectingHeaders++;
|
||||
// Try to fill in the missing headers.
|
||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
if (MaybeSendGetHeaders(pfrom, msg_type, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), peer)) {
|
||||
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending %s (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
|
||||
headers[0].GetHash().ToString(),
|
||||
headers[0].hashPrevBlock.ToString(),
|
||||
msg_type,
|
||||
m_chainman.m_best_header->nHeight,
|
||||
pfrom.GetId(), nodestate->nUnconnectingHeaders);
|
||||
}
|
||||
// Set hashLastUnknownBlock for this peer, so that if we
|
||||
// eventually get the headers - even from a different peer -
|
||||
// we can use this peer to download.
|
||||
UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash());
|
||||
|
||||
// The peer may just be broken, so periodically assign DoS points if this
|
||||
// condition persists.
|
||||
if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) {
|
||||
Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders));
|
||||
}
|
||||
}
|
||||
|
||||
bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const
|
||||
{
|
||||
uint256 hashLastBlock;
|
||||
for (const CBlockHeader& header : headers) {
|
||||
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
|
||||
return false;
|
||||
}
|
||||
hashLastBlock = header.GetHash();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PeerManagerImpl::MaybeSendGetHeaders(CNode& pfrom, const std::string& msg_type, const CBlockLocator& locator, Peer& peer)
|
||||
{
|
||||
assert(msg_type == NetMsgType::GETHEADERS || msg_type == NetMsgType::GETHEADERS2);
|
||||
|
||||
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
|
||||
|
||||
const auto current_time = GetTime<std::chrono::seconds>();
|
||||
// Only allow a new getheaders message to go out if we don't have a recent
|
||||
// one already in-flight
|
||||
if (peer.m_last_getheaders_timestamp.load() < current_time - HEADERS_RESPONSE_TIME) {
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(msg_type, locator, uint256()));
|
||||
peer.m_last_getheaders_timestamp = current_time;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a new headers tip ending in pindexLast, potentially request blocks towards that tip.
|
||||
* We require that the given tip have at least as much work as our tip, and for
|
||||
* our current tip to be "close to synced" (see CanDirectFetch()).
|
||||
*/
|
||||
void PeerManagerImpl::HeadersDirectFetchBlocks(CNode& pfrom, const CBlockIndex* pindexLast)
|
||||
{
|
||||
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
|
||||
|
||||
LOCK(cs_main);
|
||||
CNodeState *nodestate = State(pfrom.GetId());
|
||||
|
||||
if (CanDirectFetch() && pindexLast->IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= pindexLast->nChainWork) {
|
||||
|
||||
std::vector<const CBlockIndex*> vToFetch;
|
||||
const CBlockIndex *pindexWalk = pindexLast;
|
||||
// Calculate all the blocks we'd need to switch to pindexLast, up to a limit.
|
||||
while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
||||
if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) &&
|
||||
!mapBlocksInFlight.count(pindexWalk->GetBlockHash())) {
|
||||
// We don't have this block, and it's not yet in flight.
|
||||
vToFetch.push_back(pindexWalk);
|
||||
}
|
||||
pindexWalk = pindexWalk->pprev;
|
||||
}
|
||||
// If pindexWalk still isn't on our main chain, we're looking at a
|
||||
// very large reorg at a time we think we're close to caught up to
|
||||
// the main chain -- this shouldn't really happen. Bail out on the
|
||||
// direct fetch and rely on parallel download instead.
|
||||
if (!m_chainman.ActiveChain().Contains(pindexWalk)) {
|
||||
LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n",
|
||||
pindexLast->GetBlockHash().ToString(),
|
||||
pindexLast->nHeight);
|
||||
} else {
|
||||
std::vector<CInv> vGetData;
|
||||
// Download as much as possible, from earliest to latest.
|
||||
for (const CBlockIndex *pindex : reverse_iterate(vToFetch)) {
|
||||
if (nodestate->nBlocksInFlight >= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
||||
// Can't download any more from this peer
|
||||
break;
|
||||
}
|
||||
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
||||
MarkBlockAsInFlight(pfrom.GetId(), pindex->GetBlockHash(), pindex);
|
||||
LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
|
||||
pindex->GetBlockHash().ToString(), pfrom.GetId());
|
||||
}
|
||||
if (vGetData.size() > 1) {
|
||||
LogPrint(BCLog::NET, "Downloading blocks toward %s (%d) via headers direct fetch\n",
|
||||
pindexLast->GetBlockHash().ToString(), pindexLast->nHeight);
|
||||
}
|
||||
if (vGetData.size() > 0) {
|
||||
if (!m_ignore_incoming_txs &&
|
||||
nodestate->m_provides_cmpctblocks &&
|
||||
vGetData.size() == 1 &&
|
||||
mapBlocksInFlight.size() == 1 &&
|
||||
pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
|
||||
// In any case, we want to download using a compact block, not a regular one
|
||||
vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
|
||||
}
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given receipt of headers from a peer ending in pindexLast, along with
|
||||
* whether that header was new and whether the headers message was full,
|
||||
* update the state we keep for the peer.
|
||||
*/
|
||||
void PeerManagerImpl::UpdatePeerStateForReceivedHeaders(CNode& pfrom,
|
||||
const CBlockIndex *pindexLast, bool received_new_header, bool may_have_more_headers)
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CNodeState *nodestate = State(pfrom.GetId());
|
||||
if (nodestate->nUnconnectingHeaders > 0) {
|
||||
LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom.GetId(), nodestate->nUnconnectingHeaders);
|
||||
}
|
||||
nodestate->nUnconnectingHeaders = 0;
|
||||
|
||||
assert(pindexLast);
|
||||
UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash());
|
||||
|
||||
// From here, pindexBestKnownBlock should be guaranteed to be non-null,
|
||||
// because it is set in UpdateBlockAvailability. Some nullptr checks
|
||||
// are still present, however, as belt-and-suspenders.
|
||||
|
||||
if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) {
|
||||
nodestate->m_last_block_announcement = GetTime();
|
||||
}
|
||||
|
||||
// If we're in IBD, we want outbound peers that will serve us a useful
|
||||
// chain. Disconnect peers that are on chains with insufficient work.
|
||||
if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !may_have_more_headers) {
|
||||
// If the peer has no more headers to give us, then we know we have
|
||||
// their tip.
|
||||
if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
|
||||
// This peer has too little work on their headers chain to help
|
||||
// us sync -- disconnect if it is an outbound disconnection
|
||||
// candidate.
|
||||
// Note: We compare their tip to nMinimumChainWork (rather than
|
||||
// m_chainman.ActiveChain().Tip()) because we won't start block download
|
||||
// until we have a headers chain that has at least
|
||||
// nMinimumChainWork, even if a peer has a chain past our tip,
|
||||
// as an anti-DoS measure.
|
||||
if (pfrom.IsOutboundOrBlockRelayConn()) {
|
||||
LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this is an outbound full-relay peer, check to see if we should protect
|
||||
// it from the bad/lagging chain logic.
|
||||
// Note that outbound block-relay peers are excluded from this protection, and
|
||||
// thus always subject to eviction under the bad/lagging chain logic.
|
||||
// See ChainSyncTimeoutState.
|
||||
if (!pfrom.fDisconnect && pfrom.IsFullOutboundConn() && nodestate->pindexBestKnownBlock != nullptr) {
|
||||
if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) {
|
||||
LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom.GetId());
|
||||
nodestate->m_chain_sync.m_protect = true;
|
||||
++m_outbound_peers_with_protect_from_disconnect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
|
||||
const std::vector<CBlockHeader>& headers,
|
||||
bool via_compact_block)
|
||||
{
|
||||
@ -2807,57 +3027,33 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
||||
return;
|
||||
}
|
||||
|
||||
bool received_new_header = false;
|
||||
const CBlockIndex *pindexLast = nullptr;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CNodeState *nodestate = State(pfrom.GetId());
|
||||
|
||||
// If this looks like it could be a block announcement (nCount <=
|
||||
// MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers that
|
||||
// don't connect:
|
||||
// - Send a getheaders message in response to try to connect the chain.
|
||||
// - The peer can send up to MAX_UNCONNECTING_HEADERS in a row that
|
||||
// don't connect before giving DoS points
|
||||
// - Once a headers message is received that is valid and does connect,
|
||||
// nUnconnectingHeaders gets reset back to 0.
|
||||
if (!m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) && nCount <= MAX_BLOCKS_TO_ANNOUNCE) {
|
||||
nodestate->nUnconnectingHeaders++;
|
||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(msg_type, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256()));
|
||||
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending %s (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
|
||||
headers[0].GetHash().ToString(),
|
||||
headers[0].hashPrevBlock.ToString(),
|
||||
msg_type,
|
||||
m_chainman.m_best_header->nHeight,
|
||||
pfrom.GetId(), nodestate->nUnconnectingHeaders);
|
||||
// Set hashLastUnknownBlock for this peer, so that if we
|
||||
// eventually get the headers - even from a different peer -
|
||||
// we can use this peer to download.
|
||||
UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash());
|
||||
// Do these headers connect to something in our block index?
|
||||
bool headers_connect_blockindex{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers[0].hashPrevBlock) != nullptr)};
|
||||
|
||||
if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) {
|
||||
Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 hashLastBlock;
|
||||
for (const CBlockHeader& header : headers) {
|
||||
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
|
||||
Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence");
|
||||
return;
|
||||
}
|
||||
hashLastBlock = header.GetHash();
|
||||
}
|
||||
|
||||
// If we don't have the last header, then they'll have given us
|
||||
// something new (if these headers are valid).
|
||||
if (!m_chainman.m_blockman.LookupBlockIndex(hashLastBlock)) {
|
||||
received_new_header = true;
|
||||
if (!headers_connect_blockindex) {
|
||||
if (nCount <= MAX_BLOCKS_TO_ANNOUNCE) {
|
||||
// If this looks like it could be a BIP 130 block announcement, use
|
||||
// special logic for handling headers that don't connect, as this
|
||||
// could be benign.
|
||||
HandleFewUnconnectingHeaders(pfrom, peer, headers);
|
||||
} else {
|
||||
Misbehaving(pfrom.GetId(), 10, "invalid header received");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the headers connect to something in our block index.
|
||||
if (!CheckHeadersAreContinuous(headers)) {
|
||||
Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have the last header, then this peer will have given us
|
||||
// something new (if these headers are valid).
|
||||
bool received_new_header{WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(headers.back().GetHash()) == nullptr)};
|
||||
|
||||
BlockValidationState state;
|
||||
if (!m_chainman.ProcessNewBlockHeaders(headers, state, m_chainparams, &pindexLast)) {
|
||||
if (state.IsInvalid()) {
|
||||
@ -2866,122 +3062,21 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CNodeState *nodestate = State(pfrom.GetId());
|
||||
if (nodestate->nUnconnectingHeaders > 0) {
|
||||
LogPrint(BCLog::NET, "peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n", pfrom.GetId(), nodestate->nUnconnectingHeaders);
|
||||
}
|
||||
nodestate->nUnconnectingHeaders = 0;
|
||||
|
||||
assert(pindexLast);
|
||||
UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash());
|
||||
|
||||
// From here, pindexBestKnownBlock should be guaranteed to be non-null,
|
||||
// because it is set in UpdateBlockAvailability. Some nullptr checks
|
||||
// are still present, however, as belt-and-suspenders.
|
||||
|
||||
if (received_new_header && pindexLast->nChainWork > m_chainman.ActiveChain().Tip()->nChainWork) {
|
||||
nodestate->m_last_block_announcement = GetTime();
|
||||
}
|
||||
|
||||
if (nCount == MAX_HEADERS_RESULTS) {
|
||||
// Headers message had its maximum size; the peer may have more headers.
|
||||
// TODO: optimize: if pindexLast is an ancestor of m_chainman.ActiveChain().Tip or m_chainman.m_best_header, continue
|
||||
// from there instead.
|
||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
// Consider fetching more headers.
|
||||
if (nCount == MAX_HEADERS_RESULTS) {
|
||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
// Headers message had its maximum size; the peer may have more headers.
|
||||
if (MaybeSendGetHeaders(pfrom, msg_type, m_chainman.ActiveChain().GetLocator(pindexLast), peer)) {
|
||||
LogPrint(BCLog::NET, "more %s (%d) to end to peer=%d (startheight:%d)\n",
|
||||
msg_type, pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height);
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(msg_type, m_chainman.ActiveChain().GetLocator(pindexLast), uint256()));
|
||||
}
|
||||
|
||||
bool fCanDirectFetch = CanDirectFetch();
|
||||
// If this set of headers is valid and ends in a block with at least as
|
||||
// much work as our tip, download as much as possible.
|
||||
if (fCanDirectFetch && pindexLast->IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= pindexLast->nChainWork) {
|
||||
std::vector<const CBlockIndex*> vToFetch;
|
||||
const CBlockIndex *pindexWalk = pindexLast;
|
||||
// Calculate all the blocks we'd need to switch to pindexLast, up to a limit.
|
||||
while (pindexWalk && !m_chainman.ActiveChain().Contains(pindexWalk) && vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
||||
if (!(pindexWalk->nStatus & BLOCK_HAVE_DATA) &&
|
||||
!mapBlocksInFlight.count(pindexWalk->GetBlockHash())) {
|
||||
// We don't have this block, and it's not yet in flight.
|
||||
vToFetch.push_back(pindexWalk);
|
||||
}
|
||||
pindexWalk = pindexWalk->pprev;
|
||||
}
|
||||
// If pindexWalk still isn't on our main chain, we're looking at a
|
||||
// very large reorg at a time we think we're close to caught up to
|
||||
// the main chain -- this shouldn't really happen. Bail out on the
|
||||
// direct fetch and rely on parallel download instead.
|
||||
if (!m_chainman.ActiveChain().Contains(pindexWalk)) {
|
||||
LogPrint(BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n",
|
||||
pindexLast->GetBlockHash().ToString(),
|
||||
pindexLast->nHeight);
|
||||
} else {
|
||||
std::vector<CInv> vGetData;
|
||||
// Download as much as possible, from earliest to latest.
|
||||
for (const CBlockIndex *pindex : reverse_iterate(vToFetch)) {
|
||||
if (nodestate->nBlocksInFlight >= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
|
||||
// Can't download any more from this peer
|
||||
break;
|
||||
}
|
||||
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
|
||||
MarkBlockAsInFlight(pfrom.GetId(), pindex->GetBlockHash(), pindex);
|
||||
LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
|
||||
pindex->GetBlockHash().ToString(), pfrom.GetId());
|
||||
}
|
||||
if (vGetData.size() > 1) {
|
||||
LogPrint(BCLog::NET, "Downloading blocks toward %s (%d) via headers direct fetch\n",
|
||||
pindexLast->GetBlockHash().ToString(), pindexLast->nHeight);
|
||||
}
|
||||
if (vGetData.size() > 0) {
|
||||
if (!m_ignore_incoming_txs &&
|
||||
nodestate->m_provides_cmpctblocks &&
|
||||
vGetData.size() == 1 &&
|
||||
mapBlocksInFlight.size() == 1 &&
|
||||
pindexLast->pprev->IsValid(BLOCK_VALID_CHAIN)) {
|
||||
// In any case, we want to download using a compact block, not a regular one
|
||||
vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
|
||||
}
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we're in IBD, we want outbound peers that will serve us a useful
|
||||
// chain. Disconnect peers that are on chains with insufficient work.
|
||||
if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) {
|
||||
// When nCount < MAX_HEADERS_RESULTS, we know we have no more
|
||||
// headers to fetch from this peer.
|
||||
if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
|
||||
// This peer has too little work on their headers chain to help
|
||||
// us sync -- disconnect if it is an outbound disconnection
|
||||
// candidate.
|
||||
// Note: We compare their tip to nMinimumChainWork (rather than
|
||||
// m_chainman.ActiveChain().Tip()) because we won't start block download
|
||||
// until we have a headers chain that has at least
|
||||
// nMinimumChainWork, even if a peer has a chain past our tip,
|
||||
// as an anti-DoS measure.
|
||||
if (pfrom.IsOutboundOrBlockRelayConn()) {
|
||||
LogPrintf("Disconnecting outbound peer %d -- headers chain has insufficient work\n", pfrom.GetId());
|
||||
pfrom.fDisconnect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If this is an outbound full-relay peer, check to see if we should protect
|
||||
// it from the bad/lagging chain logic.
|
||||
// Note that outbound block-relay peers are excluded from this protection, and
|
||||
// thus always subject to eviction under the bad/lagging chain logic.
|
||||
// See ChainSyncTimeoutState.
|
||||
if (!pfrom.fDisconnect && pfrom.IsFullOutboundConn() && nodestate->pindexBestKnownBlock != nullptr) {
|
||||
if (m_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= m_chainman.ActiveChain().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) {
|
||||
LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom.GetId());
|
||||
nodestate->m_chain_sync.m_protect = true;
|
||||
++m_outbound_peers_with_protect_from_disconnect;
|
||||
}
|
||||
msg_type, pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height);
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePeerStateForReceivedHeaders(pfrom, pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS);
|
||||
|
||||
// Consider immediately downloading blocks.
|
||||
HeadersDirectFetchBlocks(pfrom, pindexLast);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3849,8 +3944,11 @@ void PeerManagerImpl::ProcessMessage(
|
||||
}
|
||||
if (best_block != nullptr) {
|
||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(msg_type, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *best_block));
|
||||
LogPrint(BCLog::NET, "%s (%d) %s to peer=%d\n", msg_type, m_chainman.m_best_header->nHeight, best_block->ToString(), pfrom.GetId());
|
||||
if (MaybeSendGetHeaders(pfrom, msg_type, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer)) {
|
||||
LogPrint(BCLog::NET, "%s (%d) %s to peer=%d\n",
|
||||
msg_type, m_chainman.m_best_header->nHeight, best_block->ToString(),
|
||||
pfrom.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
@ -4025,7 +4123,11 @@ void PeerManagerImpl::ProcessMessage(
|
||||
// others.
|
||||
if (m_chainman.ActiveTip() == nullptr ||
|
||||
(m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) {
|
||||
LogPrint(BCLog::NET, "Ignoring %s from peer=%d because active chain has too little work\n", msg_type, pfrom.GetId());
|
||||
LogPrint(BCLog::NET, "Ignoring %s from peer=%d because active chain has too little work; sending empty response\n", msg_type, pfrom.GetId());
|
||||
// Just respond with an empty headers message, to tell the peer to
|
||||
// go away but not treat us as unresponsive.
|
||||
std::string ret_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::HEADERS2 : NetMsgType::HEADERS;
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(ret_type, std::vector<CBlock>()));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4279,8 +4381,10 @@ void PeerManagerImpl::ProcessMessage(
|
||||
|
||||
if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
|
||||
// Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
|
||||
if (!m_chainman.ActiveChainstate().IsInitialBlockDownload())
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make((pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256()));
|
||||
if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
|
||||
std::string ret_val = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
MaybeSendGetHeaders(pfrom, ret_val, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4551,6 +4655,10 @@ void PeerManagerImpl::ProcessMessage(
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume that this is in response to any outstanding getheaders
|
||||
// request we may have sent, and clear out the time of our last request
|
||||
peer->m_last_getheaders_timestamp = 0s;
|
||||
|
||||
std::vector<CBlockHeader> headers;
|
||||
|
||||
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
|
||||
@ -5061,7 +5169,7 @@ bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt
|
||||
return fMoreWork;
|
||||
}
|
||||
|
||||
void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_seconds)
|
||||
void PeerManagerImpl::ConsiderEviction(CNode& pto, Peer& peer, std::chrono::seconds time_in_seconds)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
|
||||
@ -5099,15 +5207,16 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_
|
||||
pto.fDisconnect = true;
|
||||
} else {
|
||||
assert(state.m_chain_sync.m_work_header);
|
||||
// Here, we assume that the getheaders message goes out,
|
||||
// because it'll either go out or be skipped because of a
|
||||
// getheaders in-flight already, in which case the peer should
|
||||
// still respond to us with a sufficiently high work chain tip.
|
||||
std::string msg_type = (pto.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
LogPrint(BCLog::NET, "sending %s to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n",
|
||||
msg_type,
|
||||
pto.GetId(),
|
||||
state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "<none>",
|
||||
state.m_chain_sync.m_work_header->GetBlockHash().ToString());
|
||||
m_connman.PushMessage(&pto, msgMaker.Make(msg_type, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256()));
|
||||
MaybeSendGetHeaders(pto,
|
||||
msg_type, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev),
|
||||
peer);
|
||||
LogPrint(BCLog::NET, "sending %s to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", msg_type, pto.GetId(), state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "<none>", state.m_chain_sync.m_work_header->GetBlockHash().ToString());
|
||||
state.m_chain_sync.m_sent_getheaders = true;
|
||||
constexpr auto HEADERS_RESPONSE_TIME{2min};
|
||||
// Bump the timeout to allow a response, which could clear the timeout
|
||||
// (if the response shows the peer has synced), reset the timeout (if
|
||||
// the peer syncs to the required work but not to our tip), or result
|
||||
@ -5464,15 +5573,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
|
||||
if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex && pto->CanRelay()) {
|
||||
// Only actively request headers from a single peer, unless we're close to end of initial download.
|
||||
if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - nMaxTipAge) {
|
||||
state.fSyncStarted = true;
|
||||
state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE +
|
||||
(
|
||||
// Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling
|
||||
// to maintain precision
|
||||
std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} *
|
||||
(GetAdjustedTime() - m_chainman.m_best_header->GetBlockTime()) / consensusParams.nPowTargetSpacing
|
||||
);
|
||||
nSyncStarted++;
|
||||
const CBlockIndex* pindexStart = m_chainman.m_best_header;
|
||||
/* If possible, start at the block preceding the currently
|
||||
best known header. This ensures that we always get a
|
||||
@ -5484,8 +5584,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
|
||||
if (pindexStart->pprev)
|
||||
pindexStart = pindexStart->pprev;
|
||||
std::string msg_type = (pto->nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||
LogPrint(BCLog::NET, "initial %s (%d) to peer=%d (startheight:%d)\n", msg_type, pindexStart->nHeight, pto->GetId(), peer->m_starting_height);
|
||||
m_connman.PushMessage(pto, msgMaker.Make(msg_type, m_chainman.ActiveChain().GetLocator(pindexStart), uint256()));
|
||||
if (MaybeSendGetHeaders(*pto, msg_type, m_chainman.ActiveChain().GetLocator(pindexStart), *peer)) {
|
||||
LogPrint(BCLog::NET, "initial %s (%d) to peer=%d (startheight:%d)\n", msg_type, pindexStart->nHeight, pto->GetId(), peer->m_starting_height);
|
||||
|
||||
state.fSyncStarted = true;
|
||||
state.m_headers_sync_timeout = current_time + HEADERS_DOWNLOAD_TIMEOUT_BASE +
|
||||
(
|
||||
// Convert HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER to microseconds before scaling
|
||||
// to maintain precision
|
||||
std::chrono::microseconds{HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER} *
|
||||
(GetAdjustedTime() - m_chainman.m_best_header->GetBlockTime()) / consensusParams.nPowTargetSpacing
|
||||
);
|
||||
nSyncStarted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5880,7 +5991,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
|
||||
|
||||
// Check that outbound peers have reasonable chains
|
||||
// GetTime() is used by this anti-DoS logic so we can test this using mocktime
|
||||
ConsiderEviction(*pto, GetTime<std::chrono::seconds>());
|
||||
ConsiderEviction(*pto, *peer, GetTime<std::chrono::seconds>());
|
||||
|
||||
//
|
||||
// Message: getdata (blocks)
|
||||
|
@ -85,7 +85,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
|
||||
msg.hashstop = 0
|
||||
peer.send_and_ping(msg)
|
||||
time.sleep(5)
|
||||
assert "headers" not in peer.last_message
|
||||
assert ("headers" not in peer.last_message or len(peer.last_message["headers"].headers) == 0)
|
||||
|
||||
self.log.info("Generating one more block")
|
||||
self.nodes[0].generatetoaddress(1, self.nodes[0].get_deterministic_priv_key().address)
|
||||
|
Loading…
Reference in New Issue
Block a user