diff --git a/src/net_processing.cpp b/src/net_processing.cpp --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1392,6 +1392,219 @@ msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } +static bool ProcessHeadersMessage(const Config &config, CNode *pfrom, + CConnman *connman, + const std::vector &headers, + const CChainParams &chainparams) { + const CNetMsgMaker msgMaker(pfrom->GetSendVersion()); + size_t nCount = headers.size(); + + if (nCount == 0) { + // Nothing interesting. Stop asking this peers for more headers. + return true; + } + + 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 (mapBlockIndex.find(headers[0].hashPrevBlock) == + mapBlockIndex.end() && + nCount < MAX_BLOCKS_TO_ANNOUNCE) { + nodestate->nUnconnectingHeaders++; + connman->PushMessage( + pfrom, + msgMaker.Make(NetMsgType::GETHEADERS, + chainActive.GetLocator(pindexBestHeader), + 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(), + pindexBestHeader->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()); + + if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS == + 0) { + // The peer is sending us many headers we can't connect. + Misbehaving(pfrom, 20, "too-many-unconnected-headers"); + } + return true; + } + + uint256 hashLastBlock; + for (const CBlockHeader &header : headers) { + if (!hashLastBlock.IsNull() && + header.hashPrevBlock != hashLastBlock) { + Misbehaving(pfrom, 20, "disconnected-header"); + return error("non-continuous headers sequence"); + } + hashLastBlock = header.GetHash(); + } + } + + CValidationState state; + if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast)) { + int nDoS; + if (state.IsInvalid(nDoS)) { + if (nDoS > 0) { + LOCK(cs_main); + Misbehaving(pfrom, nDoS, state.GetRejectReason()); + } + return error("invalid header received"); + } + } + + { + 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 (nCount == MAX_HEADERS_RESULTS) { + // Headers message had its maximum size; the peer may have more + // headers. + // TODO: optimize: if pindexLast is an ancestor of chainActive.Tip + // or pindexBestHeader, continue from there instead. + LogPrint( + BCLog::NET, + "more getheaders (%d) to end to peer=%d (startheight:%d)\n", + pindexLast->nHeight, pfrom->GetId(), pfrom->nStartingHeight); + connman->PushMessage( + pfrom, + msgMaker.Make(NetMsgType::GETHEADERS, + chainActive.GetLocator(pindexLast), uint256())); + } + + bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus()); + // 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(BlockValidity::TREE) && + chainActive.Tip()->nChainWork <= pindexLast->nChainWork) { + std::vector vToFetch; + const CBlockIndex *pindexWalk = pindexLast; + // Calculate all the blocks we'd need to switch to pindexLast, up to + // a limit. + while (pindexWalk && !chainActive.Contains(pindexWalk) && + vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { + if (!pindexWalk->nStatus.hasData() && + !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 (!chainActive.Contains(pindexWalk)) { + LogPrint( + BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n", + pindexLast->GetBlockHash().ToString(), pindexLast->nHeight); + } else { + std::vector vGetData; + // Download as much as possible, from earliest to latest. + for (const CBlockIndex *pindex : + boost::adaptors::reverse(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(config, pfrom->GetId(), + pindex->GetBlockHash(), + chainparams.GetConsensus(), 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 (nodestate->fSupportsDesiredCmpctVersion && + vGetData.size() == 1 && mapBlocksInFlight.size() == 1 && + pindexLast->pprev->IsValid(BlockValidity::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); + } + 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 (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 using an outbound slot (unless + // whitelisted or addnode). + // Note: We compare their tip to nMinimumChainWork (rather than + // chainActive.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 (IsOutboundDisconnectionCandidate(pfrom)) { + LogPrintf("Disconnecting outbound peer %d -- headers " + "chain has insufficient work\n", + pfrom->GetId()); + pfrom->fDisconnect = true; + } + } + } + if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && + nodestate->pindexBestKnownBlock != nullptr) { + // If this is an outbound peer, check to see if we should protect it + // from the bad/lagging chain logic. + if (g_outbound_peers_with_protect_from_disconnect < + MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && + nodestate->pindexBestKnownBlock->nChainWork >= + chainActive.Tip()->nChainWork && + !nodestate->m_chain_sync.m_protect) { + nodestate->m_chain_sync.m_protect = true; + ++g_outbound_peers_with_protect_from_disconnect; + } + } + } + + return true; +} + static bool ProcessMessage(const Config &config, CNode *pfrom, const std::string &strCommand, CDataStream &vRecv, int64_t nTimeReceived, @@ -2610,225 +2823,8 @@ ReadCompactSize(vRecv); } - if (nCount == 0) { - // Nothing interesting. Stop asking this peers for more headers. - return true; - } - - 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 (mapBlockIndex.find(headers[0].hashPrevBlock) == - mapBlockIndex.end() && - nCount < MAX_BLOCKS_TO_ANNOUNCE) { - nodestate->nUnconnectingHeaders++; - connman->PushMessage( - pfrom, - msgMaker.Make(NetMsgType::GETHEADERS, - chainActive.GetLocator(pindexBestHeader), - 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(), - pindexBestHeader->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()); - - if (nodestate->nUnconnectingHeaders % - MAX_UNCONNECTING_HEADERS == - 0) { - // The peer is sending us many headers we can't connect. - Misbehaving(pfrom, 20, "too-many-unconnected-headers"); - } - return true; - } - - uint256 hashLastBlock; - for (const CBlockHeader &header : headers) { - if (!hashLastBlock.IsNull() && - header.hashPrevBlock != hashLastBlock) { - Misbehaving(pfrom, 20, "disconnected-header"); - return error("non-continuous headers sequence"); - } - hashLastBlock = header.GetHash(); - } - } - - CValidationState state; - if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast)) { - int nDoS; - if (state.IsInvalid(nDoS)) { - if (nDoS > 0) { - LOCK(cs_main); - Misbehaving(pfrom, nDoS, state.GetRejectReason()); - } - return error("invalid header received"); - } - } - - { - 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 (nCount == MAX_HEADERS_RESULTS) { - // Headers message had its maximum size; the peer may have more - // headers. - // TODO: optimize: if pindexLast is an ancestor of - // chainActive.Tip or pindexBestHeader, continue from there - // instead. - LogPrint( - BCLog::NET, - "more getheaders (%d) to end to peer=%d (startheight:%d)\n", - pindexLast->nHeight, pfrom->GetId(), - pfrom->nStartingHeight); - connman->PushMessage( - pfrom, - msgMaker.Make(NetMsgType::GETHEADERS, - chainActive.GetLocator(pindexLast), - uint256())); - } - - bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus()); - // 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(BlockValidity::TREE) && - chainActive.Tip()->nChainWork <= pindexLast->nChainWork) { - std::vector vToFetch; - const CBlockIndex *pindexWalk = pindexLast; - // Calculate all the blocks we'd need to switch to pindexLast, - // up to a limit. - while (pindexWalk && !chainActive.Contains(pindexWalk) && - vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { - if (!pindexWalk->nStatus.hasData() && - !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 (!chainActive.Contains(pindexWalk)) { - LogPrint(BCLog::NET, - "Large reorg, won't direct fetch to %s (%d)\n", - pindexLast->GetBlockHash().ToString(), - pindexLast->nHeight); - } else { - std::vector vGetData; - // Download as much as possible, from earliest to latest. - for (const CBlockIndex *pindex : - boost::adaptors::reverse(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(config, pfrom->GetId(), - pindex->GetBlockHash(), - chainparams.GetConsensus(), 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 (nodestate->fSupportsDesiredCmpctVersion && - vGetData.size() == 1 && - mapBlocksInFlight.size() == 1 && - pindexLast->pprev->IsValid(BlockValidity::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); - } - 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 (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 using an outbound slot - // (unless whitelisted or addnode). - // Note: We compare their tip to nMinimumChainWork (rather - // than chainActive.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 (IsOutboundDisconnectionCandidate(pfrom)) { - LogPrintf("Disconnecting outbound peer %d -- headers " - "chain has insufficient work\n", - pfrom->GetId()); - pfrom->fDisconnect = true; - } - } - } - if (!pfrom->fDisconnect && - IsOutboundDisconnectionCandidate(pfrom) && - nodestate->pindexBestKnownBlock != nullptr) { - // If this is an outbound peer, check to see if we should - // protect it from the bad/lagging chain logic. - if (g_outbound_peers_with_protect_from_disconnect < - MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && - nodestate->pindexBestKnownBlock->nChainWork >= - chainActive.Tip()->nChainWork && - !nodestate->m_chain_sync.m_protect) { - nodestate->m_chain_sync.m_protect = true; - ++g_outbound_peers_with_protect_from_disconnect; - } - } - } + return ProcessHeadersMessage(config, pfrom, connman, headers, + chainparams); } else if (strCommand == NetMsgType::BLOCK && !fImporting && !fReindex) {