Line data Source code
1 : // Copyright (c) 2014-2022 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 : #if defined(HAVE_CONFIG_H) 6 : #include <config/bitcoin-config.h> 7 : #endif 8 : 9 : #include <timedata.h> 10 : 11 : #include <common/args.h> 12 : #include <logging.h> 13 : #include <netaddress.h> 14 : #include <node/interface_ui.h> 15 : #include <sync.h> 16 : #include <tinyformat.h> 17 : #include <util/translation.h> 18 : #include <warnings.h> 19 : 20 : static GlobalMutex g_timeoffset_mutex; 21 : static int64_t nTimeOffset GUARDED_BY(g_timeoffset_mutex) = 0; 22 : 23 : /** 24 : * "Never go to sea with two chronometers; take one or three." 25 : * Our three time sources are: 26 : * - System clock 27 : * - Median of other nodes clocks 28 : * - The user (asking the user to fix the system clock if the first two disagree) 29 : */ 30 15377 : int64_t GetTimeOffset() 31 : { 32 15377 : LOCK(g_timeoffset_mutex); 33 15377 : return nTimeOffset; 34 15377 : } 35 : 36 15377 : NodeClock::time_point GetAdjustedTime() 37 : { 38 15377 : return NodeClock::now() + std::chrono::seconds{GetTimeOffset()}; 39 : } 40 : 41 : #define BITCOIN_TIMEDATA_MAX_SAMPLES 200 42 : 43 2 : static std::set<CNetAddr> g_sources; 44 2 : static CMedianFilter<int64_t> g_time_offsets{BITCOIN_TIMEDATA_MAX_SAMPLES, 0}; 45 : static bool g_warning_emitted; 46 : 47 0 : void AddTimeData(const CNetAddr& ip, int64_t nOffsetSample) 48 : { 49 0 : LOCK(g_timeoffset_mutex); 50 : // Ignore duplicates 51 0 : if (g_sources.size() == BITCOIN_TIMEDATA_MAX_SAMPLES) 52 0 : return; 53 0 : if (!g_sources.insert(ip).second) 54 0 : return; 55 : 56 : // Add data 57 0 : g_time_offsets.input(nOffsetSample); 58 0 : LogPrint(BCLog::NET, "added time data, samples %d, offset %+d (%+d minutes)\n", g_time_offsets.size(), nOffsetSample, nOffsetSample / 60); 59 : 60 : // There is a known issue here (see issue #4521): 61 : // 62 : // - The structure g_time_offsets contains up to 200 elements, after which 63 : // any new element added to it will not increase its size, replacing the 64 : // oldest element. 65 : // 66 : // - The condition to update nTimeOffset includes checking whether the 67 : // number of elements in g_time_offsets is odd, which will never happen after 68 : // there are 200 elements. 69 : // 70 : // But in this case the 'bug' is protective against some attacks, and may 71 : // actually explain why we've never seen attacks which manipulate the 72 : // clock offset. 73 : // 74 2 : // So we should hold off on fixing this and clean it up as part of 75 : // a timing cleanup that strengthens it in a number of other ways. 76 : // 77 0 : if (g_time_offsets.size() >= 5 && g_time_offsets.size() % 2 == 1) { 78 0 : int64_t nMedian = g_time_offsets.median(); 79 0 : std::vector<int64_t> vSorted = g_time_offsets.sorted(); 80 : // Only let other nodes change our time by so much 81 0 : int64_t max_adjustment = std::max<int64_t>(0, gArgs.GetIntArg("-maxtimeadjustment", DEFAULT_MAX_TIME_ADJUSTMENT)); 82 0 : if (nMedian >= -max_adjustment && nMedian <= max_adjustment) { 83 0 : nTimeOffset = nMedian; 84 0 : } else { 85 0 : nTimeOffset = 0; 86 : 87 0 : if (!g_warning_emitted) { 88 : // If nobody has a time different than ours but within 5 minutes of ours, give a warning 89 0 : bool fMatch = false; 90 0 : for (const int64_t nOffset : vSorted) { 91 0 : if (nOffset != 0 && nOffset > -5 * 60 && nOffset < 5 * 60) fMatch = true; 92 : } 93 : 94 0 : if (!fMatch) { 95 0 : g_warning_emitted = true; 96 0 : bilingual_str strMessage = strprintf(_("Please check that your computer's date and time are correct! If your clock is wrong, %s will not work properly."), PACKAGE_NAME); 97 0 : SetMiscWarning(strMessage); 98 0 : uiInterface.ThreadSafeMessageBox(strMessage, "", CClientUIInterface::MSG_WARNING); 99 0 : } 100 0 : } 101 : } 102 : 103 0 : if (LogAcceptCategory(BCLog::NET, BCLog::Level::Debug)) { 104 0 : std::string log_message{"time data samples: "}; 105 0 : for (const int64_t n : vSorted) { 106 0 : log_message += strprintf("%+d ", n); 107 : } 108 0 : log_message += strprintf("| median offset = %+d (%+d minutes)", nTimeOffset, nTimeOffset / 60); 109 0 : LogPrint(BCLog::NET, "%s\n", log_message); 110 0 : } 111 0 : } 112 0 : } 113 : 114 0 : void TestOnlyResetTimeData() 115 : { 116 0 : LOCK(g_timeoffset_mutex); 117 0 : nTimeOffset = 0; 118 0 : g_sources.clear(); 119 0 : g_time_offsets = CMedianFilter<int64_t>{BITCOIN_TIMEDATA_MAX_SAMPLES, 0}; 120 0 : g_warning_emitted = false; 121 0 : }