From 14f99cfe53f07280b6f047844fc4fba0da8cd328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Mon, 19 Jan 2026 12:35:26 +0100 Subject: [PATCH] rpc: make `uptime` monotonic across NTP jumps Compute `uptime` from `SteadyClock` so it is unaffected by system time changes after startup. Derive GUI startup time by subtracting the monotonic uptime from the wall clock time. Add a functional test covering a large `setmocktime` jump. Co-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> --- src/common/system.cpp | 9 +++------ src/common/system.h | 6 ++++-- src/qt/clientmodel.cpp | 2 +- src/rpc/server.cpp | 2 +- test/functional/rpc_uptime.py | 9 ++++++--- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/common/system.cpp b/src/common/system.cpp index 72e9de100be..98bc01471d5 100644 --- a/src/common/system.cpp +++ b/src/common/system.cpp @@ -37,9 +37,6 @@ using util::ReplaceAll; -// Application startup time (used for uptime calculation) -const int64_t nStartupTime = GetTime(); - #ifndef WIN32 std::string ShellEscape(const std::string& arg) { @@ -130,8 +127,8 @@ std::optional GetTotalRAM() return std::nullopt; } -// Obtain the application startup time (used for uptime calculation) -int64_t GetStartupTime() +SteadyClock::duration GetUptime() { - return nStartupTime; + static const auto g_startup_time{SteadyClock::now()}; + return SteadyClock::now() - g_startup_time; } diff --git a/src/common/system.h b/src/common/system.h index 2184f1d4c9e..a3100fecbc1 100644 --- a/src/common/system.h +++ b/src/common/system.h @@ -7,13 +7,15 @@ #define BITCOIN_COMMON_SYSTEM_H #include // IWYU pragma: keep +#include +#include #include #include #include -// Application startup time (used for uptime calculation) -int64_t GetStartupTime(); +/// Monotonic uptime (not affected by system time changes). +SteadyClock::duration GetUptime(); void SetupEnvironment(); [[nodiscard]] bool SetupNetworking(); diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index dda26fa059b..cd7d9e32587 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -210,7 +210,7 @@ bool ClientModel::isReleaseVersion() const QString ClientModel::formatClientStartupTime() const { - return QDateTime::fromSecsSinceEpoch(GetStartupTime()).toString(); + return QDateTime::currentDateTime().addSecs(-TicksSeconds(GetUptime())).toString(); } QString ClientModel::dataDir() const diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 5fd733d056b..01fb815a967 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -184,7 +184,7 @@ static RPCHelpMan uptime() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - return GetTime() - GetStartupTime(); + return TicksSeconds(GetUptime()); } }; } diff --git a/test/functional/rpc_uptime.py b/test/functional/rpc_uptime.py index 74880417941..817ba2b4b53 100755 --- a/test/functional/rpc_uptime.py +++ b/test/functional/rpc_uptime.py @@ -26,9 +26,12 @@ class UptimeTest(BitcoinTestFramework): assert_raises_rpc_error(-8, "Mocktime must be in the range [0, 9223372036], not -1.", self.nodes[0].setmocktime, -1) def _test_uptime(self): - wait_time = 10 - self.nodes[0].setmocktime(int(time.time() + wait_time)) - assert self.nodes[0].uptime() >= wait_time + wait_time = 20_000 + uptime_before = self.nodes[0].uptime() + self.nodes[0].setmocktime(int(time.time()) + wait_time) + uptime_after = self.nodes[0].uptime() + self.nodes[0].setmocktime(0) + assert uptime_after - uptime_before < wait_time, "uptime should not jump with wall clock" if __name__ == '__main__':