Skip to content

Commit 27aeeff

Browse files
committed
Merge bitcoin#34328: rpc: make uptime monotonic across NTP jumps
14f99cf rpc: make `uptime` monotonic across NTP jumps (Lőrinc) a9440b1 util: add `TicksSeconds` (Lőrinc) Pull request description: ### Problem `bitcoin-cli uptime` was derived from wall-clock time, so it could jump by large amounts when the system clock is corrected after `bitcoind` starts (e.g. on RTC-less systems syncing NTP). This breaks the expectation that uptime reflects process runtime. ### Fix Compute uptime from a [monotonic clock](https://en.cppreference.com/w/cpp/chrono/steady_clock.html) so it is immune to wall-clock jumps, and use that monotonic uptime for the RPC. GUI startup time is derived from wall clock time minus monotonic uptime so it remains sensible after clock corrections. ### Reproducer Revert the fix commit and run the `rpc_uptime` functional test (it should fail with `AssertionError: uptime should not jump with wall clock`): Or alternatively: ```bash cmake -B build && cmake --build build --target bitcoind bitcoin-cli -j$(nproc) DATA_DIR=$(mktemp -d) ./build/bin/bitcoind -regtest -datadir="$DATA_DIR" -connect=0 -daemon ./build/bin/bitcoin-cli -regtest -datadir="$DATA_DIR" -rpcwait uptime sleep 1 ./build/bin/bitcoin-cli -regtest -datadir="$DATA_DIR" setmocktime $(( $(date +%s) + 20000000 )) ./build/bin/bitcoin-cli -regtest -datadir="$DATA_DIR" uptime ./build/bin/bitcoin-cli -regtest -datadir="$DATA_DIR" stop ``` <details> <summary>Before (uptime jumps with wall clock)</summary> ```bash Bitcoin Core starting 0 20000001 Bitcoin Core stopping ``` </details> <details> <summary>After (uptime stays monotonic)</summary> ```bash Bitcoin Core starting 0 1 Bitcoin Core stopping ``` </details> ---------- Issue: bitcoin#34326 ACKs for top commit: maflcko: review ACK 14f99cf 🎦 willcl-ark: tACK 14f99cf w0xlt: ACK 14f99cf sedited: ACK 14f99cf Tree-SHA512: 3909973f58666ffa0b784a6df087031b9e34d2022d354900a4dbb6cbe1d36285cd92770ee71350ebf64d6e8ab212d8ff0cd851f7dca1ec46ee2f19b417f53984
2 parents f970cb3 + 14f99cf commit 27aeeff

File tree

7 files changed

+30
-13
lines changed

7 files changed

+30
-13
lines changed

src/common/system.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@
3737

3838
using util::ReplaceAll;
3939

40-
// Application startup time (used for uptime calculation)
41-
const int64_t nStartupTime = GetTime();
42-
4340
#ifndef WIN32
4441
std::string ShellEscape(const std::string& arg)
4542
{
@@ -130,8 +127,8 @@ std::optional<size_t> GetTotalRAM()
130127
return std::nullopt;
131128
}
132129

133-
// Obtain the application startup time (used for uptime calculation)
134-
int64_t GetStartupTime()
130+
SteadyClock::duration GetUptime()
135131
{
136-
return nStartupTime;
132+
static const auto g_startup_time{SteadyClock::now()};
133+
return SteadyClock::now() - g_startup_time;
137134
}

src/common/system.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
#define BITCOIN_COMMON_SYSTEM_H
88

99
#include <bitcoin-build-config.h> // IWYU pragma: keep
10+
#include <util/time.h>
1011

12+
#include <chrono>
1113
#include <cstdint>
1214
#include <optional>
1315
#include <string>
1416

15-
// Application startup time (used for uptime calculation)
16-
int64_t GetStartupTime();
17+
/// Monotonic uptime (not affected by system time changes).
18+
SteadyClock::duration GetUptime();
1719

1820
void SetupEnvironment();
1921
[[nodiscard]] bool SetupNetworking();

src/qt/clientmodel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ bool ClientModel::isReleaseVersion() const
210210

211211
QString ClientModel::formatClientStartupTime() const
212212
{
213-
return QDateTime::fromSecsSinceEpoch(GetStartupTime()).toString();
213+
return QDateTime::currentDateTime().addSecs(-TicksSeconds(GetUptime())).toString();
214214
}
215215

216216
QString ClientModel::dataDir() const

src/rpc/server.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ static RPCHelpMan uptime()
184184
},
185185
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
186186
{
187-
return GetTime() - GetStartupTime();
187+
return TicksSeconds(GetUptime());
188188
}
189189
};
190190
}

src/test/util_tests.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,15 @@ BOOST_AUTO_TEST_CASE(util_mocktime)
600600
SetMockTime(0s);
601601
}
602602

603+
BOOST_AUTO_TEST_CASE(util_ticksseconds)
604+
{
605+
BOOST_CHECK_EQUAL(TicksSeconds(0s), 0);
606+
BOOST_CHECK_EQUAL(TicksSeconds(1s), 1);
607+
BOOST_CHECK_EQUAL(TicksSeconds(999ms), 0);
608+
BOOST_CHECK_EQUAL(TicksSeconds(1000ms), 1);
609+
BOOST_CHECK_EQUAL(TicksSeconds(1500ms), 1);
610+
}
611+
603612
BOOST_AUTO_TEST_CASE(test_IsDigit)
604613
{
605614
BOOST_CHECK_EQUAL(IsDigit('0'), true);

src/util/time.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ constexpr auto Ticks(Dur2 d)
7474
{
7575
return std::chrono::duration_cast<Dur1>(d).count();
7676
}
77+
78+
template <typename Duration>
79+
constexpr int64_t TicksSeconds(Duration d)
80+
{
81+
return int64_t{Ticks<std::chrono::seconds>(d)};
82+
}
7783
template <typename Duration, typename Timepoint>
7884
constexpr auto TicksSinceEpoch(Timepoint t)
7985
{

test/functional/rpc_uptime.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ def _test_negative_time(self):
2626
assert_raises_rpc_error(-8, "Mocktime must be in the range [0, 9223372036], not -1.", self.nodes[0].setmocktime, -1)
2727

2828
def _test_uptime(self):
29-
wait_time = 10
30-
self.nodes[0].setmocktime(int(time.time() + wait_time))
31-
assert self.nodes[0].uptime() >= wait_time
29+
wait_time = 20_000
30+
uptime_before = self.nodes[0].uptime()
31+
self.nodes[0].setmocktime(int(time.time()) + wait_time)
32+
uptime_after = self.nodes[0].uptime()
33+
self.nodes[0].setmocktime(0)
34+
assert uptime_after - uptime_before < wait_time, "uptime should not jump with wall clock"
3235

3336

3437
if __name__ == '__main__':

0 commit comments

Comments
 (0)