diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index b94dc6ee88..caccc07d20 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -812,12 +812,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 ded313bb9d..ba77148ac3 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 f304736d28..683e24ec89 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 d9470a4966..afc27f2967 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 18a52695be..69acbb1081 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -483,6 +483,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 67466dec32..5e9cd7058e 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() ==