Skip to content

Commit 0c3a3c9

Browse files
committed
Merge bitcoin/bitcoin#29623: Simplify network-adjusted time warning logic
c6be144 Remove timedata (stickies-v) 92e72b5 [net processing] Move IgnoresIncomingTxs to PeerManagerInfo (dergoegge) 7d9c3ec [net processing] Introduce PeerManagerInfo (dergoegge) ee178df Add TimeOffsets helper class (stickies-v) 55361a1 [net processing] Use std::chrono for type-safe time offsets (stickies-v) 038fd97 [net processing] Move nTimeOffset to net_processing (dergoegge) Pull request description: [An earlier approach](https://github.com/bitcoin/bitcoin/commits/1d226ae1f984c5c808f5c24c431b959cdefa692e/) in #28956 involved simplifying and refactoring the network-adjusted time calculation logic, but this was eventually [left out](bitcoin/bitcoin#28956 (comment)) of the PR to make it easier for reviewers to focus on consensus logic changes. Since network-adjusted time is now only used for warning/informational purposes, cleaning up the logic (building on @dergoegge's approach in #28956) should be quite straightforward and uncontroversial. The main changes are: - Previously, we would only calculate the time offset from the first 199 outbound peers that we connected to. This limitation is now removed, and we have a proper rolling calculation. I've reduced the set to 50 outbound peers, which seems plenty. - Previously, we would automatically use the network-adjusted time if the difference was < 70 mins, and warn the user if the difference was larger than that. Since there is no longer any automated time adjustment, I've changed the warning threshold to ~~20~~ 10 minutes (which is an arbitrary number). - Previously, a warning would only be raised once, and then never again until node restart. This behaviour is now updated to 1) warn to log for every new outbound peer for as long as we appear out of sync, 2) have the RPC warning toggled on/off whenever we go in/out of sync, and 3) have the GUI warn whenever we are out of sync (again), but limited to 1 messagebox per 60 minutes - no more globals - remove the `-maxtimeadjustment` startup arg Closes #4521 ACKs for top commit: sr-gi: Re-ACK [c6be144](bitcoin/bitcoin@c6be144) achow101: reACK c6be144 dergoegge: utACK c6be144 Tree-SHA512: 1063d639542e882186cdcea67d225ad1f97847f44253621a8c4b36c4d777e8f5cb0efe86bc279f01e819d33056ae4364c3300cc7400c087fb16c3f39b3e16b96
2 parents d813ba1 + c6be144 commit 0c3a3c9

25 files changed

+268
-368
lines changed

src/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ BITCOIN_CORE_H = \
230230
node/peerman_args.h \
231231
node/protocol_version.h \
232232
node/psbt.h \
233+
node/timeoffsets.h \
233234
node/transaction.h \
234235
node/txreconciliation.h \
235236
node/utxo_snapshot.h \
@@ -280,7 +281,6 @@ BITCOIN_CORE_H = \
280281
support/lockedpool.h \
281282
sync.h \
282283
threadsafety.h \
283-
timedata.h \
284284
torcontrol.h \
285285
txdb.h \
286286
txmempool.h \
@@ -434,6 +434,7 @@ libbitcoin_node_a_SOURCES = \
434434
node/minisketchwrapper.cpp \
435435
node/peerman_args.cpp \
436436
node/psbt.cpp \
437+
node/timeoffsets.cpp \
437438
node/transaction.cpp \
438439
node/txreconciliation.cpp \
439440
node/utxo_snapshot.cpp \
@@ -461,7 +462,6 @@ libbitcoin_node_a_SOURCES = \
461462
rpc/txoutproof.cpp \
462463
script/sigcache.cpp \
463464
signet.cpp \
464-
timedata.cpp \
465465
torcontrol.cpp \
466466
txdb.cpp \
467467
txmempool.cpp \

src/Makefile.test.include

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ BITCOIN_TESTS =\
153153
test/streams_tests.cpp \
154154
test/sync_tests.cpp \
155155
test/system_tests.cpp \
156-
test/timedata_tests.cpp \
156+
test/timeoffsets_tests.cpp \
157157
test/torcontrol_tests.cpp \
158158
test/transaction_tests.cpp \
159159
test/translation_tests.cpp \
@@ -384,7 +384,7 @@ test_fuzz_fuzz_SOURCES = \
384384
test/fuzz/string.cpp \
385385
test/fuzz/strprintf.cpp \
386386
test/fuzz/system.cpp \
387-
test/fuzz/timedata.cpp \
387+
test/fuzz/timeoffsets.cpp \
388388
test/fuzz/torcontrol.cpp \
389389
test/fuzz/transaction.cpp \
390390
test/fuzz/tx_in.cpp \

src/addrman_impl.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <protocol.h>
1212
#include <serialize.h>
1313
#include <sync.h>
14-
#include <timedata.h>
1514
#include <uint256.h>
1615
#include <util/time.h>
1716

src/init.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
#include <scheduler.h>
6868
#include <script/sigcache.h>
6969
#include <sync.h>
70-
#include <timedata.h>
7170
#include <torcontrol.h>
7271
#include <txdb.h>
7372
#include <txmempool.h>
@@ -523,7 +522,6 @@ void SetupServerArgs(ArgsManager& argsman)
523522
argsman.AddArg("-maxconnections=<n>", strprintf("Maintain at most <n> automatic connections to peers (default: %u). This limit does not apply to connections manually added via -addnode or the addnode RPC, which have a separate limit of %u.", DEFAULT_MAX_PEER_CONNECTIONS, MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
524523
argsman.AddArg("-maxreceivebuffer=<n>", strprintf("Maximum per-connection receive buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXRECEIVEBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
525524
argsman.AddArg("-maxsendbuffer=<n>", strprintf("Maximum per-connection memory usage for the send buffer, <n>*1000 bytes (default: %u)", DEFAULT_MAXSENDBUFFER), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
526-
argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by outbound peers forward or backward by this amount (default: %u seconds).", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
527525
argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target per 24h. Limit does not apply to peers with 'download' permission or blocks created within past week. 0 = no limit (default: %s). Optional suffix units [k|K|m|M|g|G|t|T] (default: M). Lowercase is 1000 base while uppercase is 1024 base", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
528526
#if HAVE_SOCKADDR_UN
529527
argsman.AddArg("-onion=<ip:port|path>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy). May be a local file path prefixed with 'unix:'.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);

src/net.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,6 @@ void CNode::CopyStats(CNodeStats& stats)
615615
X(m_last_tx_time);
616616
X(m_last_block_time);
617617
X(m_connected);
618-
X(nTimeOffset);
619618
X(m_addr_name);
620619
X(nVersion);
621620
{

src/net.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ class CNodeStats
191191
std::chrono::seconds m_last_tx_time;
192192
std::chrono::seconds m_last_block_time;
193193
std::chrono::seconds m_connected;
194-
int64_t nTimeOffset;
195194
std::string m_addr_name;
196195
int nVersion;
197196
std::string cleanSubVer;
@@ -703,7 +702,6 @@ class CNode
703702
std::atomic<std::chrono::seconds> m_last_recv{0s};
704703
//! Unix epoch time at peer connection
705704
const std::chrono::seconds m_connected;
706-
std::atomic<int64_t> nTimeOffset{0};
707705
// Address of this peer
708706
const CAddress addr;
709707
// Bind address of our side of the connection

src/net_processing.cpp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <netbase.h>
2424
#include <netmessagemaker.h>
2525
#include <node/blockstorage.h>
26+
#include <node/timeoffsets.h>
2627
#include <node/txreconciliation.h>
2728
#include <policy/fees.h>
2829
#include <policy/policy.h>
@@ -34,7 +35,6 @@
3435
#include <scheduler.h>
3536
#include <streams.h>
3637
#include <sync.h>
37-
#include <timedata.h>
3838
#include <tinyformat.h>
3939
#include <txmempool.h>
4040
#include <txorphanage.h>
@@ -390,6 +390,10 @@ struct Peer {
390390
/** Whether this peer wants invs or headers (when possible) for block announcements */
391391
bool m_prefers_headers GUARDED_BY(NetEventsInterface::g_msgproc_mutex){false};
392392

393+
/** Time offset computed during the version handshake based on the
394+
* timestamp the peer sent in the version message. */
395+
std::atomic<std::chrono::seconds> m_time_offset{0s};
396+
393397
explicit Peer(NodeId id, ServiceFlags our_services)
394398
: m_id{id}
395399
, m_our_services{our_services}
@@ -513,7 +517,7 @@ class PeerManagerImpl final : public PeerManager
513517
std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override
514518
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
515519
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
516-
bool IgnoresIncomingTxs() override { return m_opts.ignore_incoming_txs; }
520+
PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
517521
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
518522
void RelayTransaction(const uint256& txid, const uint256& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
519523
void SetBestBlock(int height, std::chrono::seconds time) override
@@ -788,6 +792,8 @@ class PeerManagerImpl final : public PeerManager
788792
/** Next time to check for stale tip */
789793
std::chrono::seconds m_stale_tip_check_time GUARDED_BY(cs_main){0s};
790794

795+
TimeOffsets m_outbound_time_offsets;
796+
791797
const Options m_opts;
792798

793799
bool RejectIncomingTxs(const CNode& peer) const;
@@ -1864,10 +1870,19 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
18641870
stats.presync_height = peer->m_headers_sync->GetPresyncHeight();
18651871
}
18661872
}
1873+
stats.time_offset = peer->m_time_offset;
18671874

18681875
return true;
18691876
}
18701877

1878+
PeerManagerInfo PeerManagerImpl::GetInfo() const
1879+
{
1880+
return PeerManagerInfo{
1881+
.median_outbound_time_offset = m_outbound_time_offsets.Median(),
1882+
.ignores_incoming_txs = m_opts.ignore_incoming_txs,
1883+
};
1884+
}
1885+
18711886
void PeerManagerImpl::AddToCompactExtraTransactions(const CTransactionRef& tx)
18721887
{
18731888
if (m_opts.max_extra_txs <= 0)
@@ -3861,12 +3876,12 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
38613876
peer->m_starting_height, addrMe.ToStringAddrPort(), fRelay, pfrom.GetId(),
38623877
remoteAddr, (mapped_as ? strprintf(", mapped_as=%d", mapped_as) : ""));
38633878

3864-
int64_t nTimeOffset = nTime - GetTime();
3865-
pfrom.nTimeOffset = nTimeOffset;
3879+
peer->m_time_offset = NodeSeconds{std::chrono::seconds{nTime}} - Now<NodeSeconds>();
38663880
if (!pfrom.IsInboundConn()) {
38673881
// Don't use timedata samples from inbound peers to make it
3868-
// harder for others to tamper with our adjusted time.
3869-
AddTimeData(pfrom.addr, nTimeOffset);
3882+
// harder for others to create false warnings about our clock being out of sync.
3883+
m_outbound_time_offsets.Add(peer->m_time_offset);
3884+
m_outbound_time_offsets.WarnIfOutOfSync();
38703885
}
38713886

38723887
// If the peer is old enough to have the old alert system, send it the final alert.

src/net_processing.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <net.h>
1010
#include <validationinterface.h>
1111

12+
#include <chrono>
13+
1214
class AddrMan;
1315
class CChainParams;
1416
class CTxMemPool;
@@ -41,6 +43,12 @@ struct CNodeStateStats {
4143
bool m_addr_relay_enabled{false};
4244
ServiceFlags their_services;
4345
int64_t presync_height{-1};
46+
std::chrono::seconds time_offset{0};
47+
};
48+
49+
struct PeerManagerInfo {
50+
std::chrono::seconds median_outbound_time_offset{0s};
51+
bool ignores_incoming_txs{false};
4452
};
4553

4654
class PeerManager : public CValidationInterface, public NetEventsInterface
@@ -83,8 +91,8 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
8391
/** Get statistics from node state */
8492
virtual bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const = 0;
8593

86-
/** Whether this node ignores txs received over p2p. */
87-
virtual bool IgnoresIncomingTxs() = 0;
94+
/** Get peer manager info. */
95+
virtual PeerManagerInfo GetInfo() const = 0;
8896

8997
/** Relay transaction to all peers. */
9098
virtual void RelayTransaction(const uint256& txid, const uint256& wtxid) = 0;

src/node/timeoffsets.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) 2024-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <logging.h>
6+
#include <node/interface_ui.h>
7+
#include <node/timeoffsets.h>
8+
#include <sync.h>
9+
#include <tinyformat.h>
10+
#include <util/time.h>
11+
#include <util/translation.h>
12+
#include <warnings.h>
13+
14+
#include <algorithm>
15+
#include <chrono>
16+
#include <cstdint>
17+
#include <deque>
18+
#include <limits>
19+
#include <optional>
20+
21+
using namespace std::chrono_literals;
22+
23+
void TimeOffsets::Add(std::chrono::seconds offset)
24+
{
25+
LOCK(m_mutex);
26+
27+
if (m_offsets.size() >= MAX_SIZE) {
28+
m_offsets.pop_front();
29+
}
30+
m_offsets.push_back(offset);
31+
LogDebug(BCLog::NET, "Added time offset %+ds, total samples %d\n",
32+
Ticks<std::chrono::seconds>(offset), m_offsets.size());
33+
}
34+
35+
std::chrono::seconds TimeOffsets::Median() const
36+
{
37+
LOCK(m_mutex);
38+
39+
// Only calculate the median if we have 5 or more offsets
40+
if (m_offsets.size() < 5) return 0s;
41+
42+
auto sorted_copy = m_offsets;
43+
std::sort(sorted_copy.begin(), sorted_copy.end());
44+
return sorted_copy[sorted_copy.size() / 2]; // approximate median is good enough, keep it simple
45+
}
46+
47+
bool TimeOffsets::WarnIfOutOfSync() const
48+
{
49+
// when median == std::numeric_limits<int64_t>::min(), calling std::chrono::abs is UB
50+
auto median{std::max(Median(), std::chrono::seconds(std::numeric_limits<int64_t>::min() + 1))};
51+
if (std::chrono::abs(median) <= WARN_THRESHOLD) {
52+
SetMedianTimeOffsetWarning(std::nullopt);
53+
uiInterface.NotifyAlertChanged();
54+
return false;
55+
}
56+
57+
bilingual_str msg{strprintf(_(
58+
"Your computer's date and time appear to be more than %d minutes out of sync with the network, "
59+
"this may lead to consensus failure. After you've confirmed your computer's clock, this message "
60+
"should no longer appear when you restart your node. Without a restart, it should stop showing "
61+
"automatically after you've connected to a sufficient number of new outbound peers, which may "
62+
"take some time. You can inspect the `timeoffset` field of the `getpeerinfo` and `getnetworkinfo` "
63+
"RPC methods to get more info."
64+
), Ticks<std::chrono::minutes>(WARN_THRESHOLD))};
65+
LogWarning("%s\n", msg.original);
66+
SetMedianTimeOffsetWarning(msg);
67+
uiInterface.NotifyAlertChanged();
68+
return true;
69+
}

src/node/timeoffsets.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2024-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_NODE_TIMEOFFSETS_H
6+
#define BITCOIN_NODE_TIMEOFFSETS_H
7+
8+
#include <sync.h>
9+
10+
#include <chrono>
11+
#include <cstddef>
12+
#include <deque>
13+
14+
class TimeOffsets
15+
{
16+
//! Maximum number of timeoffsets stored.
17+
static constexpr size_t MAX_SIZE{50};
18+
//! Minimum difference between system and network time for a warning to be raised.
19+
static constexpr std::chrono::minutes WARN_THRESHOLD{10};
20+
21+
mutable Mutex m_mutex;
22+
/** The observed time differences between our local clock and those of our outbound peers. A
23+
* positive offset means our peer's clock is ahead of our local clock. */
24+
std::deque<std::chrono::seconds> m_offsets GUARDED_BY(m_mutex){};
25+
26+
public:
27+
/** Add a new time offset sample. */
28+
void Add(std::chrono::seconds offset) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
29+
30+
/** Compute and return the median of the collected time offset samples. The median is returned
31+
* as 0 when there are less than 5 samples. */
32+
std::chrono::seconds Median() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
33+
34+
/** Raise warnings if the median time offset exceeds the warnings threshold. Returns true if
35+
* warnings were raised. */
36+
bool WarnIfOutOfSync() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex);
37+
};
38+
39+
#endif // BITCOIN_NODE_TIMEOFFSETS_H

0 commit comments

Comments
 (0)