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