diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 996a3f8bd3..1cc1119465 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -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 const& nowUntrusted) { ScopedLockType _{mutex_}; consensus_.startRound( - now, prevLgrId, prevLgr, adaptor_.preStartRound(prevLgr)); + now, prevLgrId, prevLgr, nowUntrusted, adaptor_.preStartRound(prevLgr)); } } diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index 8bded9a15f..db6047fa27 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -427,7 +427,8 @@ public: startRound( NetClock::time_point const& now, RCLCxLedger::ID const& prevLgrId, - RCLCxLedger const& prevLgr); + RCLCxLedger const& prevLgr, + hash_set const& nowUntrusted); //! @see Consensus::timerEntry void diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 85bf7fd69f..0fb8e8f36b 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -172,7 +172,7 @@ RCLValidationsAdaptor::onStale(RCLValidation&& v) } void -RCLValidationsAdaptor::flush(hash_map&& remaining) +RCLValidationsAdaptor::flush(hash_map&& 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) { diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 1bd3978ede..6394408af6 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -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&& remaining); + flush(hash_map&& remaining); /** Attempt to acquire the ledger with given id from the network */ boost::optional diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 5ed2a86306..07a5d5c5fb 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -293,8 +293,7 @@ public: // Ledger proposal/close functions. void processTrustedProposal ( RCLCxPeerPos proposal, - std::shared_ptr set, - NodeID const &node) override; + std::shared_ptr 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 set, - NodeID const& node) + std::shared_ptr set) { if (mConsensus.peerProposal( app_.timeKeeper().closeTime(), peerPos)) diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index 2531748345..bf0ee58172 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -153,8 +153,7 @@ public: // ledger proposal/close functions virtual void processTrustedProposal (RCLCxPeerPos peerPos, - std::shared_ptr set, - NodeID const& node) = 0; + std::shared_ptr set) = 0; virtual bool recvValidation (STValidation::ref val, std::string const& source) = 0; diff --git a/src/ripple/app/misc/ValidatorKeys.h b/src/ripple/app/misc/ValidatorKeys.h index 702f502c9f..0f52c27e5f 100644 --- a/src/ripple/app/misc/ValidatorKeys.h +++ b/src/ripple/app/misc/ValidatorKeys.h @@ -23,6 +23,7 @@ #include #include #include +#include #include 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); diff --git a/src/ripple/app/misc/ValidatorList.h b/src/ripple/app/misc/ValidatorList.h index ea7586577e..0e8b41ff5a 100644 --- a/src/ripple/app/misc/ValidatorList.h +++ b/src/ripple/app/misc/ValidatorList.h @@ -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 added; + hash_set 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 - void - onConsensusStart ( - KeySet const& seenValidators); + TrustChanges + updateTrusted (hash_set const& seenValidators); /** Get quorum value for current trusted key set @@ -390,133 +399,6 @@ private: calculateMinimumQuorum ( std::size_t nListedKeys, bool unlistedLocal=false); }; - -//------------------------------------------------------------------------------ - -template -void -ValidatorList::onConsensusStart ( - KeySet const& seenValidators) -{ - boost::unique_lock 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 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 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::numeric_limits::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(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::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 diff --git a/src/ripple/app/misc/impl/ValidatorKeys.cpp b/src/ripple/app/misc/impl/ValidatorKeys.cpp index 599d83c567..46c34dd24e 100644 --- a/src/ripple/app/misc/impl/ValidatorKeys.cpp +++ b/src/ripple/app/misc/impl/ValidatorKeys.cpp @@ -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); } } } diff --git a/src/ripple/app/misc/impl/ValidatorList.cpp b/src/ripple/app/misc/impl/ValidatorList.cpp index 12bbab8ebe..fca5448ca0 100644 --- a/src/ripple/app/misc/impl/ValidatorList.cpp +++ b/src/ripple/app/misc/impl/ValidatorList.cpp @@ -597,4 +597,140 @@ ValidatorList::calculateMinimumQuorum ( return nListedKeys * 2/3 + 1; } +TrustChanges +ValidatorList::updateTrusted(hash_set const& seenValidators) +{ + boost::unique_lock 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 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 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::numeric_limits::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(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::max(); + + TrustChanges trustChanges; + { + hash_set 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 diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index 820fd9c3ce..3ddf101ad4 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -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 const & nowUntrusted, bool proposing); /** A peer has proposed a new position, adjust our tracking. @@ -552,7 +554,7 @@ private: hash_map currPeerPositions_; // Recently received peer positions, available when transitioning between - // ledgers or roundss + // ledgers or rounds hash_map> recentPeerPositions_; // The number of proposers who participated in the last consensus round @@ -583,6 +585,7 @@ Consensus::startRound( NetClock::time_point const& now, typename Ledger_t::ID const& prevLedgerID, Ledger_t prevLedger, + hash_set const& nowUntrusted, bool proposing) { if (firstRound_) @@ -597,6 +600,9 @@ Consensus::startRound( prevCloseTime_ = rawCloseTimes_.self; } + for(NodeID_t const& n : nowUntrusted) + recentPeerPositions_.erase(n); + ConsensusMode startMode = proposing ? ConsensusMode::proposing : ConsensusMode::observing; diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 52a0618526..75406e05b0 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -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 && remaining); + void flush(hash_map && 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>; @@ -279,18 +286,18 @@ class Validations mutable Mutex mutex_; // Validations from currently listed and trusted nodes (partial and full) - hash_map current_; + hash_map current_; // Used to enforce the largest validation invariant for the local node SeqEnforcer localSeqEnforcer_; // Sequence of the largest validation received from each node - hash_map> seqEnforcers_; + hash_map> seqEnforcers_; //! Validations from listed nodes, indexed by ledger id (partial and full) beast::aged_unordered_map< ID, - hash_map, + hash_map, 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 lastLedger_; + hash_map lastLedger_; // Set of ledgers being acquired from the network - hash_map, hash_set> acquiring_; + hash_map, hash_set> 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 = 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> prior) + boost::optional> 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 valPair{val.seq(),val.ledgerID()}; + std::pair 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 = adaptor_.acquire(val.ledgerID())) - updateTrie(lock, key, *ledger); + if (boost::optional 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& enforcer = seqEnforcers_[key]; + SeqEnforcer& 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 const& added, hash_set 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 const& aKey = a.first; - typename hash_set::size_type const& aSize = + typename hash_set::size_type const& aSize = a.second.size(); std::pair const& bKey = b.first; - typename hash_set::size_type const& bSize = + typename hash_set::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 - getCurrentPublicKeys() + auto + getCurrentNodeIDs() -> hash_set { - hash_set ret; + hash_set 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 loadFee = v.loadFee(); @@ -925,7 +972,7 @@ public: void flush() { - hash_map flushed; + hash_map flushed; { ScopedLock lock{mutex_}; for (auto it : current_) diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index ca549eada6..4fb4e1c14e 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -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( + std::ref(sit), + [this](PublicKey const& pk) { + return calcNodeID(app_.validatorManifests().getMasterKey(pk)); + }, + false); app_.getOPs().pubValidation (val); } diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index 9d94890a6b..6d15bf164e 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -1301,9 +1301,16 @@ PeerImp::onMessage (std::shared_ptr 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 weak = shared_from_this(); app_.getJobQueue ().addJob ( @@ -1611,8 +1618,13 @@ PeerImp::onMessage (std::shared_ptr const& m) STValidation::pointer val; { SerialIter sit (makeSlice(m->validation())); - val = std::make_shared < - STValidation> (std::ref (sit), false); + val = std::make_shared( + 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 { diff --git a/src/ripple/protocol/STValidation.h b/src/ripple/protocol/STValidation.h index 53baabe568..37382daa3b 100644 --- a/src/ripple/protocol/STValidation.h +++ b/src/ripple/protocol/STValidation.h @@ -20,90 +20,170 @@ #ifndef RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED #define RIPPLE_PROTOCOL_STVALIDATION_H_INCLUDED +#include #include -#include #include +#include #include +#include #include 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 +class STValidation final : public STObject, public CountedObject { public: - static char const* getCountedObjectName () { return "STValidation"; } + static char const* + getCountedObjectName() + { + return "STValidation"; + } using pointer = std::shared_ptr; - using ref = const std::shared_ptr&; + using ref = const std::shared_ptr&; - 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 + 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("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; diff --git a/src/ripple/protocol/impl/STValidation.cpp b/src/ripple/protocol/impl/STValidation.cpp index c572de4f53..d1242d7edf 100644 --- a/src/ripple/protocol/impl/STValidation.cpp +++ b/src/ripple/protocol/impl/STValidation.cpp @@ -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 ("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) diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 3f69d01189..222f13c175 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -379,7 +379,7 @@ public: for (auto const& val : validators) { auto v = std::make_shared ( - uint256(), roundTime, val, true); + uint256(), roundTime, val, calcNodeID(val), true); ++i; STVector256 field (sfAmendments); diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 0feeb2b7a8..ce2a38f9ae 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -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( + 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(); } }; diff --git a/src/test/app/ValidatorKeys_test.cpp b/src/test/app/ValidatorKeys_test.cpp index 62c59dc2af..82900e0524 100644 --- a/src/test/app/ValidatorKeys_test.cpp +++ b/src/test/app/ValidatorKeys_test.cpp @@ -18,9 +18,11 @@ //============================================================================== #include +#include #include #include #include +#include #include 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)); - auto const seedPublicKey = + PublicKey const seedPublicKey = derivePublicKey(KeyType::secp256k1, seedSecretKey); + NodeID const seedNodeID = calcNodeID(seedPublicKey); - // Keys when using [validation_token] - auto const tokenSecretKey = *parseBase58( + // Keys/ID when using [validation_token] + SecretKey const tokenSecretKey = *parseBase58( 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()); } diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index b351370d13..5cba8e94de 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -125,6 +125,16 @@ private: keys.first, keys.second, makeSlice(data))); } + static hash_set + asNodeIDs(std::initializer_list const& pks) + { + hash_set 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 cfgPublishers; - hash_set activeValidators; + hash_set activeValidators; + hash_set 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 activeValidators; + hash_set activeValidatorsNew{activeValidators}; for (auto const valKey : cfgKeys) - activeValidators.emplace (*parseBase58( - TokenType::TOKEN_NODE_PUBLIC, valKey)); - trustedKeys->onConsensusStart (activeValidators); + { + auto const ins = activeValidatorsNew.emplace( + calcNodeID(*parseBase58( + 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::max()); } @@ -680,7 +714,7 @@ private: manifests, manifests, env.timeKeeper(), beast::Journal ()); std::vector keys ({ randomNode (), randomNode () }); - hash_set activeValidators; + hash_set activeValidators; std::vector 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 cfgKeys ({ toBase58 (TokenType::TOKEN_NODE_PUBLIC, node)}); - hash_set activeValidators; + hash_set 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 keys ({ randomNode (), randomNode () }); - hash_set activeValidators ({ keys[0] }); + hash_set activeValidators (asNodeIDs({ keys[0] })); std::vector 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 list ({randomValidator(), randomValidator()}); - hash_set activeValidators ({ list[0].masterPublic, list[1].masterPublic }); + hash_set 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 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 cfgPublishers; - hash_set activeValidators; + hash_set activeValidators; + hash_set activeKeys; std::vector 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 cfgPublishers; - hash_set activeValidators; - + hash_set activeValidators; + hash_set activeKeys; std::vector 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 ( manifests, manifests, env.timeKeeper(), beast::Journal ()); - hash_set activeValidators; - + hash_set activeValidators; std::vector 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 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 validators = {randomValidator()}; - hash_set activeKeys; + hash_set 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 (); } }; diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 24821fd47d..4bb7092b05 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -169,7 +169,7 @@ class Validations_test : public beast::unit_test::suite struct StaleData { std::vector stale; - hash_map flushed; + hash_map 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&& remaining) + flush(hash_map&& 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 const& + hash_map const& flushed() const { return staleData_.flushed; @@ -462,7 +461,7 @@ class Validations_test : public beast::unit_test::suite std::vector 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 const expectedKeys = {a.masterKey(), - b.masterKey()}; - BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys); + hash_set 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 const expectedKeys = {a.masterKey(), - b.masterKey()}; - BEAST_EXPECT(harness.vals().getCurrentPublicKeys() == expectedKeys); + hash_set 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 expected; + hash_map 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 const& listed, + std::vector 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 listed({a.nodeID()}); + std::vector 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 listed({a.nodeID()}); + std::vector 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 listed({a.nodeID()}); + std::vector 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(); } }; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 19c94c82bd..93d6e4de5d 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -149,7 +149,7 @@ struct Peer } void - flush(hash_map&& remaining) + flush(hash_map&& 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 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; diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 80b74e4b84..0935f76333 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -173,6 +173,12 @@ public: trusted_ = true; } + void + setUntrusted() + { + trusted_ = false; + } + void setSeen(NetClock::time_point seen) { diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index abffdb3b6b..b18d8eb3fd 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -308,11 +308,11 @@ public: env.app().validatorSites().start(); env.app().validatorSites().join(); - std::set startKeys; + hash_set 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];