Minor cleanups in offer processing code

This commit is contained in:
Nik Bougalis
2021-08-01 20:36:13 -07:00
parent 7a088a5280
commit c231adf324
11 changed files with 31 additions and 1411 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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