Skip to content

Commit

Permalink
miner: have waitNext return after 20 min on testnet
Browse files Browse the repository at this point in the history
On testnet we need to create a min diff template after 20 min.
  • Loading branch information
Sjors committed Jan 31, 2025
1 parent ae787b1 commit 22cf6cd
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/interfaces/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlockTemplate> waitNext(const node::BlockWaitOptions options = {}) = 0;
};
Expand Down
12 changes: 11 additions & 1 deletion src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions src/test/testnet4_miner_tests.cpp
Original file line number Diff line number Diff line change
@@ -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()
6 changes: 6 additions & 0 deletions src/test/util/setup_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 22cf6cd

Please sign in to comment.