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

Code before:

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

    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;
        }

        return TestBlockValidity(state, chainman().GetParams(), chainman().ActiveChainstate(), block, tip, /*fCheckPOW=*/false, check_merkle_root);
    }

    std::unique_ptr<BlockTemplate> createNewBlock(const CScript& script_pub_key, const BlockCreateOptions& options) override
    {
        BlockAssembler::Options assemble_options{options};
        ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
        return std::make_unique<BlockTemplateImpl>(BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(script_pub_key));
    }

    NodeContext* context() override { return &m_node; }
    ChainstateManager& chainman() { return *Assert(m_node.chainman); }
    NodeContext& m_node;
};
} // namespace
} // namespace node

namespace interfaces {
std::unique_ptr<Node> MakeNode(node::NodeContext& context) { return std::make_unique<node::NodeImpl>(context); }
std::unique_ptr<Chain> MakeChain(node::NodeContext& context) { return std::make_unique<node::ChainImpl>(context); }
std::unique_ptr<Mining> MakeMining(node::NodeContext& context) { return std::make_unique<node::MinerImpl>(context); }
} // namespace interfaces

Review comment (security):
In commit "Add waitTipChanged to Mining interface" (078fc60f198eb7a3bcccb557416fe1d4498f26e7)

One problem with switching from g_best_block_cv to m_tip_block_cv is that this m_tip_block_cv is not signaled when StopRPC is called, which could mean shutdown hangs for a second instead of being instantaneous. I believe this could be fixed in earlier commit 8400d49ffec47add2dc27267ae13ede5d2fabeea with:

```diff
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -282,7 +282,7 @@ void Shutdown(NodeContext& node)
 
     StopHTTPRPC();
     StopREST();
-    StopRPC();
+    StopRPC(&node);
     StopHTTPServer();
     for (const auto& client : node.chain_clients) {
         client->flush();
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -139,7 +139,7 @@ public:
         // Stop RPC for clean shutdown if any of waitfor* commands is executed.
         if (args().GetBoolArg("-server", false)) {
             InterruptRPC();
-            StopRPC();
+            StopRPC(m_context);
         }
     }
     bool shutdownRequested() override { return ShutdownRequested(*Assert(m_context)); };
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -11,6 +11,7 @@
 #include <common/system.h>
 #include <logging.h>
 #include <node/context.h>
+#include <node/kernel_notifications.h>
 #include <rpc/server_util.h>
 #include <rpc/util.h>
 #include <sync.h>
@@ -293,16 +294,17 @@ void InterruptRPC()
     });
 }
 
-void StopRPC()
+void StopRPC(const std::any& context)
 {
     static std::once_flag g_rpc_stop_flag;
     // This function could be called twice if the GUI has been started with -server=1.
     assert(!g_rpc_running);
-    std::call_once(g_rpc_stop_flag, []() {
+    std::call_once(g_rpc_stop_flag, [&]() {
         LogPrint(BCLog::RPC, "Stopping RPC\n");
         WITH_LOCK(g_deadline_timers_mutex, deadlineTimers.clear());
         DeleteAuthCookie();
         g_best_block_cv.notify_all();
+        Assert(EnsureAnyNodeContext(context).notifications)->m_tip_block_cv.notify_all();
         LogPrint(BCLog::RPC, "RPC stopped.\n");
     });
 }
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -9,6 +9,7 @@
 #include <rpc/request.h>
 #include <rpc/util.h>
 
+#include <any>
 #include <functional>
 #include <map>
 #include <stdint.h>
@@ -172,7 +173,7 @@ extern CRPCTable tableRPC;
 
 void StartRPC();
 void InterruptRPC();
-void StopRPC();
+void StopRPC(const std::any& context);
 UniValue JSONRPCExec(const JSONRPCRequest& jreq, bool catch_errors);
 
 #endif // BITCOIN_RPC_SERVER_H
```

Code after:
    {
        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);
        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;