mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-04 11:15:56 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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 */
|
||||
|
||||
68
src/ripple/basics/MathUtilities.h
Normal file
68
src/ripple/basics/MathUtilities.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
118
src/test/protocol/BuildInfo_test.cpp
Normal file
118
src/test/protocol/BuildInfo_test.cpp
Normal 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
|
||||
Reference in New Issue
Block a user