From b60dd5749ca05390bceaf3256efc2a036a91f2e8 Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Tue, 22 Sep 2015 00:50:45 +0300 Subject: [PATCH] Automatically choose between NTP, median peer time and system clock. --- src/init.cpp | 27 +++---------------- src/ntp.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++++------------- src/ntp.h | 5 +++ src/util.cpp | 42 +++++++++++++++-------------- 4 files changed, 95 insertions(+), 62 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 9655f2a..4a9e19d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -998,31 +998,12 @@ bool AppInit2() // ********************************************************* Step 12: NTP synchronization - // First, do a simple check whether there is a local ntp server. - string strLocalHost = "127.0.0.1"; - int64_t nTime = NtpGetTime(strLocalHost); - - if (nTime < 0 || nTime != GetTime()) { - // If not, then request current timestamp from three random NTP servers. - uiInterface.InitMessage(_("Synchronizing time through NTP...")); - printf("Synchronizing time through NTP...\n"); - - for(int i = 0; i < 2; i++) { - CNetAddr ip; - int64_t nTime = NtpGetTime(ip); - - if (nTime > 0 && nTime != 2085978496) { // Skip the deliberately wrong timestamps - AddTimeData(ip, nTime); - printf("AddTimeData(%s, %" PRId64 ")\n", ip.ToString().c_str(), nTime); - } - } + // Trusted NTP server, it's localhost by default. + strTrustedUpstream = GetArg("-ntp", "localhost"); - // When done, start a periodical sampling thread - NewThread(ThreadNtpSamples, NULL); + // Start periodical NTP sampling thread + NewThread(ThreadNtpSamples, NULL); - uiInterface.InitMessage(_("Done")); - printf("Done\n"); - } // ********************************************************* Step 12: finished uiInterface.InitMessage(_("Done loading")); diff --git a/src/ntp.cpp b/src/ntp.cpp index 1d43fdb..b069f39 100644 --- a/src/ntp.cpp +++ b/src/ntp.cpp @@ -1,4 +1,3 @@ - #ifdef WIN32 #include #else @@ -13,8 +12,8 @@ #endif #include "netbase.h" -#include "util.h" #include "net.h" +#include "util.h" extern int GetRandInt(int nMax); @@ -365,20 +364,14 @@ int64_t DoReq(SOCKET sockfd, socklen_t servlen, struct sockaddr cliaddr) { } recvfrom(sockfd, (char *) msg, len, 0, NULL, NULL); - - ntohl_fp(&msg->rec, &prt->rec); ntohl_fp(&msg->xmt, &prt->xmt); - - time_t seconds_receive; time_t seconds_transmit; - - Ntp2Unix(prt->rec.Ul_i.Xl_ui, seconds_receive); Ntp2Unix(prt->xmt.Ul_i.Xl_ui, seconds_transmit); delete msg; delete prt; - return (seconds_receive + seconds_transmit) / 2; + return seconds_transmit; } int64_t NtpGetTime(CNetAddr& ip) { @@ -415,27 +408,79 @@ int64_t NtpGetTime(std::string &strHostName) return nTime; } -void ThreadNtpSamples(void* parg) -{ +// NTP server, which we unconditionally trust. This may be your own installation of ntpd somewhere, for example. +// "localhost" means "trust no one" +std::string strTrustedUpstream = "localhost"; + +// Current offset +int64_t nNtpOffset = INT64_MAX; + +int64_t GetNtpOffset() { + return nNtpOffset; +} + +void ThreadNtpSamples(void* parg) { + + // Maximum offset is 2 hours. + const int64_t nMaxOffset = 7200; + printf("ThreadNtpSamples started\n"); vnThreadsRunning[THREAD_NTP]++; // Make this thread recognisable as time synchronization thread RenameThread("novacoin-ntp-samples"); - while (!fShutdown) { - CNetAddr ip; - int64_t nTime = NtpGetTime(ip); + CMedianFilter vTimeOffsets(200,0); - if (nTime > 0 && nTime != 2085978496) { // Skip the deliberately wrong timestamps - AddTimeData(ip, nTime); + while (!fShutdown) { + if (strTrustedUpstream != "localhost") { + // Trying to get new offset sample from trusted NTP server. + int64_t nClockOffset = NtpGetTime(strTrustedUpstream) - GetTime(); + + if (abs64(nClockOffset) < nMaxOffset) { + // Everything seems right, remember new trusted offset. + nNtpOffset = nClockOffset; + } + else { + // Something went wrong. Disable trusted offset sampling and wait 600 seconds. + nNtpOffset = INT64_MAX; + strTrustedUpstream = "localhost"; + + for (int i = 0; i < 600 && !fShutdown; i++) // Sleep for 5 minutes + Sleep(1000); + + continue; + } } else { - Sleep(600000); // In case of failure wait 600 seconds and then try again - continue; + // Now, trying to get 2-4 samples from random NTP servers. + int nSamplesCount = 2 + GetRandInt(2); + + for (int i = 0; i < nSamplesCount; i++) { + CNetAddr ip; + int64_t nClockOffset = NtpGetTime(ip) - GetTime(); + + if (abs64(nClockOffset) < nMaxOffset) { // Skip the deliberately wrong timestamps + vTimeOffsets.input(nClockOffset); + } + } + + if (vTimeOffsets.size() > 2) { + nNtpOffset = vTimeOffsets.median(); + } + else { + // Not enough offsets yet, try again 300 seconds later. + nNtpOffset = INT64_MAX; + for (int i = 0; i < 300 && !fShutdown; i++) + Sleep(1000); + continue; + } } - Sleep(43200000); // Sleep for 12 hours + printf("nNtpOffset = %+" PRId64 " (%+" PRId64 " minutes)\n", nNtpOffset, nNtpOffset/60); + + for (int i = 0; i < 43200 && !fShutdown; i++) // Sleep for 12 hours + Sleep(1000); } vnThreadsRunning[THREAD_NTP]--; diff --git a/src/ntp.h b/src/ntp.h index 8be3864..6ec9191 100644 --- a/src/ntp.h +++ b/src/ntp.h @@ -4,5 +4,10 @@ int64_t NtpGetTime(CNetAddr& ip); // Get time from provided server. int64_t NtpGetTime(std::string &strHostName); +extern std::string strTrustedUpstream; + // NTP time samples thread. void ThreadNtpSamples(void* parg); + +// NTP offset +int64_t GetNtpOffset(); diff --git a/src/util.cpp b/src/util.cpp index ed7b34d..63c684b 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1298,25 +1298,32 @@ void ShrinkDebugFile() // - Median of other nodes clocks // - The user (asking the user to fix the system clock if the first two disagree) // -static int64_t nMockTime = 0; // For unit testing +// System clock int64_t GetTime() { - if (nMockTime) return nMockTime; - return time(NULL); } -void SetMockTime(int64_t nMockTimeIn) -{ - nMockTime = nMockTimeIn; -} +// Trusted NTP offset or median of NTP samples. +extern int64_t nNtpOffset; -static int64_t nTimeOffset = 0; +// Median of time samples given by other nodes. +static int64_t nNodesOffset = INT64_MAX; +// Select time offset: +// +// * If NTP and system clock are in agreement within 40 minutes, then use NTP. +// * If not, then choose between median peer time and system clock using the same condition. int64_t GetTimeOffset() { - return nTimeOffset; + if (abs64(nNtpOffset) < 40 * 60) + return nNtpOffset; + + if (abs64(nNodesOffset) < 40 * 60) + return nNodesOffset; + + return 0; } int64_t GetAdjustedTime() @@ -1343,17 +1350,18 @@ void AddTimeData(const CNetAddr& ip, int64_t nTime) // Only let other nodes change our time by so much if (abs64(nMedian) < 70 * 60) { - nTimeOffset = nMedian; + nNodesOffset = nMedian; } else { - nTimeOffset = 0; + nNodesOffset = INT64_MAX; static bool fDone; if (!fDone) { - // If nobody has a time different than ours but within 5 minutes of ours, give a warning bool fMatch = false; + + // If nobody has a time different than ours but within 5 minutes of ours, give a warning BOOST_FOREACH(int64_t nOffset, vSorted) if (nOffset != 0 && abs64(nOffset) < 5 * 60) fMatch = true; @@ -1373,17 +1381,11 @@ void AddTimeData(const CNetAddr& ip, int64_t nTime) printf("%+" PRId64 " ", n); printf("| "); } - printf("nTimeOffset = %+" PRId64 " (%+" PRId64 " minutes)\n", nTimeOffset, nTimeOffset/60); + if (nNodesOffset != INT64_MAX) + printf("nNodesOffset = %+" PRId64 " (%+" PRId64 " minutes)\n", nNodesOffset, nNodesOffset/60); } } - - - - - - - string FormatVersion(int nVersion) { if (nVersion%100 == 0) -- 1.7.1