From 22cf6cdb4190482b486752dae4e9dc3b44cfcb1e Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Fri, 31 Jan 2025 17:19:28 +0100 Subject: [PATCH] miner: have waitNext return after 20 min on testnet On testnet we need to create a min diff template after 20 min. --- src/interfaces/mining.h | 3 ++ src/node/interfaces.cpp | 12 ++++- src/test/CMakeLists.txt | 1 + src/test/testnet4_miner_tests.cpp | 75 +++++++++++++++++++++++++++++++ src/test/util/setup_common.h | 6 +++ 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 src/test/testnet4_miner_tests.cpp diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index b35a55fd28937..435151c5ddab0 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -64,6 +64,9 @@ class BlockTemplate * 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 waitNext(const node::BlockWaitOptions options = {}) = 0; }; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 4afe9225bd1df..756528e49ef7c 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -948,6 +948,7 @@ class BlockTemplateImpl : public BlockTemplate auto now{NodeClock::now()}; const auto deadline = now + options.timeout; const MillisecondsDouble tick{1000}; + const bool allow_min_difficulty{chainman().GetParams().GetConsensus().fPowAllowMinDifficultyBlocks}; while (now <= deadline) { bool tip_changed{false}; @@ -965,6 +966,14 @@ class BlockTemplateImpl : public BlockTemplate // 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 + std::chrono::seconds(20 * 60)) { + 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 @@ -1005,7 +1014,8 @@ class BlockTemplateImpl : public BlockTemplate * can call setmocktime to move the clock and exit this loop, a * unit tests would need to spawn a thread to achieve this. * Making the while loop use now < deadline won't work either, because - * then there's no wait to test its internals. + * then there's no wait to test its internals, e.g. the 20 minute + * testnet exception. */ if (now == deadline) break; now = NodeClock::now(); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 859b913206782..b92dad1e989f4 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -120,6 +120,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 0000000000000..54ca8e32cfeec --- /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 +#include +#include +#include +#include + +#include + +#include + +using interfaces::BlockTemplate; +using interfaces::Mining; +using node::BlockAssembler; +using node::BlockWaitOptions; + +namespace testnet4_miner_tests { + +struct Testnet4MinerTestingSetup : public Testnet4Setup { + std::unique_ptr 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 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 33ad258457353..57bea9086b99a 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;