From fd2282d82d98b5aebb691d4e032068413db8b929 Mon Sep 17 00:00:00 2001
From: Ryan Ofsky <ryan@ofsky.org>
Date: Fri, 6 Sep 2024 10:35:44 -0400
Subject: [PATCH] multiprocess: Add bitcoin-mine test program

See src/bitcoin-mine.cpp for usage information.

Co-authored-by: Sjors Provoost <sjors@sprovoost.nl>
---
 CMakeLists.txt                   |   1 +
 contrib/devtools/gen-manpages.py |   1 +
 src/CMakeLists.txt               |  11 +++
 src/bitcoin-mine.cpp             | 124 +++++++++++++++++++++++++++++++
 src/init/bitcoin-mine.cpp        |  29 ++++++++
 src/interfaces/init.h            |   3 +
 6 files changed, 169 insertions(+)
 create mode 100644 src/bitcoin-mine.cpp
 create mode 100644 src/init/bitcoin-mine.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f9467a5612d..3b0754b6ded 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -649,6 +649,7 @@ message("  bitcoin-util ........................ ${BUILD_UTIL}")
 message("  bitcoin-wallet ...................... ${BUILD_WALLET_TOOL}")
 message("  bitcoin-chainstate (experimental) ... ${BUILD_UTIL_CHAINSTATE}")
 message("  libbitcoinkernel (experimental) ..... ${BUILD_KERNEL_LIB}")
+message("  bitcoin-mine (experimental) ......... ${bitcoin_daemon_status}")
 message("Optional features:")
 message("  wallet support ...................... ${ENABLE_WALLET}")
 if(ENABLE_WALLET)
diff --git a/contrib/devtools/gen-manpages.py b/contrib/devtools/gen-manpages.py
index c7678817a99..0197a22f5e8 100755
--- a/contrib/devtools/gen-manpages.py
+++ b/contrib/devtools/gen-manpages.py
@@ -11,6 +11,7 @@ import argparse
 BINARIES = [
 'bin/bitcoind',
 'bin/bitcoin-cli',
+'bin/bitcoin-mine',
 'bin/bitcoin-tx',
 'bin/bitcoin-wallet',
 'bin/bitcoin-util',
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4fc14c1cffb..52f7babb9ea 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -353,6 +353,17 @@ if(WITH_MULTIPROCESS AND BUILD_DAEMON)
     $<TARGET_NAME_IF_EXISTS:bitcoin_wallet>
   )
   install_binary_component(bitcoin-node)
+
+  add_executable(bitcoin-mine
+    bitcoin-mine.cpp
+    init/bitcoin-mine.cpp
+  )
+  target_link_libraries(bitcoin-mine
+    core_interface
+    bitcoin_common
+    bitcoin_ipc
+  )
+  install_binary_component(bitcoin-mine)
 endif()
 
 if(WITH_MULTIPROCESS AND BUILD_TESTS)
diff --git a/src/bitcoin-mine.cpp b/src/bitcoin-mine.cpp
new file mode 100644
index 00000000000..b4007f04d55
--- /dev/null
+++ b/src/bitcoin-mine.cpp
@@ -0,0 +1,124 @@
+// Copyright (c) 2024 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <bitcoin-build-config.h> // IWYU pragma: keep
+
+#include <chainparams.h>
+#include <chainparamsbase.h>
+#include <clientversion.h>
+#include <common/args.h>
+#include <common/system.h>
+#include <compat/compat.h>
+#include <init/common.h>
+#include <interfaces/init.h>
+#include <interfaces/ipc.h>
+#include <logging.h>
+#include <tinyformat.h>
+#include <util/translation.h>
+
+static const char* const HELP_USAGE{R"(
+bitcoin-mine is a test program for interacting with bitcoin-node via IPC.
+
+Usage:
+  bitcoin-mine [options]
+)"};
+
+static const char* HELP_EXAMPLES{R"(
+Examples:
+  # Start separate bitcoin-node that bitcoin-mine can connect to.
+  bitcoin-node -regtest -ipcbind=unix
+
+  # Connect to bitcoin-node and print tip block hash.
+  bitcoin-mine -regtest
+
+  # Run with debug output.
+  bitcoin-mine -regtest -debug
+)"};
+
+const TranslateFn G_TRANSLATION_FUN{nullptr};
+
+static void AddArgs(ArgsManager& args)
+{
+    SetupHelpOptions(args);
+    SetupChainParamsBaseOptions(args);
+    args.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+    args.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+    args.AddArg("-ipcconnect=<address>", "Connect to bitcoin-node process in the background to perform online operations. Valid <address> values are 'unix' to connect to the default socket, 'unix:<socket path>' to connect to a socket at a nonstandard path. Default value: unix", ArgsManager::ALLOW_ANY, OptionsCategory::IPC);
+    init::AddLoggingArgs(args);
+}
+
+MAIN_FUNCTION
+{
+    ArgsManager& args = gArgs;
+    AddArgs(args);
+    std::string error_message;
+    if (!args.ParseParameters(argc, argv, error_message)) {
+        tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message);
+        return EXIT_FAILURE;
+    }
+    if (!args.ReadConfigFiles(error_message, true)) {
+        tfm::format(std::cerr, "Error reading config files: %s\n", error_message);
+        return EXIT_FAILURE;
+    }
+    if (HelpRequested(args) || args.IsArgSet("-version")) {
+        std::string output{strprintf("%s bitcoin-mine version", CLIENT_NAME) + " " + FormatFullVersion() + "\n"};
+        if (args.IsArgSet("-version")) {
+            output += FormatParagraph(LicenseInfo());
+        } else {
+            output += HELP_USAGE;
+            output += args.GetHelpMessage();
+            output += HELP_EXAMPLES;
+        }
+        tfm::format(std::cout, "%s", output);
+        return EXIT_SUCCESS;
+    }
+    if (!CheckDataDirOption(args)) {
+        tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", args.GetArg("-datadir", ""));
+        return EXIT_FAILURE;
+    }
+    SelectParams(args.GetChainType());
+
+    // Set logging options but override -printtoconsole default to depend on -debug rather than -daemon
+    init::SetLoggingOptions(args);
+    if (auto result{init::SetLoggingCategories(args)}; !result) {
+        tfm::format(std::cerr, "Error: %s\n", util::ErrorString(result).original);
+        return EXIT_FAILURE;
+    }
+    if (auto result{init::SetLoggingLevel(args)}; !result) {
+        tfm::format(std::cerr, "Error: %s\n", util::ErrorString(result).original);
+        return EXIT_FAILURE;
+    }
+    LogInstance().m_print_to_console = args.GetBoolArg("-printtoconsole", LogInstance().GetCategoryMask());
+    if (!init::StartLogging(args)) {
+        tfm::format(std::cerr, "Error: StartLogging failed\n");
+        return EXIT_FAILURE;
+    }
+
+    // Connect to existing bitcoin-node process or spawn new one.
+    std::unique_ptr<interfaces::Init> mine_init{interfaces::MakeMineInit(argc, argv)};
+    assert(mine_init);
+    std::unique_ptr<interfaces::Init> node_init;
+    try {
+        std::string address{args.GetArg("-ipcconnect", "unix")};
+        node_init = mine_init->ipc()->connectAddress(address);
+    } catch (const std::exception& exception) {
+        tfm::format(std::cerr, "Error: %s\n", exception.what());
+        tfm::format(std::cerr, "Probably bitcoin-node is not running or not listening on a unix socket. Can be started with:\n\n");
+        tfm::format(std::cerr, "    bitcoin-node -chain=%s -ipcbind=unix\n", args.GetChainTypeString());
+        return EXIT_FAILURE;
+    }
+    assert(node_init);
+    tfm::format(std::cout, "Connected to bitcoin-node\n");
+    std::unique_ptr<interfaces::Mining> mining{node_init->makeMining()};
+    assert(mining);
+
+    auto tip{mining->getTip()};
+    if (tip) {
+        tfm::format(std::cout, "Tip hash is %s.\n", tip->hash.ToString());
+    } else {
+        tfm::format(std::cout, "Tip hash is null.\n");
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/src/init/bitcoin-mine.cpp b/src/init/bitcoin-mine.cpp
new file mode 100644
index 00000000000..ad2d4a0f14a
--- /dev/null
+++ b/src/init/bitcoin-mine.cpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2024 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <interfaces/init.h>
+#include <interfaces/ipc.h>
+
+namespace init {
+namespace {
+const char* EXE_NAME = "bitcoin-mine";
+
+class BitcoinMineInit : public interfaces::Init
+{
+public:
+    BitcoinMineInit(const char* arg0) : m_ipc(interfaces::MakeIpc(EXE_NAME, arg0, *this))
+    {
+    }
+    interfaces::Ipc* ipc() override { return m_ipc.get(); }
+    std::unique_ptr<interfaces::Ipc> m_ipc;
+};
+} // namespace
+} // namespace init
+
+namespace interfaces {
+std::unique_ptr<Init> MakeMineInit(int argc, char* argv[])
+{
+    return std::make_unique<init::BitcoinMineInit>(argc > 0 ? argv[0] : "");
+}
+} // namespace interfaces
diff --git a/src/interfaces/init.h b/src/interfaces/init.h
index b496ada05f4..3c7525d7cda 100644
--- a/src/interfaces/init.h
+++ b/src/interfaces/init.h
@@ -53,6 +53,9 @@ std::unique_ptr<Init> MakeWalletInit(int argc, char* argv[], int& exit_status);
 
 //! Return implementation of Init interface for the gui process.
 std::unique_ptr<Init> MakeGuiInit(int argc, char* argv[]);
+
+//! Return implementation of Init interface for the bitcoin-mine process.
+std::unique_ptr<Init> MakeMineInit(int argc, char* argv[]);
 } // namespace interfaces
 
 #endif // BITCOIN_INTERFACES_INIT_H