mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +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) */
|
* Timeout = base + per_header * (expected number of headers) */
|
||||||
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
|
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_BASE = 15min;
|
||||||
static constexpr auto HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1ms;
|
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/
|
/** Protect at least this many outbound peers from disconnection due to slow/
|
||||||
* behind headers chain.
|
* behind headers chain.
|
||||||
*/
|
*/
|
||||||
@ -356,6 +358,9 @@ struct Peer {
|
|||||||
/** Work queue of items requested by this peer **/
|
/** Work queue of items requested by this peer **/
|
||||||
std::deque<CInv> m_getdata_requests GUARDED_BY(m_getdata_requests_mutex);
|
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)
|
explicit Peer(NodeId id, bool block_relay_only)
|
||||||
: m_id(id)
|
: m_id(id)
|
||||||
, m_tx_relay(std::make_unique<TxRelay>())
|
, 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);
|
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 */
|
/** 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 */
|
/** 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);
|
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)
|
void ProcessOrphanTx(std::set<uint256>& orphan_work_set) EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans)
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||||
/** Process a single headers message from a peer. */
|
/** 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,
|
const std::vector<CBlockHeader>& headers,
|
||||||
bool via_compact_block)
|
bool via_compact_block)
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
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)
|
void SendBlockTransactions(CNode& pfrom, const CBlock& block, const BlockTransactionsRequest& req)
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
|
||||||
@ -2795,110 +2817,92 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, const CBlock& block, c
|
|||||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp));
|
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
/**
|
||||||
const std::vector<CBlockHeader>& headers,
|
* Special handling for unconnecting headers that might be part of a block
|
||||||
bool via_compact_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());
|
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
|
||||||
size_t nCount = headers.size();
|
|
||||||
|
|
||||||
if (nCount == 0) {
|
|
||||||
// Nothing interesting. Stop asking this peers for more headers.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool received_new_header = false;
|
|
||||||
const CBlockIndex *pindexLast = nullptr;
|
|
||||||
{
|
|
||||||
LOCK(cs_main);
|
LOCK(cs_main);
|
||||||
CNodeState *nodestate = State(pfrom.GetId());
|
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++;
|
nodestate->nUnconnectingHeaders++;
|
||||||
|
// Try to fill in the missing headers.
|
||||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
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()));
|
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",
|
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].GetHash().ToString(),
|
||||||
headers[0].hashPrevBlock.ToString(),
|
headers[0].hashPrevBlock.ToString(),
|
||||||
msg_type,
|
msg_type,
|
||||||
m_chainman.m_best_header->nHeight,
|
m_chainman.m_best_header->nHeight,
|
||||||
pfrom.GetId(), nodestate->nUnconnectingHeaders);
|
pfrom.GetId(), nodestate->nUnconnectingHeaders);
|
||||||
|
}
|
||||||
// Set hashLastUnknownBlock for this peer, so that if we
|
// Set hashLastUnknownBlock for this peer, so that if we
|
||||||
// eventually get the headers - even from a different peer -
|
// eventually get the headers - even from a different peer -
|
||||||
// we can use this peer to download.
|
// we can use this peer to download.
|
||||||
UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash());
|
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) {
|
if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == 0) {
|
||||||
Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders));
|
Misbehaving(pfrom.GetId(), 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders));
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerManagerImpl::CheckHeadersAreContinuous(const std::vector<CBlockHeader>& headers) const
|
||||||
|
{
|
||||||
uint256 hashLastBlock;
|
uint256 hashLastBlock;
|
||||||
for (const CBlockHeader& header : headers) {
|
for (const CBlockHeader& header : headers) {
|
||||||
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
|
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
|
||||||
Misbehaving(pfrom.GetId(), 20, "non-continuous headers sequence");
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
hashLastBlock = header.GetHash();
|
hashLastBlock = header.GetHash();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockValidationState state;
|
|
||||||
if (!m_chainman.ProcessNewBlockHeaders(headers, state, m_chainparams, &pindexLast)) {
|
|
||||||
if (state.IsInvalid()) {
|
|
||||||
MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
LOCK(cs_main);
|
||||||
CNodeState *nodestate = State(pfrom.GetId());
|
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);
|
if (CanDirectFetch() && pindexLast->IsValid(BLOCK_VALID_TREE) && m_chainman.ActiveChain().Tip()->nChainWork <= pindexLast->nChainWork) {
|
||||||
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;
|
|
||||||
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;
|
std::vector<const CBlockIndex*> vToFetch;
|
||||||
const CBlockIndex *pindexWalk = pindexLast;
|
const CBlockIndex *pindexWalk = pindexLast;
|
||||||
// Calculate all the blocks we'd need to switch to pindexLast, up to a limit.
|
// Calculate all the blocks we'd need to switch to pindexLast, up to a limit.
|
||||||
@ -2948,11 +2952,39 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
// 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.
|
// chain. Disconnect peers that are on chains with insufficient work.
|
||||||
if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && nCount != MAX_HEADERS_RESULTS) {
|
if (m_chainman.ActiveChainstate().IsInitialBlockDownload() && !may_have_more_headers) {
|
||||||
// When nCount < MAX_HEADERS_RESULTS, we know we have no more
|
// If the peer has no more headers to give us, then we know we have
|
||||||
// headers to fetch from this peer.
|
// their tip.
|
||||||
if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
|
if (nodestate->pindexBestKnownBlock && nodestate->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
|
||||||
// This peer has too little work on their headers chain to help
|
// This peer has too little work on their headers chain to help
|
||||||
// us sync -- disconnect if it is an outbound disconnection
|
// us sync -- disconnect if it is an outbound disconnection
|
||||||
@ -2968,6 +3000,7 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is an outbound full-relay peer, check to see if we should protect
|
// If this is an outbound full-relay peer, check to see if we should protect
|
||||||
// it from the bad/lagging chain logic.
|
// it from the bad/lagging chain logic.
|
||||||
// Note that outbound block-relay peers are excluded from this protection, and
|
// Note that outbound block-relay peers are excluded from this protection, and
|
||||||
@ -2982,6 +3015,68 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, const Peer& peer,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
|
||||||
|
const std::vector<CBlockHeader>& headers,
|
||||||
|
bool via_compact_block)
|
||||||
|
{
|
||||||
|
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
|
||||||
|
size_t nCount = headers.size();
|
||||||
|
|
||||||
|
if (nCount == 0) {
|
||||||
|
// Nothing interesting. Stop asking this peers for more headers.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CBlockIndex *pindexLast = nullptr;
|
||||||
|
|
||||||
|
// 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 (!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()) {
|
||||||
|
MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePeerStateForReceivedHeaders(pfrom, pindexLast, received_new_header, nCount == MAX_HEADERS_RESULTS);
|
||||||
|
|
||||||
|
// Consider immediately downloading blocks.
|
||||||
|
HeadersDirectFetchBlocks(pfrom, pindexLast);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3849,8 +3944,11 @@ void PeerManagerImpl::ProcessMessage(
|
|||||||
}
|
}
|
||||||
if (best_block != nullptr) {
|
if (best_block != nullptr) {
|
||||||
std::string msg_type = (pfrom.nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
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));
|
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());
|
LogPrint(BCLog::NET, "%s (%d) %s to peer=%d\n",
|
||||||
|
msg_type, m_chainman.m_best_header->nHeight, best_block->ToString(),
|
||||||
|
pfrom.GetId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -4025,7 +4123,11 @@ void PeerManagerImpl::ProcessMessage(
|
|||||||
// others.
|
// others.
|
||||||
if (m_chainman.ActiveTip() == nullptr ||
|
if (m_chainman.ActiveTip() == nullptr ||
|
||||||
(m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) {
|
(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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4279,8 +4381,10 @@ void PeerManagerImpl::ProcessMessage(
|
|||||||
|
|
||||||
if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
|
if (!m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
|
||||||
// Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
|
// Doesn't connect (or is genesis), instead of DoSing in AcceptBlockHeader, request deeper headers
|
||||||
if (!m_chainman.ActiveChainstate().IsInitialBlockDownload())
|
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()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4551,6 +4655,10 @@ void PeerManagerImpl::ProcessMessage(
|
|||||||
return;
|
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;
|
std::vector<CBlockHeader> headers;
|
||||||
|
|
||||||
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
|
// 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;
|
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);
|
AssertLockHeld(cs_main);
|
||||||
|
|
||||||
@ -5099,15 +5207,16 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, std::chrono::seconds time_in_
|
|||||||
pto.fDisconnect = true;
|
pto.fDisconnect = true;
|
||||||
} else {
|
} else {
|
||||||
assert(state.m_chain_sync.m_work_header);
|
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;
|
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",
|
MaybeSendGetHeaders(pto,
|
||||||
msg_type,
|
msg_type, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev),
|
||||||
pto.GetId(),
|
peer);
|
||||||
state.pindexBestKnownBlock != nullptr ? state.pindexBestKnownBlock->GetBlockHash().ToString() : "<none>",
|
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_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()));
|
|
||||||
state.m_chain_sync.m_sent_getheaders = true;
|
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
|
// Bump the timeout to allow a response, which could clear the timeout
|
||||||
// (if the response shows the peer has synced), reset the timeout (if
|
// (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
|
// 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()) {
|
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.
|
// 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) {
|
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;
|
const CBlockIndex* pindexStart = m_chainman.m_best_header;
|
||||||
/* If possible, start at the block preceding the currently
|
/* If possible, start at the block preceding the currently
|
||||||
best known header. This ensures that we always get a
|
best known header. This ensures that we always get a
|
||||||
@ -5484,8 +5584,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
|
|||||||
if (pindexStart->pprev)
|
if (pindexStart->pprev)
|
||||||
pindexStart = pindexStart->pprev;
|
pindexStart = pindexStart->pprev;
|
||||||
std::string msg_type = (pto->nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
std::string msg_type = (pto->nServices & NODE_HEADERS_COMPRESSED) ? NetMsgType::GETHEADERS2 : NetMsgType::GETHEADERS;
|
||||||
|
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);
|
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()));
|
|
||||||
|
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
|
// Check that outbound peers have reasonable chains
|
||||||
// GetTime() is used by this anti-DoS logic so we can test this using mocktime
|
// 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)
|
// Message: getdata (blocks)
|
||||||
|
@ -85,7 +85,7 @@ class MinimumChainWorkTest(BitcoinTestFramework):
|
|||||||
msg.hashstop = 0
|
msg.hashstop = 0
|
||||||
peer.send_and_ping(msg)
|
peer.send_and_ping(msg)
|
||||||
time.sleep(5)
|
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.log.info("Generating one more block")
|
||||||
self.nodes[0].generatetoaddress(1, self.nodes[0].get_deterministic_priv_key().address)
|
self.nodes[0].generatetoaddress(1, self.nodes[0].get_deterministic_priv_key().address)
|
||||||
|
Loading…
Reference in New Issue
Block a user