Repository: bitcoin/bitcoin
File: src/net_processing.cpp (C++)

Code before:
    // processing.
    if (!already_validated_work && TryLowWorkHeadersSync(peer, pfrom,
                chain_start_header, headers)) {
        // If we successfully started a low-work headers sync, then there
        // should be no headers to process any further.
        Assume(headers.empty());
        return;
    }

    // At this point, we have a set of headers with sufficient work on them
    // which can be processed.

    // 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{last_received_header == nullptr};

    // Now process all the headers.
    BlockValidationState state;
    const bool processed{m_chainman.ProcessNewBlockHeaders(headers,
                                                           /*min_pow_checked=*/true,
                                                           state, &pindexLast)};
    if (!processed) {
        if (state.IsInvalid()) {
            MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received");
            return;
        }
    }
    assert(pindexLast);

    if (processed && received_new_header) {
        LogBlockHeader(*pindexLast, pfrom, /*via_compact_block=*/false);
    }

    // Consider fetching more headers if we are not using our headers-sync mechanism.
    if (nCount == m_opts.max_headers_result && !have_headers_sync) {
        // Headers message had its maximum size; the peer may have more headers.
        if (MaybeSendGetHeaders(pfrom, GetLocator(pindexLast), peer)) {
            LogDebug(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n",
                    pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height);
        }
    }

    UpdatePeerStateForReceivedHeaders(pfrom, peer, *pindexLast, received_new_header, nCount == m_opts.max_headers_result);

    // Consider immediately downloading blocks.
    HeadersDirectFetchBlocks(pfrom, peer, *pindexLast);

    return;
}

std::optional<node::PackageToValidate> PeerManagerImpl::ProcessInvalidTx(NodeId nodeid, const CTransactionRef& ptx, const TxValidationState& state,

Review comment (performance):
This doesn't combine well with `invalidateblock`: e.g. when invalidating the tip, the log just gets spammed with warnings as we cycle through new peers:

```
2025-10-15T14:45:32Z UpdateTip: new best=000000092ca51c152010fd76552e12d9c9c7f410ebd04012dcff49eeecb6cfff height=274019 version=0x20000000 log2_work=42.943635 tx=27881630 date='2025-10-15T14:27:13Z' progress=1.000000 cache=71.8MiB(503926txo)
...
2025-10-15T14:45:50Z UpdateTip: new best=0000000b9e7f0760c48d76ef301616dbaa1421788b29911c02f7beacbd20b867 height=274018 version=0x20000000 log2_work=42.943600 tx=27881585 date='2025-10-15T14:12:48Z' progress=0.999984 cache=71.8MiB(503258txo)
2025-10-15T14:45:50Z InvalidChainFound: invalid block=000000092ca51c152010fd76552e12d9c9c7f410ebd04012dcff49eeecb6cfff  height=274019  log2_work=42.943635  date=2025-10-15T14:27:13Z
2025-10-15T14:45:50Z InvalidChainFound:  current best=0000000b9e7f0760c48d76ef301616dbaa1421788b29911c02f7beacbd20b867  height=274018  log2_work=42.943600  date=2025-10-15T14:12:48Z
2025-10-15T14:46:01Z New outbound-full-relay v2 peer connected: version: 70016, blocks=274019, peer=8
2025-10-15T14:46:01Z [warning] Header received from peer=8 was previously marked as invalid. If this happens with all peers, database corruption is likely and -reindex may be necessary to recover.
2025-10-15T14:46:03Z New outbound-full-relay v2 peer connected: version: 70016, blocks=274019, peer=9
2025-10-15T14:46:03Z [warning] Header received from peer=9 was previously marked as invalid. If this happens with all peers, database corruption is likely and -reindex may be necessary to recover.
2025-10-15T14:46:03Z New outbound-full-relay v2 peer connected: version: 70016, blocks=274019, peer=10
2025-10-15T14:46:03Z [warning] Header received from peer=10 was previously marked as invalid. If this happens with all peers, database corruption is likely and -reindex may be necessary to recover.
```

It seems like the main purpose of this new log message is to help users understand endless pre-sync loops. In that case, maybe we can improve on this by making the warning specific to the pre-sync?

I've created a branch which implements that approach, see diff: https://github.com/mzumsande/bitcoin/compare/202510_headersync_loop..stickies-v:bitcoin:2025-10/log-presync-invalid-chain

_(note: I used `static std::atomic<int64_t> previous_height{height}` as a quick fix that works well enough, but adding a `ChainstateManager::m_last_presync_height` would be better)_

Code after:
    // processing.
    if (!already_validated_work && TryLowWorkHeadersSync(peer, pfrom,
                chain_start_header, headers)) {
        // If we successfully started a low-work headers sync, then there
        // should be no headers to process any further.
        Assume(headers.empty());
        return;
    }

    // At this point, we have a set of headers with sufficient work on them
    // which can be processed.

    // 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{last_received_header == nullptr};

    // Now process all the headers.
    BlockValidationState state;
    const bool processed{m_chainman.ProcessNewBlockHeaders(headers,
                                                           /*min_pow_checked=*/true,
                                                           state, &pindexLast)};
    if (!processed) {
        if (state.IsInvalid()) {
            if (!pfrom.IsInboundConn() && state.GetResult() == BlockValidationResult::BLOCK_CACHED_INVALID) {
                // Warn user if outgoing peers send us headers of blocks that we previously marked as invalid.
                LogWarning("%s (received from peer=%i). "
                           "If this happens with all peers, consider database corruption (that -reindex may fix) "
                           "or a potential consensus incompatibility.",
                           state.GetDebugMessage(), pfrom.GetId());
            }
            MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block, "invalid header received");
            return;
        }
    }
    assert(pindexLast);

    if (processed && received_new_header) {
        LogBlockHeader(*pindexLast, pfrom, /*via_compact_block=*/false);
    }

    // Consider fetching more headers if we are not using our headers-sync mechanism.
    if (nCount == m_opts.max_headers_result && !have_headers_sync) {
        // Headers message had its maximum size; the peer may have more headers.
        if (MaybeSendGetHeaders(pfrom, GetLocator(pindexLast), peer)) {
            LogDebug(BCLog::NET, "more getheaders (%d) to end to peer=%d (startheight:%d)\n",
                    pindexLast->nHeight, pfrom.GetId(), peer.m_starting_height);
        }
    }

    UpdatePeerStateForReceivedHeaders(pfrom, peer, *pindexLast, received_new_header, nCount == m_opts.max_headers_result);