mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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_,
|
||||
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -379,7 +379,7 @@ public:
|
||||
for (auto const& val : validators)
|
||||
{
|
||||
auto v = std::make_shared<STValidation>(
|
||||
uint256(), roundTime, val, calcNodeID(val), true);
|
||||
uint256(), uint256(), roundTime, val, calcNodeID(val), true);
|
||||
|
||||
++i;
|
||||
STVector256 field (sfAmendments);
|
||||
|
||||
252
src/test/app/LedgerHistory_test.cpp
Normal file
252
src/test/app/LedgerHistory_test.cpp
Normal 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user