mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-11 21:20:39 +02:00
kernel: add btck_set_mock_time for testing time-dependent paths
Some kernel paths read the current time (e.g. header validation's future-time check, and the `btck_SynchronizationState` carried by `btck_NotifyBlockTip` / `btck_NotifyHeaderTip` callbacks), which makes them awkward to exercise deterministically in tests. This commit exposes `btck_set_mock_time` as a wrapper over the existing `SetMockTime`, mirroring what the node has via the `setmocktime` RPC.
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
#include <util/result.h>
|
||||
#include <util/signalinterrupt.h>
|
||||
#include <util/task_runner.h>
|
||||
#include <util/time.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <validationinterface.h>
|
||||
@@ -43,6 +44,7 @@
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
@@ -1488,3 +1490,13 @@ int btck_transaction_check(const btck_Transaction* tx, btck_TxValidationState* v
|
||||
const bool ok = CheckTransaction(*btck_Transaction::get(tx), state);
|
||||
return ok ? 1 : 0;
|
||||
}
|
||||
|
||||
int btck_set_mock_time(int64_t timestamp)
|
||||
{
|
||||
constexpr int64_t max_time{std::numeric_limits<uint32_t>::max()};
|
||||
if (timestamp < 0 || timestamp > max_time) {
|
||||
return -1;
|
||||
}
|
||||
SetMockTime(std::chrono::seconds{timestamp});
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1960,6 +1960,28 @@ BITCOINKERNEL_API void btck_block_header_destroy(btck_BlockHeader* header);
|
||||
|
||||
///@}
|
||||
|
||||
/** @name Testing
|
||||
* Functions intended for testing purposes only.
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* @brief Override the current time with a fixed timestamp for testing.
|
||||
*
|
||||
* Affects all kernel time reads globally. The caller is responsible
|
||||
* for gating usage (e.g. restricting to regtest) if desired.
|
||||
*
|
||||
* The upper bound (4294967295) matches the maximum value of a block header
|
||||
* timestamp.
|
||||
*
|
||||
* @param[in] timestamp Unix epoch seconds, or 0 to restore the system clock.
|
||||
* @return 0 on success, non-zero if timestamp is outside the
|
||||
* valid [0, 4294967295] range.
|
||||
*/
|
||||
BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_set_mock_time(int64_t timestamp);
|
||||
|
||||
///@}
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <kernel/bitcoinkernel.h>
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -1355,6 +1356,13 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
inline void set_mock_time(std::chrono::seconds timestamp)
|
||||
{
|
||||
if (btck_set_mock_time(timestamp.count()) != 0) {
|
||||
throw std::runtime_error("timestamp out of range");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace btck
|
||||
|
||||
#endif // BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
|
||||
|
||||
@@ -1386,3 +1386,51 @@ BOOST_AUTO_TEST_CASE(btck_transaction_check_tests)
|
||||
"ffffffff00ffffffff000100000000000000000000000000000000000000000000000000000000"
|
||||
"00000000000000ffffffff010000000000000000015100000000");
|
||||
}
|
||||
|
||||
class KernelMockTime
|
||||
{
|
||||
public:
|
||||
explicit KernelMockTime(std::chrono::seconds timestamp) { set(timestamp); }
|
||||
~KernelMockTime()
|
||||
{
|
||||
set_mock_time(std::chrono::seconds{0});
|
||||
}
|
||||
|
||||
KernelMockTime(const KernelMockTime&) = delete;
|
||||
KernelMockTime& operator=(const KernelMockTime&) = delete;
|
||||
|
||||
void set(std::chrono::seconds timestamp) { set_mock_time(timestamp); }
|
||||
};
|
||||
|
||||
BOOST_AUTO_TEST_CASE(btck_set_mock_time_tests)
|
||||
{
|
||||
// Out-of-range timestamps throw
|
||||
BOOST_CHECK_EXCEPTION(set_mock_time(std::chrono::seconds{-1}), std::runtime_error, HasReason("timestamp out of range"));
|
||||
constexpr std::chrono::seconds max_time{std::numeric_limits<uint32_t>::max()};
|
||||
BOOST_CHECK_EXCEPTION(set_mock_time(max_time + std::chrono::seconds{1}), std::runtime_error, HasReason("timestamp out of range"));
|
||||
|
||||
// Confirm the mock time actually takes effect by exercising the header future-time check
|
||||
auto test_directory{TestDirectory{"set_mock_time_test_bitcoin_kernel"}};
|
||||
auto notifications{std::make_shared<TestKernelNotifications>()};
|
||||
auto context{create_context(notifications, ChainType::REGTEST)};
|
||||
auto chainman{create_chainman(
|
||||
test_directory, /*reindex=*/false, /*wipe_chainstate=*/false,
|
||||
/*block_tree_db_in_memory=*/true, /*chainstate_db_in_memory=*/true, context)};
|
||||
|
||||
Block block{hex_string_to_byte_vec(REGTEST_BLOCK_DATA[0])};
|
||||
BlockHeader header{block.GetHeader()};
|
||||
const std::chrono::seconds block_time{header.Timestamp()};
|
||||
|
||||
// With the time set 3h before the header, the kernel must see the header as >2h in the future and reject it
|
||||
KernelMockTime mock_time{block_time - std::chrono::hours{3}};
|
||||
BlockValidationState future_state{chainman->ProcessBlockHeader(header)};
|
||||
BOOST_CHECK(future_state.GetValidationMode() == ValidationMode::INVALID);
|
||||
BOOST_CHECK(future_state.GetBlockValidationResult() == BlockValidationResult::TIME_FUTURE);
|
||||
|
||||
// At the upper bound the header is far in the past and must be accepted; this also
|
||||
// confirms the future-time check's "now + 2h" computation doesn't overflow when now is at its max.
|
||||
mock_time.set(max_time);
|
||||
BlockValidationState ok_state{chainman->ProcessBlockHeader(header)};
|
||||
BOOST_CHECK(ok_state.GetValidationMode() == ValidationMode::VALID);
|
||||
BOOST_CHECK(ok_state.GetBlockValidationResult() == BlockValidationResult::UNSET);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user