From 1507ed66a8c151c81a009ae15816096ce8eb10e2 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 1 Mar 2018 13:22:45 -0500 Subject: [PATCH] Check consensus hash consistency (RIPD-1456): These changes use the hash of the consensus transaction set when characterizing the mismatch between a locally built ledger and fully validated network ledger. This allows detection of non-determinism in transaction process, in which consensus succeeded, but a node somehow generated a different subsequent ledger. --- Builds/VisualStudio2015/RippleD.vcxproj | 4 + .../VisualStudio2015/RippleD.vcxproj.filters | 3 + src/ripple/app/consensus/RCLConsensus.cpp | 38 +-- src/ripple/app/consensus/RCLConsensus.h | 11 +- src/ripple/app/ledger/LedgerHistory.cpp | 37 ++- src/ripple/app/ledger/LedgerHistory.h | 26 +- src/ripple/app/ledger/LedgerMaster.h | 6 +- src/ripple/app/ledger/impl/LedgerMaster.cpp | 19 +- src/ripple/consensus/Consensus.h | 30 +-- src/ripple/consensus/ConsensusTypes.h | 8 +- src/ripple/consensus/Validations.h | 21 -- src/ripple/protocol/STValidation.h | 7 + src/ripple/protocol/impl/STValidation.cpp | 7 + src/test/app/AmendmentTable_test.cpp | 4 +- src/test/app/LedgerHistory_test.cpp | 252 ++++++++++++++++++ src/test/app/RCLValidations_test.cpp | 4 +- src/test/consensus/Validations_test.cpp | 5 - src/test/csf/Peer.h | 2 +- src/test/csf/Validation.h | 3 + src/test/unity/app_test_unity1.cpp | 1 + 20 files changed, 402 insertions(+), 86 deletions(-) create mode 100644 src/test/app/LedgerHistory_test.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 41e7f2c636..3d982a810c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4697,6 +4697,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 2c9ad27fcf..d8a7c1a1ee 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5637,6 +5637,9 @@ test\app + + test\app + test\app diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 1cc1119465..bf25128cc9 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -205,17 +205,17 @@ RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal) } void -RCLConsensus::Adaptor::share(RCLTxSet const& set) +RCLConsensus::Adaptor::share(RCLTxSet const& txns) { - inboundTransactions_.giveSet(set.id(), set.map_, false); + inboundTransactions_.giveSet(txns.id(), txns.map_, false); } boost::optional RCLConsensus::Adaptor::acquireTxSet(RCLTxSet::ID const& setId) { - if (auto set = inboundTransactions_.getSet(setId, true)) + if (auto txns = inboundTransactions_.getSet(setId, true)) { - return RCLTxSet{std::move(set)}; + return RCLTxSet{std::move(txns)}; } return boost::none; } @@ -422,11 +422,11 @@ RCLConsensus::Adaptor::doAccept( //-------------------------------------------------------------------------- // Put transactions into a deterministic, but unpredictable, order - CanonicalTXSet retriableTxs{result.set.id()}; + CanonicalTXSet retriableTxs{result.txns.id()}; auto sharedLCL = buildLCL( prevLedger, - result.set, + result.txns, consensusCloseTime, closeTimeCorrect, closeResolution, @@ -449,14 +449,15 @@ RCLConsensus::Adaptor::doAccept( if (validating_ && !consensusFail && app_.getValidations().canValidateSeq(sharedLCL.seq())) { - validate(sharedLCL, proposing); + validate(sharedLCL, result.txns, proposing); JLOG(j_.info()) << "CNF Val " << newLCLHash; } else JLOG(j_.info()) << "CNF buildLCL " << newLCLHash; // See if we can accept a ledger as fully-validated - ledgerMaster_.consensusBuilt(sharedLCL.ledger_, std::move(consensusJson)); + ledgerMaster_.consensusBuilt( + sharedLCL.ledger_, result.txns.id(), std::move(consensusJson)); //------------------------------------------------------------------------- { @@ -636,7 +637,7 @@ RCLConsensus::Adaptor::notify( Typically the txFilter is used to reject transactions that already accepted in the prior ledger. - @param set set of transactions to apply + @param txns set of transactions to apply @param view ledger to apply to @param txFilter callback, return false to reject txn @return retriable transactions @@ -645,13 +646,13 @@ RCLConsensus::Adaptor::notify( CanonicalTXSet applyTransactions( Application& app, - RCLTxSet const& cSet, + RCLTxSet const& txns, OpenView& view, std::function txFilter) { auto j = app.journal("LedgerConsensus"); - auto& set = *(cSet.map_); + auto& set = *(txns.map_); CanonicalTXSet retriableTxs(set.getHash().as_uint256()); for (auto const& item : set) @@ -731,7 +732,7 @@ applyTransactions( RCLCxLedger RCLConsensus::Adaptor::buildLCL( RCLCxLedger const& previousLedger, - RCLTxSet const& set, + RCLTxSet const& txns, NetClock::time_point closeTime, bool closeTimeCorrect, NetClock::duration closeResolution, @@ -746,9 +747,9 @@ RCLConsensus::Adaptor::buildLCL( closeTimeCorrect = ((replay->closeFlags_ & sLCF_NoConsensusTime) == 0); } - JLOG(j_.debug()) << "Report: TxSt = " << set.id() << ", close " + JLOG(j_.debug()) << "Report: TxSt = " << txns.id() << ", close " << closeTime.time_since_epoch().count() - << (closeTimeCorrect ? "" : "X"); + << (closeTimeCorrect ? "" : " (incorrect)"); // Build the new last closed ledger auto buildLCL = @@ -765,7 +766,7 @@ RCLConsensus::Adaptor::buildLCL( // Set up to write SHAMap changes to our database, // perform updates, extract changes - JLOG(j_.debug()) << "Applying consensus set transactions to the" + JLOG(j_.debug()) << "Applying consensus txns transactions to the" << " last closed ledger"; { @@ -782,7 +783,7 @@ RCLConsensus::Adaptor::buildLCL( { // Normal case, we are not replaying a ledger close retriableTxs = applyTransactions( - app_, set, accum, [&buildLCL](uint256 const& txID) { + app_, txns, accum, [&buildLCL](uint256 const& txID) { return !buildLCL->txExists(txID); }); } @@ -825,7 +826,9 @@ RCLConsensus::Adaptor::buildLCL( } void -RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) +RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, + RCLTxSet const& txns, + bool proposing) { auto validationTime = app_.timeKeeper().closeTime(); if (validationTime <= lastValidationTime_) @@ -835,6 +838,7 @@ RCLConsensus::Adaptor::validate(RCLCxLedger const& ledger, bool proposing) // Build validation auto v = std::make_shared( ledger.id(), + txns.id(), validationTime, valPublic_, nodeID_, diff --git a/src/ripple/app/consensus/RCLConsensus.h b/src/ripple/app/consensus/RCLConsensus.h index db6047fa27..e4ffef0745 100644 --- a/src/ripple/app/consensus/RCLConsensus.h +++ b/src/ripple/app/consensus/RCLConsensus.h @@ -218,10 +218,10 @@ class RCLConsensus /** Share the given tx set to peers. - @param set The TxSet to share. + @param txns The TxSet to share. */ void - share(RCLTxSet const& set); + share(RCLTxSet const& txns); /** Get the ID of the previous ledger/last closed ledger(LCL) on the network @@ -332,7 +332,7 @@ class RCLConsensus can be retried in the next round. @param previousLedger Prior ledger building upon - @param set The set of transactions to apply to the ledger + @param txns The set of transactions to apply to the ledger @param closeTime The the ledger closed @param closeTimeCorrect Whether consensus agreed on close time @param closeResolution Resolution used to determine consensus close @@ -345,7 +345,7 @@ class RCLConsensus RCLCxLedger buildLCL( RCLCxLedger const& previousLedger, - RCLTxSet const& set, + RCLTxSet const& txns, NetClock::time_point closeTime, bool closeTimeCorrect, NetClock::duration closeResolution, @@ -355,6 +355,7 @@ class RCLConsensus /** Validate the given ledger and share with peers as necessary @param ledger The ledger to validate + @param txns The consensus transaction set @param proposing Whether we were proposing transactions while generating this ledger. If we are not proposing, a validation can still be sent to inform peers that @@ -362,7 +363,7 @@ class RCLConsensus but are still around and trying to catch up. */ void - validate(RCLCxLedger const& ledger, bool proposing); + validate(RCLCxLedger const& ledger, RCLTxSet const& txns, bool proposing); }; diff --git a/src/ripple/app/ledger/LedgerHistory.cpp b/src/ripple/app/ledger/LedgerHistory.cpp index 9a7da8fcd5..28285c9d7b 100644 --- a/src/ripple/app/ledger/LedgerHistory.cpp +++ b/src/ripple/app/ledger/LedgerHistory.cpp @@ -312,9 +312,12 @@ leaves (SHAMap const& sm) return v; } -void LedgerHistory::handleMismatch ( +void +LedgerHistory::handleMismatch( LedgerHash const& built, LedgerHash const& valid, + boost::optional const& builtConsensusHash, + boost::optional const& validatedConsensusHash, Json::Value const& consensus) { assert (built != valid); @@ -357,6 +360,18 @@ void LedgerHistory::handleMismatch ( return; } + if (builtConsensusHash && validatedConsensusHash) + { + if (builtConsensusHash != validatedConsensusHash) + JLOG(j_.error()) + << "MISMATCH on consensus transaction set " + << " built: " << to_string(*builtConsensusHash) + << " validated: " << to_string(*validatedConsensusHash); + else + JLOG(j_.error()) << "MISMATCH with same consensus transaction set: " + << to_string(*builtConsensusHash); + } + // Find differences between built and valid ledgers auto const builtTx = leaves(builtLedger->txMap()); auto const validTx = leaves(validLedger->txMap()); @@ -409,6 +424,7 @@ void LedgerHistory::handleMismatch ( void LedgerHistory::builtLedger ( std::shared_ptr const& ledger, + uint256 const& consensusHash, Json::Value consensus) { LedgerIndex index = ledger->info().seq; @@ -428,7 +444,12 @@ void LedgerHistory::builtLedger ( JLOG (j_.error()) << "MISMATCH: seq=" << index << " validated:" << entry->validated.get() << " then:" << hash; - handleMismatch (hash, entry->validated.get(), consensus); + handleMismatch( + hash, + entry->validated.get(), + consensusHash, + entry->validatedConsensusHash, + consensus); } else { @@ -438,11 +459,13 @@ void LedgerHistory::builtLedger ( } entry->built.emplace (hash); + entry->builtConsensusHash.emplace(consensusHash); entry->consensus.emplace (std::move (consensus)); } void LedgerHistory::validatedLedger ( - std::shared_ptr const& ledger) + std::shared_ptr const& ledger, + boost::optional const& consensusHash) { LedgerIndex index = ledger->info().seq; LedgerHash hash = ledger->info().hash; @@ -461,7 +484,12 @@ void LedgerHistory::validatedLedger ( JLOG (j_.error()) << "MISMATCH: seq=" << index << " built:" << entry->built.get() << " then:" << hash; - handleMismatch (entry->built.get(), hash, entry->consensus.get()); + handleMismatch( + entry->built.get(), + hash, + entry->builtConsensusHash, + consensusHash, + entry->consensus.get()); } else { @@ -471,6 +499,7 @@ void LedgerHistory::validatedLedger ( } entry->validated.emplace (hash); + entry->validatedConsensusHash = consensusHash; } /** Ensure m_ledgers_by_hash doesn't have the wrong hash for a particular index diff --git a/src/ripple/app/ledger/LedgerHistory.h b/src/ripple/app/ledger/LedgerHistory.h index 8c5ece0b9f..a3a3f9cfdd 100644 --- a/src/ripple/app/ledger/LedgerHistory.h +++ b/src/ripple/app/ledger/LedgerHistory.h @@ -51,7 +51,7 @@ public: return m_ledgers_by_hash.getHitRate (); } - /** Get a ledger given its squence number */ + /** Get a ledger given its sequence number */ std::shared_ptr getLedgerBySeq (LedgerIndex ledgerIndex); @@ -65,7 +65,7 @@ public: */ LedgerHash getLedgerHash (LedgerIndex ledgerIndex); - /** Set the history cache's paramters + /** Set the history cache's parameters @param size The target size of the cache @param age The target age of the cache, in seconds */ @@ -82,11 +82,13 @@ public: /** Report that we have locally built a particular ledger */ void builtLedger ( std::shared_ptr const&, + uint256 const& consensusHash, Json::Value); /** Report that we have validated a particular ledger */ void validatedLedger ( - std::shared_ptr const&); + std::shared_ptr const&, + boost::optional const& consensusHash); /** Repair a hash to index mapping @param ledgerIndex The index whose mapping is to be repaired @@ -103,9 +105,18 @@ private: validate a different one. @param built The hash of the ledger we built @param valid The hash of the ledger we deemed fully valid + @param builtConsensusHash The hash of the consensus transaction for the + ledger we built + @param validatedConsensusHash The hash of the validated ledger's + consensus transaction set @param consensus The status of the consensus round */ - void handleMismatch (LedgerHash const& built, LedgerHash const& valid, + void + handleMismatch( + LedgerHash const& built, + LedgerHash const& valid, + boost::optional const& builtConsensusHash, + boost::optional const& validatedConsensusHash, Json::Value const& consensus); Application& app_; @@ -120,8 +131,15 @@ private: // For debug and logging purposes struct cv_entry { + // Hash of locally built ledger boost::optional built; + // Hash of the validated ledger boost::optional validated; + // Hash of locally accepted consensus transaction set + boost::optional builtConsensusHash; + // Hash of validated consensus transaction set + boost::optional validatedConsensusHash; + // Consensus metadata of built ledger boost::optional consensus; }; using ConsensusValidated = TaggedCache ; diff --git a/src/ripple/app/ledger/LedgerMaster.h b/src/ripple/app/ledger/LedgerMaster.h index 2b25886f07..55ab6c9187 100644 --- a/src/ripple/app/ledger/LedgerMaster.h +++ b/src/ripple/app/ledger/LedgerMaster.h @@ -199,7 +199,11 @@ public: void checkAccept (std::shared_ptr const& ledger); void checkAccept (uint256 const& hash, std::uint32_t seq); - void consensusBuilt (std::shared_ptr const& ledger, Json::Value consensus); + void + consensusBuilt( + std::shared_ptr const& ledger, + uint256 const& consensusHash, + Json::Value consensus); LedgerIndex getBuildingLedger (); void setBuildingLedger (LedgerIndex index); diff --git a/src/ripple/app/ledger/impl/LedgerMaster.cpp b/src/ripple/app/ledger/impl/LedgerMaster.cpp index 2276edf6c1..98ff1b31b9 100644 --- a/src/ripple/app/ledger/impl/LedgerMaster.cpp +++ b/src/ripple/app/ledger/impl/LedgerMaster.cpp @@ -182,12 +182,19 @@ void LedgerMaster::setValidLedger( std::shared_ptr const& l) { + std::vector times; + boost::optional consensusHash; if (! standalone_) { - times = app_.getValidations().getTrustedValidationTimes( - l->info().hash); + auto const vals = app_.getValidations().getTrustedForLedger(l->info().hash); + times.reserve(vals.size()); + for(auto const& val: vals) + times.push_back(val->getSignTime()); + + if(!vals.empty()) + consensusHash = vals.front()->getConsensusHash(); } NetClock::time_point signTime; @@ -216,7 +223,7 @@ LedgerMaster::setValidLedger( app_.getOPs().updateLocalTx (*l); app_.getSHAMapStore().onLedgerClosed (getValidatedLedger()); - mLedgerHistory.validatedLedger (l); + mLedgerHistory.validatedLedger (l, consensusHash); app_.getAmendmentTable().doValidatedLedger (l); if (!app_.getOPs().isAmendmentBlocked() && app_.getAmendmentTable().hasUnsupportedEnabled ()) @@ -804,7 +811,9 @@ LedgerMaster::checkAccept ( /** Report that the consensus process built a particular ledger */ void LedgerMaster::consensusBuilt( - std::shared_ptr const& ledger, Json::Value consensus) + std::shared_ptr const& ledger, + uint256 const& consensusHash, + Json::Value consensus) { // Because we just built a ledger, we are no longer building one @@ -814,7 +823,7 @@ LedgerMaster::consensusBuilt( if (standalone_) return; - mLedgerHistory.builtLedger (ledger, std::move (consensus)); + mLedgerHistory.builtLedger (ledger, consensusHash, std::move (consensus)); if (ledger->info().seq <= mValidLedgerSeq) { diff --git a/src/ripple/consensus/Consensus.h b/src/ripple/consensus/Consensus.h index 3ddf101ad4..621e026505 100644 --- a/src/ripple/consensus/Consensus.h +++ b/src/ripple/consensus/Consensus.h @@ -1167,8 +1167,8 @@ Consensus::closeLedger() result_->roundTime.reset(clock_.now()); // Share the newly created transaction set if we haven't already // received it from a peer - if (acquired_.emplace(result_->set.id(), result_->set).second) - adaptor_.share(result_->set); + if (acquired_.emplace(result_->txns.id(), result_->txns).second) + adaptor_.share(result_->txns); if (mode_.get() == ConsensusMode::proposing) adaptor_.propose(result_->position); @@ -1258,7 +1258,7 @@ Consensus::updateOurPositions() parms)) { if (!mutableSet) - mutableSet.emplace(result_->set); + mutableSet.emplace(result_->txns); if (it.second.getOurVote()) { @@ -1352,14 +1352,14 @@ Consensus::updateOurPositions() result_->position.isStale(ourCutoff))) { // close time changed or our position is stale - ourNewSet.emplace(result_->set); + ourNewSet.emplace(result_->txns); } if (ourNewSet) { auto newID = ourNewSet->id(); - result_->set = std::move(*ourNewSet); + result_->txns = std::move(*ourNewSet); JLOG(j_.info()) << "Position change: CTime " << consensusCloseTime.time_since_epoch().count() @@ -1369,16 +1369,16 @@ Consensus::updateOurPositions() // Share our new transaction set and update disputes // if we haven't already received it - if (acquired_.emplace(newID, result_->set).second) + if (acquired_.emplace(newID, result_->txns).second) { if (!result_->position.isBowOut()) - adaptor_.share(result_->set); + adaptor_.share(result_->txns); for (auto const& it : currPeerPositions_) { Proposal_t const& p = it.second.proposal(); if (p.position() == newID) - updateDisputes(it.first, result_->set); + updateDisputes(it.first, result_->txns); } } @@ -1479,13 +1479,13 @@ Consensus::createDisputes(TxSet_t const& o) return; // Nothing to dispute if we agree - if (result_->set.id() == o.id()) + if (result_->txns.id() == o.id()) return; - JLOG(j_.debug()) << "createDisputes " << result_->set.id() << " to " + JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to " << o.id(); - auto differences = result_->set.compare(o); + auto differences = result_->txns.compare(o); int dc = 0; @@ -1494,10 +1494,10 @@ Consensus::createDisputes(TxSet_t const& o) ++dc; // create disputed transactions (from the ledger that has them) assert( - (id.second && result_->set.find(id.first) && !o.find(id.first)) || - (!id.second && !result_->set.find(id.first) && o.find(id.first))); + (id.second && result_->txns.find(id.first) && !o.find(id.first)) || + (!id.second && !result_->txns.find(id.first) && o.find(id.first))); - Tx_t tx = id.second ? *result_->set.find(id.first) : *o.find(id.first); + Tx_t tx = id.second ? *result_->txns.find(id.first) : *o.find(id.first); auto txID = tx.id(); if (result_->disputes.find(txID) != result_->disputes.end()) @@ -1505,7 +1505,7 @@ Consensus::createDisputes(TxSet_t const& o) JLOG(j_.debug()) << "Transaction " << txID << " is disputed"; - typename Result::Dispute_t dtx{tx, result_->set.exists(txID), + typename Result::Dispute_t dtx{tx, result_->txns.exists(txID), std::max(prevProposers_, currPeerPositions_.size()), j_}; // Update all of the available peer's votes on the disputed transaction diff --git a/src/ripple/consensus/ConsensusTypes.h b/src/ripple/consensus/ConsensusTypes.h index c570b4fded..04ddb25a77 100644 --- a/src/ripple/consensus/ConsensusTypes.h +++ b/src/ripple/consensus/ConsensusTypes.h @@ -169,7 +169,7 @@ public: The initial consensus proposal from each peer has that peer's view of when the ledger closed. This object stores all those close times for - analysis of clock drift between peerss. + analysis of clock drift between peers. */ struct ConsensusCloseTimes { @@ -210,13 +210,13 @@ struct ConsensusResult using Dispute_t = DisputedTx; ConsensusResult(TxSet_t&& s, Proposal_t&& p) - : set{std::move(s)}, position{std::move(p)} + : txns{std::move(s)}, position{std::move(p)} { - assert(set.id() == position.position()); + assert(txns.id() == position.position()); } //! The set of transactions consensus agrees go in the ledger - TxSet_t set; + TxSet_t txns; //! Our proposed position on transactions/close time Proposal_t position; diff --git a/src/ripple/consensus/Validations.h b/src/ripple/consensus/Validations.h index 75406e05b0..bf2dd4bdf7 100644 --- a/src/ripple/consensus/Validations.h +++ b/src/ripple/consensus/Validations.h @@ -918,27 +918,6 @@ public: return res; } - /** Return the sign times of all trusted full validations - - @param ledgerID The identifier of ledger of interest - @return Vector of times - */ - std::vector - getTrustedValidationTimes(ID const& ledgerID) - { - std::vector times; - ScopedLock lock{mutex_}; - byLedger( - lock, - ledgerID, - [&](std::size_t numValidations) { times.reserve(numValidations); }, - [&](NodeID const&, Validation const& v) { - if (v.trusted() && v.full()) - times.emplace_back(v.signTime()); - }); - return times; - } - /** Returns fees reported by trusted full validators in the given ledger @param ledgerID The identifier of ledger of interest diff --git a/src/ripple/protocol/STValidation.h b/src/ripple/protocol/STValidation.h index 37382daa3b..f338e99e5a 100644 --- a/src/ripple/protocol/STValidation.h +++ b/src/ripple/protocol/STValidation.h @@ -88,6 +88,7 @@ public: signed before sharing with other nodes. @param ledgerHash The hash of the validated ledger + @param consensusHash The hash of the consensus transaction set @param signTime When the validation is signed @param publicKey The current signing public key @param nodeID ID corresponding to node's public master key @@ -97,6 +98,7 @@ public: STValidation( uint256 const& ledgerHash, + uint256 const& consensusHash, NetClock::time_point signTime, PublicKey const& publicKey, NodeID const& nodeID, @@ -114,9 +116,14 @@ public: return emplace(n, buf, std::move(*this)); } + // Hash of the validated ledger uint256 getLedgerHash() const; + // Hash of consensus transaction set used to generate ledger + uint256 + getConsensusHash() const; + NetClock::time_point getSignTime() const; diff --git a/src/ripple/protocol/impl/STValidation.cpp b/src/ripple/protocol/impl/STValidation.cpp index d1242d7edf..1a09c098f4 100644 --- a/src/ripple/protocol/impl/STValidation.cpp +++ b/src/ripple/protocol/impl/STValidation.cpp @@ -28,6 +28,7 @@ namespace ripple { STValidation::STValidation( uint256 const& ledgerHash, + uint256 const& consensusHash, NetClock::time_point signTime, PublicKey const& publicKey, NodeID const& nodeID, @@ -36,6 +37,7 @@ STValidation::STValidation( { // Does not sign setFieldH256 (sfLedgerHash, ledgerHash); + setFieldH256 (sfConsensusHash, consensusHash); setFieldU32 (sfSigningTime, signTime.time_since_epoch().count()); setFieldVL (sfSigningPubKey, publicKey.slice()); @@ -65,6 +67,11 @@ uint256 STValidation::getLedgerHash () const return getFieldH256 (sfLedgerHash); } +uint256 STValidation::getConsensusHash () const +{ + return getFieldH256 (sfConsensusHash); +} + NetClock::time_point STValidation::getSignTime () const { diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 222f13c175..6388370443 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -378,8 +378,8 @@ public: int i = 0; for (auto const& val : validators) { - auto v = std::make_shared ( - uint256(), roundTime, val, calcNodeID(val), true); + auto v = std::make_shared( + uint256(), uint256(), roundTime, val, calcNodeID(val), true); ++i; STVector256 field (sfAmendments); diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp new file mode 100644 index 0000000000..c51d8ef97b --- /dev/null +++ b/src/test/app/LedgerHistory_test.cpp @@ -0,0 +1,252 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2018 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 + +namespace ripple { +namespace test { + +class LedgerHistory_test : public beast::unit_test::suite +{ +public: + /** Log manager that searches for a specific message substring + */ + class CheckMessageLogs : public Logs + { + std::string msg_; + bool& found_; + + class CheckMessageSink : public beast::Journal::Sink + { + CheckMessageLogs& owner_; + + public: + CheckMessageSink( + beast::severities::Severity threshold, + CheckMessageLogs& owner) + : beast::Journal::Sink(threshold, false), owner_(owner) + { + } + + void + write(beast::severities::Severity level, std::string const& text) + override + { + if (text.find(owner_.msg_) != std::string::npos) + owner_.found_ = true; + } + }; + + public: + /** Constructor + + @param msg The message string to search for + @param found The variable to set to true if the message is found + */ + CheckMessageLogs(std::string msg, bool& found) + : Logs{beast::severities::kDebug} + , msg_{std::move(msg)} + , found_{found} + { + } + + std::unique_ptr + makeSink( + std::string const& partition, + beast::severities::Severity threshold) override + { + return std::make_unique(threshold, *this); + } + }; + + /** Generate a new ledger by hand, applying a specific close time offset + and optionally inserting a transaction. + + If prev is nullptr, then the genesis ledger is made and no offset or + transaction is applied. + + */ + static std::shared_ptr + makeLedger( + std::shared_ptr const& prev, + jtx::Env& env, + LedgerHistory& lh, + NetClock::duration closeOffset, + std::shared_ptr stx = {}) + { + if (!prev) + { + assert(!stx); + return std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().family()); + } + auto res = std::make_shared( + *prev, prev->info().closeTime + closeOffset); + + if (stx) + { + OpenView accum(&*res); + applyTransaction( + env.app(), accum, *stx, false, tapNO_CHECK_SIGN, env.journal); + accum.apply(*res); + } + res->updateSkipList(); + + { + res->stateMap().flushDirty(hotACCOUNT_NODE, res->info().seq); + res->txMap().flushDirty(hotTRANSACTION_NODE, res->info().seq); + } + res->unshare(); + + // Accept ledger + res->setAccepted( + res->info().closeTime, + res->info().closeTimeResolution, + true /* close time correct*/, + env.app().config()); + lh.insert(res, false); + return res; + } + + void + testHandleMismatch() + { + testcase("LedgerHistory mismatch"); + using namespace jtx; + using namespace std::chrono; + + // No mismatch + { + bool found = false; + Env env{*this, + envconfig(), + std::make_unique("MISMATCH ", found)}; + LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; + auto const genesis = makeLedger({}, env, lh, 0s); + uint256 const dummyTxHash{1}; + lh.builtLedger(genesis, dummyTxHash, {}); + lh.validatedLedger(genesis, dummyTxHash); + + BEAST_EXPECT(!found); + } + + // Close time mismatch + { + bool found = false; + Env env{*this, + envconfig(), + std::make_unique( + "MISMATCH on close time", found)}; + LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; + auto const genesis = makeLedger({}, env, lh, 0s); + auto const ledgerA = makeLedger(genesis, env, lh, 4s); + auto const ledgerB = makeLedger(genesis, env, lh, 40s); + + uint256 const dummyTxHash{1}; + lh.builtLedger(ledgerA, dummyTxHash, {}); + lh.validatedLedger(ledgerB, dummyTxHash); + + BEAST_EXPECT(found); + } + + // Prior ledger mismatch + { + bool found = false; + Env env{*this, + envconfig(), + std::make_unique( + "MISMATCH on prior ledger", found)}; + LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; + auto const genesis = makeLedger({}, env, lh, 0s); + auto const ledgerA = makeLedger(genesis, env, lh, 4s); + auto const ledgerB = makeLedger(genesis, env, lh, 40s); + auto const ledgerAC = makeLedger(ledgerA, env, lh, 4s); + auto const ledgerBD = makeLedger(ledgerB, env, lh, 4s); + + uint256 const dummyTxHash{1}; + lh.builtLedger(ledgerAC, dummyTxHash, {}); + lh.validatedLedger(ledgerBD, dummyTxHash); + + BEAST_EXPECT(found); + } + + // Simulate a bug in which consensus may agree on transactions, but + // somehow generate different ledgers + for (bool const txBug : {true, false}) + { + std::string const msg = txBug + ? "MISMATCH with same consensus transaction set" + : "MISMATCH on consensus transaction set"; + bool found = false; + Env env{*this, + envconfig(), + std::make_unique(msg, found)}; + LedgerHistory lh{beast::insight::NullCollector::New(), env.app()}; + + Account alice{"A1"}; + Account bob{"A2"}; + env.fund(XRP(1000), alice, bob); + env.close(); + + auto const ledgerBase = + env.app().getLedgerMaster().getClosedLedger(); + + JTx txAlice = env.jt(noop(alice)); + auto const ledgerA = + makeLedger(ledgerBase, env, lh, 4s, txAlice.stx); + + JTx txBob = env.jt(noop(bob)); + auto const ledgerB = makeLedger(ledgerBase, env, lh, 4s, txBob.stx); + + lh.builtLedger(ledgerA, txAlice.stx->getTransactionID(), {}); + // Simulate the bug by claiming ledgerB had the same consensus hash + // as ledgerA, but somehow generated different ledgers + lh.validatedLedger( + ledgerB, + txBug ? txAlice.stx->getTransactionID() + : txBob.stx->getTransactionID()); + + BEAST_EXPECT(found); + } + } + + void + run() + { + testHandleMismatch(); + } +}; + +BEAST_DEFINE_TESTSUITE(LedgerHistory, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index ce2a38f9ae..fe5094f4bd 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -36,8 +36,8 @@ class RCLValidations_test : public beast::unit_test::suite { testcase("Change validation trusted status"); PublicKey key = derivePublicKey(KeyType::ed25519, randomSecretKey()); - auto v = std::make_shared( - uint256(), NetClock::time_point(), key, calcNodeID(key), true); + auto v = std::make_shared(uint256(), uint256(), + NetClock::time_point(), key, calcNodeID(key), true); BEAST_EXPECT(!v->isTrusted()); v->setTrusted(); diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index 4bb7092b05..4301a3bbd4 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -683,12 +683,10 @@ class Validations_test : public beast::unit_test::suite 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)); } @@ -696,9 +694,6 @@ class Validations_test : public beast::unit_test::suite sorted(harness.vals().fees(id, baseFee)) == sorted(expectedFees)); - BEAST_EXPECT( - sorted(harness.vals().getTrustedValidationTimes(id)) == - sorted(expectedTimes)); } }; diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index 93d6e4de5d..91536d232c 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -549,7 +549,7 @@ struct Peer const bool proposing = mode == ConsensusMode::proposing; const bool consensusFail = result.state == ConsensusState::MovedOn; - TxSet const acceptedTxs = injectTxs(prevLedger, result.set); + TxSet const acceptedTxs = injectTxs(prevLedger, result.txns); Ledger const newLedger = oracle.accept( prevLedger, acceptedTxs.txs(), diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 0935f76333..a65e67b275 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -138,6 +138,9 @@ public: Validation const& unwrap() const { + // For the rippled implementation in which RCLValidation wraps + // STValidation, the csf::Validation has no more specific type it + // wraps, so csf::Validation unwraps to itself return *this; } diff --git a/src/test/unity/app_test_unity1.cpp b/src/test/unity/app_test_unity1.cpp index 374f88dcb9..342cfa05ee 100644 --- a/src/test/unity/app_test_unity1.cpp +++ b/src/test/unity/app_test_unity1.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include