Dynamize trusted validator list and quorum (RIPD-1220):

Instead of specifying a static list of trusted validators in the config
or validators file, the configuration can now include trusted validator
list publisher keys.

The trusted validator list and quorum are now reset each consensus
round using the latest validator lists and the list of recent
validations seen. The minimum validation quorum is now only
configurable via the command line.
This commit is contained in:
wilsonianb
2016-08-30 09:46:24 -07:00
committed by seelabs
parent 74977ab3db
commit e823e60ca0
42 changed files with 2482 additions and 1570 deletions

View File

@@ -17,117 +17,39 @@
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/basics/Slice.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/json_reader.h>
#include <beast/core/detail/base64.hpp>
#include <boost/regex.hpp>
namespace ripple {
ValidatorList::ValidatorList (beast::Journal j)
: j_ (j)
ValidatorList::ValidatorList (
ManifestCache& validatorManifests,
ManifestCache& publisherManifests,
TimeKeeper& timeKeeper,
beast::Journal j,
boost::optional<std::size_t> minimumQuorum)
: validatorManifests_ (validatorManifests)
, publisherManifests_ (publisherManifests)
, timeKeeper_ (timeKeeper)
, j_ (j)
, quorum_ (minimumQuorum ? *minimumQuorum : 1) // Genesis ledger quorum
, minimumQuorum_ (minimumQuorum)
{
}
boost::optional<std::string>
ValidatorList::member (PublicKey const& identity) const
ValidatorList::~ValidatorList()
{
std::lock_guard <std::mutex> sl (mutex_);
auto ret = ephemeral_.find (identity);
if (ret != ephemeral_.end())
return ret->second;
ret = permanent_.find (identity);
if (ret != permanent_.end())
return ret->second;
return boost::none;
}
bool
ValidatorList::trusted (PublicKey const& identity) const
{
return static_cast<bool> (member(identity));
}
bool
ValidatorList::insertEphemeralKey (
PublicKey const& identity,
std::string const& comment)
{
std::lock_guard <std::mutex> sl (mutex_);
if (permanent_.find (identity) != permanent_.end())
{
JLOG (j_.error()) <<
toBase58 (TokenType::TOKEN_NODE_PUBLIC, identity) <<
": ephemeral key exists in permanent table!";
return false;
}
return ephemeral_.emplace (identity, comment).second;
}
bool
ValidatorList::removeEphemeralKey (
PublicKey const& identity)
{
std::lock_guard <std::mutex> sl (mutex_);
return ephemeral_.erase (identity);
}
bool
ValidatorList::insertPermanentKey (
PublicKey const& identity,
std::string const& comment)
{
std::lock_guard <std::mutex> sl (mutex_);
if (ephemeral_.find (identity) != ephemeral_.end())
{
JLOG (j_.error()) <<
toBase58 (TokenType::TOKEN_NODE_PUBLIC, identity) <<
": permanent key exists in ephemeral table!";
return false;
}
return permanent_.emplace (identity, comment).second;
}
bool
ValidatorList::removePermanentKey (
PublicKey const& identity)
{
std::lock_guard <std::mutex> sl (mutex_);
return permanent_.erase (identity);
}
std::size_t
ValidatorList::size () const
{
std::lock_guard <std::mutex> sl (mutex_);
return permanent_.size () + ephemeral_.size ();
}
void
ValidatorList::for_each (
std::function<void(PublicKey const&, std::string const&, bool)> func) const
{
std::lock_guard <std::mutex> sl (mutex_);
for (auto const& v : permanent_)
func (v.first, v.second, false);
for (auto const& v : ephemeral_)
func (v.first, v.second, true);
}
bool
ValidatorList::load (
Section const& validators)
PublicKey const& localSigningKey,
std::vector<std::string> const& configKeys,
std::vector<std::string> const& publisherKeys)
{
static boost::regex const re (
"[[:space:]]*" // skip leading whitespace
@@ -141,12 +63,61 @@ ValidatorList::load (
")?" // end optional comment block
);
boost::unique_lock<boost::shared_mutex> read_lock{mutex_};
JLOG (j_.debug()) <<
"Loading configured validators";
"Loading configured trusted validator list publisher keys";
std::size_t count = 0;
for (auto key : publisherKeys)
{
JLOG (j_.trace()) <<
"Processing '" << key << "'";
for (auto const& n : validators.values ())
auto const ret = strUnHex (key);
if (! ret.second || ! ret.first.size ())
{
JLOG (j_.error()) <<
"Invalid validator list publisher key: " << key;
return false;
}
auto id = PublicKey(Slice{ ret.first.data (), ret.first.size() });
if (validatorManifests_.revoked (id))
{
JLOG (j_.warn()) <<
"Configured validator list publisher key is revoked: " << key;
continue;
}
if (publisherLists_.count(id))
{
JLOG (j_.warn()) <<
"Duplicate validator list publisher key: " << key;
continue;
}
publisherLists_[id].available = false;
++count;
}
JLOG (j_.debug()) <<
"Loaded " << count << " keys";
localPubKey_ = validatorManifests_.getMasterKey (localSigningKey);
// Treat local validator key as though it was listed in the config
if (localPubKey_.size())
keyListings_.insert ({ localPubKey_, 1 });
JLOG (j_.debug()) <<
"Loading configured validator keys";
count = 0;
PublicKey local;
for (auto const& n : configKeys)
{
JLOG (j_.trace()) <<
"Processing '" << n << "'";
@@ -165,20 +136,23 @@ ValidatorList::load (
if (!id)
{
JLOG (j_.error()) <<
"Invalid node identity: " << match[1];
JLOG (j_.error()) << "Invalid node identity: " << match[1];
return false;
}
if (trusted (*id))
// Skip local key which was already added
if (*id == localPubKey_ || *id == localSigningKey)
continue;
auto ret = keyListings_.insert ({*id, 1});
if (! ret.second)
{
JLOG (j_.warn()) <<
"Duplicate node identity: " << match[1];
JLOG (j_.warn()) << "Duplicate node identity: " << match[1];
continue;
}
if (insertPermanentKey(*id, trim_whitespace (match[2])))
++count;
publisherLists_[local].list.emplace_back (std::move(*id));
publisherLists_[local].available = true;
++count;
}
JLOG (j_.debug()) <<
@@ -187,4 +161,244 @@ ValidatorList::load (
return true;
}
ListDisposition
ValidatorList::applyList (
std::string const& manifest,
std::string const& blob,
std::string const& signature,
std::uint32_t version)
{
if (version != 1)
return ListDisposition::unsupported_version;
boost::unique_lock<boost::shared_mutex> lock{mutex_};
Json::Value list;
PublicKey pubKey;
auto const result = verify (list, pubKey, manifest, blob, signature);
if (result != ListDisposition::accepted)
return result;
// Update publisher's list
Json::Value const& newList = list["validators"];
publisherLists_[pubKey].available = true;
publisherLists_[pubKey].sequence = list["sequence"].asUInt ();
publisherLists_[pubKey].expiration = list["expiration"].asUInt ();
std::vector<PublicKey>& publisherList = publisherLists_[pubKey].list;
std::vector<PublicKey> oldList = publisherList;
publisherList.clear ();
publisherList.reserve (newList.size ());
for (auto const& val : newList)
{
if (val.isObject () &&
val.isMember ("validation_public_key") &&
val["validation_public_key"].isString ())
{
std::pair<Blob, bool> ret (strUnHex (
val["validation_public_key"].asString ()));
if (! ret.second || ! ret.first.size ())
{
JLOG (j_.error()) <<
"Invalid node identity: " <<
val["validation_public_key"].asString ();
}
else
{
publisherList.push_back (
PublicKey(Slice{ ret.first.data (), ret.first.size() }));
}
}
}
// Update keyListings_ for added and removed keys
std::sort (
publisherList.begin (),
publisherList.end ());
auto iNew = publisherList.begin ();
auto iOld = oldList.begin ();
while (iNew != publisherList.end () ||
iOld != oldList.end ())
{
if (iOld == oldList.end () ||
(iNew != publisherList.end () &&
*iNew < *iOld))
{
// Increment list count for added keys
++keyListings_[*iNew];
++iNew;
}
else if (iNew == publisherList.end () ||
(iOld != oldList.end () && *iOld < *iNew))
{
// Decrement list count for removed keys
if (keyListings_[*iOld] == 1)
keyListings_.erase (*iOld);
else
--keyListings_[*iOld];
++iOld;
}
else
{
++iNew;
++iOld;
}
}
if (publisherList.empty())
{
JLOG (j_.warn()) <<
"No validator keys included in valid list";
}
return ListDisposition::accepted;
}
ListDisposition
ValidatorList::verify (
Json::Value& list,
PublicKey& pubKey,
std::string const& manifest,
std::string const& blob,
std::string const& signature)
{
auto m = Manifest::make_Manifest (beast::detail::base64_decode(manifest));
if (! m || ! publisherLists_.count (m->masterKey))
return ListDisposition::untrusted;
pubKey = m->masterKey;
auto const revoked = m->revoked();
auto const result = publisherManifests_.applyManifest (
std::move(*m));
if (revoked && result == ManifestDisposition::accepted)
{
removePublisherList (pubKey);
publisherLists_.erase (pubKey);
}
if (revoked || result == ManifestDisposition::invalid)
return ListDisposition::untrusted;
auto const sig = strUnHex(signature);
auto const data = beast::detail::base64_decode (blob);
if (! sig.second ||
! ripple::verify (
publisherManifests_.getSigningKey(pubKey),
makeSlice(data),
makeSlice(sig.first)))
return ListDisposition::invalid;
Json::Reader r;
if (! r.parse (data, list))
return ListDisposition::invalid;
if (list.isMember("sequence") && list["sequence"].isInt() &&
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())
return ListDisposition::stale;
}
else
{
return ListDisposition::invalid;
}
return ListDisposition::accepted;
}
bool
ValidatorList::listed (
PublicKey const& identity) const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
auto const pubKey = validatorManifests_.getMasterKey (identity);
return keyListings_.find (pubKey) != keyListings_.end ();
}
bool
ValidatorList::trusted (PublicKey const& identity) const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
auto const pubKey = validatorManifests_.getMasterKey (identity);
return trustedKeys_.find (pubKey) != trustedKeys_.end();
}
boost::optional<PublicKey>
ValidatorList::getListedKey (
PublicKey const& identity) const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
auto const pubKey = validatorManifests_.getMasterKey (identity);
if (keyListings_.find (pubKey) != keyListings_.end ())
return pubKey;
return boost::none;
}
boost::optional<PublicKey>
ValidatorList::getTrustedKey (PublicKey const& identity) const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
auto const pubKey = validatorManifests_.getMasterKey (identity);
if (trustedKeys_.find (pubKey) != trustedKeys_.end())
return pubKey;
return boost::none;
}
bool
ValidatorList::trustedPublisher (PublicKey const& identity) const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
return identity.size() && publisherLists_.count (identity);
}
bool
ValidatorList::removePublisherList (PublicKey const& publisherKey)
{
auto const iList = publisherLists_.find (publisherKey);
if (iList == publisherLists_.end ())
return false;
JLOG (j_.debug()) <<
"Removing validator list for revoked publisher " <<
toBase58(TokenType::TOKEN_NODE_PUBLIC, publisherKey);
for (auto const& val : iList->second.list)
{
auto const& iVal = keyListings_.find (val);
if (iVal == keyListings_.end())
continue;
if (iVal->second <= 1)
keyListings_.erase (iVal);
else
--iVal->second;
}
return true;
}
void
ValidatorList::for_each_listed (
std::function<void(PublicKey const&, bool)> func) const
{
boost::shared_lock<boost::shared_mutex> read_lock{mutex_};
for (auto const& v : keyListings_)
func (v.first, trusted(v.first));
}
} // ripple