diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index fb68dd3e7..44bbbfd43 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -867,6 +867,12 @@ + + True + True + + + @@ -1125,12 +1131,6 @@ - - True - True - - - @@ -1871,6 +1871,8 @@ + + @@ -4455,6 +4457,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index bac2171c9..efaff1978 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -1401,6 +1401,12 @@ ripple\app\consensus + + ripple\app\consensus + + + ripple\app\consensus + ripple\app\ledger @@ -1665,12 +1671,6 @@ ripple\app\misc - - ripple\app\misc - - - ripple\app\misc - ripple\app\misc @@ -2517,6 +2517,9 @@ ripple\consensus + + ripple\consensus + ripple\core @@ -5217,6 +5220,9 @@ test\consensus + + test\consensus + test\core diff --git a/docs/source.dox b/docs/source.dox index 7475222c9..1fb7de3f0 100644 --- a/docs/source.dox +++ b/docs/source.dox @@ -113,6 +113,7 @@ INPUT = \ ../src/ripple/consensus/ConsensusProposal.h \ ../src/ripple/consensus/DisputedTx.h \ ../src/ripple/consensus/LedgerTiming.h \ + ../src/ripple/consensus/Validations.h \ ../src/ripple/app/consensus/RCLCxTx.h \ ../src/ripple/app/consensus/RCLCxLedger.h \ ../src/ripple/app/consensus/RCLConsensus.h \ @@ -120,6 +121,7 @@ INPUT = \ ../src/ripple/app/tx/apply.h \ ../src/ripple/app/tx/applySteps.h \ ../src/ripple/app/tx/impl/InvariantCheck.h \ + ../src/ripple/app/consensus/RCLValidations.h \ INPUT_ENCODING = UTF-8 FILE_PATTERNS = diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 55323ff86..2d95cdb99 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -222,7 +223,7 @@ RCLConsensus::hasOpenTransactions() const std::size_t RCLConsensus::proposersValidated(LedgerHash const& h) const { - return app_.getValidations().getTrustedValidationCount(h); + return app_.getValidations().numTrustedForLedger(h); } std::size_t @@ -244,8 +245,9 @@ RCLConsensus::getPrevLedger( // Get validators that are on our ledger, or "close" to being on // our ledger. - auto vals = app_.getValidations().getCurrentValidations( - ledgerID, parentID, ledgerMaster_.getValidLedgerIndex()); + auto vals = + app_.getValidations().currentTrustedDistribution( + ledgerID, parentID, ledgerMaster_.getValidLedgerIndex()); uint256 netLgr = ledgerID; int netLgrCount = 0; @@ -253,11 +255,11 @@ RCLConsensus::getPrevLedger( { // Switch to ledger supported by more peers // Or stick with ours on a tie - if ((it.second.first > netLgrCount) || - ((it.second.first == netLgrCount) && (it.first == ledgerID))) + if ((it.second.count > netLgrCount) || + ((it.second.count== netLgrCount) && (it.first == ledgerID))) { netLgr = it.first; - netLgrCount = it.second.first; + netLgrCount = it.second.count; } } @@ -269,7 +271,7 @@ RCLConsensus::getPrevLedger( if (auto stream = j_.debug()) { for (auto& it : vals) - stream << "V: " << it.first << ", " << it.second.first; + stream << "V: " << it.first << ", " << it.second.count; stream << getJson(true); } } @@ -317,14 +319,10 @@ RCLConsensus::onClose( { // previous ledger was flag ledger, add pseudo-transactions auto const validations = - app_.getValidations().getValidations(prevLedger->info().parentHash); + app_.getValidations().getTrustedForLedger ( + prevLedger->info().parentHash); - std::size_t const count = std::count_if( - validations.begin(), validations.end(), [](auto const& v) { - return v.second->isTrusted(); - }); - - if (count >= app_.validators().quorum()) + if (validations.size() >= app_.validators ().quorum ()) { feeVote_->doVoting(prevLedger, validations, initialSet); app_.getAmendmentTable().doVoting( @@ -843,7 +841,7 @@ RCLConsensus::validate(RCLCxLedger const& ledger, bool proposing) v->setTrusted(); // suppress it if we receive it - FIXME: wrong suppression app_.getHashRouter().addSuppression(signingHash); - app_.getValidations().addValidation(v, "local"); + handleNewValidation(app_, v, "local"); Blob validation = v->getSerialized(); protocol::TMValidation val; val.set_validation(&validation[0], validation.size()); diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp new file mode 100644 index 000000000..f1de8f55a --- /dev/null +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -0,0 +1,280 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +RCLValidationsPolicy::RCLValidationsPolicy(Application& app) : app_(app) +{ + staleValidations_.reserve(512); +} + +NetClock::time_point +RCLValidationsPolicy::now() const +{ + return app_.timeKeeper().closeTime(); +} + +void +RCLValidationsPolicy::onStale(RCLValidation&& v) +{ + // Store the newly stale validation; do not do significant work in this + // function since this is a callback from Validations, which may be + // doing other work. + + ScopedLockType sl(staleLock_); + staleValidations_.emplace_back(std::move(v)); + if (staleWriting_) + return; + + staleWriting_ = true; + app_.getJobQueue().addJob( + jtWRITE, "Validations::doStaleWrite", [this](Job&) { + auto event = + app_.getJobQueue().makeLoadEvent(jtDISK, "ValidationWrite"); + ScopedLockType sl(staleLock_); + doStaleWrite(sl); + }); +} + +void +RCLValidationsPolicy::flush(hash_map&& remaining) +{ + bool anyNew = false; + { + ScopedLockType sl(staleLock_); + + for (auto const& keyVal : remaining) + { + staleValidations_.emplace_back(std::move(keyVal.second)); + anyNew = true; + } + + // If we have new validations to write and there isn't a write in + // progress already, then write to the database synchronously. + if (anyNew && !staleWriting_) + { + staleWriting_ = true; + doStaleWrite(sl); + } + + // In the case when a prior asynchronous doStaleWrite was scheduled, + // this loop will block until all validations have been flushed. + // This ensures that all validations are written upon return from + // this function. + + while (staleWriting_) + { + ScopedUnlockType sul(staleLock_); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } +} + +// NOTE: doStaleWrite() must be called with staleLock_ *locked*. The passed +// ScopedLockType& acts as a reminder to future maintainers. +void +RCLValidationsPolicy::doStaleWrite(ScopedLockType&) +{ + static const std::string insVal( + "INSERT INTO Validations " + "(InitialSeq, LedgerSeq, LedgerHash,NodePubKey,SignTime,RawData) " + "VALUES (:initialSeq, :ledgerSeq, " + ":ledgerHash,:nodePubKey,:signTime,:rawData);"); + static const std::string findSeq( + "SELECT LedgerSeq FROM Ledgers WHERE Ledgerhash=:ledgerHash;"); + + assert(staleWriting_); + + while (!staleValidations_.empty()) + { + std::vector currentStale; + currentStale.reserve(512); + staleValidations_.swap(currentStale); + + { + ScopedUnlockType sul(staleLock_); + { + auto db = app_.getLedgerDB().checkoutDb(); + + Serializer s(1024); + soci::transaction tr(*db); + for (auto const& rclValidation : currentStale) + { + s.erase(); + STValidation::pointer const& val = rclValidation.unwrap(); + val->add(s); + + auto const ledgerHash = to_string(val->getLedgerHash()); + + boost::optional ledgerSeq; + *db << findSeq, soci::use(ledgerHash), + soci::into(ledgerSeq); + + auto const initialSeq = ledgerSeq.value_or( + app_.getLedgerMaster().getCurrentLedgerIndex()); + auto const nodePubKey = toBase58( + TokenType::TOKEN_NODE_PUBLIC, val->getSignerPublic()); + auto const signTime = + val->getSignTime().time_since_epoch().count(); + + soci::blob rawData(*db); + rawData.append( + reinterpret_cast(s.peekData().data()), + s.peekData().size()); + assert(rawData.get_len() == s.peekData().size()); + + *db << insVal, soci::use(initialSeq), soci::use(ledgerSeq), + soci::use(ledgerHash), soci::use(nodePubKey), + soci::use(signTime), soci::use(rawData); + } + + tr.commit(); + } + } + } + + staleWriting_ = false; +} + +bool +handleNewValidation(Application& app, + STValidation::ref val, + std::string const& source) +{ + PublicKey const& signer = val->getSignerPublic(); + uint256 const& hash = val->getLedgerHash(); + + // Ensure validation is marked as trusted if signer currently trusted + boost::optional pubKey = app.validators().getTrustedKey(signer); + if (!val->isTrusted() && pubKey) + val->setTrusted(); + RCLValidations& validations = app.getValidations(); + + beast::Journal j = validations.journal(); + + // Do not process partial validations. + if (!val->isFull()) + { + const bool current = isCurrent( + validations.parms(), + app.timeKeeper().closeTime(), + val->getSignTime(), + val->getSeenTime()); + + JLOG(j.debug()) << "Val (partial) for " << hash << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " ignored " + << (val->isTrusted() ? "trusted/" : "UNtrusted/") + << (current ? "current" : "stale"); + + // Only forward if current and trusted + return current && val->isTrusted(); + } + + if (!val->isTrusted()) + { + JLOG(j.trace()) << "Node " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " not in UNL st=" + << val->getSignTime().time_since_epoch().count() + << ", hash=" << hash + << ", shash=" << val->getSigningHash() + << " src=" << source; + } + + // If not currently trusted, see if signer is currently listed + if (!pubKey) + pubKey = app.validators().getListedKey(signer); + + bool shouldRelay = false; + + // only add trusted or listed + if (pubKey) + { + using AddOutcome = RCLValidations::AddOutcome; + + AddOutcome const res = validations.add(*pubKey, val); + + // This is a duplicate validation + if (res == AddOutcome::repeat) + return false; + + // This validation replaced a prior one with the same sequence number + if (res == AddOutcome::sameSeq) + { + auto const seq = val->getFieldU32(sfLedgerSequence); + JLOG(j.warn()) << "Trusted node " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, *pubKey) + << " published multiple validations for ledger " + << seq; + } + + JLOG(j.debug()) << "Val for " << hash << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " added " + << (val->isTrusted() ? "trusted/" : "UNtrusted/") + << ((res == AddOutcome::current) ? "current" : "stale"); + + // Trusted current validations should be checked and relayed. + // Trusted validations with sameSeq replaced an older validation + // with that sequence number, so should still be checked and relayed. + if (val->isTrusted() && + (res == AddOutcome::current || res == AddOutcome::sameSeq)) + { + app.getLedgerMaster().checkAccept( + hash, val->getFieldU32(sfLedgerSequence)); + + shouldRelay = true; + } + } + else + { + JLOG(j.debug()) << "Val for " << hash << " from " + << toBase58(TokenType::TOKEN_NODE_PUBLIC, signer) + << " not added UNtrusted/"; + } + + // This currently never forwards untrusted validations, though we may + // reconsider in the future. From @JoelKatz: + // The idea was that we would have a certain number of validation slots with + // priority going to validators we trusted. Remaining slots might be + // allocated to validators that were listed by publishers we trusted but + // that we didn't choose to trust. The shorter term plan was just to forward + // untrusted validations if peers wanted them or if we had the + // ability/bandwidth to. None of that was implemented. + return shouldRelay; +} +} // namespace ripple diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h new file mode 100644 index 000000000..ec1939e8c --- /dev/null +++ b/src/ripple/app/consensus/RCLValidations.h @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED +#define RIPPLE_APP_CONSENSUSS_VALIDATIONS_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace ripple { + +class Application; + +/** Wrapper over STValidation for generic Validation code + + Wraps an STValidation::pointer for compatibility with the generic validation + code. +*/ +class RCLValidation +{ + STValidation::pointer val_; +public: + + /** Constructor + + @param v The validation to wrap. + */ + RCLValidation(STValidation::pointer const& v) : val_{v} + { + } + + /// Validated ledger's hash + uint256 + ledgerID() const + { + return val_->getLedgerHash(); + } + + /// Validated ledger's sequence number (0 if none) + std::uint32_t + seq() const + { + if(auto res = (*val_)[~sfLedgerSequence]) + return *res; + return 0; + } + + /// Validation's signing time + NetClock::time_point + signTime() const + { + return val_->getSignTime(); + } + + /// Validated ledger's first seen time + NetClock::time_point + seenTime() const + { + return val_->getSeenTime(); + } + + /// Public key of validator that published the validation + PublicKey + key() const + { + return val_->getSignerPublic(); + } + + /// NodeID of validator that published the validation + NodeID + nodeID() const + { + return val_->getNodeID(); + } + + /// Whether the validation is considered trusted. + bool + trusted() const + { + return val_->isTrusted(); + } + + /// Set the prior ledger hash this validation is following + void + setPreviousLedgerID(uint256 const& hash) + { + val_->setPreviousHash(hash); + } + + /// Get the prior ledger hash this validation is following + uint256 + getPreviousLedgerID() const + { + return val_->getPreviousHash(); + } + + /// Check whether the given hash matches this validation's prior hash + bool + isPreviousLedgerID(uint256 const& hash) const + { + return val_->isPreviousHash(hash); + } + + /// Get the load fee of the validation if it exists + boost::optional + loadFee() const + { + return ~(*val_)[~sfLoadFee]; + } + + /// Extract the underlying STValidation being wrapped + STValidation::pointer + unwrap() const + { + return val_; + } + +}; + +/** Implements the StalePolicy policy class for adapting Validations in the RCL + + Manages storing and writing stale RCLValidations to the sqlite DB. +*/ +class RCLValidationsPolicy +{ + using LockType = std::mutex; + using ScopedLockType = std::lock_guard; + using ScopedUnlockType = GenericScopedUnlock; + + Application& app_; + + // Lock for managing staleValidations_ and writing_ + std::mutex staleLock_; + std::vector staleValidations_; + bool staleWriting_ = false; + + // Write the stale validations to sqlite DB, the scoped lock argument + // is used to remind callers that the staleLock_ must be *locked* prior + // to making the call + void + doStaleWrite(ScopedLockType&); + +public: + + RCLValidationsPolicy(Application & app); + + /** Current time used to determine if validations are stale. + */ + NetClock::time_point + now() const; + + /** Handle a newly stale validation. + + @param v The newly stale validation + + @warning This should do minimal work, as it is expected to be called + by the generic Validations code while it may be holding an + internal lock + */ + void + onStale(RCLValidation&& v); + + /** Flush current validations to disk before shutdown. + + @param remaining The remaining validations to flush + */ + void + flush(hash_map && remaining); +}; + + +/// Alias for RCL-specific instantiation of generic Validations +using RCLValidations = + Validations; + +/** Handle a new validation + + 1. Set the trust status of a validation based on the validating node's + public key and this node's current UNL. + 2. Add the validation to the set of validations if current. + 3. If new and trusted, send the validation to the ledgerMaster. + + @param app Application object containing validations and ledgerMaster + @param val The validation to add + @param source Name associated with validation used in logging + + @return Whether the validation should be relayed +*/ +bool +handleNewValidation(Application & app, STValidation::ref val, std::string const& source); + + +} // namespace ripple + +#endif diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp index 6814c1d2e..a0e3f83b8 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -200,7 +200,7 @@ LedgerMaster::setValidLedger( if (! standalone_) { - times = app_.getValidations().getValidationTimes( + times = app_.getValidations().getTrustedValidationTimes( l->info().hash); } @@ -700,7 +700,7 @@ LedgerMaster::checkAccept (uint256 const& hash, std::uint32_t seq) return; valCount = - app_.getValidations().getTrustedValidationCount (hash); + app_.getValidations().numTrustedForLedger (hash); if (valCount >= app_.validators ().quorum ()) { @@ -764,8 +764,7 @@ LedgerMaster::checkAccept ( return; auto const minVal = getNeededValidations(); - auto const tvc = app_.getValidations().getTrustedValidationCount( - ledger->info().hash); + auto const tvc = app_.getValidations().numTrustedForLedger(ledger->info().hash); if (tvc < minVal) // nothing we can do { JLOG (m_journal.trace()) << @@ -791,7 +790,7 @@ LedgerMaster::checkAccept ( app_.getOrderBookDB().setup(ledger); } - std::uint64_t const base = app_.getFeeTrack().getLoadBase(); + std::uint32_t const base = app_.getFeeTrack().getLoadBase(); auto fees = app_.getValidations().fees (ledger->info().hash, base); { auto fees2 = app_.getValidations().fees ( @@ -799,7 +798,7 @@ LedgerMaster::checkAccept ( fees.reserve (fees.size() + fees2.size()); std::copy (fees2.begin(), fees2.end(), std::back_inserter(fees)); } - std::uint64_t fee; + std::uint32_t fee; if (! fees.empty()) { std::sort (fees.begin(), fees.end()); @@ -854,7 +853,7 @@ LedgerMaster::consensusBuilt( // maybe we saved up validations for some other ledger that can be auto const val = - app_.getValidations().getCurrentTrustedValidations(); + app_.getValidations().currentTrusted(); // Track validation counts with sequence numbers class valSeq diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 30c4f758c..d827ac496 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -328,7 +329,7 @@ public: std::unique_ptr m_amendmentTable; std::unique_ptr mFeeTrack; std::unique_ptr mHashRouter; - std::unique_ptr mValidations; + RCLValidations mValidations; std::unique_ptr m_loadManager; std::unique_ptr txQ_; DeadlineTimer m_sweepTimer; @@ -471,7 +472,8 @@ public: , mHashRouter (std::make_unique( stopwatch(), HashRouter::getDefaultHoldTime ())) - , mValidations (make_Validations (*this)) + , mValidations (ValidationParms(),stopwatch(), logs_->journal("Validations"), + *this) , m_loadManager (make_LoadManager (*this, *this, logs_->journal("LoadManager"))) @@ -670,9 +672,9 @@ public: return *mHashRouter; } - Validations& getValidations () override + RCLValidations& getValidations () override { - return *mValidations; + return mValidations; } ValidatorList& validators () override @@ -853,7 +855,7 @@ public: m_entropyTimer.cancel (); } - mValidations->flush (); + mValidations.flush (); validatorSites_->stop (); @@ -926,7 +928,7 @@ public: getNodeStore().sweep(); getLedgerMaster().sweep(); getTempNodeCache().sweep(); - getValidations().sweep(); + getValidations().expire(); getInboundLedgers().sweep(); m_acceptedLedgerCache.sweep(); family().treecache().sweep(); diff --git a/src/ripple/app/main/Application.h b/src/ripple/app/main/Application.h index a6033636c..589c18922 100644 --- a/src/ripple/app/main/Application.h +++ b/src/ripple/app/main/Application.h @@ -64,7 +64,7 @@ class STLedgerEntry; class TimeKeeper; class TransactionMaster; class TxQ; -class Validations; + class ValidatorList; class ValidatorSite; class Cluster; @@ -74,6 +74,13 @@ class SHAMapStore; using NodeCache = TaggedCache ; +template +class Validations; +class RCLValidation; +class RCLValidationsPolicy; +using RCLValidations = + Validations; + class Application : public beast::PropertyStream::Source { public: @@ -128,7 +135,7 @@ public: virtual ManifestCache& validatorManifests () = 0; virtual ManifestCache& publisherManifests () = 0; virtual Cluster& cluster () = 0; - virtual Validations& getValidations () = 0; + virtual RCLValidations& getValidations () = 0; virtual NodeStore::Database& getNodeStore () = 0; virtual InboundLedgers& getInboundLedgers () = 0; virtual InboundTransactions& getInboundTransactions () = 0; diff --git a/src/ripple/app/misc/AmendmentTable.h b/src/ripple/app/misc/AmendmentTable.h index 50f722e28..2cad2343b 100644 --- a/src/ripple/app/misc/AmendmentTable.h +++ b/src/ripple/app/misc/AmendmentTable.h @@ -21,7 +21,7 @@ #define RIPPLE_APP_MISC_AMENDMENTTABLE_H_INCLUDED #include -#include +#include #include #include @@ -78,7 +78,7 @@ public: NetClock::time_point closeTime, std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, - ValidationSet const& valSet) = 0; + std::vector const& valSet) = 0; // Called by the consensus code when we need to // add feature entries to a validation @@ -112,7 +112,7 @@ public: void doVoting ( std::shared_ptr const& lastClosedLedger, - ValidationSet const& parentValidations, + std::vector const& parentValidations, std::shared_ptr const& initialPosition) { // Ask implementation what to do diff --git a/src/ripple/app/misc/FeeVote.h b/src/ripple/app/misc/FeeVote.h index ea271545e..4e8377303 100644 --- a/src/ripple/app/misc/FeeVote.h +++ b/src/ripple/app/misc/FeeVote.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -72,7 +72,7 @@ public: virtual void doVoting (std::shared_ptr const& lastClosedLedger, - ValidationSet const& parentValidations, + std::vector const& parentValidations, std::shared_ptr const& initialPosition) = 0; }; diff --git a/src/ripple/app/misc/FeeVoteImpl.cpp b/src/ripple/app/misc/FeeVoteImpl.cpp index 221d43df6..9a24aca59 100644 --- a/src/ripple/app/misc/FeeVoteImpl.cpp +++ b/src/ripple/app/misc/FeeVoteImpl.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include @@ -103,7 +103,7 @@ public: void doVoting (std::shared_ptr const& lastClosedLedger, - ValidationSet const& parentValidations, + std::vector const& parentValidations, std::shared_ptr const& initialPosition) override; }; @@ -149,8 +149,8 @@ FeeVoteImpl::doValidation( void FeeVoteImpl::doVoting( std::shared_ptr const& lastClosedLedger, - ValidationSet const& set, - std::shared_ptr const& initialPosition) + std::vector const& set, + std::shared_ptr const& initialPosition) { // LCL must be flag ledger assert ((lastClosedLedger->info().seq % 256) == 0); @@ -164,33 +164,31 @@ FeeVoteImpl::doVoting( detail::VotableInteger incReserveVote ( lastClosedLedger->fees().increment, target_.owner_reserve); - for (auto const& e : set) + for (auto const& val : set) { - STValidation const& val = *e.second; - - if (val.isTrusted ()) + if (val->isTrusted ()) { - if (val.isFieldPresent (sfBaseFee)) + if (val->isFieldPresent (sfBaseFee)) { - baseFeeVote.addVote (val.getFieldU64 (sfBaseFee)); + baseFeeVote.addVote (val->getFieldU64 (sfBaseFee)); } else { baseFeeVote.noVote (); } - if (val.isFieldPresent (sfReserveBase)) + if (val->isFieldPresent (sfReserveBase)) { - baseReserveVote.addVote (val.getFieldU32 (sfReserveBase)); + baseReserveVote.addVote (val->getFieldU32 (sfReserveBase)); } else { baseReserveVote.noVote (); } - if (val.isFieldPresent (sfReserveIncrement)) + if (val->isFieldPresent (sfReserveIncrement)) { - incReserveVote.addVote (val.getFieldU32 (sfReserveIncrement)); + incReserveVote.addVote (val->getFieldU32 (sfReserveIncrement)); } else { diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 231f31a5b..4c9690ebf 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1301,17 +1302,17 @@ bool NetworkOPsImp::checkLastClosedLedger ( hash_map ledgers; { - auto current = app_.getValidations ().getCurrentValidations ( + auto current = app_.getValidations ().currentTrustedDistribution ( closedLedger, prevClosedLedger, m_ledgerMaster.getValidLedgerIndex()); for (auto& it: current) { auto& vc = ledgers[it.first]; - vc.trustedValidations += it.second.first; + vc.trustedValidations += it.second.count; - if (it.second.second > vc.highValidation) - vc.highValidation = it.second.second; + if (it.second.highNode > vc.highValidation) + vc.highValidation = it.second.highNode; } } @@ -2103,7 +2104,7 @@ bool NetworkOPsImp::recvValidation ( JLOG(m_journal.debug()) << "recvValidation " << val->getLedgerHash () << " from " << source; pubValidation (val); - return app_.getValidations ().addValidation (val, source); + return handleNewValidation(app_, val, source); } Json::Value NetworkOPsImp::getConsensusInfo () diff --git a/src/ripple/app/misc/Validations.cpp b/src/ripple/app/misc/Validations.cpp deleted file mode 100644 index 9f11aae5c..000000000 --- a/src/ripple/app/misc/Validations.cpp +++ /dev/null @@ -1,557 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ripple { - -class ValidationsImp : public Validations -{ -private: - using LockType = std::mutex; - using ScopedLockType = std::lock_guard ; - using ScopedUnlockType = GenericScopedUnlock ; - - Application& app_; - std::mutex mutable mLock; - - TaggedCache mValidations; - ValidationSet mCurrentValidations; - std::vector mStaleValidations; - - bool mWriting; - beast::Journal j_; - -private: - std::shared_ptr findCreateSet (uint256 const& ledgerHash) - { - auto j = mValidations.fetch (ledgerHash); - - if (!j) - { - j = std::make_shared (); - mValidations.canonicalize (ledgerHash, j); - } - - return j; - } - - std::shared_ptr findSet (uint256 const& ledgerHash) - { - return mValidations.fetch (ledgerHash); - } - -public: - explicit - ValidationsImp (Application& app) - : app_ (app) - , mValidations ("Validations", 4096, 600, stopwatch(), - app.journal("TaggedCache")) - , mWriting (false) - , j_ (app.journal ("Validations")) - { - mStaleValidations.reserve (512); - } - -private: - bool addValidation (STValidation::ref val, std::string const& source) override - { - auto signer = val->getSignerPublic (); - auto hash = val->getLedgerHash (); - bool isCurrent = current (val); - - auto pubKey = app_.validators ().getTrustedKey (signer); - - if (!val->isTrusted() && pubKey) - val->setTrusted(); - - // Do not process partial validations. - if(!val->isFull()) - { - JLOG (j_.debug()) << - "Val (partial) for " << hash << - " from " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) << - " ignored " << (val->isTrusted () ? "trusted/" : "UNtrusted/") << - (isCurrent ? "current" : "stale"); - - // Only forward if current - return isCurrent && val->isTrusted(); - } - - if (!val->isTrusted ()) - { - JLOG (j_.trace()) << - "Node " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) << - " not in UNL st=" << val->getSignTime().time_since_epoch().count() << - ", hash=" << hash << - ", shash=" << val->getSigningHash () << - " src=" << source; - } - - if (! pubKey) - pubKey = app_.validators ().getListedKey (signer); - - if (isCurrent && - (val->isTrusted () || pubKey)) - { - ScopedLockType sl (mLock); - - if (!findCreateSet (hash)->insert ( - std::make_pair (*pubKey, val)).second) - return false; - - auto it = mCurrentValidations.find (*pubKey); - - if (it == mCurrentValidations.end ()) - { - // No previous validation from this validator - mCurrentValidations.emplace (*pubKey, val); - } - else if (!it->second) - { - // Previous validation has expired - it->second = val; - } - else - { - auto const oldSeq = (*it->second)[~sfLedgerSequence]; - auto const newSeq = (*val)[~sfLedgerSequence]; - - if (oldSeq && newSeq && *oldSeq == *newSeq) - { - JLOG (j_.warn()) << - "Trusted node " << - toBase58 (TokenType::TOKEN_NODE_PUBLIC, *pubKey) << - " published multiple validations for ledger " << - *oldSeq; - - // Remove current validation for the revoked signing key - if (signer != it->second->getSignerPublic()) - { - auto set = findSet (it->second->getLedgerHash ()); - if (set) - set->erase (*pubKey); - } - } - - if (val->getSignTime () > it->second->getSignTime () || - signer != it->second->getSignerPublic()) - { - // This is either a newer validation or a new signing key - val->setPreviousHash (it->second->getLedgerHash ()); - mStaleValidations.push_back (it->second); - it->second = val; - condWrite (); - } - else - { - // We already have a newer validation from this source - isCurrent = false; - } - } - } - - JLOG (j_.debug()) << - "Val for " << hash << - " from " << toBase58 (TokenType::TOKEN_NODE_PUBLIC, signer) << - " added " << (val->isTrusted () ? "trusted/" : "UNtrusted/") << - (isCurrent ? "current" : "stale"); - - if (val->isTrusted () && isCurrent) - { - app_.getLedgerMaster ().checkAccept (hash, val->getFieldU32 (sfLedgerSequence)); - return true; - } - - // FIXME: This never forwards untrusted validations - return false; - } - - ValidationSet getValidations (uint256 const& ledger) override - { - { - ScopedLockType sl (mLock); - auto set = findSet (ledger); - - if (set) - return *set; - } - return ValidationSet (); - } - - bool current (STValidation::ref val) override - { - // Because this can be called on untrusted, possibly - // malicious validations, we do our math in a way - // that avoids any chance of overflowing or underflowing - // the signing time. - - auto const now = app_.timeKeeper().closeTime(); - auto const signTime = val->getSignTime(); - - return - (signTime > (now - VALIDATION_VALID_EARLY)) && - (signTime < (now + VALIDATION_VALID_WALL)) && - ((val->getSeenTime() == NetClock::time_point{}) || - (val->getSeenTime() < (now + VALIDATION_VALID_LOCAL))); - } - - std::size_t - getTrustedValidationCount (uint256 const& ledger) override - { - std::size_t trusted = 0; - ScopedLockType sl (mLock); - auto set = findSet (ledger); - - if (set) - { - for (auto& it: *set) - { - if (it.second->isTrusted ()) - ++trusted; - } - } - - return trusted; - } - - std::vector - fees (uint256 const& ledger, std::uint64_t base) override - { - std::vector result; - std::lock_guard lock (mLock); - auto const set = findSet (ledger); - if (set) - { - for (auto const& v : *set) - { - if (v.second->isTrusted()) - { - if (v.second->isFieldPresent(sfLoadFee)) - result.push_back(v.second->getFieldU32(sfLoadFee)); - else - result.push_back(base); - } - } - } - - return result; - } - - int getNodesAfter (uint256 const& ledger) override - { - // Number of trusted nodes that have moved past this ledger - int count = 0; - ScopedLockType sl (mLock); - for (auto& it: mCurrentValidations) - { - if (it.second->isTrusted () && it.second->isPreviousHash (ledger)) - ++count; - } - return count; - } - - int getLoadRatio (bool overLoaded) override - { - // how many trusted nodes are able to keep up, higher is better - int goodNodes = overLoaded ? 1 : 0; - int badNodes = overLoaded ? 0 : 1; - { - ScopedLockType sl (mLock); - for (auto& it: mCurrentValidations) - { - if (it.second->isTrusted ()) - { - if (it.second->isFull ()) - ++goodNodes; - else - ++badNodes; - } - } - } - return (goodNodes * 100) / (goodNodes + badNodes); - } - - std::list getCurrentTrustedValidations () override - { - std::list ret; - - ScopedLockType sl (mLock); - auto it = mCurrentValidations.begin (); - - while (it != mCurrentValidations.end ()) - { - if (!it->second) // contains no record - it = mCurrentValidations.erase (it); - else if (! current (it->second)) - { - // contains a stale record - mStaleValidations.push_back (it->second); - it->second.reset (); - condWrite (); - it = mCurrentValidations.erase (it); - } - else - { - // contains a live record - if (it->second->isTrusted ()) - ret.push_back (it->second); - - ++it; - } - } - - return ret; - } - - hash_set getCurrentPublicKeys () override - { - hash_set ret; - - ScopedLockType sl (mLock); - auto it = mCurrentValidations.begin (); - - while (it != mCurrentValidations.end ()) - { - if (!it->second) // contains no record - it = mCurrentValidations.erase (it); - else if (! current (it->second)) - { - // contains a stale record - mStaleValidations.push_back (it->second); - it->second.reset (); - condWrite (); - it = mCurrentValidations.erase (it); - } - else - { - // contains a live record - ret.insert (it->first); - - ++it; - } - } - - return ret; - } - - LedgerToValidationCounter getCurrentValidations ( - uint256 currentLedger, - uint256 priorLedger, - LedgerIndex cutoffBefore) override - { - bool valCurrentLedger = currentLedger.isNonZero (); - bool valPriorLedger = priorLedger.isNonZero (); - - LedgerToValidationCounter ret; - - ScopedLockType sl (mLock); - auto it = mCurrentValidations.begin (); - - while (it != mCurrentValidations.end ()) - { - if (!it->second) // contains no record - it = mCurrentValidations.erase (it); - else if (! current (it->second)) - { - // contains a stale record - mStaleValidations.push_back (it->second); - it->second.reset (); - condWrite (); - it = mCurrentValidations.erase (it); - } - else if (! it->second->isTrusted()) - ++it; - else if (! it->second->isFieldPresent (sfLedgerSequence) || - (it->second->getFieldU32 (sfLedgerSequence) >= cutoffBefore)) - { - // contains a live record - bool countPreferred = valCurrentLedger && (it->second->getLedgerHash () == currentLedger); - - if (!countPreferred && // allow up to one ledger slip in either direction - ((valCurrentLedger && it->second->isPreviousHash (currentLedger)) || - (valPriorLedger && (it->second->getLedgerHash () == priorLedger)))) - { - countPreferred = true; - JLOG (j_.trace()) << "Counting for " << currentLedger << " not " << it->second->getLedgerHash (); - } - - ValidationCounter& p = countPreferred ? ret[currentLedger] : ret[it->second->getLedgerHash ()]; - ++ (p.first); - auto ni = it->second->getNodeID (); - - if (ni > p.second) - p.second = ni; - - ++it; - } - else - { - ++it; - } - } - - return ret; - } - - std::vector - getValidationTimes (uint256 const& hash) override - { - std::vector times; - ScopedLockType sl (mLock); - if (auto j = findSet (hash)) - for (auto& it : *j) - if (it.second->isTrusted()) - times.push_back (it.second->getSignTime()); - return times; - } - - void flush () override - { - bool anyNew = false; - - JLOG (j_.info()) << "Flushing validations"; - ScopedLockType sl (mLock); - for (auto& it: mCurrentValidations) - { - if (it.second) - mStaleValidations.push_back (it.second); - - anyNew = true; - } - mCurrentValidations.clear (); - - if (anyNew) - condWrite (); - - while (mWriting) - { - ScopedUnlockType sul (mLock); - std::this_thread::sleep_for (std::chrono::milliseconds (100)); - } - - JLOG (j_.debug()) << "Validations flushed"; - } - - void condWrite () - { - if (mWriting) - return; - - mWriting = true; - app_.getJobQueue ().addJob ( - jtWRITE, "Validations::doWrite", - [this] (Job&) { doWrite(); }); - } - - void doWrite () - { - auto event = app_.getJobQueue ().makeLoadEvent (jtDISK, "ValidationWrite"); - - std::string insVal ("INSERT INTO Validations " - "(InitialSeq, LedgerSeq, LedgerHash,NodePubKey,SignTime,RawData) " - "VALUES (:initialSeq, :ledgerSeq, :ledgerHash,:nodePubKey,:signTime,:rawData);"); - std::string findSeq("SELECT LedgerSeq FROM Ledgers WHERE Ledgerhash=:ledgerHash;"); - - ScopedLockType sl (mLock); - assert (mWriting); - - while (!mStaleValidations.empty ()) - { - std::vector vector; - vector.reserve (512); - mStaleValidations.swap (vector); - - { - ScopedUnlockType sul (mLock); - { - auto db = app_.getLedgerDB ().checkoutDb (); - - Serializer s (1024); - soci::transaction tr(*db); - for (auto it: vector) - { - s.erase (); - it->add (s); - - auto const ledgerHash = to_string(it->getLedgerHash()); - - boost::optional ledgerSeq; - *db << findSeq, soci::use(ledgerHash), - soci::into(ledgerSeq); - - auto const initialSeq = ledgerSeq.value_or( - app_.getLedgerMaster().getCurrentLedgerIndex()); - auto const nodePubKey = toBase58( - TokenType::TOKEN_NODE_PUBLIC, - it->getSignerPublic()); - auto const signTime = - it->getSignTime().time_since_epoch().count(); - - soci::blob rawData(*db); - rawData.append(reinterpret_cast( - s.peekData().data()), s.peekData().size()); - assert(rawData.get_len() == s.peekData().size()); - - *db << - insVal, - soci::use(initialSeq), - soci::use(ledgerSeq), - soci::use(ledgerHash), - soci::use(nodePubKey), - soci::use(signTime), - soci::use(rawData); - } - - tr.commit (); - } - } - } - - mWriting = false; - } - - void sweep () override - { - ScopedLockType sl (mLock); - mValidations.sweep (); - } -}; - -std::unique_ptr make_Validations (Application& app) -{ - return std::make_unique (app); -} - -} // ripple diff --git a/src/ripple/app/misc/Validations.h b/src/ripple/app/misc/Validations.h deleted file mode 100644 index af6285d31..000000000 --- a/src/ripple/app/misc/Validations.h +++ /dev/null @@ -1,85 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#ifndef RIPPLE_APP_MISC_VALIDATIONS_H_INCLUDED -#define RIPPLE_APP_MISC_VALIDATIONS_H_INCLUDED - -#include -#include -#include -#include -#include - -namespace ripple { - -// VFALCO TODO rename and move these type aliases into the Validations interface - -// nodes validating and highest node ID validating -using ValidationSet = hash_map; - -using ValidationCounter = std::pair; -using LedgerToValidationCounter = hash_map; - -class Validations -{ -public: - virtual ~Validations() = default; - - virtual bool addValidation (STValidation::ref, std::string const& source) = 0; - - virtual bool current (STValidation::ref) = 0; - - virtual ValidationSet getValidations (uint256 const& ledger) = 0; - - virtual std::size_t getTrustedValidationCount (uint256 const& ledger) = 0; - - /** Returns fees reported by trusted validators in the given ledger. */ - virtual - std::vector - fees (uint256 const& ledger, std::uint64_t base) = 0; - - virtual int getNodesAfter (uint256 const& ledger) = 0; - virtual int getLoadRatio (bool overLoaded) = 0; - - virtual hash_set getCurrentPublicKeys () = 0; - - // VFALCO TODO make a type alias for this ugly return value! - virtual LedgerToValidationCounter getCurrentValidations ( - uint256 currentLedger, uint256 previousLedger, - LedgerIndex cutoffBefore) = 0; - - /** Return the times of all validations for a particular ledger hash. */ - virtual std::vector getValidationTimes ( - uint256 const& ledger) = 0; - - virtual std::list - getCurrentTrustedValidations () = 0; - - virtual void flush () = 0; - - virtual void sweep () = 0; -}; - -extern -std::unique_ptr -make_Validations(Application& app); - -} // ripple - -#endif diff --git a/src/ripple/app/misc/impl/AmendmentTable.cpp b/src/ripple/app/misc/impl/AmendmentTable.cpp index 10820e23b..9be2cb183 100644 --- a/src/ripple/app/misc/impl/AmendmentTable.cpp +++ b/src/ripple/app/misc/impl/AmendmentTable.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -207,7 +207,7 @@ public: NetClock::time_point closeTime, std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, - ValidationSet const& validations) override; + std::vector const& validations) override; }; //------------------------------------------------------------------------------ @@ -411,7 +411,7 @@ AmendmentTableImpl::doVoting ( NetClock::time_point closeTime, std::set const& enabledAmendments, majorityAmendments_t const& majorityAmendments, - ValidationSet const& valSet) + std::vector const& valSet) { JLOG (j_.trace()) << "voting at " << closeTime.time_since_epoch().count() << @@ -422,16 +422,16 @@ AmendmentTableImpl::doVoting ( auto vote = std::make_unique (); // process validations for ledger before flag ledger - for (auto const& entry : valSet) + for (auto const& val : valSet) { - if (entry.second->isTrusted ()) + if (val->isTrusted ()) { std::set ballot; - if (entry.second->isFieldPresent (sfAmendments)) + if (val->isFieldPresent (sfAmendments)) { auto const choices = - entry.second->getFieldV256 (sfAmendments); + val->getFieldV256 (sfAmendments); ballot.insert (choices.begin (), choices.end ()); } diff --git a/src/ripple/consensus/LedgerTiming.h b/src/ripple/consensus/LedgerTiming.h index db135031b..19f2914d0 100644 --- a/src/ripple/consensus/LedgerTiming.h +++ b/src/ripple/consensus/LedgerTiming.h @@ -55,29 +55,6 @@ auto constexpr decreaseLedgerTimeResolutionEvery = 1; //! The number of seconds a ledger may remain idle before closing auto constexpr LEDGER_IDLE_INTERVAL = 15s; -/** The number of seconds a validation remains current after its ledger's close - time. - - This is a safety to protect against very old validations and the time - it takes to adjust the close time accuracy window. -*/ -auto constexpr VALIDATION_VALID_WALL = 5min; - -/** Duration a validation remains current after first observed. - - The number of seconds a validation remains current after the time we first - saw it. This provides faster recovery in very rare cases where the number - of validations produced by the network is lower than normal -*/ -auto constexpr VALIDATION_VALID_LOCAL = 3min; - -/** Duration pre-close in which validations are acceptable. - - The number of seconds before a close time that we consider a validation - acceptable. This protects against extreme clock errors -*/ -auto constexpr VALIDATION_VALID_EARLY = 3min; - //! The number of seconds we wait minimum to ensure participation auto constexpr LEDGER_MIN_CONSENSUS = 1950ms; diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h new file mode 100644 index 000000000..78bcd3761 --- /dev/null +++ b/src/ripple/consensus/Validations.h @@ -0,0 +1,726 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_CONSENSUS_VALIDATIONS_H_INCLUDED +#define RIPPLE_CONSENSUS_VALIDATIONS_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { + +/** Timing parameters to control validation staleness and expiration. + + @note These are protocol level parameters that should not be changed without + careful consideration. They are *not* implemented as static constexpr + to allow simulation code to test alternate parameter settings. + */ +struct ValidationParms +{ + /** The number of seconds a validation remains current after its ledger's + close time. + + This is a safety to protect against very old validations and the time + it takes to adjust the close time accuracy window. + */ + std::chrono::seconds validationCURRENT_WALL = std::chrono::minutes{5}; + + /** Duration a validation remains current after first observed. + + The number of seconds a validation remains current after the time we + first saw it. This provides faster recovery in very rare cases where the + number of validations produced by the network is lower than normal + */ + std::chrono::seconds validationCURRENT_LOCAL = std::chrono::minutes{3}; + + /** Duration pre-close in which validations are acceptable. + + The number of seconds before a close time that we consider a validation + acceptable. This protects against extreme clock errors + */ + std::chrono::seconds validationCURRENT_EARLY = std::chrono::minutes{3}; + + /** Duration a set of validations for a given ledger hash remain valid + + The number of seconds before a set of validations for a given ledger + hash can expire. This keeps validations for recent ledgers available + for a reasonable interval. + */ + std::chrono::seconds validationSET_EXPIRES = std::chrono::minutes{10}; +}; + +/** Whether a validation is still current + + Determines whether a validation can still be considered the current + validation from a node based on when it was signed by that node and first + seen by this node. + + @param p ValidationParms with timing parameters + @param now Current time + @param signTime When the validation was signed + @param seenTime When the validation was first seen locally +*/ +inline bool +isCurrent( + ValidationParms const& p, + NetClock::time_point now, + NetClock::time_point signTime, + NetClock::time_point seenTime) +{ + // Because this can be called on untrusted, possibly + // malicious validations, we do our math in a way + // that avoids any chance of overflowing or underflowing + // the signing time. + + return (signTime > (now - p.validationCURRENT_EARLY)) && + (signTime < (now + p.validationCURRENT_WALL)) && + ((seenTime == NetClock::time_point{}) || + (seenTime < (now + p.validationCURRENT_LOCAL))); +} + +/** Maintains current and recent ledger validations. + + Manages storage and queries related to validations received on the network. + Stores the most current validation from nodes and sets of recent + validations grouped by ledger identifier. + + Stored validations are not necessarily from trusted nodes, so clients + and implementations should take care to use `trusted` member functions or + check the validation's trusted status. + + This class uses a policy design to allow adapting the handling of stale + validations in various circumstances. Below is a set of stubs illustrating + the required type interface. + + @warning The MutexType is used to manage concurrent access to private + members of Validations but does not manage any data in the + StalePolicy instance. + + @code + + // Identifier types that should be equality-comparable and copyable + struct LedgerID; + struct NodeID; + struct NodeKey; + + struct Validation + { + // Ledger ID associated with this validation + LedgerID ledgerID() const; + + // Sequence number of validation's ledger (0 means no sequence number) + std::uint32_t seq() const + + // When the validation was signed + NetClock::time_point signTime() const; + + // When the validation was first observed by this node + NetClock::time_point seenTime() const; + + // Signing key of node that published the validation + NodeKey key() const; + + // Identifier of node that published the validation + NodeID nodeID() const; + + // Whether the publishing node was trusted at the time the validation + // arrived + bool trusted() const; + + // The PreviousLedger functions below assume instances corresponding + // to the same validation (ID/hash/key etc.) share the same previous + // ledger ID storage. + + // Set the previous validation ledger from this publishing node that + // this validation replaced + void setPreviousLedgerID(LedgerID &); + + // Get the previous validation ledger from this publishing node that + // this validation replaced + LedgerID getPreviousLedgerID() const; + + // Check if this validation had the given ledger ID as its prior ledger + bool isPreviousLedgerID(LedgerID const& ) const; + + implementation_specific_t + unwrap() -> return the implementation-specific type being wrapped + + // ... implementation specific + }; + + class StalePolicy + { + // Handle a newly stale validation, this should do minimal work since + // it is called by Validations while it may be iterating Validations + // under lock + void onStale(Validation && ); + + // Flush the remaining validations (typically done on shutdown) + void flush(hash_map && remaining); + + // Return the current network time (used to determine staleness) + NetClock::time_point now() const; + + // ... implementation specific + }; + @endcode + + @tparam StalePolicy Determines how to determine and handle stale validations + @tparam Validation Conforming type representing a ledger validation + @tparam MutexType Mutex used to manage concurrent access + +*/ +template +class Validations +{ + template + using decay_result_t = std::decay_t>; + + using WrappedValidationType = + decay_result_t; + using LedgerID = + decay_result_t; + using NodeKey = decay_result_t; + using NodeID = decay_result_t; + using ValidationMap = hash_map; + + using ScopedLock = std::lock_guard; + + // Manages concurrent access to current_ and byLedger_ + MutexType mutex_; + + //! The latest validation from each node + ValidationMap current_; + + //! Recent validations from nodes, indexed by ledger identifier + beast::aged_unordered_map< + LedgerID, + ValidationMap, + std::chrono::steady_clock, + beast::uhash<>> + byLedger_; + + //! Parameters to determine validation staleness + ValidationParms const parms_; + + beast::Journal j_; + + //! StalePolicy details providing now(), onStale() and flush() callbacks + //! Is NOT managed by the mutex_ above + StalePolicy stalePolicy_; + +private: + /** Iterate current validations. + + Iterate current validations, optionally removing any stale validations + if a time is specified. + + @param t (Optional) Time used to determine staleness + @param pre Invokable with signature (std::size_t) called prior to + looping. + @param f Invokable with signature (NodeKey const &, Validations const &) + for each current validation. + + @note The invokable `pre` is called _prior_ to checking for staleness + and reflects an upper-bound on the number of calls to `f. + @warning The invokable `f` is expected to be a simple transformation of + its arguments and will be called with mutex_ under lock. + */ + + template + void + current(boost::optional t, Pre&& pre, F&& f) + { + ScopedLock lock{mutex_}; + pre(current_.size()); + auto it = current_.begin(); + while (it != current_.end()) + { + // Check for staleness, if time specified + if (t && + !isCurrent( + parms_, *t, it->second.signTime(), it->second.seenTime())) + { + // contains a stale record + stalePolicy_.onStale(std::move(it->second)); + it = current_.erase(it); + } + else + { + auto cit = typename ValidationMap::const_iterator{it}; + // contains a live record + f(cit->first, cit->second); + ++it; + } + } + } + + /** Iterate the set of validations associated with a given ledger id + + @param ledgerID The identifier of the ledger + @param pre Invokable with signature(std::size_t) + @param f Invokable with signature (NodeKey const &, Validation const &) + + @note The invokable `pre` is called prior to iterating validations. The + argument is the number of times `f` will be called. + @warning The invokable f is expected to be a simple transformation of + its arguments and will be called with mutex_ under lock. + */ + template + void + byLedger(LedgerID const& ledgerID, Pre&& pre, F&& f) + { + ScopedLock lock{mutex_}; + auto it = byLedger_.find(ledgerID); + if (it != byLedger_.end()) + { + // Update set time since it is being used + byLedger_.touch(it); + pre(it->second.size()); + for (auto const& keyVal : it->second) + f(keyVal.first, keyVal.second); + } + } + +public: + /** Constructor + + @param p ValidationParms to control staleness/expiration of validaitons + @param c Clock to use for expiring validations stored by ledger + @param j Journal used for logging + @param ts Parameters for constructing StalePolicy instance + */ + template + Validations( + ValidationParms const& p, + beast::abstract_clock& c, + beast::Journal j, + Ts&&... ts) + : byLedger_(c), parms_(p), j_(j), stalePolicy_(std::forward(ts)...) + { + } + + /** Return the validation timing parameters + */ + ValidationParms const& + parms() const + { + return parms_; + } + + /** Return the journal + */ + beast::Journal + journal() const + { + return j_; + } + + /** Result of adding a new validation + */ + enum class AddOutcome { + /// This was a new validation and was added + current, + /// Already had this validation + repeat, + /// Not current or was older than current from this node + stale, + /// Had a validation with same sequence number + sameSeq, + }; + + /** Add a new validation + + Attempt to add a new validation. + + @param key The NodeKey to use for the validation + @param val The validation to store + @return The outcome of the attempt + + @note The provided key may differ from the validation's + key() member since we might be storing by master key and the + validation might be signed by a temporary or rotating key. + + */ + AddOutcome + add(NodeKey const& key, Validation const& val) + { + NetClock::time_point t = stalePolicy_.now(); + if (!isCurrent(parms_, t, val.signTime(), val.seenTime())) + return AddOutcome::stale; + + LedgerID const& id = val.ledgerID(); + + // This is only seated if a validation became stale + boost::optional maybeStaleValidation; + + AddOutcome result = AddOutcome::current; + + { + ScopedLock lock{mutex_}; + + auto const ret = byLedger_[id].emplace(key, val); + + // This validation is a repeat if we already have + // one with the same id and signing key. + if (!ret.second && ret.first->second.key() == val.key()) + return AddOutcome::repeat; + + // Attempt to insert + auto const ins = current_.emplace(key, val); + + if (!ins.second) + { + // Had a previous validation from the node, consider updating + Validation& oldVal = ins.first->second; + + std::uint32_t const oldSeq{oldVal.seq()}; + std::uint32_t const newSeq{val.seq()}; + + // Sequence of 0 indicates a missing sequence number + if (oldSeq && newSeq && oldSeq == newSeq) + { + result = AddOutcome::sameSeq; + + // If the validation key was revoked, update the + // existing validation in the byLedger_ set + if (val.key() != oldVal.key()) + { + auto const mapIt = byLedger_.find(oldVal.ledgerID()); + if (mapIt != byLedger_.end()) + { + ValidationMap& validationMap = mapIt->second; + // If a new validation with the same ID was + // reissued we simply replace. + if(oldVal.ledgerID() == val.ledgerID()) + { + auto replaceRes = validationMap.emplace(key, val); + // If it was already there, replace + if(!replaceRes.second) + replaceRes.first->second = val; + } + else + { + // If the new validation has a different ID, + // we remove the old. + validationMap.erase(key); + // Erase the set if it is now empty + if (validationMap.empty()) + byLedger_.erase(mapIt); + } + } + } + } + + if (val.signTime() > oldVal.signTime() || + val.key() != oldVal.key()) + { + // This is either a newer validation or a new signing key + LedgerID const prevID = [&]() { + // In the normal case, the prevID is the ID of the + // ledger we replace + if (oldVal.ledgerID() != val.ledgerID()) + return oldVal.ledgerID(); + // In the case the key was revoked and a new validation + // for the same ledger ID was sent, the previous ledger + // is still the one the now revoked validation had + return oldVal.getPreviousLedgerID(); + }(); + + // Allow impl to take over oldVal + maybeStaleValidation.emplace(std::move(oldVal)); + // Replace old val in the map and set the previous ledger ID + ins.first->second = val; + ins.first->second.setPreviousLedgerID(prevID); + } + else + { + // We already have a newer validation from this source + result = AddOutcome::stale; + } + } + } + + // Handle the newly stale validation outside the lock + if (maybeStaleValidation) + { + stalePolicy_.onStale(std::move(*maybeStaleValidation)); + } + + return result; + } + + /** Expire old validation sets + + Remove validation sets that were accessed more than + validationSET_EXPIRES ago. + */ + void + expire() + { + ScopedLock lock{mutex_}; + beast::expire(byLedger_, parms_.validationSET_EXPIRES); + } + + struct ValidationCounts + { + //! The number of trusted validations + std::size_t count; + //! The highest trusted node ID + NodeID highNode; + }; + + /** Distribution of current trusted validations + + Calculates the distribution of current validations but allows + ledgers one away from the current ledger to count as the current. + + @param currentLedger The identifier of the ledger we believe is current + @param priorLedger The identifier of our previous current ledger + @param cutoffBefore Ignore ledgers with sequence number before this + */ + hash_map + currentTrustedDistribution( + LedgerID const& currentLedger, + LedgerID const& priorLedger, + std::uint32_t cutoffBefore) + { + bool const valCurrentLedger = currentLedger != beast::zero; + bool const valPriorLedger = priorLedger != beast::zero; + + hash_map ret; + + current( + stalePolicy_.now(), + // The number of validations does not correspond to the number of + // distinct ledgerIDs so we do not call reserve on ret. + [&](std::size_t) {}, + [&](NodeKey const&, Validation const& v) { + + if (!v.trusted()) + return; + + std::uint32_t const seq = v.seq(); + if ((seq == 0) || (seq >= cutoffBefore)) + { + // contains a live record + bool countPreferred = + valCurrentLedger && (v.ledgerID() == currentLedger); + + if (!countPreferred && // allow up to one ledger slip in + // either direction + ((valCurrentLedger && + v.isPreviousLedgerID(currentLedger)) || + (valPriorLedger && (v.ledgerID() == priorLedger)))) + { + countPreferred = true; + JLOG(j_.trace()) << "Counting for " << currentLedger + << " not " << v.ledgerID(); + } + + ValidationCounts& p = + countPreferred ? ret[currentLedger] : ret[v.ledgerID()]; + ++(p.count); + + NodeID const ni = v.nodeID(); + if (ni > p.highNode) + p.highNode = ni; + } + }); + + return ret; + } + + /** Count the number of current trusted validators working on the next + ledger. + + Counts the number of current trusted validations that replaced the + provided ledger. Does not check or update staleness of the validations. + + @param ledgerID The identifier of the preceding ledger of interest + @return The number of current trusted validators with ledgerID as the + prior ledger. + */ + std::size_t + getNodesAfter(LedgerID const& ledgerID) + { + std::size_t count = 0; + + // Historically this did not not check for stale validations + // That may not be important, but this preserves the behavior + current( + boost::none, + [&](std::size_t) {}, // nothing to reserve + [&](NodeKey const&, Validation const& v) { + if (v.trusted() && v.isPreviousLedgerID(ledgerID)) + ++count; + }); + return count; + } + + /** Get the currently trusted validations + + @return Vector of validations from currently trusted validators + */ + std::vector + currentTrusted() + { + std::vector ret; + + current( + stalePolicy_.now(), + [&](std::size_t numValidations) { ret.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + ret.push_back(v.unwrap()); + }); + return ret; + } + + /** Get the set of known public keys associated with current validations + + @return The set of of knowns keys for current trusted and untrusted + validations + */ + hash_set + getCurrentPublicKeys() + { + hash_set ret; + current( + stalePolicy_.now(), + [&](std::size_t numValidations) { ret.reserve(numValidations); }, + [&](NodeKey const& k, Validation const&) { ret.insert(k); }); + + return ret; + } + + /** Count the number of trusted validations for the given ledger + + @param ledgerID The identifier of ledger of interest + @return The number of trusted validations + */ + std::size_t + numTrustedForLedger(LedgerID const& ledgerID) + { + std::size_t count = 0; + byLedger( + ledgerID, + [&](std::size_t) {}, // nothing to reserve + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + ++count; + }); + return count; + } + + /** Get set of trusted validations associated with a given ledger + + @param ledgerID The identifier of ledger of interest + @return Trusted validations associated with ledger + */ + std::vector + getTrustedForLedger(LedgerID const& ledgerID) + { + std::vector res; + byLedger( + ledgerID, + [&](std::size_t numValidations) { res.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + res.emplace_back(v.unwrap()); + }); + + return res; + } + + /** Return the sign times of all validations associated with a given ledger + + @param ledgerID The identifier of ledger of interest + @return Vector of times + */ + std::vector + getTrustedValidationTimes(LedgerID const& ledgerID) + { + std::vector times; + byLedger( + ledgerID, + [&](std::size_t numValidations) { times.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + times.emplace_back(v.signTime()); + }); + return times; + } + + /** Returns fees reported by trusted validators in the given ledger + + @param ledgerID The identifier of ledger of interest + @param baseFee The fee to report if not present in the validation + @return Vector of fees + */ + std::vector + fees(LedgerID const& ledgerID, std::uint32_t baseFee) + { + std::vector res; + byLedger( + ledgerID, + [&](std::size_t numValidations) { res.reserve(numValidations); }, + [&](NodeKey const&, Validation const& v) { + if (v.trusted()) + { + boost::optional loadFee = v.loadFee(); + if (loadFee) + res.push_back(*loadFee); + else + res.push_back(baseFee); + } + }); + return res; + } + + /** Flush all current validations + */ + void + flush() + { + JLOG(j_.info()) << "Flushing validations"; + + hash_map flushed; + using std::swap; + { + ScopedLock lock{mutex_}; + swap(flushed, current_); + } + + stalePolicy_.flush(std::move(flushed)); + + JLOG(j_.debug()) << "Validations flushed"; + } +}; +} // namespace ripple +#endif diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index bcb356f9a..f74491daf 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include #include @@ -1576,7 +1576,10 @@ PeerImp::onMessage (std::shared_ptr const& m) val->setSeen (closeTime); } - if (! app_.getValidations().current (val)) + if (! isCurrent(app_.getValidations().parms(), + app_.timeKeeper().closeTime(), + val->getSignTime(), + val->getSeenTime())) { JLOG(p_journal_.trace()) << "Validation: Not current"; fee_ = Resource::feeUnwantedData; diff --git a/src/ripple/unity/app_consensus.cpp b/src/ripple/unity/app_consensus.cpp index 794f35192..e716d88d0 100644 --- a/src/ripple/unity/app_consensus.cpp +++ b/src/ripple/unity/app_consensus.cpp @@ -20,3 +20,5 @@ #include #include +#include + diff --git a/src/ripple/unity/app_misc.cpp b/src/ripple/unity/app_misc.cpp index ae6522f2d..65d0a5b9b 100644 --- a/src/ripple/unity/app_misc.cpp +++ b/src/ripple/unity/app_misc.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 1ff93dec3..b2301cd33 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -378,7 +378,7 @@ public: auto const roundTime = weekTime (week); // Build validations - ValidationSet validations; + std::vector validations; validations.reserve (validators.size ()); int i = 0; @@ -402,7 +402,7 @@ public: v->setFieldV256 (sfAmendments, field); v->setTrusted(); - validations [val] = v; + validations.emplace_back(v); } ourVotes = table.doValidation (enabled); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp new file mode 100644 index 000000000..52878f752 --- /dev/null +++ b/src/test/consensus/Validations_test.cpp @@ -0,0 +1,986 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class Validations_test : public beast::unit_test::suite +{ + using clock_type = beast::abstract_clock const; + //-------------------------------------------------------------------------- + // Basic type wrappers for validation types + + // Represents a ledger sequence number + struct Seq + { + explicit Seq(std::uint32_t sIn) : s{sIn} + { + } + + Seq() : s{0} + { + } + + operator std::uint32_t() const + { + return s; + } + + std::uint32_t s; + }; + + // Represents a unique ledger identifier + struct ID + { + explicit ID(std::uint32_t idIn) : id{idIn} + { + } + + ID() : id{0} + { + } + + int + signum() const + { + return id == 0 ? 0 : 1; + } + + operator std::size_t() const + { + return id; + } + + template + friend void + hash_append(Hasher& h, ID const& id) + { + using beast::hash_append; + hash_append(h, id.id); + } + + std::uint32_t id; + }; + + class Node; + + // Basic implementation of the requirements of Validation in the generic + // Validations class + class Validation + { + friend class Node; + + ID ledgerID_ = ID{0}; + Seq seq_ = Seq{0}; + NetClock::time_point signTime_; + NetClock::time_point seenTime_; + std::string key_; + std::size_t nodeID_ = 0; + bool trusted_ = true; + boost::optional loadFee_; + // Shared prevID to ensure value is shared amongst instances + std::shared_ptr prevID_; + + public: + Validation() : prevID_(std::make_shared(0)) + { + } + + ID + ledgerID() const + { + return ledgerID_; + } + + Seq + seq() const + { + return seq_; + } + + NetClock::time_point + signTime() const + { + return signTime_; + } + + NetClock::time_point + seenTime() const + { + return seenTime_; + } + + std::string + key() const + { + return key_; + } + + std::uint32_t + nodeID() const + { + return nodeID_; + } + + bool + trusted() const + { + return trusted_; + } + + void + setPreviousLedgerID(ID const& prevID) + { + *prevID_ = prevID; + } + + bool + isPreviousLedgerID(ID const& prevID) const + { + return *prevID_ == prevID; + } + + ID + getPreviousLedgerID() const + { + return *prevID_; + } + + boost::optional + loadFee() const + { + return loadFee_; + } + + Validation const& + unwrap() const + { + return *this; + } + + auto + asTie() const + { + return std::tie( + ledgerID_, + seq_, + signTime_, + seenTime_, + key_, + nodeID_, + trusted_, + *prevID_, + loadFee_); + } + bool + operator==(Validation const& o) const + { + return asTie() == o.asTie(); + } + + bool + operator<(Validation const& o) const + { + return asTie() < o.asTie(); + } + }; + + // Helper to convert steady_clock to a reasonable NetClock + // This allows a single manual clock in the unit tests + static NetClock::time_point + toNetClock(clock_type const& c) + { + // 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 are positive + using namespace std::chrono; + return NetClock::time_point(duration_cast( + c.now().time_since_epoch() + 86400s)); + } + + // Represents a node that can issue validations + class Node + { + clock_type const& c_; + std::size_t nodeID_; + bool trusted_ = true; + std::size_t signIdx_ = 0; + boost::optional loadFee_; + + public: + Node(std::uint32_t nodeID, clock_type const& c) : c_(c), nodeID_(nodeID) + { + } + + void + untrust() + { + trusted_ = false; + } + + void + trust() + { + trusted_ = true; + } + + void + setLoadFee(std::uint32_t fee) + { + loadFee_ = fee; + } + + std::size_t + nodeID() const + { + return nodeID_; + } + + void + advanceKey() + { + signIdx_++; + } + + std::string + masterKey() const + { + return std::to_string(nodeID_); + } + + std::string + currKey() const + { + return masterKey() + "_" + std::to_string(signIdx_); + } + + NetClock::time_point + now() const + { + return toNetClock(c_); + } + + // Issue a new validation with given sequence number and id and + // with signing and seen times offset from the common clock + Validation + validation( + Seq seq, + ID i, + NetClock::duration signOffset, + NetClock::duration seenOffset) const + { + Validation v; + v.seq_ = seq; + v.ledgerID_ = i; + + v.signTime_ = now() + signOffset; + v.seenTime_ = now() + seenOffset; + + v.nodeID_ = nodeID_; + v.key_ = currKey(); + v.trusted_ = trusted_; + v.loadFee_ = loadFee_; + return v; + } + + // Issue a new validation with the given sequence number and id + Validation + validation(Seq seq, ID i) const + { + return validation( + seq, i, NetClock::duration{0}, NetClock::duration{0}); + } + }; + + // Non-locking mutex to avoid the need for testing generic Validations + struct DummyMutex + { + void + lock() + { + } + + void + unlock() + { + } + }; + + // Saved StaleData for inspection in test + struct StaleData + { + std::vector stale; + hash_map flushed; + }; + + // Generic Validations policy that saves stale/flushed data into + // a StaleData instance. + class StalePolicy + { + StaleData& staleData_; + clock_type& c_; + + public: + StalePolicy(StaleData& sd, clock_type& c) + : staleData_{sd}, c_{c} + { + } + + NetClock::time_point + now() const + { + return toNetClock(c_); + } + + void + onStale(Validation&& v) + { + staleData_.stale.emplace_back(std::move(v)); + } + + void + flush(hash_map&& remaining) + { + staleData_.flushed = std::move(remaining); + } + }; + + // Specialize generic Validations using the above types + using TestValidations = + Validations; + + // Hoist enum for writing simpler tests + using AddOutcome = TestValidations::AddOutcome; + + // Gather the dependencies of TestValidations in a single class and provide + // accessors for simplifying test logic + class TestHarness + { + StaleData staleData_; + ValidationParms p_; + beast::manual_clock clock_; + beast::Journal j_; + TestValidations tv_; + int nextNodeId_ = 0; + + public: + TestHarness() : tv_(p_, clock_, j_, staleData_, clock_) + { + } + + // Helper to add an existing validation + AddOutcome + add(Node const& n, Validation const& v) + { + return tv_.add(n.masterKey(), v); + } + + // Helper to directly create the validation + template + std::enable_if_t<(sizeof...(Ts) > 1), AddOutcome> + add(Node const& n, Ts&&... ts) + { + return add(n, n.validation(std::forward(ts)...)); + } + + TestValidations& + vals() + { + return tv_; + } + + Node + makeNode() + { + return Node(nextNodeId_++, clock_); + } + + ValidationParms + parms() const + { + return p_; + } + + auto& + clock() + { + return clock_; + } + + std::vector const& + stale() const + { + return staleData_.stale; + } + + hash_map const& + flushed() const + { + return staleData_.flushed; + } + }; + + void + testAddValidation() + { + // Test adding current,stale,repeat,sameSeq validations + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(); + { + { + auto const v = a.validation(Seq{1}, ID{1}); + + // Add a current validation + BEAST_EXPECT(AddOutcome::current == harness.add(a, v)); + + // Re-adding is repeat + BEAST_EXPECT(AddOutcome::repeat == harness.add(a, v)); + } + + { + harness.clock().advance(1s); + // Replace with a new validation and ensure the old one is stale + BEAST_EXPECT(harness.stale().empty()); + + BEAST_EXPECT( + AddOutcome::current == harness.add(a, Seq{2}, ID{2})); + + BEAST_EXPECT(harness.stale().size() == 1); + + BEAST_EXPECT(harness.stale()[0].ledgerID() == 1); + } + + { + // Test the node changing signing key, then reissuing a ledger + + // Confirm old ledger on hand, but not new ledger + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 1); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{20}) == 0); + + // Issue a new signing key and re-issue the validation with a + // new ID but the same sequence number + a.advanceKey(); + + BEAST_EXPECT( + AddOutcome::sameSeq == harness.add(a, Seq{2}, ID{20})); + + // Old ID should be gone ... + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{2}) == 0); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{20}) == 1); + // ... and the previous ID set to the old ID + { + auto trustedVals = + harness.vals().getTrustedForLedger(ID{20}); + BEAST_EXPECT(trustedVals.size() == 1); + BEAST_EXPECT(trustedVals[0].key() == a.currKey()); + BEAST_EXPECT(trustedVals[0].getPreviousLedgerID() == ID{2}); + } + + // A new key, but re-issue a validation with the same ID and + // Sequence + a.advanceKey(); + + BEAST_EXPECT( + AddOutcome::sameSeq == harness.add(a, Seq{2}, ID{20})); + // Ensure the old prevLedgerID was retained + { + auto trustedVals = + harness.vals().getTrustedForLedger(ID{20}); + BEAST_EXPECT(trustedVals.size() == 1); + BEAST_EXPECT(trustedVals[0].key() == a.currKey()); + BEAST_EXPECT(trustedVals[0].getPreviousLedgerID() == ID{2}); + } + } + + { + // Processing validations out of order should ignore the older + harness.clock().advance(2s); + auto const val3 = a.validation(Seq{3}, ID{3}); + + harness.clock().advance(4s); + auto const val4 = a.validation(Seq{4}, ID{4}); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, val4)); + + BEAST_EXPECT(AddOutcome::stale == harness.add(a, val3)); + + // re-issued should not be added + auto const val4reissue = a.validation(Seq{4}, ID{44}); + + BEAST_EXPECT(AddOutcome::stale == harness.add(a, val4reissue)); + + } + { + // Process validations out of order with shifted times + + // flush old validations + harness.clock().advance(1h); + + // Establish a new current validation + BEAST_EXPECT( + AddOutcome::current == harness.add(a, Seq{8}, ID{8})); + + // Process a validation that has "later" seq but early sign time + BEAST_EXPECT( + AddOutcome::stale == + harness.add(a, Seq{9}, ID{9}, -1s, -1s)); + + // Process a validation that has an "earlier" seq but later sign time + BEAST_EXPECT( + AddOutcome::current == + harness.add(a, Seq{7}, ID{7}, 1s, 1s)); + } + { + // Test stale on arrival validations + harness.clock().advance(1h); + + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a, + Seq{15}, + ID{15}, + -harness.parms().validationCURRENT_EARLY, + 0s)); + + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a, + Seq{15}, + ID{15}, + harness.parms().validationCURRENT_WALL, + 0s)); + + BEAST_EXPECT( + AddOutcome::stale == + harness.add( + a, + Seq{15}, + ID{15}, + 0s, + harness.parms().validationCURRENT_LOCAL)); + } + } + } + + void + testOnStale() + { + // Verify validation becomes stale based solely on time passing + TestHarness harness; + Node a = harness.makeNode(); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1})); + harness.vals().currentTrusted(); + BEAST_EXPECT(harness.stale().empty()); + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + + // trigger iteration over current + harness.vals().currentTrusted(); + + BEAST_EXPECT(harness.stale().size() == 1); + BEAST_EXPECT(harness.stale()[0].ledgerID() == 1); + } + + void + testGetNodesAfter() + { + // Test getting number of nodes working on a validation following + // a prescribed one + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(), d = harness.makeNode(); + + c.untrust(); + + // first round a,b,c agree, d has differing id + for (auto const& node : {a, b, c}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{1}, ID{1})); + BEAST_EXPECT(AddOutcome::current == harness.add(d, Seq{1}, ID{10})); + + // Nothing past ledger 1 yet + BEAST_EXPECT(harness.vals().getNodesAfter(ID{1}) == 0); + + harness.clock().advance(5s); + + // a and b have the same prior id, but b has a different current id + // c is untrusted but on the same prior id + // d has a different prior id + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{2}, ID{2})); + BEAST_EXPECT(AddOutcome::current == harness.add(b, Seq{2}, ID{20})); + BEAST_EXPECT(AddOutcome::current == harness.add(c, Seq{2}, ID{2})); + BEAST_EXPECT(AddOutcome::current == harness.add(d, Seq{2}, ID{2})); + + BEAST_EXPECT(harness.vals().getNodesAfter(ID{1}) == 2); + } + + void + testCurrentTrusted() + { + // Test getting current trusted validations + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(); + b.untrust(); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1})); + BEAST_EXPECT(AddOutcome::current == harness.add(b, Seq{1}, ID{3})); + + // Only a is trusted + BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); + BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ID{1}); + BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == Seq{1}); + + harness.clock().advance(3s); + + for (auto const& node : {a, b}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{2}, ID{2})); + + // New validation for a + BEAST_EXPECT(harness.vals().currentTrusted().size() == 1); + BEAST_EXPECT(harness.vals().currentTrusted()[0].ledgerID() == ID{2}); + BEAST_EXPECT(harness.vals().currentTrusted()[0].seq() == Seq{2}); + + // Pass enough time for it to go stale + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + BEAST_EXPECT(harness.vals().currentTrusted().empty()); + } + + void + testGetCurrentPublicKeys() + { + // Test getting current keys validations + using namespace std::chrono_literals; + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(); + b.untrust(); + + for (auto const& node : {a, b}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{1}, ID{1})); + + { + hash_set const expectedKeys = {a.masterKey(), + b.masterKey()}; + BEAST_EXPECT( + harness.vals().getCurrentPublicKeys() == expectedKeys); + } + + harness.clock().advance(3s); + + // Change keys + a.advanceKey(); + b.advanceKey(); + + for (auto const& node : {a, b}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{2}, ID{2})); + + { + hash_set const expectedKeys = {a.masterKey(), + b.masterKey()}; + BEAST_EXPECT( + harness.vals().getCurrentPublicKeys() == expectedKeys); + } + + // Pass enough time for them to go stale + harness.clock().advance(harness.parms().validationCURRENT_LOCAL); + BEAST_EXPECT(harness.vals().getCurrentPublicKeys().empty()); + } + + void + testCurrentTrustedDistribution() + { + // Test the trusted distribution calculation, including ledger slips + // and sequence cutoffs + using namespace std::chrono_literals; + + TestHarness harness; + + Node baby = harness.makeNode(), papa = harness.makeNode(), + mama = harness.makeNode(), goldilocks = harness.makeNode(); + goldilocks.untrust(); + + // Stagger the validations around sequence 2 + // papa on seq 1 is behind + // baby on seq 2 is just right + // mama on seq 3 is ahead + // goldilocks on seq 2, but is not trusted + + for (auto const& node : {baby, papa, mama, goldilocks}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{1}, ID{1})); + + harness.clock().advance(1s); + for (auto const& node : {baby, mama, goldilocks}) + BEAST_EXPECT( + AddOutcome::current == harness.add(node, Seq{2}, ID{2})); + + harness.clock().advance(1s); + BEAST_EXPECT(AddOutcome::current == harness.add(mama, Seq{3}, ID{3})); + + { + // Allow slippage that treats all trusted as the current ledger + auto res = harness.vals().currentTrustedDistribution( + ID{2}, // Current ledger + ID{1}, // Prior ledger + Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 1); + BEAST_EXPECT(res[ID{2}].count == 3); + BEAST_EXPECT(res[ID{2}].highNode == mama.nodeID()); + } + + { + // Don't allow slippage back for prior ledger + auto res = harness.vals().currentTrustedDistribution( + ID{2}, // Current ledger + ID{0}, // No prior ledger + Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 2); + BEAST_EXPECT(res[ID{2}].count == 2); + BEAST_EXPECT(res[ID{2}].highNode == mama.nodeID()); + BEAST_EXPECT(res[ID{1}].count == 1); + BEAST_EXPECT(res[ID{1}].highNode == papa.nodeID()); + } + + { + // Don't allow any slips + auto res = harness.vals().currentTrustedDistribution( + ID{0}, // No current ledger + ID{0}, // No prior ledger + Seq{0}); // No cutoff + + BEAST_EXPECT(res.size() == 3); + BEAST_EXPECT(res[ID{1}].count == 1); + BEAST_EXPECT(res[ID{1}].highNode == papa.nodeID()); + BEAST_EXPECT(res[ID{2}].count == 1); + BEAST_EXPECT(res[ID{2}].highNode == baby.nodeID()); + BEAST_EXPECT(res[ID{3}].count == 1); + BEAST_EXPECT(res[ID{3}].highNode == mama.nodeID()); + } + + { + // Cutoff old sequence numbers + auto res = harness.vals().currentTrustedDistribution( + ID{2}, // current ledger + ID{1}, // prior ledger + Seq{2}); // Only sequence 2 or later + BEAST_EXPECT(res.size() == 1); + BEAST_EXPECT(res[ID{2}].count == 2); + BEAST_EXPECT(res[ID{2}].highNode == mama.nodeID()); + } + } + + void + testTrustedByLedgerFunctions() + { + // Test the Validations functions that calculate a value by ledger ID + using namespace std::chrono_literals; + + // Several Validations functions return a set of values associated + // with trusted ledgers sharing the same ledger ID. The tests below + // exercise this logic by saving the set of trusted Validations, and + // verifying that the Validations member functions all calculate the + // proper transformation of the available ledgers. + + TestHarness harness; + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(), d = harness.makeNode(); + c.untrust(); + // Mix of load fees + a.setLoadFee(12); + b.setLoadFee(1); + c.setLoadFee(12); + + hash_map> trustedValidations; + + //---------------------------------------------------------------------- + // checkers + auto sorted = [](auto vec) { + std::sort(vec.begin(), vec.end()); + return vec; + }; + auto compare = [&]() { + for (auto& it : trustedValidations) + { + auto const& id = it.first; + auto const& expectedValidations = it.second; + + BEAST_EXPECT( + harness.vals().numTrustedForLedger(id) == + expectedValidations.size()); + BEAST_EXPECT( + sorted(harness.vals().getTrustedForLedger(id)) == + sorted(expectedValidations)); + + std::vector expectedTimes; + std::uint32_t baseFee = 0; + std::vector expectedFees; + for (auto const& val : expectedValidations) + { + expectedTimes.push_back(val.signTime()); + expectedFees.push_back(val.loadFee().value_or(baseFee)); + } + + BEAST_EXPECT( + sorted(harness.vals().fees(id, baseFee)) == + sorted(expectedFees)); + + BEAST_EXPECT( + sorted(harness.vals().getTrustedValidationTimes(id)) == + sorted(expectedTimes)); + } + }; + + //---------------------------------------------------------------------- + // Add a dummy ID to cover unknown ledger identifiers + trustedValidations[ID{100}] = {}; + + // first round a,b,c agree, d differs + for (auto const& node : {a, b, c}) + { + auto const val = node.validation(Seq{1}, ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + if (val.trusted()) + trustedValidations[val.ledgerID()].emplace_back(val); + } + { + auto const val = d.validation(Seq{1}, ID{11}); + BEAST_EXPECT(AddOutcome::current == harness.add(d, val)); + trustedValidations[val.ledgerID()].emplace_back(val); + } + + harness.clock().advance(5s); + // second round, a,b,c move to ledger 2, d now thinks ledger 1 + for (auto const& node : {a, b, c}) + { + auto const val = node.validation(Seq{2}, ID{2}); + BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + if (val.trusted()) + trustedValidations[val.ledgerID()].emplace_back(val); + } + { + auto const val = d.validation(Seq{2}, ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(d, val)); + trustedValidations[val.ledgerID()].emplace_back(val); + } + + compare(); + } + + void + testExpire() + { + // Verify expiring clears out validations stored by ledger + + TestHarness harness; + Node a = harness.makeNode(); + + BEAST_EXPECT(AddOutcome::current == harness.add(a, Seq{1}, ID{1})); + BEAST_EXPECT(harness.vals().numTrustedForLedger(ID{1})); + harness.clock().advance(harness.parms().validationSET_EXPIRES); + harness.vals().expire(); + BEAST_EXPECT(!harness.vals().numTrustedForLedger(ID{1})); + } + + void + testFlush() + { + // Test final flush of validations + using namespace std::chrono_literals; + + TestHarness harness; + + Node a = harness.makeNode(), b = harness.makeNode(), + c = harness.makeNode(); + c.untrust(); + + hash_map expected; + Validation staleA; + for (auto const& node : {a, b, c}) + { + auto const val = node.validation(Seq{1}, ID{1}); + BEAST_EXPECT(AddOutcome::current == harness.add(node, val)); + if (node.nodeID() == a.nodeID()) + { + staleA = val; + } + else + expected[node.masterKey()] = val; + } + + // 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.validation(Seq{2}, ID{2}); + BEAST_EXPECT(AddOutcome::current == harness.add(a, newVal)); + newVal.setPreviousLedgerID(staleA.ledgerID()); + expected[a.masterKey()] = newVal; + + // Now flush + harness.vals().flush(); + + // Original a validation was stale + BEAST_EXPECT(harness.stale().size() == 1); + BEAST_EXPECT(harness.stale()[0] == staleA); + BEAST_EXPECT(harness.stale()[0].nodeID() == a.nodeID()); + + auto const& flushed = harness.flushed(); + + BEAST_EXPECT(flushed == expected); + } + + void + run() override + { + testAddValidation(); + testOnStale(); + testGetNodesAfter(); + testCurrentTrusted(); + testGetCurrentPublicKeys(); + testCurrentTrustedDistribution(); + testTrustedByLedgerFunctions(); + testExpire(); + testFlush(); + } +}; + +BEAST_DEFINE_TESTSUITE(Validations, consensus, ripple); +} // namespace test +} // namespace ripple diff --git a/src/test/quiet_reporter.h b/src/test/quiet_reporter.h index da5a3bbd9..7b6c804af 100644 --- a/src/test/quiet_reporter.h +++ b/src/test/quiet_reporter.h @@ -134,7 +134,8 @@ public: return b.second < a.second; }); - top.resize(10); + if(top.size() > 10) + top.resize(10); os_ << "Longest suite times:\n"; for(auto const& i : top) diff --git a/src/test/unity/consensus_test_unity.cpp b/src/test/unity/consensus_test_unity.cpp index cdc3717ad..926add487 100644 --- a/src/test/unity/consensus_test_unity.cpp +++ b/src/test/unity/consensus_test_unity.cpp @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012-2016 Ripple Labs Inc. + Copyright (c) 2012-2017 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -19,3 +19,4 @@ #include #include +#include