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

View File

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

View File

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

View File

@@ -101,7 +101,19 @@ public:
return val_->isTrusted(); 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 bool
full() const full() const
{ {
@@ -214,7 +226,7 @@ public:
@param remaining The remaining validations to flush @param remaining The remaining validations to flush
*/ */
void void
flush(hash_map<PublicKey, RCLValidation>&& remaining); flush(hash_map<NodeID, RCLValidation>&& remaining);
/** Attempt to acquire the ledger with given id from the network */ /** Attempt to acquire the ledger with given id from the network */
boost::optional<RCLValidatedLedger> boost::optional<RCLValidatedLedger>

View File

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

View File

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

View File

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

View File

@@ -60,6 +60,14 @@ enum class ListDisposition
std::string std::string
to_string(ListDisposition disposition); 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 Trusted Validators List
----------------------- -----------------------
@@ -202,22 +210,23 @@ public:
std::string const& signature, std::string const& signature,
std::uint32_t version); 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. and lists.
@param seenValidators Set of public keys used to sign recently @param seenValidators Set of NodeIDs of validators that have signed
received validations recently received validations
@return TrustedKeyChanges instance with newly trusted or untrusted
node identities.
@par Thread Safety @par Thread Safety
May be called concurrently May be called concurrently
*/ */
template<class KeySet> TrustChanges
void updateTrusted (hash_set<NodeID> const& seenValidators);
onConsensusStart (
KeySet const& seenValidators);
/** Get quorum value for current trusted key set /** Get quorum value for current trusted key set
@@ -390,133 +399,6 @@ private:
calculateMinimumQuorum ( calculateMinimumQuorum (
std::size_t nListedKeys, bool unlistedLocal=false); 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 } // ripple
#endif #endif

View File

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

View File

@@ -597,4 +597,140 @@ ValidatorList::calculateMinimumQuorum (
return nListedKeys * 2/3 + 1; 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 } // ripple

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,90 +20,170 @@
#ifndef RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED #ifndef RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED
#define RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED #define RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED
#include <ripple/basics/Log.h>
#include <ripple/protocol/PublicKey.h> #include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/STObject.h> #include <ripple/protocol/STObject.h>
#include <ripple/protocol/SecretKey.h>
#include <cstdint> #include <cstdint>
#include <functional>
#include <memory> #include <memory>
namespace ripple { namespace ripple {
// Validation flags // 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 class STValidation final : public STObject, public CountedObject<STValidation>
: public STObject
, public CountedObject <STValidation>
{ {
public: public:
static char const* getCountedObjectName () { return "STValidation"; } static char const*
getCountedObjectName()
{
return "STValidation";
}
using pointer = std::shared_ptr<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 if (checkSignature && !isValid())
STValidation (SerialIter & sit, bool checkSignature = true); {
JLOG(debugLog().error()) << "Invalid validation" << getJson(0);
Throw<std::runtime_error>("Invalid validation");
}
}
// Does not sign the validation /** Construct a new STValidation
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, uint256 const& ledgerHash,
NetClock::time_point signTime, NetClock::time_point signTime,
PublicKey const& raPub, PublicKey const& publicKey,
NodeID const& nodeID,
bool isFull); bool isFull);
STBase* STBase*
copy (std::size_t n, void* buf) const override copy(std::size_t n, void* buf) const override
{ {
return emplace(n, buf, *this); return emplace(n, buf, *this);
} }
STBase* STBase*
move (std::size_t n, void* buf) override move(std::size_t n, void* buf) override
{ {
return emplace(n, buf, std::move(*this)); return emplace(n, buf, std::move(*this));
} }
uint256 getLedgerHash () const; uint256
NetClock::time_point getSignTime () const; getLedgerHash() const;
NetClock::time_point getSeenTime () const;
std::uint32_t getFlags () const; NetClock::time_point
PublicKey getSignerPublic () const; getSignTime() const;
NodeID getNodeID () const
NetClock::time_point
getSeenTime() const;
std::uint32_t
getFlags() const;
PublicKey
getSignerPublic() const;
NodeID
getNodeID() const
{ {
return mNodeID; return mNodeID;
} }
bool isValid () const;
bool isFull () const; bool
bool isTrusted () const isValid() const;
bool
isFull() const;
bool
isTrusted() const
{ {
return mTrusted; return mTrusted;
} }
uint256 getSigningHash () const;
bool isValid (uint256 const& ) const;
void setTrusted () uint256
getSigningHash() const;
bool
isValid(uint256 const&) const;
void
setTrusted()
{ {
mTrusted = true; mTrusted = true;
} }
void setSeen (NetClock::time_point s)
void
setUntrusted()
{
mTrusted = false;
}
void
setSeen(NetClock::time_point s)
{ {
mSeen = s; mSeen = s;
} }
Blob getSerialized () const;
Blob getSignature () const; Blob
getSerialized() const;
Blob
getSignature() const;
// Signs the validation and returns the signing hash // Signs the validation and returns the signing hash
uint256 sign (SecretKey const& secretKey); uint256
sign(SecretKey const& secretKey);
private: private:
static SOTemplate const& getFormat (); static SOTemplate const&
getFormat();
void setNode ();
NodeID mNodeID; NodeID mNodeID;
bool mTrusted = false; bool mTrusted = false;

View File

@@ -26,35 +26,19 @@
namespace ripple { namespace ripple {
STValidation::STValidation (SerialIter& sit, bool checkSignature) STValidation::STValidation(
: STObject (getFormat (), sit, sfValidation) uint256 const& ledgerHash,
{ NetClock::time_point signTime,
mNodeID = calcNodeID( PublicKey const& publicKey,
PublicKey(makeSlice (getFieldVL (sfSigningPubKey)))); NodeID const& nodeID,
assert (mNodeID.isNonZero ()); bool isFull)
: STObject(getFormat(), sfValidation), mNodeID(nodeID), mSeen(signTime)
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)
{ {
// Does not sign // Does not sign
setFieldH256 (sfLedgerHash, ledgerHash); setFieldH256 (sfLedgerHash, ledgerHash);
setFieldU32 (sfSigningTime, signTime.time_since_epoch().count()); setFieldU32 (sfSigningTime, signTime.time_since_epoch().count());
setFieldVL (sfSigningPubKey, publicKey.slice()); setFieldVL (sfSigningPubKey, publicKey.slice());
mNodeID = calcNodeID(publicKey);
assert (mNodeID.isNonZero ()); assert (mNodeID.isNonZero ());
if (isFull) if (isFull)

View File

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

View File

@@ -31,15 +31,38 @@ namespace test {
class RCLValidations_test : public beast::unit_test::suite class RCLValidations_test : public beast::unit_test::suite
{ {
public:
void 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; beast::Journal j;
using Seq = RCLValidatedLedger::Seq; using Seq = RCLValidatedLedger::Seq;
using ID = RCLValidatedLedger::ID; using ID = RCLValidatedLedger::ID;
// This tests RCLValidatedLedger properly implements the type // This tests RCLValidatedLedger properly implements the type
// requirements of a LedgerTrie ledger, with its added behavior that // requirements of a LedgerTrie ledger, with its added behavior that
// only the 256 prior ledger hashes are available to determine ancestry. // 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/ValidatorKeys.h>
#include <ripple/app/misc/Manifest.h>
#include <ripple/beast/unit_test.h> #include <ripple/beast/unit_test.h>
#include <ripple/core/Config.h> #include <ripple/core/Config.h>
#include <ripple/core/ConfigSections.h> #include <ripple/core/ConfigSections.h>
#include <beast/core/detail/base64.hpp>
#include <string> #include <string>
namespace ripple { namespace ripple {
@@ -74,19 +76,24 @@ public:
{ {
beast::Journal j; beast::Journal j;
// Keys when using [validation_seed] // Keys/ID when using [validation_seed]
auto const seedSecretKey = SecretKey const seedSecretKey =
generateSecretKey(KeyType::secp256k1, *parseBase58<Seed>(seed)); generateSecretKey(KeyType::secp256k1, *parseBase58<Seed>(seed));
auto const seedPublicKey = PublicKey const seedPublicKey =
derivePublicKey(KeyType::secp256k1, seedSecretKey); derivePublicKey(KeyType::secp256k1, seedSecretKey);
NodeID const seedNodeID = calcNodeID(seedPublicKey);
// Keys when using [validation_token] // Keys/ID when using [validation_token]
auto const tokenSecretKey = *parseBase58<SecretKey>( SecretKey const tokenSecretKey = *parseBase58<SecretKey>(
TokenType::TOKEN_NODE_PRIVATE, tokenSecretStr); TokenType::TOKEN_NODE_PRIVATE, tokenSecretStr);
PublicKey const tokenPublicKey =
auto const tokenPublicKey =
derivePublicKey(KeyType::secp256k1, tokenSecretKey); 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 // No config -> no key but valid
Config c; Config c;
@@ -104,6 +111,7 @@ public:
ValidatorKeys k{c, j}; ValidatorKeys k{c, j};
BEAST_EXPECT(k.publicKey == seedPublicKey); BEAST_EXPECT(k.publicKey == seedPublicKey);
BEAST_EXPECT(k.secretKey == seedSecretKey); BEAST_EXPECT(k.secretKey == seedSecretKey);
BEAST_EXPECT(k.nodeID == seedNodeID);
BEAST_EXPECT(k.manifest.empty()); BEAST_EXPECT(k.manifest.empty());
BEAST_EXPECT(!k.configInvalid()); BEAST_EXPECT(!k.configInvalid());
} }
@@ -127,6 +135,7 @@ public:
BEAST_EXPECT(k.publicKey == tokenPublicKey); BEAST_EXPECT(k.publicKey == tokenPublicKey);
BEAST_EXPECT(k.secretKey == tokenSecretKey); BEAST_EXPECT(k.secretKey == tokenSecretKey);
BEAST_EXPECT(k.nodeID == tokenNodeID);
BEAST_EXPECT(k.manifest == tokenManifest); BEAST_EXPECT(k.manifest == tokenManifest);
BEAST_EXPECT(!k.configInvalid()); BEAST_EXPECT(!k.configInvalid());
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -308,11 +308,11 @@ public:
env.app().validatorSites().start(); env.app().validatorSites().start();
env.app().validatorSites().join(); env.app().validatorSites().join();
std::set<PublicKey> startKeys; hash_set<NodeID> startKeys;
for (auto const& val : validators) 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]; auto const jrr = env.rpc("server_info")[jss::result];