mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge 87d012324a into merged_master (Bitcoin PR bitcoin/bitcoin#25454)
This commit is contained in:
commit
6e9f70937f
3 changed files with 307 additions and 190 deletions
|
|
@ -61,6 +61,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 = 2ms;
|
||||
/** 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.
|
||||
*/
|
||||
|
|
@ -355,6 +357,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<NodeClock::time_point> m_last_getheaders_timestamp{NodeSeconds{}};
|
||||
|
||||
Peer(NodeId id)
|
||||
: m_id{id}
|
||||
{}
|
||||
|
|
@ -501,7 +506,7 @@ public:
|
|||
|
||||
private:
|
||||
/** 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);
|
||||
|
|
@ -560,6 +565,22 @@ private:
|
|||
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);
|
||||
/** 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 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, Peer& peer, const CBlock& block, const BlockTransactionsRequest& req);
|
||||
|
||||
|
|
@ -2198,6 +2219,203 @@ void PeerManagerImpl::SendBlockTransactions(CNode& pfrom, Peer& peer, const CBlo
|
|||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::BLOCKTXN, resp));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), peer)) {
|
||||
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
|
||||
headers[0].GetHash().ToString(),
|
||||
headers[0].hashPrevBlock.ToString(),
|
||||
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(peer, 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 CBlockLocator& locator, Peer& peer)
|
||||
{
|
||||
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
|
||||
|
||||
const auto current_time = NodeClock::now();
|
||||
|
||||
// Only allow a new getheaders message to go out if we don't have a recent
|
||||
// one already in-flight
|
||||
if (current_time - peer.m_last_getheaders_timestamp.load() > HEADERS_RESPONSE_TIME) {
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, 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) &&
|
||||
!IsBlockRequested(pindexWalk->GetBlockHash()) &&
|
||||
(!DeploymentActiveAt(*pindexWalk, m_chainman, Consensus::DEPLOYMENT_SEGWIT) || State(pfrom.GetId())->fHaveWitness)) {
|
||||
// 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;
|
||||
}
|
||||
uint32_t nFetchFlags = GetFetchFlags(pfrom);
|
||||
vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash()));
|
||||
BlockRequested(pfrom.GetId(), *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)
|
||||
|
|
@ -2240,55 +2458,33 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
|
|||
}
|
||||
}
|
||||
|
||||
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++;
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256()));
|
||||
LogPrint(BCLog::NET, "received header %s: missing prev block %s, sending getheaders (%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
|
||||
headers[0].GetHash().ToString(),
|
||||
headers[0].hashPrevBlock.ToString(),
|
||||
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(peer, 20, strprintf("%d non-connecting headers", nodestate->nUnconnectingHeaders));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint256 hashLastBlock;
|
||||
for (const CBlockHeader& header : headers) {
|
||||
if (!hashLastBlock.IsNull() && header.hashPrevBlock != hashLastBlock) {
|
||||
Misbehaving(peer, 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(peer, 10, "invalid header received");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, the headers connect to something in our block index.
|
||||
if (!CheckHeadersAreContinuous(headers)) {
|
||||
Misbehaving(peer, 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;
|
||||
bool all_duplicate = false;
|
||||
if (!m_chainman.ProcessNewBlockHeaders(headers, state, &pindexLast, &all_duplicate)) {
|
||||
|
|
@ -2298,139 +2494,38 @@ void PeerManagerImpl::ProcessHeadersMessage(CNode& pfrom, Peer& peer,
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
// If a peer gives us as many headers as possible, this is implicitly a signal that the
|
||||
// peer has more headers to send us. In Bitcoin Core, the node always asks for more
|
||||
// headers at this point. Our logic is slightly more complex, to work around an apparent
|
||||
// bug in the Bitcoin Core state machine, where we can end up downloading headers from
|
||||
/// lots of peers at the same time by accident.
|
||||
if (nCount == MAX_HEADERS_RESULTS) {
|
||||
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 a peer gives us as many headers as possible, this is implicitly a signal that the
|
||||
// peer has more headers to send us. In Bitcoin Core, the node always asks for more
|
||||
// headers at this point. Our logic is slightly more complex, to work around an apparent
|
||||
// bug in the Bitcoin Core state machine, where we can end up downloading headers from
|
||||
/// lots of peers at the same time by accident.
|
||||
if (nCount == MAX_HEADERS_RESULTS) {
|
||||
if (all_duplicate && !nodestate->fSyncStarted) {
|
||||
// In this case two things are true:
|
||||
// 1) This node's most recent batch of headers only included ones we already had.
|
||||
// 2) We don't have this node marked as a peer to header-sync from.
|
||||
// This happens when some exogenous event, like an INV of a new block, causes us
|
||||
// to ask a peer for an unbounded number of headers, when we're already in the
|
||||
// process of downloading the headers from a different peer.
|
||||
// In this case the right thing to do is simply stop syncing headers from this
|
||||
// peer; it's redundant. Here we do nothing; since we don't ask the peer for
|
||||
// more headers, it will stop sending them.
|
||||
} else {
|
||||
// TODO: optimize: if pindexLast is an ancestor of m_chainman.ActiveChain().Tip or m_chainman.m_best_header, continue
|
||||
// from there instead.
|
||||
if (all_duplicate && !nodestate->fSyncStarted) {
|
||||
// In this case two things are true:
|
||||
// 1) This node's most recent batch of headers only included ones we already had.
|
||||
// 2) We don't have this node marked as a peer to header-sync from.
|
||||
// This happens when some exogenous event, like an INV of a new block, causes us
|
||||
// to ask a peer for an unbounded number of headers, when we're already in the
|
||||
// process of downloading the headers from a different peer.
|
||||
// In this case the right thing to do is simply stop syncing headers from this
|
||||
// peer; it's redundant. Here we do nothing; since we don't ask the peer for
|
||||
// more headers, it will stop sending them.
|
||||
} else {
|
||||
if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(pindexLast), peer)) {
|
||||
LogPrint(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n",
|
||||
pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height);
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexLast), uint256()));
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (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) &&
|
||||
!IsBlockRequested(pindexWalk->GetBlockHash()) &&
|
||||
(!DeploymentActiveAt(*pindexWalk, m_chainman, Consensus::DEPLOYMENT_SEGWIT) || State(pfrom.GetId())->fHaveWitness)) {
|
||||
// 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;
|
||||
}
|
||||
uint32_t nFetchFlags = GetFetchFlags(pfrom);
|
||||
vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash()));
|
||||
BlockRequested(pfrom.GetId(), *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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -3193,8 +3288,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
}
|
||||
|
||||
if (best_block != nullptr) {
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *best_block));
|
||||
LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", m_chainman.m_best_header->nHeight, best_block->ToString(), pfrom.GetId());
|
||||
if (MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer)) {
|
||||
LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n",
|
||||
m_chainman.m_best_header->nHeight, best_block->ToString(),
|
||||
pfrom.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -3367,7 +3465,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
// others.
|
||||
if (m_chainman.ActiveTip() == nullptr ||
|
||||
(m_chainman.ActiveTip()->nChainWork < nMinimumChainWork && !pfrom.HasPermission(NetPermissionFlags::Download))) {
|
||||
LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work\n", pfrom.GetId());
|
||||
LogPrint(BCLog::NET, "Ignoring getheaders from peer=%d because active chain has too little work; sending empty response\n", pfrom.GetId());
|
||||
// Just respond with an empty headers message, to tell the peer to
|
||||
// go away but not treat us as unresponsive.
|
||||
m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::HEADERS, std::vector<CBlock>()));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3656,8 +3757,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
|
||||
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(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), uint256()));
|
||||
if (!m_chainman.ActiveChainstate().IsInitialBlockDownload()) {
|
||||
MaybeSendGetHeaders(pfrom, m_chainman.ActiveChain().GetLocator(m_chainman.m_best_header), *peer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3937,6 +4039,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
|
|||
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.store(NodeSeconds{});
|
||||
|
||||
std::vector<CBlockHeader> headers;
|
||||
|
||||
// Bypass the normal CBlock deserialization, as we don't want to risk deserializing 2000 full blocks.
|
||||
|
|
@ -4365,7 +4471,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);
|
||||
|
||||
|
|
@ -4403,10 +4509,15 @@ 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.
|
||||
MaybeSendGetHeaders(pto,
|
||||
m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev),
|
||||
peer);
|
||||
LogPrint(BCLog::NET, "sending getheaders to outbound peer=%d to verify chain work (current best known block:%s, benchmark blockhash: %s)\n", 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(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(state.m_chain_sync.m_work_header->pprev), uint256()));
|
||||
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
|
||||
|
|
@ -4817,15 +4928,6 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
|
|||
if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex && !got_enough_headers) {
|
||||
// Only actively request headers from a single peer, unless we're close to today.
|
||||
if ((nSyncStarted == 0 && sync_blocks_and_headers_from_peer) || m_chainman.m_best_header->GetBlockTime() > GetAdjustedTime() - 24 * 60 * 60) {
|
||||
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
|
||||
|
|
@ -4836,8 +4938,19 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
|
|||
got back an empty response. */
|
||||
if (pindexStart->pprev)
|
||||
pindexStart = pindexStart->pprev;
|
||||
LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", pindexStart->nHeight, pto->GetId(), peer->m_starting_height);
|
||||
m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::GETHEADERS, m_chainman.ActiveChain().GetLocator(pindexStart), uint256()));
|
||||
if (MaybeSendGetHeaders(*pto, m_chainman.ActiveChain().GetLocator(pindexStart), *peer)) {
|
||||
LogPrint(BCLog::NET, "initial getheaders (%d) to peer=%d (startheight:%d)\n", 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5185,7 +5298,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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue