Add validator list RPC commands (RIPD-1541):

In support of dynamic validator list, this changeset:

1. Adds a new `validator_list_expires` field to `server_info` that
indicates when the current validator list will become stale.
2. Adds a new admin only `validator_lists` RPC that returns the
current list of known validators and the most recent published validator
lists.
3. Adds a new admin only `validator_sites` RPC that returns the list of
configured validator publisher sites and when they were most recently
queried.
This commit is contained in:
Brad Chase
2017-10-05 10:52:38 -04:00
parent 02059a27d6
commit 044dd53513
18 changed files with 1140 additions and 201 deletions

View File

@@ -2148,6 +2148,29 @@ Json::Value NetworkOPsImp::getServerInfo (bool human, bool admin)
info[jss::validation_quorum] = static_cast<Json::UInt>(
app_.validators ().quorum ());
if (admin)
{
if (auto when = app_.validators().expires())
{
if (human)
{
if(*when == TimeKeeper::time_point::max())
info[jss::validator_list_expires] = "never";
else
info[jss::validator_list_expires] = to_string(*when);
}
else
info[jss::validator_list_expires] =
static_cast<Json::UInt>(when->time_since_epoch().count());
}
else
{
if (human)
info[jss::validator_list_expires] = "unknown";
else
info[jss::validator_list_expires] = 0;
}
}
info[jss::io_latency_ms] = static_cast<Json::UInt> (
app_.getIOLatency().count());

View File

@@ -25,6 +25,7 @@
#include <ripple/basics/UnorderedContainers.h>
#include <ripple/core/TimeKeeper.h>
#include <ripple/crypto/csprng.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/PublicKey.h>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/range/adaptors.hpp>
@@ -40,6 +41,9 @@ enum class ListDisposition
/// List is valid
accepted = 0,
/// Same sequence as current list
same_sequence,
/// List version is not supported
unsupported_version,
@@ -50,9 +54,12 @@ enum class ListDisposition
stale,
/// Invalid format or signature
invalid,
invalid
};
std::string
to_string(ListDisposition disposition);
/**
Trusted Validators List
-----------------------
@@ -103,7 +110,7 @@ class ValidatorList
bool available;
std::vector<PublicKey> list;
std::size_t sequence;
std::size_t expiration;
TimeKeeper::time_point expiration;
};
ManifestCache& validatorManifests_;
@@ -126,6 +133,9 @@ class ValidatorList
PublicKey localPubKey_;
// Currently supported version of publisher list format
static constexpr std::uint32_t requiredListVersion = 1;
// The minimum number of listed validators required to allow removing
// non-communicative validators from the trusted set. In other words, if the
// number of listed validators is less, then use all of them in the
@@ -135,6 +145,8 @@ class ValidatorList
// tolerance isn't needed.
std::size_t const BYZANTINE_THRESHOLD {32};
public:
ValidatorList (
ManifestCache& validatorManifests,
@@ -318,6 +330,26 @@ public:
for_each_listed (
std::function<void(PublicKey const&, bool)> func) 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
been updated since its expiration. It will be boost::none if any
configured published list has not been fetched.
@par Thread Safety
May be called concurrently
*/
boost::optional<TimeKeeper::time_point>
expires() const;
/** Return a JSON representation of the state of the validator list
@par Thread Safety
May be called concurrently
*/
Json::Value
getJson() const;
private:
/** Check response for trusted valid published list
@@ -374,10 +406,9 @@ ValidatorList::onConsensusStart (
for (auto const& list : publisherLists_)
{
// Remove any expired published lists
if (list.second.expiration &&
list.second.expiration <=
timeKeeper_.now().time_since_epoch().count())
removePublisherList (list.first);
if (TimeKeeper::time_point{} < list.second.expiration &&
list.second.expiration <= timeKeeper_.now())
removePublisherList(list.first);
if (! list.second.available)
allListsAvailable = false;

View File

@@ -24,6 +24,7 @@
#include <ripple/app/misc/detail/Work.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/json_value.h>
#include <boost/asio.hpp>
#include <mutex>
@@ -68,10 +69,17 @@ private:
struct Site
{
struct Status
{
clock_type::time_point refreshed;
ListDisposition disposition;
};
std::string uri;
parsedURL pUrl;
std::chrono::minutes refreshInterval;
clock_type::time_point nextRefresh;
boost::optional<Status> lastRefreshStatus;
};
boost::asio::io_service& ios_;
@@ -146,6 +154,11 @@ public:
void
stop ();
/** Return JSON representation of configured validator sites
*/
Json::Value
getJson() const;
private:
/// Queue next site to be fetched
void

View File

@@ -21,11 +21,33 @@
#include <ripple/basics/Slice.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/json_reader.h>
#include <ripple/protocol/JsonFields.h>
#include <beast/core/detail/base64.hpp>
#include <boost/regex.hpp>
namespace ripple {
std::string
to_string(ListDisposition disposition)
{
switch (disposition)
{
case ListDisposition::accepted:
return "accepted";
case ListDisposition::same_sequence:
return "same_sequence";
case ListDisposition::unsupported_version:
return "unsupported_version";
case ListDisposition::untrusted:
return "untrusted";
case ListDisposition::stale:
return "stale";
case ListDisposition::invalid:
return "invalid";
}
return "unknown";
}
ValidatorList::ValidatorList (
ManifestCache& validatorManifests,
ManifestCache& publisherManifests,
@@ -150,8 +172,15 @@ ValidatorList::load (
JLOG (j_.warn()) << "Duplicate node identity: " << match[1];
continue;
}
publisherLists_[local].list.emplace_back (std::move(*id));
publisherLists_[local].available = true;
auto it = publisherLists_.emplace(
std::piecewise_construct,
std::forward_as_tuple(local),
std::forward_as_tuple());
// Config listed keys never expire
if (it.second)
it.first->second.expiration = TimeKeeper::time_point::max();
it.first->second.list.emplace_back(std::move(*id));
it.first->second.available = true;
++count;
}
@@ -169,7 +198,7 @@ ValidatorList::applyList (
std::string const& signature,
std::uint32_t version)
{
if (version != 1)
if (version != requiredListVersion)
return ListDisposition::unsupported_version;
boost::unique_lock<boost::shared_mutex> lock{mutex_};
@@ -184,7 +213,8 @@ ValidatorList::applyList (
Json::Value const& newList = list["validators"];
publisherLists_[pubKey].available = true;
publisherLists_[pubKey].sequence = list["sequence"].asUInt ();
publisherLists_[pubKey].expiration = list["expiration"].asUInt ();
publisherLists_[pubKey].expiration = TimeKeeper::time_point{
TimeKeeper::duration{list["expiration"].asUInt()}};
std::vector<PublicKey>& publisherList = publisherLists_[pubKey].list;
std::vector<PublicKey> oldList = publisherList;
@@ -328,11 +358,14 @@ ValidatorList::verify (
list.isMember("expiration") && list["expiration"].isInt() &&
list.isMember("validators") && list["validators"].isArray())
{
auto const sequence = list["sequence"].asUInt ();
auto const expiration = list["expiration"].asUInt ();
if (sequence <= publisherLists_[pubKey].sequence ||
expiration <= timeKeeper_.now().time_since_epoch().count())
auto const sequence = list["sequence"].asUInt();
auto const expiration = TimeKeeper::time_point{
TimeKeeper::duration{list["expiration"].asUInt()}};
if (sequence < publisherLists_[pubKey].sequence ||
expiration <= timeKeeper_.now())
return ListDisposition::stale;
else if (sequence == publisherLists_[pubKey].sequence)
return ListDisposition::same_sequence;
}
else
{
@@ -427,6 +460,103 @@ ValidatorList::removePublisherList (PublicKey const& publisherKey)
return true;
}
boost::optional<TimeKeeper::time_point>
ValidatorList::expires() const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
boost::optional<TimeKeeper::time_point> res{boost::none};
for (auto const& p : publisherLists_)
{
// Unfetched
if (p.second.expiration == TimeKeeper::time_point{})
return boost::none;
// Earliest
if (!res || p.second.expiration < *res)
res = p.second.expiration;
}
return res;
}
Json::Value
ValidatorList::getJson() const
{
Json::Value res(Json::objectValue);
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
res[jss::validation_quorum] = static_cast<Json::UInt>(quorum());
if (auto when = expires())
{
if (*when == TimeKeeper::time_point::max())
res[jss::validator_list_expires] = "never";
else
res[jss::validator_list_expires] = to_string(*when);
}
else
res[jss::validator_list_expires] = "unknown";
// Local static keys
PublicKey local;
Json::Value& jLocalStaticKeys =
(res[jss::local_static_keys] = Json::arrayValue);
auto it = publisherLists_.find(local);
if (it != publisherLists_.end())
{
for (auto const& key : it->second.list)
jLocalStaticKeys.append(
toBase58(TokenType::TOKEN_NODE_PUBLIC, key));
}
// Publisher lists
Json::Value& jPublisherLists =
(res[jss::publisher_lists] = Json::arrayValue);
for (auto const& p : publisherLists_)
{
if(local == p.first)
continue;
Json::Value& curr = jPublisherLists.append(Json::objectValue);
curr[jss::pubkey_publisher] = strHex(p.first);
curr[jss::available] = p.second.available;
if(p.second.expiration != TimeKeeper::time_point{})
{
curr[jss::seq] = static_cast<Json::UInt>(p.second.sequence);
curr[jss::expiration] = to_string(p.second.expiration);
curr[jss::version] = requiredListVersion;
}
Json::Value& keys = (curr[jss::list] = Json::arrayValue);
for (auto const& key : p.second.list)
{
keys.append(toBase58(TokenType::TOKEN_NODE_PUBLIC, key));
}
}
// Trusted validator keys
Json::Value& jValidatorKeys =
(res[jss::trusted_validator_keys] = Json::arrayValue);
for (auto const& k : trustedKeys_)
{
jValidatorKeys.append(toBase58(TokenType::TOKEN_NODE_PUBLIC, k));
}
// signing keys
Json::Value& jSigningKeys = (res[jss::signing_keys] = Json::objectValue);
validatorManifests_.for_each_manifest(
[&jSigningKeys, this](Manifest const& manifest) {
auto it = keyListings_.find(manifest.masterKey);
if (it != keyListings_.end())
{
jSigningKeys[toBase58(
TokenType::TOKEN_NODE_PUBLIC, manifest.masterKey)] =
toBase58(TokenType::TOKEN_NODE_PUBLIC, manifest.signingKey);
}
});
return res;
}
void
ValidatorList::for_each_listed (
std::function<void(PublicKey const&, bool)> func) const

View File

@@ -17,12 +17,13 @@
*/
//==============================================================================
#include <ripple/app/misc/detail/WorkPlain.h>
#include <ripple/app/misc/detail/WorkSSL.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/misc/ValidatorSite.h>
#include <ripple/app/misc/detail/WorkPlain.h>
#include <ripple/app/misc/detail/WorkSSL.h>
#include <ripple/basics/Slice.h>
#include <ripple/json/json_reader.h>
#include <ripple/protocol/JsonFields.h>
#include <beast/core/detail/base64.hpp>
#include <boost/regex.hpp>
@@ -211,6 +212,9 @@ ValidatorSite::onSiteFetch(
JLOG (j_.warn()) <<
"Request for validator list at " <<
sites_[siteIdx].uri << " returned " << res.result_int();
sites_[siteIdx].lastRefreshStatus.emplace(
Site::Status{clock_type::now(), ListDisposition::invalid});
}
else if (! ec)
{
@@ -231,12 +235,21 @@ ValidatorSite::onSiteFetch(
body["signature"].asString(),
body["version"].asUInt());
sites_[siteIdx].lastRefreshStatus.emplace(
Site::Status{clock_type::now(), disp});
if (ListDisposition::accepted == disp)
{
JLOG (j_.debug()) <<
"Applied new validator list from " <<
sites_[siteIdx].uri;
}
else if (ListDisposition::same_sequence == disp)
{
JLOG (j_.debug()) <<
"Validator list with current sequence from " <<
sites_[siteIdx].uri;
}
else if (ListDisposition::stale == disp)
{
JLOG (j_.warn()) <<
@@ -277,10 +290,17 @@ ValidatorSite::onSiteFetch(
JLOG (j_.warn()) <<
"Unable to parse JSON response from " <<
sites_[siteIdx].uri;
sites_[siteIdx].lastRefreshStatus.emplace(
Site::Status{clock_type::now(), ListDisposition::invalid});
}
}
else
{
std::lock_guard <std::mutex> lock{sites_mutex_};
sites_[siteIdx].lastRefreshStatus.emplace(
Site::Status{clock_type::now(), ListDisposition::invalid});
JLOG (j_.warn()) <<
"Problem retrieving from " <<
sites_[siteIdx].uri <<
@@ -297,4 +317,32 @@ ValidatorSite::onSiteFetch(
cv_.notify_all();
}
Json::Value
ValidatorSite::getJson() const
{
using namespace std::chrono;
using Int = Json::Value::Int;
Json::Value jrr(Json::objectValue);
Json::Value& jSites = (jrr[jss::validator_sites] = Json::arrayValue);
{
std::lock_guard<std::mutex> lock{sites_mutex_};
for (Site const& site : sites_)
{
Json::Value& v = jSites.append(Json::objectValue);
v[jss::uri] = site.uri;
if (site.lastRefreshStatus)
{
v[jss::last_refresh_time] =
to_string(site.lastRefreshStatus->refreshed);
v[jss::last_refresh_status] =
to_string(site.lastRefreshStatus->disposition);
}
v[jss::refresh_interval_min] =
static_cast<Int>(site.refreshInterval.count());
}
}
return jrr;
}
} // ripple