Merge bitcoin/bitcoin#34858: test: Use NodeClockContext in more tests

faad08e59c test: Use NodeClockContext in more tests (MarcoFalke)
fa8fe0941e fuzz: Use NodeClockContext (MarcoFalke)
fa9f434df8 test: Allow time_point in boost checks (MarcoFalke)

Pull request description:

  Currently mocktime is written to a global, which may leak between sub-tests (albeit some tests try to reset the mocktime on a best-effort basis). Also, when advancing it, one has to keep a counter variable around.

  Fix both issues by using the recently added `NodeClockContext`, which resets the mocktime once it goes out of scope. Also, it has a method to advance the mocktime by a delta.

ACKs for top commit:
  achow101:
    ACK faad08e59c
  seduless:
    Tested ACK faad08e59c
  frankomosh:
    Tested ACK faad08e59c. Ran all relevant tests, all clean. Also verified that the default-constructor call sites in orphanage_tests and addrman_tests behave identically to the explicit `{Now<NodeSeconds>()}` form.
  ryanofsky:
    Code review ACK faad08e59c but had a question about dropping +1 in one test below.

Tree-SHA512: bd56931970eed02bfcf3f3593ef64a61a8a1d8cc8adf190d6903b35df0fd7e6d865678c7d5bd23ce53d074cb2cf53a0a19212fdeb593b047dac5561859bc86b0
This commit is contained in:
Ava Chow
2026-04-09 14:33:30 -07:00
13 changed files with 66 additions and 57 deletions

View File

@@ -3,7 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <bench/bench.h>
#include <test/util/time.h>
#include <util/time.h>
static void BenchTimeDeprecated(benchmark::Bench& bench)
@@ -15,11 +15,10 @@ static void BenchTimeDeprecated(benchmark::Bench& bench)
static void BenchTimeMock(benchmark::Bench& bench)
{
SetMockTime(111);
NodeClockContext clock_ctx{111s};
bench.run([&] {
(void)GetTime<std::chrono::seconds>();
});
SetMockTime(0);
}
static void BenchTimeMillis(benchmark::Bench& bench)

View File

@@ -10,6 +10,7 @@
#include <sync.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <uint256.h>
#include <util/time.h>
#include <validation.h>
@@ -32,7 +33,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
// Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
// The reason is 'generatetoaddress', which creates a chain with deterministic timestamps in the past.
SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime);
NodeClockContext clock_ctx{test_setup->m_node.chainman->GetParams().GenesisBlock().Time()};
CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()};
{
LOCK(wallet.cs_wallet);

View File

@@ -20,6 +20,7 @@
#include <script/script.h>
#include <sync.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <uint256.h>
#include <util/result.h>
#include <util/time.h>
@@ -117,7 +118,7 @@ static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type
const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
// Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime);
NodeClockContext clock_ctx{test_setup->m_node.chainman->GetParams().GenesisBlock().Time()};
CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()};
{
LOCK(wallet.cs_wallet);
@@ -172,7 +173,7 @@ static void AvailableCoins(benchmark::Bench& bench, const std::vector<OutputType
{
const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
// Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime);
NodeClockContext clock_ctx{test_setup->m_node.chainman->GetParams().GenesisBlock().Time()};
CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()};
{
LOCK(wallet.cs_wallet);

View File

@@ -12,6 +12,7 @@
#include <random.h>
#include <test/data/asmap.raw.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/asmap.h>
#include <util/string.h>
@@ -1030,7 +1031,8 @@ BOOST_AUTO_TEST_CASE(addrman_evictionworks)
BOOST_CHECK_EQUAL(addrman->SelectTriedCollision().first.ToStringAddrPort(), "250.1.1.36:0");
// Eviction is also successful if too much time has passed since last try
SetMockTime(GetTime() + 4 * 60 *60);
NodeClockContext clock_ctx{};
clock_ctx += 4h;
addrman->ResolveCollisions();
BOOST_CHECK(addrman->SelectTriedCollision().first.ToStringAddrPort() == "[::]:0");
//Now 19 is in tried again, and 36 back to new

View File

@@ -8,16 +8,16 @@
#include <streams.h>
#include <test/util/logging.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/readwritefile.h>
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(banman_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(file)
{
SetMockTime(777s);
NodeClockContext clock_ctx{777s};
const fs::path banlist_path{m_args.GetDataDirBase() / "banlist_test"};
{
const std::string entries_write{

View File

@@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <validation.h>
#include <validationinterface.h>
@@ -30,6 +31,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup)
m_node.validation_signals->RegisterSharedValidationInterface(sub);
auto& chainstate{Assert(m_node.chainman)->ActiveChainstate()};
BlockValidationState state_dummy{};
NodeClockContext clock_ctx{};
// The first periodic flush sets m_next_write and does not flush
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
@@ -37,12 +39,12 @@ BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup)
BOOST_CHECK(!sub->m_did_flush);
// The periodic flush interval is between 50 and 70 minutes (inclusive)
SetMockTime(GetTime<std::chrono::minutes>() + DATABASE_WRITE_INTERVAL_MIN - 1min);
clock_ctx += DATABASE_WRITE_INTERVAL_MIN - 1min;
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(!sub->m_did_flush);
SetMockTime(GetTime<std::chrono::minutes>() + DATABASE_WRITE_INTERVAL_MAX);
clock_ctx += DATABASE_WRITE_INTERVAL_MAX;
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
m_node.validation_signals->SyncWithValidationInterfaceQueue();
BOOST_CHECK(sub->m_did_flush);

View File

@@ -16,6 +16,7 @@
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/string.h>
#include <util/time.h>
#include <validation.h>
@@ -90,17 +91,17 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
}
connman.FlushSendBuffer(dummyNode1);
int64_t nStartTime = GetTime();
// Wait 21 minutes
SetMockTime(nStartTime+21*60);
NodeClockContext clock_ctx{};
clock_ctx += 21min;
BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in getheaders
{
LOCK(dummyNode1.cs_vSend);
const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend(false);
BOOST_CHECK(!to_send.empty());
}
// Wait 3 more minutes
SetMockTime(nStartTime+24*60);
clock_ctx += 3min;
BOOST_CHECK(peerman.SendMessages(dummyNode1)); // should result in disconnect
BOOST_CHECK(dummyNode1.fDisconnect == true);
@@ -151,9 +152,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
CConnman::Options options;
options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS;
const auto time_init{GetTime<std::chrono::seconds>()};
SetMockTime(time_init);
const auto time_later{time_init + 3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s};
const auto time_init{Now<NodeSeconds>()};
NodeClockContext clock_ctx{time_init};
const auto delta{3 * std::chrono::seconds{m_node.chainman->GetConsensus().nPowTargetSpacing} + 1s};
connman->Init(options);
std::vector<CNode *> vNodes;
@@ -169,7 +170,7 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
BOOST_CHECK(node->fDisconnect == false);
}
SetMockTime(time_later);
clock_ctx += delta;
// Now tip should definitely be stale, and we should look for an extra
// outbound peer
@@ -184,9 +185,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
// If we add one more peer, something should get marked for eviction
// on the next check (since we're mocking the time to be in the future, the
// required time connected check should be satisfied).
SetMockTime(time_init);
clock_ctx.set(time_init);
AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY);
SetMockTime(time_later);
clock_ctx += delta;
peerLogic->CheckForStaleTipAndEvictPeers();
for (int i = 0; i < max_outbound_full_relay; ++i) {
@@ -212,9 +213,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
// Add an onion peer, that will be protected because it is the only one for
// its network, so another peer gets disconnected instead.
SetMockTime(time_init);
clock_ctx.set(time_init);
AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY, /*onion_peer=*/true);
SetMockTime(time_later);
clock_ctx += delta;
peerLogic->CheckForStaleTipAndEvictPeers();
for (int i = 0; i < max_outbound_full_relay - 2; ++i) {
@@ -225,9 +226,9 @@ BOOST_FIXTURE_TEST_CASE(stale_tip_peer_management, OutboundTest)
BOOST_CHECK(vNodes[max_outbound_full_relay]->fDisconnect == false);
// Add a second onion peer which won't be protected
SetMockTime(time_init);
clock_ctx.set(time_init);
AddRandomOutboundPeer(id, vNodes, *peerLogic, *connman, ConnectionType::OUTBOUND_FULL_RELAY, /*onion_peer=*/true);
SetMockTime(time_later);
clock_ctx += delta;
peerLogic->CheckForStaleTipAndEvictPeers();
BOOST_CHECK(vNodes.back()->fDisconnect == true);
@@ -246,7 +247,7 @@ BOOST_FIXTURE_TEST_CASE(block_relay_only_eviction, OutboundTest)
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, nullptr, *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
constexpr int max_outbound_block_relay{MAX_BLOCK_RELAY_ONLY_CONNECTIONS};
constexpr int64_t MINIMUM_CONNECT_TIME{30};
constexpr auto MINIMUM_CONNECT_TIME{30s};
CConnman::Options options;
options.m_max_automatic_connections = DEFAULT_MAX_PEER_CONNECTIONS;
@@ -273,7 +274,8 @@ BOOST_FIXTURE_TEST_CASE(block_relay_only_eviction, OutboundTest)
}
BOOST_CHECK(vNodes.back()->fDisconnect == false);
SetMockTime(GetTime() + MINIMUM_CONNECT_TIME + 1);
NodeClockContext clock_ctx{};
clock_ctx += MINIMUM_CONNECT_TIME;
peerLogic->CheckForStaleTipAndEvictPeers();
for (int i = 0; i < max_outbound_block_relay; ++i) {
BOOST_CHECK(vNodes[i]->fDisconnect == false);
@@ -411,8 +413,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime)
auto peerLogic = PeerManager::make(*connman, *m_node.addrman, banman.get(), *m_node.chainman, *m_node.mempool, *m_node.warnings, {});
banman->ClearBanned();
int64_t nStartTime = GetTime();
SetMockTime(nStartTime); // Overrides future calls to GetTime()
const NodeClockContext clock_ctx{}; // keep mocktime constant
CAddress addr(ip(0xa0b0c001), NODE_NONE);
NodeId id{0};

View File

@@ -4,6 +4,7 @@
#include <common/system.h>
#include <policy/policy.h>
#include <test/util/time.h>
#include <test/util/txmempool.h>
#include <txmempool.h>
#include <util/time.h>
@@ -251,28 +252,29 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
TryAddToMempool(pool, entry.Fee(900LL).FromTx(tx7));
std::vector<CTransactionRef> vtx;
SetMockTime(42);
SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE);
NodeClockContext clock_ctx{42s};
constexpr std::chrono::seconds HALFLIFE{CTxMemPool::ROLLING_FEE_HALFLIFE};
clock_ctx += HALFLIFE;
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE);
// ... we should keep the same min fee until we get a block
pool.removeForBlock(vtx, 1);
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE);
clock_ctx += HALFLIFE;
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/2.0));
// ... then feerate should drop 1/2 each halflife
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2);
clock_ctx += HALFLIFE / 2;
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/4.0));
// ... with a 1/2 halflife when mempool is < 1/2 its target size
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
clock_ctx += HALFLIFE / 4;
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/8.0));
// ... with a 1/4 halflife when mempool is < 1/4 its target size
SetMockTime(42 + 7*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
clock_ctx += 5 * HALFLIFE;
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), DEFAULT_INCREMENTAL_RELAY_FEE);
// ... but feerate should never drop below DEFAULT_INCREMENTAL_RELAY_FEE
SetMockTime(42 + 8*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
clock_ctx += HALFLIFE;
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 0);
// ... unless it has gone all the way to 0 (after getting past DEFAULT_INCREMENTAL_RELAY_FEE/2)
}

View File

@@ -13,6 +13,7 @@
#include <test/util/common.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <test/util/transaction_utils.h>
#include <array>
@@ -431,9 +432,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
FillableSigningProvider keystore;
BOOST_CHECK(keystore.AddKey(key));
// Freeze time for length of test
auto now{GetTime<std::chrono::seconds>()};
SetMockTime(now);
NodeClockContext clock_ctx{};
std::vector<CTransactionRef> orphans_added;

View File

@@ -5,11 +5,12 @@
#include <common/system.h>
#include <interfaces/mining.h>
#include <node/miner.h>
#include <test/util/common.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <util/time.h>
#include <validation.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
using interfaces::BlockTemplate;
@@ -39,14 +40,14 @@ BOOST_AUTO_TEST_CASE(MiningInterface)
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);
const auto template_time{3min + WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->Time())};
NodeClockContext clock_ctx{template_time};
block_template = mining->createNewBlock(options, /*cooldown=*/false);
BOOST_REQUIRE(block_template);
// The template should use the mocked system time
BOOST_REQUIRE_EQUAL(block_template->getBlockHeader().nTime, genesis_time + 3 * 60);
BOOST_REQUIRE_EQUAL(block_template->getBlockHeader().Time(), template_time);
const BlockWaitOptions wait_options{.timeout = MillisecondsDouble{0}, .fee_threshold = 1};
@@ -55,20 +56,14 @@ BOOST_AUTO_TEST_CASE(MiningInterface)
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);
}
clock_ctx += 17min;
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);
}
clock_ctx += 1s;
block_template = block_template->waitNext(wait_options);
BOOST_REQUIRE(block_template);
}

View File

@@ -5,8 +5,9 @@
#ifndef BITCOIN_TEST_UTIL_COMMON_H
#define BITCOIN_TEST_UTIL_COMMON_H
#include <ostream>
#include <chrono>
#include <optional>
#include <ostream>
#include <string>
/**
@@ -27,6 +28,12 @@ private:
// Make types usable in BOOST_CHECK_* @{
namespace std {
template <typename Clock, typename Duration>
inline std::ostream& operator<<(std::ostream& os, const std::chrono::time_point<Clock, Duration>& tp)
{
return os << tp.time_since_epoch().count();
}
template <typename T> requires std::is_enum_v<T>
inline std::ostream& operator<<(std::ostream& os, const T& e)
{

View File

@@ -12,6 +12,7 @@
#include <test/util/common.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/time.h>
#include <uint256.h>
#include <util/bitdeque.h>
#include <util/byte_units.h>
@@ -585,7 +586,7 @@ BOOST_AUTO_TEST_CASE(strprintf_numbers)
BOOST_AUTO_TEST_CASE(util_mocktime)
{
SetMockTime(111s);
NodeClockContext clock_ctx{111s};
// Check that mock time does not change after a sleep
for (const auto& num_sleep : {0ms, 1ms}) {
UninterruptibleSleep(num_sleep);
@@ -598,7 +599,6 @@ BOOST_AUTO_TEST_CASE(util_mocktime)
BOOST_CHECK_EQUAL(111000, TicksSinceEpoch<std::chrono::milliseconds>(NodeClock::now()));
BOOST_CHECK_EQUAL(111000000, GetTime<std::chrono::microseconds>().count());
}
SetMockTime(0s);
}
BOOST_AUTO_TEST_CASE(util_ticksseconds)

View File

@@ -205,7 +205,7 @@ FUZZ_TARGET(spkm_migration, .init = initialize_spkm_migration)
{
SeedRandomStateForTest(SeedRand::ZEROS);
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
SetMockTime(ConsumeTime(fuzzed_data_provider));
NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)};
const auto& node{g_setup->m_node};
Chainstate& chainstate{node.chainman->ActiveChainstate()};