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

Code before:
    {
        return chainman().GetParams().IsTestChain();
    }

    bool isInitialBlockDownload() override
    {
        return chainman().IsInitialBlockDownload();
    }

    std::optional<BlockRef> getTip() override
    {
        LOCK(::cs_main);
        CBlockIndex* tip{chainman().ActiveChain().Tip()};
        if (!tip) return {};
        return BlockRef{tip->GetBlockHash(), tip->nHeight};
    }

    BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
    {
        // Interrupt check interval
        const MillisecondsDouble tick{1000};
        auto now{std::chrono::steady_clock::now()};
        auto deadline = now + timeout;
        // std::chrono does not check against overflow
        if (deadline < now) deadline = std::chrono::steady_clock::time_point::max();
        {
            WAIT_LOCK(notifications().m_tip_block_mutex, lock);
            while ((notifications().m_tip_block == uint256() || notifications().m_tip_block == current_tip) && !chainman().m_interrupt) {
                now = std::chrono::steady_clock::now();
                if (now >= deadline) break;
                notifications().m_tip_block_cv.wait_until(lock, std::min(deadline, now + tick));
            }
        }
        // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
        LOCK(::cs_main);
        return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};
    }

    bool processNewBlock(const std::shared_ptr<const CBlock>& block, bool* new_block) override
    {
        return chainman().ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/new_block);
    }

    unsigned int getTransactionsUpdated() override
    {
        return context()->mempool->GetTransactionsUpdated();
    }

    bool testBlockValidity(const CBlock& block, bool check_merkle_root, BlockValidationState& state) override
    {
        LOCK(cs_main);

Review comment (suggestion):
444424b96deef72780f617d1dbab844a2c9d72ab  I think that since `waitTipChanged` takes a `current_tip` argument the `m_tip_block != uint256::ZERO` check is no longer needed. Though it seems fine to keep it.

IIRC the reason I added it was in case `waitTipChanged` was called during early node initialization, before `m_tip_block` is set to the genesis block.

That scenario is now handled by `m_tip_block != current_tip`.

In terms of how the Template Provider works, it used to rely on this check, but now it just waits for `getTip()` to return a value before calling `waitTipChanged()`. .

When implemented as part of Bitcoin Core, as in #29432, it can't call `waitTipChanged()` earlier, because it has no way of knowing what to set for `current_tip`.

When implemented as an external application (e.g. https://github.com/Sjors/bitcoin/pull/48) it might know the tip from some other source before `m_tip_block` is set. So it could call `waitTipChanged()` very early in node initialization, but that still doesn't require the `m_tip_block != uint256::ZERO` check.

Code after:

    bool isTestChain() override
    {
        return chainman().GetParams().IsTestChain();
    }

    bool isInitialBlockDownload() override
    {
        return chainman().IsInitialBlockDownload();
    }

    std::optional<BlockRef> getTip() override
    {
        LOCK(::cs_main);
        CBlockIndex* tip{chainman().ActiveChain().Tip()};
        if (!tip) return {};
        return BlockRef{tip->GetBlockHash(), tip->nHeight};
    }

    BlockRef waitTipChanged(uint256 current_tip, MillisecondsDouble timeout) override
    {
        if (timeout > std::chrono::years{100}) timeout = std::chrono::years{100}; // Upper bound to avoid UB in std::chrono
        {
            WAIT_LOCK(notifications().m_tip_block_mutex, lock);
            notifications().m_tip_block_cv.wait_for(lock, timeout, [&]() EXCLUSIVE_LOCKS_REQUIRED(notifications().m_tip_block_mutex) {
                return (notifications().m_tip_block != current_tip && notifications().m_tip_block != uint256::ZERO) || chainman().m_interrupt;
            });
        }
        // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks.
        LOCK(::cs_main);
        return BlockRef{chainman().ActiveChain().Tip()->GetBlockHash(), chainman().ActiveChain().Tip()->nHeight};
    }

    bool processNewBlock(const std::shared_ptr<const CBlock>& block, bool* new_block) override
    {
        return chainman().ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/new_block);
    }

    unsigned int getTransactionsUpdated() override
    {
        return context()->mempool->GetTransactionsUpdated();
    }

    bool testBlockValidity(const CBlock& block, bool check_merkle_root, BlockValidationState& state) override
    {
        LOCK(cs_main);
        CBlockIndex* tip{chainman().ActiveChain().Tip()};
        // Fail if the tip updated before the lock was taken
        if (block.hashPrevBlock != tip->GetBlockHash()) {
            state.Error("Block does not connect to current chain tip.");
            return false;