test: Use NodeClockContext in more tests
What changed, and why it matters
This commit only changes Bitcoin Core's internal test and benchmark code. It replaces an older way of faking the system clock during tests (SetMockTime) with a newer helper object (NodeClockContext). No production code is touched, so this cannot affect real users, wallets, or the live network.
No security action needed. Treat as normal code hygiene / test modernization.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff is a pure test refactor across 11 files under src/test and src/bench. It removes calls to the global SetMockTime() helper and introduces local NodeClockContext instances to control mock time in unit tests and benchmarks. The changes are syntactic/structural: time values are now managed through an RAII-style test utility rather than a global setter. There are no changes to consensus, networking, wallet, mempool, or validation logic.
Changed components
src/bench/util_time.cppsrc/bench/wallet_balance.cppsrc/bench/wallet_create_tx.cppsrc/test/addrman_tests.cppsrc/test/banman_tests.cppsrc/test/chainstate_write_tests.cppsrc/test/denialofservice_tests.cppsrc/test/mempool_tests.cppsrc/test/orphanage_tests.cppsrc/test/testnet4_miner_tests.cppsrc/test/util_tests.cppInspect captured patch +57 / −55
diff --git a/src/bench/util_time.cpp b/src/bench/util_time.cpp
index bc3e3892..19ffd0e0 100644
--- a/src/bench/util_time.cpp
+++ b/src/bench/util_time.cpp
@@ -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)
diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp
index 03774ef5..45f23494 100644
--- a/src/bench/wallet_balance.cpp
+++ b/src/bench/wallet_balance.cpp
@@ -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);
diff --git a/src/bench/wallet_create_tx.cpp b/src/bench/wallet_create_tx.cpp
index 8ff1e39a..11125e26 100644
--- a/src/bench/wallet_create_tx.cpp
+++ b/src/bench/wallet_create_tx.cpp
@@ -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);
diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp
index e9069079..cd1c651a 100644
--- a/src/test/addrman_tests.cpp
+++ b/src/test/addrman_tests.cpp
@@ -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
diff --git a/src/test/banman_tests.cpp b/src/test/banman_tests.cpp
index 2b0c6218..d1e79e35 100644
--- a/src/test/banman_tests.cpp
+++ b/src/test/banman_tests.cpp
@@ -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{
diff --git a/src/test/chainstate_write_tests.cpp b/src/test/chainstate_write_tests.cpp
index 4a79cd8a..d0925b61 100644
--- a/src/test/chainstate_write_tests.cpp
+++ b/src/test/chainstate_write_tests.cpp
@@ -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);
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index 776a1878..9709eacd 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -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};
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
index 67e26438..77024c3e 100644
--- a/src/test/mempool_tests.cpp
+++ b/src/test/mempool_tests.cpp
@@ -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)
}
diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp
index 6ebfab34..9903434c 100644
--- a/src/test/orphanage_tests.cpp
+++ b/src/test/orphanage_tests.cpp
@@ -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;
diff --git a/src/test/testnet4_miner_tests.cpp b/src/test/testnet4_miner_tests.cpp
index d0aeebe0..1bfc19db 100644
--- a/src/test/testnet4_miner_tests.cpp
+++ b/src/test/testnet4_miner_tests.cpp
@@ -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);
}
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 8896807f..f6a8d3a4 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -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)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.