From b36e11bc492f3d261c835728f1cc1acc0207cd93 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Thu, 18 Oct 2018 02:11:46 -0700 Subject: [PATCH] Properly handle expired validator lists when validating (RIPD-1661): A validator that was configured to use a published validator list could exhibit aberrent behavior if that validator list expired. This commit introduces additional logic that makes validators operating with an expired validator list bow out of the consensus process instead of continuing to publish validations. Normal operation will resume once a non-expired validator list becomes available. This commit also enhances status reporting when using the `server_info` and `validators` commands. Before, only the expiration time of the list would be returned; now, its current status is also reported in a format that is clearer. --- src/ripple/app/consensus/RCLConsensus.cpp | 16 +++++++- src/ripple/app/misc/NetworkOPs.cpp | 43 ++++++++++++++++------ src/ripple/app/misc/ValidatorList.h | 4 ++ src/ripple/app/misc/impl/ValidatorList.cpp | 34 ++++++++++++++--- src/ripple/protocol/JsonFields.h | 1 + src/test/rpc/ValidatorRPC_test.cpp | 19 ++++++---- 6 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index cfe8d480c..ec43eb9b6 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -806,12 +806,26 @@ RCLConsensus::peerProposal( bool RCLConsensus::Adaptor::preStartRound(RCLCxLedger const & prevLgr) { - // We have a key and do not want out of sync validations after a restart, + // We have a key, we do not want out of sync validations after a restart // and are not amendment blocked. validating_ = valPublic_.size() != 0 && prevLgr.seq() >= app_.getMaxDisallowedLedger() && !app_.getOPs().isAmendmentBlocked(); + // If we are not running in standalone mode and there's a configured UNL, + // check to make sure that it's not expired. + if (validating_ && !app_.config().standalone() && app_.validators().count()) + { + auto const when = app_.validators().expires(); + + if (!when || *when < app_.timeKeeper().now()) + { + JLOG(j_.error()) << "Voluntarily bowing out of consensus process " + "because of an expired validator list."; + validating_ = false; + } + } + const bool synced = app_.getOPs().getOperatingMode() == NetworkOPs::omFULL; if (validating_) diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 239bd85af..94bebbc84 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -2103,25 +2103,44 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin, bool counters) if (admin) { - if (auto when = app_.validators().expires()) + auto when = app_.validators().expires(); + + if (!human) { - if (human) - { - if(*when == TimeKeeper::time_point::max()) - info[jss::validator_list_expires] = "never"; - else - info[jss::validator_list_expires] = to_string(*when); - } - else + if (when) info[jss::validator_list_expires] = static_cast(when->time_since_epoch().count()); + else + info[jss::validator_list_expires] = 0; } else { - if (human) - info[jss::validator_list_expires] = "unknown"; + auto& x = (info[jss::validator_list] = Json::objectValue); + + x[jss::count] = static_cast(app_.validators().count()); + + if (when) + { + if (*when == TimeKeeper::time_point::max()) + { + x[jss::expiration] = "never"; + x[jss::status] = "active"; + } + else + { + x[jss::expiration] = to_string(*when); + + if (*when > app_.timeKeeper().now()) + x[jss::status] = "active"; + else + x[jss::status] = "expired"; + } + } else - info[jss::validator_list_expires] = 0; + { + x[jss::status] = "unknown"; + x[jss::expiration] = "unknown"; + } } } info[jss::io_latency_ms] = static_cast ( diff --git a/src/ripple/app/misc/ValidatorList.h b/src/ripple/app/misc/ValidatorList.h index f304736d2..683e24ec8 100644 --- a/src/ripple/app/misc/ValidatorList.h +++ b/src/ripple/app/misc/ValidatorList.h @@ -331,6 +331,10 @@ public: for_each_listed ( std::function func) const; + /** Return the number of configured validator list sites. */ + std::size_t + count() const; + /** Return the time when the validator list will expire @note This may be a time in the past if a published list has not diff --git a/src/ripple/app/misc/impl/ValidatorList.cpp b/src/ripple/app/misc/impl/ValidatorList.cpp index d9470a496..afc27f296 100644 --- a/src/ripple/app/misc/impl/ValidatorList.cpp +++ b/src/ripple/app/misc/impl/ValidatorList.cpp @@ -459,6 +459,13 @@ ValidatorList::removePublisherList (PublicKey const& publisherKey) return true; } +std::size_t +ValidatorList::count() const +{ + std::shared_lock read_lock{mutex_}; + return publisherLists_.size(); +} + boost::optional ValidatorList::expires() const { @@ -486,19 +493,34 @@ ValidatorList::getJson() const res[jss::validation_quorum] = static_cast(quorum()); - if (auto when = expires()) { - if (*when == TimeKeeper::time_point::max()) + auto& x = (res[jss::validator_list] = Json::objectValue); + + x[jss::count] = static_cast(count()); + + if (auto when = expires()) { - res[jss::validator_list_expires] = "never"; + if (*when == TimeKeeper::time_point::max()) + { + x[jss::expiration] = "never"; + x[jss::status] = "active"; + } + else + { + x[jss::expiration] = to_string(*when); + + if (*when > timeKeeper_.now()) + x[jss::status] = "active"; + else + x[jss::status] = "expired"; + } } else { - res[jss::validator_list_expires] = to_string(*when); + x[jss::status] = "unknown"; + x[jss::expiration] = "unknown"; } } - else - res[jss::validator_list_expires] = "unknown"; // Local static keys PublicKey local; diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index 70c2cc2bd..d9fcecabe 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -481,6 +481,7 @@ JSS ( validate ); // in: DownloadShard JSS ( validated ); // out: NetworkOPs, RPCHelpers, AccountTx* // Tx JSS ( validator_list_expires ); // out: NetworkOps, ValidatorList +JSS ( validator_list ); // out: NetworkOps, ValidatorList JSS ( validated_ledger ); // out: NetworkOPs JSS ( validated_ledgers ); // out: NetworkOPs JSS ( validation_key ); // out: ValidationCreate, ValidationSeed diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index 67466dec3..5e9cd7058 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -111,7 +111,7 @@ public: auto const jrr = env.rpc("server_info")[jss::result]; BEAST_EXPECT(jrr[jss::status] == "success"); BEAST_EXPECT(jrr[jss::info].isMember( - jss::validator_list_expires) == isAdmin); + jss::validator_list) == isAdmin); } { @@ -145,7 +145,8 @@ public: { auto const jrr = env.rpc("server_info")[jss::result]; BEAST_EXPECT( - jrr[jss::info][jss::validator_list_expires] == "never"); + jrr[jss::info][jss::validator_list][jss::expiration] == + "never"); } { auto const jrr = env.rpc("server_state")[jss::result]; @@ -156,7 +157,7 @@ public: // All our keys are in the response { auto const jrr = env.rpc("validators")[jss::result]; - BEAST_EXPECT(jrr[jss::validator_list_expires] == "never"); + BEAST_EXPECT(jrr[jss::validator_list][jss::expiration] == "never"); BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == keys.size()); BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == keys.size()); BEAST_EXPECT(jrr[jss::publisher_lists].size() == 0); @@ -226,7 +227,8 @@ public: { auto const jrr = env.rpc("server_info")[jss::result]; BEAST_EXPECT( - jrr[jss::info][jss::validator_list_expires] == "unknown"); + jrr[jss::info][jss::validator_list][jss::expiration] == + "unknown"); } { auto const jrr = env.rpc("server_state")[jss::result]; @@ -239,7 +241,8 @@ public: std::numeric_limits::max()); BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0); BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() == 0); - BEAST_EXPECT(jrr[jss::validator_list_expires] == "unknown"); + BEAST_EXPECT( + jrr[jss::validator_list][jss::expiration] == "unknown"); if (BEAST_EXPECT(jrr[jss::publisher_lists].size() == 1)) { @@ -312,7 +315,8 @@ public: { auto const jrr = env.rpc("server_info")[jss::result]; - BEAST_EXPECT(jrr[jss::info][jss::validator_list_expires] == + BEAST_EXPECT( + jrr[jss::info][jss::validator_list][jss::expiration] == to_string(expiration)); } { @@ -325,7 +329,8 @@ public: auto const jrr = env.rpc("validators")[jss::result]; BEAST_EXPECT(jrr[jss::validation_quorum].asUInt() == 2); BEAST_EXPECT( - jrr[jss::validator_list_expires] == to_string(expiration)); + jrr[jss::validator_list][jss::expiration] == + to_string(expiration)); BEAST_EXPECT(jrr[jss::local_static_keys].size() == 0); BEAST_EXPECT(jrr[jss::trusted_validator_keys].size() ==