Implement version upgrade warning:

If the 'HardenedValidations' amendment is enabled, this commit will
track the version of the software that validators embed in their
validations.

If a server notices that at least 60% of the validators on its UNL
are running a newer version than it is running, it will periodically
print an informational message, reminding the operator to check for
update.
This commit is contained in:
Peng Wang
2020-06-08 20:34:24 -04:00
committed by manojsdoshi
parent cf6f40ea8f
commit 51bd4626b1
7 changed files with 369 additions and 55 deletions

View File

@@ -150,6 +150,7 @@ install (
src/ripple/basics/IOUAmount.h
src/ripple/basics/LocalValue.h
src/ripple/basics/Log.h
src/ripple/basics/MathUtilities.h
src/ripple/basics/safe_cast.h
src/ripple/basics/Slice.h
src/ripple/basics/StringUtilities.h
@@ -870,6 +871,7 @@ target_sources (rippled PRIVATE
test sources:
subdir: protocol
#]===============================]
src/test/protocol/BuildInfo_test.cpp
src/test/protocol/InnerObjectFormats_test.cpp
src/test/protocol/Issue_test.cpp
src/test/protocol/PublicKey_test.cpp

View File

@@ -407,6 +407,9 @@ private:
// without first wiping the database.
LedgerIndex const max_ledger_difference_{1000000};
// Time that the previous upgrade warning was issued.
TimeKeeper::time_point upgradeWarningPrevTime_{};
private:
struct Stats
{

View File

@@ -34,6 +34,7 @@
#include <ripple/app/paths/PathRequests.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/MathUtilities.h>
#include <ripple/basics/TaggedCache.h>
#include <ripple/basics/UptimeClock.h>
#include <ripple/basics/contract.h>
@@ -43,6 +44,7 @@
#include <ripple/nodestore/DatabaseShard.h>
#include <ripple/overlay/Overlay.h>
#include <ripple/overlay/Peer.h>
#include <ripple/protocol/BuildInfo.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/digest.h>
#include <ripple/resource/Fees.h>
@@ -1049,6 +1051,78 @@ LedgerMaster::checkAccept(std::shared_ptr<Ledger const> const& ledger)
app_.getFeeTrack().setRemoteFee(fee);
tryAdvance();
if (ledger->seq() % 256 == 0)
{
// Check if the majority of validators run a higher version rippled
// software. If so print a warning.
//
// Once the HardenedValidations amendment is enabled, validators include
// their rippled software version in the validation messages of every
// (flag - 1) ledger. We wait for one ledger time before checking the
// version information to accumulate more validation messages.
auto currentTime = app_.timeKeeper().now();
bool needPrint = false;
// The variable upgradeWarningPrevTime_ will be set when and only when
// the warning is printed.
if (upgradeWarningPrevTime_ == TimeKeeper::time_point())
{
// Have not printed the warning before, check if need to print.
auto const vals = app_.getValidations().getTrustedForLedger(
ledger->info().parentHash);
std::size_t higherVersionCount = 0;
std::size_t rippledCount = 0;
for (auto const& v : vals)
{
if (v->isFieldPresent(sfServerVersion))
{
auto version = v->getFieldU64(sfServerVersion);
higherVersionCount +=
BuildInfo::isNewerVersion(version) ? 1 : 0;
rippledCount +=
BuildInfo::isRippledVersion(version) ? 1 : 0;
}
}
// We report only if (1) we have accumulated validation messages
// from 90% validators from the UNL, (2) 60% of validators
// running the rippled implementation have higher version numbers,
// and (3) the calculation won't cause divide-by-zero.
if (higherVersionCount > 0 && rippledCount > 0)
{
constexpr std::size_t reportingPercent = 90;
constexpr std::size_t cutoffPercent = 60;
auto const unlSize{
app_.validators().getQuorumKeys().second.size()};
needPrint = unlSize > 0 &&
calculatePercent(vals.size(), unlSize) >=
reportingPercent &&
calculatePercent(higherVersionCount, rippledCount) >=
cutoffPercent;
}
}
// To throttle the warning messages, instead of printing a warning
// every flag ledger, we print every week.
else if (currentTime - upgradeWarningPrevTime_ >= weeks{1})
{
// Printed the warning before, and assuming most validators
// do not downgrade, we keep printing the warning
// until the local server is restarted.
needPrint = true;
}
if (needPrint)
{
upgradeWarningPrevTime_ = currentTime;
auto const upgradeMsg =
"Check for upgrade: "
"A majority of trusted validators are "
"running a newer version.";
std::cerr << upgradeMsg << std::endl;
JLOG(m_journal.error()) << upgradeMsg;
}
}
}
/** Report that the consensus process built a particular ledger */

View File

@@ -0,0 +1,68 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_BASICS_MATHUTILITIES_H_INCLUDED
#define RIPPLE_BASICS_MATHUTILITIES_H_INCLUDED
#include <algorithm>
#include <assert.h>
#include <cstddef>
namespace ripple {
/** Calculate one number divided by another number in percentage.
* The result is rounded up to the next integer, and capped in the range [0,100]
* E.g. calculatePercent(1, 100) = 1 because 1/100 = 0.010000
* calculatePercent(1, 99) = 2 because 1/99 = 0.010101
* calculatePercent(0, 100) = 0
* calculatePercent(100, 100) = 100
* calculatePercent(200, 100) = 100 because the result is capped to 100
*
* @param count dividend
* @param total divisor
* @return the percentage, in [0, 100]
*
* @note total cannot be zero.
* */
constexpr std::size_t
calculatePercent(std::size_t count, std::size_t total)
{
assert(total != 0);
return ((std::min(count, total) * 100) + total - 1) / total;
}
// unit tests
static_assert(calculatePercent(1, 2) == 50);
static_assert(calculatePercent(0, 100) == 0);
static_assert(calculatePercent(100, 100) == 100);
static_assert(calculatePercent(200, 100) == 100);
static_assert(calculatePercent(1, 100) == 1);
static_assert(calculatePercent(1, 99) == 2);
static_assert(calculatePercent(6, 14) == 43);
static_assert(calculatePercent(29, 33) == 88);
static_assert(calculatePercent(1, 64) == 2);
static_assert(calculatePercent(0, 100'000'000) == 0);
static_assert(calculatePercent(1, 100'000'000) == 1);
static_assert(calculatePercent(50'000'000, 100'000'000) == 50);
static_assert(calculatePercent(50'000'001, 100'000'000) == 51);
static_assert(calculatePercent(99'999'999, 100'000'000) == 100);
} // namespace ripple
#endif

View File

@@ -43,7 +43,7 @@ getVersionString();
std::string const&
getFullVersionString();
/** Returns the server version packed in a 64-bit integer.
/** Encode an arbitrary server software version in a 64-bit integer.
The general format is:
@@ -64,10 +64,38 @@ getFullVersionString();
10 if an RC
01 if a beta
N: 6-bit rc/beta number (1-63)
@param the version string
@return the encoded version in a 64-bit integer
*/
std::uint64_t
encodeSoftwareVersion(char const* const versionStr);
/** Returns this server's version packed in a 64-bit integer. */
std::uint64_t
getEncodedVersion();
/** Check if the encoded software version is a rippled software version.
@param version another node's encoded software version
@return true if the version is a rippled software version, false otherwise
*/
bool
isRippledVersion(std::uint64_t version);
/** Check if the version is newer than the local node's rippled software
version.
@param version another node's encoded software version
@return true if the version is newer than the local node's rippled software
version, false otherwise.
@note This function only understands version numbers that are generated by
rippled. Please see the encodeSoftwareVersion() function for detail.
*/
bool
isNewerVersion(std::uint64_t version);
} // namespace BuildInfo
} // namespace ripple

View File

@@ -77,77 +77,98 @@ getFullVersionString()
return value;
}
static constexpr std::uint64_t implementationVersionIdentifier =
0x183B'0000'0000'0000LLU;
static constexpr std::uint64_t implementationVersionIdentifierMask =
0xFFFF'0000'0000'0000LLU;
std::uint64_t
getEncodedVersion()
encodeSoftwareVersion(char const* const versionStr)
{
static std::uint64_t const cookie = []() {
std::uint64_t c = 0x183B000000000000;
std::uint64_t c = implementationVersionIdentifier;
beast::SemanticVersion v;
beast::SemanticVersion v;
if (v.parse(versionString))
if (v.parse(std::string(versionStr)))
{
if (v.majorVersion >= 0 && v.majorVersion <= 255)
c |= static_cast<std::uint64_t>(v.majorVersion) << 40;
if (v.minorVersion >= 0 && v.minorVersion <= 255)
c |= static_cast<std::uint64_t>(v.minorVersion) << 32;
if (v.patchVersion >= 0 && v.patchVersion <= 255)
c |= static_cast<std::uint64_t>(v.patchVersion) << 24;
if (!v.isPreRelease())
c |= static_cast<std::uint64_t>(0xC00000);
if (v.isPreRelease())
{
if (v.majorVersion >= 0 && v.majorVersion <= 255)
c |= static_cast<std::uint64_t>(v.majorVersion) << 40;
std::uint8_t x = 0;
if (v.minorVersion >= 0 && v.minorVersion <= 255)
c |= static_cast<std::uint64_t>(v.minorVersion) << 32;
if (v.patchVersion >= 0 && v.patchVersion <= 255)
c |= static_cast<std::uint64_t>(v.patchVersion) << 24;
if (!v.isPreRelease())
c |= static_cast<std::uint64_t>(0xC00000);
if (v.isPreRelease())
for (auto id : v.preReleaseIdentifiers)
{
std::uint8_t x = 0;
auto parsePreRelease = [](std::string_view identifier,
std::string_view prefix,
std::uint8_t key,
std::uint8_t lok,
std::uint8_t hik) -> std::uint8_t {
std::uint8_t ret = 0;
for (auto id : v.preReleaseIdentifiers)
if (prefix != identifier.substr(0, prefix.length()))
return 0;
if (!beast::lexicalCastChecked(
ret,
std::string(identifier.substr(prefix.length()))))
return 0;
if (std::clamp(ret, lok, hik) != ret)
return 0;
return ret + key;
};
x = parsePreRelease(id, "rc", 0x80, 0, 63);
if (x == 0)
x = parsePreRelease(id, "b", 0x40, 0, 63);
if (x & 0xC0)
{
auto parsePreRelease =
[](std::string_view identifier,
std::string_view prefix,
std::uint8_t key,
std::uint8_t lok,
std::uint8_t hik) -> std::uint8_t {
std::uint8_t ret = 0;
if (prefix != identifier.substr(0, prefix.length()))
return 0;
if (!beast::lexicalCastChecked(
ret,
std::string(
identifier.substr(prefix.length()))))
return 0;
if (std::clamp(ret, lok, hik) != ret)
return 0;
return ret + key;
};
x = parsePreRelease(id, "rc", 0x80, 0, 63);
if (x == 0)
x = parsePreRelease(id, "b", 0x40, 0, 63);
if (x & 0xC0)
{
c |= static_cast<std::uint64_t>(x) << 16;
break;
}
c |= static_cast<std::uint64_t>(x) << 16;
break;
}
}
}
}
return c;
}();
return c;
}
std::uint64_t
getEncodedVersion()
{
static std::uint64_t const cookie = {encodeSoftwareVersion(versionString)};
return cookie;
}
bool
isRippledVersion(std::uint64_t version)
{
return (version & implementationVersionIdentifierMask) ==
implementationVersionIdentifier;
}
bool
isNewerVersion(std::uint64_t version)
{
if (isRippledVersion(version))
return version > getEncodedVersion();
return false;
}
} // namespace BuildInfo
} // namespace ripple

View File

@@ -0,0 +1,118 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/beast/unit_test.h>
#include <ripple/protocol/BuildInfo.h>
namespace ripple {
class BuildInfo_test : public beast::unit_test::suite
{
public:
void
testEncodeSoftwareVersion()
{
testcase("EncodeSoftwareVersion");
auto encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b7");
// the first two bytes identify the particular implementation, 0x183B
BEAST_EXPECT(
(encodedVersion & 0xFFFF'0000'0000'0000LLU) ==
0x183B'0000'0000'0000LLU);
// the next three bytes: major version, minor version, patch version,
// 0x010203
BEAST_EXPECT(
(encodedVersion & 0x0000'FFFF'FF00'0000LLU) ==
0x0000'0102'0300'0000LLU);
// the next two bits:
{
// 01 if a beta
BEAST_EXPECT(
(encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22 == 0b01);
// 10 if an RC
encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.4-rc7");
BEAST_EXPECT(
(encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22 == 0b10);
// 11 if neither an RC nor a beta
encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.5");
BEAST_EXPECT(
(encodedVersion & 0x0000'0000'00C0'0000LLU) >> 22 == 0b11);
}
// the next six bits: rc/beta number (1-63)
encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.6-b63");
BEAST_EXPECT((encodedVersion & 0x0000'0000'003F'0000LLU) >> 16 == 63);
// the last two bytes are zeros
BEAST_EXPECT((encodedVersion & 0x0000'0000'0000'FFFFLLU) == 0);
// Test some version strings with wrong formats:
// no rc/beta number
encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b");
BEAST_EXPECT((encodedVersion & 0x0000'0000'00FF'0000LLU) == 0);
// rc/beta number out of range
encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.3-b64");
BEAST_EXPECT((encodedVersion & 0x0000'0000'00FF'0000LLU) == 0);
// Check that the rc/beta number of a release is 0:
encodedVersion = BuildInfo::encodeSoftwareVersion("1.2.6");
BEAST_EXPECT((encodedVersion & 0x0000'0000'003F'0000LLU) == 0);
}
void
testIsRippledVersion()
{
testcase("IsRippledVersion");
auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU;
BEAST_EXPECT(!BuildInfo::isRippledVersion(vFF));
auto vRippled = 0x183B'0000'0000'0000LLU;
BEAST_EXPECT(BuildInfo::isRippledVersion(vRippled));
}
void
testIsNewerVersion()
{
testcase("IsNewerVersion");
auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU;
BEAST_EXPECT(!BuildInfo::isNewerVersion(vFF));
auto v159 = BuildInfo::encodeSoftwareVersion("1.5.9");
BEAST_EXPECT(!BuildInfo::isNewerVersion(v159));
auto vCurrent = BuildInfo::getEncodedVersion();
BEAST_EXPECT(!BuildInfo::isNewerVersion(vCurrent));
auto vMax = BuildInfo::encodeSoftwareVersion("255.255.255");
BEAST_EXPECT(BuildInfo::isNewerVersion(vMax));
}
void
run() override
{
testEncodeSoftwareVersion();
testIsRippledVersion();
testIsNewerVersion();
}
};
BEAST_DEFINE_TESTSUITE(BuildInfo, protocol, ripple);
} // namespace ripple