From 7ae3c91015a501e46b7e6022f99a4554456b89cc Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 5 Apr 2017 13:27:01 -0400 Subject: [PATCH] Refactor Validations (RIPD-1412,RIPD-1356): Introduces a generic Validations class for storing and querying current and recent validations. Aditionally migrates the validation related timing constants from LedgerTiming to the new Validations code. Introduces RCLValidations as the version of Validations adapted for use in the RCL. This adds support for flushing/writing validations to the sqlite log and also manages concurrent access to the Validations data. RCLValidations::flush() no longer uses the JobQueue for its database write at shutdown. It performs the write directly without changing threads. --- Builds/VisualStudio2015/RippleD.vcxproj | 18 +- .../VisualStudio2015/RippleD.vcxproj.filters | 18 +- docs/source.dox | 2 + src/ripple/app/consensus/RCLConsensus.cpp | 28 +- src/ripple/app/consensus/RCLValidations.cpp | 280 +++++ src/ripple/app/consensus/RCLValidations.h | 214 ++++ src/ripple/app/ledger/impl/LedgerMaster.cpp | 15 +- src/ripple/app/main/Application.cpp | 14 +- src/ripple/app/main/Application.h | 11 +- src/ripple/app/misc/AmendmentTable.h | 6 +- src/ripple/app/misc/FeeVote.h | 4 +- src/ripple/app/misc/FeeVoteImpl.cpp | 26 +- src/ripple/app/misc/NetworkOPs.cpp | 11 +- src/ripple/app/misc/Validations.cpp | 557 ---------- src/ripple/app/misc/Validations.h | 85 -- src/ripple/app/misc/impl/AmendmentTable.cpp | 14 +- src/ripple/consensus/LedgerTiming.h | 23 - src/ripple/consensus/Validations.h | 726 +++++++++++++ src/ripple/overlay/impl/PeerImp.cpp | 7 +- src/ripple/unity/app_consensus.cpp | 2 + src/ripple/unity/app_misc.cpp | 1 - src/test/app/AmendmentTable_test.cpp | 4 +- src/test/consensus/Validations_test.cpp | 986 ++++++++++++++++++ src/test/quiet_reporter.h | 3 +- src/test/unity/consensus_test_unity.cpp | 3 +- 25 files changed, 2312 insertions(+), 746 deletions(-) create mode 100644 src/ripple/app/consensus/RCLValidations.cpp create mode 100644 src/ripple/app/consensus/RCLValidations.h delete mode 100644 src/ripple/app/misc/Validations.cpp delete mode 100644 src/ripple/app/misc/Validations.h create mode 100644 src/ripple/consensus/Validations.h create mode 100644 src/test/consensus/Validations_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index fb68dd3e77..44bbbfd43a 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 bac2171c94..efaff19785 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 7475222c9b..1fb7de3f06 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 55323ff86d..2d95cdb99c 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 0000000000..f1de8f55af --- /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 0000000000..ec1939e8c4 --- /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 6814c1d2e7..a0e3f83b88 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 30c4f758c6..d827ac496d 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 a6033636cf..589c189220 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 50f722e280..2cad2343b0 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 ea271545e7..4e8377303d 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 221d43df69..9a24aca597 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 231f31a5bf..4c9690ebf7 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 9f11aae5c4..0000000000 --- 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 af6285d310..0000000000 --- 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 10820e23ba..9be2cb1831 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 db135031b0..19f2914d03 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 0000000000..78bcd3761f --- /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 bcb356f9a7..f74491dafe 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 794f351925..e716d88d00 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 ae6522f2d9..65d0a5b9b2 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 1ff93dec32..b2301cd338 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 0000000000..52878f752b --- /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 da5a3bbd93..7b6c804af9 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 cdc3717ade..926add4878 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