mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -173,6 +173,12 @@ public:
|
||||
trusted_ = true;
|
||||
}
|
||||
|
||||
void
|
||||
setUntrusted()
|
||||
{
|
||||
trusted_ = false;
|
||||
}
|
||||
|
||||
void
|
||||
setSeen(NetClock::time_point seen)
|
||||
{
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user