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.
This commit is contained in:
Brad Chase
2018-03-01 13:22:45 -05:00
committed by seelabs
parent 3a5a6c3637
commit 1507ed66a8
20 changed files with 402 additions and 86 deletions

View File

@@ -4697,6 +4697,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\app\LedgerHistory_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\test\app\LedgerLoad_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>

View File

@@ -5637,6 +5637,9 @@
<ClCompile Include="..\..\src\test\app\HashRouter_test.cpp">
<Filter>test\app</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\app\LedgerHistory_test.cpp">
<Filter>test\app</Filter>
</ClCompile>
<ClCompile Include="..\..\src\test\app\LedgerLoad_test.cpp">
<Filter>test\app</Filter>
</ClCompile>

View File

@@ -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<RCLTxSet>
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<bool(uint256 const&)> 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<STValidation>(
ledger.id(),
txns.id(),
validationTime,
valPublic_,
nodeID_,

View File

@@ -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);
};

View File

@@ -312,9 +312,12 @@ leaves (SHAMap const& sm)
return v;
}
void LedgerHistory::handleMismatch (
void
LedgerHistory::handleMismatch(
LedgerHash const& built,
LedgerHash const& valid,
boost::optional<uint256> const& builtConsensusHash,
boost::optional<uint256> 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<Ledger const> 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<Ledger const> const& ledger)
std::shared_ptr<Ledger const> const& ledger,
boost::optional<uint256> 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

View File

@@ -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<Ledger const>
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<Ledger const> const&,
uint256 const& consensusHash,
Json::Value);
/** Report that we have validated a particular ledger */
void validatedLedger (
std::shared_ptr<Ledger const> const&);
std::shared_ptr<Ledger const> const&,
boost::optional<uint256> 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<uint256> const& builtConsensusHash,
boost::optional<uint256> 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<LedgerHash> built;
// Hash of the validated ledger
boost::optional<LedgerHash> validated;
// Hash of locally accepted consensus transaction set
boost::optional<uint256> builtConsensusHash;
// Hash of validated consensus transaction set
boost::optional<uint256> validatedConsensusHash;
// Consensus metadata of built ledger
boost::optional<Json::Value> consensus;
};
using ConsensusValidated = TaggedCache <LedgerIndex, cv_entry>;

View File

@@ -199,7 +199,11 @@ public:
void checkAccept (std::shared_ptr<Ledger const> const& ledger);
void checkAccept (uint256 const& hash, std::uint32_t seq);
void consensusBuilt (std::shared_ptr<Ledger const> const& ledger, Json::Value consensus);
void
consensusBuilt(
std::shared_ptr<Ledger const> const& ledger,
uint256 const& consensusHash,
Json::Value consensus);
LedgerIndex getBuildingLedger ();
void setBuildingLedger (LedgerIndex index);

View File

@@ -182,12 +182,19 @@ void
LedgerMaster::setValidLedger(
std::shared_ptr<Ledger const> const& l)
{
std::vector <NetClock::time_point> times;
boost::optional<uint256> 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<Ledger const> const& ledger, Json::Value consensus)
std::shared_ptr<Ledger const> 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)
{

View File

@@ -1167,8 +1167,8 @@ Consensus<Adaptor>::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<Adaptor>::updateOurPositions()
parms))
{
if (!mutableSet)
mutableSet.emplace(result_->set);
mutableSet.emplace(result_->txns);
if (it.second.getOurVote())
{
@@ -1352,14 +1352,14 @@ Consensus<Adaptor>::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<Adaptor>::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<Adaptor>::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<Adaptor>::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<Adaptor>::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

View File

@@ -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<Tx_t, NodeID_t>;
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;

View File

@@ -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<NetClock::time_point>
getTrustedValidationTimes(ID const& ledgerID)
{
std::vector<NetClock::time_point> 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

View File

@@ -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;

View File

@@ -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
{

View File

@@ -378,8 +378,8 @@ public:
int i = 0;
for (auto const& val : validators)
{
auto v = std::make_shared <STValidation> (
uint256(), roundTime, val, calcNodeID(val), true);
auto v = std::make_shared<STValidation>(
uint256(), uint256(), roundTime, val, calcNodeID(val), true);
++i;
STVector256 field (sfAmendments);

View File

@@ -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 <BeastConfig.h>
#include <ripple/app/ledger/LedgerHistory.h>
#include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/tx/apply.h>
#include <ripple/beast/insight/NullCollector.h>
#include <ripple/beast/unit_test.h>
#include <ripple/ledger/OpenView.h>
#include <chrono>
#include <memory>
#include <sstream>
#include <test/jtx.h>
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<beast::Journal::Sink>
makeSink(
std::string const& partition,
beast::severities::Severity threshold) override
{
return std::make_unique<CheckMessageSink>(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<Ledger>
makeLedger(
std::shared_ptr<Ledger const> const& prev,
jtx::Env& env,
LedgerHistory& lh,
NetClock::duration closeOffset,
std::shared_ptr<STTx const> stx = {})
{
if (!prev)
{
assert(!stx);
return std::make_shared<Ledger>(
create_genesis,
env.app().config(),
std::vector<uint256>{},
env.app().family());
}
auto res = std::make_shared<Ledger>(
*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<CheckMessageLogs>("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<CheckMessageLogs>(
"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<CheckMessageLogs>(
"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<CheckMessageLogs>(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

View File

@@ -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<STValidation>(
uint256(), NetClock::time_point(), key, calcNodeID(key), true);
auto v = std::make_shared<STValidation>(uint256(), uint256(),
NetClock::time_point(), key, calcNodeID(key), true);
BEAST_EXPECT(!v->isTrusted());
v->setTrusted();

View File

@@ -683,12 +683,10 @@ class Validations_test : public beast::unit_test::suite
sorted(harness.vals().getTrustedForLedger(id)) ==
sorted(expectedValidations));
std::vector<NetClock::time_point> expectedTimes;
std::uint32_t baseFee = 0;
std::vector<uint32_t> 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));
}
};

View File

@@ -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(),

View File

@@ -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;
}

View File

@@ -29,6 +29,7 @@
#include <test/app/Flow_test.cpp>
#include <test/app/Freeze_test.cpp>
#include <test/app/HashRouter_test.cpp>
#include <test/app/LedgerHistory_test.cpp>
#include <test/app/LedgerLoad_test.cpp>
#include <test/app/LoadFeeTrack_test.cpp>
#include <test/app/Manifest_test.cpp>