diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index b35a55fd289..435151c5dda 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -64,6 +64,9 @@ public: * for the next block should rise (default infinite). * * @returns a new BlockTemplate or nothing if the timeout occurs. + * + * On testnet this will additionally return a template with difficulty 1 if + * the tip is more than 20 minutes old. */ virtual std::unique_ptr<BlockTemplate> waitNext(const node::BlockWaitOptions options = {}) = 0; }; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 3fd7fc1bf27..50207c658d8 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -958,6 +958,7 @@ public: auto now{NodeClock::now()}; const auto deadline = now + options.timeout; const MillisecondsDouble tick{1000}; + const bool allow_min_difficulty{chainman().GetParams().GetConsensus().fPowAllowMinDifficultyBlocks}; do { bool tip_changed{false}; @@ -982,6 +983,14 @@ public: // Must release m_tip_block_mutex before locking cs_main, to avoid deadlocks. LOCK(::cs_main); + // On test networks return a minimum difficulty block after 20 minutes + if (!tip_changed && allow_min_difficulty) { + const NodeClock::time_point tip_time{std::chrono::seconds{chainman().ActiveChain().Tip()->GetBlockTime()}}; + if (now > tip_time + 20min) { + tip_changed = true; + } + } + /** * We determine if fees increased compared to the previous template by generating * a fresh template. There may be more efficient ways to determine how much diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index b0dd27894d3..6e245aefb34 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -121,6 +121,7 @@ add_executable(test_bitcoin streams_tests.cpp sync_tests.cpp system_tests.cpp + testnet4_miner_tests.cpp timeoffsets_tests.cpp torcontrol_tests.cpp transaction_tests.cpp diff --git a/src/test/testnet4_miner_tests.cpp b/src/test/testnet4_miner_tests.cpp new file mode 100644 index 00000000000..54ca8e32cfe --- /dev/null +++ b/src/test/testnet4_miner_tests.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2025 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 <common/system.h> +#include <interfaces/mining.h> +#include <node/miner.h> +#include <util/time.h> +#include <validation.h> + +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> + +using interfaces::BlockTemplate; +using interfaces::Mining; +using node::BlockAssembler; +using node::BlockWaitOptions; + +namespace testnet4_miner_tests { + +struct Testnet4MinerTestingSetup : public Testnet4Setup { + std::unique_ptr<Mining> MakeMining() + { + return interfaces::MakeMining(m_node); + } +}; +} // namespace testnet4_miner_tests + +BOOST_FIXTURE_TEST_SUITE(testnet4_miner_tests, Testnet4MinerTestingSetup) + +BOOST_AUTO_TEST_CASE(MiningInterface) +{ + auto mining{MakeMining()}; + BOOST_REQUIRE(mining); + + BlockAssembler::Options options; + std::unique_ptr<BlockTemplate> block_template; + + // Set node time a few minutes past the testnet4 genesis block + const int64_t genesis_time{WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->GetBlockTime())}; + SetMockTime(genesis_time + 3 * 60); + + block_template = mining->createNewBlock(options); + BOOST_REQUIRE(block_template); + + // The template should use the mocked system time + BOOST_REQUIRE_EQUAL(block_template->getBlockHeader().nTime, genesis_time + 3 * 60); + + const BlockWaitOptions wait_options{.timeout = MillisecondsDouble{0}, .fee_threshold = 1}; + + // waitNext() should return nullptr because there is no better template + auto should_be_nullptr = block_template->waitNext(wait_options); + BOOST_REQUIRE(should_be_nullptr == nullptr); + + // This remains the case when exactly 20 minutes have gone by + { + LOCK(cs_main); + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetBlockTime() + 20 * 60); + } + should_be_nullptr = block_template->waitNext(wait_options); + BOOST_REQUIRE(should_be_nullptr == nullptr); + + // One second later the difficulty drops and it returns a new template + // Note that we can't test the actual difficulty change, because the + // difficulty is already at 1. + { + LOCK(cs_main); + SetMockTime(m_node.chainman->ActiveChain().Tip()->GetBlockTime() + 20 * 60 + 1); + } + block_template = block_template->waitNext(wait_options); + BOOST_REQUIRE(block_template); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h index 33ad2584573..57bea9086b9 100644 --- a/src/test/util/setup_common.h +++ b/src/test/util/setup_common.h @@ -130,6 +130,12 @@ struct RegTestingSetup : public TestingSetup { : TestingSetup{ChainType::REGTEST} {} }; +/** Identical to TestingSetup, but chain set to testnet4 */ +struct Testnet4Setup : public TestingSetup { + Testnet4Setup() + : TestingSetup{ChainType::TESTNET4} {} +}; + class CBlock; struct CMutableTransaction; class CScript;