Merge bitcoin/bitcoin#31283: Add waitNext() to BlockTemplate interface

cadbd4137d miner: have waitNext return after 20 min on testnet (Sjors Provoost)
d4020f502a Add waitNext() to BlockTemplate interface (Sjors Provoost)

Pull request description:

  This PR introduces `waitNext()`. It waits for either the tip to update or for fees at the top of the mempool to rise sufficiently. It then returns a new template, with which the caller can rinse and repeat.

  On testnet3 and testnet4 the difficulty drops after 20 minutes, so the second ensures that a new template is returned in that case.

  Alternative approach to #31003, suggested in https://github.com/bitcoin/bitcoin/issues/31109#issuecomment-2451942362

ACKs for top commit:
  ryanofsky:
    Code review ACK cadbd4137d. Main change since last review is adding back a missing `m_interrupt` check in the waitNext loop. Also made various code cleanups in both commits.
  ismaelsadeeq:
    Code review ACK cadbd4137d
  vasild:
    ACK cadbd4137d

Tree-SHA512: c5a40053723c1c1674449ba1e4675718229a2022c8b0a4853b12a2c9180beb87536a1f99fde969a0ef099bca9ac69ca14ea4f399d277d2db7f556465ce47de95
This commit is contained in:
Ryan Ofsky
2025-03-12 15:20:36 -04:00
8 changed files with 251 additions and 11 deletions

View File

@@ -99,6 +99,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

View File

@@ -180,8 +180,11 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout[0].nValue = 5000000000LL - 1000 - 50000 - feeToUse;
Txid hashLowFeeTx = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(feeToUse).FromTx(tx));
block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
// waitNext() should return nullptr because there is no better template
auto should_be_nullptr = block_template->waitNext({.timeout = MillisecondsDouble{0}, .fee_threshold = 1});
BOOST_REQUIRE(should_be_nullptr == nullptr);
block = block_template->getBlock();
// Verify that the free tx and the low fee tx didn't get selected
for (size_t i=0; i<block.vtx.size(); ++i) {
@@ -196,7 +199,9 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee
hashLowFeeTx = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(feeToUse + 2).FromTx(tx));
block_template = mining->createNewBlock(options);
// waitNext() should return if fees for the new template are at least 1 sat up
block_template = block_template->waitNext({.fee_threshold = 1});
BOOST_REQUIRE(block_template);
block = block_template->getBlock();
BOOST_REQUIRE_EQUAL(block.vtx.size(), 6U);
@@ -671,9 +676,15 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
for (const auto& bi : BLOCKINFO) {
const int current_height{mining->getTip()->height};
// Simple block creation, nothing special yet:
block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
/**
* Simple block creation, nothing special yet.
* If current_height is odd, block_template will have already been
* set at the end of the previous loop.
*/
if (current_height % 2 == 0) {
block_template = mining->createNewBlock(options);
BOOST_REQUIRE(block_template);
}
CBlock block{block_template->getBlock()};
CMutableTransaction txCoinbase(*block.vtx[0]);
@@ -709,8 +720,13 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
auto maybe_new_tip{Assert(m_node.chainman)->ActiveChain().Tip()};
BOOST_REQUIRE_EQUAL(maybe_new_tip->GetBlockHash(), block.GetHash());
}
// This just adds coverage
mining->waitTipChanged(block.hashPrevBlock);
if (current_height % 2 == 0) {
block_template = block_template->waitNext();
BOOST_REQUIRE(block_template);
} else {
// This just adds coverage
mining->waitTipChanged(block.hashPrevBlock);
}
}
LOCK(cs_main);

View File

@@ -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()

View File

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