time: add runtime sanity check

std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed
to use the Unix epoch timestamp, but in practice they almost certainly will.
Any differing behavior will be assumed to be an error, unless certain
platforms prove to consistently deviate, at which point we'll cope with it
by adding offsets.

Do a quick runtime check to verify that
time_t(0) == std::chrono::system_clock's epoch time == unix epoch.

Co-authored-by: Anthony Towns <aj@erisian.com.au>

Zcash: The first commit of bitcoin/bitcoin#21110; we intend to handle
the changes made by the second commit of that PR in a separate fashion;
see zcash/zcash#6042.

(cherry picked from commit bitcoin/bitcoin@3c2e16be22)
This commit is contained in:
Cory Fields 2017-01-16 15:01:37 -05:00 committed by Kris Nuttycombe
parent 66725e686c
commit a423c2af8c
4 changed files with 53 additions and 0 deletions

View File

@ -833,6 +833,10 @@ bool InitSanityCheck(void)
if (!glibc_sanity_test() || !glibcxx_sanity_test())
return false;
if (!ChronoSanityCheck()) {
return InitError("Clock epoch mismatch. Aborting.");
}
return true;
}

View File

@ -6,6 +6,7 @@
#include "compat/sanity.h"
#include "key.h"
#include "test/test_bitcoin.h"
#include "util/time.h"
#include <boost/test/unit_test.hpp>
@ -16,6 +17,7 @@ BOOST_AUTO_TEST_CASE(basic_sanity)
BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test");
BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test");
BOOST_CHECK_MESSAGE(CKey::ECC_InitSanityCheck() == true, "ECC sanity test");
BOOST_CHECK_MESSAGE(ChronoSanityCheck() == true, "chrono epoch test");
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -25,6 +25,50 @@ int64_t GetTime()
return time(NULL);
}
bool ChronoSanityCheck()
{
// std::chrono::system_clock.time_since_epoch and time_t(0) are not guaranteed
// to use the Unix epoch timestamp, prior to C++20, but in practice they almost
// certainly will. Any differing behavior will be assumed to be an error, unless
// certain platforms prove to consistently deviate, at which point we'll cope
// with it by adding offsets.
// Create a new clock from time_t(0) and make sure that it represents 0
// seconds from the system_clock's time_since_epoch. Then convert that back
// to a time_t and verify that it's the same as before.
const time_t time_t_epoch{};
auto clock = std::chrono::system_clock::from_time_t(time_t_epoch);
if (std::chrono::duration_cast<std::chrono::seconds>(clock.time_since_epoch()).count() != 0) {
return false;
}
time_t time_val = std::chrono::system_clock::to_time_t(clock);
if (time_val != time_t_epoch) {
return false;
}
// Check that the above zero time is actually equal to the known unix timestamp.
struct tm epoch;
#ifdef HAVE_GMTIME_R
if (gmtime_r(&time_val, &epoch) == nullptr) {
#else
if (gmtime_s(&epoch, &time_val) != 0) {
#endif
return false;
}
if ((epoch.tm_sec != 0) ||
(epoch.tm_min != 0) ||
(epoch.tm_hour != 0) ||
(epoch.tm_mday != 1) ||
(epoch.tm_mon != 0) ||
(epoch.tm_year != 70)) {
return false;
}
return true;
}
void SetMockTime(int64_t nMockTimeIn)
{
nMockTime = nMockTimeIn;

View File

@ -18,4 +18,7 @@ void MilliSleep(int64_t n);
std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime);
/** Sanity check epoch match normal Unix epoch */
bool ChronoSanityCheck();
#endif // BITCOIN_UTIL_TIME_H