Warn operators about upcoming unknown amendments:

* When an unknown amendment reaches majority, log an error-level
  message, and return a `warnings` array on all successful
  admin-level RPC calls to `server_info` and `server_state` with
  a message describing the problem, and the expected deadline.
* In addition to the `amendment_blocked` flag returned by
  `server_info` and `server_state`, return a warning with a more
  verbose description when the server is amendment blocked.
* Check on every flag ledger to see if the amendment(s) lose majority.
  Logs again if they don't, resumes normal operations if they did.

The intention is to give operators earlier warning that their
instances are in danger of being amendment blocked, which will
hopefully motivate them to update ahead of time.
This commit is contained in:
Edward Hennis
2020-01-09 19:04:20 -05:00
committed by Manoj doshi
parent 4315913a5d
commit 5ff23f8f31
11 changed files with 418 additions and 50 deletions

View File

@@ -308,12 +308,38 @@ LedgerMaster::setValidLedger(
app_.getSHAMapStore().onLedgerClosed (getValidatedLedger());
mLedgerHistory.validatedLedger (l, consensusHash);
app_.getAmendmentTable().doValidatedLedger (l);
if (!app_.getOPs().isAmendmentBlocked() &&
app_.getAmendmentTable().hasUnsupportedEnabled ())
if (!app_.getOPs().isAmendmentBlocked())
{
JLOG (m_journal.error()) <<
"One or more unsupported amendments activated: server blocked.";
app_.getOPs().setAmendmentBlocked();
if (app_.getAmendmentTable().hasUnsupportedEnabled())
{
JLOG(m_journal.error()) << "One or more unsupported amendments "
"activated: server blocked.";
app_.getOPs().setAmendmentBlocked();
}
else if (!app_.getOPs().isAmendmentWarned() || ((l->seq() % 256) == 0))
{
// Amendments can lose majority, so re-check periodically (every
// flag ledger), and clear the flag if appropriate. If an unknown
// amendment gains majority log a warning as soon as it's
// discovered, then again every flag ledger until the operator
// upgrades, the amendment loses majority, or the amendment goes
// live and the node gets blocked. Unlike being amendment blocked,
// this message may be logged more than once per session, because
// the node will otherwise function normally, and this gives
// operators an opportunity to see and resolve the warning.
if (auto const first =
app_.getAmendmentTable().firstUnsupportedExpected())
{
JLOG(m_journal.error()) << "One or more unsupported amendments "
"reached majority. Upgrade before "
<< to_string(*first)
<< " to prevent your server from "
"becoming amendment blocked.";
app_.getOPs().setAmendmentWarned();
}
else
app_.getOPs().clearAmendmentWarned();
}
}
}

View File

@@ -54,6 +54,8 @@ public:
* @return true if an unsupported feature is enabled on the network
*/
virtual bool hasUnsupportedEnabled () = 0;
virtual boost::optional<NetClock::time_point>
firstUnsupportedExpected() = 0;
virtual Json::Value getJson (int) = 0;
@@ -61,11 +63,15 @@ public:
virtual Json::Value getJson (uint256 const& ) = 0;
/** Called when a new fully-validated ledger is accepted. */
void doValidatedLedger (std::shared_ptr<ReadView const> const& lastValidatedLedger)
void
doValidatedLedger(
std::shared_ptr<ReadView const> const& lastValidatedLedger)
{
if (needValidatedLedger (lastValidatedLedger->seq ()))
doValidatedLedger (lastValidatedLedger->seq (),
getEnabledAmendments (*lastValidatedLedger));
if (needValidatedLedger(lastValidatedLedger->seq()))
doValidatedLedger(
lastValidatedLedger->seq(),
getEnabledAmendments(*lastValidatedLedger),
getMajorityAmendments(*lastValidatedLedger));
}
/** Called to determine whether the amendment logic needs to process
@@ -77,7 +83,8 @@ public:
virtual void
doValidatedLedger (
LedgerIndex ledgerSeq,
std::set <uint256> const& enabled) = 0;
std::set <uint256> const& enabled,
majorityAmendments_t const& majority) = 0;
// Called by the consensus code when we need to
// inject pseudo-transactions

View File

@@ -31,6 +31,7 @@
#include <ripple/app/ledger/OrderBookDB.h>
#include <ripple/app/ledger/TransactionMaster.h>
#include <ripple/app/main/LoadManager.h>
#include <ripple/app/misc/AmendmentTable.h>
#include <ripple/app/misc/HashRouter.h>
#include <ripple/app/misc/LoadFeeTrack.h>
#include <ripple/app/misc/Transaction.h>
@@ -389,6 +390,21 @@ public:
return amendmentBlocked_;
}
void setAmendmentBlocked () override;
bool
isAmendmentWarned() override
{
return !amendmentBlocked_ && amendmentWarned_;
}
void
setAmendmentWarned() override
{
amendmentWarned_ = true;
}
void
clearAmendmentWarned() override
{
amendmentWarned_ = false;
}
void consensusViewChange () override;
Json::Value getConsensusInfo () override;
@@ -582,6 +598,7 @@ private:
std::atomic <bool> needNetworkLedger_ {false};
std::atomic <bool> amendmentBlocked_ {false};
std::atomic <bool> amendmentWarned_ {false};
ClosureCounter<void, boost::system::error_code const&> waitHandlerCounter_;
boost::asio::steady_timer heartbeatTimer_;
@@ -2258,6 +2275,38 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters)
{
Json::Value info = Json::objectValue;
// System-level warnings
{
Json::Value warnings{Json::arrayValue};
if (isAmendmentBlocked())
{
Json::Value& w = warnings.append(Json::objectValue);
w[jss::id] = warnRPC_AMENDMENT_BLOCKED;
w[jss::message] =
"This server is amendment blocked, and must be updated to be "
"able to stay in sync with the network.";
}
if (admin && isAmendmentWarned())
{
Json::Value& w = warnings.append(Json::objectValue);
w[jss::id] = warnRPC_UNSUPPORTED_MAJORITY;
w[jss::message] =
"One or more unsupported amendments have reached majority. "
"Upgrade to the latest version before they are activated "
"to avoid being amendment blocked.";
if (auto const expected =
app_.getAmendmentTable().firstUnsupportedExpected())
{
auto& d = w[jss::details] = Json::objectValue;
d[jss::expected_date] = expected->time_since_epoch().count();
d[jss::expected_date_UTC] = to_string(*expected);
}
}
if (warnings.size())
info[jss::warnings] = std::move(warnings);
}
// hostid: unique string describing the machine
if (human)
info [jss::hostid] = getHostId (admin);

View File

@@ -185,6 +185,9 @@ public:
virtual void setMode(OperatingMode om) = 0;
virtual bool isAmendmentBlocked () = 0;
virtual void setAmendmentBlocked () = 0;
virtual bool isAmendmentWarned() = 0;
virtual void setAmendmentWarned() = 0;
virtual void clearAmendmentWarned() = 0;
virtual void consensusViewChange () = 0;
virtual Json::Value getConsensusInfo () = 0;

View File

@@ -158,6 +158,10 @@ protected:
// True if an unsupported amendment is enabled
bool unsupportedEnabled_;
// Unset if no unsupported amendments reach majority,
// else set to the earliest time an unsupported amendment
// will be enabled.
boost::optional<NetClock::time_point> firstUnsupportedExpected_;
beast::Journal const j_;
@@ -190,15 +194,19 @@ public:
bool isSupported (uint256 const& amendment) override;
bool hasUnsupportedEnabled () override;
boost::optional<NetClock::time_point>
firstUnsupportedExpected() override;
Json::Value getJson (int) override;
Json::Value getJson (uint256 const&) override;
bool needValidatedLedger (LedgerIndex seq) override;
void doValidatedLedger (
void
doValidatedLedger(
LedgerIndex seq,
std::set<uint256> const& enabled) override;
std::set<uint256> const& enabled,
majorityAmendments_t const& majority) override;
std::vector <uint256>
doValidation (std::set<uint256> const& enabledAmendments) override;
@@ -392,6 +400,13 @@ AmendmentTableImpl::hasUnsupportedEnabled ()
return unsupportedEnabled_;
}
boost::optional<NetClock::time_point>
AmendmentTableImpl::firstUnsupportedExpected()
{
std::lock_guard sl(mutex_);
return firstUnsupportedExpected_;
}
std::vector <uint256>
AmendmentTableImpl::doValidation (
std::set<uint256> const& enabled)
@@ -539,12 +554,36 @@ AmendmentTableImpl::needValidatedLedger (LedgerIndex ledgerSeq)
}
void
AmendmentTableImpl::doValidatedLedger (
AmendmentTableImpl::doValidatedLedger(
LedgerIndex ledgerSeq,
std::set<uint256> const& enabled)
std::set<uint256> const& enabled,
majorityAmendments_t const& majority)
{
for (auto& e : enabled)
enable(e);
std::lock_guard sl(mutex_);
// Since we have the whole list in `majority`, reset the time flag, even if
// it's currently set. If it's not set when the loop is done, then any
// prior unknown amendments have lost majority.
firstUnsupportedExpected_.reset();
for (auto const& [ hash, time ] : majority)
{
auto s = add(hash);
if (s->enabled)
continue;
if (!s->supported)
{
JLOG(j_.info()) << "Unsupported amendment " << hash
<< " reached majority at " << to_string(time);
if (!firstUnsupportedExpected_ || firstUnsupportedExpected_ > time)
firstUnsupportedExpected_ = time;
}
}
if (firstUnsupportedExpected_)
firstUnsupportedExpected_ = *firstUnsupportedExpected_ + majorityTime_;
}
void

View File

@@ -138,6 +138,16 @@ enum error_code_i
rpcLAST = rpcINVALID_LGR_RANGE // rpcLAST should always equal the last code.=
};
/** Codes returned in the `warnings` array of certain RPC commands.
These values need to remain stable.
*/
enum warning_code_i
{
warnRPC_UNSUPPORTED_MAJORITY = 1001,
warnRPC_AMENDMENT_BLOCKED = 1002,
};
//------------------------------------------------------------------------------
// VFALCO NOTE these should probably not be in the RPC namespace.

View File

@@ -202,7 +202,7 @@ JSS ( destination_amount ); // in: PathRequest, RipplePathFind
JSS ( destination_currencies ); // in: PathRequest, RipplePathFind
JSS ( destination_tag ); // in: PathRequest
// out: AccountChannels
JSS ( details ); // out: Manifest
JSS ( details ); // out: Manifest, server_info
JSS ( dir_entry ); // out: DirectoryEntryIterator
JSS ( dir_index ); // out: DirectoryEntryIterator
JSS ( dir_root ); // out: DirectoryEntryIterator
@@ -223,6 +223,8 @@ JSS ( error_exception ); // out: Submit
JSS ( error_message ); // out: error
JSS ( escrow ); // in: LedgerEntry
JSS ( expand ); // in: handler/Ledger
JSS ( expected_date ); // out: any (warnings)
JSS ( expected_date_UTC ); // out: any (warnings)
JSS ( expected_ledger_size ); // out: TxQ
JSS ( expiration ); // out: AccountOffers, AccountChannels,
// ValidatorList
@@ -573,6 +575,7 @@ JSS ( version ); // out: RPCVersion
JSS ( vetoed ); // out: AmendmentTableImpl
JSS ( vote ); // in: Feature
JSS ( warning ); // rpc:
JSS ( warnings ); // out: server_info, server_state
JSS ( workers );
JSS ( write_load ); // out: GetCounts

View File

@@ -189,38 +189,6 @@ Status callMethod (
}
}
template <class Method, class Object>
void getResult (
JsonContext& context, Method method, Object& object, std::string const& name)
{
auto&& result = Json::addObject (object, jss::result);
if (auto status = callMethod (context, method, name, result))
{
JLOG (context.j.debug()) << "rpcError: " << status.toString();
result[jss::status] = jss::error;
auto rq = context.params;
if (rq.isObject())
{
if (rq.isMember(jss::passphrase.c_str()))
rq[jss::passphrase.c_str()] = "<masked>";
if (rq.isMember(jss::secret.c_str()))
rq[jss::secret.c_str()] = "<masked>";
if (rq.isMember(jss::seed.c_str()))
rq[jss::seed.c_str()] = "<masked>";
if (rq.isMember(jss::seed_hex.c_str()))
rq[jss::seed_hex.c_str()] = "<masked>";
}
result[jss::request] = rq;
}
else
{
result[jss::status] = jss::success;
}
}
} // namespace
Status doCommand (