mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-18 18:15:50 +00:00
Minor cleanups in offer processing code
This commit is contained in:
@@ -478,7 +478,6 @@ target_sources (rippled PRIVATE
|
||||
src/ripple/ledger/impl/BookDirs.cpp
|
||||
src/ripple/ledger/impl/CachedSLEs.cpp
|
||||
src/ripple/ledger/impl/CachedView.cpp
|
||||
src/ripple/ledger/impl/CashDiff.cpp
|
||||
src/ripple/ledger/impl/Directory.cpp
|
||||
src/ripple/ledger/impl/OpenView.cpp
|
||||
src/ripple/ledger/impl/PaymentSandbox.cpp
|
||||
@@ -833,7 +832,6 @@ target_sources (rippled PRIVATE
|
||||
subdir: ledger
|
||||
#]===============================]
|
||||
src/test/ledger/BookDirs_test.cpp
|
||||
src/test/ledger/CashDiff_test.cpp
|
||||
src/test/ledger/Directory_test.cpp
|
||||
src/test/ledger/Invariants_test.cpp
|
||||
src/test/ledger/PaymentSandbox_test.cpp
|
||||
|
||||
@@ -222,8 +222,7 @@ OpenLedger::apply(
|
||||
{
|
||||
try
|
||||
{
|
||||
// Dereferencing the iterator can
|
||||
// throw since it may be transformed.
|
||||
// Dereferencing the iterator can throw since it may be transformed.
|
||||
auto const tx = *iter;
|
||||
auto const txId = tx->getTransactionID();
|
||||
if (check.txExists(txId))
|
||||
@@ -233,9 +232,10 @@ OpenLedger::apply(
|
||||
if (result == Result::retry)
|
||||
retries.insert(tx);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(j.error()) << "Caught exception";
|
||||
JLOG(j.error())
|
||||
<< "OpenLedger::apply: Caught exception: " << e.what();
|
||||
}
|
||||
}
|
||||
bool retry = true;
|
||||
|
||||
@@ -638,11 +638,11 @@ LedgerMaster::getValidatedRange(std::uint32_t& minVal, std::uint32_t& maxVal)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (std::exception&)
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
JLOG(m_journal.error())
|
||||
<< __func__ << " : "
|
||||
<< "Error parsing result of getCompleteLedgers()";
|
||||
JLOG(m_journal.error()) << "LedgerMaster::getValidatedRange: "
|
||||
"exception parsing complete ledgers: "
|
||||
<< e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <ripple/app/paths/Flow.h>
|
||||
#include <ripple/app/tx/impl/CreateOffer.h>
|
||||
#include <ripple/beast/utility/WrappedSink.h>
|
||||
#include <ripple/ledger/CashDiff.h>
|
||||
#include <ripple/ledger/PaymentSandbox.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Quality.h>
|
||||
@@ -120,7 +119,7 @@ CreateOffer::preflight(PreflightContext const& ctx)
|
||||
if (saTakerPays.native() != !uPaysIssuerID ||
|
||||
saTakerGets.native() != !uGetsIssuerID)
|
||||
{
|
||||
JLOG(j.warn()) << "Malformed offer: bad issuer";
|
||||
JLOG(j.debug()) << "Malformed offer: bad issuer";
|
||||
return temBAD_ISSUER;
|
||||
}
|
||||
|
||||
@@ -153,26 +152,24 @@ CreateOffer::preclaim(PreclaimContext const& ctx)
|
||||
if (isGlobalFrozen(ctx.view, uPaysIssuerID) ||
|
||||
isGlobalFrozen(ctx.view, uGetsIssuerID))
|
||||
{
|
||||
JLOG(ctx.j.info()) << "Offer involves frozen asset";
|
||||
|
||||
JLOG(ctx.j.debug()) << "Offer involves frozen asset";
|
||||
return tecFROZEN;
|
||||
}
|
||||
else if (
|
||||
accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <=
|
||||
|
||||
if (accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <=
|
||||
beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "delay: Offers must be at least partially funded.";
|
||||
|
||||
return tecUNFUNDED_OFFER;
|
||||
}
|
||||
|
||||
// This can probably be simplified to make sure that you cancel sequences
|
||||
// before the transaction sequence number.
|
||||
else if (cancelSequence && (uAccountSequence <= *cancelSequence))
|
||||
if (cancelSequence && (uAccountSequence <= *cancelSequence))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "uAccountSequenceNext=" << uAccountSequence
|
||||
<< " uOfferSequence=" << *cancelSequence;
|
||||
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
@@ -188,8 +185,9 @@ CreateOffer::preclaim(PreclaimContext const& ctx)
|
||||
// Note that this will get checked again in applyGuts, but it saves
|
||||
// us a call to checkAcceptAsset and possible false negative.
|
||||
//
|
||||
// The return code change is attached to featureChecks as a convenience.
|
||||
// The change is not big enough to deserve its own amendment.
|
||||
// The return code change is attached to featureDepositPreauth as a
|
||||
// convenience, as the change is not big enough to deserve its own
|
||||
// amendment.
|
||||
return ctx.view.rules().enabled(featureDepositPreauth)
|
||||
? TER{tecEXPIRED}
|
||||
: TER{tesSUCCESS};
|
||||
@@ -226,8 +224,9 @@ CreateOffer::checkAcceptAsset(
|
||||
|
||||
if (!issuerAccount)
|
||||
{
|
||||
JLOG(j.warn()) << "delay: can't receive IOUs from non-existent issuer: "
|
||||
<< to_string(issue.account);
|
||||
JLOG(j.debug())
|
||||
<< "delay: can't receive IOUs from non-existent issuer: "
|
||||
<< to_string(issue.account);
|
||||
|
||||
return (flags & tapRETRY) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER};
|
||||
}
|
||||
@@ -861,248 +860,25 @@ CreateOffer::flowCross(
|
||||
return {tecINTERNAL, takerAmount};
|
||||
}
|
||||
|
||||
enum class SBoxCmp { same, dustDiff, offerDelDiff, xrpRound, diff };
|
||||
|
||||
static std::string
|
||||
to_string(SBoxCmp c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case SBoxCmp::same:
|
||||
return "same";
|
||||
case SBoxCmp::dustDiff:
|
||||
return "dust diffs";
|
||||
case SBoxCmp::offerDelDiff:
|
||||
return "offer del diffs";
|
||||
case SBoxCmp::xrpRound:
|
||||
return "XRP round to zero";
|
||||
case SBoxCmp::diff:
|
||||
return "different";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static SBoxCmp
|
||||
compareSandboxes(
|
||||
char const* name,
|
||||
ApplyContext const& ctx,
|
||||
detail::ApplyViewBase const& viewTaker,
|
||||
detail::ApplyViewBase const& viewFlow,
|
||||
beast::Journal j)
|
||||
{
|
||||
SBoxCmp c = SBoxCmp::same;
|
||||
CashDiff diff = cashFlowDiff(
|
||||
CashFilter::treatZeroOfferAsDeletion,
|
||||
viewTaker,
|
||||
CashFilter::none,
|
||||
viewFlow);
|
||||
|
||||
if (diff.hasDiff())
|
||||
{
|
||||
using namespace beast::severities;
|
||||
// There is a special case of an offer with XRP on one side where
|
||||
// the XRP gets rounded to zero. It mostly looks like dust-level
|
||||
// differences. It is easier to detect if we look for it before
|
||||
// removing the dust differences.
|
||||
if (int const side = diff.xrpRoundToZero())
|
||||
{
|
||||
char const* const whichSide = side > 0 ? "; Flow" : "; Taker";
|
||||
j.stream(kWarning)
|
||||
<< "FlowCross: " << name << " different" << whichSide
|
||||
<< " XRP rounded to zero. tx: " << ctx.tx.getTransactionID();
|
||||
return SBoxCmp::xrpRound;
|
||||
}
|
||||
|
||||
c = SBoxCmp::dustDiff;
|
||||
Severity s = kInfo;
|
||||
std::string diffDesc = ", but only dust.";
|
||||
diff.rmDust();
|
||||
if (diff.hasDiff())
|
||||
{
|
||||
// From here on we want to note the transaction ID of differences.
|
||||
std::stringstream txIdSs;
|
||||
txIdSs << ". tx: " << ctx.tx.getTransactionID();
|
||||
auto txID = txIdSs.str();
|
||||
|
||||
// Sometimes one version deletes offers that the other doesn't
|
||||
// delete. That's okay, but keep track of it.
|
||||
c = SBoxCmp::offerDelDiff;
|
||||
s = kWarning;
|
||||
int sides = diff.rmLhsDeletedOffers() ? 1 : 0;
|
||||
sides |= diff.rmRhsDeletedOffers() ? 2 : 0;
|
||||
if (!diff.hasDiff())
|
||||
{
|
||||
char const* t = "";
|
||||
switch (sides)
|
||||
{
|
||||
case 1:
|
||||
t = "; Taker deleted more offers";
|
||||
break;
|
||||
case 2:
|
||||
t = "; Flow deleted more offers";
|
||||
break;
|
||||
case 3:
|
||||
t = "; Taker and Flow deleted different offers";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
diffDesc = std::string(t) + txID;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A difference without a broad classification...
|
||||
c = SBoxCmp::diff;
|
||||
std::stringstream ss;
|
||||
ss << "; common entries: " << diff.commonCount()
|
||||
<< "; Taker unique: " << diff.lhsOnlyCount()
|
||||
<< "; Flow unique: " << diff.rhsOnlyCount() << txID;
|
||||
diffDesc = ss.str();
|
||||
}
|
||||
}
|
||||
j.stream(s) << "FlowCross: " << name << " different" << diffDesc;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
CreateOffer::cross(Sandbox& sb, Sandbox& sbCancel, Amounts const& takerAmount)
|
||||
{
|
||||
using beast::zero;
|
||||
|
||||
// There are features for Flow offer crossing and for comparing results
|
||||
// between Taker and Flow offer crossing. Turn those into bools.
|
||||
bool const useFlowCross{sb.rules().enabled(featureFlowCross)};
|
||||
bool const doCompare{sb.rules().enabled(featureCompareTakerFlowCross)};
|
||||
if (sb.rules().enabled(featureFlowCross))
|
||||
{
|
||||
PaymentSandbox psbFlow{&sb};
|
||||
PaymentSandbox psbCancelFlow{&sbCancel};
|
||||
auto const ret = flowCross(psbFlow, psbCancelFlow, takerAmount);
|
||||
psbFlow.apply(sb);
|
||||
psbCancelFlow.apply(sbCancel);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Sandbox sbTaker{&sb};
|
||||
Sandbox sbCancelTaker{&sbCancel};
|
||||
auto const takerR = (!useFlowCross || doCompare)
|
||||
? takerCross(sbTaker, sbCancelTaker, takerAmount)
|
||||
: std::make_pair(tecINTERNAL, takerAmount);
|
||||
|
||||
PaymentSandbox psbFlow{&sb};
|
||||
PaymentSandbox psbCancelFlow{&sbCancel};
|
||||
auto const flowR = (useFlowCross || doCompare)
|
||||
? flowCross(psbFlow, psbCancelFlow, takerAmount)
|
||||
: std::make_pair(tecINTERNAL, takerAmount);
|
||||
|
||||
if (doCompare)
|
||||
{
|
||||
SBoxCmp c = SBoxCmp::same;
|
||||
if (takerR.first != flowR.first)
|
||||
{
|
||||
c = SBoxCmp::diff;
|
||||
j_.warn() << "FlowCross: Offer cross tec codes different. tx: "
|
||||
<< ctx_.tx.getTransactionID();
|
||||
}
|
||||
else if (
|
||||
(takerR.second.in == zero && flowR.second.in == zero) ||
|
||||
(takerR.second.out == zero && flowR.second.out == zero))
|
||||
{
|
||||
c = compareSandboxes(
|
||||
"Both Taker and Flow fully crossed",
|
||||
ctx_,
|
||||
sbTaker,
|
||||
psbFlow,
|
||||
j_);
|
||||
}
|
||||
else if (takerR.second.in == zero && takerR.second.out == zero)
|
||||
{
|
||||
char const* crossType =
|
||||
"Taker fully crossed, Flow partially crossed";
|
||||
if (flowR.second.in == takerAmount.in &&
|
||||
flowR.second.out == takerAmount.out)
|
||||
crossType = "Taker fully crossed, Flow not crossed";
|
||||
|
||||
c = compareSandboxes(crossType, ctx_, sbTaker, psbFlow, j_);
|
||||
}
|
||||
else if (flowR.second.in == zero && flowR.second.out == zero)
|
||||
{
|
||||
char const* crossType =
|
||||
"Taker partially crossed, Flow fully crossed";
|
||||
if (takerR.second.in == takerAmount.in &&
|
||||
takerR.second.out == takerAmount.out)
|
||||
crossType = "Taker not crossed, Flow fully crossed";
|
||||
|
||||
c = compareSandboxes(crossType, ctx_, sbTaker, psbFlow, j_);
|
||||
}
|
||||
else if (ctx_.tx.getFlags() & tfFillOrKill)
|
||||
{
|
||||
c = compareSandboxes(
|
||||
"FillOrKill offer", ctx_, sbCancelTaker, psbCancelFlow, j_);
|
||||
}
|
||||
else if (
|
||||
takerR.second.in == takerAmount.in &&
|
||||
flowR.second.in == takerAmount.in &&
|
||||
takerR.second.out == takerAmount.out &&
|
||||
flowR.second.out == takerAmount.out)
|
||||
{
|
||||
char const* crossType = "Neither Taker nor Flow crossed";
|
||||
c = compareSandboxes(crossType, ctx_, sbTaker, psbFlow, j_);
|
||||
}
|
||||
else if (
|
||||
takerR.second.in == takerAmount.in &&
|
||||
takerR.second.out == takerAmount.out)
|
||||
{
|
||||
char const* crossType = "Taker not crossed, Flow partially crossed";
|
||||
c = compareSandboxes(crossType, ctx_, sbTaker, psbFlow, j_);
|
||||
}
|
||||
else if (
|
||||
flowR.second.in == takerAmount.in &&
|
||||
flowR.second.out == takerAmount.out)
|
||||
{
|
||||
char const* crossType = "Taker partially crossed, Flow not crossed";
|
||||
c = compareSandboxes(crossType, ctx_, sbTaker, psbFlow, j_);
|
||||
}
|
||||
else
|
||||
{
|
||||
c = compareSandboxes(
|
||||
"Partial cross offer", ctx_, sbTaker, psbFlow, j_);
|
||||
|
||||
// If we've gotten this far then the returned amounts matter.
|
||||
if (c <= SBoxCmp::dustDiff && takerR.second != flowR.second)
|
||||
{
|
||||
c = SBoxCmp::dustDiff;
|
||||
using namespace beast::severities;
|
||||
Severity s = kInfo;
|
||||
std::string onlyDust = ", but only dust.";
|
||||
if (!diffIsDust(takerR.second.in, flowR.second.in) ||
|
||||
(!diffIsDust(takerR.second.out, flowR.second.out)))
|
||||
{
|
||||
char const* outSame = "";
|
||||
if (takerR.second.out == flowR.second.out)
|
||||
outSame = " but outs same";
|
||||
|
||||
c = SBoxCmp::diff;
|
||||
s = kWarning;
|
||||
std::stringstream ss;
|
||||
ss << outSame
|
||||
<< ". Taker in: " << takerR.second.in.getText()
|
||||
<< "; Taker out: " << takerR.second.out.getText()
|
||||
<< "; Flow in: " << flowR.second.in.getText()
|
||||
<< "; Flow out: " << flowR.second.out.getText()
|
||||
<< ". tx: " << ctx_.tx.getTransactionID();
|
||||
onlyDust = ss.str();
|
||||
}
|
||||
j_.stream(s)
|
||||
<< "FlowCross: Partial cross amounts different" << onlyDust;
|
||||
}
|
||||
}
|
||||
j_.error() << "FlowCross cmp result: " << to_string(c);
|
||||
}
|
||||
|
||||
// Return one result or the other based on amendment.
|
||||
if (useFlowCross)
|
||||
{
|
||||
psbFlow.apply(sb);
|
||||
psbCancelFlow.apply(sbCancel);
|
||||
return flowR;
|
||||
}
|
||||
|
||||
auto const ret = takerCross(sbTaker, sbCancelTaker, takerAmount);
|
||||
sbTaker.apply(sb);
|
||||
sbCancelTaker.apply(sbCancel);
|
||||
return takerR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_LEDGER_CASHDIFF_H_INCLUDED
|
||||
#define RIPPLE_LEDGER_CASHDIFF_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/safe_cast.h>
|
||||
#include <ripple/protocol/STAmount.h>
|
||||
#include <memory> // std::unique_ptr
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class ReadView;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class ApplyStateTable;
|
||||
|
||||
}
|
||||
|
||||
// Used by CashDiff to specify filters applied while processing differences.
|
||||
// Entries are bit flags that can be ANDed and ORed.
|
||||
enum class CashFilter : std::uint8_t {
|
||||
none = 0x0,
|
||||
treatZeroOfferAsDeletion = 0x1
|
||||
};
|
||||
inline CashFilter
|
||||
operator|(CashFilter lhs, CashFilter rhs)
|
||||
{
|
||||
using ul_t = std::underlying_type<CashFilter>::type;
|
||||
return static_cast<CashFilter>(safe_cast<ul_t>(lhs) | safe_cast<ul_t>(rhs));
|
||||
}
|
||||
inline CashFilter
|
||||
operator&(CashFilter lhs, CashFilter rhs)
|
||||
{
|
||||
using ul_t = std::underlying_type<CashFilter>::type;
|
||||
return static_cast<CashFilter>(safe_cast<ul_t>(lhs) & safe_cast<ul_t>(rhs));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// A class to identify differences between two ApplyStateTable instances
|
||||
// for debugging.
|
||||
class CashDiff
|
||||
{
|
||||
public:
|
||||
CashDiff() = delete;
|
||||
CashDiff(CashDiff const&) = delete;
|
||||
CashDiff(CashDiff&& other) noexcept;
|
||||
CashDiff&
|
||||
operator=(CashDiff const&) = delete;
|
||||
~CashDiff();
|
||||
|
||||
CashDiff(
|
||||
ReadView const& view,
|
||||
CashFilter lhsFilter,
|
||||
detail::ApplyStateTable const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
detail::ApplyStateTable const& rhs);
|
||||
|
||||
// Returns the number of cases where lhs and rhs had the same entries
|
||||
// (but not necessarily the same amounts)
|
||||
std::size_t
|
||||
commonCount() const;
|
||||
|
||||
// Returns the number of entries that were present in rhs but not in lhs.
|
||||
std::size_t
|
||||
rhsOnlyCount() const;
|
||||
|
||||
// Returns the number of entries that were present in lhs but not in rhs.
|
||||
std::size_t
|
||||
lhsOnlyCount() const;
|
||||
|
||||
// Returns true is there are any differences to report.
|
||||
bool
|
||||
hasDiff() const;
|
||||
|
||||
// Checks for the XRP round-to-zero case. Returns zero if not detected.
|
||||
// Otherwise returns -1 if seen on lhs, +1 if seen on rhs.
|
||||
//
|
||||
// For tiny offers of TakerPays IOU and TakerGets XRP, cases have been
|
||||
// observed where XRP rounding allows a tiny amount of IOU to be
|
||||
// removed from an Offer while returning no XRP to the offer owner.
|
||||
// That's because the XRP amount was rounded down to zero drops.
|
||||
//
|
||||
// The person submitting the tiny offer does not, however, get something
|
||||
// for nothing. The transaction's fee is significantly larger than the
|
||||
// value of the received IOU.
|
||||
//
|
||||
// This check should be made before calling rmDust().
|
||||
int
|
||||
xrpRoundToZero() const;
|
||||
|
||||
// Remove dust-sized differences. Returns true is dust was removed.
|
||||
bool
|
||||
rmDust();
|
||||
|
||||
// Remove offer deletion differences from a given side. Returns true
|
||||
// if any deleted offers were removed from the differences.
|
||||
bool
|
||||
rmLhsDeletedOffers();
|
||||
bool
|
||||
rmRhsDeletedOffers();
|
||||
|
||||
struct OfferAmounts
|
||||
{
|
||||
static std::size_t constexpr count_ = 2;
|
||||
static std::size_t constexpr count()
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
STAmount amounts[count_];
|
||||
STAmount const&
|
||||
takerPays() const
|
||||
{
|
||||
return amounts[0];
|
||||
}
|
||||
STAmount const&
|
||||
takerGets() const
|
||||
{
|
||||
return amounts[1];
|
||||
}
|
||||
STAmount const&
|
||||
operator[](std::size_t i) const
|
||||
{
|
||||
assert(i < count());
|
||||
return amounts[i];
|
||||
}
|
||||
friend bool
|
||||
operator<(OfferAmounts const& lhs, OfferAmounts const& rhs)
|
||||
{
|
||||
if (lhs[0] < rhs[0])
|
||||
return true;
|
||||
if (lhs[0] > rhs[0])
|
||||
return false;
|
||||
return lhs[1] < rhs[1];
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
// Return true if the difference between two STAmounts is "small".
|
||||
//
|
||||
// If v1 and v2 have different issues, then their difference is never dust.
|
||||
// If v1 < v2, smallness is computed as v1 / (v2 - v1).
|
||||
// The e10 argument says at least how big that ratio must be. Default is 10^6.
|
||||
// If both v1 and v2 are XRP, consider any diff of 2 drops or less to be dust.
|
||||
bool
|
||||
diffIsDust(STAmount const& v1, STAmount const& v2, std::uint8_t e10 = 6);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <ripple/basics/XRPAmount.h>
|
||||
#include <ripple/ledger/ApplyView.h>
|
||||
#include <ripple/ledger/CashDiff.h>
|
||||
#include <ripple/ledger/OpenView.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/ledger/detail/ApplyStateTable.h>
|
||||
@@ -120,13 +119,6 @@ public:
|
||||
void
|
||||
rawDestroyXRP(XRPAmount const& feeDrops) override;
|
||||
|
||||
friend CashDiff
|
||||
cashFlowDiff(
|
||||
CashFilter lhsFilter,
|
||||
ApplyViewBase const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
ApplyViewBase const& rhs);
|
||||
|
||||
protected:
|
||||
ApplyFlags flags_;
|
||||
ReadView const* base_;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/ledger/CashDiff.h>
|
||||
#include <ripple/ledger/detail/ApplyViewBase.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -175,18 +174,5 @@ ApplyViewBase::rawDestroyXRP(XRPAmount const& fee)
|
||||
items_.destroyXRP(fee);
|
||||
}
|
||||
|
||||
//---
|
||||
|
||||
CashDiff
|
||||
cashFlowDiff(
|
||||
CashFilter lhsFilter,
|
||||
ApplyViewBase const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
ApplyViewBase const& rhs)
|
||||
{
|
||||
assert(lhs.base_ == rhs.base_);
|
||||
return CashDiff{*lhs.base_, lhsFilter, lhs.items_, rhsFilter, rhs.items_};
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ripple
|
||||
|
||||
@@ -1,846 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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 <ripple/ledger/CashDiff.h>
|
||||
#include <ripple/ledger/detail/ApplyStateTable.h>
|
||||
#include <ripple/protocol/st.h>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <cassert>
|
||||
#include <cstdlib> // std::abs()
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace detail {
|
||||
|
||||
// Data structure that summarize cash changes in a single ApplyStateTable.
|
||||
struct CashSummary
|
||||
{
|
||||
explicit CashSummary() = default;
|
||||
|
||||
// Sorted vectors. All of the vectors fill in for std::maps.
|
||||
std::vector<std::pair<AccountID, XRPAmount>> xrpChanges;
|
||||
|
||||
std::vector<std::pair<std::tuple<AccountID, AccountID, Currency>, STAmount>>
|
||||
trustChanges;
|
||||
|
||||
std::vector<std::pair<std::tuple<AccountID, AccountID, Currency>, bool>>
|
||||
trustDeletions;
|
||||
|
||||
std::vector<
|
||||
std::pair<std::tuple<AccountID, std::uint32_t>, CashDiff::OfferAmounts>>
|
||||
offerChanges;
|
||||
|
||||
// Note that the OfferAmounts hold the amount *prior* to deletion.
|
||||
std::vector<
|
||||
std::pair<std::tuple<AccountID, std::uint32_t>, CashDiff::OfferAmounts>>
|
||||
offerDeletions;
|
||||
|
||||
bool
|
||||
hasDiff() const
|
||||
{
|
||||
return !xrpChanges.empty() || !trustChanges.empty() ||
|
||||
!trustDeletions.empty() || !offerChanges.empty() ||
|
||||
!offerDeletions.empty();
|
||||
}
|
||||
|
||||
void
|
||||
reserve(size_t newCap)
|
||||
{
|
||||
xrpChanges.reserve(newCap);
|
||||
trustChanges.reserve(newCap);
|
||||
trustDeletions.reserve(newCap);
|
||||
offerChanges.reserve(newCap);
|
||||
offerDeletions.reserve(newCap);
|
||||
}
|
||||
|
||||
void
|
||||
shrink_to_fit()
|
||||
{
|
||||
xrpChanges.shrink_to_fit();
|
||||
trustChanges.shrink_to_fit();
|
||||
trustDeletions.shrink_to_fit();
|
||||
offerChanges.shrink_to_fit();
|
||||
offerDeletions.shrink_to_fit();
|
||||
}
|
||||
|
||||
void
|
||||
sort()
|
||||
{
|
||||
std::sort(xrpChanges.begin(), xrpChanges.end());
|
||||
std::sort(trustChanges.begin(), trustChanges.end());
|
||||
std::sort(trustDeletions.begin(), trustDeletions.end());
|
||||
std::sort(offerChanges.begin(), offerChanges.end());
|
||||
std::sort(offerDeletions.begin(), offerDeletions.end());
|
||||
}
|
||||
};
|
||||
|
||||
// treatZeroOfferAsDeletion()
|
||||
//
|
||||
// Older payment code might set an Offer's TakerPays and TakerGets to
|
||||
// zero and let the offer be cleaned up later. A more recent version
|
||||
// may be more proactive about removing offers. We attempt to paper
|
||||
// over that difference here.
|
||||
//
|
||||
// Two conditions are checked:
|
||||
//
|
||||
// o A modified Offer with both TakerPays and TakerGets set to zero is
|
||||
// added to offerDeletions (not offerChanges).
|
||||
//
|
||||
// o Any deleted offer that was zero before deletion is ignored. It will
|
||||
// have been treated as deleted when the offer was first set to zero.
|
||||
//
|
||||
// The returned bool indicates whether the passed in data was handled.
|
||||
// This allows the caller to avoid further handling.
|
||||
static bool
|
||||
treatZeroOfferAsDeletion(
|
||||
CashSummary& result,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
using beast::zero;
|
||||
|
||||
if (!before)
|
||||
return false;
|
||||
|
||||
auto const& prev = *before;
|
||||
|
||||
if (isDelete)
|
||||
{
|
||||
if (prev.getType() == ltOFFER && prev[sfTakerPays] == zero &&
|
||||
prev[sfTakerGets] == zero)
|
||||
{
|
||||
// Offer was previously treated as deleted when it was zeroed.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// modify
|
||||
if (!after)
|
||||
return false;
|
||||
|
||||
auto const& cur = *after;
|
||||
if (cur.getType() == ltOFFER && cur[sfTakerPays] == zero &&
|
||||
cur[sfTakerGets] == zero)
|
||||
{
|
||||
// Either ignore or treat as delete.
|
||||
auto const oldTakerPays = prev[sfTakerPays];
|
||||
auto const oldTakerGets = prev[sfTakerGets];
|
||||
if (oldTakerPays != zero && oldTakerGets != zero)
|
||||
{
|
||||
result.offerDeletions.push_back(std::make_pair(
|
||||
std::make_tuple(prev[sfAccount], prev[sfSequence]),
|
||||
CashDiff::OfferAmounts{{oldTakerPays, oldTakerGets}}));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
getBasicCashFlow(
|
||||
CashSummary& result,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (isDelete)
|
||||
{
|
||||
if (!before)
|
||||
return false;
|
||||
|
||||
auto const& prev = *before;
|
||||
switch (prev.getType())
|
||||
{
|
||||
case ltACCOUNT_ROOT:
|
||||
result.xrpChanges.push_back(
|
||||
std::make_pair(prev[sfAccount], XRPAmount{0}));
|
||||
return true;
|
||||
|
||||
case ltRIPPLE_STATE:
|
||||
result.trustDeletions.push_back(std::make_pair(
|
||||
std::make_tuple(
|
||||
prev[sfLowLimit].getIssuer(),
|
||||
prev[sfHighLimit].getIssuer(),
|
||||
prev[sfBalance].getCurrency()),
|
||||
false));
|
||||
return true;
|
||||
|
||||
case ltOFFER:
|
||||
result.offerDeletions.push_back(std::make_pair(
|
||||
std::make_tuple(prev[sfAccount], prev[sfSequence]),
|
||||
CashDiff::OfferAmounts{
|
||||
{prev[sfTakerPays], prev[sfTakerGets]}}));
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// insert or modify
|
||||
if (!after)
|
||||
{
|
||||
assert(after);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& cur = *after;
|
||||
switch (cur.getType())
|
||||
{
|
||||
case ltACCOUNT_ROOT: {
|
||||
auto const curXrp = cur[sfBalance].xrp();
|
||||
if (!before || (*before)[sfBalance].xrp() != curXrp)
|
||||
result.xrpChanges.push_back(
|
||||
std::make_pair(cur[sfAccount], curXrp));
|
||||
return true;
|
||||
}
|
||||
case ltRIPPLE_STATE: {
|
||||
auto const curBalance = cur[sfBalance];
|
||||
if (!before || (*before)[sfBalance] != curBalance)
|
||||
result.trustChanges.push_back(std::make_pair(
|
||||
std::make_tuple(
|
||||
cur[sfLowLimit].getIssuer(),
|
||||
cur[sfHighLimit].getIssuer(),
|
||||
curBalance.getCurrency()),
|
||||
curBalance));
|
||||
return true;
|
||||
}
|
||||
case ltOFFER: {
|
||||
auto const curTakerPays = cur[sfTakerPays];
|
||||
auto const curTakerGets = cur[sfTakerGets];
|
||||
if (!before || (*before)[sfTakerGets] != curTakerGets ||
|
||||
(*before)[sfTakerPays] != curTakerPays)
|
||||
{
|
||||
result.offerChanges.push_back(std::make_pair(
|
||||
std::make_tuple(cur[sfAccount], cur[sfSequence]),
|
||||
CashDiff::OfferAmounts{{curTakerPays, curTakerGets}}));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the final cash state from an ApplyStateTable.
|
||||
static CashSummary
|
||||
getCashFlow(ReadView const& view, CashFilter f, ApplyStateTable const& table)
|
||||
{
|
||||
CashSummary result;
|
||||
result.reserve(table.size());
|
||||
|
||||
// Make a container of filters based on the passed in filter flags.
|
||||
using FuncType = decltype(&getBasicCashFlow);
|
||||
boost::container::static_vector<FuncType, 2> filters;
|
||||
|
||||
if ((f & CashFilter::treatZeroOfferAsDeletion) != CashFilter::none)
|
||||
filters.push_back(treatZeroOfferAsDeletion);
|
||||
|
||||
filters.push_back(&getBasicCashFlow);
|
||||
|
||||
auto each = [&result, &filters](
|
||||
uint256 const& key,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after) {
|
||||
auto discarded = std::find_if(
|
||||
filters.begin(),
|
||||
filters.end(),
|
||||
[&result, isDelete, &before, &after](FuncType func) {
|
||||
return func(result, isDelete, before, after);
|
||||
});
|
||||
(void)discarded;
|
||||
};
|
||||
|
||||
table.visit(view, each);
|
||||
result.sort();
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Holds all of the CashDiff-related data.
|
||||
class CashDiff::Impl
|
||||
{
|
||||
private:
|
||||
// Note differences in destroyed XRP between two ApplyStateTables.
|
||||
struct DropsGone
|
||||
{
|
||||
XRPAmount lhs;
|
||||
XRPAmount rhs;
|
||||
};
|
||||
|
||||
ReadView const& view_;
|
||||
|
||||
std::size_t commonKeys_ = 0; // Number of keys common to both rhs and lhs.
|
||||
std::size_t lhsKeys_ = 0; // Number of keys in lhs but not rhs.
|
||||
std::size_t rhsKeys_ = 0; // Number of keys in rhs but not lhs.
|
||||
std::optional<DropsGone> dropsGone_;
|
||||
detail::CashSummary lhsDiffs_;
|
||||
detail::CashSummary rhsDiffs_;
|
||||
|
||||
public:
|
||||
// Constructor.
|
||||
Impl(
|
||||
ReadView const& view,
|
||||
CashFilter lhsFilter,
|
||||
detail::ApplyStateTable const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
detail::ApplyStateTable const& rhs)
|
||||
: view_(view)
|
||||
{
|
||||
findDiffs(lhsFilter, lhs, rhsFilter, rhs);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
commonCount() const
|
||||
{
|
||||
return commonKeys_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
lhsOnlyCount() const
|
||||
{
|
||||
return lhsKeys_;
|
||||
}
|
||||
|
||||
std::size_t
|
||||
rhsOnlyCount() const
|
||||
{
|
||||
return rhsKeys_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasDiff() const
|
||||
{
|
||||
return dropsGone_ != std::nullopt || lhsDiffs_.hasDiff() ||
|
||||
rhsDiffs_.hasDiff();
|
||||
}
|
||||
|
||||
int
|
||||
xrpRoundToZero() const;
|
||||
|
||||
// Filter out differences that are small enough to be in the floating
|
||||
// point noise.
|
||||
bool
|
||||
rmDust();
|
||||
|
||||
// Remove offer deletion differences from a given side
|
||||
bool
|
||||
rmLhsDeletedOffers();
|
||||
bool
|
||||
rmRhsDeletedOffers();
|
||||
|
||||
private:
|
||||
void
|
||||
findDiffs(
|
||||
CashFilter lhsFilter,
|
||||
detail::ApplyStateTable const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
detail::ApplyStateTable const& rhs);
|
||||
};
|
||||
|
||||
// Template function to count difference types in individual CashDiff vectors.
|
||||
// Assumes those vectors are sorted.
|
||||
//
|
||||
// Returned array:
|
||||
// [0] count of keys present in both vectors.
|
||||
// [1] count of keys present in lhs only.
|
||||
// [2] count of keys present in rhs only.
|
||||
template <typename T, typename U>
|
||||
static std::array<std::size_t, 3>
|
||||
countKeys(
|
||||
std::vector<std::pair<T, U>> const& lhs,
|
||||
std::vector<std::pair<T, U>> const& rhs)
|
||||
{
|
||||
std::array<std::size_t, 3> ret{}; // Zero initialize;
|
||||
|
||||
auto lhsItr = lhs.cbegin();
|
||||
auto rhsItr = rhs.cbegin();
|
||||
|
||||
while (lhsItr != lhs.cend() || rhsItr != rhs.cend())
|
||||
{
|
||||
if (lhsItr == lhs.cend())
|
||||
{
|
||||
// rhs has an entry that is not in lhs.
|
||||
ret[2] += 1;
|
||||
++rhsItr;
|
||||
}
|
||||
else if (rhsItr == rhs.cend())
|
||||
{
|
||||
// lhs has an entry that is not in rhs.
|
||||
ret[1] += 1;
|
||||
++lhsItr;
|
||||
}
|
||||
else if (lhsItr->first < rhsItr->first)
|
||||
{
|
||||
// This key is only in lhs.
|
||||
ret[1] += 1;
|
||||
++lhsItr;
|
||||
}
|
||||
else if (rhsItr->first < lhsItr->first)
|
||||
{
|
||||
// This key is only in rhs.
|
||||
ret[2] += 1;
|
||||
++rhsItr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The equivalent key is present in both vectors.
|
||||
ret[0] += 1;
|
||||
++lhsItr;
|
||||
++rhsItr;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Given two CashSummary instances, count the keys. Assumes both
|
||||
// CashSummaries have sorted entries.
|
||||
//
|
||||
// Returned array:
|
||||
// [0] count of keys present in both vectors.
|
||||
// [1] count of keys present in lhs only.
|
||||
// [2] count of keys present in rhs only.
|
||||
static std::array<std::size_t, 3>
|
||||
countKeys(detail::CashSummary const& lhs, detail::CashSummary const& rhs)
|
||||
{
|
||||
std::array<std::size_t, 3> ret{}; // Zero initialize;
|
||||
|
||||
// Lambda to add in new results.
|
||||
auto addIn = [&ret](std::array<std::size_t, 3> const& a) {
|
||||
std::transform(
|
||||
a.cbegin(),
|
||||
a.cend(),
|
||||
ret.cbegin(),
|
||||
ret.begin(),
|
||||
std::plus<std::size_t>());
|
||||
};
|
||||
addIn(countKeys(lhs.xrpChanges, rhs.xrpChanges));
|
||||
addIn(countKeys(lhs.trustChanges, rhs.trustChanges));
|
||||
addIn(countKeys(lhs.trustDeletions, rhs.trustDeletions));
|
||||
addIn(countKeys(lhs.offerChanges, rhs.offerChanges));
|
||||
addIn(countKeys(lhs.offerDeletions, rhs.offerDeletions));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
CashDiff::Impl::xrpRoundToZero() const
|
||||
{
|
||||
// The case has one OfferChange that is present on both lhs_ and rhs_.
|
||||
// That OfferChange should have XRP for TakerGets. There should be a 1
|
||||
// drop difference between the TakerGets of lhsDiffs_ and rhsDiffs_.
|
||||
if (lhsDiffs_.offerChanges.size() != 1 ||
|
||||
rhsDiffs_.offerChanges.size() != 1)
|
||||
return 0;
|
||||
|
||||
if (!lhsDiffs_.offerChanges[0].second.takerGets().native() ||
|
||||
!rhsDiffs_.offerChanges[0].second.takerGets().native())
|
||||
return 0;
|
||||
|
||||
bool const lhsBigger =
|
||||
lhsDiffs_.offerChanges[0].second.takerGets().mantissa() >
|
||||
rhsDiffs_.offerChanges[0].second.takerGets().mantissa();
|
||||
|
||||
detail::CashSummary const& bigger = lhsBigger ? lhsDiffs_ : rhsDiffs_;
|
||||
detail::CashSummary const& smaller = lhsBigger ? rhsDiffs_ : lhsDiffs_;
|
||||
if (bigger.offerChanges[0].second.takerGets().mantissa() -
|
||||
smaller.offerChanges[0].second.takerGets().mantissa() !=
|
||||
1)
|
||||
return 0;
|
||||
|
||||
// The side with the smaller XRP balance in the OfferChange should have
|
||||
// two XRP differences. The other side should have no XRP differences.
|
||||
if (smaller.xrpChanges.size() != 2)
|
||||
return 0;
|
||||
if (!bigger.xrpChanges.empty())
|
||||
return 0;
|
||||
|
||||
// There should be no other differences.
|
||||
if (!smaller.trustChanges.empty() || !bigger.trustChanges.empty() ||
|
||||
!smaller.trustDeletions.empty() || !bigger.trustDeletions.empty() ||
|
||||
!smaller.offerDeletions.empty() || !bigger.offerDeletions.empty())
|
||||
return 0;
|
||||
|
||||
// Return which side exhibited the problem.
|
||||
return lhsBigger ? -1 : 1;
|
||||
}
|
||||
|
||||
// Function that compares two CashDiff::OfferAmounts and returns true if
|
||||
// the difference is dust-sized.
|
||||
static bool
|
||||
diffIsDust(CashDiff::OfferAmounts const& lhs, CashDiff::OfferAmounts const& rhs)
|
||||
{
|
||||
for (auto i = 0; i < lhs.count(); ++i)
|
||||
{
|
||||
if (!diffIsDust(lhs[i], rhs[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Template function to remove dust from individual CashDiff vectors.
|
||||
template <typename T, typename U, typename L>
|
||||
static bool
|
||||
rmVecDust(
|
||||
std::vector<std::pair<T, U>>& lhs,
|
||||
std::vector<std::pair<T, U>>& rhs,
|
||||
L&& justDust)
|
||||
{
|
||||
static_assert(
|
||||
std::is_same<bool, decltype(justDust(lhs[0].second, rhs[0].second))>::
|
||||
value,
|
||||
"Invalid lambda passed to rmVecDust");
|
||||
|
||||
bool dustWasRemoved = false;
|
||||
auto lhsItr = lhs.begin();
|
||||
while (lhsItr != lhs.end())
|
||||
{
|
||||
using value_t = std::pair<T, U>;
|
||||
auto const found = std::equal_range(
|
||||
rhs.begin(),
|
||||
rhs.end(),
|
||||
*lhsItr,
|
||||
[](value_t const& a, value_t const& b) {
|
||||
return a.first < b.first;
|
||||
});
|
||||
|
||||
if (found.first != found.second)
|
||||
{
|
||||
// The same entry changed for both lhs and rhs. Check whether
|
||||
// the differences are small enough to be removed.
|
||||
if (justDust(lhsItr->second, found.first->second))
|
||||
{
|
||||
dustWasRemoved = true;
|
||||
rhs.erase(found.first);
|
||||
// Dodge an invalid iterator by using erase's return value.
|
||||
lhsItr = lhs.erase(lhsItr);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
++lhsItr;
|
||||
}
|
||||
return dustWasRemoved;
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::Impl::rmDust()
|
||||
{
|
||||
bool removedDust = false;
|
||||
|
||||
// Four of the containers can have small (floating point style)
|
||||
// amount differences: xrpChanges, trustChanges, offerChanges, and
|
||||
// offerDeletions. Rifle through those containers and remove any
|
||||
// entries that are _almost_ the same between lhs and rhs.
|
||||
|
||||
// xrpChanges. We call a difference of 2 drops or less dust.
|
||||
removedDust |= rmVecDust(
|
||||
lhsDiffs_.xrpChanges,
|
||||
rhsDiffs_.xrpChanges,
|
||||
[](XRPAmount const& lhs, XRPAmount const& rhs) {
|
||||
return diffIsDust(lhs, rhs);
|
||||
});
|
||||
|
||||
// trustChanges.
|
||||
removedDust |= rmVecDust(
|
||||
lhsDiffs_.trustChanges,
|
||||
rhsDiffs_.trustChanges,
|
||||
[](STAmount const& lhs, STAmount const& rhs) {
|
||||
return diffIsDust(lhs, rhs);
|
||||
});
|
||||
|
||||
// offerChanges.
|
||||
removedDust |= rmVecDust(
|
||||
lhsDiffs_.offerChanges,
|
||||
rhsDiffs_.offerChanges,
|
||||
[](CashDiff::OfferAmounts const& lhs,
|
||||
CashDiff::OfferAmounts const& rhs) { return diffIsDust(lhs, rhs); });
|
||||
|
||||
// offerDeletions.
|
||||
removedDust |= rmVecDust(
|
||||
lhsDiffs_.offerDeletions,
|
||||
rhsDiffs_.offerDeletions,
|
||||
[](CashDiff::OfferAmounts const& lhs,
|
||||
CashDiff::OfferAmounts const& rhs) { return diffIsDust(lhs, rhs); });
|
||||
|
||||
return removedDust;
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::Impl::rmLhsDeletedOffers()
|
||||
{
|
||||
bool const ret = !lhsDiffs_.offerDeletions.empty();
|
||||
if (ret)
|
||||
lhsDiffs_.offerDeletions.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::Impl::rmRhsDeletedOffers()
|
||||
{
|
||||
bool const ret = !rhsDiffs_.offerDeletions.empty();
|
||||
if (ret)
|
||||
rhsDiffs_.offerDeletions.clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Deposits differences between two sorted vectors into a destination.
|
||||
template <typename T>
|
||||
static void
|
||||
setDiff(std::vector<T> const& a, std::vector<T> const& b, std::vector<T>& dest)
|
||||
{
|
||||
dest.clear();
|
||||
std::set_difference(
|
||||
a.cbegin(),
|
||||
a.cend(),
|
||||
b.cbegin(),
|
||||
b.cend(),
|
||||
std::inserter(dest, dest.end()));
|
||||
}
|
||||
|
||||
void
|
||||
CashDiff::Impl::findDiffs(
|
||||
CashFilter lhsFilter,
|
||||
detail::ApplyStateTable const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
detail::ApplyStateTable const& rhs)
|
||||
{
|
||||
// If dropsDestroyed_ is different, note that.
|
||||
if (lhs.dropsDestroyed() != rhs.dropsDestroyed())
|
||||
{
|
||||
dropsGone_ = DropsGone{lhs.dropsDestroyed(), rhs.dropsDestroyed()};
|
||||
}
|
||||
|
||||
// Extract cash flow changes from the state tables
|
||||
auto lhsDiffs = getCashFlow(view_, lhsFilter, lhs);
|
||||
auto rhsDiffs = getCashFlow(view_, rhsFilter, rhs);
|
||||
|
||||
// Get statistics on keys.
|
||||
auto const counts = countKeys(lhsDiffs, rhsDiffs);
|
||||
commonKeys_ = counts[0];
|
||||
lhsKeys_ = counts[1];
|
||||
rhsKeys_ = counts[2];
|
||||
|
||||
// Save only the differences between the results.
|
||||
// xrpChanges:
|
||||
setDiff(lhsDiffs.xrpChanges, rhsDiffs.xrpChanges, lhsDiffs_.xrpChanges);
|
||||
setDiff(rhsDiffs.xrpChanges, lhsDiffs.xrpChanges, rhsDiffs_.xrpChanges);
|
||||
|
||||
// trustChanges:
|
||||
setDiff(
|
||||
lhsDiffs.trustChanges, rhsDiffs.trustChanges, lhsDiffs_.trustChanges);
|
||||
setDiff(
|
||||
rhsDiffs.trustChanges, lhsDiffs.trustChanges, rhsDiffs_.trustChanges);
|
||||
|
||||
// trustDeletions:
|
||||
setDiff(
|
||||
lhsDiffs.trustDeletions,
|
||||
rhsDiffs.trustDeletions,
|
||||
lhsDiffs_.trustDeletions);
|
||||
setDiff(
|
||||
rhsDiffs.trustDeletions,
|
||||
lhsDiffs.trustDeletions,
|
||||
rhsDiffs_.trustDeletions);
|
||||
|
||||
// offerChanges:
|
||||
setDiff(
|
||||
lhsDiffs.offerChanges, rhsDiffs.offerChanges, lhsDiffs_.offerChanges);
|
||||
setDiff(
|
||||
rhsDiffs.offerChanges, lhsDiffs.offerChanges, rhsDiffs_.offerChanges);
|
||||
|
||||
// offerDeletions:
|
||||
setDiff(
|
||||
lhsDiffs.offerDeletions,
|
||||
rhsDiffs.offerDeletions,
|
||||
lhsDiffs_.offerDeletions);
|
||||
setDiff(
|
||||
rhsDiffs.offerDeletions,
|
||||
lhsDiffs.offerDeletions,
|
||||
rhsDiffs_.offerDeletions);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Locates differences between two ApplyStateTables.
|
||||
CashDiff::CashDiff(CashDiff&& other) noexcept : impl_(std::move(other.impl_))
|
||||
{
|
||||
}
|
||||
|
||||
CashDiff::~CashDiff() = default;
|
||||
|
||||
CashDiff::CashDiff(
|
||||
ReadView const& view,
|
||||
CashFilter lhsFilter,
|
||||
detail::ApplyStateTable const& lhs,
|
||||
CashFilter rhsFilter,
|
||||
detail::ApplyStateTable const& rhs)
|
||||
: impl_(new Impl(view, lhsFilter, lhs, rhsFilter, rhs))
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CashDiff::commonCount() const
|
||||
{
|
||||
return impl_->commonCount();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CashDiff::rhsOnlyCount() const
|
||||
{
|
||||
return impl_->rhsOnlyCount();
|
||||
}
|
||||
|
||||
std::size_t
|
||||
CashDiff::lhsOnlyCount() const
|
||||
{
|
||||
return impl_->lhsOnlyCount();
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::hasDiff() const
|
||||
{
|
||||
return impl_->hasDiff();
|
||||
}
|
||||
|
||||
int
|
||||
CashDiff::xrpRoundToZero() const
|
||||
{
|
||||
return impl_->xrpRoundToZero();
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::rmDust()
|
||||
{
|
||||
return impl_->rmDust();
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::rmLhsDeletedOffers()
|
||||
{
|
||||
return impl_->rmLhsDeletedOffers();
|
||||
}
|
||||
|
||||
bool
|
||||
CashDiff::rmRhsDeletedOffers()
|
||||
{
|
||||
return impl_->rmRhsDeletedOffers();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Function that compares two STAmounts and returns true if the difference
|
||||
// is dust-sized.
|
||||
bool
|
||||
diffIsDust(STAmount const& v1, STAmount const& v2, std::uint8_t e10)
|
||||
{
|
||||
// If one value is positive and the other negative then there's something
|
||||
// odd afoot.
|
||||
if (v1 != beast::zero && v2 != beast::zero &&
|
||||
(v1.negative() != v2.negative()))
|
||||
return false;
|
||||
|
||||
// v1 and v2 must be the same Issue for their difference to make sense.
|
||||
if (v1.native() != v2.native())
|
||||
return false;
|
||||
|
||||
if (!v1.native() && (v1.issue() != v2.issue()))
|
||||
return false;
|
||||
|
||||
// If v1 == v2 then the dust is vanishingly small.
|
||||
if (v1 == v2)
|
||||
return true;
|
||||
|
||||
STAmount const& small = v1 < v2 ? v1 : v2;
|
||||
STAmount const& large = v1 < v2 ? v2 : v1;
|
||||
|
||||
// Handling XRP is different from IOU.
|
||||
if (v1.native())
|
||||
{
|
||||
std::uint64_t const s = small.mantissa();
|
||||
std::uint64_t const l = large.mantissa();
|
||||
|
||||
// Always allow a couple of drops of noise.
|
||||
if (l - s <= 2)
|
||||
return true;
|
||||
|
||||
static_assert(sizeof(1ULL) == sizeof(std::uint64_t), "");
|
||||
std::uint64_t const ratio = s / (l - s);
|
||||
static constexpr std::uint64_t e10Lookup[]{
|
||||
1ULL,
|
||||
10ULL,
|
||||
100ULL,
|
||||
1'000ULL,
|
||||
10'000ULL,
|
||||
100'000ULL,
|
||||
1'000'000ULL,
|
||||
10'000'000ULL,
|
||||
100'000'000ULL,
|
||||
1'000'000'000ULL,
|
||||
10'000'000'000ULL,
|
||||
100'000'000'000ULL,
|
||||
1'000'000'000'000ULL,
|
||||
10'000'000'000'000ULL,
|
||||
100'000'000'000'000ULL,
|
||||
1'000'000'000'000'000ULL,
|
||||
10'000'000'000'000'000ULL,
|
||||
100'000'000'000'000'000ULL,
|
||||
1'000'000'000'000'000'000ULL,
|
||||
10'000'000'000'000'000'000ULL,
|
||||
};
|
||||
static std::size_t constexpr maxIndex =
|
||||
sizeof(e10Lookup) / sizeof e10Lookup[0];
|
||||
|
||||
// Make sure the table is big enough.
|
||||
static_assert(
|
||||
std::numeric_limits<std::uint64_t>::max() /
|
||||
e10Lookup[maxIndex - 1] <
|
||||
10,
|
||||
"Table too small");
|
||||
|
||||
if (e10 >= maxIndex)
|
||||
return false;
|
||||
|
||||
return ratio >= e10Lookup[e10];
|
||||
}
|
||||
|
||||
// Non-native. Note that even though large and small may not be equal,
|
||||
// their difference may be zero. One way that can happen is if two
|
||||
// values are different, but their difference results in an STAmount
|
||||
// with an exponent less than -96.
|
||||
STAmount const diff = large - small;
|
||||
if (diff == beast::zero)
|
||||
return true;
|
||||
|
||||
STAmount const ratio = divide(small, diff, v1.issue());
|
||||
STAmount const one(v1.issue(), 1);
|
||||
int const ratioExp = ratio.exponent() - one.exponent();
|
||||
|
||||
return ratioExp >= e10;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
@@ -351,7 +351,6 @@ foreachFeature(FeatureBitset bs, F&& f)
|
||||
|
||||
extern uint256 const featureOwnerPaysFee;
|
||||
extern uint256 const featureFlow;
|
||||
extern uint256 const featureCompareTakerFlowCross;
|
||||
extern uint256 const featureFlowCross;
|
||||
extern uint256 const featureCryptoConditionsSuite;
|
||||
extern uint256 const fix1513;
|
||||
|
||||
@@ -166,7 +166,6 @@ bitsetIndexToFeature(size_t i)
|
||||
uint256 const
|
||||
featureOwnerPaysFee = *getRegisteredFeature("OwnerPaysFee"),
|
||||
featureFlow = *getRegisteredFeature("Flow"),
|
||||
featureCompareTakerFlowCross = *getRegisteredFeature("CompareTakerFlowCross"),
|
||||
featureFlowCross = *getRegisteredFeature("FlowCross"),
|
||||
featureCryptoConditionsSuite = *getRegisteredFeature("CryptoConditionsSuite"),
|
||||
fix1513 = *getRegisteredFeature("fix1513"),
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2016 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 <ripple/beast/unit_test.h>
|
||||
#include <ripple/ledger/CashDiff.h>
|
||||
#include <ripple/protocol/STAmount.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
class CashDiff_test : public beast::unit_test::suite
|
||||
{
|
||||
static_assert(!std::is_default_constructible<CashDiff>{}, "");
|
||||
static_assert(!std::is_copy_constructible<CashDiff>{}, "");
|
||||
static_assert(!std::is_copy_assignable<CashDiff>{}, "");
|
||||
static_assert(std::is_nothrow_move_constructible<CashDiff>{}, "");
|
||||
static_assert(!std::is_move_assignable<CashDiff>{}, "");
|
||||
|
||||
// Exercise diffIsDust (STAmount, STAmount)
|
||||
void
|
||||
testDust()
|
||||
{
|
||||
testcase("diffIsDust (STAmount, STAmount)");
|
||||
|
||||
Issue const usd(Currency(0x5553440000000000), AccountID(0x4985601));
|
||||
Issue const usf(Currency(0x5553460000000000), AccountID(0x4985601));
|
||||
|
||||
// Positive and negative are never dust.
|
||||
expect(!diffIsDust(STAmount{usd, 1}, STAmount{usd, -1}));
|
||||
|
||||
// Different issues are never dust.
|
||||
expect(!diffIsDust(STAmount{usd, 1}, STAmount{usf, 1}));
|
||||
|
||||
// Native and non-native are never dust.
|
||||
expect(!diffIsDust(STAmount{usd, 1}, STAmount{1}));
|
||||
|
||||
// Equal values are always dust.
|
||||
expect(diffIsDust(STAmount{0}, STAmount{0}));
|
||||
{
|
||||
// Test IOU.
|
||||
std::uint64_t oldProbe = 0;
|
||||
std::uint64_t newProbe = 10;
|
||||
std::uint8_t e10 = 1;
|
||||
do
|
||||
{
|
||||
STAmount large(usd, newProbe + 1);
|
||||
STAmount small(usd, newProbe);
|
||||
|
||||
expect(diffIsDust(large, small, e10));
|
||||
expect(diffIsDust(large, small, e10 + 1) == (e10 > 13));
|
||||
|
||||
oldProbe = newProbe;
|
||||
newProbe = oldProbe * 10;
|
||||
e10 += 1;
|
||||
} while (newProbe > oldProbe &&
|
||||
newProbe < std::numeric_limits<std::int64_t>::max());
|
||||
}
|
||||
{
|
||||
// Test XRP.
|
||||
// A delta of 2 or less is always dust.
|
||||
expect(diffIsDust(STAmount{2}, STAmount{0}));
|
||||
|
||||
std::uint64_t oldProbe = 0;
|
||||
std::uint64_t newProbe = 10;
|
||||
std::uint8_t e10 = 0;
|
||||
do
|
||||
{
|
||||
// Differences of 2 of fewer drops are always treated as dust,
|
||||
// so use a delta of 3.
|
||||
STAmount large(newProbe + 3);
|
||||
STAmount small(newProbe);
|
||||
|
||||
expect(diffIsDust(large, small, e10));
|
||||
expect(diffIsDust(large, small, e10 + 1) == (e10 >= 20));
|
||||
|
||||
oldProbe = newProbe;
|
||||
newProbe = oldProbe * 10;
|
||||
e10 += 1;
|
||||
} while (newProbe > oldProbe &&
|
||||
newProbe < std::numeric_limits<std::int64_t>::max());
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testDust();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(CashDiff, ledger, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
Reference in New Issue
Block a user