Update validations on UNL change (RIPD-1566):

Change the trust status of existing validations based when nodes are
added or removed from the UNL.
This commit is contained in:
Brad Chase
2018-02-13 13:32:16 -05:00
committed by Mike Ellery
parent 8b909d5c17
commit 20defb4844
24 changed files with 764 additions and 389 deletions

View File

@@ -81,7 +81,7 @@ RCLConsensus::Adaptor::Adaptor(
, localTxs_(localTxs)
, inboundTransactions_{inboundTransactions}
, j_(journal)
, nodeID_{calcNodeID(app.nodeIdentity().first)}
, nodeID_{validatorKeys.nodeID}
, valPublic_{validatorKeys.publicKey}
, valSecret_{validatorKeys.secretKey}
{
@@ -837,6 +837,7 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing)
ledger.id(),
validationTime,
valPublic_,
nodeID_,
proposing /* full if proposed */);
v->setFieldU32(sfLedgerSequence, ledger.seq());
@@ -980,10 +981,11 @@ void
RCLConsensus::startRound(
NetClock::time_point const& now,
RCLCxLedger::ID const& prevLgrId,
RCLCxLedger const& prevLgr)
RCLCxLedger const& prevLgr,
hash_set<NodeID> const& nowUntrusted)
{
ScopedLockType _{mutex_};
consensus_.startRound(
now, prevLgrId, prevLgr, adaptor_.preStartRound(prevLgr));
now, prevLgrId, prevLgr, nowUntrusted, adaptor_.preStartRound(prevLgr));
}
}

View File

@@ -427,7 +427,8 @@ public:
startRound(
NetClock::time_point const& now,
RCLCxLedger::ID const& prevLgrId,
RCLCxLedger const& prevLgr);
RCLCxLedger const& prevLgr,
hash_set<NodeID> const& nowUntrusted);
//! @see Consensus::timerEntry
void

View File

@@ -172,7 +172,7 @@ RCLValidationsAdaptor::onStale(RCLValidation&& v)
}
void
RCLValidationsAdaptor::flush(hash_map<PublicKey, RCLValidation>&& remaining)
RCLValidationsAdaptor::flush(hash_map<NodeID, RCLValidation>&& remaining)
{
bool anyNew = false;
{
@@ -317,7 +317,7 @@ handleNewValidation(Application& app,
// masterKey is seated only if validator is trusted or listed
if (masterKey)
{
ValStatus const outcome = validations.add(*masterKey, val);
ValStatus const outcome = validations.add(calcNodeID(*masterKey), val);
if(j.debug())
dmp(j.debug(), to_string(outcome));
@@ -327,13 +327,6 @@ handleNewValidation(Application& app,
dmp(j.warn(),
"already validated sequence at or past " + to_string(seq));
}
else if(outcome == ValStatus::repeatID && j.warn())
{
auto const seq = val->getFieldU32(sfLedgerSequence);
dmp(j.warn(),
"already validated ledger with same id but different seq "
"than" + to_string(seq));
}
if (val->isTrusted() && outcome == ValStatus::current)
{

View File

@@ -101,7 +101,19 @@ public:
return val_->isTrusted();
}
/// Whether the validatioon is full (not-partial)
void
setTrusted()
{
val_->setTrusted();
}
void
setUntrusted()
{
val_->setUntrusted();
}
/// Whether the validation is full (not-partial)
bool
full() const
{
@@ -214,7 +226,7 @@ public:
@param remaining The remaining validations to flush
*/
void
flush(hash_map<PublicKey, RCLValidation>&& remaining);
flush(hash_map<NodeID, RCLValidation>&& remaining);
/** Attempt to acquire the ledger with given id from the network */
boost::optional<RCLValidatedLedger>

View File

@@ -293,8 +293,7 @@ public:
// Ledger proposal/close functions.
void processTrustedProposal (
RCLCxPeerPos proposal,
std::shared_ptr<protocol::TMProposeSet> set,
NodeID const &node) override;
std::shared_ptr<protocol::TMProposeSet> set) override;
bool recvValidation (
STValidation::ref val, std::string const& source) override;
@@ -1434,13 +1433,17 @@ bool NetworkOPsImp::beginConsensus (uint256 const& networkClosed)
assert (closingInfo.parentHash ==
m_ledgerMaster.getClosedLedger()->info().hash);
app_.validators().onConsensusStart (
app_.getValidations().getCurrentPublicKeys ());
TrustChanges const changes = app_.validators().updateTrusted(
app_.getValidations().getCurrentNodeIDs());
mConsensus.startRound (
if (!changes.added.empty() || !changes.removed.empty())
app_.getValidations().trustChanged(changes.added, changes.removed);
mConsensus.startRound(
app_.timeKeeper().closeTime(),
networkClosed,
prevLedger);
prevLedger,
changes.removed);
JLOG(m_journal.debug()) << "Initiating consensus engine";
return true;
@@ -1453,8 +1456,7 @@ uint256 NetworkOPsImp::getConsensusLCL ()
void NetworkOPsImp::processTrustedProposal (
RCLCxPeerPos peerPos,
std::shared_ptr<protocol::TMProposeSet> set,
NodeID const& node)
std::shared_ptr<protocol::TMProposeSet> set)
{
if (mConsensus.peerProposal(
app_.timeKeeper().closeTime(), peerPos))

View File

@@ -153,8 +153,7 @@ public:
// ledger proposal/close functions
virtual void processTrustedProposal (RCLCxPeerPos peerPos,
std::shared_ptr<protocol::TMProposeSet> set,
NodeID const& node) = 0;
std::shared_ptr<protocol::TMProposeSet> set) = 0;
virtual bool recvValidation (STValidation::ref val,
std::string const& source) = 0;

View File

@@ -23,6 +23,7 @@
#include <ripple/beast/utility/Journal.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/UintTypes.h>
#include <string>
namespace ripple {
@@ -37,6 +38,7 @@ class ValidatorKeys
public:
PublicKey publicKey;
SecretKey secretKey;
NodeID nodeID;
std::string manifest;
ValidatorKeys(Config const& config, beast::Journal j);

View File

@@ -60,6 +60,14 @@ enum class ListDisposition
std::string
to_string(ListDisposition disposition);
/** Changes in trusted nodes after updating validator list
*/
struct TrustChanges
{
hash_set<NodeID> added;
hash_set<NodeID> removed;
};
/**
Trusted Validators List
-----------------------
@@ -202,22 +210,23 @@ public:
std::string const& signature,
std::uint32_t version);
/** Update trusted keys
/** Update trusted nodes
Reset the trusted keys based on latest manifests, received validations,
Reset the trusted nodes based on latest manifests, received validations,
and lists.
@param seenValidators Set of public keys used to sign recently
received validations
@param seenValidators Set of NodeIDs of validators that have signed
recently received validations
@return TrustedKeyChanges instance with newly trusted or untrusted
node identities.
@par Thread Safety
May be called concurrently
*/
template<class KeySet>
void
onConsensusStart (
KeySet const& seenValidators);
TrustChanges
updateTrusted (hash_set<NodeID> const& seenValidators);
/** Get quorum value for current trusted key set
@@ -390,133 +399,6 @@ private:
calculateMinimumQuorum (
std::size_t nListedKeys, bool unlistedLocal=false);
};
//------------------------------------------------------------------------------
template<class KeySet>
void
ValidatorList::onConsensusStart (
KeySet const& seenValidators)
{
boost::unique_lock<boost::shared_mutex> lock{mutex_};
// Check that lists from all configured publishers are available
bool allListsAvailable = true;
for (auto const& list : publisherLists_)
{
// Remove any expired published lists
if (TimeKeeper::time_point{} < list.second.expiration &&
list.second.expiration <= timeKeeper_.now())
removePublisherList(list.first);
if (! list.second.available)
allListsAvailable = false;
}
std::multimap<std::size_t, PublicKey> rankedKeys;
bool localKeyListed = false;
// "Iterate" the listed keys in random order so that the rank of multiple
// keys with the same number of listings is not deterministic
std::vector<std::size_t> indexes (keyListings_.size());
std::iota (indexes.begin(), indexes.end(), 0);
std::shuffle (indexes.begin(), indexes.end(), crypto_prng());
for (auto const& index : indexes)
{
auto const& val = std::next (keyListings_.begin(), index);
if (validatorManifests_.revoked (val->first))
continue;
if (val->first == localPubKey_)
{
localKeyListed = val->second > 1;
rankedKeys.insert (
std::pair<std::size_t,PublicKey>(
std::numeric_limits<std::size_t>::max(), localPubKey_));
}
// If the total number of validators is too small, or
// no validations are being received, use all validators.
// Otherwise, do not use validators whose validations aren't
// being received.
else if (keyListings_.size() < MINIMUM_RESIZEABLE_UNL ||
seenValidators.empty() ||
seenValidators.find (val->first) != seenValidators.end ())
{
rankedKeys.insert (
std::pair<std::size_t,PublicKey>(val->second, val->first));
}
}
// This minimum quorum guarantees safe overlap with the trusted sets of
// other nodes using the same set of published lists.
std::size_t quorum = calculateMinimumQuorum (keyListings_.size(),
localPubKey_.size() && !localKeyListed);
JLOG (j_.debug()) <<
rankedKeys.size() << " of " << keyListings_.size() <<
" listed validators eligible for inclusion in the trusted set";
auto size = rankedKeys.size();
// Require 80% quorum if there are lots of validators.
if (rankedKeys.size() > BYZANTINE_THRESHOLD)
{
// Use all eligible keys if there is only one trusted list
if (publisherLists_.size() == 1 ||
keyListings_.size() < MINIMUM_RESIZEABLE_UNL)
{
// Try to raise the quorum to at least 80% of the trusted set
quorum = std::max(quorum, size - size / 5);
}
else
{
// Reduce the trusted set size so that the quorum represents
// at least 80%
size = quorum * 1.25;
}
}
if (minimumQuorum_ && seenValidators.size() < quorum)
{
quorum = *minimumQuorum_;
JLOG (j_.warn())
<< "Using unsafe quorum of "
<< quorum_
<< " as specified in the command line";
}
// Do not use achievable quorum until lists from all configured
// publishers are available
else if (! allListsAvailable)
quorum = std::numeric_limits<std::size_t>::max();
trustedKeys_.clear();
quorum_ = quorum;
for (auto const& val : boost::adaptors::reverse (rankedKeys))
{
if (size <= trustedKeys_.size())
break;
trustedKeys_.insert (val.second);
}
JLOG (j_.debug()) <<
"Using quorum of " << quorum_ << " for new set of " <<
trustedKeys_.size() << " trusted validators";
if (trustedKeys_.size() < quorum_)
{
JLOG (j_.warn()) <<
"New quorum of " << quorum_ <<
" exceeds the number of trusted validators (" <<
trustedKeys_.size() << ")";
}
}
} // ripple
#endif

View File

@@ -47,7 +47,6 @@ ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j)
KeyType::secp256k1, token->validationSecret);
auto const m = Manifest::make_Manifest(
beast::detail::base64_decode(token->manifest));
if (! m || pk != m->signingKey)
{
configInvalid_ = true;
@@ -58,6 +57,7 @@ ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j)
{
secretKey = token->validationSecret;
publicKey = pk;
nodeID = calcNodeID(m->masterKey);
manifest = std::move(token->manifest);
}
}
@@ -82,6 +82,7 @@ ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j)
{
secretKey = generateSecretKey(KeyType::secp256k1, *seed);
publicKey = derivePublicKey(KeyType::secp256k1, secretKey);
nodeID = calcNodeID(publicKey);
}
}
}

View File

@@ -597,4 +597,140 @@ ValidatorList::calculateMinimumQuorum (
return nListedKeys * 2/3 + 1;
}
TrustChanges
ValidatorList::updateTrusted(hash_set<NodeID> const& seenValidators)
{
boost::unique_lock<boost::shared_mutex> lock{mutex_};
// Check that lists from all configured publishers are available
bool allListsAvailable = true;
for (auto const& list : publisherLists_)
{
// Remove any expired published lists
if (TimeKeeper::time_point{} < list.second.expiration &&
list.second.expiration <= timeKeeper_.now())
removePublisherList(list.first);
if (! list.second.available)
allListsAvailable = false;
}
std::multimap<std::size_t, PublicKey> rankedKeys;
bool localKeyListed = false;
// "Iterate" the listed keys in random order so that the rank of multiple
// keys with the same number of listings is not deterministic
std::vector<std::size_t> indexes (keyListings_.size());
std::iota (indexes.begin(), indexes.end(), 0);
std::shuffle (indexes.begin(), indexes.end(), crypto_prng());
for (auto const& index : indexes)
{
auto const& val = std::next (keyListings_.begin(), index);
if (validatorManifests_.revoked (val->first))
continue;
if (val->first == localPubKey_)
{
localKeyListed = val->second > 1;
rankedKeys.insert (
std::pair<std::size_t,PublicKey>(
std::numeric_limits<std::size_t>::max(), localPubKey_));
}
// If the total number of validators is too small, or
// no validations are being received, use all validators.
// Otherwise, do not use validators whose validations aren't
// being received.
else if (
keyListings_.size() < MINIMUM_RESIZEABLE_UNL ||
seenValidators.empty() ||
seenValidators.find(calcNodeID(val->first)) != seenValidators.end())
{
rankedKeys.insert (
std::pair<std::size_t,PublicKey>(val->second, val->first));
}
}
// This minimum quorum guarantees safe overlap with the trusted sets of
// other nodes using the same set of published lists.
std::size_t quorum = calculateMinimumQuorum (keyListings_.size(),
localPubKey_.size() && !localKeyListed);
JLOG (j_.debug()) <<
rankedKeys.size() << " of " << keyListings_.size() <<
" listed validators eligible for inclusion in the trusted set";
auto size = rankedKeys.size();
// Require 80% quorum if there are lots of validators.
if (rankedKeys.size() > BYZANTINE_THRESHOLD)
{
// Use all eligible keys if there is only one trusted list
if (publisherLists_.size() == 1 ||
keyListings_.size() < MINIMUM_RESIZEABLE_UNL)
{
// Try to raise the quorum to at least 80% of the trusted set
quorum = std::max(quorum, size - size / 5);
}
else
{
// Reduce the trusted set size so that the quorum represents
// at least 80%
size = quorum * 1.25;
}
}
if (minimumQuorum_ && seenValidators.size() < quorum)
{
quorum = *minimumQuorum_;
JLOG (j_.warn())
<< "Using unsafe quorum of "
<< quorum_
<< " as specified in the command line";
}
// Do not use achievable quorum until lists from all configured
// publishers are available
else if (! allListsAvailable)
quorum = std::numeric_limits<std::size_t>::max();
TrustChanges trustChanges;
{
hash_set<PublicKey> newTrustedKeys;
for (auto const& val : boost::adaptors::reverse(rankedKeys))
{
if (size <= newTrustedKeys.size())
break;
newTrustedKeys.insert(val.second);
if (trustedKeys_.erase(val.second) == 0)
trustChanges.added.insert(calcNodeID(val.second));
}
for (auto const& k : trustedKeys_)
trustChanges.removed.insert(calcNodeID(k));
trustedKeys_ = std::move(newTrustedKeys);
}
quorum_ = quorum;
JLOG(j_.debug()) << "Using quorum of " << quorum_ << " for new set of "
<< trustedKeys_.size() << " trusted validators ("
<< trustChanges.added.size() << " added, "
<< trustChanges.removed.size() << " removed)";
if (trustedKeys_.size() < quorum_)
{
JLOG (j_.warn()) <<
"New quorum of " << quorum_ <<
" exceeds the number of trusted validators (" <<
trustedKeys_.size() << ")";
}
return trustChanges;
}
} // ripple

View File

@@ -337,6 +337,7 @@ public:
@param now The network adjusted time
@param prevLedgerID the ID of the last ledger
@param prevLedger The last ledger
@param nowUntrusted ID of nodes that are newly untrusted this round
@param proposing Whether we want to send proposals to peers this round.
@note @b prevLedgerID is not required to the ID of @b prevLedger since
@@ -347,6 +348,7 @@ public:
NetClock::time_point const& now,
typename Ledger_t::ID const& prevLedgerID,
Ledger_t prevLedger,
hash_set<NodeID_t> const & nowUntrusted,
bool proposing);
/** A peer has proposed a new position, adjust our tracking.
@@ -552,7 +554,7 @@ private:
hash_map<NodeID_t, PeerPosition_t> currPeerPositions_;
// Recently received peer positions, available when transitioning between
// ledgers or roundss
// ledgers or rounds
hash_map<NodeID_t, std::deque<PeerPosition_t>> recentPeerPositions_;
// The number of proposers who participated in the last consensus round
@@ -583,6 +585,7 @@ Consensus<Adaptor>::startRound(
NetClock::time_point const& now,
typename Ledger_t::ID const& prevLedgerID,
Ledger_t prevLedger,
hash_set<NodeID_t> const& nowUntrusted,
bool proposing)
{
if (firstRound_)
@@ -597,6 +600,9 @@ Consensus<Adaptor>::startRound(
prevCloseTime_ = rawCloseTimes_.self;
}
for(NodeID_t const& n : nowUntrusted)
recentPeerPositions_.erase(n);
ConsensusMode startMode =
proposing ? ConsensusMode::proposing : ConsensusMode::observing;

View File

@@ -151,8 +151,6 @@ isCurrent(
enum class ValStatus {
/// This was a new validation and was added
current,
/// Already had this validation for this ID but different seq
repeatID,
/// Not current or was older than current from this node
stale,
/// A validation violates the increasing seq requirement
@@ -166,8 +164,6 @@ to_string(ValStatus m)
{
case ValStatus::current:
return "current";
case ValStatus::repeatID:
return "repeatID";
case ValStatus::stale:
return "stale";
case ValStatus::badSeq:
@@ -204,6 +200,7 @@ to_string(ValStatus m)
struct Validation
{
using NodeID = ...;
using NodeKey = ...;
// Ledger ID associated with this validation
@@ -225,9 +222,19 @@ to_string(ValStatus m)
// arrived
bool trusted() const;
// Set the validation as trusted
void setTrusted();
// Set the validation as untrusted
void setUntrusted();
// Whether this is a full or partial validation
bool full() const;
// Identifier for this node that remains fixed even when rotating signing
// keys
NodeID nodeID() const;
implementation_specific_t
unwrap() -> return the implementation-specific type being wrapped
@@ -246,7 +253,7 @@ to_string(ValStatus m)
void onStale(Validation && );
// Flush the remaining validations (typically done on shutdown)
void flush(hash_map<NodeKey,Validation> && remaining);
void flush(hash_map<NodeID,Validation> && remaining);
// Return the current network time (used to determine staleness)
NetClock::time_point now() const;
@@ -268,7 +275,7 @@ class Validations
using Ledger = typename Adaptor::Ledger;
using ID = typename Ledger::ID;
using Seq = typename Ledger::Seq;
using NodeKey = typename Validation::NodeKey;
using NodeID = typename Validation::NodeID;
using WrappedValidationType = std::decay_t<
std::result_of_t<decltype (&Validation::unwrap)(Validation)>>;
@@ -279,18 +286,18 @@ class Validations
mutable Mutex mutex_;
// Validations from currently listed and trusted nodes (partial and full)
hash_map<NodeKey, Validation> current_;
hash_map<NodeID, Validation> current_;
// Used to enforce the largest validation invariant for the local node
SeqEnforcer<Seq> localSeqEnforcer_;
// Sequence of the largest validation received from each node
hash_map<NodeKey, SeqEnforcer<Seq>> seqEnforcers_;
hash_map<NodeID, SeqEnforcer<Seq>> seqEnforcers_;
//! Validations from listed nodes, indexed by ledger id (partial and full)
beast::aged_unordered_map<
ID,
hash_map<NodeKey, Validation>,
hash_map<NodeID, Validation>,
std::chrono::steady_clock,
beast::uhash<>>
byLedger_;
@@ -300,10 +307,10 @@ class Validations
// Last (validated) ledger successfully acquired. If in this map, it is
// accounted for in the trie.
hash_map<NodeKey, Ledger> lastLedger_;
hash_map<NodeID, Ledger> lastLedger_;
// Set of ledgers being acquired from the network
hash_map<std::pair<Seq,ID>, hash_set<NodeKey>> acquiring_;
hash_map<std::pair<Seq,ID>, hash_set<NodeID>> acquiring_;
// Parameters to determine validation staleness
ValidationParms const parms_;
@@ -315,23 +322,23 @@ class Validations
private:
// Remove support of a validated ledger
void
removeTrie(ScopedLock const&, NodeKey const& key, Validation const& val)
removeTrie(ScopedLock const&, NodeID const& nodeID, Validation const& val)
{
{
auto it = acquiring_.find(std::make_pair(val.seq(), val.ledgerID()));
if (it != acquiring_.end())
{
it->second.erase(key);
it->second.erase(nodeID);
if (it->second.empty())
acquiring_.erase(it);
}
}
{
auto it = lastLedger_.find(key);
auto it = lastLedger_.find(nodeID);
if (it != lastLedger_.end() && it->second.id() == val.ledgerID())
{
trie_.remove(it->second);
lastLedger_.erase(key);
lastLedger_.erase(nodeID);
}
}
}
@@ -345,8 +352,8 @@ private:
if (boost::optional<Ledger> ledger =
adaptor_.acquire(it->first.second))
{
for (NodeKey const& key : it->second)
updateTrie(lock, key, *ledger);
for (NodeID const& nodeID : it->second)
updateTrie(lock, nodeID, *ledger);
it = acquiring_.erase(it);
}
@@ -357,9 +364,9 @@ private:
// Update the trie to reflect a new validated ledger
void
updateTrie(ScopedLock const&, NodeKey const& key, Ledger ledger)
updateTrie(ScopedLock const&, NodeID const& nodeID, Ledger ledger)
{
auto ins = lastLedger_.emplace(key, ledger);
auto ins = lastLedger_.emplace(nodeID, ledger);
if (!ins.second)
{
trie_.remove(ins.first->second);
@@ -376,16 +383,16 @@ private:
node remains.
@param lock Existing lock of mutex_
@param key The master public key identifying the validating node
@param nodeID The node identifier of the validating node
@param val The trusted validation issued by the node
@param prior If not none, the last current validated ledger Seq,ID of key
*/
void
updateTrie(
ScopedLock const& lock,
NodeKey const& key,
NodeID const& nodeID,
Validation const& val,
boost::optional<std::pair<Seq,ID>> prior)
boost::optional<std::pair<Seq, ID>> prior)
{
assert(val.trusted());
@@ -395,7 +402,7 @@ private:
auto it = acquiring_.find(*prior);
if (it != acquiring_.end())
{
it->second.erase(key);
it->second.erase(nodeID);
if (it->second.empty())
acquiring_.erase(it);
}
@@ -403,20 +410,20 @@ private:
checkAcquired(lock);
std::pair<Seq,ID> valPair{val.seq(),val.ledgerID()};
std::pair<Seq, ID> valPair{val.seq(), val.ledgerID()};
auto it = acquiring_.find(valPair);
if(it != acquiring_.end())
if (it != acquiring_.end())
{
it->second.insert(key);
it->second.insert(nodeID);
}
else
{
if (boost::optional<Ledger> ledger = adaptor_.acquire(val.ledgerID()))
updateTrie(lock, key, *ledger);
if (boost::optional<Ledger> ledger =
adaptor_.acquire(val.ledgerID()))
updateTrie(lock, nodeID, *ledger);
else
acquiring_[valPair].insert(key);
acquiring_[valPair].insert(nodeID);
}
}
/** Use the trie for a calculation
@@ -448,7 +455,7 @@ private:
@param lock Existing lock of mutex_
@param pre Invokable with signature (std::size_t) called prior to
looping.
@param f Invokable with signature (NodeKey const &, Validations const &)
@param f Invokable with signature (NodeID const &, Validations const &)
for each current validation.
@note The invokable `pre` is called _prior_ to checking for staleness
@@ -489,7 +496,7 @@ private:
@param lock Existing lock on mutex_
@param ledgerID The identifier of the ledger
@param pre Invokable with signature(std::size_t)
@param f Invokable with signature (NodeKey const &, Validation const &)
@param f Invokable with signature (NodeID const &, Validation const &)
@note The invokable `pre` is called prior to iterating validations. The
argument is the number of times `f` will be called.
@@ -514,7 +521,7 @@ private:
public:
/** Constructor
@param p ValidationParms to control staleness/expiration of validaitons
@param p ValidationParms to control staleness/expiration of validations
@param c Clock to use for expiring validations stored by ledger
@param ts Parameters for constructing Adaptor instance
*/
@@ -561,15 +568,12 @@ public:
Attempt to add a new validation.
@param key The master key associated with this validation
@param nodeID The identity of the node issuing this validation
@param val The validation to store
@return The outcome
@note The provided key may differ from the validation's key()
member if the validator is using ephemeral signing keys.
*/
ValStatus
add(NodeKey const& key, Validation const& val)
add(NodeID const& nodeID, Validation const& val)
{
if (!isCurrent(parms_, adaptor_.now(), val.signTime(), val.seenTime()))
return ValStatus::stale;
@@ -580,17 +584,16 @@ public:
// Check that validation sequence is greater than any non-expired
// validations sequence from that validator
auto const now = byLedger_.clock().now();
SeqEnforcer<Seq>& enforcer = seqEnforcers_[key];
SeqEnforcer<Seq>& enforcer = seqEnforcers_[nodeID];
if (!enforcer(now, val.seq(), parms_))
return ValStatus::badSeq;
// This validation is a repeat if we already have
// one with the same id for this key
auto const ret = byLedger_[val.ledgerID()].emplace(key, val);
if (!ret.second && ret.first->second.key() == val.key())
return ValStatus::repeatID;
// Use insert_or_assign when C++17 supported
auto ret = byLedger_[val.ledgerID()].emplace(nodeID, val);
if (!ret.second)
ret.first->second = val;
auto const ins = current_.emplace(key, val);
auto const ins = current_.emplace(nodeID, val);
if (!ins.second)
{
// Replace existing only if this one is newer
@@ -601,14 +604,14 @@ public:
adaptor_.onStale(std::move(oldVal));
ins.first->second = val;
if (val.trusted())
updateTrie(lock, key, val, old);
updateTrie(lock, nodeID, val, old);
}
else
return ValStatus::stale;
}
else if (val.trusted())
{
updateTrie(lock, key, val, boost::none);
updateTrie(lock, nodeID, val, boost::none);
}
}
return ValStatus::current;
@@ -626,6 +629,50 @@ public:
beast::expire(byLedger_, parms_.validationSET_EXPIRES);
}
/** Update trust status of validations
Updates the trusted status of known validations to account for nodes
that have been added or removed from the UNL. This also updates the trie
to ensure only currently trusted nodes' validations are used.
@param added Identifiers of nodes that are now trusted
@param removed Identifiers of nodes that are no longer trusted
*/
void
trustChanged(hash_set<NodeID> const& added, hash_set<NodeID> const& removed)
{
ScopedLock lock{mutex_};
for (auto& it : current_)
{
if (added.count(it.first))
{
it.second.setTrusted();
updateTrie(lock, it.first, it.second, boost::none);
}
else if (removed.count(it.first))
{
it.second.setUntrusted();
removeTrie(lock, it.first, it.second);
}
}
for (auto& it : byLedger_)
{
for (auto& nodeVal : it.second)
{
if (added.count(nodeVal.first))
{
nodeVal.second.setTrusted();
}
else if (removed.count(nodeVal.first))
{
nodeVal.second.setUntrusted();
}
}
}
}
Json::Value
getJsonTrie() const
{
@@ -663,10 +710,10 @@ public:
acquiring_.end(),
[](auto const& a, auto const& b) {
std::pair<Seq, ID> const& aKey = a.first;
typename hash_set<NodeKey>::size_type const& aSize =
typename hash_set<NodeID>::size_type const& aSize =
a.second.size();
std::pair<Seq, ID> const& bKey = b.first;
typename hash_set<NodeKey>::size_type const& bSize =
typename hash_set<NodeID>::size_type const& bSize =
b.second.size();
// order by number of trusted peers validating that ledger
// break ties with ledger ID
@@ -764,7 +811,7 @@ public:
@param ledger The working ledger
@param ledgerID The preferred ledger
@return The number of current trusted validators working on a descendent
@return The number of current trusted validators working on a descendant
of the preferred ledger
@note If ledger.id() != ledgerID, only counts immediate child ledgers of
@@ -804,26 +851,26 @@ public:
current(
lock,
[&](std::size_t numValidations) { ret.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
[&](NodeID const&, Validation const& v) {
if (v.trusted() && v.full())
ret.push_back(v.unwrap());
});
return ret;
}
/** Get the set of known public keys associated with current validations
/** Get the set of node ids associated with current validations
@return The set of known keys for current listed validators
@return The set of node ids for active, listed validators
*/
hash_set<NodeKey>
getCurrentPublicKeys()
auto
getCurrentNodeIDs() -> hash_set<NodeID>
{
hash_set<NodeKey> ret;
hash_set<NodeID> ret;
ScopedLock lock{mutex_};
current(
lock,
[&](std::size_t numValidations) { ret.reserve(numValidations); },
[&](NodeKey const& k, Validation const&) { ret.insert(k); });
[&](NodeID const& nid, Validation const&) { ret.insert(nid); });
return ret;
}
@@ -842,7 +889,7 @@ public:
lock,
ledgerID,
[&](std::size_t) {}, // nothing to reserve
[&](NodeKey const&, Validation const& v) {
[&](NodeID const&, Validation const& v) {
if (v.trusted() && v.full())
++count;
});
@@ -863,7 +910,7 @@ public:
lock,
ledgerID,
[&](std::size_t numValidations) { res.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
[&](NodeID const&, Validation const& v) {
if (v.trusted() && v.full())
res.emplace_back(v.unwrap());
});
@@ -885,7 +932,7 @@ public:
lock,
ledgerID,
[&](std::size_t numValidations) { times.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
[&](NodeID const&, Validation const& v) {
if (v.trusted() && v.full())
times.emplace_back(v.signTime());
});
@@ -907,7 +954,7 @@ public:
lock,
ledgerID,
[&](std::size_t numValidations) { res.reserve(numValidations); },
[&](NodeKey const&, Validation const& v) {
[&](NodeID const&, Validation const& v) {
if (v.trusted() && v.full())
{
boost::optional<std::uint32_t> loadFee = v.loadFee();
@@ -925,7 +972,7 @@ public:
void
flush()
{
hash_map<NodeKey, Validation> flushed;
hash_map<NodeID, Validation> flushed;
{
ScopedLock lock{mutex_};
for (auto it : current_)

View File

@@ -899,8 +899,12 @@ OverlayImpl::send (protocol::TMValidation& m)
});
SerialIter sit (m.validation().data(), m.validation().size());
auto val = std::make_shared <
STValidation> (std::ref (sit), false);
auto val = std::make_shared<STValidation>(
std::ref(sit),
[this](PublicKey const& pk) {
return calcNodeID(app_.validatorManifests().getMasterKey(pk));
},
false);
app_.getOPs().pubValidation (val);
}

View File

@@ -1301,9 +1301,16 @@ PeerImp::onMessage (std::shared_ptr <protocol::TMProposeSet> const& m)
"Proposal: " << (isTrusted ? "trusted" : "UNTRUSTED");
auto proposal = RCLCxPeerPos(
publicKey, signature, suppression,
RCLCxPeerPos::Proposal{prevLedger, set.proposeseq (), proposeHash, closeTime,
app_.timeKeeper().closeTime(),calcNodeID(publicKey)});
publicKey,
signature,
suppression,
RCLCxPeerPos::Proposal{
prevLedger,
set.proposeseq(),
proposeHash,
closeTime,
app_.timeKeeper().closeTime(),
calcNodeID(app_.validatorManifests().getMasterKey(publicKey))});
std::weak_ptr<PeerImp> weak = shared_from_this();
app_.getJobQueue ().addJob (
@@ -1611,8 +1618,13 @@ PeerImp::onMessage (std::shared_ptr <protocol::TMValidation> const& m)
STValidation::pointer val;
{
SerialIter sit (makeSlice(m->validation()));
val = std::make_shared <
STValidation> (std::ref (sit), false);
val = std::make_shared<STValidation>(
std::ref(sit),
[this](PublicKey const& pk) {
return calcNodeID(
app_.validatorManifests().getMasterKey(pk));
},
false);
val->setSeen (closeTime);
}
@@ -1954,8 +1966,7 @@ PeerImp::checkPropose (Job& job,
if (isTrusted)
{
app_.getOPs ().processTrustedProposal (
peerPos, packet, calcNodeID (publicKey_));
app_.getOPs ().processTrustedProposal (peerPos, packet);
}
else
{

View File

@@ -20,90 +20,170 @@
#ifndef RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED
#define RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED
#include <ripple/basics/Log.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/SecretKey.h>
#include <cstdint>
#include <functional>
#include <memory>
namespace ripple {
// Validation flags
const std::uint32_t vfFullyCanonicalSig = 0x80000000; // signature is fully canonical
const std::uint32_t vfFullyCanonicalSig =
0x80000000; // signature is fully canonical
class STValidation final
: public STObject
, public CountedObject <STValidation>
class STValidation final : public STObject, public CountedObject<STValidation>
{
public:
static char const* getCountedObjectName () { return "STValidation"; }
static char const*
getCountedObjectName()
{
return "STValidation";
}
using pointer = std::shared_ptr<STValidation>;
using ref = const std::shared_ptr<STValidation>&;
using ref = const std::shared_ptr<STValidation>&;
enum
enum { kFullFlag = 0x1 };
/** Construct a STValidation from a peer.
Construct a STValidation from serialized data previously shared by a
peer.
@param sit Iterator over serialized data
@param lookupNodeID Invocable with signature
NodeID(PublicKey const&)
used to find the Node ID based on the public key
that signed the validation. For manifest based
validators, this should be the NodeID of the master
public key.
@param checkSignature Whether to verify the data was signed properly
@note Throws if the object is not valid
*/
template <class LookupNodeID>
STValidation(
SerialIter& sit,
LookupNodeID&& lookupNodeID,
bool checkSignature)
: STObject(getFormat(), sit, sfValidation)
{
kFullFlag = 0x1
};
mNodeID =
lookupNodeID(PublicKey(makeSlice(getFieldVL(sfSigningPubKey))));
assert(mNodeID.isNonZero());
// These throw if the object is not valid
STValidation (SerialIter & sit, bool checkSignature = true);
if (checkSignature && !isValid())
{
JLOG(debugLog().error()) << "Invalid validation" << getJson(0);
Throw<std::runtime_error>("Invalid validation");
}
}
// Does not sign the validation
STValidation (
/** Construct a new STValidation
Constructs a new STValidation issued by a node. The instance should be
signed before sharing with other nodes.
@param ledgerHash The hash of the validated ledger
@param signTime When the validation is signed
@param publicKey The current signing public key
@param nodeID ID corresponding to node's public master key
@param isFull Whether the validation is full or partial
*/
STValidation(
uint256 const& ledgerHash,
NetClock::time_point signTime,
PublicKey const& raPub,
PublicKey const& publicKey,
NodeID const& nodeID,
bool isFull);
STBase*
copy (std::size_t n, void* buf) const override
copy(std::size_t n, void* buf) const override
{
return emplace(n, buf, *this);
}
STBase*
move (std::size_t n, void* buf) override
move(std::size_t n, void* buf) override
{
return emplace(n, buf, std::move(*this));
}
uint256 getLedgerHash () const;
NetClock::time_point getSignTime () const;
NetClock::time_point getSeenTime () const;
std::uint32_t getFlags () const;
PublicKey getSignerPublic () const;
NodeID getNodeID () const
uint256
getLedgerHash() const;
NetClock::time_point
getSignTime() const;
NetClock::time_point
getSeenTime() const;
std::uint32_t
getFlags() const;
PublicKey
getSignerPublic() const;
NodeID
getNodeID() const
{
return mNodeID;
}
bool isValid () const;
bool isFull () const;
bool isTrusted () const
bool
isValid() const;
bool
isFull() const;
bool
isTrusted() const
{
return mTrusted;
}
uint256 getSigningHash () const;
bool isValid (uint256 const& ) const;
void setTrusted ()
uint256
getSigningHash() const;
bool
isValid(uint256 const&) const;
void
setTrusted()
{
mTrusted = true;
}
void setSeen (NetClock::time_point s)
void
setUntrusted()
{
mTrusted = false;
}
void
setSeen(NetClock::time_point s)
{
mSeen = s;
}
Blob getSerialized () const;
Blob getSignature () const;
Blob
getSerialized() const;
Blob
getSignature() const;
// Signs the validation and returns the signing hash
uint256 sign (SecretKey const& secretKey);
uint256
sign(SecretKey const& secretKey);
private:
static SOTemplate const& getFormat ();
void setNode ();
static SOTemplate const&
getFormat();
NodeID mNodeID;
bool mTrusted = false;

View File

@@ -26,35 +26,19 @@
namespace ripple {
STValidation::STValidation (SerialIter& sit, bool checkSignature)
: STObject (getFormat (), sit, sfValidation)
{
mNodeID = calcNodeID(
PublicKey(makeSlice (getFieldVL (sfSigningPubKey))));
assert (mNodeID.isNonZero ());
if (checkSignature && !isValid ())
{
JLOG (debugLog().error())
<< "Invalid validation" << getJson (0);
Throw<std::runtime_error> ("Invalid validation");
}
}
STValidation::STValidation (
uint256 const& ledgerHash,
NetClock::time_point signTime,
PublicKey const& publicKey,
bool isFull)
: STObject (getFormat (), sfValidation)
, mSeen (signTime)
STValidation::STValidation(
uint256 const& ledgerHash,
NetClock::time_point signTime,
PublicKey const& publicKey,
NodeID const& nodeID,
bool isFull)
: STObject(getFormat(), sfValidation), mNodeID(nodeID), mSeen(signTime)
{
// Does not sign
setFieldH256 (sfLedgerHash, ledgerHash);
setFieldU32 (sfSigningTime, signTime.time_since_epoch().count());
setFieldVL (sfSigningPubKey, publicKey.slice());
mNodeID = calcNodeID(publicKey);
assert (mNodeID.isNonZero ());
if (isFull)

View File

@@ -379,7 +379,7 @@ public:
for (auto const& val : validators)
{
auto v = std::make_shared <STValidation> (
uint256(), roundTime, val, true);
uint256(), roundTime, val, calcNodeID(val), true);
++i;
STVector256 field (sfAmendments);

View File

@@ -31,15 +31,38 @@ namespace test {
class RCLValidations_test : public beast::unit_test::suite
{
public:
void
run() override
testChangeTrusted()
{
testcase("Change validation trusted status");
PublicKey key = derivePublicKey(KeyType::ed25519, randomSecretKey());
auto v = std::make_shared<STValidation>(
uint256(), NetClock::time_point(), key, calcNodeID(key), true);
BEAST_EXPECT(!v->isTrusted());
v->setTrusted();
BEAST_EXPECT(v->isTrusted());
v->setUntrusted();
BEAST_EXPECT(!v->isTrusted());
RCLValidation rcv{v};
BEAST_EXPECT(!rcv.trusted());
rcv.setTrusted();
BEAST_EXPECT(rcv.trusted());
rcv.setUntrusted();
BEAST_EXPECT(!rcv.trusted());
}
void
testRCLValidatedLedger()
{
testcase("RCLValidatedLedger ancestry");
beast::Journal j;
using Seq = RCLValidatedLedger::Seq;
using ID = RCLValidatedLedger::ID;
// This tests RCLValidatedLedger properly implements the type
// requirements of a LedgerTrie ledger, with its added behavior that
// only the 256 prior ledger hashes are available to determine ancestry.
@@ -193,8 +216,14 @@ public:
}
}
}
}
public:
void
run() override
{
testChangeTrusted();
testRCLValidatedLedger();
}
};

View File

@@ -18,9 +18,11 @@
//==============================================================================
#include <ripple/app/misc/ValidatorKeys.h>
#include <ripple/app/misc/Manifest.h>
#include <ripple/beast/unit_test.h>
#include <ripple/core/Config.h>
#include <ripple/core/ConfigSections.h>
#include <beast/core/detail/base64.hpp>
#include <string>
namespace ripple {
@@ -74,19 +76,24 @@ public:
{
beast::Journal j;
// Keys when using [validation_seed]
auto const seedSecretKey =
// Keys/ID when using [validation_seed]
SecretKey const seedSecretKey =
generateSecretKey(KeyType::secp256k1, *parseBase58<Seed>(seed));
auto const seedPublicKey =
PublicKey const seedPublicKey =
derivePublicKey(KeyType::secp256k1, seedSecretKey);
NodeID const seedNodeID = calcNodeID(seedPublicKey);
// Keys when using [validation_token]
auto const tokenSecretKey = *parseBase58<SecretKey>(
// Keys/ID when using [validation_token]
SecretKey const tokenSecretKey = *parseBase58<SecretKey>(
TokenType::TOKEN_NODE_PRIVATE, tokenSecretStr);
auto const tokenPublicKey =
PublicKey const tokenPublicKey =
derivePublicKey(KeyType::secp256k1, tokenSecretKey);
auto const m = Manifest::make_Manifest(
beast::detail::base64_decode(tokenManifest));
BEAST_EXPECT(m);
NodeID const tokenNodeID = calcNodeID(m->masterKey);
{
// No config -> no key but valid
Config c;
@@ -104,6 +111,7 @@ public:
ValidatorKeys k{c, j};
BEAST_EXPECT(k.publicKey == seedPublicKey);
BEAST_EXPECT(k.secretKey == seedSecretKey);
BEAST_EXPECT(k.nodeID == seedNodeID);
BEAST_EXPECT(k.manifest.empty());
BEAST_EXPECT(!k.configInvalid());
}
@@ -127,6 +135,7 @@ public:
BEAST_EXPECT(k.publicKey == tokenPublicKey);
BEAST_EXPECT(k.secretKey == tokenSecretKey);
BEAST_EXPECT(k.nodeID == tokenNodeID);
BEAST_EXPECT(k.manifest == tokenManifest);
BEAST_EXPECT(!k.configInvalid());
}

View File

@@ -125,6 +125,16 @@ private:
keys.first, keys.second, makeSlice(data)));
}
static hash_set<NodeID>
asNodeIDs(std::initializer_list<PublicKey> const& pks)
{
hash_set<NodeID> res;
res.reserve(pks.size());
for (auto const& pk : pks)
res.insert(calcNodeID(pk));
return res;
}
void
testGenesisQuorum ()
{
@@ -509,9 +519,9 @@ private:
}
void
testUpdate ()
testUpdateTrusted ()
{
testcase ("Update");
testcase ("Update trusted");
PublicKey emptyLocalKey;
ManifestCache manifests;
@@ -520,7 +530,8 @@ private:
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<std::string> cfgPublishers;
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidators;
hash_set<NodeID> secondAddedValidators;
// BFT: n >= 3f+1
std::size_t const n = 40;
@@ -535,15 +546,18 @@ private:
cfgKeys.push_back (toBase58(
TokenType::TOKEN_NODE_PUBLIC, valKey));
if (cfgKeys.size () <= n - 5)
activeValidators.emplace (valKey);
activeValidators.emplace (calcNodeID(valKey));
}
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
// onConsensusStart should make all available configured
// updateTrusted should make all available configured
// validators trusted
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.added == activeValidators);
BEAST_EXPECT(changes.removed.empty());
// Add 1 to n because I'm not on a published list.
BEAST_EXPECT(trustedKeys->quorum () == n + 1 - f);
std::size_t i = 0;
@@ -564,11 +578,19 @@ private:
{
// Quorum should be 80% with all listed validators active
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidatorsNew{activeValidators};
for (auto const valKey : cfgKeys)
activeValidators.emplace (*parseBase58<PublicKey>(
TokenType::TOKEN_NODE_PUBLIC, valKey));
trustedKeys->onConsensusStart (activeValidators);
{
auto const ins = activeValidatorsNew.emplace(
calcNodeID(*parseBase58<PublicKey>(
TokenType::TOKEN_NODE_PUBLIC, valKey)));
if(ins.second)
secondAddedValidators.insert(*ins.first);
}
TrustChanges changes =
trustedKeys->updateTrusted(activeValidatorsNew);
BEAST_EXPECT(changes.added == secondAddedValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(trustedKeys->quorum () == cfgKeys.size() * 4/5);
}
}
@@ -586,10 +608,13 @@ private:
auto const signingKeys1 = randomKeyPair(KeyType::secp256k1);
auto const signingPublic1 = signingKeys1.first;
activeValidators.emplace (masterPublic);
activeValidators.emplace (calcNodeID(masterPublic));
// Should not trust ephemeral signing key if there is no manifest
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.added == asNodeIDs({masterPublic}));
BEAST_EXPECT(changes.removed == secondAddedValidators);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
BEAST_EXPECT(!trustedKeys->listed (signingPublic1));
@@ -603,7 +628,9 @@ private:
BEAST_EXPECT(
manifests.applyManifest(std::move (*m1)) ==
ManifestDisposition::accepted);
trustedKeys->onConsensusStart (activeValidators);
changes = trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == n + 2 - f);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
@@ -620,7 +647,9 @@ private:
BEAST_EXPECT(
manifests.applyManifest(std::move (*m2)) ==
ManifestDisposition::accepted);
trustedKeys->onConsensusStart (activeValidators);
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == n + 2 - f);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(trustedKeys->trusted (masterPublic));
@@ -632,7 +661,7 @@ private:
// Should not trust keys from revoked master public key
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
auto const signingPublicMax = signingKeysMax.first;
activeValidators.emplace (signingPublicMax);
activeValidators.emplace (calcNodeID(signingPublicMax));
auto mMax = Manifest::make_Manifest (makeManifestString (
masterPublic, masterPrivate,
signingPublicMax, signingKeysMax.second,
@@ -644,7 +673,9 @@ private:
ManifestDisposition::accepted);
BEAST_EXPECT(manifests.getSigningKey (masterPublic) == masterPublic);
BEAST_EXPECT(manifests.revoked (masterPublic));
trustedKeys->onConsensusStart (activeValidators);
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed == asNodeIDs({masterPublic}));
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == n + 1 - f);
BEAST_EXPECT(trustedKeys->listed (masterPublic));
BEAST_EXPECT(!trustedKeys->trusted (masterPublic));
@@ -670,7 +701,10 @@ private:
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, emptyCfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () ==
std::numeric_limits<std::size_t>::max());
}
@@ -680,7 +714,7 @@ private:
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidators;
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[0]),
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[1])});
@@ -688,7 +722,10 @@ private:
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == asNodeIDs({keys[0], keys[1]}));
BEAST_EXPECT(trustedKeys->quorum () == 2);
for (auto const& key : keys)
@@ -704,16 +741,21 @@ private:
auto const node = randomNode ();
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, node)});
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidators;
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == asNodeIDs({node}));
BEAST_EXPECT(trustedKeys->quorum () == minQuorum);
activeValidators.emplace (node);
trustedKeys->onConsensusStart (activeValidators);
activeValidators.emplace (calcNodeID(node));
changes = trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(trustedKeys->quorum () == 1);
}
{
@@ -722,7 +764,7 @@ private:
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<PublicKey> keys ({ randomNode (), randomNode () });
hash_set<PublicKey> activeValidators ({ keys[0] });
hash_set<NodeID> activeValidators (asNodeIDs({ keys[0] }));
std::vector<std::string> cfgKeys ({
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[0]),
toBase58 (TokenType::TOKEN_NODE_PUBLIC, keys[1])});
@@ -731,7 +773,11 @@ private:
BEAST_EXPECT(trustedKeys->load (
localKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(
changes.added == asNodeIDs({keys[0], keys[1], localKey}));
BEAST_EXPECT(trustedKeys->quorum () == 2);
// local validator key is always trusted
@@ -758,7 +804,8 @@ private:
emptyLocalKey, emptyCfgKeys, cfgKeys));
std::vector<Validator> list ({randomValidator(), randomValidator()});
hash_set<PublicKey> activeValidators ({ list[0].masterPublic, list[1].masterPublic });
hash_set<NodeID> activeValidators(
asNodeIDs({list[0].masterPublic, list[1].masterPublic}));
// do not apply expired list
auto const version = 1;
@@ -773,16 +820,21 @@ private:
trustedKeys->applyList (
manifest, blob, sig, version));
trustedKeys->onConsensusStart (activeValidators);
for(Validator const & val : list)
{
BEAST_EXPECT(trustedKeys->trusted (val.masterPublic));
BEAST_EXPECT(trustedKeys->trusted (val.signingPublic));
}
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == activeValidators);
for(Validator const & val : list)
{
BEAST_EXPECT(trustedKeys->trusted (val.masterPublic));
BEAST_EXPECT(trustedKeys->trusted (val.signingPublic));
}
BEAST_EXPECT(trustedKeys->quorum () == 2);
env.timeKeeper().set(expiration);
trustedKeys->onConsensusStart (activeValidators);
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed == activeValidators);
BEAST_EXPECT(changes.added.empty());
BEAST_EXPECT(! trustedKeys->trusted (list[0].masterPublic));
BEAST_EXPECT(! trustedKeys->trusted (list[1].masterPublic));
BEAST_EXPECT(trustedKeys->quorum () ==
@@ -790,7 +842,7 @@ private:
// (Re)trust validators from new valid list
std::vector<Validator> list2 ({list[0], randomValidator()});
activeValidators.insert(list2[1].masterPublic);
activeValidators.insert(calcNodeID(list2[1].masterPublic));
auto const sequence2 = 2;
NetClock::time_point const expiration2 =
env.timeKeeper().now() + 60s;
@@ -802,14 +854,18 @@ private:
trustedKeys->applyList (
manifest, blob2, sig2, version));
trustedKeys->onConsensusStart (activeValidators);
for(Validator const & val : list2)
{
BEAST_EXPECT(trustedKeys->trusted (val.masterPublic));
BEAST_EXPECT(trustedKeys->trusted (val.signingPublic));
}
changes = trustedKeys->updateTrusted (activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(
changes.added ==
asNodeIDs({list2[0].masterPublic, list2[1].masterPublic}));
for(Validator const & val : list2)
{
BEAST_EXPECT(trustedKeys->trusted (val.masterPublic));
BEAST_EXPECT(trustedKeys->trusted (val.signingPublic));
}
BEAST_EXPECT(! trustedKeys->trusted (list[1].masterPublic));
BEAST_EXPECT(! trustedKeys->trusted (list[1].signingPublic));
BEAST_EXPECT(! trustedKeys->trusted (list[1].signingPublic));
BEAST_EXPECT(trustedKeys->quorum () == 2);
}
{
@@ -818,7 +874,8 @@ private:
manifests, manifests, env.timeKeeper(), beast::Journal ());
std::vector<std::string> cfgPublishers;
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidators;
hash_set<PublicKey> activeKeys;
std::vector<std::string> cfgKeys;
cfgKeys.reserve(9);
@@ -828,15 +885,19 @@ private:
auto const valKey = randomNode();
cfgKeys.push_back (toBase58(
TokenType::TOKEN_NODE_PUBLIC, valKey));
activeValidators.emplace (valKey);
activeValidators.emplace (calcNodeID(valKey));
activeKeys.emplace(valKey);
BEAST_EXPECT(trustedKeys->load (
emptyLocalKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
BEAST_EXPECT(changes.added == asNodeIDs({valKey}));
BEAST_EXPECT(trustedKeys->quorum () ==
((cfgKeys.size() <= 6) ? cfgKeys.size()/2 + 1 :
cfgKeys.size() * 2/3 + 1));
for (auto const& key : activeValidators)
for (auto const& key : activeKeys)
BEAST_EXPECT(trustedKeys->trusted (key));
}
}
@@ -847,8 +908,8 @@ private:
auto const localKey = randomNode();
std::vector<std::string> cfgPublishers;
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidators;
hash_set<PublicKey> activeKeys;
std::vector<std::string> cfgKeys {
toBase58(TokenType::TOKEN_NODE_PUBLIC, localKey)};
cfgKeys.reserve(9);
@@ -858,17 +919,25 @@ private:
auto const valKey = randomNode();
cfgKeys.push_back (toBase58(
TokenType::TOKEN_NODE_PUBLIC, valKey));
activeValidators.emplace (valKey);
activeValidators.emplace (calcNodeID(valKey));
activeKeys.emplace(valKey);
BEAST_EXPECT(trustedKeys->load (
localKey, cfgKeys, cfgPublishers));
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(changes.removed.empty());
if (cfgKeys.size() > 2)
BEAST_EXPECT(changes.added == asNodeIDs({valKey}));
else
BEAST_EXPECT(
changes.added == asNodeIDs({localKey, valKey}));
BEAST_EXPECT(trustedKeys->quorum () ==
((cfgKeys.size() <= 6) ? cfgKeys.size()/2 + 1 :
(cfgKeys.size() + 1) * 2/3 + 1));
for (auto const& key : activeValidators)
for (auto const& key : activeKeys)
BEAST_EXPECT(trustedKeys->trusted (key));
}
}
@@ -878,15 +947,15 @@ private:
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), beast::Journal ());
hash_set<PublicKey> activeValidators;
hash_set<NodeID> activeValidators;
std::vector<Validator> valKeys;
valKeys.reserve(n);
while (valKeys.size () != n)
{
valKeys.push_back (randomValidator());
activeValidators.emplace (valKeys.back().masterPublic);
activeValidators.emplace(
calcNodeID(valKeys.back().masterPublic));
}
auto addPublishedList = [this, &env, &trustedKeys, &valKeys]()
@@ -923,17 +992,24 @@ private:
for (auto i = 0; i < 3; ++i)
addPublishedList();
trustedKeys->onConsensusStart (activeValidators);
TrustChanges changes =
trustedKeys->updateTrusted(activeValidators);
// Minimum quorum should be used
BEAST_EXPECT(trustedKeys->quorum () == (valKeys.size() * 2/3 + 1));
hash_set<NodeID> added;
std::size_t nTrusted = 0;
for (auto const& key : activeValidators)
for (auto const& val : valKeys)
{
if (trustedKeys->trusted (key))
if (trustedKeys->trusted (val.masterPublic))
{
added.insert(calcNodeID(val.masterPublic));
++nTrusted;
}
}
BEAST_EXPECT(changes.added == added);
BEAST_EXPECT(changes.removed.empty());
// The number of trusted keys should be 125% of the minimum quorum
BEAST_EXPECT(nTrusted ==
@@ -979,9 +1055,9 @@ private:
manifests, manifests, env.app().timeKeeper(), journal);
std::vector<Validator> validators = {randomValidator()};
hash_set<PublicKey> activeKeys;
hash_set<NodeID> activeValidators;
for(Validator const & val : validators)
activeKeys.insert(val.masterPublic);
activeValidators.insert(calcNodeID(val.masterPublic));
// Store prepared list data to control when it is applied
struct PreparedList
{
@@ -1053,7 +1129,7 @@ private:
// Advance past the first list's expiration, but it remains the
// earliest expiration
env.timeKeeper().set(prep1.expiration + 1s);
trustedKeys->onConsensusStart(activeKeys);
trustedKeys->updateTrusted(activeValidators);
BEAST_EXPECT(
trustedKeys->expires() &&
trustedKeys->expires().get() == prep1.expiration);
@@ -1066,7 +1142,7 @@ public:
testGenesisQuorum ();
testConfigLoad ();
testApplyList ();
testUpdate ();
testUpdateTrusted ();
testExpires ();
}
};

View File

@@ -169,7 +169,7 @@ class Validations_test : public beast::unit_test::suite
struct StaleData
{
std::vector<Validation> stale;
hash_map<PeerKey, Validation> flushed;
hash_map<PeerID, Validation> flushed;
};
// Generic Validations adaptor that saves stale/flushed data into
@@ -216,7 +216,7 @@ class Validations_test : public beast::unit_test::suite
}
void
flush(hash_map<PeerKey, Validation>&& remaining)
flush(hash_map<PeerID, Validation>&& remaining)
{
staleData_.flushed = std::move(remaining);
}
@@ -250,8 +250,7 @@ class Validations_test : public beast::unit_test::suite
ValStatus
add(Validation const& v)
{
PeerKey masterKey{v.nodeID(), 0};
return tv_.add(masterKey, v);
return tv_.add(v.nodeID(), v);
}
TestValidations&
@@ -284,7 +283,7 @@ class Validations_test : public beast::unit_test::suite
return staleData_.stale;
}
hash_map<PeerKey, Validation> const&
hash_map<PeerID, Validation> const&
flushed() const
{
return staleData_.flushed;
@@ -462,7 +461,7 @@ class Validations_test : public beast::unit_test::suite
std::vector<Trigger> triggers = {
[&](TestValidations& vals) { vals.currentTrusted(); },
[&](TestValidations& vals) { vals.getCurrentPublicKeys(); },
[&](TestValidations& vals) { vals.getCurrentNodeIDs(); },
[&](TestValidations& vals) { vals.getPreferred(genesisLedger); },
[&](TestValidations& vals) {
vals.getNodesAfter(ledgerA, ledgerA.id());
@@ -610,9 +609,9 @@ class Validations_test : public beast::unit_test::suite
ValStatus::current == harness.add(node.validate(ledgerA)));
{
hash_set<PeerKey> const expectedKeys = {a.masterKey(),
b.masterKey()};
BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys);
hash_set<PeerID> const expectedKeys = {a.nodeID(),
b.nodeID()};
BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys);
}
harness.clock().advance(3s);
@@ -626,14 +625,14 @@ class Validations_test : public beast::unit_test::suite
ValStatus::current == harness.add(node.partial(ledgerAC)));
{
hash_set<PeerKey> const expectedKeys = {a.masterKey(),
b.masterKey()};
BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys);
hash_set<PeerID> const expectedKeys = {a.nodeID(),
b.nodeID()};
BEAST_EXPECT(harness.vals().getCurrentNodeIDs() == expectedKeys);
}
// Pass enough time for them to go stale
harness.clock().advance(harness.parms().validationCURRENT_LOCAL);
BEAST_EXPECT(harness.vals().getCurrentPublicKeys().empty());
BEAST_EXPECT(harness.vals().getCurrentNodeIDs().empty());
}
void
@@ -719,7 +718,7 @@ class Validations_test : public beast::unit_test::suite
if (val.trusted())
trustedValidations[val.ledgerID()].emplace_back(val);
}
// d diagrees
// d disagrees
{
auto const val = d.validate(ledgerB);
BEAST_EXPECT(ValStatus::current == harness.add(val));
@@ -786,21 +785,21 @@ class Validations_test : public beast::unit_test::suite
Ledger ledgerA = h["a"];
Ledger ledgerAB = h["ab"];
hash_map<PeerKey, Validation> expected;
hash_map<PeerID, Validation> expected;
for (auto const& node : {a, b, c})
{
auto const val = node.validate(ledgerA);
BEAST_EXPECT(ValStatus::current == harness.add(val));
expected.emplace(node.masterKey(), val);
expected.emplace(node.nodeID(), val);
}
Validation staleA = expected.find(a.masterKey())->second;
Validation staleA = expected.find(a.nodeID())->second;
// Send in a new validation for a, saving the new one into the expected
// map after setting the proper prior ledger ID it replaced
harness.clock().advance(1s);
auto newVal = a.validate(ledgerAB);
BEAST_EXPECT(ValStatus::current == harness.add(newVal));
expected.find(a.masterKey())->second = newVal;
expected.find(a.nodeID())->second = newVal;
// Now flush
harness.vals().flush();
@@ -1056,6 +1055,97 @@ class Validations_test : public beast::unit_test::suite
BEAST_EXPECT(enforcer(clock.now(), Seq{1}, p));
}
void
testTrustChanged()
{
testcase("TrustChanged");
using namespace std::chrono;
auto checker = [this](
TestValidations& vals,
hash_set<PeerID> const& listed,
std::vector<Validation> const& trustedVals) {
Ledger::ID testID = trustedVals.empty() ? this->genesisLedger.id()
: trustedVals[0].ledgerID();
BEAST_EXPECT(vals.currentTrusted() == trustedVals);
BEAST_EXPECT(vals.getCurrentNodeIDs() == listed);
BEAST_EXPECT(
vals.getNodesAfter(this->genesisLedger, genesisLedger.id()) ==
trustedVals.size());
BEAST_EXPECT(
vals.getPreferred(this->genesisLedger).second == testID);
BEAST_EXPECT(vals.getTrustedForLedger(testID) == trustedVals);
BEAST_EXPECT(
vals.numTrustedForLedger(testID) == trustedVals.size());
};
{
// Trusted to untrusted
LedgerHistoryHelper h;
TestHarness harness(h.oracle);
Node a = harness.makeNode();
Ledger ledgerAB = h["ab"];
Validation v = a.validate(ledgerAB);
BEAST_EXPECT(ValStatus::current == harness.add(v));
hash_set<PeerID> listed({a.nodeID()});
std::vector<Validation> trustedVals({v});
checker(harness.vals(), listed, trustedVals);
trustedVals.clear();
harness.vals().trustChanged({}, {a.nodeID()});
checker(harness.vals(), listed, trustedVals);
}
{
// Untrusted to trusted
LedgerHistoryHelper h;
TestHarness harness(h.oracle);
Node a = harness.makeNode();
a.untrust();
Ledger ledgerAB = h["ab"];
Validation v = a.validate(ledgerAB);
BEAST_EXPECT(ValStatus::current == harness.add(v));
hash_set<PeerID> listed({a.nodeID()});
std::vector<Validation> trustedVals;
checker(harness.vals(), listed, trustedVals);
trustedVals.push_back(v);
harness.vals().trustChanged({a.nodeID()}, {});
checker(harness.vals(), listed, trustedVals);
}
{
// Trusted but not acquired -> untrusted
LedgerHistoryHelper h;
TestHarness harness(h.oracle);
Node a = harness.makeNode();
Validation v =
a.validate(Ledger::ID{2}, Ledger::Seq{2}, 0s, 0s, true);
BEAST_EXPECT(ValStatus::current == harness.add(v));
hash_set<PeerID> listed({a.nodeID()});
std::vector<Validation> trustedVals({v});
auto& vals = harness.vals();
BEAST_EXPECT(vals.currentTrusted() == trustedVals);
BEAST_EXPECT(
vals.getPreferred(genesisLedger).second == v.ledgerID());
BEAST_EXPECT(
vals.getNodesAfter(genesisLedger, genesisLedger.id()) == 0);
trustedVals.clear();
harness.vals().trustChanged({}, {a.nodeID()});
// make acquiring ledger available
h["ab"];
BEAST_EXPECT(vals.currentTrusted() == trustedVals);
BEAST_EXPECT(
vals.getPreferred(genesisLedger).second == genesisLedger.id());
BEAST_EXPECT(
vals.getNodesAfter(genesisLedger, genesisLedger.id()) == 0);
}
}
void
run() override
{
@@ -1072,6 +1162,7 @@ class Validations_test : public beast::unit_test::suite
testAcquireValidatedLedger();
testNumTrustedForLedger();
testSeqEnforcer();
testTrustChanged();
}
};

View File

@@ -149,7 +149,7 @@ struct Peer
}
void
flush(hash_map<PeerKey, Validation>&& remaining)
flush(hash_map<PeerID, Validation>&& remaining)
{
}
@@ -681,9 +681,9 @@ struct Peer
{
v.setTrusted();
v.setSeen(now());
ValStatus const res = validations.add(v.key(), v);
ValStatus const res = validations.add(v.nodeID(), v);
if(res == ValStatus::stale || res == ValStatus::repeatID)
if(res == ValStatus::stale)
return false;
// Acquire will try to get from network if not already local
@@ -875,8 +875,10 @@ struct Peer
issue(StartRound{bestLCL, lastClosedLedger});
// Not yet modeling dynamic UNL.
hash_set<PeerID> nowUntrusted;
consensus.startRound(
now(), bestLCL, lastClosedLedger, runAsValidator);
now(), bestLCL, lastClosedLedger, nowUntrusted, runAsValidator);
}
// Start the consensus process assuming it is not yet running
@@ -895,7 +897,7 @@ struct Peer
{
// We don't care about the actual epochs, but do want the
// generated NetClock time to be well past its epoch to ensure
// any subtractions of two NetClock::time_point in the consensu
// any subtractions of two NetClock::time_point in the consensus
// code are positive. (e.g. proposeFRESHNESS)
using namespace std::chrono;
using namespace std::chrono_literals;

View File

@@ -173,6 +173,12 @@ public:
trusted_ = true;
}
void
setUntrusted()
{
trusted_ = false;
}
void
setSeen(NetClock::time_point seen)
{

View File

@@ -308,11 +308,11 @@ public:
env.app().validatorSites().start();
env.app().validatorSites().join();
std::set<PublicKey> startKeys;
hash_set<NodeID> startKeys;
for (auto const& val : validators)
startKeys.insert(val.masterPublic);
startKeys.insert(calcNodeID(val.masterPublic));
env.app().validators().onConsensusStart(startKeys);
env.app().validators().updateTrusted(startKeys);
{
auto const jrr = env.rpc("server_info")[jss::result];