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>
This commit is contained in:
LőrincandMarcoFalke committed 2026-01-19 17:08:08 +01:00
1 parent a9440b1595
commit 14f99cfe53
5 files changed
+15 -13

No files matched your search

+3 -6
View File
@@ -37,9 +37,6 @@
using util::ReplaceAll; using util::ReplaceAll;
// Application startup time (used for uptime calculation)
const int64_t nStartupTime = GetTime();
#ifndef WIN32 #ifndef WIN32
std::string ShellEscape(const std::string& arg) std::string ShellEscape(const std::string& arg)
{ {
@@ -130,8 +127,8 @@ std::optional<size_t> GetTotalRAM()
return std::nullopt; return std::nullopt;
} }
// Obtain the application startup time (used for uptime calculation) SteadyClock::duration GetUptime()
int64_t GetStartupTime()
{ {
return nStartupTime; static const auto g_startup_time{SteadyClock::now()};
return SteadyClock::now() - g_startup_time;
} }
+4 -2
View File
@@ -7,13 +7,15 @@
#define BITCOIN_COMMON_SYSTEM_H #define BITCOIN_COMMON_SYSTEM_H
#include <bitcoin-build-config.h> // IWYU pragma: keep #include <bitcoin-build-config.h> // IWYU pragma: keep
#include <util/time.h>
#include <chrono>
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
#include <string> #include <string>
// Application startup time (used for uptime calculation) /// Monotonic uptime (not affected by system time changes).
int64_t GetStartupTime(); SteadyClock::duration GetUptime();
void SetupEnvironment(); void SetupEnvironment();
[[nodiscard]] bool SetupNetworking(); [[nodiscard]] bool SetupNetworking();
+1 -1
View File
@@ -210,7 +210,7 @@ bool ClientModel::isReleaseVersion() const
QString ClientModel::formatClientStartupTime() const QString ClientModel::formatClientStartupTime() const
{ {
return QDateTime::fromSecsSinceEpoch(GetStartupTime()).toString(); return QDateTime::currentDateTime().addSecs(-TicksSeconds(GetUptime())).toString();
} }
QString ClientModel::dataDir() const QString ClientModel::dataDir() const
+1 -1
View File
@@ -184,7 +184,7 @@ static RPCHelpMan uptime()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
return GetTime() - GetStartupTime(); return TicksSeconds(GetUptime());
} }
}; };
} }
+6 -3
View File
@@ -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) assert_raises_rpc_error(-8, "Mocktime must be in the range [0, 9223372036], not -1.", self.nodes[0].setmocktime, -1)
def _test_uptime(self): def _test_uptime(self):
wait_time = 10 wait_time = 20_000
self.nodes[0].setmocktime(int(time.time() + wait_time)) uptime_before = self.nodes[0].uptime()
assert self.nodes[0].uptime() >= wait_time 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__': if __name__ == '__main__':